vegman.dev
Все статьи

RAG простыми словами - и когда он вам не нужен

2026-02-18статья

Полночь, ресепшен, заезжает гость с иностранным паспортом. Администратор на третьей смене в своей жизни открывает внутреннего ассистента и спрашивает: «как оформить регистрацию иностранного гостя». Ассистент отвечает подробно, уверенно и неправильно - он нашел в базе фрагмент регламента про граждан РФ и аккуратно его пересказал.

Это не выдуманная сцена. Я разбирала такой сбой в ассистенте для отельной автоматизации: он отвечает сотрудникам по базе внутренних инструкций и регламентов. Под капотом у него 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 или вы только собираетесь его делать - вот что реально стоит сделать руками.

  • Соберите двадцать настоящих вопросов от тех, кто будет этим пользоваться. Не придуманных вами - настоящих, с их формулировками и опечатками.
  • Прогоните их и смотрите не на ответы, а на найденные фрагменты. Выведите их себе в лог вместе с оценками похожести. Пока вы этого не видите, вы чините вслепую.
  • Отдельно проверьте каждый раздел базы на пустоту: задайте по вопросу из каждой темы и убедитесь, что ни по одной не возвращается ноль фрагментов. Это ловит сбои фильтрации, которые иначе всплывут через месяц у пользователя.
  • Возьмите несколько узких отраслевых терминов и проверьте, находятся ли они. Если нет - вам нужен не другой векторизатор, а добавленный поиск по вхождению слова.
  • Покрутите порог в обе стороны и посмотрите, что меняется: при слишком низком к модели поедет шум, при слишком высоком начнутся ответы «не знаю» там, где ответ есть.
  • Откройте пять случайных чанков и прочитайте их так, будто вы видите текст впервые и без заголовка. Если непонятно, к чему они относятся, - модели тоже будет непонятно. Это сигнал добавить категорию и преамбулу.
  • Найдите в своем списке вопрос, который на самом деле про таблицу («сколько», «сравни», «за период»). Решите его отдельно - фильтром, а не поиском.

Как понять, что получилось

Критерий не «ответы стали красивее». Красиво они звучали и раньше - в этом и опасность.

Проверяйте так:

  • Для каждого контрольного вопроса нужный фрагмент попадает в найденное. Это измеряется отдельно от качества ответа и чинится отдельными средствами. Если фрагмент не найден, разговор про формулировки бессмысленен.
  • Ни один раздел базы не дает ноль. Пустой результат должен быть событием, о котором вы узнаете из логов, а не из жалобы сотрудника.
  • Ответ проверяем по источнику. Возьмите ответ и найдите в процитированных фрагментах ту самую фразу. Если ее там нет - модель дописала от себя, и это лечится инструкцией отвечать строго по контексту.
  • Вы можете назвать виновника любой ошибки. Вот это главный признак зрелости системы: глядя на неверный ответ, вы за минуту говорите - «поиск не нашел» или «нашел верно, но модель переврала». Пока эти два случая для вас неразличимы, вы не отлаживаете систему, а гадаете.

И последнее. Если после всех проверок выяснится, что ваша задача - про таблицы, точные коды или три страницы правил, самый ценный результат этой статьи в том, что вы не станете строить RAG. Это не поражение, это сэкономленные месяцы.