RAG · Retrieval-augmented generation · урок 4 · 13 мин

RAG online: retrieve → rerank → generate → check

RAG online: retrieve, rerank, generate, check

ТемыRAGПоиск и embeddings

Зачем

После урока ты пройдёшь вслух online-половину RAG — что происходит на каждый вопрос пользователя — и объяснишь, где стоят фильтры, зачем нужен reranker и что делать, когда ответа в найденном нет. Это вторая половина ответа «walk me through a RAG pipeline» и место, где собеседующий проверяет, понимаешь ли ты, что гарантии живут в коде, а не в prompt.

Шесть шагов на каждый вопрос

Offline-половина заранее подготовила библиотеку: chunks (куски документов) с metadata (карточкой: продукт, язык, версия, даты действия, доступ), векторный индекс и индекс по словам. Online-половина работает на каждый вопрос:

  1. Query rewrite — переписать вопрос для поиска.
  2. Filter — отсеять по metadata кодом.
  3. Retrieve — быстро достать 50–150 кандидатов.
  4. Rerank — внимательно пересортировать и оставить 5–20.
  5. Generate — написать ответ только по ним, со ссылками.
  6. Check — проверить ответ до того, как его увидит клиент.

Пройдём их на одном вопросе: клиент в чате пишет «а сколько за досрочку по моему автокредиту?».

Query rewrite: вопрос, пригодный для поиска

Query rewrite (переформулировка запроса) превращает разговорную фразу в запрос, по которому поиск что-то найдёт. «Досрочка» раскрывается в «досрочное погашение, early settlement fee»; «мой автокредит» — в продукт из профиля клиента; местоимения из прошлых реплик («а по нему?») заменяются тем, о чём шла речь. Запрос проходит ту же normalization (приведение цифр, Unicode и написаний к одному виду), что и документы.

Переписывает обычно LLM (large language model, большая языковая модель), и это новая точка отказа: переписанный запрос пишут в trace (журнал запроса), чтобы потом видеть, не исказил ли он вопрос.

Filter: гарантии живут в коде

Metadata filter (отсев по карточке) применяется здесь, в коде поиска, до ranking (упорядочивания). Три фильтра:

Почему не в prompt (текст инструкций для модели)? Инструкция «используй самую свежую версию» — совет: модель может его пропустить, неверно понять, а внедрённый в данные текст может её переубедить. Да и сам совет неверен для старого договора. Фильтр в коде срабатывает одинаково каждый раз и проверяется детерминированным тестом.

Pre-filter (фильтр внутри запроса поиска) лучше post-filter (фильтра после выдачи) дважды: запрещённый документ вообще не проходит через систему, и top-k (k лучших результатов) не пустеет после отсева.

Retrieve и rerank: быстро и грубо, потом медленно и точно

Retrieve (выборка) — hybrid search (гибридный поиск): векторный поиск по смыслу и BM25 (стандартная формула поиска по словам) параллельно, списки сливаются по рангам. Он быстрый, потому что документы закодированы заранее: bi-encoder (модель, кодирующая запрос и документ по отдельности) превращает запрос в один вектор, а индекс находит соседей среди миллионов. Цена скорости — грубость: в top-50 полно похожего, но не того.

Rerank (переранжирование) делает reranker, обычно cross-encoder (модель, которая читает вопрос и chunk вместе и выдаёт оценку релевантности). Он видит связь между словами вопроса и куска, поэтому точнее. Но ему нужен полный прогон модели на каждую пару «вопрос — chunk», а заранее посчитать ничего нельзя: пара зависит от вопроса.

Арифметика объясняет, почему reranker не гоняют по всему корпусу. Корпус в 1 000 000 chunks — миллион прогонов на один вопрос. Кандидатов 50 — пятьдесят прогонов, в 20 000 раз меньше. Документация Sentence-Transformers описывает ту же схему: retriever достаёт, например, 100 кандидатов, cross-encoder пересортировывает только их.

Сколько оставлять после rerank — тоже замер. Anthropic в статье о contextual retrieval (2024) сравнивала 5, 10 и 20 chunks в context (всём, что модель видит в запросе): лучше всего вышло 20, но там же оговорка, что лишние chunks отвлекают модель, и число надо проверять на своей задаче.

Generate: только из найденного, со ссылками

Generation (генерация) — модель получает вопрос и отобранные chunks с инструкцией: отвечать только по ним и для каждого утверждения указывать citation (ссылку на chunk). Если ответа в chunks нет, правильное поведение — сказать «не нашёл» или передать человеку, а не угадывать. Это поведение проверяют отдельно: в наборе тестовых вопросов должны быть вопросы без ответа в корпусе.

Check: проверка до клиента

Check (проверка) — последний шаг перед клиентом. Каждое утверждение, особенно каждое число, должно найтись в процитированном chunk. Это groundedness (опора ответа на источник), её проверяют кодом для чисел и отдельной моделью-судьёй для смысла.

Для самых рискованных фактов есть решение сильнее проверки: не давать модели производить число из текста вообще. Комиссию по договору клиента читает tool (вызываемая функция) из системы учёта, а модель только вставляет её в ответ. Источник числа становится системой учёта, а не текстом, и проверка сводится к простому сравнению: число в ответе обязано совпасть с тем, что вернул tool.

Пример целиком

Клиент с договором от мая 2024 года спрашивает про досрочку. Rewrite даёт «early settlement fee, auto loan». Фильтр оставляет chunks автокредита на русском, доступные клиентам, в версии, действовавшей в мае 2024 года: тариф 2026 года отсечён, хотя по словам он похож сильнее. Hybrid retrieve возвращает 50 кандидатов; нужный пункт тарифа на 7-м месте, выше стоят общие страницы о кредитах. Cross-encoder поднимает его на 1-е место, в context идут 5 chunks. Модель отвечает «1% от остатка, но не меньше 5 000 ₸» со ссылкой на chunk (цифры в примере выдуманы), а check находит обе цифры в нём — или комиссию сразу читает tool по номеру договора.

Что пишется в trace

На каждом шаге: исходный и переписанный запрос, применённые фильтры, chunk IDs и версии документов с рангами до и после rerank, что попало в context, ответ с citations, результат проверки. Без этого неверный ответ потом не разобрать.

Схема

Термины · Key terms

query rewrite
переформулировка вопроса перед поиском
metadata filter
отсев chunks по карточке кодом: доступ, дата, язык
pre-filter / post-filter
фильтр внутри поиска / после выдачи
top-k
k лучших результатов
bi-encoder
модель, кодирующая запрос и документ по отдельности; быстрый поиск
cross-encoder
модель, читающая вопрос и chunk вместе; точная оценка
reranker
второй, медленный и точный отбор кандидатов
citation
ссылка на chunk, из которого взято утверждение
groundedness
опора каждого утверждения ответа на найденный текст
tool
функция, которую агент вызывает, например чтение комиссии из системы учёта

Сделай сам

Объясни вслух термин cross-encoder за одну минуту, в четыре части: русский смысл, простое значение, образ для запоминания, пример из банка. Запиши голосовую заметку.

Обсуди с тьютором: перескажи текстом, что сказал, и попроси тьютора задать тебе уточняющий вопрос собеседующего: «why not run the cross-encoder over the whole corpus?»

Проверь себя · ответ под вопросом

1Почему reranker не прогоняют сразу по всему корпусу?
Cross-encoder требует полного прогона модели на каждую пару «вопрос — chunk», и заранее это не посчитать. На миллионе chunks это миллион прогонов на вопрос. Поэтому сначала быстрый retrieve сужает до 50–150 кандидатов, и reranker работает только с ними.
2На каком шаге online-половины применяется фильтр по дате, и почему не в prompt?
На шаге filter, в коде поиска, до ranking. Инструкция в prompt — совет: модель может её пропустить или её переубедит внедрённый текст. Кроме того, нужная дата зависит от вопроса (сегодня или дата договора), и это решает код.
3Что должна сделать система, если в отобранных chunks ответа нет?
Сказать, что не нашла ответа, или передать вопрос человеку, а не угадывать. Это поведение проверяют вопросами без ответа в тестовом наборе. Заодно стоит записать такой случай в trace: он покажет пробел в корпусе или в retrieval.

Interview questions · US / UK

1Walk me through what happens online, from the user's question to the answer.
Strong answer: query rewrite and normalization → metadata filters in code (ACL from the session token, effective date, language) → hybrid retrieve ~50 → cross-encoder rerank → ~5 → generate with citations or "I don't know" → check numbers against chunks or a tool; everything logged in the trace.
2What's the difference between a bi-encoder and a cross-encoder, and where does each sit?
Strong answer: bi-encoder encodes query and documents separately, precomputable, fast over millions — retrieval; cross-encoder reads both together, more accurate, one model pass per pair — reranking a shortlist.
3Follow-up probe: a colleague wants to add "only use documents the user is allowed to see" to the system prompt. Is that enough?
Strong answer: no — a prompt is advice and can be overridden by injected text; ACL in chunk metadata synced from the permission owner; groups from the login token; filter inside the search query before ranking; deterministic access tests with planted documents.

Ключевые фразы · Say it aloud

Материалы