ADR 0132 — Golden-set попаданий RAG и gate rag-acertivo
Контекст
ADR 0129 (Часть 2, Этап 1)
дал гибридному поиску след — rag_searches/rag_feedback, с весами,
ЗАМОРОЖЕННЫМИ в строке, — но намеренно ничего не измерял: только инструментировал.
pnpm --filter api medir:rag читает этот след по проекту, когда он есть. Не
хватало того, что сам rag-search-limits.ts продолжает объявлять:
NENHUM dos quatro números abaixo vem de calibração com dado real: não há, ainda, um corpo de perguntas reais rodado contra este índice.
Это Этап 2 той же программы — второй из пяти, в порядке, установленном владельцем продукта. Он ничего не калибрует (это отдельное продуктовое решение, Этап 5, с собственным критерием отказа); он даёт корпус вопросов, который Этап 1 оставил пробелом.
Шаблон уже существует, и это ADR 0123
Golden-set регрессий СЕМАНТИЧЕСКОГО суждения QA de Automação (ADR 0123) решил соседнюю задачу — «как измерить надёжность чего-то недетерминированного, без CI, с порогом-храповиком», — и решение здесь — переиспользовать всю структуру, а не изобретать во второй раз то же рассуждение:
| деталь | ADR 0123 (QA) | этот ADR (RAG) |
|---|---|---|
| alias | mix.exs "golden_set.qa" | "golden_set.rag" |
| постоянное исключение | test_helper.exs :golden_set_qa | :golden_set_rag |
| тест ExUnit | test/engine/gates/qa_automacao_agent_golden_test.exs | test/engine/rag/rag_golden_test.exs |
| порог-храповик | test/fixtures/golden_set_qa/floor.json | test/fixtures/golden_set_rag/floor.json |
| seed | apps/api/scripts/seed-golden-set-qa.ts | apps/api/scripts/seed-golden-set-rag.ts |
Постоянное исключение по ТЕГУ (никогда не обнаружение «Ollama доступен») действует
по ТОЙ ЖЕ причине, что и на стороне QA: на этой машине разработчика Ollama уже
работает постоянно, и автоматическое включение заставило бы модуль срабатывать внутри
ЛЮБОГО mix test, тратя токены без предупреждения.
Где шаблон НЕ подходит — и почему это не отклонение
Golden-set QA тестирует суждение чат-LLM, вызываемого через
Engine.Gates.QaAutomacaoAgent.run/5. У RAG нет никакого суждения LLM
на измеряемом пути — весь гибридный поиск (эмбеддинг + ts_rank +
слияние по весу + отсечение по порогу) выполняется внутри api (HybridSearchUseCase),
а не engine. Переписать поиск на Elixir только ради этого теста значило бы тестировать
ВТОРУЮ реализацию, никогда не настоящую, — ту же ошибку, которой ADR 0123 избежал,
используя настоящий LLM-клиент вместо симуляции суждения.
Решение: seed-golden-set-rag.ts делает ОБЕ вещи — подготавливает и
ищет, — возвращая уже готовый результат; rag_golden_test.exs лишь вызывает
скрипт (через System.cmd, тот же механизм, что на стороне QA) и применяет порог к
вернувшемуся JSON. Роль стороны Elixir — не «выполнять поиск», а «быть единой
точкой mix test --only/постоянного исключения/порога-храповика» — та же
дисциплина golden-set, которую уже использует остальной продукт, применённая к
механизму, который случайно живёт целиком по другую сторону границы api↔engine.
Вопросы: составленные, а не извлечённые — и это сказано, а не скрыто
Исходный запрос требовал РЕАЛЬНЫХ вопросов из dogfooding. Проверено до
написания этого ADR: gh issue list --repo daneiel/brabo --state all возвращает
«No Issues» — это сольный проект, без истории вопросов, сформулированных кем-то,
кто пользуется продуктом, в issue или PR (заголовки PR все вида
tipo(escopo): o que mudou, никогда не вопрос).
17 вопросов seed-golden-set-rag.ts поэтому составлены на основе
РЕАЛЬНЫХ RN и ADR этого репозитория — никогда не выдуманы без опоры.
У каждого есть expectedPath — реальный файл, где живёт ответ, и
составление проверено прямым чтением целевого файла до написания
вопроса (не по заголовку/grep). Это путь, который сам запрос предусмотрел на
случай отсутствия подлинного корпуса, а притворяться, что они «реальные», было бы хуже, чем
объявить составление.
Индексируемый корпус реален, отобран, не синтетичен
В отличие от QA (чей каркас кода синтетический, намеренно
спроектированный под каждый случай), корпус golden-set RAG — РЕАЛЬНОЕ ПОДМНОЖЕСТВО
docs/ самого этого monorepo — 22 файла (ARQUIVOS_CURADOS в seed),
скопированных дословно в репозиторий засеянного проекта, проиндексированных ТЕМ ЖЕ
IndexProjectDocsUseCase, что использует любой реальный проект. Отобран, а не всё
дерево (в реальном дереве больше 130 ADR), — из-за стоимости эмбеддингов в ручном
прогоне: поиск среди нескольких сотен чанков уже проверяет слияние
вектор+лексика так же, как поиск среди тысяч, а переиндексация более сотни ADR на
каждом ручном прогоне умножила бы время, не улучшая того, что измеряет golden-set.
Один проект, а не по одному на случай
Golden-set QA подготавливает по репозиторию на СЛУЧАЙ, потому что каждый случай тестирует отдельное бизнес-правило над ДРУГИМ кодом — изоляция существенна. 17 случаев RAG — это 17 вопросов о ТОМ ЖЕ корпусе документации: разбиение на 17 проектов проиндексировало бы одно и то же содержимое 17 раз (17x стоимость эмбеддингов), чтобы изолировать поиски, которые по природе уже только для чтения и без побочных эффектов друг на друга.
execution_mode: 'runner', а не container по умолчанию
ReadProjectCodeUseCase.portaoDoContainer (RN-105) отклоняет с 409 любое
чтение проекта container, пока Arquiteto не выберет образ, —
IndexProjectDocsUseCase вызывает этот путь, чтобы просмотреть docs/.
Golden-set никогда не задействует Arquiteto (нет никакого выполнения, которое бы это
оправдывало, только индексация документации), поэтому seed вставляет проект с
execution_mode: 'runner' — RN-169/RN-421 освобождают mounted/runner от
всего барьера, и это ровно для такого случая: проекту без собственного
контейнера незачем ждать решения об образе, чтобы его код был прочитан.
CHECK projects_workspace_path_casa_com_modo требует непустого workspace_path
для execution_mode <> 'container' — seed записывает синтетический
путь (никогда не используемый: ни один настоящий runner к этому проекту не подключается).
Критерий попадания: путь файла, top-K, никогда не rank 1
Ожидаемое попадание — это путь файла, а не точный чанк, — та же свободная мерка ADR 0123, адаптированная к RAG: привязка к чанку ломала бы golden-set при каждой настройке chunking, а это как раз тот параметр, ради возможности пересматривать который без переписывания случаев существует эта программа.
GOLDEN_SET_RAG_TOP_K = 5, а не rank 1: так продукт реально использует
результат — Chat RAG цитирует НЕСКОЛЬКО фрагментов рядом с ответом
(RagCitationCard, во множественном числе), а не только первый. Измерение против rank 1 измеряло бы
вопрос, который UI не задаёт. 5 — это также не тот RAG_SEARCH_RESULT_LIMIT
(10), который реальный маршрут использует по умолчанию, — запросить 10 и мерить против 10 значило бы тестировать
более свободный k, чем использует большинство реальных поисков (панель Chat RAG не
прокручивается, чтобы увидеть все 10). Критерий живёт в
apps/api/src/domain/rag/golden-set-criterio.ts, чистом и протестированном
(golden-set-criterio.spec.ts), по той же причине, что gate-registry.ts:
решение «это совпало» не должно зависеть от того, как пришёл результат.
Телеметрия: ВКЛЮЧЕНА, намеренно
HybridSearchUseCase записывает строку в rag_searches при каждом вызове,
всегда — параметра для отключения нет, и его не должно быть. Правильный
вопрос не «отключить?», а «загрязняет ли это реальное измерение?»: нет. medir:rag
требует --projeto <uuid> (никогда не агрегирует глобально, см. заголовок самого
скрипта), а golden-set всегда создаёт НОВЫЙ проект с суффиксом метки времени —
записываемые им строки никогда не появляются в medir:rag реального проекта, по той
же причине, по которой golden-set QA не загрязняет ни одну метрику реального проекта.
А настоящая запись — то, что заставляет этот golden-set проходить ТОТ ЖЕ путь
кода, что использует настоящий Chat RAG.
Gate rag-acertivo
- id: rag-acertivo
fluxo: transversal # o RAG serve todos os fluxos, como acao-aprovada
dono: usuario
entrada: [golden-set-rag]
entregavel: taxa-de-acerto-acima-do-piso
verificacao: script
severidade: warn
aprovacao_humana: false
status: active
evidencia:
tipo: teste
arquivo: apps/engine/test/engine/rag/rag_golden_test.exs
fluxo: transversal и dono: usuario переиспользованы из gate acao-aprovada
намеренно — ни одно из двух полей не проверяется по enum
(apps/api/src/domain/gates/gate-registry.ts), а изобретение новой категории
из одного члена (instrumentos/medicao) прошло бы валидатор, ничего не
означая. Если однажды понадобится владелец medicao, он уже существует как
РОЛЬ в docs/fluxo.yml (camada_instrumentos), и его введение — отдельное
решение, а не побочный эффект этого gate.
evidencia.tipo: teste, а не event_log — этот gate доказывает сам
golden-set, а не событие в логе (нет ни proposed_action, ни доменного события,
связанного с прохождением golden-set). Прецедент:
merge-protegida уже использует tipo: teste по той же причине — гарантия не берётся
из event log.
severidade: warn ОБЯЗАТЕЛЬНО, а не стилистически: RN-070 требует
verificacao: script для block (который у этого gate ЕСТЬ), но решение о warn
приходит из другого места — нет CI с LLM (то же решение, уже зафиксированное для
golden-set QA), поэтому gate выполняется ВРУЧНУЮ, а обещание block описывало бы
автоматическую блокировку, которой не существует. docs/explanation/gates.md получает
пятый разобранный пример «почему warn», по той же аргументационной
структуре, что и четыре предыдущих (necessidade-validada,
workspace-verificado, implementavel, paralelismo-autorizado).
entrada: [golden-set-rag] — не ссылка на другой gate: эвристика
валидатора (entrada, оканчивающийся на -verificada/-segura) не совпадает с этим
именем, поэтому проверка осиротевших не срабатывает. Подтверждено чтением
apps/api/src/domain/gates/gate-registry.ts (pareceGate), а не предположено.
Решение
- Скопировать структуру ADR 0123 (alias, постоянное исключение, тест
ExUnit, порог-храповик по МОДЕЛИ — здесь модели ЭМБЕДДИНГОВ, —
внешний seed через
System.cmd) в домен RAG, с двумя адаптациями, которых требует домен: seed выполняет поиск (а не только подготавливает, потому что суждение целиком живёт на стороне api), а корпус реален и отобран (не синтетичен). apps/api/src/domain/rag/golden-set-criterio.ts— чистый, протестированный — определяет критерий попадания (путь файла, top-5, никогда точный чанк/rank 1) и потребляется как seed, так и любым будущим отладочным вызовом.docs/gates.ymlполучаетrag-acertivo,warn,status: active,evidencia.tipo: teste, указывающий на тест ExUnit.floor.jsonс ключом по модели ЭМБЕДДИНГОВ (RAG_EMBEDDING_MODEL, сегодняnomic-embed-text— единственная, которую продукт поддерживает, RN-222), количество иof, никогда процент, записывается только человеком.
Последствия
Подключение к CI — TODO(humano), как и у QA. Без секрета LLM через API или
новой инфраструктуры (настоящий Ollama в CI) golden-set выполняется ВРУЧНУЮ —
mix golden_set.rag — и никогда в обычном mix test.
17 вопросов измеряют retrieval по корпусу из 22 файлов, а не по 130+ реальным ADR. Golden-set против полного корпуса измерял бы более реалистичное распределение сложности (больше правдоподобных отвлекающих, больше шансов, что два файла покрывают один вопрос), — решение о стоимости эмбеддингов в ручном прогоне, пересматриваемое, если реальный прогон покажет искусственно лёгкий потолок.
Golden-set измеряет RETRIEVAL, а не ОТВЕТ. Он никогда не вызывает чат-LLM,
чтобы синтезировать ответ из попаданий, — лишь измеряет, появился ли правильный путь
среди кандидатов. Это метрика, которую Этап 1 (medir:rag) тоже
использует (precision@k, распределение рангов), поэтому оба инструмента говорят на одном
языке.
О реальном прогоне и о том, что фиксирует порог: см.
test/fixtures/golden_set_rag/floor.json — комментарий _comment с
измеренным результатом (или заявлением о том, что измерение осталось ожидающим ручного
прогона, если окружение этой сессии не позволило прогнать против настоящего
Ollama).