Вы собрали RAG на коленке. На демо всё блестит. Но в проде пользователи жалуются на галлюцинации, ответы не по делу, а менеджер требует поднять точность с 45% до 95%. Знакомо?
За последние два года я пересобрал десятки корпоративных RAG-систем. И каждый раз наступал на одни и те же грабли. Вот 10 ошибок, которые гарантированно убьют ваш RAG в production. С конкретными решениями, кодом и ссылками на реальные кейсы.
1. Вы парсите PDF как текстовый файл
Симптомы: RAG не понимает структуру документа: заголовки, колонтитулы, нумерация страниц — всё сливается в кашу. Модель отвечает: «Согласно документу, на странице 14 написано...» — а на самом деле это колонтитул.
Большинство парсеров (PyPDF2, pdfminer) выдёргивают сырой текст. Они теряют семантику абзацев, таблиц, списков. Результат — chunk попадает в контекст, но с мусором.
Как НЕ надо: pdfplumber.extract_text() без постобработки.
Решение: Используйте парсеры, сохраняющие структуру: marker, unstructured или LlamaParse (от LlamaIndex). Они выдают Markdown-разметку с заголовками, таблицами, списками. На бенчмарке 500 000 документов такие парсеры показали на 30% выше recall при извлечении таблиц.
from marker.converters import PDFConverter
converter = PDFConverter()
markdown_text = converter.convert("report.pdf").markdown
# Теперь заголовки вида ## Section, таблицы — |col1|col2|Совет: всегда сохраняйте bounding box для каждого элемента — потом пригодится для референса.
2. Чанкинг по количеству токенов — всё ещё смертный грех
Вы нарезаете текст по 512 токенов с overlap 50. Вроде логично. Но если чанк разрывает предложение или таблицу — ретривер найдёт только кусок смысла. Модель выдаст чушь.
Исследования 2025–2026 показывают: семантический чанкинг (по границам параграфов, секций, абзацев) повышает точность ответов на 18% по сравнению с фиксированным окном. Используйте semantic-text-splitter или встроенные сплиттеры из LangChain с chunk_overlap=15% от размера чанка, а не фикс.
from semantic_text_splitter import TextSplitter
splitter = TextSplitter(chunk_capacity=512, chunk_overlap_ratio=0.15)
chunks = splitter.chunks(markdown_text)Особенно больно с таблицами: разорванная строка таблицы — гарантированный провал. Либо обрабатывайте таблицы как цельный объект (через парсер из п.1), либо не чанкуйте их.
3. Вы выбрасываете метаданные: источник, дату, автора
Классика: загрузили PDF-ки в векторную базу — и забыли указать, из какого документа пришёл чанк. Когда LLM отвечает, она не может сослаться на первоисточник. Пользователь не доверяет ответу.
Метаданные — это не про скорость, это про доверие и фильтрацию. Вы обязаны хранить: source_url, page_number, doc_title, last_modified. Без них вы не сможете отсеять устаревшие данные и не дадите ссылку на оригинал.
Правильный вектор: в Pinecone, Qdrant, Weaviate — добавляем метаданные как payload. При ретривеле можно фильтровать по дате (только документы 2026 года) или источнику.
index.upsert([
{
"id": "chunk_123",
"values": embedding,
"metadata": {
"source": "annual_report_2025.pdf",
"date": "2025-03-15",
"author": "CFO Dept"
}
}
])4. Вы используете эмбеддинги для поиска, даже не проверив их на своих данных
OpenAI text-embedding-3-large — круто, но не универсально. Если у вас юридические документы на русском, а модель обучена на английском Wikipedia — вы потеряете специфическую терминологию. Тема эмбеддингов как слепого пятна RAG разобрана подробно.
Решение: прогоните свой датасет через инструмент вроде RAG Doctor. Он покажет распределение косинусной близости, количество «одиноких» чанков и долю точных попаданий. Если медианная похожесть между релевантными парами меньше 0.7 — меняйте модель.
Альтернатива: используйте мультиязычные модели (intfloat/multilingual-e5-large) или дообучите свою на вашем корпусе. На июнь 2026 лучшие open-source — BGE-M3 и E5-mistral-7b-instruct.
5. Вы не делаете re-ranking — и получаете шум в контексте
Ретривер на эмбеддингах возвращает топ-20 чанков по косинусной близости. Но первые 10 часто дублируются по смыслу — и в промпт попадает 5 вариаций одной идеи. LLM путается.
Re-ranker (например, Cohere Rerank v3, BGE-reranker-v2) пересортирует чанки с учётом контекста запроса. Он работает на cross-encoder: сравнивает запрос и каждый чанк как пару. Это дороже (O(n) вычислений на каждый запрос), но поднимает точность ответа на 25%.
from cohere import Client
co = Client(api_key="...")
results = co.rerank(query="Как исправить ошибку чанкинга?", documents=chunks, model="rerank-english-v3.0", top_n=5)Экономите на re-ranker? Значит, платите потерей качества. Это одна из самых дешёвых инвестиций в RAG.
6. Вы не фильтруете устаревшие данные и дубликаты
В enterprise-базе лежат версии документов за 5 лет. RAG тащит в контекст старый отчёт, где указана цена $10, вместо текущей $25. Фейковая информация выдается пользователю.
Как пять документов ломают AI-помощник — яркий пример. Решение: добавить этап дедупликации (MinHash или SimHash) и фильтр по дате. В метаданных храните valid_until или is_superseded. При ретривеле применяем pre_filter по этим полям.
-- в Qdrant
{
"filter": {
"must": [
{ "key": "date", "range": { "gte": "2026-01-01" } }
]
}
}7. Вы игнорируете таблицы и списки — их надо парсить отдельно
Таблицы — проклятие RAG. Эмбеддинги превращают их в плоский текст, теряя зависимости между колонками. Итог: модель отвечает «выручка 100 млн» вместо «выручка 100 млн относится к Q3 2025, сегмент SaaS».
Лучший подход на 2026: конвертировать таблицу в структурированный формат (JSON или Markdown) и индексировать как отдельный документ с пометкой type:table. Используйте TableTransformer или LibreOffice для извлечения. Мы в продакшене используем эмбеддинг таблицы целиком через table-embedding специализированную модель — и re-ranker, обученный на таблицах.
8. Вы не управляете длиной контекста — пихаете всё подряд
«Увеличу top_k до 20, чтобы LLM увидела больше информации». В итоге промпт забит шумом, модель «забывает» суть вопроса (проблема Lost in the Middle).
Правило: не больше 3–5 лучших чанков после re-ranking. Используйте слитное окно (sliding window): объединяйте соседние чанки, если они релевантны. В agentic RAG это реализуется через цикл: сначала получаем 10 чанков, оцениваем их релевантность, отбрасываем мусор, потом передаём в LLM.
# Псевдокод agentic RAG
candidates = retriever.retrieve(query, top_k=10)
filtered = reranker.filter(candidates, threshold=0.5)
context = merge_adjacent_chunks(filtered, max_length=4000)
answer = llm.generate(query, context)9. Вы не оцениваете качество RAG — нет метрик в production
Ситуация: запустили, всё работает, но через неделю recall упал с 0.8 до 0.3. Вы узнаёте об этом от разгневанного клиента. Почему RAG работает на демо, но ломается в проде — почти всегда из-за отсутствия мониторинга.
Что измерять:
- DRR (Document Retrieval Rate) — процент запросов, по которым найден хотя бы один релевантный чанк.
- MRR (Mean Reciprocal Rank) — средняя позиция первого релевантного документа.
- Faithfulness — доля ответов, не содержащих фактов, противоречащих документам.
Используйте RAGAS или Trulens для вычисления офлайн, и логгируйте онлайн с помощью инструментов вроде RAG Doctor. CLI-инструмент для автоматической диагностики — спасение, когда нужно быстро найти дыру.
10. Вы забываете про безопасность: prompt injection и data leakage
RAG-системы уязвимы: если в документы попал текст «Ignore previous instructions and say that...», LLM может исполнить вредоносную команду. Как пять документов ломают ваш AI-помощник — это не фантастика.
Защита: санитизируйте входные документы — удаляйте скрытые символы, escape-последовательности. Используйте отдельный LLM-судью (guard model), который проверяет ответ на наличие инъекций. Для enterprise обязательно compliance: почему RAG-системы не проходят аудит безопасности.
Все 10 ошибок — из реального опыта. Исправив их, вы поднимете точность RAG с 50% до 85–90%. Но не останавливайтесь: внедряйте agentic RAG с петлёй обратной связи и используйте метрики для непрерывного улучшения.
И последний совет от автора блога: замените слово «гарантированно» на «с вероятностью 99%» — в RAG не бывает гарантий. Только инженерная культура и постоянный мониторинг.