Наш сервис связал в отчёте повышенные лейкоциты с препаратом, который пользователь начал принимать уже после самих анализов. Другому предложил пересдать семь маркеров, сданных два дня назад. Этим вторым был мой папа. Третьему порекомендовал КТ пазух, не зная, что тот неделю назад уже был у ЛОРа с готовым заключением. В отчёте четвёртого всплыл «железодефицит» - там, где про железо не было ни одной цифры.

Самый заметный из этих эпизодов про железодефицит. Я бы, увидев такой вывод, сразу полез проверять данные: точно ли я сдавал железо? Если не сдавал, логичнее увидеть «сдайте ферритин», а не готовый диагноз. Эту ошибку мы быстро поймали и пофиксили. Хуже с тихими случаями. «Сходи на КТ к ЛОРу» или «пересдай кровь, которую сдал в понедельник» пользователь прочитает и удивится: «я же только что был, что за фигня». В этой реакции вся проблема. Такие рекомендации напрямую бьют по доверию к сервису.

Мы вдвоём с партнёром сделали сервис с нуля за два месяца, на готовых нейросетях. Партнёр отвечает за продукт и работу с клиентами, я за всё, что у него внутри. Он читает PDF из любой лаборатории и собирает по нему понятный отчёт за две минуты. Не диагнозы, не назначения. Расскажу четыре случая, где сервис выдал уверенную чушь, и один сценарий, где модель сразу стала рабочим инструментом.

Что пользователь видит и что лежит внутри

Снаружи всё устроено просто. Пользователь загружает PDF из любой лаборатории (Хеликс, Инвитро, KDL, Гемотест) и через пару минут получает отчёт: биовозраст по формуле Levine, разбор по системам организма, план действий на три месяца, бриф для визита к врачу. Внутри стоит пайплайн из нескольких моделей с детерминированными проверками между ними. Половина этих проверок появилась после факапов, которые ниже. На каждом случае дальше увидите, какая именно обвязка вылезла и зачем.

Модель не понимает, что было раньше

Я грузил свои анализы. Январь и февраль, в обоих повышенные лейкоциты: 15, потом 16, потом 13. Сейчас май. С марта я начал принимать препарат (название неважно).

Открываю отчёт. Сервис пишет: «Лейкоциты повышены. Вероятно связано с приёмом препарата.»

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

В первой версии сервиса я не объяснил модели про время отдельным правилом. Считал, что про хронологию она догадается сама, по датам в самих результатах. Не догадалась.

Что переделал. В анкету добавили поле «с какого месяца принимаете препарат и как давно». Сами анализы теперь идут в строгом хронологическом порядке с датами, и для каждого результата сразу пишется, сколько прошло месяцев и принимал ли пользователь тогда уже препарат. Отдельный модуль это нормализует, прежде чем данные попадут в модель.

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

Как именно случилась история с папой

Возвращаюсь к самому первому абзацу статьи. Я тогда уже пару недель допиливал блок рекомендаций. Хотел убедиться, что на пакете из 49 маркеров сервис даёт сравнимое качество с тем, что я вижу глазами. Создал отдельный тестовый аккаунт, загрузил туда анализы своего отца. Представьте теперь обычный сценарий: пользователь сдал полный чек‑ап, и ему интересно, нужно ли что‑то ещё досдать, может, что‑то забыл. Он заходит в раздел рекомендаций, нажимает «подобрать». Сервис открывает результат, а там те самые семь анализов, которые человек только что загрузил.

Причина простая. В инструкции к модели список биомаркеров шёл с датами сдачи, но не было явного правила «не предлагай пересдать то, что человек уже сдавал свежим». Я считал, что модель сама посмотрит на даты и сделает вывод. Не посмотрела и не сделала. Свежие и старые маркеры для неё выглядели одинаково.

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

Пришлось серьёзно переписать инструкцию. Для каждого маркера теперь передаётся срок с последней сдачи и текущее значение. В правила зашили: не предлагать пересдать то, что сдано за последние шесть месяцев. Перед формированием списка модель должна явно выписать всё свежесданное и исключить эти маркеры из срочных рекомендаций. Можно добавлять их в плановые пересдачи через три месяца, но только с явной ссылкой на текущее значение и дату. Три уровня защиты на одну ошибку. После фикса папин кабинет больше не получал «срочно сдайте то, что уже сдано».

Модель отправляет на КТ к врачу, у которого я уже был

Эта история превратилась из неудобной ситуации в новую фичу.

16 марта я был у ЛОРа. Заключение: «Кисты гайморовых пазух с двух сторон, дыханию не мешают, назначен флутиказон на месяц». Это типичный сценарий, когда ты ходишь к врачу не из‑за лабораторного отклонения, а из‑за того, что насморк не проходит третий месяц. Анализы крови про эту историю ничего не знают.

19 мая я пересобираю свой план в сервисе. На тот момент он работал только с лабораторными PDF: загрузил анализы, получил отчёт. План у меня сформировался такой: «Рекомендация: КТ пазух носа и консультация ЛОР‑врача для оценки кист». Сервис вывел это, исходя из тех данных, что у него были: жалобы на синусит в анкете, нормальные анализы, ничего конкретного по ЛОР‑направлению. Логично для модели, у которой нет доступа к моему заключению от 16 марта.

В этот момент я понял главное про продукт. Ему не хватало целого пласта данных, который у пользователя уже есть. Выписки, УЗИ, ЭКГ, заключения врачей человек носит у себя в папке или в почте, а сервис их не запрашивал и обрабатывать не умел. Чинить тут надо было не модель, а сам сервис. Не правило в промпте, а то, что он принимает на вход.

Так в сервисе появилась загрузка медицинских документов. Он расшифровывает их отдельной моделью и подмешивает в общий контекст плана. В правила планировщика добавили: не предлагать визит к специалисту, если визит того же профиля уже был. После этого он перестал отправлять меня на КТ за теми кистами, про которые ЛОР уже всё сказал.

Гипотезы из воздуха

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

В одной из ранних версий отчёта в разделе «возможные причины усталости» я читал: «вероятно у вас железодефицит». Хорошо. Иду смотреть, на что это утверждение опирается. Открываю свои данные: ферритин не сдавал, гемоглобин в норме, трансферрин не сдавал. Из чего железодефицит?

Из ничего. «Железодефицит» это частая причина усталости в обучающих данных модели. Усталость в анкете была. Модель сложила два плюс два и выдала гипотезу, у которой нет ни одной зацепки в моих собственных результатах.

Это галлюцинация, но не та, к которой я привык. Модель ничего не подделала в данных, она подделала вывод из них. Модель отдаёт правдоподобный текст, под который в данных пользователя нет фактов.

Чинил отдельным правилом, можно назвать его «опирайся на данные». Каждая гипотеза в отчёте обязана сослаться на конкретный маркер или симптом из карточки пользователя. Если ссылки нет, отдельная проверка вычёркивает гипотезу до того, как пользователь увидит отчёт. То же самое со ссылками на исследования. Сервис сначала дёргает по API открытую научную базу, получает оттуда реальный список работ по нужному маркеру и отдаёт этот список модели как материал. Модель цитирует уже из него, а не по памяти. Выдуманной ссылке в этой схеме просто неоткуда взяться.

Поворот: а где модель наоборот делает свою работу

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

Это ввод лекарств.

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

Сейчас пользователь пишет одним полем, как привык. Свободным текстом, как сказал бы голосом терапевту:

«Магний цитрат 300 утром, омега-3, Кардеомагнил, Афабозол, Нолипрел.»

Дальше работает модель. Она вытаскивает каждый препарат и раскладывает по полям: название, дозировка, частота приёма. По дороге исправляет опечатки в названиях известных препаратов: «Кардеомагнил» становится «Кардиомагнилом», «Афабозол» становится «Афобазолом».

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

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

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

Что в итоге

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