Прямой ответ
RAG (retrieval-augmented generation) — конвейер из четырёх звеньев, и качество ответа определяется самым слабым: нарезка документов на чанки (размер 300–800 токенов с перекрытием 10–15%, границы по структуре — абзацы/заголовки, не по символам), эмбеддинги (модель под ваш язык и домен; для локального контура — sentence-transformers или Ollama-эмбеддинги), поиск (top-k 5–10 по косинусной близости + метаданные-фильтры: источник, дата, раздел), генерация (промпт: «отвечай только по предоставленным фрагментам, цитируй источники, если ответа нет — скажи»). Типичная ошибка новичков — искать проблему в модели генерации, когда брак происходит в нарезке (чанк разорвал таблицу пополам — поиск нашёл половину, модель ответила по половине). Правило отладки: сначала поиск, потом генерация — покажите релевантный чанк глазами; если его нет в выдаче — никакая модель не спасёт; второй по частоте провал — отсутствие честного «не знаю»: без него модель дофантазирует ответ из общих знаний и выдаст уверенную неправду с вашей же ссылкой.
Нарезка: чанки решают больше, чем модель
Стратегии нарезки по возрастанию аккуратности: фиксированный размер с перекрытием (500 токенов, 50 перекрытие — дефолт, работает сносно), структурная (по заголовкам markdown/разделам — документы с явной структурой режутся по ней: чанк = смысловая единица), рекурсивная (сначала по разделителям верхнего уровня — главы, потом абзацы, потом предложения — LlamaIndex/LangChain реализуют из коробки). Практические правила: чанк должен быть самодостаточным (фрагмент без контекста «см. выше» — при генерации контекста не будет: добавляйте заголовок раздела в начало чанка), таблицы и списки не рвать (разорванная таблица — мусор на входе и на выходе; крупные таблицы — отдельный чанк целиком или текстовое резюме + ссылка), код — по функциям/классам с сигнатурами, перекрытие спасает границы (вопрос про последнее предложение абзаца найдёт соседний чанк). Итерация важнее теории: начните с 500/50, прогоните десяток реальных вопросов, посмотрите выдачу — размер подкрутится под ваши документы быстрее, чем выводится «правильный» из статей.
Эмбеддинги и поиск: где живёт качество выдачи
Выбор эмбеддинг-модели: для русского/многоязычного контента — многоязычные модели (семейство multilingual-e5, bge-m3; бенчмарк MTEB — ориентир, но проверяйте на своих данных); локальный запуск — sentence-transformers на CPU для тысяч документов или Ollama (ollama pull <embedding-model>) в одном контуре с генерацией. Размерность и скорость: 384–1024 измерения — на качестве для типовых объёмов разница меньше, чем кажется; важнее согласованность (индекс и запрос — одна модель, смена модели = полная переиндексация). Поиск: чистая векторная близость misses точные совпадения (артикулы, имена, коды ошибок — «ORA-12154» эмбеддится «похоже» на другие коды); гибрид — вектор + полнотекстовый BM25 с слиянием рангов (RRF) — заметно точнее на технических данных; pgvector + полнотекстовый индекс в одном Postgres закрывает гибрид без новой инфраструктуры (сравнение хранилищ). Метаданные — фильтр до ранжирования (документы отдела X, версия 2.x, последний год): «поиск по всем документам сразу» — частая причина нерелевантной выдачи в больших базах. Top-k и порог: k=5 — старт; близость ниже порога (калибруется глазами) — не тянуть в контекст: нерелевантный чанк хуже пустого (модель станет на него опираться).
Генерация: промпт, источники, честное «не знаю»
Системный промпт генерации — короткий контракт: «Отвечай на вопрос строго по фрагментам ниже. Цитируй источники в формате [1], [2]. Если фрагменты не содержат ответа — ответь: 'В предоставленных документах ответа нет'. Не добавляй знания вне фрагментов.» Фрагменты нумеруются и подаются с заголовками/путями источника (модель цитирует то, что видит: без подписей источников ссылки не появятся). Температура — низкая (0–0.3): фактологическая генерация не требует креативности. «Не знаю» — не косметика, а граница доверия системы: без неё модель ответит на любой вопрос (галлюцинация с цитатой — худший вид лжи: выглядит проверенной). Проверка «не знаю» — в эвалах: вопросы заведомо вне базы должны давать отказ. И размер контекста: 5–10 чанков по 500 токенов — 2.5–5k токенов входа — комфортно для локальных 7–8B моделей (RAM/VRAM-расчёт); раздувание top-k «для надёжности» и качество портит (модель теряется в середине длинного контекста), и денег/времени стоит.
Практический сценарий: база знаний команды за выходные
Задача: ответы по внутренней документации (~200 markdown-страниц). Суббота, этап 1: экспорт документов, нарезка структурная (по заголовкам, лимит 600 токенов, заголовок раздела в префикс чанка) — ~1500 чанков; эмбеддинги — multilingual-модель через sentence-transformers, индекс — Chroma (файловое хранилище, ноль инфраструктуры для старта). Этап 2 — до генерации: десять реальных вопросов коллег в скрипт поиска, выдача глазами: на семи вопросах релевантный чанк в топ-3; провалы — вопросы про процедуры с таблицами (порванная нарезка) — правка: таблицы целиком; вопросы «что изменилось в версии X» — фильтр по метаданным версии. Этап 3: генерация через Ollama (локальная 8B), промпт-контракт выше; проверка честности: три вопроса вне базы — на всех «в документах нет». Воскресенье: FastAPI-эндпоинт (вопрос → поиск → генерация с источниками), минимальный UI. Понедельник — сбор обратной связи: половина вопросов — про свежие страницы (индекс устарел) — cron-переиндексация изменённых файлов по git-diff. Итог: качество упирается в актуальность индекса и нарезку, а не в модель — типичная траектория любого RAG-проекта.
FAQ
RAG или fine-tuning? RAG — для фактов, которые меняются (документация, статусы, цены): обновление = переиндексация, источники цитируемы; fine-tuning — для стиля/формата/доменного языка, не для фактов (модель «запоминает» ненадёжно и устаревает). В продуктовых системах почти всегда начинают с RAG.
Нужна ли отдельная векторная БД для старта? Нет: до сотен тысяч чанков хватает Chroma/FAISS (файловые) или pgvector в существующем Postgres; отдельный Qdrant/Milvus — когда нужны распределённость, фильтрация под высокой нагрузкой, прод-эксплуатация (критерии выбора).
Почему модель отвечает общими словами, игнорируя чанки? Сигналы: чанки нерелевантны (проверить выдачу — звено поиска), промпт не содержит жёсткого контракта («строго по фрагментам»), или контекст переполнен и фрагменты тонут в середине. Диагностика по звеньям: сначала показать промпт модели глазами, потом искать в нём ответ руками — часто оказывается, что и человек не нашёл бы.
Как обновлять индекс без полной переиндексации? Хранить хеш/версию документа в метаданных: изменился файл — удалить его чанки и добавить новые (все векторные БД поддерживают удаление по фильтру метаданных); полная переиндексация — только при смене эмбеддинг-модели.
Источники: документация LlamaIndex, sentence-transformers, Ollama — ссылки в шапке.