Зачем и для кого статья?

  • Для тех, кто хочет сделать своего ИИ-помощника, удобный поисковик.

  • Кому интересна тема RAG в целом.

  • Кто хочет понять, как это всё работает изнутри, на живом примере, а не на схеме из учебника.

Здесь будет

  • Личный опыт и точка зрения автора, грабли и находки.

  • Путь наименьшего сопротивления.

  • С чем Вы, скорее всего, столкнётесь и как это решается.

  • Вода и мысли.

Не будет

  • Пересказа тысячи статей о том, что такое RAG и как вектора "отражают смысл". Заголовок некликбейтный - предполагаю, что Вы уже хоть что-то про RAG слышали. Но пару ссылок, что почитать новичку, всё же приложу

  • Кода и разбора фреймворков - это есть в профильной документации

  • Киллерфич и инсайдов для senior RAG-архитекторов

Если Вы совсем новичок в теме, советую сначала прочитать:

https://inllm.ru/rag/vvedenie-v-rag - просто и разжёвано.

https://developers.sber.ru/help/business-development/what-is-rag - аналогично.

Введение

Больше года я занимаюсь реальным RAG проектом в своей небольшой компании. Проект - это ИИ-помощник (сейчас) и агентская система в будущем, со своими тулзами и навыками. Начиналось всё до неприличия просто, безграмотно и наобум. Приход более-менее вменяемых по качеству LLM с одной стороны и бесконечные, однотипные, ранее решённые вопросы от коллег в рабочих чатах - с другой, подтолкнули меня к мысли: здорово бы заиметь своего ИИ-помощника, который будет всё знать и отвечать на все вопросы, экономя вагон времени.

Я зашёл на YouTube и вбил в поиск "Своя база знаний в LLM", нырнул в 1-2 видео из топа, где блогер (англоязычный) показывает, как он запихнул целый документ, аж на 4000 токенов, в LM Studio, и ИИ ему отвечал на основе этого документа. Я сразу побежал ставить LM Studio, загрузил пару моделей 3b/8b и пару побольше, навалил туда 50+ инструкций своей компании, думаю: ну всё, сейчас все проблемы уйдут)0) ага...

Если не считать неприлично долгого времени ответа («съедание промпта» + генерация), то и само качество ответов не годилось для реального пользователя. Ответы вроде похожие на правду, но либо неполные, либо совсем неверные. От такой системы больше вреда, чем пользы. А мне хотелось, чтобы ИИ не просто нашёл очевидный ответ в документации, а-ля «Чек-бокс такой-то находится на такой-то вкладке», а умел в примитивный анализ: «Чек-бокс такой-то не отражается в кабинете пользователя, потому что в настройках отключён режим такой-то». То есть получить вопрос пользователя и самостоятельно диагностировать проблему.

Мои первые шаги

Я начал изучать литературу, читать такие же статьи на Хабре и смотреть всякие лекции на ютубе. Все предлагали так называемый классический - наивный RAG (Naive RAG). Реализация простая: порезать документы на куски, через эмбеддер записать их смысл в виде массива чисел n-размерности в векторную БД, а при вопросе пользователя прогонять его вопрос через тот же эмбеддер и сравнивать косинусную близость топ-N этих кусков документации и вопроса юзера. Кто-то предлагал схему чуть сложнее: подключить BM25 (поиск по ключевым словам, хорошо работает на всяких аббревиатурах, чей смысл нельзя адекватно записать в вектор, например код отчёта "01-ЦФИ.Я2") и реранкер.

Уже полезный, практический опыт

Что я получил? Да, в целом чуть улучшенную в плане экономии токенов версию поисковика, чем запихнуть всё разом в LLM. Но понимания доменной области и базового навыка диагностики/аналитики ИИ это не дало. Если Вам нужно просто найти фрагмент(ы) в огромном массиве документов и не более - этот вариант Вам подойдёт. Не усложняйте.

Этот вариант очень хорошо работает и подходит ещё для такого сценария. Например, мы хотим, чтобы ИИ отвечал на вопросы, на которые ранее уже отвечали в рабочих чатах, почте, helpdesk и прочих каналах. Мы выгружаем все тонны переписки и батчами (порциями) гоним их через ту же локальную LLM с промптом:

ROLE: Ты специалист по разметке данных.
Тебе на вход подаётся сырой кусок переписки из рабочего чата. Твоя задача - отсечь весь мусор (приветствия, поздравления, пожелания приятного аппетита) и найти полезные пары "вопрос-ответ", которые касаются исключительно бизнес/системной логики компании.
Ответ считается полезным, если на него есть явная благодарность или обратная связь от задавшего вопрос.

Формат ответа:
TXT, разделитель между парами - "*****".
Пример готовых пар вопрос-ответ:
вопрос: Где найти отчёт 01-ЦФИ.Я2 ?
ответ: В разделе "Клиенты > Меню > Личная карточка клиента > Отчёты > Отчёты за период операций"

*****
вопрос: Где найти отчёт 03-ЦФИ.Я3 ?
ответ: В разделе "Клиенты > Меню > Личная карточка клиента > Отчёты > Отчёты за период операций"
*****
вопрос: Где найти отчёт 04-ЦФA.Я5 ?
ответ: В разделе "Клиенты > Меню > Личная карточка клиента > Отчёты > Отчёты за период операций"

Этот сценарий фактически позволяет Вам собрать не просто почти готовые кейсы, но и сформировать живой, не синтетический eval-набор для будущей оценки работы системы. И вот здесь наивный RAG показывает свою силу и предназначение.

Продолжаем

Я совершенно случайно нашёл, куда применить такую простую схему, но проблему с пониманием доменной области и аналитикой это не решило. Часть доменных знаний уже содержится в документации, другая часть - только в головах сотрудников компании. Разные тонкости и нюансы, моменты, на которые бот явно должен обращать внимание и в каких-то случаях уточнять. И, конечно, у нас, как и у многих, был огромный пробел в документации: в тепличных условиях всё красиво, но в жизни так не бывает.

Это вторая моя находка, очевидная, но не сразу. Тут я сразу вспомнил статьи про RAG и выражение «Мусор на входе → мусор на выходе» - полностью согласен. Если у Вас нет документации, нет собранных и проверенных сотрудниками пар вопрос-решение - Вам в данный момент нужно заниматься документацией, а не RAG! Магии тут нет, готовьте качественные данные. Отличная связка для такого проекта - AI-инженер + технический писатель.

Что получилось?

Я сделал всю систему как по учебнику, - гибридный поиск(семантика + BM25).

https://inllm.ru/rag/gibridnyy-poisk

Добавил реранкер cohere через OpenRouter.

https://openrouter.ai/cohere/rerank-v3.5

БД - qdrant

https://github.com/qdrant/qdrant

В качестве генератора - deepseek по их API

https://platform.deepseek.com

Сейчас v4-flash, и опционально юзер может выбрать ПРО-режим, где под капотом v4-pro.

Я сразу позаботился о банальных recall, precision, hit rate. И добавил обратную связь от пользователя - либо лайк ? и точка, либо ? и поп-ап, куда юзер пишет, что не понравилось. Советую сразу делать метрики и обратную связь, но это не самое сложное. Самое сложное - приучить людей оставлять обратную связь: у меня пользователей немного, порядка 30 человек, но обратную связь оставляют только на 15% ответов.

Стало ли лучше? В подходе и архитектуре RAG - да. В качестве ответов ИИ - нет.

Расследование снова указало на документацию и способ подачи найденных чанков в генератор

Мне не нравилось, что ИИ сокращает ответы, хоть DeepSeek к этому и не склонен. В процедурных инструкциях, где важно ЦЕЛИКОМ воспроизвести инструкцию или её часть, генератор выдавал обрубки. Это категорически недопустимо для таких документов, особенно если у Вас финансовая, медицинская, юридическая и прочая ответственность. Не нравилось мне и то, что приходилось угадывать с количеством top-n, top-k, чтобы точно извлечь все нужные фрагменты даже в рамках одного документа. Как пример - справочник отчётов на 200+ кодов и описаний. Под некоторые критерии запроса юзера попадает 1-2 отчёта, под некоторые - 100. Что делать? Сортировать отчёты по содержанию, расположению? Да тоже не вариант. Запрос может охватывать их отовсюду.

Решение найдено - паттерн Child-Parent, но частично, только для процедурных документов или там, где важно видеть картину целиком. Когда мы находим нужный кусок, а отдаём генератору весь документ (флаг True у fullDoc). Дополнительно нужно, чтобы в каких-то документах стиль ответа был немного строже или ИИ обращал внимание на тонкости. Но писать эти тонкости и внутренние моменты в саму документацию нельзя. Решение - METAINFO к каждому документу, который пишете Вы и постепенно, с опытом, корректируете. METAINFO крепится как дополнительный блок при подаче в генератор, например:

{
 "source_file": "отчёты.md",
 "chunk_id": 4,
 "fullDoc": true,
 "metainfo": "Это блок метаинфо, его не отображаем юзеру! При ответе всегда сообщай пользователю о наиболее похожих отчётах, если ты не нашёл полностью соответствующих его запросу"
}

Приведя в порядок документацию, добавив частично паттерн child-parent и контекстную инженерию(metainfo), я получил реально полезную систему, которую живые пользователи высоко оценивают (94% Positive Rate, отношение ? к ?).
https://inllm.ru/prompting/kontekstnaya-inzheneriya

Итого

  • 50 документов

  • Гибридный поиск

  • Реранкер

  • Метрики

  • Обратная связь

  • Контекстная инженерия

  • Child-Parent подход

А нужно ли?

Коротко: в данном случае - НЕТ. Хоть это и масштабируемо, НО я сделал трактор для клумбы. А клумбы пока нет. И будет ЛИ? Зато получил опыт, как полезный, так и негативный. Хотя негативный тоже считаю полезным.

Меня осенило, что всё это можно было решить другой, более простой схемой - с наименьшим количеством точек отказа. Избавиться от векторной БД, эмбеддера и реранкера. Как? Использовать LLM в два вызова.

На первом подаём так называемую карту всех документов, map.json:

[
    {
        "file_name": "отчёты.md",
        "summary": "Здесь описаны все отчёты компании «Рога и копыта». Используем этот документ, если пользователь спрашивает про отчёты, где посмотреть продажи, или присылает непонятные коды - И1.ЦФ.02 и похожие",
        "key_words": "отчёты, % комиссии, за период операций",
        "metainfo": "Это блок метаинфо, его не отображаем юзеру! При ответе всегда сообщай пользователю о наиболее похожих отчётах, если ты не нашёл полностью соответствующих его запросу"
    },
    {
        "file_name": "отчёты2.md",
        "summary": "Здесь описаны все отчёты компании «Рога и копыта». Используем этот документ, если пользователь спрашивает про отчёты, где посмотреть продажи, или присылает непонятные коды - И1.ЦФ.02 и похожие",
        "key_words": "отчёты, % комиссии, за период операций",
        "metainfo": "Это блок метаинфо, его не отображаем юзеру! При ответе всегда сообщай пользователю о наиболее похожих отчётах, если ты не нашёл полностью соответствующих его запросу"
    }
]

И промпт роутера:

ROLE: Ты роутер в RAG-системе.
Ты на вход получаешь вопрос пользователя и map.json.
Твоя задача - определить, какие документы могут ответить на вопрос пользователя, и вернуть в формате JSON список документов. Если явных документов нет - верни пустой массив [].

На втором этапе берём список документов от роутера и подаём вместе с вопросом пользователя в генератор. И всё.

Я сделал такой движок как резервный и подключил к своей RAG, сравнил на реальных пользователях - и качество ретрива (роутера в данном случае) и генератора осталось таким же. Причём карту сгенерировала ИИ, я лишь немного поправил её. А если картой заняться основательно, думаю, на таком объёме документов она побьёт векторный вариант RAG.

У этого подхода есть явные плюсы:

  • Он хорошо прослеживается и корректируется человеком.

  • Он прост в управлении при небольшом количестве документов.

  • Он отлично кешируется LLM и выходит в копейки.

  • Не требуется эмбеддер, реранкер, дополнительная БД.

  • Генерация и управление картой легко автоматизируются с помощью той же LLM.

Минусы очевидные тоже есть:

  • Сложно масштабируется. Хотя можно делать вложенность, карты в карту. Но объективно потолок - 300-500 документов на карту.

  • Лишний вызов.

  • Ошибка роутера.

Я рекомендую всегда идти от простого к сложному. Не нужно сразу строить мультиагентную систему с тулзами к векторному поиску, обходом графа и прочим, если у Вас нет нужного объёма документации, даже если эксперты будут уверять что Вам это нужно. Для своей задачи - свой инструмент.

Еще один кейс

Вы можете сделать ИИ-Асистента, но обыватель будет использовать его так, как привык использовать все ИИ. Задавать вопросы, уточнять. Я сначала сделал бота без мульти-диалогов, - Multi-Turn. То есть если пользователь захочет что-то уточнить, его уточнение пойдет в ретривер и ретривер без контекста предыдущих сообщений будет искать ответ. Выглядит так:

user: Какие есть отчеты по продажам?
  [система ищет вектора похожие на {Какие есть отчеты по продажам?}]
  ИИ: A1, A5, A7
user: А еще?
  [система ищет вектора похожие на {А еще?}]

И вот тут грабли, казалось бы Вы так и планировали, но юзер ждёт привычный опыт, а не новый чат на каждый вопрос.

Как это решается?

Ответ: Прикреплением предыдущих сообщений и Рерайтером

Рерайтер - та же LLM, которая видит вопрос пользователя и предыдущий диалог и решает нужно ли переформулировать вопрос пользователя? Относится ли он к предыдущему диалогу?

Переформулирование запроса (query rewriting) — Переписывание запроса агентом перед поиском: раскрытие аббревиатур, починка формулировки, подбор терминов ближе к языку документов. Один большой нечёткий запрос почти всегда проигрывает нескольким точным.

Роль рерайтера может выполнять роутер. Так же если пользователь задал мульти-вопрос, можно реализовать его разбиение на отдельные вопросы и каждому искать похожие чанки.

А сколько это стоит?

У меня 50 инструкций, общий объём с метаинфой ~150.000 токенов. Но для удобства расчёта возьмём 100 документов, ~300.000 токенов.

Токенайзер https://inllm.ru/osnovy/tokenizatsiya

Беру deepseek https://api-docs.deepseek.com/quick_start/token_usage

Курс доллара 75 ₽.

Тип токенов

Без кэша, ₽/1M

С кэшем, ₽/1M

Входящие (input)

10,5 ₽

0,15 ₽

Исходящие (output)

31,5 ₽

0,22 ₽

Я протестировал более чем на 1500 реальных ответов пользователям. В среднем для ответа нужно 1-4 документа + промпт + карта на первом вызове. Если всё сложить и усреднить, стоимость ответа получается 30 копеек. На векторном поиске + реранкер + генерация - 46 копеек. Чанками в кэш попасть сложно.

Своё железо, аренда или облако?

Тут уже зависит, насколько чувствительные у Вас данные и какая позиция руководства. Количество запросов, в том числе одновременных: если параллелить, нужно более сильное железо. Нужна ли сильная модель, как v4, или, может, Вам хватит условной 8-30b? Мне они не очень понравились, но тут нужно тестировать лично Вам. К тому же своё железо нужно обслуживать, аренду - администрировать. С облаком заплатил и пользуйся.

В моём случае аренда той же A4000 для qwen3.6-35b-a3b стоила бы порядка 30.000 в месяц. Это ~100.000 запросов к куда более сильной v4-flash. У меня нет такого объёма даже с автотестами.

В общем, считайте под свои нужды.

А может, вообще всё в контекст?

Мог бы расписать то, что уже сто раз расписано, - про потерю контекста в центре и прочие нюансы.

Можно почитать тут https://inllm.ru/rag/rag-vs-dlinnyy-kontekst

Но я бы не исключал такого варианта: если у Вас до 200-300к контекста, с кэшированием это будет стоить копейки. Насколько удовлетворит качество? В каждом случае индивидуально, я лично не тестировал, хотя в планах есть. Обязательно проверяйте - возможно, Вам этого хватит и по скорости, и по качеству. Если цель минимальными усилиями решить небольшую задачу и сделать простого чат-бота - делайте так. Я понимаю, что за такой совет от AI-инженеров может прилететь, но это моя точка зрения :)

Что же выбрать?

Лично я бы начал в такой последовательно, учитывая свой опыт

Ваш случай

Что брать

Почему

Нет нормальной документации

Сначала - займитесь документацией

Мусор на входе → мусор на выходе

Документация влезает в контекст (до ~200-300к токенов)

Всё в контекст + кэш

Ни БД, ни эмбеддера, ни роутера. С кэшем - копейки. Просто попробуйте

До ~300-500 документов

LLM-роутер по map.json (2 вызова)

Прослеживается, корректируется руками, отлично кэшируется. Без вектора и реранкера

Десятки тысяч документов, нужен поиск по фрагментам

Гибридный поиск (вектор + BM25) + реранкер

Классика. Масштабируется туда, где карта в контекст уже не влезает

Связанные сущности, вопросы "кто с кем" и "как это влияет на то"

Graph RAG

Граф держит связи, которых нет в отдельных чанках
Но здесь я о нём намеренно не рассказывал.

Главное правило: идти снизу вверх по этой таблице, а не сверху вниз. Не повторяйте мой путь.

Ещё пара наблюдений из практики

  • Эмбеддер. На русском языке лучшие результаты у меня показал qwen3-4b. Пробовал BGE и другие - qwen зашёл. Русские не пробовал.

  • Бюджетный генератор. Из недорогих моделей рекомендую ту же серию qwen: можно брать на OpenRouter либо официально у Alibaba. Их много, идите по возрастающей по цене и тестируйте на своей клумбе.

  • Обратная связь и eval. Делайте обратную связь от пользователей с самого начала и собирайте живой eval-датасет. И сразу автоматизируйте тестирование - чтобы после каждого изменения видеть по цифрам, стало лучше или хуже, а не "по ощущениям".

Заключение

Возможно мой кейс и путь опытным RAG мастодонтам покажутся глупыми или местами абсурдными, но таков был мой путь. Я открыт к обсуждению, полезным советам, а так же к критике, возможно она спасёт от еще не обнаруженных мной граблей. Я стараюсь так же читать кейсы других людей на ХАБР`е и изучать новое. Порой из личных кейсов можно подчерпнуть больше полезной информации чем из научных статей, по этому пишите:)

Комментарии (0)