Полночь, ресепшен, заезжает гость с иностранным паспортом. Администратор на третьей смене в своей жизни открывает внутреннего ассистента и спрашивает: «как оформить регистрацию иностранного гостя». Ассистент отвечает подробно, уверенно и неправильно — он нашёл в базе фрагмент регламента про граждан РФ и аккуратно его пересказал.
Это не выдуманная сцена. Я разбирала такой сбой в ассистенте для отельной автоматизации: он отвечает сотрудникам по базе внутренних инструкций и регламентов. Под капотом у него RAG — схема, про которую часто пишут, что она «избавляет от галлюцинаций». Она от них не избавляет. Она их перемещает: из головы модели в ваш поиск.
К концу статьи вы будете понимать, что такое RAG, из каких деталей он собран, и — это важнее — как отличить задачу, где он действительно нужен, от задачи, где он только добавит вам три новых способа ошибиться.
Что такое RAG на пальцах: поиск + генерация
Языковая модель знает то, что было в её обучающих данных. Ваших внутренних регламентов там не было. Спросите её про вашу процедуру заселения — она либо честно скажет, что не знает, либо придумает правдоподобный ответ. Второе случается чаще.
Вариантов, по сути, два.
- Дообучить модель на своих документах: дорого, долго, и при каждом изменении регламента процедуру надо повторять.
- Модель не трогать вообще, а перед вопросом найти в своих документах подходящий кусок текста и положить его прямо в запрос: «вот выдержка из инструкции, ответь строго по ней».
Это и есть RAG (retrieval‑augmented generation — «генерация с опорой на найденное»).
Важно с самого начала держать в голове, что RAG — это две независимые ступени:
- Поиск (retrieval). По вопросу пользователя достать из базы несколько наиболее подходящих фрагментов текста.
- Генерация. Отдать модели вопрос вместе с этими фрагментами и попросить сформулировать ответ.
Отсюда главный вывод для новичка, который экономит недели: модель в RAG ничего не «знает» о вашей базе. Она читает только то, что ей положили в запрос прямо сейчас. Если поиск принёс мусор — модель уверенно и красиво перескажет мусор.
Поэтому, когда ассистент отвечает неверно, первый вопрос всегда не «какая у нас модель», а «что нашёл поиск».
flowchart LR
Q["Вопрос"] --> S["Поиск по базе"]
S --> CH["Найденные чанки"]
CH --> PR["Промпт: вопрос + чанки"]
PR --> M["Модель"]
M --> A["Ответ"]
KB[("База знаний")] --- S
KB -.->|"модель её<br/>не видит"| M
S -.->|"нашёл мусор"| A2["Модель уверенно<br/>перескажет мусор"]Из чего он собран на самом деле
Теперь по деталям — на примере той самой отельной базы знаний.
Чанки. Документы нельзя искать целиком: инструкция на двадцать страниц не влезет в запрос к модели, да и большая её часть к вопросу не относится. Поэтому тексты режут на фрагменты — их называют чанками. В нашей базе таких фрагментов около полутора тысяч. Чанк — это и единица поиска, и единица, которую в итоге прочитает модель.
Эмбеддинги. Чтобы искать по смыслу, а не по буквам, каждый чанк превращают в набор чисел — вектор. Это делает отдельная модель‑векторизатор; у нас это GigaEmbeddings. Смысл превращения в том, что тексты, похожие по содержанию, получают близкие наборы чисел, даже если написаны разными словами. Вопрос пользователя прогоняют через ту же модель и ищут чанки, чьи числа оказались ближе всего.
Векторная база. Место, где эти векторы лежат и где по ним быстро ищут. У нас — ChromaDB. По сути это специализированное хранилище, умеющее отвечать на запрос «дай мне k самых близких векторов к вот этому».
Обратите внимание на важную деталь, которую новички почти всегда склеивают: векторизация и генерация — это разные модели и разные этапы. GigaEmbeddings превращает тексты в числа на этапе поиска. GigaChat пишет ответ на этапе генерации. Они не взаимозаменяемы, и менять их можно независимо: смена модели‑генератора не улучшит поиск, а смена векторизатора требует пересчитать всю базу заново.
Порог похожести. Поиск по векторам возвращает результат всегда — даже если в базе нет ничего по теме, вам вернут «наименее непохожее». Поэтому нужен порог: насколько близко должно быть, чтобы фрагмент вообще считался найденным.
У нас фрагменты с оценкой ниже 0.11 отбрасываются совсем, а до модели доходят только те, что выше 0.3. Эти числа подобраны эмпирически, под конкретную базу и конкретный векторизатор. Не переносите их к себе: у вас будут свои.
Гибридный поиск. Изначально поиск был только семантическим — по векторам. Он промахивался на специфических терминах: узкое слово из отраслевого регламента размывается в векторе и теряется среди похожих по общему смыслу текстов. Пришлось добавить обычный поиск по вхождению слова и объединить результаты. Звучит скучно и старомодно, но именно это чинит целый класс ошибок.
Первая честная проблема: похожие тексты не значит правильные
Векторный поиск отвечает на вопрос «что здесь похоже?». А пользователю нужен ответ на вопрос «что здесь правильно?». Это не одно и то же, и вся практическая работа над RAG живёт в этом зазоре.
Два сбоя из реальной эксплуатации.
Сбой первый: ноль фрагментов по целому разделу. По теме регистрации иностранных гостей поиск не возвращал вообще ничего. Не «плохо», а ноль: фильтр по домену документов отсекал нужные фрагменты до того, как до них доходило дело.
Ассистент при этом не молчал — он отвечал общими словами, и со стороны это выглядело как «модель плоховата». Лечилось не моделью, а подстраховкой: если по теме не нашлось ничего, включается запасной поиск без ограничивающего фильтра. И отдельно — регулярной проверкой, что ни один раздел базы не даёт пустоту.
Сбой второй: чанк без контекста врёт. Все фрагменты одного раздела трактовались как относящиеся к иностранцам, хотя часть из них была про граждан РФ. Причина простая: при нарезке фрагмент теряет то, что было понятно из заголовка и соседнего абзаца. Внутри самого куска текста может не быть ни слова о том, к какой категории гостей он относится.
Поиск честно находит похожее, модель честно пересказывает найденное, а ответ неверный. Пришлось при нарезке помечать фрагменты категориями и добавлять к ним поясняющую преамбулу — короткий текст, который возвращает чанку потерянный контекст.
Из двух этих историй складывается вывод, который я считаю главным практическим итогом: качество ответа чаще упирается не в модель, а в то, как нарезаны и размечены документы. Соблазн решать проблему сменой модели огромен — это одна строчка в конфиге. Но починка почти всегда лежит на этапе подготовки данных.
flowchart TB
T["Задача"] --> C1{"Ответ содержится<br/>в связном тексте?"}
C1 -->|"нет: цифры, таблицы"| N1["Запрос к данным,<br/>а не RAG"]
C1 -->|"да"| C2{"Текста много?"}
C2 -->|"нет"| N2["Положить текст<br/>прямо в промпт"]
C2 -->|"да"| C3{"Неизвестно заранее,<br/>где лежит нужное?"}
C3 -->|"нет"| N3["Обычный поиск<br/>по разделу"]
C3 -->|"да"| YES["Это случай для RAG"]Когда RAG не нужен
Теперь то, ради чего всё затевалось. RAG — инструмент для одной конкретной ситуации: ответ содержится в связном тексте, текста много, и заранее неизвестно, в каком именно месте лежит нужное.
Если хотя бы одно из этих условий не выполняется, вы строите сложную конструкцию там, где хватило бы простой.
- Таблицы. Самый частый и самый дорогой промах. Когда на входе таблица, а не текст, RAG работает плохо. Вопросы к таблицам — это «сколько», «у кого больше», «покажи все за период». Такие вопросы решаются фильтрами и группировками, то есть маленькими операциями над данными, а не поиском по смыслу. Векторный поиск найдёт «похожие строки» — но похожие строки не складываются в правильную сумму.
- Точные справочники. Если у сущности есть код, артикул или идентификатор, ищите по нему напрямую. Точное совпадение и быстрее, и надёжнее, и его не надо настраивать порогами.
- Короткие регламенты. Если весь свод правил — несколько страниц, его можно целиком положить модели в запрос. Никакой векторной базы, никакой нарезки, никаких порогов. Меньше деталей — меньше мест, где сломается.
- Один документ на один вопрос. Когда пользователь и так знает, в какой инструкции ответ, вы решаете задачу навигации, а не поиска.
Правило, к которому я пришла: RAG стоит заводить тогда, когда вы уже пробовали обойтись без него и упёрлись. Не наоборот.
Применить у себя
Что проверить на практике
Если у вас уже есть RAG или вы только собираетесь его делать — вот что реально стоит сделать руками.
- Соберите двадцать настоящих вопросов от тех, кто будет этим пользоваться. Не придуманных вами — настоящих, с их формулировками и опечатками.
- Прогоните их и смотрите не на ответы, а на найденные фрагменты. Выведите их себе в лог вместе с оценками похожести. Пока вы этого не видите, вы чините вслепую.
- Отдельно проверьте каждый раздел базы на пустоту: задайте по вопросу из каждой темы и убедитесь, что ни по одной не возвращается ноль фрагментов. Это ловит сбои фильтрации, которые иначе всплывут через месяц у пользователя.
- Возьмите несколько узких отраслевых терминов и проверьте, находятся ли они. Если нет — вам нужен не другой векторизатор, а добавленный поиск по вхождению слова.
- Покрутите порог в обе стороны и посмотрите, что меняется: при слишком низком к модели поедет шум, при слишком высоком начнутся ответы «не знаю» там, где ответ есть.
- Откройте пять случайных чанков и прочитайте их так, будто вы видите текст впервые и без заголовка. Если непонятно, к чему они относятся, — модели тоже будет непонятно. Это сигнал добавить категорию и преамбулу.
- Найдите в своём списке вопрос, который на самом деле про таблицу («сколько», «сравни», «за период»). Решите его отдельно — фильтром, а не поиском.
Как понять, что получилось
Критерий не «ответы стали красивее». Красиво они звучали и раньше — в этом и опасность.
Проверяйте так:
- Для каждого контрольного вопроса нужный фрагмент попадает в найденное. Это измеряется отдельно от качества ответа и чинится отдельными средствами. Если фрагмент не найден, разговор про формулировки бессмысленен.
- Ни один раздел базы не даёт ноль. Пустой результат должен быть событием, о котором вы узнаёте из логов, а не из жалобы сотрудника.
- Ответ проверяем по источнику. Возьмите ответ и найдите в процитированных фрагментах ту самую фразу. Если её там нет — модель дописала от себя, и это лечится инструкцией отвечать строго по контексту.
- Вы можете назвать виновника любой ошибки. Вот это главный признак зрелости системы: глядя на неверный ответ, вы за минуту говорите — «поиск не нашёл» или «нашёл верно, но модель переврала». Пока эти два случая для вас неразличимы, вы не отлаживаете систему, а гадаете.
И последнее. Если после всех проверок выяснится, что ваша задача — про таблицы, точные коды или три страницы правил, самый ценный результат этой статьи в том, что вы не станете строить RAG. Это не поражение, это сэкономленные месяцы.