rag-chatbot-simply.md — vim

RAG на пальцах: бот, который отвечает по нашим документам

Как устроен чат-бот поверх внутренних регламентов: эмбеддинги, векторная база, разбиение на куски. И где эта конструкция ломается.


Задача звучала буднично: «сделайте бота, который отвечает на вопросы по нашим регламентам». Модель про наши регламенты ничего не знает — она училась на публичных данных. Дообучать её после каждой правки документа дорого и медленно.

Поэтому — RAG. Retrieval-Augmented Generation, генерация с дополненным поиском. Идея объясняется за минуту.

Экзамен с конспектом

Представь студента, которому разрешили пользоваться конспектом. Он не помнит всё наизусть, но умеет читать и формулировать. Перед ответом находит нужные две страницы и отвечает по ним.

RAG делает ровно это:

  1. пользователь задаёт вопрос;
  2. мы ищем в своей базе документов куски, относящиеся к вопросу;
  3. подкладываем найденное в промпт: «ответь, опираясь только на этот текст»;
  4. модель формулирует ответ.

Никакого дообучения. Обновился регламент — переиндексировали документ, и бот отвечает уже по новой версии. Это, кстати, главный аргумент в разговоре с бизнесом: правки в документах не требуют разработчика.

Как искать по смыслу

Обычный текстовый поиск ищет слова. Вопрос «сколько дней отпуска на испытательном» не найдёт документ, где написано «в период испытательного срока предоставляется…» - слова другие.

Тут работают эмбеддинги. Модель превращает текст в вектор - массив чисел, который кодирует смысл. Близкие по смыслу тексты дают близкие векторы, близость меряется косинусным расстоянием:

const v1 = await embed('сколько дней отпуска на испытательном сроке');
const v2 = await embed('в период испытательного срока отпуск предоставляется…');
cosineSimilarity(v1, v2); // ≈ 0.87 - близко, хотя слова разные

Векторы всех кусков документации складываем в векторную базу (мы брали Qdrant), она быстро находит ближайших соседей к вектору вопроса.

Разбиение на куски - самое важное решение

Целый документ в промпт не влезет, да и не нужен: чем больше лишнего текста, тем хуже ответ.

Что выяснилось на практике:

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

Размер 300-800 токенов с перекрытием в сотню-полторы. Перекрытие спасает, когда ответ лежит ровно на границе кусков.

Метаданные хранить обязательно: источник, раздел, дата, права доступа:

await qdrant.upsert('docs', {
  points: [{
    id, vector,
    payload: { text: chunk, source: 'regulation_hr.pdf', section: '4.2', updated: '2026-03-01' },
  }],
});

Права доступа - не мелочь. Бот, процитировавший зарплатный документ не тому сотруднику, это инцидент, а не забавный баг. И фильтровать надо на этапе поиска, а не просить модель «не отвечать, если нельзя».

Как выглядит запрос

const qVec = await embed(question);
const found = await qdrant.search('docs', {
  vector: qVec,
  limit: 5,
  filter: { must: [{ key: 'access', match: { any: userRoles } }] },
});

const context = found
  .map(f => `[${f.payload.source} §${f.payload.section}]\n${f.payload.text}`)
  .join('\n\n');

const answer = await llm.complete(`
Отвечай только на основе контекста ниже. Если ответа в контексте нет - так и скажи.
Обязательно указывай источник в формате [файл §раздел].

Контекст:
${context}

Вопрос: ${question}
`);

Две строчки про «только по контексту» и «если нет - скажи» убирают большую часть выдумок. Не все, но большую часть.

Где ломается

Поиск нашёл не то. Самая частая причина плохих ответов - не модель, а retrieval. Прежде чем менять модель, посмотри, какие куски вообще нашлись. В половине случаев проблема в разбиении.

Вопросы вида «а что изменилось за год». RAG видит пять кусков, а не всю картину, и на обзорные вопросы отвечает плохо. Это закрывается отдельно, обычными запросами к структурированным данным.

Противоречивые документы. Лежат старый и новый регламент - модель честно процитирует оба. Лечится не промптом, а гигиеной источников и фильтром по дате.

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

Ещё сильно помогает гибридный поиск. Векторный плохо ищет точные обозначения - номера статей, артикулы, коды ошибок. Связка «векторный плюс обычный полнотекстовый» стабильно лучше любого по отдельности.

В сухом остатке: RAG - это не магия и не революция. Это хорошая поисковая система, к которой приделали умение формулировать. И качество итога определяется качеством поиска, то есть качеством ваших документов.

Ссылки