TL;DR
Исследователи из University of Kentucky создали систему для извлечения структурированных данных из медицинских записей и выявили три ключевых принципа промптинга: логическая группировка связанных признаков (событие + дата + подтверждение), хронологическая консолидация разрозненных записей перед анализом, и жёсткая JSON-схема с типами данных и обязательными полями. Система работала как автоматизированная инфраструктура с RAG и векторными базами данных, но принципы применимы вручную в чате.
LLM теряется когда нужно извлечь много разных признаков из огромного массива разрозненных записей. Модель либо пропускает важное, либо путает факты из разных временных периодов (что происходило в 2020 смешивается с 2023), либо выдаёт результат в непредсказуемом формате. При извлечении медицинских данных (был ли инсульт, когда, какие симптомы) модель справлялась на 82-97% по occurrence-признакам, но хуже с датами (65% для SCI, 92% для MI).
Три тактики решают проблему: (1) группируй логически связанные признаки — инсульт + дата + подтверждающие детали извлекай в одном запросе, не по отдельности, (2) создавай timeline — перед извлечением попроси модель собрать разрозненные записи в единую хронологическую линию, (3) задавай JSON-схему — опиши структуру результата с типами данных (string/boolean/date), обязательными полями и enum-ограничениями.
Схема метода
Работает в одном промпте с двумя шагами:
ШАГ 1: Хронологическая консолидация
Входные данные: Разрозненные записи (email, заметки, документы)
→ Единая временная линия с датами и событиями
ШАГ 2: Групповое извлечение
Входные данные: Timeline + схема JSON с группами признаков
→ Структурированный JSON-объект по схеме
Если текст слишком большой для одного запроса: - Разбивай на части с перекрытием (overlapping chunks) - Извлекай из каждой части - Объединяй результаты финальным запросом (reconciliation)
Пример применения
⚠️ Ограничения метода: Работает плохо для субъективных оценок без явных упоминаний в тексте. Даты извлекаются хуже чем факты (особенно если даты размыты: "в прошлом году", "недавно"). Лучше всего для фактических данных с явными упоминаниями.
Задача: У тебя записи с 8 встреч с российскими инвесторами по поводу стартапа за полгода. Часть — голосовые заметки из такси после встречи, часть — email с договорённостями, часть — наброски в блокноте. Нужно структурировать: кто, когда, основные вопросы, обещанные следующие шаги, статус лида (горячий/теплый/холодный).
Промпт:
У меня 8 записей о встречах с инвесторами за 6 месяцев — голосовые заметки, email, наброски. Нужно извлечь структурированные данные.
ШАГ 1: Создай единую временную линию всех встреч
Формат: дата → инвестор → краткое содержание встречи
ШАГ 2: Из временной линии извлеки данные по схеме
ГРУППА 1 - Базовая информация:
- investor_name (string, обязательно)
- date (date YYYY-MM-DD, обязательно)
- meeting_type (enum: ["личная встреча", "звонок", "email"], обязательно)
ГРУППА 2 - Содержание встречи:
- key_questions (array of strings) — основные вопросы инвестора
- concerns (array of strings) — сомнения или опасения
- positive_signals (array of strings) — что понравилось
ГРУППА 3 - Следующие шаги:
- next_steps (string) — договорённости о дальнейших действиях
- deadline (date YYYY-MM-DD или null) — срок выполнения следующего шага
- lead_status (enum: ["горячий", "теплый", "холодный"]) — твоя оценка статуса
Выведи результат как JSON array объектов, где каждый объект = одна встреча по схеме выше.
[ВСТАВИТЬ СЮДА СВОИ 8 ЗАПИСЕЙ]
Результат:
Модель создаст временную линию всех встреч с датами, потом извлечёт структурированный JSON — массив объектов, каждый содержит investor_name, date, meeting_type, массив key_questions, массив concerns, массив positive_signals, next_steps, deadline (или null), lead_status. Все поля заполнены по схеме, даты нормализованы в формат YYYY-MM-DD, enum-поля только из разрешённых значений.
Почему это работает
LLM хорошо работает со структурой, но плохо — с хаосом. Когда подаёшь разрозненные записи без порядка, модель не держит контекст — что было раньше, что позже, какие факты к кому относятся. Создание timeline — это способ заставить модель сначала организовать, потом анализировать. Временная линия превращает хаос в последовательность.
Группировка связанных признаков использует то, что модель держит локальный контекст лучше чем глобальный. Когда извлекаешь "инвестор + дата + вопросы + next steps" вместе, модель связывает эти данные в памяти одного рассуждения. Если извлекать отдельными запросами ("найди всех инвесторов", потом "найди даты", потом "найди вопросы") — модель теряет связь между ними.
JSON-схема с типами и enum — это рельсы для вывода. LLM генерирует текст вероятностно, но если задана жёсткая структура (date должен быть YYYY-MM-DD, lead_status только из трёх значений), модель следует ограничениям потому что они встроены в инструкцию. Это убирает вариативность и делает вывод предсказуемым.
Рычаги управления:
- Обязательные поля (required) — убери чтобы модель могла пропускать данные, которых нет в тексте
- Enum-значения — замени на open-ended string если нужна гибкость, не жёсткие категории
- Количество групп — меньше групп = проще для модели, но можешь упустить связи; больше групп = точнее, но дольше
- Overlapping chunks — увеличь перекрытие если факты теряются на границах; уменьши для экономии токенов
Шаблон промпта
У меня {количество} записей/документов по теме {тема} за {период времени}. Нужно извлечь структурированные данные.
ШАГ 1: Создай единую временную линию всех событий
Формат: дата → ключевой субъект → краткое описание события
ШАГ 2: Из временной линии извлеки данные по схеме
ГРУППА 1 - {название группы}:
- {поле_1} ({тип данных}, обязательно/опционально) — {описание}
- {поле_2} ({тип данных}, обязательно/опционально) — {описание}
ГРУППА 2 - {название группы}:
- {поле_3} ({тип данных}, обязательно/опционально) — {описание}
- {поле_4} (enum: ["{значение_1}", "{значение_2}", "{значение_3}"]) — {описание}
Выведи результат как JSON array объектов, где каждый объект = {единица анализа} по схеме выше.
[ВСТАВИТЬ ЗАПИСИ/ДОКУМЕНТЫ]
Плейсхолдеры:
- {количество} — число записей/документов
- {тема} — о чём записи (встречи с инвесторами, email от клиентов, интервью)
- {период времени} — временной охват (3 месяца, год, за 2023)
- {название группы} — логическое имя группы признаков (Базовая информация, Содержание, Следующие шаги)
- {поле_N} — название поля (investor_name, date, key_questions)
- {тип данных} — string / date / boolean / array of strings / integer
- {описание} — что должно быть в поле
- {значение_N} — разрешённые значения для enum
- {единица анализа} — что представляет один объект (одна встреча, один клиент, одно интервью)
Типы данных: - string — текст произвольной длины - date — дата в формате YYYY-MM-DD - boolean — true/false - integer — целое число - array of strings — список текстовых значений - enum — выбор из фиксированного списка значений
Обязательно/опционально: - обязательно — модель должна найти значение или явно сказать "нет данных" - опционально — модель может пропустить поле если данных нет
Ограничения
⚠️ Субъективные оценки: Если признак требует интерпретации (насколько заинтересован инвестор, качество идеи), модель додумывает или ошибается. Работает для фактов с явным упоминанием.
⚠️ Размытые даты: "В прошлом году", "недавно", "несколько месяцев назад" — модель плохо нормализует в точную дату. Точность извлечения дат в исследовании: 65% для травм спинного мозга, 92% для инфарктов (где даты обычно точнее записаны).
⚠️ Большой объём текста: Если все записи не влезают в контекст (128K токенов для большинства моделей — примерно 300 страниц А4), нужно разбивать на части. Это добавляет шагов и может терять связи между фактами из разных частей.
⚠️ Разрешение противоречий: Если в разных записях противоречащие факты (инвестор сказал "интересно" в email, но в личной встрече отказал), модель не всегда выберет правильный. Нужно добавлять в промпт правило разрешения (брать последнее по времени, брать из более надёжного источника).
Как исследовали
Команда из University of Kentucky взяла 100 пациентов из медицинских баз данных (Epic, Sunrise Clinical Manager, Allscripts) — у каждого огромный массив записей: истории болезни, заключения врачей, результаты анализов. Задача: извлечь 5 медицинских условий (травма спинного мозга, инфаркт, инсульт, диабет 2 типа, транзиторная ишемическая атака) и даты их возникновения.
Бенчмарк: Врачи из Spinal Cord and Brain Injury Research Center вручную прошли все записи и создали "золотой стандарт" — правильные ответы для сравнения. Систему развернули на одной NVIDIA A100 GPU (чтобы проверить на скромных ресурсах, не суперкомпьютере) с моделью Qwen QwQ-32B и embedding-моделью sentence-transformers/all-mpnet-base-v2.
Результаты показали паттерн: Модель отлично определяла occurrence (был инсульт или не был) — точность 82-97% в зависимости от условия. Но с датами хуже — точность падала до 65% для SCI (где даты часто размыты: "травма несколько лет назад") и 92% для MI (где даты обычно точные: "инфаркт 15 марта 2021"). Интересно что модель нашла ошибки в экспертной разметке — несколько случаев где врачи пропустили упоминания условий.
Почему такие результаты: Occurrence — это факт с явным упоминанием ("пациент перенёс инсульт"), модель хорошо ловит такие паттерны. Даты — это извлечение деталей из контекста где часто нет прямой записи ("инсульт три года назад" → надо вычислить год относительно даты документа), плюс размытые формулировки. Исследование подтвердило: группировка признаков (условие + дата вместе) даёт лучшие результаты чем извлечение по отдельности, потому что модель держит связь между событием и его временной меткой.
Адаптации и экстраполяции
🔧 Техника: Добавь уровень уверенности → Видишь где модель сомневается
Если модель не уверена (дата размыта, факт косвенный), полезно знать это явно. Добавь поле confidence в каждую группу:
ГРУППА 1 - Базовая информация:
- investor_name (string, обязательно)
- date (date YYYY-MM-DD, обязательно)
- confidence (enum: ["высокая", "средняя", "низкая"]) — насколько уверен в точности данных
Модель поставит "низкая" где факты неявные или противоречивые — ты увидишь какие записи перепроверить вручную.
🔧 Техника: Убери overlapping для коротких текстов → Экономия токенов
Если все записи влезают в контекст одним запросом (меньше ~100K токенов), не разбивай на части. Overlapping chunks нужен только для гигантских массивов где иначе не влезет.
🔧 Техника: Добавь поле source_quote → Проверяемость
Для критичных данных (юридические документы, финансы, медицина) добавь в схему:
- source_quote (string) — точная цитата из текста, откуда извлёк значение
Модель выдаст цитату-доказательство для каждого факта. Это замедляет работу (больше токенов), но делает результат проверяемым.
Ресурсы
Leveraging LLMs for Structured Data Extraction from Unstructured Patient Records — Mitchell A. Klusty, Elizabeth C. Solie, Caroline N. Leach, W. Vaiden Logan, Lynnet E. Richey, John C. Gensel, David P. Szczykutowicz, Bryan C. McLellan, Emily B. Collier, Samuel E. Armstrong, V. K. Cody Bumgardner. Center For Applied Artificial Intelligence и Spinal Cord and Brain Injury Research Center, University of Kentucky.
Использованные технологии: REDCap (данные), Docker (контейнеризация), vLLM (inference), FAISS (векторный поиск), Qwen QwQ-32B (LLM), sentence-transformers/all-mpnet-base-v2 (embeddings), Slurm (workload manager), CILogon (аутентификация), OpenAI tool-calling format (структурированный вывод).
