Что получится, если взять клинические рекомендации на сотню страниц и попросить LLM превратить их в интерактивную модель заболевания? На первый взгляд задача кажется простой. На практике всё оказалось гораздо сложнее.

Последовательность выполнения.
Последовательность выполнения.

Я разрабатываю приложение, который превращает клинические рекомендации в структурированное представление заболевания: граф патогенеза, knowledge graph медицинских и биологических сущностей, временную ленту, несколько способов визуализации одного набора знаний, обучающие материалы и связь каждого утверждения с исходным текстом.

Сгенерированный тест по клинической рекомендации.
Сгенерированный тест по клинической рекомендации.

Проект уже находится на финальной стадии перед релизом, поэтому хочу рассказать не столько об интерфейсе, сколько о том, как я решаю проблему надёжной структурированной генерации медицинских знаний с помощью LLM.

Сгенерированный список заболеваний
Сгенерированный список заболеваний

Чтобы не быть голословным, дальше буду приводить примеры на реальных данных по клиническим рекомендациям по ожирению, которые Минздрав публикует в открытом API клиническая рекомендация Минздрава России или сам документ. Всё, что будет дальше в статье - уже результат работы pipeline приложение поверх этого текста.

Сначала я попробовал сделать всё одним запросом.

Первая версия была максимально простой.

Вызов модели, один prompt и один JSON.
Вызов модели, один prompt и один JSON.

На небольших документах, где итоговый граф укладывался в десяток сущностей и связей, эта схема отрабатывала стабильно, модель успевала уместить весь результат в один ответ, и он получался валидным. Но как только я перешёл на реальные клинические рекомендации в сотни страниц с десятками стадий патогенеза, сущностей и связей между ними, всё начало ломаться сразу по нескольким направлениям, и дальше расскажу, по каким именно.

1. Размер результата. Knowledge graph может содержать десятки сущностей и десятки связей, а у каждой сущности и связи есть обязательные поля, включая информацию об источнике. В результате итоговый JSON становился очень большим и легко приближался к ограничениям на размер ответа модели.

2. Обрыв JSON. Если модель обрывала ответ на середине, мы получали не просто неполный текст, а невалидный структурированный объект. Попросить модель продолжить оказалось не лучшим решением, продолжение уже является отдельной генерацией, и целостность результата приходится восстанавливать дополнительно.

3. Рассинхронизация сущностей и связей. Более интересная проблема появилась внутри самого графа, когда модель одновременно генерирует сущности и связи, она может создать новую сущность прямо во время построения relation. Например, есть сущности E1, E2, E3, а среди связей внезапно появляется E2 → E7 и E7 при этом нигде не определена. Это уже не проблема формата JSON, а проблема целостности самого графа.

4. Семантические галлюцинации. И здесь начинается самое важное. LLM обладает огромным количеством общих медицинских знаний, поэтому модель может построить связь, которая выглядит абсолютно логично с точки зрения биологии, но при этом не подтверждается конкретным документом. Для медицинского приложения это гораздо серьёзнее обычной ошибки генерации.

Поэтому я разделил генерацию на этапы.

Вместо одного большого запроса получился pipeline, где каждый этап отдельный вызов модели с отдельной, узкой задачей

Отдельный вызов модели с отдельной задачей.
Отдельный вызов модели с отдельной задачей.

Это не продолжение одного разговора с моделью, а независимые этапы с заранее определённой структурой входа и выхода, каждый следующий получает уже проверенный результат предыдущего.

1. Graph - сначала макроуровень

Первый этап строит структуру патогенеза стадии заболевания, последовательность процессов, переходы, временные зависимости, клинически значимые события. Здесь модель не пытается одновременно построить весь knowledge graph, её задача только понять структуру развития заболевания.

Например, для ожирения получается цепочка из 13 стадий:

  1. наследственная предрасположенность

  2. факторы среды и образа жизни

  3. энергетический дисбаланс

  4. нарушения оси кишечник-мозг и микробиоты

  5. изменения жировой ткани

  6. хроническое воспаление жировой ткани

  7. инсулинорезистентность

  8. кардиометаболические факторы риска

  9. клиническая картина

  10. ассоциированные заболевания и осложнения

  11. диагностика

  12. лечение

  13. удержание массы и диспансерное наблюдение

2. Entities - сначала фиксируем пространство сущностей

Следующий этап отдельно извлекает сущности, гены, белки, гормоны, клетки, органы, биомаркеры, лекарственные вещества, физиологические процессы. Каждой сущности назначается стабильный code, например

{ “code”: “E17”, “type”: “protein”, “name”: “…” }

После этого появляется важное ограничение, мы сначала создаём закрытый набор допустимых сущностей, а уже потом разрешаем модели строить связи между ними.

Сгенерированный граф знаний
Сгенерированный граф знаний
Увеличенный участок, графа знаний
Увеличенный участок, графа знаний

3. Relations - модель больше не создаёт узлы "на лету"

На этапе построения связей модель получает уже сформированный список code например, E1, E2, E3, E4. Допустимыми становятся связи вроде E1 → E2 или E3 → E2, но E1 → E9 система отклонит, если E9 отсутствует в списке сущностей.

Это кажется простым ограничением, но оно сильно меняет задачу, модель больше не должна одновременно отвечать на два вопроса, какие сущности существуют и какие между ними отношения. Первый вопрос уже решён на предыдущем этапе, остаётся только второй. Так мы уменьшаем пространство допустимых решений перед следующим шагом.

4. Evidence - связь с исходным документом

Существование двух сущностей ещё не доказывает, что связь между ними действительно есть. Поэтому значимые утверждения получают provenance ссылку на исходный текст. Например:

{“entity”: “obesity_genes”,“name”: “Гены регуляции массы тела”,“quote”: “идентифицировано множество генов, кодирующих работу тех или иных звеньев регуляции массы тела и обмена веществ”,“source”: { “section”: “1.2”, “paragraph”: 1, “sentence_start”: 3, “sentence_end”: 3 }}

Подробная информация entity
Подробная информация entity
Продолжение подробной информации entity
Продолжение подробной информации entity

В результате пользователь может пройти цепочку, граф → утверждение → источник → исходный текст. Это одна из ключевых частей системы LLM не должна становиться самостоятельным источником медицинской истины, она используется только для того, чтобы извлечь и структурировать информацию из исходного документа.

Внутренние связи entity
Внутренние связи entity

Достоверность и важность не одно и то же (Confidence ≠ Importance)

Я также разделяю две характеристики, которые легко спутать.

Достоверность (confidence) - насколько непосредственно исходный текст подтверждает утверждение.

Важность (importance) - насколько информация значима для понимания заболевания или клинической практики.

Это разные оси, и одно нельзя автоматически выводить из другого.

Например, в тексте рекомендации онкологические заболевания просто перечислены через запятую среди осложнений ожирения - "СД 2, ССЗ, онкологические заболевания, остеоартрозы и др." без отдельного разбора механизма, достоверность (confidence) средняя (medium), но клинически это одно из самых значимых осложнений, важность (importance) высокая (major).

А роль термогенеза бурой жировой ткани в тексте описана как гипотеза, достоверность (confidence) низкая (low), тип доказательства (evidence_type) гипотеза (hypothesis), и для основной патогенетической цепочки это уже второстепенная деталь, важность (importance) низкая (minor).

Чем отличаются тип доказательства (evidence_type) и уровень доказательности (evidence_level).

Это два разных поля, и путать их не стоит.

Тип доказательства (evidence_type) - собственная оценка модели, как именно исходный текст формулирует утверждение. Возможные значения, установленный факт (established), вероятное утверждение (probable) или гипотеза (hypothesis). Модель определяет это по формулировке в тексте например, "доказано, что..." даёт established, а "предполагается, что..." hypothesis.

Уровень доказательности (evidence_level) - это не оценка модели, а официальная шкала доказательности из самой клинической рекомендации, уровень достоверности доказательств (УДД, от 1 до 5, где 1 самый строгий) и уровень убедительности рекомендаций (УУР, от A до C). Эту шкалу использует Минздрав во всех клинических рекомендациях, и врачам она хорошо знакома. Она уже проставлена в исходном документе для части утверждений, а модель просто извлекает и переносит её в структуру данных, ничего не оценивая сама.

Так что если для утверждения есть уровень доказательности (УДД 1, УУР A) - это значит, что сама клиническая рекомендация относит его к наиболее доказанным. А если поле пустое значит, автор документа не проставил такую оценку для этого конкретного утверждения, и мы полагаемся только на тип доказательства (evidence_type), определённый моделью по формулировке текста.

Валидация, сначала обычный код

Здесь я сознательно не стал сразу решать всё ещё одной LLM. Первый уровень валидации выполняется обычным кодом и проверяет, соответствие JSON схеме, обязательные поля, уникальность ID, корректность from/to, отсутствие дублей и ссылки только на существующие сущности.

То есть система проверяет, что:

E1 → E2 ✅

E1 → E7 ❌

если E7 не существует.

Это может показаться очевидным, но для структурированной генерации такие проверки очень важны. И главное для них вообще не нужна нейросеть. Если ошибку можно гарантированно обнаружить обычным кодом, нет смысла тратить токены на LLM.

Следующий уровень - AI-проверка семантики (AI semantic verification)

Здесь начинается то, что я сейчас уже готов подключать к pipeline, но пока не выдаю за полностью завершённую часть системы. У меня уже есть отдельные prompts для проверки двух разных классов ошибок.

Проверка стадий (stages) и сущностей (entities)

У элементов есть цитата (quote) - дословный фрагмент из источника. AI проверяет:

  • действительно ли цитата (quote) подтверждает описание (description/summary/label);

  • не усилила ли модель утверждение;

  • соответствует ли тип доказательства (evidence_type) и достоверность (confidence) силе цитаты;

  • нет ли в описании деталей, которых нет в источнике.

Например, если источник говорит, "A связано с B", а генерация утверждает: "A вызывает B" источник фиксирует лишь связь между A и B, без утверждения о причине, а генерация уже приписывает этой связи причинно-следственный характер. Это уже не просто структурная ошибка, а семантическое усиление утверждения.

Для таких случаев проверка должна отличать как минимум четыре статуса, подтверждено (supported), усилено (overstated), не подтверждено (unsupported) и несоответствие типа (type_mismatch).

При этом AI получает явное ограничение:

Не додумывай, что должно быть по общей медицине. Оценивай строго соответствие утверждения представленным доказательствам (evidence).

Это принципиально важно. Я не хочу, чтобы проверяющая модель сама начинала использовать свои общие медицинские знания как источник истины.

Проверка связей (relations)

Для связей (relations) сейчас есть другой тип проверки. Модель проверяет внутреннюю согласованность полей, соответствует ли тип связи (relation_type) текстовому названию связи (relation_label), согласуется ли эффект (effect) с типом отношения, не противоречат ли друг другу достоверность (confidence), тип доказательства (evidence_type), уровень доказательности (evidence_level) и важность (importance), и нет ли явно рискованных комбинаций.

Например, relation_type: associated_with вместе с relation_label: "вызывает" - это явное противоречие или наоборот, relation_type: causes вместе с relation_label: "ассоциирован с" - тоже.

Но здесь есть важное ограничение. Если связь содержит только указатель на источник, а не сам текст источника, AI не может честно подтвердить, что конкретная связь действительно присутствует в документе. Поэтому я сознательно разделяю проверку внутренней согласованности и проверку соответствия исходному тексту.

Следующий шаг - проверять связь вместе с исходным текстом (relation + source text).

Это следующий этап, который я подключаю к pipeline. Логика выглядит так: связь (relation) → указатель на источник (source reference) → небольшой фрагмент исходного текста → AI-проверка (AI verification).

Вместо того чтобы снова отправлять модели весь документ на миллионы токенов, backend может достать только нужный фрагмент и несколько соседних предложений. Тогда задача модели становится намного уже, поддерживает ли этот конкретный фрагмент именно это утверждение и именно такой тип связи?

Например, если связь утверждает: "A вызывает B", а источник говорит только, "A связано с B" проверка должна определить, что источник не подтверждает причинность. Это уже гораздо ближе к настоящей проверке доказательств (evidence verification), и именно поэтому я не хочу делать вид, что одна LLM, проверяющая другую LLM, автоматически решает проблему галлюцинаций.

Почему я не считаю LLM окончательным источником истины.

Я не рассчитываю на то, что одна модель сможет построить медицинский граф без ошибок. Поэтому архитектура постепенно движется к такой схеме

И здесь появляется ещё один важный слой, врачебная редактура. Врач должен иметь возможность изменить связь, удалить ошибочную, добавить новую, изменить тип отношения, скорректировать сущность и посмотреть источник изменения. В итоге AI становится не "источником истины", а инструментом, который значительно ускоряет создание первого структурированного слоя знаний, а профессиональная проверка остаётся за специалистом.

Почему это важно при масштабе медицинских знаний

Если смотреть на отдельное заболевание, можно представить ручную работу врача или группы специалистов. Но если говорить о тысячах заболеваний, такой подход становится практически неподъёмным. Именно здесь генерация может дать другой масштаб:

Врач уже не начинает с пустого холста, он получает первый структурированный вариант, который можно проверить и отредактировать. В перспективе я вижу это как профессиональную редактируемую базу медицинских знаний, где AI ускоряет первоначальное построение, а специалисты постепенно проверяют и совершенствуют результат.

Один набор знаний - несколько способов его изучать.

Граф не обязательно является лучшим способом представления информации для любой задачи, поэтому один и тот же набор знаний можно показывать по-разному:

  • Граф (Graph) - для исследования взаимосвязей между сущностями;

  • Путь (Path) - для последовательного прохождения причинно-следственной цепочки: фактор → механизм → изменение → проявление → осложнение;

  • Временная шкала (Timeline) - для понимания развития патологического процесса во времени;

  • Блоки (Blocks) - для изучения отдельных механизмов и компактных групп информации.

Сгенерированная рекомендация в виде пути
Сгенерированная рекомендация в виде пути
Сгенерированная рекомендация в виде блока
Сгенерированная рекомендация в виде блока

Это не четыре разных набора данных, а разные проекции одной модели знаний.

Что видит пользователь

Интерфейс построен на React и React Flow. Пользователь может исследовать общий граф.

Общий граф заболевания "ожирение"
Общий граф заболевания "ожирение"

Выбрать узел (node) и увидеть связанные сущности, открыть детали связи, посмотреть доказательства (evidence), перейти к исходному тексту и увидеть подсветку фрагмента, на котором основано утверждение.

Увеличенный участок сгенерированного графа
Увеличенный участок сгенерированного графа

Генерация проходит поэтапно и отображает прогресс через WebSocket, вместо бесконечного индикатора загрузки, пользователь видит, что именно происходит внутри pipeline.

Почему генерация занимает столько времени

Есть ещё одна инженерная проблема - стоимость. На одном документе полный pipeline в среднем может обработать ~3,2 млн токенов. Генерация может занимать порядка 12-15 минут, а стоимость одного полного построения сейчас составляет примерно 150 ₽ (все зависит от модели и ее ценовой политики), даже с использованием кэширования и оптимизации запросов. Поэтому очевидно, что нельзя гонять полный pipeline (пользователь открыл заболевание → запустили генерацию → 150 ₽) для каждого пользователя.

Поэтому результат генерации становится отдельным артефактом

Вместо этого, клиническая рекомендация → generation pipeline → версионированный knowledge graph → кэш (cache) → и уже из кэша результат раздаётся множеству пользователей. Один раз созданный результат может использоваться сколько угодно раз. Это также позволяет хранить версию исходной клинической рекомендации и версию построенного на её основе графа, а если рекомендация обновилась, можно построить новую версию.

Что я понял в процессе

Главный вывод оказался не в конкретном промпте (prompt), надёжность здесь достигается архитектурой. Вместо цепочки "документ → LLM → огромный JSON" получается документ → структура → сущности → ограниченный домен → связи → доказательства → валидация кодом → AI-проверка семантики → проверка врачом.

Причём не все этапы должны выполняться AI. Если проблему можно решить обычным кодом, её лучше решить обычным кодом. Если требуется понять семантику, тогда уже имеет смысл использовать LLM. А если речь идёт о профессиональной медицинской базе, окончательная ответственность за содержательную корректность должна оставаться на стороне специалиста.

Именно поэтому я не ставлю перед собой задачу "как сделать LLM абсолютно безошибочной" такой задачи, скорее всего, просто не существует. Гораздо реалистичнее, как построить систему, в которой ошибка становится обнаруживаемой, проверяемой и исправляемой.

Итог

Я не рассматриваю LLM как медицинскую истину. Я рассматриваю её как один из компонентов pipeline: LLM извлекает → система ограничивает → код проверяет структуру → AI проверяет семантику → врач проверяет содержание.

В итоге граф становится не заменой клиническим рекомендациям, а интерактивным слоем над ними. Пользователь может не просто увидеть, что "система считает, что A связано с B", а спросить "почему?" и перейти непосредственно к тому месту в исходном документе, на котором основано это утверждение. А врач в дальнейшем сможет не просто использовать готовый граф, но и исправлять, дополнять и развивать его.

Именно такую модель взаимодействия с медицинскими знаниями я сейчас строю в своем приложении.

Комментарии (0)