Skip to content

RAG-лаборатория: retrieval как инструмент агента

RAG Lab — компактная лаборатория курса, где retrieval показан как компонент агентной системы, а не как отдельная дисциплина. Задача лаборатории — не устройство векторного поиска, а два вопроса: как найденный контекст становится частью агента и что происходит, когда релевантной информации нет.

Логика лаборатории вытекает из одного ограничения: модель не знает содержимое корпуса, а передать весь корпус целиком нельзя — он не поместится в контекст и затеряет релевантное в шуме. Значит, ищем только те фрагменты, которые нужны текущему вопросу, и передаём их вместе с источником. Это и есть retrieval; RAG (Retrieval-Augmented Generation, генерация с извлечением) — ответ модели по найденному фрагменту.

Why it matters

Ошибка здесь тихая: если релевантного фрагмента не нашлось, модель отвечает «из своей памяти» — уверенно и неправильно. Лаборатория делает этот отказ измеримым вместо того, чтобы надеяться на аккуратность промпта.

Архитектура

Индексация корпуса и запрос — два разных прохода:

%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 28, "rankSpacing": 42, "wrappingWidth": 460}, "themeVariables": {"fontSize": "14px"}}}%%
flowchart TD
  subgraph IDX["Индексация: один раз"]
    D["Documents"] --> C["Chunk / Index"] --> V[("Vector store")]
  end
  subgraph Q["Запрос: на каждый вопрос"]
    S["Search"] --> RC["Relevant context"] --> A["Agent"] --> AW["Answer with citations"]
  end
  V --> S

Агент не получает корпус целиком. Он сам решает, что ему не хватает фактов, вызывает поиск, получает ссылки с оценками и короткими сниппетами и загружает полный текст только тех фрагментов, которые нужны текущему вопросу. Это тот же Just-in-Time Context из недели 3, реализованный кодом.

Один вопрос целиком

Возьмём вопрос из questions.json: «Какие проверки нужны перед записью в репозиторий?»

  1. Агент видит, что не знает ответа, и вызывает поиск по индексу одним запросом — весь корпус он не читает.
  2. Поиск возвращает три фрагмента: два из раздела про policy — совпали термины, и один из раздела про локальные модели — совпало только слово «запись». Порог отсекает третий.
  3. Агент загружает полный текст только двух оставшихся и отвечает со ссылкой на конкретный файл и секцию.
  4. На вопрос «Какие требования к API-ключу?» конвейер возвращает insufficient_context: подходящего фрагмента в корпусе нет, и агент не отвечает «из своей памяти».

Шаги 1–3 и есть RAG: найденное плюс сгенерированный ответ. Шаг 4 — часть, которую нельзя убрать: без него система отвечает уверенно и неправильно.

Что реализовано

Windows PowerShell

Из корня репозитория установите пакет, выполните тесты и запустите оценку:

.\.venv\Scripts\python.exe -m pip install -e ".\labs\rag-lab[dev]"
.\.venv\Scripts\python.exe -m pytest .\labs\rag-lab\tests
.\.venv\Scripts\python.exe -m rag_lab --corpus .\labs\rag-lab\corpus --cases .\labs\rag-lab\questions.json

macOS / Linux

Из корня репозитория выполните те же шаги с POSIX-путями:

./.venv/bin/python -m pip install -e "./labs/rag-lab[dev]"
./.venv/bin/python -m pytest ./labs/rag-lab/tests
./.venv/bin/python -m rag_lab --corpus ./labs/rag-lab/corpus --cases ./labs/rag-lab/questions.json

Если .venv ещё не настроено, инструкции для вашей ОС есть в разделе учебные репозитории курса. Pytest должен пройти, а последний запуск напечатает метрики и результаты для набора вопросов.

Шаг Что видно в коде
Ingestion и chunking Разбиение Markdown на фрагменты (chunking) по секциям и скользящему окну; у каждого фрагмента есть документ, секция и идентификатор. Один проход построения индекса.
Embeddings Числовые отпечатки текста, по которым фрагменты сравниваются между собой. Здесь embedder детерминированный, локальный, без внешних API.
Vector store и поиск Хранилище фрагментов и их отпечатков; поиск берёт top-k самых похожих по cosine similarity — мере похожести векторов — и отсекает всё, что ниже порога.
Retrieval tool Два узких инструмента с явной схемой аргументов; неизвестное имя и неверный тип отклоняются до исполнения.
Агент и ответ Цикл поиск → чтение → ответ, отказ при отсутствии опоры в контексте, trace каждого шага.
Оценка Три метрики недели 7, применённые к retrieval.

Когда отвечать нельзя

В questions.json есть два негативных случая. Один вопрос вообще не про этот корпус, другой частично совпадает по словам, но ответа не содержит. В обоих случаях агент возвращает insufficient_context: поиск либо ничего не находит, либо находит фрагменты, в которых нет ни одного утверждения, опирающегося на вопрос.

Failure mode

Два уровня защиты здесь намеренно разделены: порог схожести отсекает нерелевантное, а проверка опоры в загруженном контексте не даёт ответить по памяти модели. Если заменить детерминированный ответчик на реальную модель, именно вторая проверка перестаёт быть гарантированной — это и показывает метрика unsupported_answer_rate.

Оценка

Отчёт печатает релевантность retrieval, долю grounded-ответов и долю неподкреплённых ответов:

Метрика Что означает Чего ждать при ухудшении
Retrieval relevance Доля поддерживаемых вопросов, для которых найден ожидаемый документ эмбеддер или chunking перестали подходить для корпуса
Grounded answer rate Доля ответов, чьи утверждения целиком найдены в процитированном фрагменте ответчик пересказывает или додумывает
Unsupported answer rate Доля ответов на неподдерживаемые вопросы система уверенно отвечает без источника

Все три — измерения этого офлайн-индекса, а не доказательство правильности ответа. Встроенный embedder хеширует слова и не понимает смысл, поэтому «права доступа» не найдут фрагмент про least privilege, а хеш-коллизии дают ложные совпадения с ненулевой оценкой. Именно поэтому качество retrieval измеряется, а не считается заданным.

Границы

Лаборатория локальная, без API-ключей и внешних зависимостей. Инструменты читают индекс и ничего не пишут в корпус. Не помещайте в corpus/ секреты и персональные данные: текст фрагментов попадает в отчёт и в trace.

Команды запуска, формат набора вопросов и практические задания описаны в labs/rag-lab/README.md исходного репозитория.