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: «Какие проверки нужны перед записью в репозиторий?»
- Агент видит, что не знает ответа, и вызывает поиск по индексу одним запросом — весь корпус он не читает.
- Поиск возвращает три фрагмента: два из раздела про policy — совпали термины, и один из раздела про локальные модели — совпало только слово «запись». Порог отсекает третий.
- Агент загружает полный текст только двух оставшихся и отвечает со ссылкой на конкретный файл и секцию.
- На вопрос «Какие требования к 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 исходного репозитория.