AI и документы-первоисточники: как заставить LLM не врать
TL;DR. «Галлюцинация» LLM — это симптом, а не диагноз. Диагноз — плохой ретривал и/или плохое цитирование. В статье разбираем, как построить AI-ответы на документах клиента так, чтобы за каждой фразой стояла ссылка на конкретный фрагмент конкретного документа, и чтобы любой инженер мог проверить ответ за 10 секунд. Подход: citation-mandatory generation + hybrid retrieval + structural ingest + хороший eval-сет.
1. Конфликт: «AI всё придумал»
Сцена: внедрили AI-ассистент на документах клиента. Через неделю руководитель пишет: «Он сказал, что в нашем регламенте написано Х. Я открыл регламент, а там нет Х. Что делать?»
Если в этот момент команда отвечает «ну, это галлюцинация LLM, такое бывает, мы поработаем над промптом», проект мёртв. Клиент потерял доверие, и оно обычно не возвращается.
Проблема решаемая, причём архитектурой, а не просьбой к LLM «быть аккуратнее». Ключевая идея: каждая фраза в ответе должна иметь ссылку на конкретный chunk конкретного документа, а ссылка должна быть проверяемой автоматически.
2. Для кого это актуально
- Регулируемые ниши: охрана труда, медицина, юриспруденция, бухгалтерия. Здесь ошибка AI означает реальный ущерб.
- Внутренние базы знаний, где сотрудники должны доверять системе и быстро проверять ответы.
- Customer support, где неправильный ответ оборачивается возвратом, претензией или оттоком.
- Любой проект, где ответ AI рано или поздно перепроверяет человек, а время этой перепроверки умножается на тысячу запросов.
3. Распространённый неправильный подход
- Берут стандартный RAG-шаблон: chunking → embeddings → top-K → LLM.
- Считают, что «LLM умный, он сам разберётся в источниках».
- Получают ответы без цитат. Или цитаты в стиле «согласно документу X», без указания страницы.
- Когда юрист просит «покажи, где именно», ответа нет.
Дополнительные грабли:
- Chunking по символам. Структура документа теряется. Таблицы рвутся пополам.
- Только dense retrieval. Точные коды и идентификаторы не находятся (см. предыдущую статью про Taris § 4.2).
- Промпт «отвечай только из контекста» без post-validation. LLM игнорирует его, когда захочет.
- Нет eval-сета на «отказы». Никто не проверял, отказывается ли система отвечать, когда в источниках ответа нет.
4. Инженерный подход: структурный ингест + citation-mandatory generation
Каким должен быть пайплайн:
flowchart LR RAW[PDF / DOCX / HTML] --> PARSE[Structured parser<br/>page, section, table] PARSE --> CHUNK[Smart chunking<br/>respect structure] CHUNK --> META[Metadata enrichment<br/>page, section, lang] META --> EMB[Embeddings + FTS] EMB --> PG[(Postgres + pgvector)] QUERY[User question] --> HYBRID[Hybrid retrieval<br/>BM25 + dense + RRF] HYBRID --> RERANK[Cross-encoder rerank] RERANK --> PROMPT[Citation-mandatory prompt] PROMPT --> LLM[LLM] LLM --> VALIDATE[Citation validator] VALIDATE -- ok --> ANSWER[Ответ с маркерами<br/>doc:42#p:18#§:4.2] VALIDATE -- fail --> RETRY[Retry с top-3 force-fed] RETRY --> VALIDATE
Ключевые элементы:
4.1. Структурный парсер
Не «PDF → текст», а «PDF → последовательность (page, section, char_range, text, table?, image?)». Для этого:
- PDF:
pdfplumberилиpymupdfс сохранением координат. - DOCX:
python-docxс проходом по style runs. - Сложные документы со сканами: OCR (Tesseract или PaddleOCR) с language detection.
Обработка занимает ~2–4 секунды на страницу, и на цитировании это окупается.
4.2. Умный chunking
Правила, которые мы применяем:
- Не рвать середину предложения.
- Не рвать таблицу.
- Если секция короткая (< 500 токенов), вся она становится одним chunk-ом.
- Если длинная, используем sliding window 400 токенов с overlap 80, и окно сбрасывается на границе секции.
- Каждый chunk несёт метаданные:
{page, section, parent_section, lang, source_uri}.
Эти метаданные потом становятся видимой частью цитаты.
4.3. Citation-mandatory prompt
Шаблон (упрощённо):
Ты отвечаешь только на основе фрагментов ниже.
Каждое утверждение в ответе ДОЛЖНО заканчиваться маркером
вида [doc:ID#chunk:N].
Если во фрагментах нет ответа, ответь
"В предоставленных источниках ответа нет."
Не добавляй фактов, которых нет в фрагментах.
Фрагменты:
[doc:42#chunk:7] (страница 18, §4.2) ... текст ...
[doc:42#chunk:8] (страница 19, §4.3) ... текст ...
...
Вопрос: {question}
4.4. Post-validation
После генерации запускается простой код:
def validate_citations(answer: str, allowed_chunk_ids: set[str]) -> bool:
sentences = split_sentences(answer)
for s in sentences:
markers = re.findall(r"\[doc:(\d+)#chunk:(\d+)\]", s)
if not markers:
return False # фраза без цитаты
for doc_id, chunk_id in markers:
if f"{doc_id}#{chunk_id}" not in allowed_chunk_ids:
return False # цитата на чужой источник
return True
Если валидация не прошла, следует повторный вызов LLM с top-3 chunks force-fed и более строгим промптом. Если не помогло и это, система отвечает «В предоставленных источниках ответа нет». Это feature, а не bug.
5. Таблица: критерии «настоящего» цитирования
| Критерий | Слабо | Хорошо | Отлично |
|---|---|---|---|
| Указан документ | «согласно регламенту» | «согласно регламенту X» | doc-id + название |
| Указана страница | нет | в конце абзаца | у каждой фразы |
| Указан раздел | нет | глава | глава + параграф |
| Цитата проверяема автоматически | нет | regex | machine-readable + hyperlink |
| Reject при отсутствии источника | нет | иногда | всегда |
| Eval-сет на reject | нет | 5–10 вопросов | 30+ probe-questions |
6. SINTARIS mini-case
Worksafety Superassistant — наш самый «тяжёлый» по требованиям к цитированию деплой. Корпус: ~3000 страниц нормативных документов по охране труда плюс внутренние SOP клиента.
Что сделали:
- Структурный парсер с сохранением (page, section, paragraph).
- Hybrid retrieval с приоритетом BM25 (регламенты насыщены ключевыми словами).
- Citation-mandatory prompt + validator.
- Probe-set из 30 «вопросов-ловушек» (например, вопрос о нормах PPE для подводной сварки на высоте: такой нормы не существует, и ожидаемый ответ звучит как «не найдено»).
- Eval-сет на 120 реальных вопросов с эталонными цитатами.
Метрики через 6 месяцев работы:
- Citation accuracy (цитата ведёт на chunk, который реально подтверждает фразу): 94%.
- Refusal correctness (доля правильных отказов на probe-set): 96%.
- Median latency: 2.1 сек (включая rerank).
- Жалоб от safety officers за квартал: 0.
Подробности описаны в главах Worksafety § 6 RAG pipeline и Architecture patterns § 2 Citation-mandatory generation.
7. Чек-лист (15 пунктов) для citation-mandatory RAG
- Парсер сохраняет структуру (page, section, paragraph), а не складывает «весь PDF одним блобом».
- Chunking учитывает структуру и не рвёт предложения и таблицы.
- Метаданные (page, section) сохраняются в каждом chunk-е.
- Hybrid retrieval (BM25 + dense + RRF), а не только embeddings.
- Cross-encoder rerank: top-12 → top-5.
- Prompt template требует маркеров цитат.
- Post-validation проверяет наличие маркеров.
- Refusal pattern: ответ «в источниках нет» как полноправный вариант поведения.
- Eval-сет минимум на 30 вопросов с эталонными цитатами.
- Probe-сет на отказы: минимум 20 вопросов, на которые нельзя ответить.
- Citation accuracy измеряется и выводится в Grafana, а не проверяется «иногда вручную».
- Refusal rate измеряется отдельно от accuracy.
- Drift monitor раз в неделю проверяет, не падают ли метрики.
- Threshold для retrieval: если top-1 score < X, система отказывает.
- Hyperlinks в ответах: пользователь кликает и видит исходный документ.
8. Риски
- Latency. Cross-encoder rerank + validation добавляют 200–400 ms. Это нормально для бизнес-сценариев, плохо для чата real-time. Помогает кеширование частых запросов.
- «Слишком осторожный». Если порог слишком высокий, система часто отвечает «не знаю». Лечится калибровкой на eval-сете.
- Структурный парсинг сложных PDF. Сканированные таблицы — это до сих пор боль. Бюджетируйте время на OCR-проверку.
- Multi-language citations. Если нормативные документы на немецком, а ответ на словенском, citation labels должны быть language-stable. Лучше использовать numeric IDs, а не «§4.2».
9. Что делать дальше
Если у вас уже есть RAG-пилот, который иногда «врёт», начните с двух действий:
- Включите citation-mandatory prompt + validator.
- Сделайте probe-сет на 20 вопросов, на которые ответа в корпусе нет, и измерьте refusal correctness.
Часто этих двух действий хватает, чтобы поднять качество с «иногда правильно» до «всегда либо правильно, либо честный отказ».
Если хотите пройти полный цикл, AI-Pilot за 4–8 недель включает построение citation-mandatory RAG как стандартный паттерн. Объём и стоимость согласуем после вводного звонка.
10. Источники
- Lewis, P. et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401.
- Karpukhin, V. et al. (2020). Dense Passage Retrieval for Open-Domain Question Answering. arXiv:2004.04906.
- Cormack, G., Clarke, C., Buettcher, S. (2009). Reciprocal Rank Fusion outperforms Condorcet and individual rank learning methods. SIGIR '09.
- Liu, N. F. et al. (2023). Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172.
- Es, S. et al. (2024). RAGAS: Automated Evaluation of Retrieval-Augmented Generation. arXiv:2309.15217.
- pdfplumber — https://github.com/jsvine/pdfplumber
- PaddleOCR — https://github.com/PaddlePaddle/PaddleOCR
- Внутренняя KB SINTARIS: Worksafety Superassistant, Architecture patterns § 2 Citation-mandatory generation, n8n + RAG patterns § 6 RAG node pattern.
SINTARIS строит RAG-системы с проверяемыми цитатами для бизнеса в ЕС. Вводный звонок бесплатный, 30 минут.