This commit is contained in:
2026-03-27 15:51:10 +03:00
parent 15586f9a8c
commit 0bff171936
1245 changed files with 99621 additions and 543076 deletions
+190
View File
@@ -0,0 +1,190 @@
# Снимок runtime-контура CODE_QA (answer layer)
Документ фиксирует текущее состояние runtime-контура `CODE_QA` после рефакторинга для планирования доработок answer layer. Без предложений по новому дизайну и без implementation brief.
---
## 1. Entry point
- **HTTP:** `POST /api/chat/messages``ChatModule.public_router()``send_message()`.
Файл: `src/app/modules/chat/module.py`, строки 7481.
- **Условие:** при `SIMPLE_CODE_EXPLAIN_ONLY=true` запрос идёт в `CodeExplainChatService.handle_message()` (прямой explain без полного CODE_QA pipeline). При `false` — в оркестратор.
- **Оркестратор:** `ChatOrchestrator.enqueue_message()` создаёт задачу и запускает `_process_task()` → в нём вызывается `self._runtime.run(...)`.
Файл: `src/app/modules/chat/service.py`, строки 4769, 71132.
- **Runtime-адаптер:** `CodeQaRunnerAdapter` реализует `AgentRunner`; в `run()` вызывает `self._executor.execute(user_query=..., rag_session_id=..., files_map=...)` в thread pool.
Файл: `src/app/modules/agent/runtime/code_qa_runner_adapter.py`, строки 2141.
- **Фактическая точка входа CODE_QA:** `AgentRuntimeExecutor.execute()`.
Файл: `src/app/modules/agent/runtime/executor.py`, строка 53.
Создание executor: `application.py`, строка 48 — `_executor = AgentRuntimeExecutor(llm=..., retrieval=...)`.
---
## 2. Runtime pipeline
Цепочка внутри `AgentRuntimeExecutor.execute()` (файл `executor.py`):
| Шаг | Файл | Класс/функция | Роль |
|-----|------|----------------|------|
| 1. Роутинг | `executor.py` | `self._router.route(user_query, ...)` | Intent + sub-intent, query_plan, retrieval_spec, symbol_resolution (pending). |
| 2. Сборка запроса retrieval | `retrieval_request_builder.py` | `build_retrieval_request(router_result, rag_session_id)` | Из `RouterResult` собирается `RetrievalRequest`: query, sub_intent, path_scope, requested_layers, retrieval_spec, constraints, query_plan. |
| 3. Retrieval | `executor.py` | `self._retrieve(state)``RuntimeRetrievalAdapter.retrieve_with_plan()` или `retrieve_exact_files()` для OPEN_FILE | По плану или по точным путям; возвращает `raw_rows` (list[dict]). |
| 4. Догидрация (только FIND_ENTRYPOINTS) | `executor.py` | `_hydrate_entrypoint_sources()` | Дозапрос C0 по путям из C3 entrypoints. |
| 5. Разрешение символа | `executor.py` | `_resolve_symbol(initial, raw_rows)` | По C1_SYMBOL_CATALOG: resolved / ambiguous / not_found; обновляет `state.router_result.symbol_resolution`. |
| 6. Retrieval result | `retrieval_result_builder.py` | `build_retrieval_result(raw_rows, report, symbol_resolution)` | Нормализованный `RetrievalResult`: code_chunks, relations, entrypoints, test_candidates, layer_outcomes и т.д. Для EXPLAIN при not_found/ambiguous — пересборка с пустыми rows (строки 9091 executor). |
| 7. Evidence bundle | `evidence_bundle_builder.py` | `build_evidence_bundle(retrieval_result, router_result)` | `EvidenceBundle`: resolved_sub_intent, resolved_target, code_chunks, relations, entrypoints, test_evidence, retrieval_summary. sufficient/failure_reasons не выставляются здесь. |
| 8. Pre evidence gate | `evidence_gate.py` | `evaluate_evidence(state.evidence_pack)` | По sub_intent проверяет достаточность (target, evidence_count, слои, entrypoints, tests). Выставляет `bundle.sufficient`, возвращает `EvidenceGateDecision`; от этого — `state.answer_mode` (normal/degraded). |
| 9. Answer policy | `policy.py` | `self._answer_policy.decide(router_result, gate_decision)` | Решение: вызывать LLM или короткий ответ (OPEN_FILE not_found, EXPLAIN not_found/ambiguous, gate не прошёл). При `should_call_llm=False` сразу идём в `assemble_final_result` с `decision.answer`. |
| 10. Synthesis input | `answer_synthesis.py` | `build_answer_synthesis_input(user_query, state.evidence_pack)` | Строит `AnswerSynthesisInput`: fast_context, deep_context, evidence_summary, semantic_hints, curated_facts (из answer_fact_curator). |
| 11. Выбор промпта | `prompt_selector.py` | `self._prompt_selector.select(sub_intent=..., answer_mode=...)` | Имя системного промпта по sub_intent (и degraded). |
| 12. Payload | `prompt_payload_builder.py` | `self._payload_builder.build(user_query, synthesis_input, evidence_pack, answer_mode)` | JSON payload для LLM: user_query, resolved_scenario, fast/deep_context, evidence_summary, curated must_mention_*, layer_guide, entrypoints, scenario-specific поля. |
| 13. Генерация черновика | `generator.py` | `self._generator.generate(prompt_name, prompt_payload)` | Вызов `AgentLlmService.generate(prompt_name, payload)` → черновик ответа. |
| 14. Post evidence gate | `post_gate.py` | `self._post_gate.validate(answer, answer_mode, ..., sub_intent, user_query, evidence_pack)` | Проверка черновика по sub_intent (EXPLAIN/ARCHITECTURE/TRACE_FLOW/…), возврат `RuntimeValidationResult(passed, action, reasons)`. |
| 15. Repair (если не passed) | `repair.py` | `self._repair.repair(draft_answer, validation, prompt_payload)` | Один вызов LLM с промптом `code_qa_repair_answer`; повторная валидация; при повторном fail — fallback answer. |
| 16. Финальный результат | `result_assembler.py` | `assemble_final_result(state, draft=..., final_answer=..., ...)` | Сборка `RuntimeFinalResult` и диагностики. |
Sub-intent для CODE_QA задаётся в роутере: `QueryPlanBuilder` использует `SubIntentDetector.detect()` и `_resolve_sub_intent()`; итог в `query_plan.sub_intent`. Ретривал-слои по sub_intent задаются в `RetrievalSpecFactory._with_sub_intent_layers()` (`retrieval_spec_factory.py`).
---
## 3. Answer path
- **Выбор промпта:** `RuntimePromptSelector.select(sub_intent, answer_mode)``src/app/modules/agent/runtime/steps/generation/prompt_selector.py`, строки 1821. При answer_mode in `{"degraded","not_found","insufficient"}` возвращается `code_qa_degraded_answer`, иначе — по `sub_intent` из словаря (fallback `code_qa_explain_answer`).
- **Сборка payload:** `RuntimePromptPayloadBuilder.build()``prompt_payload_builder.py`, строки 21–44. В payload попадают: `user_query`, `resolved_scenario`, `resolved_target`, `answer_mode`, `fast_context`, `deep_context`, `evidence_summary`, `semantic_hints`, `diagnostic_hints`, `retrieval_summary`, `confirmed_entrypoints`, `required_entrypoints`, `layer_guide`, плюс сценарий-специфичные поля из `_scenario_payload(synthesis_input)` (must_mention_*, fact_gaps и т.д.).
- **Draft answer:** создаётся в `executor.py`, строки 242246: `RuntimeDraftAnswer(prompt_name=..., prompt_payload=..., answer=self._generator.generate(...))`.
- **Post-processing:** отдельного шага нет; после генерации сразу идёт post-validation.
- **Repair:** `RuntimeAnswerRepairService.repair()``repair.py`, строки 16–37. Формирует JSON с draft_answer, validation_reasons, repair_focus, prompt_payload и один раз вызывает LLM с `code_qa_repair_answer`.
- **Final text:** в executor: при passed — `final_answer = draft.answer` (или результат repair); при не passed после repair — `_fallback_answer(state)`. Итоговая строка попадает в `RuntimeFinalResult.final_answer` в `assemble_final_result()`.
---
## 4. Prompt selection
- **Где:** `src/app/modules/agent/runtime/steps/generation/prompt_selector.py`, класс `RuntimePromptSelector`, метод `select(sub_intent, answer_mode)`.
- **Правила:**
- answer_mode in `{"degraded","not_found","insufficient"}``code_qa_degraded_answer`.
- Иначе по `sub_intent.upper()` из `_PROMPTS`; при отсутствии ключа — `code_qa_explain_answer`.
- **Используемые имена промптов для целевых sub_intent:**
| sub_intent | prompt name |
|-------------|--------------------------------|
| EXPLAIN | `code_qa_explain_answer` |
| EXPLAIN_LOCAL| `code_qa_explain_local_answer` |
| ARCHITECTURE| `code_qa_architecture_answer` |
| TRACE_FLOW | `code_qa_trace_flow_answer` |
- **Шаблоны:** загружаются по имени из YAML в `AgentLlmService.generate()``PromptLoader.load(name)`; конфиг — `src/app/modules/agent/llm/prompts.yml`. Ключи в YAML совпадают с именами выше (в т.ч. `code_qa_explain_answer`, `code_qa_architecture_answer`, `code_qa_trace_flow_answer`); repair — `code_qa_repair_answer`.
- **Выбор по sub_intent:** да, только через `RuntimePromptSelector.select(sub_intent=state.retrieval_request.sub_intent, ...)` в executor, строка 231.
---
## 5. Evidence-to-answer boundary
- **В answer layer evidence приходит как:**
- `EvidenceBundle` (в state.evidence_pack) и
- `AnswerSynthesisInput` (state.synthesis_input), собранный из bundle в `build_answer_synthesis_input()`.
- **Модели/DTO:**
- `EvidenceBundle`: `contracts.py`, 90106 — resolved_intent, resolved_sub_intent, resolved_target, target_type, code_chunks, relations, entrypoints, test_evidence, evidence_count, retrieval_summary.
- `AnswerSynthesisInput`: `contracts.py`, 109121 — user_question, resolved_scenario, resolved_target, fast_context, deep_context, evidence_summary, semantic_hints, **curated_facts**, evidence_sufficient, diagnostic_hints.
- Curated facts строит `answer_fact_curator.build_curated_answer_facts(bundle)` — словарь с ключами `explain`, `architecture`, `trace_flow` и общими полями (scenario, semantic_hints, relation_count и т.д.).
- **Что реально уходит в payload (prompt_payload_builder):**
- Общее: user_query, resolved_scenario, resolved_target, answer_mode, fast_context, deep_context, evidence_summary, semantic_hints, diagnostic_hints, retrieval_summary, confirmed_entrypoints, required_entrypoints, layer_guide.
- EXPLAIN: must_mention_methods/fields/calls/dependencies/constructor_args/files, must_not_infer_missing_details, fact_gaps (из curated_facts["explain"]).
- ARCHITECTURE: must_mention_components/relations, must_use_relation_verbs, must_avoid_semantic_labels_as_primary_claims, must_not_use_retrieval_labels, fact_gaps (из curated_facts["architecture"]).
- TRACE_FLOW: must_mention_flow_steps/calls/sequence_edges, must_avoid_overclaiming_full_flow, fact_gaps (из curated_facts["trace_flow"]).
- **Curated-поля (answer_fact_curator):**
- explain: required_methods, required_calls, required_fields, required_dependencies, required_constructor_args, required_files, fact_gaps (и др.).
- architecture: required_components, required_relations (source/verb/target/edge_type), required_relation_verbs, required_*_edges, forbidden_labels, fact_gaps.
- trace_flow: required_flow_steps (step, source, verb, target, path, line_span), required_calls, required_sequence_edges, fact_gaps.
То есть в LLM попадает не сырой retrieval, а нормализованный контекст (fast/deep_context, evidence_summary) плюс явные списки «must_mention_*» и fact_gaps по сценарию; для methods/dependencies/relations/flow steps уже есть выделенные curated-поля.
---
## 6. Post-validation / answer quality control
- **Post-evidence gate (runtime):** есть. `RuntimePostEvidenceGate.validate()``src/app/modules/agent/runtime/steps/gates/post/post_gate.py`, строки 39–65. Вызывается после генерации черновика (и после repair — повторно).
- **Answer validator:** это тот же post_gate: проверяет пустой ответ, соответствие answer_mode (degraded/not_found/ambiguous) требуемым формулировкам, длину при degraded, затем для normal — `_normal_answer_reasons()` по sub_intent.
- **Repair loop:** один раунд. При `not validation.passed` и наличии `self._repair` вызывается `repair()`; затем повторный `validate()`; если снова не passed — подставляется `_fallback_answer()` и смена answer_mode (`executor.py`, 281298).
- **Правила по sub_intent (post_gate):**
- **EXPLAIN** (93124): target focus; vagueness (_VAGUE_PHRASES); наличие required_methods/calls/dependencies (хотя бы одна группа); «too_vague_for_explain» при нуле совпадений; semantic_leakage (роли из semantic_hints без опоры на код).
- **ARCHITECTURE** (126150): target focus; vagueness; required_components, required_relations, relation_verbs; forbidden_labels (retrieval artifacts); methods_as_primary_components; «too_vague_for_architecture»; semantic_leakage.
- **TRACE_FLOW** (152171): target focus; vagueness; required_flow_steps и required_calls; _mentions_steps (сначала/затем или нумерация); overclaims (_OPTIMISTIC_TRACE_CLAIMS); «too_vague_for_trace_flow».
- **Technical precision для EXPLAIN:** проверяется косвенно: упоминание методов/вызовов/зависимостей из curated; явной проверки «только факты из кода» по токенам нет.
- **Concrete relations для ARCHITECTURE:** да — `_mentions_relations(answer, relations)` и упоминание verbs.
- **Concrete steps и overclaim для TRACE_FLOW:** да — `_mentions_steps`, `_mentions_relations` по steps, и проверка фраз из _OPTIMISTIC_TRACE_CLAIMS.
---
## 7. Problem sources (что может давать слабые ответы)
- **Payload shaping:** `prompt_payload_builder.py` — если curated_facts пустые или скудные (мало methods/calls/relations/steps), must_mention_* не направляют модель; deep_context обрезается до 30 чанков по 800 символов — возможна потеря важных деталей.
- **Prompts:** `prompts.yml` — длинные общие инструкции; для EXPLAIN/ARCHITECTURE/TRACE_FLOW нет жёсткой привязки к структуре payload (например, «обязательно используй must_mention_flow_steps по порядку»); модель может игнорировать fact_gaps.
- **Evidence normalization:** `answer_fact_curator` — методы/вызовы/relations извлекаются эвристически (regex, C1/C2); при слабом C1/C2 или нестандартных именах curated-списки пустеют → валидатор не к чему привязываться, ответ считается «vague».
- **Weak validation:** `post_gate` — проверки по вхождению подстрок (alias) и по небольшому набору фраз; нет проверки полноты (все ли must_mention_* упомянуты), нет проверки порядка шагов для TRACE_FLOW; semantic_leakage выключается при has_concrete_support, что может пропускать смешанные ответы.
- **Repair policy:** один вызов repair с общим промптом `code_qa_repair_answer` и repair_focus по reasons; при множественных reasons фокус может размываться; после repair при повторном fail сразу fallback — без второго раунда repair.
---
## 8. Minimal intervention points
1. **`src/app/modules/agent/runtime/steps/generation/prompt_payload_builder.py`**
Класс `RuntimePromptPayloadBuilder`, метод `build()` и `_scenario_payload()`.
Контролирует: какие поля и списки (must_mention_*, fact_gaps, layer_guide) попадают в JSON для LLM.
Удобно: один вход в «что видит модель»; можно усилить структуру под EXPLAIN/ARCHITECTURE/TRACE_FLOW без трогания оркестрации.
2. **`src/app/modules/agent/runtime/steps/context/answer_fact_curator.py`**
Функции `_explain_facts()`, `_architecture_facts()`, `_trace_flow_facts()`.
Контролируют: состав и качество curated_facts (required_*, fact_gaps).
Удобно: улучшение извлечения методов/relations/steps напрямую улучшает и payload, и валидацию.
3. **`src/app/modules/agent/runtime/steps/gates/post/post_gate.py`**
Класс `RuntimePostEvidenceGate`, методы `_validate_explain()`, `_validate_architecture()`, `_validate_trace_flow()` и хелперы (`_mentions_fact_group`, `_mentions_relations`, `_mentions_steps`).
Контролирует: критерии прохождения и набор reasons для repair.
Удобно: уже разбито по сценариям; можно ужесточить правила и добавить новые reasons без смены архитектуры.
4. **`src/app/modules/agent/llm/prompts.yml`**
Блоки `code_qa_explain_answer`, `code_qa_architecture_answer`, `code_qa_trace_flow_answer`, `code_qa_repair_answer`.
Контролируют: инструкции для черновика и починки.
Удобно: точечные правки формулировок и явные отсылки к полям payload (must_mention_*, fact_gaps).
5. **`src/app/modules/agent/runtime/steps/generation/prompt_selector.py`**
Класс `RuntimePromptSelector`, словарь `_PROMPTS` и метод `select()`.
Контролирует: какой системный промпт выбирается по sub_intent/answer_mode.
Удобно: введение отдельных промптов для подвидов (например, TRACE_FLOW по типу запроса) без изменения executor.
6. **`src/app/modules/agent/runtime/steps/context/answer_synthesis.py`**
Функция `build_answer_synthesis_input()`, формирование `fast_context` и `deep_context` (в т.ч. фильтр по C4 для EXPLAIN/ARCHITECTURE).
Контролирует: объём и приоритет контекста, передаваемого в synthesis_input.
Удобно: можно менять лимиты, порядок чанков или фильтры слоёв локально.
7. **`src/app/modules/agent/runtime/steps/finalization/repair.py`**
Класс `RuntimeAnswerRepairService`, метод `repair()` и `_repair_focus()`.
Контролирует: как validation.reasons мапятся в repair_focus и что уходит в промпт починки.
Удобно: можно сузить фокус repair под конкретные reasons или добавить приоритизацию без изменения цикла в executor.
---
*Документ описывает только текущую реализацию по коду после рефакторинга.*
+271
View File
@@ -0,0 +1,271 @@
# План доработки БД для хранения контекста Story и метаданных RAG
## Цель
Зафиксировать проект миграции, который:
- добавляет в таблицу чанков признаки артефакта (тип, источник, контекст),
- вводит отдельный контур хранения инкремента по `story_id`,
- не зависит от выбранного режима RAG (общий/сессионный/гибридный).
## Границы
- Документ описывает план и целевую схему.
- Реализация SQL-миграций и backfill выполняется отдельным шагом после согласования.
## 1) Метаданные чанков (RAG-слой)
### 1.1. Что добавить
Для таблицы `rag_chunks` (или эквивалента таблицы чанков) добавить поля:
- `artifact_type` (`REQ|ARCH|API|DB|UI|CODE|OTHER`)
- `path` (нормализованный относительный путь файла)
- `section` (заголовок/логический раздел документа)
- `doc_id` (стабильный идентификатор документа)
- `doc_version` (версия документа/ревизия)
- `owner` (ответственная команда/человек)
- `system_component` (система/подсистема/компонент)
- `last_modified` (время последнего изменения источника)
- `staleness_score` (0..1, в первую очередь для `CODE`)
### 1.2. Ограничения и индексы
- `CHECK` для `artifact_type` и диапазона `staleness_score`.
- Индексы:
- `(artifact_type)`
- `(doc_id, doc_version)`
- `(system_component)`
- `(path)`
- GIN/BTREE по потребности для фильтрации в retrieval.
## 2) Контур Story (отдельно от чанков)
### 2.1. Таблица `story_records`
Карточка Story:
- `story_id` (PK, строковый уникальный идентификатор)
- `project_id` (идентификатор проекта/репозитория)
- `title`
- `status` (`draft|in_progress|review|done|archived`)
- `baseline_commit_sha` (базовый снимок)
- `snapshot_id` (опционально для session-RAG)
- `created_at`, `updated_at`
- `created_by`, `updated_by`
Индексы:
- `(project_id)`
- `(status)`
- `(updated_at)`
### 2.2. Таблица `story_artifacts`
Связь Story с артефактами изменений:
- `id` (PK)
- `story_id` (FK -> `story_records.story_id`)
- `artifact_role` (`requirement|analysis|doc_change|test_model|note|decision|risk`)
- `doc_id`
- `doc_version`
- `path`
- `section`
- `chunk_id` (nullable; ссылка на chunk если стабильно поддерживается)
- `change_type` (`added|updated|removed|linked`)
- `summary` (краткое описание изменения)
- `source_ref` (ссылка/внешний id)
- `created_at`
- `created_by`
Уникальность (черновик):
- `UNIQUE(story_id, artifact_role, COALESCE(doc_id,''), COALESCE(path,''), COALESCE(section,''), COALESCE(change_type,''))`
Индексы:
- `(story_id, artifact_role)`
- `(story_id, change_type)`
- `(doc_id, doc_version)`
- `(path)`
### 2.3. Таблица `story_links`
Связи Story с внешними сущностями и Story-to-Story:
- `id` (PK)
- `story_id` (FK)
- `link_type` (`story|adr|ticket|pr|commit|doc|external`)
- `target_ref` (идентификатор/ссылка)
- `description`
- `created_at`
Индексы:
- `(story_id, link_type)`
- `(target_ref)`
## 3) Почему `story_id` не в чанках
- Один чанк может относиться к нескольким Story.
- Чанки нестабильны при переиндексации.
- Разделение слоев упрощает поддержку и не привязывает модель к типу RAG.
Итог: связь Story и чанков/документов хранить в `story_artifacts`, а не в `rag_chunks`.
## 4) Целевая модель RAG: Hybrid-Lite
Выбранный вектор на текущем этапе: `Session-first + Shared Cache + Story Ledger`.
### 4.1. Принципы
- Рабочий retrieval выполняется из сессионного индекса (видит незакоммиченные изменения).
- Общий кэш чанков/эмбеддингов используется только для ускорения индексации.
- Источник правды по инкременту Story находится в Story-таблицах, а не в RAG-индексе.
### 4.2. Что хранить дополнительно
- `rag_blob_cache`: кэш файловых blob по `repo_id + blob_sha`.
- `rag_chunk_cache`: кэш чанков/эмбеддингов, привязанный к `blob_sha`.
- `rag_session_chunk_map`: привязка сессии к используемым chunk (чтобы retrieval был изолированным).
- `session_artifacts`: временные артефакты сессии до появления `story_id` (late binding).
### 4.3. Алгоритм индексации (delta-only)
1. На старте сессии сканировать рабочее дерево и считать `blob_sha` для файлов индексации.
2. Для каждого файла:
- `cache hit`: взять chunk/embedding из кэша и связать с текущей сессией.
- `cache miss`: выполнить chunk+embed и записать результат в кэш.
3. Для retrieval использовать `rag_session_chunk_map` как первичный источник.
4. При необходимости делать fallback к cache-scoped данным по `repo_id` (опционально, под флагом).
### 4.4. Почему это подходит
- Нет необходимости в сложном ACL общего RAG на уровне приложения.
- Нет обязательной зависимости от ручного commit, индекс отражает локальные изменения.
- Снижается время загрузки сессии за счет переиспользования эмбеддингов.
- История Story не теряется и не зависит от режима RAG.
### 4.5. Late binding `story_id` (целевой процесс)
1. Аналитик запускает работу только со ссылкой на документ (без `story_id`).
2. Агент обрабатывает задачу в `session-RAG` и сохраняет все изменения в `session_artifacts`.
3. Аналитик вручную делает commit и указывает `story_id`.
4. Вебхук на commit:
- извлекает `story_id` из commit metadata/message,
- обновляет репозиторный RAG,
- выполняет `bind session -> story`: переносит/привязывает `session_artifacts` к `story_artifacts`,
- фиксирует связь `story_id <-> commit_sha <-> changed_files`.
5. Исходный документ аналитики тоже попадает в контекст Story ретроспективно, даже если изначально был без `story_id`.
## 5) Черновик DDL (PostgreSQL)
```sql
-- 0. Enum-like checks можно заменить на справочники при необходимости
-- A) Session artifacts (временный слой до появления story_id)
CREATE TABLE IF NOT EXISTS session_artifacts (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
project_id TEXT NOT NULL,
artifact_role TEXT NOT NULL,
source_ref TEXT,
doc_id TEXT,
doc_version TEXT,
path TEXT,
section TEXT,
chunk_id TEXT,
change_type TEXT,
summary TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
CONSTRAINT chk_session_artifact_role CHECK (artifact_role IN (
'analysis','doc_change','note','decision','risk','test_model'
)),
CONSTRAINT chk_session_change_type CHECK (change_type IS NULL OR change_type IN (
'added','updated','removed','linked'
))
);
CREATE INDEX IF NOT EXISTS idx_session_artifacts_session ON session_artifacts(session_id);
CREATE INDEX IF NOT EXISTS idx_session_artifacts_project ON session_artifacts(project_id);
CREATE INDEX IF NOT EXISTS idx_session_artifacts_role ON session_artifacts(artifact_role);
-- 1) Story records
CREATE TABLE IF NOT EXISTS story_records (
story_id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
title TEXT,
status TEXT NOT NULL DEFAULT 'draft',
baseline_commit_sha TEXT,
snapshot_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_by TEXT,
CONSTRAINT chk_story_status CHECK (status IN (
'draft','in_progress','review','done','archived'
))
);
CREATE INDEX IF NOT EXISTS idx_story_records_project ON story_records(project_id);
CREATE INDEX IF NOT EXISTS idx_story_records_status ON story_records(status);
CREATE INDEX IF NOT EXISTS idx_story_records_updated_at ON story_records(updated_at DESC);
-- 2) Story artifacts
CREATE TABLE IF NOT EXISTS story_artifacts (
id BIGSERIAL PRIMARY KEY,
story_id TEXT NOT NULL REFERENCES story_records(story_id) ON DELETE CASCADE,
artifact_role TEXT NOT NULL,
doc_id TEXT,
doc_version TEXT,
path TEXT,
section TEXT,
chunk_id TEXT,
change_type TEXT,
summary TEXT,
source_ref TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
CONSTRAINT chk_story_artifact_role CHECK (artifact_role IN (
'requirement','analysis','doc_change','test_model','note','decision','risk'
)),
CONSTRAINT chk_story_change_type CHECK (change_type IS NULL OR change_type IN (
'added','updated','removed','linked'
))
);
CREATE INDEX IF NOT EXISTS idx_story_artifacts_story_role ON story_artifacts(story_id, artifact_role);
CREATE INDEX IF NOT EXISTS idx_story_artifacts_story_change ON story_artifacts(story_id, change_type);
CREATE INDEX IF NOT EXISTS idx_story_artifacts_doc ON story_artifacts(doc_id, doc_version);
CREATE INDEX IF NOT EXISTS idx_story_artifacts_path ON story_artifacts(path);
-- Вариант уникальности можно уточнить после согласования процессов
-- 3) Story links
CREATE TABLE IF NOT EXISTS story_links (
id BIGSERIAL PRIMARY KEY,
story_id TEXT NOT NULL REFERENCES story_records(story_id) ON DELETE CASCADE,
link_type TEXT NOT NULL,
target_ref TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_story_link_type CHECK (link_type IN (
'story','adr','ticket','pr','commit','doc','external'
))
);
CREATE INDEX IF NOT EXISTS idx_story_links_story_type ON story_links(story_id, link_type);
CREATE INDEX IF NOT EXISTS idx_story_links_target_ref ON story_links(target_ref);
```
## 6) План внедрения (после согласования)
1. Подтвердить перечень полей и enum-значений.
2. Подготовить SQL-миграцию `Vxxx__story_context.sql`.
3. Обновить bootstrap/инициализацию схемы.
4. Обновить репозитории для `story_records/story_artifacts/story_links`.
5. Добавить таблицу и репозиторий `session_artifacts` (session-scoped артефакты без `story_id`).
6. Добавить запись session-артефактов в оркестраторе во время работы аналитика.
7. Добавить webhook-обработчик `bind session -> story` при появлении commit со `story_id`.
8. Добавить API/сервисный метод `get_story_context(story_id)` для повторного входа в Story.
9. Добавить тесты:
- unit на репозитории,
- интеграционные на happy-path записи/чтения,
- регресс на отсутствие зависимости от типа RAG.
10. Добавить миграцию для `rag_blob_cache/rag_chunk_cache/rag_session_chunk_map`.
11. Внедрить `delta-only` индексацию для session-RAG с переиспользованием кэша.
## 7) Открытые вопросы
- Нужен ли отдельный справочник для `artifact_type`, `artifact_role`, `link_type`.
- Что считать `doc_version`: semver, дата, commit, hash файла.
- Нужна ли soft-delete политика для Story.
- Требуется ли аудит (кто/когда менял `summary` и связи).
- Какой уровень обязательности `chunk_id` (опционален по умолчанию).
- Нужна ли TTL/очистка для `rag_blob_cache/rag_chunk_cache`.
- Делать ли fallback к репозиторному кэшу по умолчанию или только при explicit-флаге.
- Как определять соответствие `session_id` и commit в webhook (1:1, последний активный, explicit token).
- Как долго хранить `session_artifacts` до bind/cleanup.
## 8) Критерии готовности
- По `story_id` можно восстановить инкремент без исходной сессии.
- История изменений не теряется при переиндексации RAG.
- Аналитик и тестировщик используют один `story_id` как общий ключ контекста.
- Схема работает при любом выбранном режиме RAG.
- Session-RAG поднимается быстрее за счет cache hit по неизмененным файлам.
- Артефакты аналитика, созданные до появления `story_id`, корректно попадают в Story после commit/webhook bind.
+161
View File
@@ -0,0 +1,161 @@
# Агент для работы с проектной документацией
## 1. Общее описание
Приложение представляет собой backend агентного режима для работы с документацией и кодом проекта.
Система решает следующие задачи:
- индексирует локальную копию проекта в `rag_session` и использует ее как основной рабочий контекст пользователя;
- принимает webhook коммитов репозитория в `rag_repo` и фиксирует контекст изменений по `story_id`;
- ускоряет построение `rag_session` за счет переиспользования кэша чанков и эмбеддингов из `rag_repo`;
- обрабатывает пользовательские запросы через `chat`, `agent`, оркестратор и специализированные графы;
- сохраняет quality-метрики, Story-контекст и артефакты сессии в PostgreSQL.
Ключевая идея архитектуры:
- `rag_session` отвечает за пользовательскую рабочую сессию и всегда остается основным источником retrieval;
- `rag_repo` не участвует напрямую в пользовательском ответе, а служит фоновым источником кэша и контекста коммитов;
- `story_id` связывает изменения аналитика, документацию и последующую работу тестировщика.
## 2. Архитектура
```mermaid
flowchart LR
User["Пользователь"]
Git["Git репозиторий\n(Gitea / Bitbucket)"]
Chat["Модуль chat"]
Agent["Модуль agent"]
RagSession["Модуль rag_session"]
RagRepo["Модуль rag_repo"]
Shared["Модуль shared"]
DB["PostgreSQL + pgvector"]
Giga["GigaChat API"]
User --> Chat
Chat --> Agent
Agent --> RagSession
Agent --> Shared
RagSession --> Shared
RagRepo --> Shared
Chat --> DB
Agent --> DB
RagSession --> DB
RagRepo --> DB
RagSession --> Giga
Agent --> Giga
Git --> RagRepo
RagRepo -.кэш и контекст коммитов.-> RagSession
```
Кратко по ролям модулей:
- `chat` — внешний API чата, фоновые задачи, SSE события, диалоги.
- `agent` — роутер интентов, оркестратор, графы, tools, генерация ответа и changeset.
- `rag_session` — создание и сопровождение пользовательского RAG индекса по локальным файлам.
- `rag_repo` — прием webhook коммитов, определение `story_id`, фиксация контекста коммита и заполнение repo-cache.
- `shared` — инфраструктурный слой: БД, retry, event bus, GigaChat client, настройки.
## 3. Типичный флоу
```mermaid
sequenceDiagram
actor User as Пользователь
participant Git as Git репозиторий
participant RagRepo as Модуль rag_repo
participant DB as PostgreSQL
participant RagSession as Модуль rag_session
participant Chat as Модуль chat
participant Agent as Модуль agent
Note over User,RagSession: Первая рабочая сессия: кэша репозитория еще нет
User->>RagSession: Создание rag_session и загрузка файлов проекта
RagSession->>DB: Проверка cache hit/miss
DB-->>RagSession: Кэш пуст
RagSession->>RagSession: Чанкинг и расчет embeddings без repo-cache
RagSession->>DB: Сохранение rag_chunks и rag_index_jobs
User->>Chat: Отправка запроса в чат
Chat->>Agent: Передача задачи
Agent->>RagSession: Retrieval контекста
RagSession->>DB: Чтение rag_chunks
DB-->>RagSession: Релевантные чанки
RagSession-->>Agent: Контекст
Agent-->>Chat: Ответ / changeset
Note over User,Git: Пользователь вручную делает commit и push
Git->>RagRepo: Webhook push
RagRepo->>RagRepo: Определение provider, commit_sha, changed_files, story_id
RagRepo->>DB: Запись story_records/story_links/story_artifacts
RagRepo->>DB: Запись rag_blob_cache/rag_chunk_cache/rag_session_chunk_map
Note over User,RagSession: Вторая рабочая сессия: repo-cache уже существует
User->>RagSession: Создание новой rag_session и загрузка файлов проекта
RagSession->>DB: Проверка cache hit/miss
DB-->>RagSession: Найдены cache hit по части файлов
RagSession->>RagSession: Переиспользование чанков из rag_repo cache
RagSession->>RagSession: Пересчет embeddings только для cache miss файлов
RagSession->>DB: Сохранение rag_chunks, rag_session_chunk_map, rag_index_jobs
User->>Chat: Новый запрос
Chat->>Agent: Передача задачи
Agent->>RagSession: Retrieval по новой сессии
RagSession-->>Agent: Контекст из session-RAG
Agent-->>Chat: Ответ / changeset
```
Что важно в этом сценарии:
- первый запуск индексации может быть полностью без кэша;
- после коммита `rag_repo` фиксирует контекст изменений и наполняет cache-таблицы;
- во второй и последующих сессиях `rag_session` использует `cache_hit_files`, чтобы уменьшить объем новых embeddings.
## 4. Инструкции к запуску
### Локальный запуск
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 15000
```
### Запуск через Docker Compose
1. Создать `.env` на основе примера:
```bash
cp .env.example .env
```
2. Заполнить как минимум `GIGACHAT_TOKEN`.
3. Запустить сервисы:
```bash
docker compose up -d --build
```
4. Проверить доступность backend:
```bash
curl http://localhost:15000/health
```
Ожидаемый ответ:
```json
{"status":"ok"}
```
### Основные адреса
- Backend API: `http://localhost:15000`
- PostgreSQL + pgvector: `localhost:5432`
- Webhook репозитория: `POST /internal/rag-repo/webhook`
### Базовый сценарий проверки
1. Создать `rag_session` через `POST /api/rag/sessions`.
2. Дождаться завершения index-job через `GET /api/rag/sessions/{rag_session_id}/jobs/{index_job_id}`.
3. Создать диалог через `POST /api/chat/dialogs`.
4. Отправить сообщение через `POST /api/chat/messages`.
5. Настроить webhook репозитория на `POST /internal/rag-repo/webhook`.
6. Сделать commit с `story_id` в сообщении, например `FEAT-1 ...`.
7. Проверить заполнение таблиц:
- `story_records`, `story_links`, `story_artifacts`
- `rag_blob_cache`, `rag_chunk_cache`, `rag_session_chunk_map`
8. Во второй сессии индексации проверить поля job-статуса:
- `indexed_files`
- `cache_hit_files`
- `cache_miss_files`
### Полезные замечания
- Текущая chat-модель: `GigaChat`.
- Основной retrieval всегда идет из `rag_session`.
- `rag_repo` используется как фоновый источник кэша и контекста коммитов.
- Если в webhook не найден `story_id`, commit-контекст Story не будет привязан, но cache-таблицы все равно должны наполняться.
+33
View File
@@ -0,0 +1,33 @@
# Agent Rules v1
## 1. Evidence-first
Агент должен формировать документацию только на основе подтвержденных источников: кода, существующей документации, системной аналитики и других явно доступных артефактов. Нельзя додумывать поведение системы, зависимости или бизнес-логику, если они не подтверждаются исходными материалами.
## 2. One Stable Object = One Document
Каждый документ должен описывать только одну устойчивую техническую сущность или один устойчивый аспект системы. Если по сущности могут ссылаться другие документы или она может переиспользоваться, ее нужно выносить в отдельный документ.
## 3. No Semantic Duplication
Документы не должны пересекаться по смыслу и повторять одно и то же содержание. Если одна и та же логика, правило или описание нужны в нескольких местах, агент должен вынести их в отдельный документ и использовать ссылки вместо дублирования текста.
## 4. Explicit Document Type
Для каждого документа агент должен явно определять его тип. На базовом уровне нужно использовать типы `ui_page`, `api_method`, `logic_block` и применять для каждого типа свой шаблон содержания и набор метаданных.
## 5. Hierarchical File Structure
Агент должен строить документацию как иерархию каталогов и файлов, а не как плоский набор страниц. Документы нужно раскладывать по смысловым разделам, например `docs/ui`, `docs/api`, `docs/logic`, `docs/architecture`, чтобы структура отражала архитектуру и упрощала навигацию.
## 6. Required YAML Frontmatter
Каждый документ должен начинаться с единообразного `YAML frontmatter`. В нем обязательно должны быть базовые метаданные документа: стабильный `id`, `title`, `doc_type`, `status`, `source_of_truth`, а также ссылки на связанные документы и кодовые артефакты.
## 7. Correct Internal Decomposition
Содержимое документа должно следовать шаблону своего типа и быть правильно декомпозировано внутри самого документа. Сценарии работы нужно описывать отдельно от детальных правил, контрактов, ограничений и дополнительных требований, чтобы документ оставался читаемым, атомарным и пригодным для индексирования.
## 8. Explicit Links Between Code and Docs
Каждый документ должен быть явно связан как минимум с соответствующим кодом и с соседними документами, если такие связи существуют. Агент должен фиксировать эти связи в метаданных и в тексте документа, чтобы документация образовывала связанную систему знаний, а не набор изолированных файлов.
+422
View File
@@ -0,0 +1,422 @@
# Архитектура агента As Is — отчёт
## 1. Executive Summary
- **Текущее устройство.** Запрос пользователя принимается в модуле `chat` (POST `/api/chat/messages`), ставится в очередь задач и обрабатывается асинхронно. Обработку выполняет `GraphAgentRuntime`: вызывается **router агента** (не IntentRouterV2), по его решению выбирается сценарий и план шагов оркестратора; шаги выполняются как вызовы функций или подграфов LangGraph. Итог — ответ или changeset в задаче и в событии `task_result`.
- **Два режима входа.** При `SIMPLE_CODE_EXPLAIN_ONLY=true` (по умолчанию) запросы идут в **CodeExplainChatService**: без роутера, сразу retrieval через CodeExplainRetrieverV2, evidence gate и один промпт. При `false` используется полный агент с роутером и оркестратором.
- **Router.** В продакшене используется **RouterService** из `app/modules/agent/engine/router/`: классификатор интентов (эвристики + LLM), регистр графов по `domain_id`/`process_id`, контекст диалога в AgentRepository. **IntentRouterV2** (модуль `rag/intent_router_v2`) в основном приложении **не вызывается** — только в тестах и пайплайне `tests/pipeline_intent_rag`.
- **Retrieval.** В основном флоу агента **RAG по запросу в рантайме не вызывается**: в `GraphAgentRuntime.run` переменная `rag_ctx` задаётся как пустой список и не заполняется; граф `project_qa/context_retrieval` получает этот пустой список и строит `source_bundle` только из `files_map` (вложения пользователя). Реальный retrieval по индексу выполняется только: (1) в режиме **direct code explain** (CodeExplainRetrieverV2 → LayeredRetrievalGateway → RagRepository); (2) внутри шага **build_code_explain_pack** при сценарии EXPLAIN_PART (тот же CodeExplainRetrieverV2). Контракт `RagRetriever` (async `retrieve(rag_session_id, query)`) в коде **нигде не реализован**: RagService умеет только индексировать, не извлекать.
- **Индексация.** При создании/обновлении RAG-сессии используется RagService с двумя пайплайнами: CodeIndexingPipeline (C0C4: chunks, symbols, edges, entrypoints, traces, semantic roles) и DocsIndexingPipeline (D1D4: module catalog, facts, sections, policy). Слои попадают в `rag_chunks`; retrieval по слоям делается только через RagRepository (LayeredRetrievalGateway), не через RagService.
- **Графы и пайплайны.** Есть общий оркестратор (ScenarioTemplateRegistry → PlanCompiler → ExecutionEngine). Сценарии: GENERAL_QA (в т.ч. project/qa как подплан project_qa), EXPLAIN_PART, ANALYTICS_REVIEW, DOCS_FROM_ANALYTICS, TARGETED_EDIT, GHERKIN_MODEL. Для project/qa выполняется цепочка подграфов: conversation_understanding → question_classification → context_retrieval (без вызова RAG) → опционально build_code_explain_pack → context_analysis → answer_composition. Отдельные графы зарегистрированы для default/general, project/qa, project/edits, docs/generation и для подшагов project_qa.
- **Сборка контекста для LLM.** В основном флоу в модель попадают: `rag_context` (из task_spec; по факту пустой), `confluence_context` (страницы из вложений), `files_map` (переданные пользователем файлы). Контекст по коду из индекса собирается только в direct code explain и в build_code_explain_pack (ExplainPack → PromptBudgeter).
- **Диагностика.** В приложении: логи (router decision, graph step result, code explain pack, orchestrator decision), события прогресса (task_progress с stage/message/meta), в `task_result` — meta (route, used_rag, orchestrator_steps, quality). Отдельная структура диагностики (router_plan, execution, retrieval, timings_ms, constraint_violations) реализована в тестовом пайплайне под IntentRouterV2 и в ответ не отдаётся.
- **Главные пробелы.** (1) Нет вызова RAG в основном агентском флоу — context_retrieval не использует индекс. (2) IntentRouterV2 и его retrieval/diagnostics не интегрированы в продакшен. (3) RagService не реализует контракт RagRetriever. (4) Целевые слои C5 (Test Mappings), C6 (Code Facts), D5 (Reference Graph), D6 (Doc-Code Links) в индексации и retrieval не представлены. (5) Evidence gate есть только у direct code explain; в оркестраторе quality gates проверяют артефакты шагов, но не «достаточность evidence» в целевом смысле.
- **Близость к целевой архитектуре.** Частично: индексация уже многослойная (CODE C0C4, DOCS D1D4), есть задел intent-based роутинга (IntentRouterV2 в тестах), оркестрация по сценариям и графам есть. Не хватает: подключения retrieval к основному флоу, объединения роутера с intent/retrieval-спеками, полного набора слоёв и явного evidence gate в оркестраторе.
---
## 2. End-to-End Flow As Is
### 2.1. Request intake
**Что происходит.** Клиент шлёт POST `/api/chat/messages` (ChatMessageRequest: message, mode, attachments, files, dialog_session_id и т.д.). ChatModule при `SIMPLE_CODE_EXPLAIN_ONLY=true` отдаёт запрос в CodeExplainChatService.handle_message; иначе — в ChatOrchestrator.enqueue_message. В последнем случае создаётся задача (TaskStore), по idempotency может возвращаться существующая, затем в фоне запускается _process_task.
**Evidence.** `app/modules/chat/module.py` (send_message, условие по `_simple_code_explain_only`), `app/modules/chat/service.py` (enqueue_message, _process_task, _resolve_sessions).
**Комментарий.** Точка входа одна; разветвление по флагу окружения определяет упрощённый (direct) или полный (agent) путь.
### 2.2. Routing
**Что происходит.** В полном пути GraphAgentRuntime.run вызывает `self._router.resolve(message, dialog_session_id, mode)`. Это RouterService (agent): читает контекст из RouterContextStore (AgentRepository), при принудительном mode возвращает маршрут из маппинга (project_qa, project_edits, docs_generation и др.), иначе — IntentClassifier: короткие подтверждения → last_routing, детерминированные правила (редакт файла, запрос документации) → соответствующий route, в остальных случаях — LLM (prompt "router_intent") с разбором JSON. Результат — RouteResolution (domain_id, process_id, confidence, reason, fallback_used). При низкой уверенности или невалидной паре (domain_id, process_id) возвращается fallback default/general. Graph factory выбирается по (domain_id, process_id) из IntentRegistry.
**Evidence.** `app/modules/agent/service.py` (run, _resolve_graph), `app/modules/agent/engine/router/router_service.py` (resolve, _is_acceptable, _resolution, _fallback), `app/modules/agent/engine/router/intent_classifier.py` (classify_new_intent, from_mode, _deterministic_route, _classify_with_llm), `app/modules/agent/engine/router/__init__.py` (build_router_service, регистрация графов).
**Комментарий.** IntentRouterV2 в этом флоу не участвует. graph_id в продакшене — это пара (domain_id, process_id), а не строка из IntentRouterV2.
### 2.3. Retrieval
**Что происходит.** В GraphAgentRuntime.run перед вызовом оркестратора `rag_ctx` инициализируется как `[]` и не заполняется. TaskSpec получает rag_items=rag_ctx и rag_context=_format_rag(rag_ctx) — т.е. пустой контекст. В плане project_qa шаг context_retrieval (граф ProjectQaRetrievalGraphFactory) в _retrieve_context читает state (resolved_request, question_profile, files_map), объявляет `rag_items: list[dict] = []`, вызывает build_source_bundle(profile, rag_items, files_map) — т.е. только ранжирование переданных файлов, без вызова RAG. Реальный retrieval: (1) CodeExplainChatService — CodeExplainRetrieverV2.build_pack → LayeredRetrievalGateway (C3, C1, C2, C0, lexical fallback); (2) шаг build_code_explain_pack в оркестраторе — тот же build_pack с file_candidates из source_bundle.
**Evidence.** `app/modules/agent/service.py` (rag_ctx: list[dict] = [], task_spec), `app/modules/agent/engine/graphs/project_qa_step_graphs.py` (ProjectQaRetrievalGraphFactory._retrieve_context, rag_items=[], build_source_bundle), `app/modules/rag/explain/retriever_v2.py` (build_pack, _entrypoints, _seed_symbols, _trace_builder, _excerpt_fetcher, lexical fallback), `app/modules/rag/explain/layered_gateway.py` (retrieve_layer, retrieve_lexical_code → repository).
**Комментарий.** Намеренно не меняя код: в as is основной агентский пайплайн не выполняет retrieval по индексу; контракт RagRetriever не реализован (RagService не имеет метода retrieve).
### 2.4. Context assembly
**Что происходит.** Для основного агента контекст для шагов собирается в TaskSpecBuilder: metadata содержит rag_items, rag_context, confluence_context, files_map. rag_context формируется в agent как _format_rag(rag_ctx) — при пустом rag_ctx это пустая строка. Confluence — из вложений типа confluence_url. В шагах оркестратора (collect_state) в agent_state попадает этот metadata; графы получают state с rag_context, confluence_context, files_map. Для ответа LLM контекст собирается внутри графов: в project_qa — ProjectQaSupport (build_answer_brief, compose_answer) и при наличии explain_pack — PromptBudgeter.build_prompt_input + code_explain_answer_v2. В direct code explain контекст — только ExplainPack через PromptBudgeter.
**Evidence.** `app/modules/agent/service.py` (_format_rag, _format_confluence, task_spec), `app/modules/agent/engine/orchestrator/step_registry.py` (_collect_state, agent_state), `app/modules/agent/engine/graphs/project_qa_step_graphs.py` (ProjectQaAnswerGraphFactory._compose_answer, _compose_explain_answer), `app/modules/rag/explain` (PromptBudgeter).
**Комментарий.** Разделения «intent-driven» сборки контекста по слоям в основном флоу нет; слои используются только в CodeExplainRetrieverV2 и при формировании промпта code_explain_answer_v2.
### 2.5. LLM answer synthesis
**Что происходит.** Ответ генерируется внутри графов и шагов оркестратора. Для default/general — граф, собранный BaseGraphFactory(llm). Для project/qa — цепочка подграфов, финальный ответ в ProjectQaAnswerGraphFactory (_compose_answer: при наличии explain_pack — LLM code_explain_answer_v2, иначе ProjectQaSupport.compose_answer по brief). Для direct code explain — один вызов LLM code_explain_answer_v2 после evidence gate. AgentLlmService.generate вызывается с ключом промпта (router_intent, code_explain_answer_v2 и т.д.) и payload; промпты загружаются через PromptLoader.
**Evidence.** `app/modules/agent/engine/graphs/project_qa_step_graphs.py` (ProjectQaAnswerGraphFactory), `app/modules/agent/llm` (AgentLlmService), `app/modules/chat/direct_service.py` (CodeExplainChatService), `app/modules/agent/engine/graphs` (фабрики графов).
**Комментарий.** Итоговый ответ и/или changeset собираются ResultAssembler из артефактов шагов (final_answer, final_changeset).
### 2.6. Diagnostics
**Что происходит.** В рантайме: логи (router decision, graph step result, code explain pack, orchestrator decision); события task_progress (stage, message, meta); в конце — task_result с meta (route, used_rag, used_confluence, orchestrator_steps, quality при наличии). Quality метрики при наличии записываются MetricsPersister в agent_repository. Структурированная диагностика (router_plan, execution, retrieval, timings_ms, constraint_violations) строится в тестах pipeline_intent_rag (helpers/diagnostics) для результата IntentRouterV2 и в ответ API не входит.
**Evidence.** `app/modules/agent/service.py` (LOGGER.warning по route/orchestrator, meta в AgentResult, _persist_quality_metrics), `app/modules/chat/service.py` (_publish_progress, task_result), `tests/pipeline_intent_rag/helpers/diagnostics.py` (build_router_plan, init_diagnostics, apply_retrieval_report, validate_constraints).
**Комментарий.** used_rag в meta всегда False в текущем коде, т.к. rag_ctx не заполняется.
---
## 3. Router As Is
### 3.1. Main router entrypoints
Единственная точка входа роутера в основном приложении — `RouterService.resolve(user_message, conversation_key, mode)` в `app/modules/agent/engine/router/router_service.py`. Сборка: `build_router_service` в `app/modules/agent/engine/router/__init__.py` (IntentRegistry, IntentClassifier, RouterContextStore, IntentSwitchDetector, RouterService).
### 3.2. Input contract
- **Вход:** `user_message: str`, `conversation_key: str` (dialog_session_id), `mode: str` (например "auto", "project_qa", "project_edits", "docs_generation").
- **Контекст:** из RouterContextStore по conversation_key — RouterContext (last_routing, message_history, active_intent, dialog_started, turn_index). Контекст обновляется при persist_context после ответа агента.
**Evidence.** `app/modules/agent/engine/router/router_service.py` (resolve, context = self._ctx.get), `app/modules/agent/engine/router/schemas.py` (RouterContext).
### 3.3. Output contract
RouteResolution: domain_id, process_id, confidence, reason, fallback_used, decision_type, explicit_switch. Фабрика графа запрашивается отдельно: graph_factory(domain_id, process_id).
**Evidence.** `app/modules/agent/engine/router/schemas.py` (RouteResolution), `app/modules/agent/engine/router/router_service.py` (_resolution, _fallback, _continue_current, graph_factory).
### 3.4. Supported intents and sub-intents
**Намерения (domain_id / process_id):** default/general, project/qa, project/edits, docs/generation. Внутри оркестратора сценарий (Scenario) определяется TaskSpecBuilder по mode, route и тексту сообщения: GENERAL_QA, EXPLAIN_PART, ANALYTICS_REVIEW, DOCS_FROM_ANALYTICS, TARGETED_EDIT, GHERKIN_MODEL. Подграфы project_qa зарегистрированы как project_qa/conversation_understanding, question_classification, context_retrieval, context_analysis, answer_composition. Sub-intent в смысле IntentRouterV2 (EXPLAIN, OPEN_FILE и т.д.) в этом роутере не фигурируют.
**Evidence.** `app/modules/agent/engine/router/__init__.py` (registry.register), `app/modules/agent/engine/router/intent_classifier.py` (_route_mapping, from_mode), `app/modules/agent/engine/orchestrator/task_spec_builder.py` (_detect_scenario).
### 3.5. Graph selection logic
Граф выбирается по (domain_id, process_id). Для основного запроса оркестратор по сценарию решает, какой план выполнять; в плане шаги могут иметь graph_id="route" (тогда берётся domain_id/process_id из task.routing) или graph_id вида "project_qa/context_retrieval". Резолвер графа: _resolve_graph в GraphAgentRuntime — factory = self._router.graph_factory(domain_id, process_id), fallback на default/general.
**Evidence.** `app/modules/agent/service.py` (_resolve_graph), `app/modules/agent/engine/orchestrator/step_registry.py` (_execute_graph_step, graph_key), `app/modules/agent/engine/orchestrator/template_registry.py` (graph_id в шагах).
### 3.6. Heuristics / LLM / deterministic logic
- Детерминировано: короткие подтверждения → last_routing; целевой редакт файла (_is_targeted_file_edit_request); запрос документации (_is_broad_docs_request) → project/edits, docs/generation.
- LLM: один вызов при отсутствии детерминированного решения — prompt "router_intent", ожидается JSON с route, confidence, reason; парсинг с допуском code fence.
- Эвристики: min_confidence=0.7, проверка registry.is_valid(domain_id, process_id); при переключении интента — IntentSwitchDetector.should_switch, при отказе — _continue_current.
**Evidence.** `app/modules/agent/engine/router/intent_classifier.py`, `app/modules/agent/engine/router/router_service.py`.
### 3.7. Current limitations
- Нет keyword_hints, path_scope, layers в выходном контракте роутера; они не передаются в retrieval, т.к. retrieval в основном флоу не вызывается.
- Контекст диалога ограничен last_routing и message_history; нет структуры query plan / anchors как в IntentRouterV2.
- IntentRouterV2 с полным контрактом (intent, sub_intent, graph_id, retrieval_spec, retrieval_constraints, evidence_policy) в приложении не используется.
| Router capability | Current state | Status | Evidence | Notes |
|-------------------------|---------------|--------|---------------------------------------------------------------------------|------------------------------------------------------|
| Main entrypoint | RouterService.resolve | full | router_service.py | |
| Input: message, session, mode | Yes | full | router_service.py | |
| Output: domain_id, process_id, confidence, reason | Yes | full | RouteResolution, schemas.py | |
| intent / sub-intent | domain/process only | partial | intent_classifier _route_mapping; no sub_intent in agent router | Sub-intent только в IntentRouterV2 |
| graph_id | (domain_id, process_id) | full | registry.get_factory, _resolve_graph | |
| conversation_mode | decision_type (start/continue/switch) | partial | RouteResolution.decision_type | Не буквально CONTINUE/FOLLOWUP_LIKELY |
| keyword_hints | — | none | — | Есть в IntentRouterV2.query_plan |
| path_scope | — | none | — | Есть в IntentRouterV2.retrieval_spec.filters |
| layers | — | none | — | Есть в IntentRouterV2 |
| Heuristics + LLM | Yes | full | intent_classifier | |
| Fallback default/general| Yes | full | _fallback, _is_acceptable | |
---
## 4. Retrieval / RAG As Is
### 4.1. Code retrieval as is
- **Где выполняется:** только при direct code explain (CodeExplainChatService) и в шаге build_code_explain_pack (сценарий EXPLAIN_PART). Оба пути используют CodeExplainRetrieverV2 → LayeredRetrievalGateway → RagRepository.
- **Chunking:** при индексации — CodeTextChunker (code), при retrieval — C0_SOURCE_CHUNKS и lexical fallback по тексту запроса.
- **Symbol-based:** C1_SYMBOL_CATALOG через retrieve_layer; после entrypoints подтягиваются handler symbols через CodeGraphRepository.get_symbols_by_ids.
- **Path-aware:** path_prefixes передаются в retrieve_layer и retrieve_lexical_code (из intent/file_candidates).
- **Entrypoints:** C3_ENTRYPOINTS, фильтрация по entry_type из intent.
- **Test-specific:** exclude_tests в gateway; при малом числе excerpts делается _merge_test_fallback с include_tests.
- **Структурные связи:** C2 (edges), TraceBuilder строит пути по графу; SourceExcerptFetcher по путям достаёт фрагменты. Execution traces индексируются (entrypoints + execution_trace_document_builder).
В основном агентском флоу (project_qa/context_retrieval) вызова RAG нет: rag_items пустые, source_bundle строится только из files_map.
**Evidence.** `app/modules/rag/explain/retriever_v2.py`, `app/modules/rag/explain/layered_gateway.py`, `app/modules/rag/persistence/repository.py`, `app/modules/agent/engine/graphs/project_qa_step_graphs.py` (ProjectQaRetrievalGraphFactory).
### 4.2. Docs retrieval as is
- **Парсинг документов:** DocsIndexingPipeline при индексации: frontmatter, MarkdownDocChunker, DocsClassifier (doc_kind), build_module_catalog, build_section, build_policy, _extract_facts. Слои: D1_MODULE_CATALOG, D2_FACT_INDEX, D3_SECTION_INDEX, D4_POLICY_INDEX (enums).
- **Retrieval по документам:** в основном приложении отдельного «docs retrieval» по запросу пользователя не вызывается. LayeredRetrievalGateway и RetrievalStatementBuilder поддерживают слои D1–D4 в запросах (layer_rank_sql), но код, который бы по intent «DOCS_QA» или «GENERATE_DOCS_FROM_CODE» вызывал только docs-слои, в основном флоу не прослеживается — IntentRouterV2 с retrieval_profile "docs" используется только в тестах.
- **Doc-to-code linking:** явного слоя или индекса D6 (Doc-Code Links) нет. В индексации есть ссылки в документах (frontmatter links), но отдельного кросс-доменного индекса нет.
**Evidence.** `app/modules/rag/indexing/docs/pipeline.py`, `app/modules/rag/contracts/enums.py`, `app/modules/rag/persistence/retrieval_statement_builder.py` (D1D4 в SQL).
### 4.3. Map current implementation to target layers
| Target element | Current implementation | Status | Evidence | Notes |
|----------------|------------------------|--------|----------|--------|
| CODE / C0 Source Chunks | CodeTextChunker + CodeTextDocumentBuilder, слой C0_SOURCE_CHUNKS; retrieval через retrieve_lexical_code и retrieve(..., layers=[C0]) | full | code_text/document_builder.py, pipeline.py, retrieval_statement_builder.py, layered_gateway | |
| CODE / C1 Symbol Catalog | SymbolExtractor + SymbolDocumentBuilder, C1_SYMBOL_CATALOG; retrieval в retriever_v2 _seed_symbols | full | indexing/code/symbols/, enums.py, retriever_v2.py | |
| CODE / C2 Symbol Relations | EdgeExtractor, EdgeDocumentBuilder, DataflowDocumentBuilder; слой C2_DEPENDENCY_GRAPH; TraceBuilder использует граф | full | indexing/code/edges/, graph_repository.py | Целевое имя «Symbol Relations»; в коде «dependency/dataflow» |
| CODE / C3 Entrypoints | EntrypointDetectorRegistry (FastAPI, Flask, Typer/Click), EntrypointDocumentBuilder, execution_trace; C3_ENTRYPOINTS | full | indexing/code/entrypoints/, retriever_v2 _entrypoints | |
| CODE / C4 Execution Paths | Execution traces индексируются в C2 (execution_trace); пути строятся в рантайме TraceBuilder по графу | partial | execution_trace_builder, trace_builder.py, retriever_v2 | Нет отдельного слоя «C4 Execution Paths»; логика есть |
| CODE / C5 Test Mappings | Нет отдельного слоя или индекса тест→код | none | — | test_filter только исключает/включает пути |
| CODE / C6 Code Facts | Нет слоя «code facts» | none | — | |
| DOCS / D0 Document Chunks | Секции документов как D3_SECTION_INDEX; общего «D0» как аналога C0 нет | partial | docs pipeline build_section | Chunks по сути есть как section docs |
| DOCS / D1 Document Catalog | D1_MODULE_CATALOG, build_module_catalog | full | enums.py, docs/document_builder.py | |
| DOCS / D2 Fact Index | D2_FACT_INDEX, _extract_facts из frontmatter links | full | docs pipeline | |
| DOCS / D3 Entity Catalog | D3_SECTION_INDEX в коде; целевое «Entity Catalog» может отличаться | partial | enums.py DOCS_SECTION_INDEX | Именование не 1:1 |
| DOCS / D4 Workflow Index | D4_POLICY_INDEX (policy-документы) | partial | docs pipeline build_policy | Ограничено type=policy |
| DOCS / D5 Reference Graph | Нет отдельного графа ссылок между документами | none | — | |
| DOCS / D6 Doc-Code Links | Нет | none | — | |
---
## 5. Graphs / Pipelines As Is
### 5.1. List of existing graphs / pipelines
- **По (domain_id, process_id):** default/general (BaseGraphFactory), project/qa (ProjectQaGraphFactory), project/edits (ProjectEditsGraphFactory), docs/generation (DocsGraphFactory).
- **Подграфы project_qa:** project_qa/conversation_understanding (ProjectQaConversationGraphFactory), project_qa/question_classification (ProjectQaClassificationGraphFactory), project_qa/context_retrieval (ProjectQaRetrievalGraphFactory), project_qa/context_analysis (ProjectQaAnalysisGraphFactory), project_qa/answer_composition (ProjectQaAnswerGraphFactory).
Планы (ExecutionPlan) задаются ScenarioTemplateRegistry по сценарию: general_qa_v1, project_qa_reasoning_v1, explain_part_v1, analytics_review_v1, docs_from_analytics_v1, targeted_edit_v1, gherkin_model_v1.
**Evidence.** `app/modules/agent/engine/router/__init__.py`, `app/modules/agent/engine/orchestrator/template_registry.py`, `app/modules/agent/engine/graphs/`.
### 5.2. Shared orchestration logic
OrchestratorService: build template → compile plan → validate → ExecutionEngine.run(ctx) → ResultAssembler.assemble. Шаги выполняются StepRegistry: либо функция (action_id), либо executor="graph" с graph_id. Состояние передаётся через ExecutionContext (task, plan, artifacts, evidences, graph_resolver, graph_invoker). Общая логика: один сценарий на запрос, линейные/параллельные зависимости шагов.
**Evidence.** `app/modules/agent/engine/orchestrator/service.py`, `app/modules/agent/engine/orchestrator/execution_engine.py`, `app/modules/agent/engine/orchestrator/step_registry.py`.
### 5.3. Specialized logic
- project_qa: последовательность подграфов + опционально build_code_explain_pack; context_retrieval не вызывает RAG; context_analysis использует explain_pack или analyzer по profile (code/docs).
- explain_part: при route project/qa тот же project_qa_reasoning_v1 с добавлением шага build_code_explain_pack; анализ и ответ с ExplainPack.
- Остальные сценарии (review, docs, edit, gherkin) — свои шаги (fetch_source_doc, normalize_document, …), без слоёвого retrieval.
**Evidence.** template_registry.py _project_qa, _explain, _review, _docs, _edit, _gherkin; project_qa_step_graphs.py.
### 5.4. Retrieval failure behavior
В основном флоу retrieval не вызывается, поэтому «failure» не возникает. В CodeExplainRetrieverV2 при пустых entrypoints/symbols/traces/excerpts заполняется pack.missing, делается lexical fallback; при недостатке excerpts — test fallback. CodeExplainEvidenceGate при числе excerpts < min_excerpts возвращает passed=False и шаблонный ответ с диагностикой (без вызова LLM).
**Evidence.** retriever_v2.py (_run_pass, _merge_test_fallback), evidence_gate.py.
### 5.5. Fallback behavior
Router: при низкой уверенности или невалидной паре — fallback default/general. При явном переключении интента, если новый интент не принят — _continue_current. В retrieval (CodeExplainRetrieverV2): lexical fallback, затем при необходимости test fallback. Отдельного «fallback graph» или единого fallback-пайплайна при слабом retrieval в оркестраторе нет.
**Evidence.** router_service.py _fallback, _continue_current; retriever_v2.py.
### 5.6. Evidence sufficiency checks
CodeExplainEvidenceGate (direct code explain): проверка количества code_excerpts >= min_excerpts; при неуспехе — ответ без вызова LLM. В оркестраторе QualityGateRunner проверяет evidence_required по ctx.evidences; шаги вроде explain/review могут добавлять evidence через add_evidence, но в project_qa flow заполнение evidences не прослеживается в коде (collect_state не вызывает retrieval). Т.е. «evidence gate» в смысле «достаточно ли retrieval для ответа» есть только в direct code explain.
**Evidence.** `app/modules/chat/evidence_gate.py`, `app/modules/agent/engine/orchestrator/quality_gates.py` (_evidence_required), `app/modules/agent/engine/orchestrator/actions/common.py` (add_evidence).
| Pipeline / graph capability | Current state | Status | Evidence | Notes |
|-----------------------------|---------------|--------|----------|--------|
| List of graphs by route | Yes | full | router __init__, template_registry | |
| Shared orchestration | Yes | full | OrchestratorService, ExecutionEngine, StepRegistry | |
| project_qa subgraphs | Yes | full | project_qa_step_graphs, template_registry | |
| context_retrieval uses RAG | No | none | project_qa_step_graphs rag_items=[] | |
| Fallback default/general | Yes (router) | full | router_service | |
| Fallback on weak retrieval | Only direct explain (evidence gate) | partial | evidence_gate, retriever_v2 | |
| Evidence sufficiency in orchestrator | Gates есть, evidences в project_qa не наполняются из RAG | partial | quality_gates, step_registry | |
---
## 6. LLM Context Assembly As Is
### 6.1. Inputs to LLM
В основном флоу: message (user_message), rag_context (пустой), confluence_context (из вложений), files_map (из запроса). В state графов дополнительно: resolved_request, question_profile, source_bundle (rag_items + file_candidates; rag_items пустые), при EXPLAIN_PART — explain_pack (entrypoints, trace_paths, code_excerpts, missing). В direct code explain — только user message и ExplainPack (после build_pack и evidence gate).
**Evidence.** agent/service.py (task_spec, _format_rag), template_registry (metadata), project_qa_step_graphs (state), direct_service.py.
### 6.2. Context selection logic
Нет отдельного «context selection» по слоям в основном агенте. В CodeExplainRetrieverV2: порядок C3 → C1 → C2 (traces) → excerpts по путям; при нехватке — lexical C0, затем при необходимости тесты. Ограничение по объёму — в PromptBudgeter при сборке промпта.
**Evidence.** retriever_v2.py, explain/prompt_budgeter (если есть).
### 6.3. Prompt construction
AgentLlmService.generate(key, payload). Ключи: router_intent, code_explain_answer_v2 и др. Промпты загружаются через PromptLoader. Для code_explain_answer_v2 вход формирует PromptBudgeter.build_prompt_input(message, pack).
**Evidence.** app/modules/agent/llm, project_qa_step_graphs _compose_explain_answer, direct_service.
### 6.4. Noise control
Явного «noise control» в основном флоу нет. В CodeExplainRetrieverV2: ограничение числа entrypoints, seeds, trace depth, excerpts; PromptBudgeter ограничивает объём в промпте.
### 6.5. Handling tests
exclude_tests в LayeredRetrievalGateway и retrieval; при малом количестве excerpts — _merge_test_fallback с include_tests. В project_qa_support при build_source_bundle для file_candidates/rag_items применяется штраф за test path, если нет explicit_test в terms.
**Evidence.** layered_gateway (exclude_tests, _filter_args), retriever_v2 _merge_test_fallback, project_qa_support build_source_bundle.
### 6.6. Current weaknesses
- Пустой rag_context в основном флоу; контекст по коду только из вложений (files_map) и при сценарии explain — из explain_pack.
- Нет intent-driven выбора слоёв и объёмов контекста в оркестраторе.
- Prompt-level контракты (имена полей, лимиты) разбросаны по промптам и бюджетеру, не оформлены как единый контракт.
---
## 7. Diagnostics As Is
### 7.1. Existing diagnostic artifacts
- В приложении: объект meta в ответе задачи (route, used_rag, used_confluence, orchestrator_steps, changeset_filtered_out и т.д.); при наличии quality — сохраняются через MetricsPersister. События: task_status, task_progress (stage, message, meta), task_result, task_error.
- В тестовом пайплайне (IntentRouterV2): router_plan (intent, sub_intent, graph_id, layers, path_scope, keyword_hints, retrieval_constraints), execution (executed_layers, retrieval_mode_by_layer, top_k_by_layer, filters_by_layer, repo_scope), retrieval (requests, applied, fallback), timings_ms (router, retrieval_total, retrieval_by_layer, prompt_build, llm_call), constraint_violations, prompt (prompt_stats, evidence_summary).
**Evidence.** agent/service.py (AgentResult.meta, _persist_quality_metrics), chat/service.py (task_result, _publish_progress), tests/pipeline_intent_rag/helpers/diagnostics.py.
### 7.2. Where diagnostics are produced
- Router: логи (router decision с route, reason, confidence, fallback_used) в agent/service.py.
- Графы: логи (graph step result) в project_qa_step_graphs и др.
- Оркестратор: логи (orchestrator decision), meta в результате (orchestrator_steps, scenario).
- Quality: quality_meta из orchestrator_result.meta, сохраняется в БД при наличии.
- IntentRouterV2-диагностика: только в тестах (init_diagnostics, apply_retrieval_report, validate_constraints).
### 7.3. Human-readable value
Поле message в task_progress; ответ при evidence gate failure (paths, entrypoints, symbols, missing); meta.route (domain_id, process_id, reason); orchestrator_steps (step_id, status). Для аналитика полезны: сценарий, маршрут, факт использования RAG (сейчас всегда false), количество шагов и их статусы.
### 7.4. Technical-only value
timings_ms, executed_layers, retrieval_mode_by_layer, constraint_violations, детали retrieval_spec — в текущем приложении в API не отдаются, только в тестовой диагностике.
### 7.5. Gaps and overload
- Пробел: в продакшене нет слоёвой диагностики retrieval (какие слои запрашивались, сколько вернулось, fallback), нет constraint_violations. Слабо видно, почему ответ мог быть слабым (нет привязки к «мало evidence»).
- Перегрузка: при желании вывести полную диагностику IntentRouterV2 в API пришлось бы дублировать структуру; сейчас она только для тестов.
| Diagnostic field / artifact | Produced where | Useful for human | Useful for debug | Notes |
|-----------------------------|----------------|------------------|------------------|--------|
| task_progress (stage, message) | chat/service, agent run | Yes | Yes | |
| meta.route | agent/service | Yes | Yes | |
| meta.orchestrator_steps | agent/service | Yes | Yes | |
| meta.used_rag | agent/service | Yes | — | Всегда false as is |
| quality (persisted) | metrics_persister | Yes | Yes | |
| router_plan, execution, retrieval, timings_ms | tests pipeline_intent_rag | — | Yes | Не в API |
| constraint_violations | diagnostics.validate_constraints | — | Yes | Только тесты |
---
## 8. Docs Support As Is
### 8.1. Current docs ingestion / parsing
DocsIndexingPipeline: поддержка файлов по DocsFileFilter; парсинг frontmatter (YAML), MarkdownDocChunker по body; DocsClassifier по path (doc_kind); build_module_catalog (D1), build_section (D3), build_policy для type=policy (D4), _extract_facts из frontmatter links (D2). Результат пишется в те же rag_chunks с layer D1D4.
**Evidence.** `app/modules/rag/indexing/docs/pipeline.py`, file_filter, chunkers, document_builder.
### 8.2. Current docs retrieval
Retrieval по слоям D1–D4 возможен через RagRepository.retrieve(..., layers=[...]) и используется в общем retrieval_statement_builder (layer_rank_sql). В основном агенте и в direct code explain путь «только docs» не вызывается; IntentRouterV2 в тестах может задавать retrieval_profile "docs", но в приложении этот путь не задействован.
### 8.3. Existing cross-domain hooks
Нет явного слоя или индекса Doc-Code Links. В документах есть frontmatter links; код и доки индексируются в одну БД (rag_chunks), общий запрос по слоям может смешивать CODE и DOCS слои, но выделенной логики «связать док с кодом» нет.
### 8.4. Readiness for docs generation from code
Сценарий docs_from_analytics и граф docs/generation есть: шаги fetch_source_doc, normalize_document, extract_change_intents, map_to_doc_tree, load_current_docs_context, generate_doc_updates и т.д. Это рассчитано на генерацию/обновление документации из аналитики/контекста, а не напрямую «из кода» в одну команду. База для генерации доков (текущие доки, структура) частично есть; генерация «из кода» опиралась бы на код-контекст, который в основном флоу сейчас не подтягивается через RAG.
### 8.5. Main limitations
- Нет отдельного docs-only retrieval path в рантайме основного приложения.
- Нет D0 как общего «document chunks» и нет D5 (Reference Graph), D6 (Doc-Code Links).
- Различие code vs docs на уровне retrieval_profile и слоёв реализовано в IntentRouterV2 и в БД, но не в цепочке chat → agent → retrieval.
---
## 9. Gap Analysis vs Target Architecture
| Target capability | Current state | Gap | Priority | Notes |
|------------------|---------------|-----|----------|--------|
| Router: intent + sub-intent + graph_id | domain_id/process_id, сценарий по эвристикам | Нет sub_intent, keyword_hints, path_scope, layers в контракте агента | high | IntentRouterV2 есть, не подключён |
| Retrieval в основном флоу | Не вызывается; context_retrieval с пустым rag_items | Подключить вызов RAG по запросу и передавать результат в source_bundle | high | RagService не реализует retrieve |
| RagRetriever contract | Не реализован | Либо адаптер над RagRepository/LayeredRetrievalGateway, либо вызов gateway из агента | high | |
| Intent-driven context assembly | Только в direct explain и build_code_explain_pack | Сборка контекста по intent/layers в оркестраторе | medium | |
| Evidence gate в оркестраторе | Только quality gates по артефактам; evidence_required без RAG | Решение: достаточность evidence после retrieval и при отказе — fallback/деградация | medium | |
| Слои C5, C6, D5, D6 | Отсутствуют | C5 Test Mappings, C6 Code Facts; D5 Reference Graph, D6 Doc-Code Links | medium | |
| Docs retrieval в рантайме | Слои есть в БД, вызова нет | Отдельный путь или единый retrieval с profile docs | medium | |
| Диагностика retrieval и constraints | Только в тестах | Вынести router_plan, execution, retrieval, timings, violations в API или лог-артефакт | low | |
| README vs реализация | README описывает rag_session как источник retrieval | В основном флоу rag_session по запросу не используется для ответа | high | Уточнить или менять реализацию |
---
## 10. Concrete Evidence Index
### 10.1. Router
- `app/modules/agent/engine/router/router_service.py` — RouterService.resolve, persist_context, graph_factory, _fallback, _continue_current, _is_acceptable.
- `app/modules/agent/engine/router/intent_classifier.py` — classify_new_intent, from_mode, _deterministic_route, _classify_with_llm, _route_mapping.
- `app/modules/agent/engine/router/__init__.py` — build_router_service, регистрация графов в IntentRegistry.
- `app/modules/agent/engine/router/schemas.py` — RouteDecision, RouteResolution, RouterContext.
- `app/modules/agent/engine/router/intents_registry.yaml` — список допустимых пар (domain_id, process_id) для IntentRegistry.is_valid.
### 10.2. Retrieval
- `app/modules/agent/service.py` — rag_ctx = [], task_spec с rag_items/rag_context.
- `app/modules/agent/engine/graphs/project_qa_step_graphs.py` — ProjectQaRetrievalGraphFactory._retrieve_context (rag_items=[]), build_source_bundle.
- `app/modules/rag/explain/retriever_v2.py` — build_pack, _entrypoints, _seed_symbols, _run_pass, _merge_test_fallback.
- `app/modules/rag/explain/layered_gateway.py` — retrieve_layer, retrieve_lexical_code, RagRepository.
- `app/modules/rag/persistence/repository.py` — retrieve, retrieve_lexical_code, retrieve_exact_files.
- `app/modules/rag/services/rag_service.py` — только index_snapshot/index_changes, нет retrieve.
- `app/modules/contracts.py` — RagRetriever (async retrieve).
### 10.3. Graph orchestration
- `app/modules/agent/engine/orchestrator/service.py` — OrchestratorService.run, template → compile → validate → engine.run → assemble.
- `app/modules/agent/engine/orchestrator/template_registry.py` — ScenarioTemplateRegistry.build, _general, _project_qa, _explain, _review, _docs, _edit, _gherkin.
- `app/modules/agent/engine/orchestrator/step_registry.py` — StepRegistry.execute, _execute_graph_step, graph_id, _build_graph_state.
- `app/modules/agent/engine/orchestrator/execution_engine.py` — выполнение шагов, quality gates.
- `app/modules/agent/engine/graphs/project_qa_step_graphs.py` — все ProjectQa*GraphFactory.
### 10.4. Diagnostics
- `app/modules/agent/service.py` — LOGGER.warning (route, orchestrator), AgentResult.meta, _persist_quality_metrics.
- `app/modules/chat/service.py` — _publish_progress, task_result event.
- `tests/pipeline_intent_rag/helpers/diagnostics.py` — build_router_plan, init_diagnostics, apply_retrieval_report, validate_constraints.
### 10.5. Docs support
- `app/modules/rag/indexing/docs/pipeline.py` — DocsIndexingPipeline.index_file, D1D4.
- `app/modules/rag/contracts/enums.py` — RagLayer DOCS_*.
- `app/modules/rag/intent_router_v2/router.py` — _resolve_retrieval_profile("docs" для DOCS_QA).
---
## 11. Final Conclusion
### 11.1. Насколько текущая реализация уже соответствует целевой архитектуре?
Реализация частично соответствует: есть многослойная индексация (CODE C0C4, DOCS D1D4), оркестрация по сценариям и подграфам, роутер по домену/процессу с LLM и эвристиками, задел в виде IntentRouterV2 с полным контрактом (intent, retrieval_spec, evidence_policy) и тестовый пайплайн с диагностикой. При этом основной агентский флоу не вызывает RAG при ответе, контракт RagRetriever не реализован, а IntentRouterV2 и его retrieval не интегрированы в приложение. Целевые слои C5, C6, D5, D6 отсутствуют; evidence gate в смысле «достаточность retrieval» есть только в direct code explain.
### 11.2. Что логичнее делать следующим шагом?
Имеет смысл **сначала уточнить документацию (README и целевую модель)** под as is, чтобы зафиксировать два режима (direct code explain vs full agent), факт отсутствия вызова RAG в основном флоу и роль IntentRouterV2 как тестового/перспективного контура. Затем **параллельно** задать приоритеты: (1) подключение retrieval к основному флоу (адаптер или вызов LayeredRetrievalGateway по решению роутера) и (2) решение о том, переносить ли в продакшен контракт IntentRouterV2 (intent, retrieval_spec, layers) или расширять текущий RouterService. Делать только доработку реализации без обновления README рискованно — расхождение описания и кода сохранится.
### 11.3. Какие 3–5 самых важных расхождений между as is и target есть сейчас?
1. **Retrieval не вызывается в основном флоу** — context_retrieval не использует индекс; rag_context всегда пустой. Приоритет: high.
2. **RagRetriever не реализован** — RagService только индексирует; агент не может получить список документов по запросу через контракт. Приоритет: high.
3. **IntentRouterV2 не в продакшене** — intent, sub_intent, retrieval_spec, layers, evidence_policy есть только в тестах; в приложении другой роутер и нет слоёвого retrieval. Приоритет: high.
4. **README говорит про retrieval из rag_session для ответа** — по факту для ответа в основном режиме rag_session по запросу не используется. Приоритет: high (документация).
5. **Нет evidence gate в оркестраторе** после retrieval — проверка «достаточно ли evidence» и деградация при слабом retrieval только в direct code explain. Приоритет: medium.
+323
View File
@@ -0,0 +1,323 @@
# Концепция документации (Strong MVP, без связи с кодом)
## 1. Область применения
Документ описывает систему работы с документацией как самостоятельный слой.
Включает:
- текущее состояние (as-is)
- целевые сценарии использования
- целевую модель документации
- расширенный frontmatter
- базовую структуру документа `frontmatter + Summary + Details`
- RAG-слои (без связи с кодом)
---
# 2. Текущее состояние (As-Is)
## 2.1 Источник истины
- Документация хранится в Confluence
- Основная модель — иерархия страниц
- Навигация через дерево страниц
## 2.2 Структура и связи
- Документы не атомарны
- Одна страница содержит несколько тем
- Используются перекрестные ссылки между страницами
## 2.3 Ограничения
- Нет типизации документов
- Нет структурированного metadata-слоя
- Связи не формализованы
---
# 3. Целевые сценарии использования
## 3.1 Поиск документации
Пользователь формулирует запрос, например: "как работает создание заказа".
Система должна:
- найти релевантные документы
- отобрать наиболее точные фрагменты
- учитывать тип документа, модуль и сущности
Результат:
- короткий список релевантных документов
- возможность перейти в детали
## 3.2 Объяснение (Explain)
Пользователь хочет понять:
- как работает компонент
- что делает API
- как устроен процесс
Система должна:
- взять summary документа
- дополнить его деталями из Details
- дать краткое и точное объяснение без лишнего текста
## 3.3 Поиск по сущности (Entity Lookup)
Пользователь ищет:
- где используется сущность
- какие документы с ней связаны
Система должна:
- найти все документы, где упоминается сущность
- показать связи между ними
## 3.4 Навигация по документации
Пользователь начинает с одного документа и хочет:
- понять контекст
- перейти к связанным частям
Система должна:
- использовать parent/children
- использовать links
- строить осмысленный путь по документации
## 3.5 Генерация документации
Система создает новый документ:
- по шаблону
- с корректным frontmatter
- с заполненными разделами `Summary` и `Details`
Результат:
- документ сразу пригоден для использования
- соответствует структуре системы
## 3.6 Актуализация документации
При изменениях документа:
- система должна обновить только нужные части
- сохранить структуру
- обновить frontmatter и Details
Результат:
- документ остается консистентным
- не происходит деградации структуры
## 3.7 Связывание документации
При создании или обновлении:
- система добавляет связи между документами
- формирует граф знаний
Результат:
- документы не изолированы
- появляется навигация и reasoning
---
# 4. Frontmatter (расширенный контракт)
```yaml
id: api.create_order
type: api_method
name: create_order
title: Create order API
module: orders
layer: application
status: draft
updated_at: 2026-03-20
tags:
- orders
- api
entities:
- Order
- Cart
parent: orders_api
children: []
links:
- type: related_api
target: api.get_order
```
## Назначение ключевых полей
- `id` — стабильный идентификатор документа
- `type` — тип документа
- `title` — человекочитаемое имя
- `module` — модуль или bounded context
- `layer` — слой системы
- `status` — состояние документа
- `updated_at` — дата последнего обновления
- `tags` — фильтрация и поиск
- `entities` — база для entity lookup
- `parent` / `children` — иерархия
- `links` — граф связей
Все сведения о связях, сущностях и навигации должны храниться во frontmatter.
В теле документа отдельные разделы для связей, сущностей и навигации не создаются.
---
# 5. Структура документа (целевая)
Каждый документ состоит из двух смысловых слоев:
- frontmatter
- контент
Контент документа всегда состоит из двух обязательных разделов:
- `# Summary`
- `# Details`
## 5.1 Общие правила структуры
- `Summary` и `Details` всегда имеют заголовок уровня `#`
- других заголовков первого уровня в документе быть не должно
- `Summary` остается кратким и пригодным для explain
- `Details` заменяет прежние разделы `Context` и `Основное описание`
- внутренняя структура `Details` зависит от типа документа
## 5.2 Summary
Краткое описание на 3-6 строк.
Используется для explain, краткого ответа и быстрого понимания сути документа.
## 5.3 Details
Раздел содержит полное содержательное описание документа.
Состав подразделов определяется типом документа.
---
# 6. Типовая структура Details для API
Для документов типа `api_method` раздел `# Details` должен содержать следующие подразделы:
- `## Описание`
- `## Сценарий`
- `## Функциональные требования`
- `## Нефункциональные требования`
- `## Контракт`
## 6.1 Описание
Короткое описание сути метода:
- какую работу он выполняет
- для чего предназначен
## 6.2 Сценарий
Сценарий описывается в формате технического use case и включает:
- название
- предусловия
- триггер
- основной сценарий
- альтернативный сценарий
- обработку ошибок
- постусловие
Правила для сценария:
- сценарий должен быть лаконичным
- сценарий должен быть читаемым человеком
- в сценарии фиксируется суть шага, а не полная техническая реализация
- если условие можно выразить одним предложением, его допустимо оставить в сценарии
- если шаг требует детального описания формирования запроса, обработки ответа или внутренней логики, детали выносятся в функциональные требования
## 6.3 Функциональные требования
Функциональные требования описываются как последовательность требований внутри одного документа.
Формат:
- `FR-1`
- `FR-2`
- `FR-3`
Правила:
- идентификаторы локальны для документа
- на них нельзя ссылаться извне как на сквозные идентификаторы
- каждое требование описывает отдельный обязательный аспект реализации
## 6.4 Нефункциональные требования
Нефункциональные требования описываются аналогично функциональным.
Формат:
- `NFR-1`
- `NFR-2`
- `NFR-3`
Детальный формат записи может быть уточнен правилами конкретного проекта.
## 6.5 Контракт
Контракт должен быть пригоден для последующей сборки OpenAPI-спецификации.
Контракт описывает:
- входные параметры метода API
- выходные параметры
- структуру сообщения, как правило JSON
- обязательность полей
- типы полей
- ограничения на размер и формат
- назначение полей
- правила заполнения полей
- примеры данных
---
# 7. Базовая структура Details для остальных типов документов
Для всех типов документов, кроме `api_method`, на текущем этапе обязателен минимум:
- `# Summary`
- `# Details`
Дополнительные рекомендации по внутренней структуре `Details` для `logic_block`, `architecture_overview`, `domain_entity` и других типов будут заданы отдельно.
---
# 8. RAG слои (только документация)
## D0 — Чанки документов
Полный текст + разбиение.
## D1 — Каталог документов
- `id`
- `type`
- `title`
- `module`
- `tags`
## D2 — Индекс фактов
Факты, извлеченные из документов.
## D3 — Каталог сущностей
- сущности
- документы, где они используются
## D4 — Индекс сценариев (Workflow)
- последовательности действий
- пользовательские сценарии
## D5 — Граф связей
- связи между документами
---
# 9. Итог
Система документации:
- атомарные документы
- строгий frontmatter
- единая структура `Summary + Details`
- типовые правила для API-документов
- разделенные RAG-слои
Это позволяет:
- точно искать
- объяснять
- строить навигацию
- генерировать и обновлять документацию
@@ -0,0 +1,267 @@
# Iteration 2 — Calibration Harness Report
## 1. Executive Summary
This iteration adds **calibration and evaluation infrastructure** for the canonical CODE_QA pipeline. The pipeline remains test-first and is not integrated into the UI or production runtime.
**Added:**
- A small **deterministic fixture repository** (`tests/fixtures/code_qa_repo/`) for reproducible tests.
- **Golden case format and initial cases** for OPEN_FILE, EXPLAIN, FIND_TESTS, FIND_ENTRYPOINTS, and GENERAL_QA (positive, borderline, negative).
- An **evaluation harness** that indexes a repo (fixture or user-provided path), runs golden cases through `CodeQAPipelineRunner` with the **real retrieval adapter** (`RagDbAdapter`), and compares actual vs expected (intent, sub_intent, answer_mode, path_scope, symbol_candidates).
- **Diagnostics artifact dumping** per run (Markdown + JSON) under `tests/artifacts/code_qa_eval/<run_id>/`.
- A **batch evaluation summary** (Markdown table + failure list) for manual review.
- **Two modes:** fixture repo by default; optional `CODE_QA_REPO_PATH` for a local real repository.
**Now possible:**
- Run the canonical pipeline end-to-end on the fixture repo with real indexing and retrieval.
- Run the same harness against a user-provided repo path (no hardcoded external repo).
- Inspect per-case diagnostics and batch summary to tune routing, retrieval, evidence gate, and answer mode.
**Still manual / out of scope:**
- Tuning prompts and retrieval heuristics (harness supports observation, not automatic tuning).
- UI integration, docs runtime retrieval, production router replacement.
- Exact LLM answer matching (we assert routing, retrieval alignment, evidence sufficiency, answer mode only).
---
## 2. Fixture Repository
**Location:** `tests/fixtures/code_qa_repo/`
**Structure:**
```
tests/fixtures/code_qa_repo/
├── app/
│ └── main.py # Entrypoint: create_app(), app.run()
├── api/
│ └── orders.py # Handlers: create_order, get_order; OrderService, OrderRepository
├── services/
│ └── order_service.py # OrderService: create_order, get_order
├── repositories/
│ └── order_repository.py # OrderRepository: save, find_by_id
├── domain/
│ └── order.py # Order: id, product_id, quantity, status
├── tests/
│ └── test_order_service.py # test_create_order, test_get_order_returns_saved_order
└── utils/
└── helpers.py # format_order_id
```
**Purpose of each file:**
| File | Purpose |
|------|--------|
| `app/main.py` | Single clear entrypoint for FIND_ENTRYPOINTS and “open main” style queries. |
| `api/orders.py` | API/handler layer; distinct symbols `create_order`, `get_order`, `create_app`. |
| `services/order_service.py` | Service calling repository; symbol `OrderService`. |
| `repositories/order_repository.py` | Persistence; symbol `OrderRepository`. |
| `domain/order.py` | Domain model; symbol `Order`. |
| `tests/test_order_service.py` | Tests tied to production code for FIND_TESTS. |
| `utils/helpers.py` | Extra module for bounded GENERAL_QA and path/symbol variety. |
**Scenarios covered:**
- **File by path:** `app/main.py`, `api/orders.py` (OPEN_FILE).
- **Symbol explanation:** `Order`, `OrderService`, `create_order` (EXPLAIN).
- **Import/call relations:** service → repository → domain (EXPLAIN / GENERAL_QA).
- **Entrypoint:** `app/main.py` (FIND_ENTRYPOINTS).
- **Related tests:** `tests/test_order_service.py` for OrderService/Order (FIND_TESTS).
- **Fallback:** “Что делает этот проект?” (GENERAL_QA with bounded context).
The fixture is small and structured so routing and retrieval expectations are unambiguous for calibration.
---
## 3. Real Adapter Integration
The canonical pipeline runs with the **existing** retrieval/index stack:
- **Indexing:** `RagSessionIndexer` (in `tests/pipeline_intent_rag/helpers/repo_indexer.py`) uses `RagService` and `LocalRepoFileCollector` to index a directory. The fixture (or `CODE_QA_REPO_PATH`) is indexed once per eval run.
- **Retrieval:** `RagDbAdapter` (in `tests/pipeline_intent_rag/helpers/rag_db_adapter.py`) implements the pipelines `RetrievalAdapter` protocol: `retrieve_with_plan`, `retrieve_exact_files`, `hydrate_resolved_symbol_sources`, `force_symbol_context_c0`, `consume_retrieval_report`. It uses `RagRepository` and the same layer logic as the rest of the project.
- **Pipeline:** `CodeQAPipelineRunner` (in `app/modules/rag/code_qa_pipeline/pipeline.py`) takes `IntentRouterV2` and this adapter, builds `RetrievalRequest` from the router, runs retrieval, builds `EvidenceBundle`, runs the evidence gate, and produces diagnostics.
**Fixture repo:** The harness indexes `tests/fixtures/code_qa_repo` by default and runs all golden cases against that index. No external repo is required.
**User-provided repo:** Set `CODE_QA_REPO_PATH` to a local directory. The harness indexes that path and runs the same golden cases (or the user can add repo-specific cases). Optional `CODE_QA_PROJECT_ID` sets the project id for the session. The codebase does **not** depend on any private or external repo being present.
---
## 4. Golden Case Format
**Location:** `tests/golden/code_qa/`
**File:** `cases.yaml`
**Fields per case:**
| Field | Meaning |
|-------|--------|
| `id` | Unique case id. |
| `query` | User query text. |
| `expected_intent` | Expected top-level intent (e.g. CODE_QA). |
| `expected_sub_intent` | OPEN_FILE \| EXPLAIN \| FIND_TESTS \| FIND_ENTRYPOINTS \| GENERAL_QA. |
| `expected_answer_mode` | normal \| degraded \| insufficient. |
| `expected_target_hint` | Optional: path, symbol, or test-like. |
| `expected_path_scope_contains` | Optional list of substrings that must appear in path_scope. |
| `expected_symbol_candidates_contain` | Optional list of symbols that must appear in symbol_candidates. |
| `expected_layers` | Optional list of layer ids expected in the retrieval plan. |
| `notes` | Optional: borderline, negative, or calibration hint. |
**Expected results:** We assert routing (intent, sub_intent), retrieval alignment (path_scope, symbol_candidates, layers when specified), evidence sufficiency (via answer_mode), and diagnostics shape. We do **not** assert exact LLM wording.
**Not asserted (yet):** Exact chunk content, relation counts, or full evidence bundle structure beyond what drives answer_mode and target hints.
---
## 5. Golden Runner / Evaluation Harness
**Entrypoints:**
- **Programmatic:** `tests.code_qa_eval.runner.run_eval(config)` — runs all golden cases and returns `list[EvalCaseResult]`.
- **CLI:** `python -m tests.code_qa_eval.run` (from project root) — loads config, runs eval, writes artifacts and summary, exits 0 only if all pass.
**Fixture mode (default):**
1. Do not set `CODE_QA_REPO_PATH`.
2. Run: `python -m tests.code_qa_eval.run` (or call `run_eval(EvalConfig.from_env())`).
3. Repo used: `tests/fixtures/code_qa_repo`. It is indexed once; then each golden case is run through the pipeline and compared to expected.
**User-provided repo:**
1. Set `CODE_QA_REPO_PATH` to the repository root (e.g. `export CODE_QA_REPO_PATH=/path/to/your/repo`).
2. Optionally set `CODE_QA_PROJECT_ID`.
3. Run the same command. The harness indexes that path and runs the same golden cases (or you can point to a different `cases.yaml` by changing `EvalConfig.golden_cases_path` in code).
**Outputs:**
- **Per case:** under `tests/artifacts/code_qa_eval/<run_id>/`: `<case_id>.md` and `<case_id>.json` (query, expected/actual, router, retrieval, evidence gate, timings, mismatches).
- **Batch:** `tests/artifacts/code_qa_eval/summary_<run_id>.md` — table (case id, query, expected/actual scenario, target, evidence, answer mode, pass/fail) and a failure list.
- **Exit code:** 0 if all cases pass, 1 otherwise; failures are printed to stderr.
---
## 6. Diagnostics Artifacts
**Generated artifacts:**
- **Per run (per case):** `<run_id>/<case_id>.md` and `<case_id>.json`.
- **Batch:** `summary_<run_id>.md` in `tests/artifacts/code_qa_eval/`.
**Location:** `tests/artifacts/code_qa_eval/` (created if missing).
**Markdown (per case) contains:**
- Query, expected (intent, sub_intent, answer_mode), actual (intent, sub_intent, answer_mode, evidence_gate_passed, evidence_count).
- Pass/fail and list of mismatches.
- Router: path_scope, layers.
- Retrieval: requested_layers, chunk_count, layer_outcomes.
- Evidence gate: failure_reasons.
- Timings (ms).
**JSON (per case)** adds machine-readable detail: full expected/actual, passed, mismatches, router_result, retrieval_request, per_layer_outcome, failure_reasons, timings_ms.
**Useful for calibration:**
- **Router:** path_scope and layers — confirm OPEN_FILE vs EXPLAIN vs FIND_* routing and plan.
- **Retrieval:** layer_outcomes and chunk_count — see which layers returned hits.
- **Evidence gate:** failure_reasons and evidence_count — see why answer_mode is degraded/insufficient.
- **Mismatches:** quick list of what to fix (routing vs retrieval vs gate).
**Example snippet (Markdown):**
```markdown
# open_file_main_positive
## Query
Открой файл app/main.py
## Expected
- intent: CODE_QA, sub_intent: OPEN_FILE
- answer_mode: normal
## Actual
- intent: CODE_QA, sub_intent: OPEN_FILE
- answer_mode: normal
- evidence_gate_passed: True
- evidence_count: 2
## Result
PASS
```
---
## 7. Tests Added
| File | What it validates |
|------|-------------------|
| `tests/code_qa_eval/test_eval_harness.py` | Golden loader, compare logic, config, fixture-mode run structure. |
**Test groups:**
- **Golden loader:** `test_load_golden_cases_returns_list` — loads `cases.yaml`, checks count and field presence (id, query, expected_intent, expected_sub_intent, expected_answer_mode).
- **Compare logic:** `test_compare_passed_when_all_match`, `test_compare_fails_on_intent_mismatch`, `test_compare_fails_on_answer_mode_mismatch`, `test_compare_path_scope_contains` — assert pass/fail and mismatch messages for intent, sub_intent, answer_mode, path_scope.
- **Config:** `test_eval_config_fixture_mode_by_default` — default config uses fixture path, golden path, and artifacts dir under `tests/`.
- **Fixture-mode run:** `test_run_eval_fixture_mode_structure` — runs `run_eval(config)` with fixture config; asserts result list and that each item is `EvalCaseResult` with case, pipeline_result, passed, mismatches. **Skips** if DB or dependencies (e.g. sqlalchemy) are unavailable.
**Modes:** Loader and compare tests are unit (no DB). Config test uses paths only. Fixture-mode test is integration-style with real adapter and DB; it is skipped when the environment cannot connect or import.
---
## 8. Known Limitations
- **LLM answer:** The harness does not call the LLM; `answer_mode` is derived from the evidence gate only. No assertion on final answer text.
- **Routing stability:** Golden expectations (especially borderline/negative) may need manual adjustment as the router or retrieval changes.
- **Real DB required:** Full eval (index + retrieve) needs a configured DB; otherwise the integration test and CLI run skip or fail. No in-memory SQLite path is implemented in this iteration.
- **Single session per run:** Each run indexes the repo once and reuses one RAG session for all cases. Cross-session or re-index behaviour is not exercised.
- **Docs / cross-domain:** Golden cases and harness are CODE_QA only; docs retrieval and cross-domain flows are out of scope.
- **Performance:** No timings or regression assertions; artifacts are for manual inspection and tuning.
---
## 9. How to Use for Manual Calibration
1. **Run fixture evaluation**
From project root: `python -m tests.code_qa_eval.run`. Check exit code and console output (pass/fail counts and failure lines).
2. **Inspect diagnostics**
Open `tests/artifacts/code_qa_eval/<run_id>/*.md` for failing (or borderline) cases. Use router (path_scope, layers), retrieval (layer_outcomes, chunk_count), and evidence gate (failure_reasons) to see why a case failed.
3. **Run against a real local repo**
Set `CODE_QA_REPO_PATH=/path/to/repo`, then run the same command. Compare behaviour to the fixture run.
4. **Compare mismatches**
Use the batch summary and per-case mismatches to decide what to tune: intent/sub_intent (router/prompts), path_scope/symbol_candidates (router or retrieval), or evidence thresholds (evidence gate).
5. **Adjust and re-run**
Update router, retrieval, or evidence policy; add/edit golden cases if needed; re-run the harness and confirm improvements in the summary and artifacts.
---
## 10. Changed Files Index
| File | Purpose |
|------|--------|
| `tests/fixtures/code_qa_repo/app/main.py` | Fixture entrypoint. |
| `tests/fixtures/code_qa_repo/api/orders.py` | Fixture API handlers. |
| `tests/fixtures/code_qa_repo/services/order_service.py` | Fixture service layer. |
| `tests/fixtures/code_qa_repo/repositories/order_repository.py` | Fixture repository. |
| `tests/fixtures/code_qa_repo/domain/order.py` | Fixture domain model. |
| `tests/fixtures/code_qa_repo/tests/test_order_service.py` | Fixture tests. |
| `tests/fixtures/code_qa_repo/utils/helpers.py` | Fixture utility. |
| `tests/golden/code_qa/README.md` | Golden case format description. |
| `tests/golden/code_qa/cases.yaml` | Golden cases for all MVP scenarios. |
| `tests/code_qa_eval/__init__.py` | Package init. |
| `tests/code_qa_eval/config.py` | EvalConfig: repo path (fixture vs CODE_QA_REPO_PATH), artifacts dir, golden path. |
| `tests/code_qa_eval/golden_loader.py` | Load and parse golden cases from YAML. |
| `tests/code_qa_eval/runner.py` | run_eval: index repo, run pipeline, compare to golden; _compare logic. |
| `tests/code_qa_eval/artifacts.py` | dump_run_artifact (md+json), write_batch_summary. |
| `tests/code_qa_eval/run.py` | CLI entrypoint: load config, run eval, write artifacts and summary. |
| `tests/code_qa_eval/test_eval_harness.py` | Tests for loader, compare, config, fixture-mode run. |
| `pytest.ini` | Added marker `code_qa_eval`. |
| `iteration2_calibration_harness_report.md` | This report. |
No changes were made to production router, UI, or docs retrieval. The canonical pipeline and existing retrieval/index stack are reused; the harness is test-side only.
@@ -0,0 +1,546 @@
# Текущая архитектура тестового пайплайна `pipeline_setup_v3`
Документ предназначен как краткое, но точное описание текущего устройства `pipeline_setup_v3` для внешней модели вроде ChatGPT.
Важно: текущий `pipeline_setup_v3` уже использует реальные runtime-компоненты агента, но по сути остается в первую очередь `code-first` пайплайном. Это особенно заметно в `evidence gate` и в наборе prompt'ов для LLM.
## 1. Общая схема пайплайна
`pipeline_setup_v3` запускает один из трех режимов:
- `router_only`
- `router_rag`
- `full_chain`
Во всех режимах используется `AgentRuntimeAdapter`, который является тестовым адаптером поверх реальных компонентов рантайма.
Общий поток для `full_chain`:
1. Пользовательский запрос
2. `IntentRouterV2`
3. Построение `RetrievalRequest`
4. `RuntimeRetrievalAdapter`
5. Построение нормализованного `RetrievalResult`
6. Сборка `EvidenceBundle`
7. `pre-evidence gate`
8. `RuntimeAnswerPolicy`
9. Вызов LLM через `AgentLlmService`
10. `post-evidence gate`
11. При необходимости `repair`
12. Сборка итогового результата, диагностики и артефактов теста
Ключевая идея: `pipeline_setup_v3` не эмулирует локальный тестовый сценарий вручную, а прогоняет реальные компоненты: роутер, retrieval, runtime executor и LLM.
## 2. Из каких компонентов состоит `pipeline_setup_v3`
### 2.1. Harness уровня тестов
Основные части:
- `tests/pipeline_setup_v3/run.py` — CLI-вход для запуска набора кейсов
- `tests/pipeline_setup_v3/core/runner.py` — оркестратор прогона кейсов
- `tests/pipeline_setup_v3/core/case_loader.py` — загрузка YAML-кейсов
- `tests/pipeline_setup_v3/core/validators.py` — проверка ожиданий
- `tests/pipeline_setup_v3/core/artifacts.py` — запись JSON/Markdown-результатов
- `tests/pipeline_setup_v3/runtime/agent_runtime_adapter.py` — мост к runtime-компонентам приложения
### 2.2. Runtime-компоненты, которые реально вызываются
- `IntentRouterV2`
- `RuntimeRepoContextFactory`
- `RuntimeRetrievalAdapter`
- `AgentRuntimeExecutor`
- `AgentLlmService`
- `PromptLoader`
### 2.3. Два варианта исполнения
#### `router_only`
Проверяет только результат роутера:
- `intent`
- `sub_intent`
- `graph_id`
- `conversation_mode`
RAG и LLM не вызываются.
#### `router_rag`
Проверяет:
- роутер
- retrieval plan
- реальный retrieval
- нормализованный `RetrievalResult`
LLM не вызывается.
#### `full_chain`
Проверяет полный runtime-контур:
- роутер
- retrieval
- evidence bundle
- pre-gate
- answer policy
- LLM
- post-gate
- repair
- итоговый answer/diagnostics
## 3. Компонент: Intent Router
### 3.1. Что это такое
`IntentRouterV2` классифицирует запрос и возвращает структурированный план для retrieval и дальнейшего рантайма.
Он не просто выбирает `intent`, а сразу строит:
- `conversation_mode`
- `query_plan`
- `retrieval_spec`
- `retrieval_constraints`
- `symbol_resolution`
- `evidence_policy`
- `graph_id`
### 3.2. Какие интенты есть сейчас
Сейчас в коде поддерживаются:
- `CODE_QA`
- `DOCUMENTATION_EXPLAIN`
- `GENERATE_DOCS_FROM_CODE`
- `FALLBACK`
### 3.3. Какие саб-интенты есть сейчас
#### Для `CODE_QA`
- `OPEN_FILE`
- `EXPLAIN`
- `EXPLAIN_LOCAL`
- `FIND_TESTS`
- `FIND_ENTRYPOINTS`
- `TRACE_FLOW`
- `ARCHITECTURE`
- `NEXT_STEPS` может появляться как follow-up режим на уровне query planning
#### Для `DOCUMENTATION_EXPLAIN`
- `SYSTEM_FLOW_EXPLAIN`
- `COMPONENT_EXPLAIN`
- `API_METHOD_EXPLAIN`
- `ENTITY_EXPLAIN`
#### Для `FALLBACK`
- `GENERIC_FALLBACK`
### 3.4. Что роутер возвращает на выходе
Результат роутера — это `IntentRouterResult`.
Ключевые поля:
- `intent`
- `retrieval_profile`
- `graph_id`
- `conversation_mode`
- `query_plan`
- `retrieval_spec`
- `retrieval_constraints`
- `symbol_resolution`
- `evidence_policy`
### 3.5. Что входит в `query_plan`
`query_plan` содержит:
- `raw`
- `normalized`
- `sub_intent`
- `negations`
- `expansions`
- `keyword_hints`
- `path_hints`
- `doc_scope_hints`
- `symbol_candidates`
- `symbol_kind_hint`
- `anchors`
Это основной bridge между классификацией запроса и retrieval.
### 3.6. Что входит в `retrieval_spec`
`retrieval_spec` содержит:
- `domains`
- `layer_queries`
- `filters`
- `rerank_profile`
Именно этот объект задает, какие слои RAG должны быть запрошены.
### 3.7. Что важно про текущее состояние
Текущий роутер уже умеет выделять docs-интенты и docs-сабинтенты, но downstream runtime ниже по пайплайну все еще во многом оптимизирован под `CODE_QA`.
Это означает:
- docs routing уже есть
- docs layer selection уже есть
- но `pre/post evidence gate` и prompt selection пока ориентированы в первую очередь на code sub-intents
## 4. Структура RAG
### 4.1. Общая идея
RAG устроен как многослойный индекс. Retrieval работает не по одному единственному типу чанков, а по наборам специализированных слоев.
### 4.2. Code-слои
- `C0_SOURCE_CHUNKS` — сырой код / чанки исходников
- `C1_SYMBOL_CATALOG` — каталог символов
- `C2_DEPENDENCY_GRAPH` — зависимости и связи
- `C3_ENTRYPOINTS` — точки входа, маршруты, handler'ы
- `C4_SEMANTIC_ROLES` — семантические роли и behavioral hints
### 4.3. Docs-слои
- `D0_DOC_CHUNKS` — чанки документов
- `D1_DOCUMENT_CATALOG` — каталог документов
- `D2_FACT_INDEX` — атомарные факты
- `D3_ENTITY_CATALOG` — сущности
- `D4_WORKFLOW_INDEX` — сценарии и workflow
- `D5_RELATION_GRAPH` — связи между документами
### 4.4. Как retrieval связывается с роутером
Роутер возвращает:
- `domains`
- `layer_queries`
- `filters`
- `retrieval_constraints`
Дальше `build_retrieval_request(...)` превращает это в `RetrievalRequest`, который содержит:
- `rag_session_id`
- `query`
- `sub_intent`
- `path_scope`
- `keyword_hints`
- `symbol_candidates`
- `requested_layers`
- `retrieval_spec`
- `retrieval_constraints`
- `query_plan`
### 4.5. Что возвращает retrieval
Сырые строки RAG затем нормализуются в `RetrievalResult`, который содержит:
- `target_symbol_candidates`
- `resolved_symbol`
- `symbol_resolution_status`
- `file_candidates`
- `code_chunks`
- `relations`
- `semantic_hints`
- `entrypoints`
- `test_candidates`
- `layer_outcomes`
- `missing_layers`
- `raw_rows`
- `retrieval_report`
### 4.6. Как из retrieval строится evidence
`build_evidence_bundle(...)` собирает `EvidenceBundle`.
Ключевые поля:
- `resolved_intent`
- `resolved_sub_intent`
- `resolved_target`
- `target_type`
- `target_symbol_candidates`
- `file_candidates`
- `code_chunks`
- `relations`
- `entrypoints`
- `test_evidence`
- `evidence_count`
- `sufficient`
- `failure_reasons`
- `retrieval_summary`
Важно: `evidence_count` сейчас считается по количеству `code_chunks`. Это еще одно подтверждение, что runtime сегодня code-first.
## 5. Evidence Gate
В пайплайне есть два gate'а.
## 5.1. Pre-evidence gate
Расположение:
- `src/app/modules/agent/runtime/steps/gates/pre/evidence_gate.py`
Когда срабатывает:
- после retrieval
- после сборки `EvidenceBundle`
- до вызова LLM
Задача:
- понять, достаточно ли evidence для уверенного ответа
- выдать `passed/failure_reasons/degraded_message`
Как работает сейчас:
- для `OPEN_FILE` требует найденный path/file и хотя бы один `C0` chunk
- для `EXPLAIN` требует target symbol и минимум 2 evidence chunk'а
- для `FIND_TESTS` требует target и хотя бы один test candidate
- для `FIND_ENTRYPOINTS` требует хотя бы один entrypoint
- для остальных сценариев требует минимум 1 evidence
Что возвращает:
- `passed`
- `failure_reasons`
- `degraded_message`
Ограничение:
- логика pre-gate пока написана в терминах code sub-intents
- docs-сценарии там явно не моделированы
## 5.2. Post-evidence gate
Расположение:
- `src/app/modules/agent/runtime/steps/gates/post/post_gate.py`
Когда срабатывает:
- после генерации draft answer
- до возврата финального ответа
Задача:
- проверить groundedness черновика
- убедиться, что ответ действительно опирается на evidence
- решить, нужен ли `repair`
Что проверяет:
- что ответ не пустой
- что degraded/not_found/ambiguous-ответы содержат обязательные guardrail-фразы
- что normal answer не слишком общий
- что упоминаются обязательные факты из curated evidence
- что нет явной semantic leakage или contradictions
Отдельные проверки есть для:
- `FIND_ENTRYPOINTS`
- `EXPLAIN`
- `ARCHITECTURE`
- `TRACE_FLOW`
Если gate не проходит, он возвращает:
- `passed=False`
- `action="repair"`
- список `reasons`
После этого runtime может сделать дополнительный шаг `repair` через LLM.
Ограничение:
- post-gate тоже пока ориентирован на code-oriented sub-intents
- docs-сабинтенты для него еще не описаны отдельными правилами
## 6. Обращение к LLM
### 6.1. Где вызывается LLM
В `pipeline_setup_v3` есть два места использования LLM:
1. В классификации интента внутри `IntentClassifierV2`
2. В финальной генерации ответа внутри `AgentRuntimeExecutor`
### 6.2. Prompt для классификации интента
Используется prompt:
- `rag_intent_router_v2`
Назначение:
- если deterministic rules не дали результата, LLM выбирает интент
Текущий prompt исторически описывает старые имена интентов, поэтому его еще нужно синхронизировать с новым docs/fallback контрактом.
### 6.3. Prompt'ы для генерации ответа
Prompt selector сейчас выбирает:
- `code_qa_architecture_answer`
- `code_qa_explain_answer`
- `code_qa_explain_local_answer`
- `code_qa_find_entrypoints_answer`
- `code_qa_find_tests_answer`
- `code_qa_general_answer`
- `code_qa_open_file_answer`
- `code_qa_trace_flow_answer`
- `code_qa_degraded_answer`
Дополнительно для repair используется:
- `code_qa_repair_answer`
### 6.4. Как строится payload для LLM
Перед вызовом LLM runtime собирает:
- `AnswerSynthesisInput`
- `EvidenceBundle`
- `prompt_payload`
В payload передаются:
- `user_question`
- `resolved_target`
- `answer_mode`
- `evidence_summary`
- `retrieval_summary`
- curated facts
- обязательные для упоминания сущности, методы, связи, шаги и т.д.
То есть LLM не получает просто "вопрос и куски текста", а получает уже структурированный grounded payload.
### 6.5. Что важно про текущее состояние prompt'ов
Сейчас runtime prompt selection и prompt contracts явно заточены под code QA.
Это значит:
- для `CODE_QA` full chain оформлен хорошо
- для `DOCUMENTATION_EXPLAIN` routing и retrieval есть, но отдельного docs answer-prompt слоя пока нет
- для docs full-chain пока не хватает собственных prompt names, prompt payload contract и post-gate правил
## 7. Что именно сейчас проверяет `pipeline_setup_v3`
YAML-кейс может проверять четыре группы ожиданий:
- `router`
- `retrieval`
- `llm`
- `pipeline`
Примеры ожиданий:
- ожидаемый `intent`
- ожидаемый `sub_intent`
- нужные слои в `layers_include`
- что retrieval не пустой
- что в answer есть нужные фразы
- какой `answer_mode` получился
## 8. Ключевые выводы о текущей архитектуре
### 8.1. Что уже сделано хорошо
- `pipeline_setup_v3` работает поверх реальных runtime-компонентов
- есть явный контракт между router → retrieval → evidence → answer
- есть два evidence gate
- есть structured diagnostics
- есть нормализованные типы `RetrievalRequest`, `RetrievalResult`, `EvidenceBundle`
### 8.2. Что остается code-first
- pre-evidence gate
- post-evidence gate
- prompt selector
- набор answer prompts
- часть логики нормализации evidence
### 8.3. Что это значит для docs use case
Сейчас docs use case уже частично внедрен:
- есть docs intent
- есть docs sub-intents
- есть docs layer mapping
- есть docs retrieval profile
Но для полноценного `full_chain` по документации еще не хватает:
- docs-oriented pre-gate правил
- docs-oriented post-gate правил
- docs-specific answer prompts
- docs-specific synthesis contract
- отдельных full-chain test cases для `DOCUMENTATION_EXPLAIN` и `FALLBACK`
## 9. Краткое резюме по компонентам
### Intent Router
Назначение:
- классифицировать запрос
- построить retrieval plan
- задать evidence policy
Выход:
- `IntentRouterResult`
### RAG
Назначение:
- вернуть evidence из многослойного индекса
Выход:
- `RetrievalResult`
- затем `EvidenceBundle`
### Pre-evidence gate
Назначение:
- решить, можно ли вообще уверенно отвечать
Выход:
- `passed/failure_reasons/degraded_message`
### Post-evidence gate
Назначение:
- проверить, grounded ли уже сгенерированный ответ
Выход:
- `return` или `repair`
### LLM
Назначение:
- классификация сложных интентов
- генерация финального ответа
- repair ответа при провале post-gate
Текущий фокус:
- в первую очередь `CODE_QA`
+323
View File
@@ -0,0 +1,323 @@
# 0) TL;DR (1015 строк)
- В `intent_router_v2` графы задаются как строковые `graph_id`: `CodeQAGraph` и `DocsQAGraph` (`app/modules/rag/intent_router_v2/intent/graph_id_resolver.py:4`, класс `GraphIdResolver.resolve`).
- Точка входа роутера: `IntentRouterV2.route(...)` (`app/modules/rag/intent_router_v2/router.py:35`, класс `IntentRouterV2`).
- Отдельные классы/функции с именами `CodeQAGraph` и `DocsQAGraph` в `app/` не найдены (поиск по коду).
- В тестовом CLI-пайплайне выполнение идет через `IntentRouterRagPipelineRunner.run_case(...)` (`tests/pipeline_intent_rag/helpers/pipeline_runner.py:45`) и `PipelineRuntime.run_*` (`tests/pipeline_intent_rag/helpers/runtime.py:31`).
- Контракт роутера возвращается моделью `IntentRouterResult` (`app/modules/rag/intent_router_v2/models.py:139`) и включает `intent`, `retrieval_profile`, `query_plan`, `retrieval_spec`, `retrieval_constraints`, `symbol_resolution`, `evidence_policy`.
- `sub_intent`, `symbol_kind_hint`, `symbol_candidates`, `path_hints`, `doc_scope_hints` находятся внутри `query_plan` (`app/modules/rag/intent_router_v2/models.py:43`).
- Топ-слои retrieval задаются фабрикой `RetrievalSpecFactory` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:9`).
- Реально индексируемые/используемые слои: `C0..C3`, `D1..D4` (`app/modules/rag/indexing/code/pipeline.py:37`, `app/modules/rag/indexing/docs/pipeline.py:24`, `app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:10`).
- В enum есть `C4..C6`, но build/retrieval-код для них в `app/modules/rag` (кроме `README` и `enums.py`) не найден (`app/modules/rag/contracts/enums.py:13`).
- Слой `F0` в `app/` и `tests/` не найден (поиск по коду).
- Ранжирование retrieval: `lexical_rank -> test_penalty -> layer_rank -> distance` (`app/modules/rag/persistence/retrieval_statement_builder.py:59`).
- В `pipeline_intent_rag` evidence gate отсутствует; в `chat` есть `CodeExplainEvidenceGate(min_excerpts=2)` (`app/modules/chat/evidence_gate.py:15`).
## 1) High-level pipeline (диаграмма текстом)
### CodeQAGraph
`Router -> Plan -> Retrieval per layer -> Merge/Rank -> Evidence gate -> LLM prompt builder`
- Router: `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`, класс `IntentRouterV2`).
Определяет `intent`, `graph_id`, `query_plan`, `retrieval_spec`, `retrieval_constraints`, `symbol_resolution`.
- Plan: `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`, класс `QueryPlanBuilder`).
Извлекает anchors, `sub_intent`, symbol/path/doc hints.
- Retrieval per layer: `RetrievalSpecFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:74`) + `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`).
Для `CODE_QA` выбираются `C*` слои; выполняется запрос в БД с фильтрами.
- Merge/Rank: `RetrievalStatementBuilder.build_retrieve` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
Все выбранные слои читаются одним SQL и сортируются по `lexical_rank`, `test_penalty`, `layer_rank`, `distance`.
- Evidence gate: в цепочке `tests/pipeline_intent_rag` не найден.
Для отдельного code-explain чата есть `CodeExplainEvidenceGate.evaluate` (`app/modules/chat/evidence_gate.py:19`).
- LLM prompt builder: в режиме `full_chain``GigaChatAnswerer.answer`/`_format_context` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:15`, `:27`).
Формируется prompt из вопроса и первых RAG-строк.
### DocsQAGraph
`Router -> Plan -> Retrieval per layer -> Merge/Rank -> Evidence gate -> LLM prompt builder`
- Router: `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`).
Для `DOCS_QA` ставит `retrieval_profile="docs"` (`app/modules/rag/intent_router_v2/router.py:112`).
- Plan: `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`).
Для `DOCS_QA` принудительно `sub_intent="EXPLAIN"` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:78`).
- Retrieval per layer: `RetrievalSpecFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:74`) + `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`).
Для docs выбираются `D*` слои; при `path_scope` используется docs-scoped набор (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:97`).
- Merge/Rank: `RetrievalStatementBuilder.build_retrieve` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
Механизм ранжирования тот же, что и для code.
- Evidence gate: для docs в `pipeline_intent_rag` не найден.
- LLM prompt builder: в `full_chain` тот же `GigaChatAnswerer` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:15`).
## 2) Router contract (фактическое API)
### Пример JSON (code)
```json
{
"intent": "CODE_QA",
"sub_intent": "EXPLAIN",
"retrieval_profile": "code",
"symbol_kind_hint": "class",
"symbol_candidates": ["Context"],
"path_hints": [],
"path_scope": [],
"doc_scope_hints": [],
"layers": ["C1_SYMBOL_CATALOG", "C0_SOURCE_CHUNKS", "C2_DEPENDENCY_GRAPH"],
"retrieval_constraints": {
"include_globs": ["src/**"],
"exclude_globs": ["tests/**", "**/test_*.py", "**/*_test.py"],
"prefer_globs": [],
"test_file_globs": [],
"test_symbol_patterns": [],
"max_candidates": 20,
"fuzzy_symbol_search": {"enabled": true, "max_distance": 2, "top_k": 5}
},
"symbol_resolution": {
"status": "pending",
"resolved_symbol": null,
"alternatives": ["Context"],
"confidence": 0.0
}
}
```
### Пример JSON (docs)
```json
{
"intent": "DOCS_QA",
"sub_intent": "EXPLAIN",
"retrieval_profile": "docs",
"symbol_kind_hint": "unknown",
"symbol_candidates": [],
"path_hints": ["README_DEPLOY.md"],
"path_scope": ["README_DEPLOY.md"],
"doc_scope_hints": ["README_DEPLOY.md", "README*", "docs/**", "**/*.md"],
"layers": ["D3_SECTION_INDEX", "D2_FACT_INDEX"],
"retrieval_constraints": {
"include_globs": ["README_DEPLOY.md", "docs/**", "README*", "**/*.md"],
"exclude_globs": [".venv/**", "node_modules/**", "**/*.bin", "**/*.png", "**/*.jpg"],
"prefer_globs": [],
"test_file_globs": [],
"test_symbol_patterns": [],
"max_candidates": 20,
"fuzzy_symbol_search": {"enabled": true, "max_distance": 2, "top_k": 5}
},
"symbol_resolution": {
"status": "not_requested",
"resolved_symbol": null,
"alternatives": [],
"confidence": 0.0
}
}
```
### Поля контракта
- `intent`, `retrieval_profile`: формируются в `IntentRouterV2.route` (`app/modules/rag/intent_router_v2/router.py:35`).
- `sub_intent`, `symbol_kind_hint`, `symbol_candidates`, `path_hints`, `doc_scope_hints`: внутри `QueryPlan` (`app/modules/rag/intent_router_v2/models.py:43`), заполняются в `QueryPlanBuilder.build` (`app/modules/rag/intent_router_v2/analysis/query_plan_builder.py:50`).
- `path_scope`: находится в `retrieval_spec.filters.path_scope`; строится `RetrievalFilterBuilder._path_scope` (`app/modules/rag/intent_router_v2/retrieval/retrieval_filter_builder.py:63`).
- `layers`: отдельного top-level поля в `IntentRouterResult` нет; фактический источник — `retrieval_spec.layer_queries[].layer_id` (`app/modules/rag/intent_router_v2/models.py:121`, `:125`).
- `retrieval_constraints`: модель `RetrievalConstraints` (`app/modules/rag/intent_router_v2/models.py:67`), заполняется в `RetrievalConstraintsFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_constraints_factory.py:22`).
- `symbol_resolution`: модель `SymbolResolution` (`app/modules/rag/intent_router_v2/models.py:79`), первичный статус задает `_initial_symbol_resolution` (`app/modules/rag/intent_router_v2/router.py:93`).
### `symbol_resolution` статусы и переходы
- Допустимые статусы: `not_requested`, `pending`, `resolved`, `ambiguous`, `not_found` (`app/modules/rag/intent_router_v2/models.py:15`).
- Начальное состояние: `IntentRouterV2._initial_symbol_resolution` (`app/modules/rag/intent_router_v2/router.py:93`).
- Изменение статуса в тестовом router+rag пайплайне: `_resolve_symbol` (`tests/pipeline_intent_rag/helpers/pipeline_runner.py:69`), где `pending` переходит в `resolved`/`ambiguous`/`not_found`.
### Где определены типы/схемы и где валидация
- Типы/схемы: `QueryPlan`, `RetrievalConstraints`, `SymbolResolution`, `RetrievalSpec`, `IntentRouterResult` в `app/modules/rag/intent_router_v2/models.py:43`, `:67`, `:79`, `:121`, `:139`.
- Валидация: pydantic-модели с `ConfigDict(extra="forbid")` (например `app/modules/rag/intent_router_v2/models.py:44`, `:68`, `:80`, `:122`, `:140`), плюс валидаторы confidence (`app/modules/rag/intent_router_v2/models.py:37`, `:208`).
## 3) Layer inventory (что реально индексируется и как)
### 3.1 Code layers
#### C0_SOURCE_CHUNKS
- Назначение: фрагменты исходного кода для цитирования и lexical fallback.
- Единица индексации: chunk.
- Источник данных: AST-разбор (`ast.parse`) с fallback на оконную нарезку (`app/modules/rag/indexing/code/code_text/chunker.py:17`).
- Схема записи:
- Базовые поля документа: `layer`, `repo_id`, `commit_sha`, `path`, `title`, `text`, `span_start/end`, `metadata`, `embedding` (`app/modules/rag/contracts/documents.py:62`).
- C0 metadata: `chunk_index`, `chunk_type`, `module_or_unit`, `is_test`, `artifact_type` (`app/modules/rag/indexing/code/code_text/document_builder.py:17`).
- Колонки БД: `rag_chunks` + `layer/lang/repo_id/commit_sha/title/metadata_json/span_*...` (`app/modules/rag/persistence/schema_repository.py:47`, `:112`).
- Где строится индекс: `CodeIndexingPipeline.index_file` (`app/modules/rag/indexing/code/pipeline.py:37`) + `CodeTextDocumentBuilder.build` (`app/modules/rag/indexing/code/code_text/document_builder.py:9`).
- Где retrieval: `RagDbAdapter.retrieve` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:41`) -> `RagRepository.retrieve|retrieve_lexical_code` (`app/modules/rag/persistence/repository.py:62`, `:87`).
- Как ранжируется: `build_retrieve` и `build_lexical_code` (`app/modules/rag/persistence/retrieval_statement_builder.py:9`, `:64`).
#### C1_SYMBOL_CATALOG
- Назначение: каталог символов (class/function/method/import aliases).
- Единица индексации: symbol.
- Источник данных: Python AST visitor (`app/modules/rag/indexing/code/symbols/extractor.py:32`).
- Схема записи:
- C1 metadata: `symbol_id`, `qname`, `kind`, `signature`, `decorators_or_annotations`, `docstring_or_javadoc`, `parent_symbol_id`, `package_or_module`, `is_entry_candidate`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/symbols/document_builder.py:20`).
- Где строится индекс: `SymbolExtractor.extract` (`app/modules/rag/indexing/code/symbols/extractor.py:24`) + `SymbolDocumentBuilder.build` (`app/modules/rag/indexing/code/symbols/document_builder.py:9`).
- Где retrieval: тот же общий путь SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:31`, `:39`, `:59`).
#### C2_DEPENDENCY_GRAPH
- Назначение: связи между символами.
- Единица индексации: edge.
- Источник данных: AST visitor по классам/функциям/import/call (`app/modules/rag/indexing/code/edges/extractor.py:33`).
- Схема записи:
- C2 metadata: `edge_id`, `edge_type`, `src_symbol_id`, `src_qname`, `dst_symbol_id`, `dst_ref`, `resolution`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/edges/document_builder.py:18`).
- Где строится индекс: `EdgeExtractor.extract` (`app/modules/rag/indexing/code/edges/extractor.py:24`) + `EdgeDocumentBuilder.build` (`app/modules/rag/indexing/code/edges/document_builder.py:9`).
- Где retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:59`).
Отдельно по `C2_DEPENDENCY_GRAPH`:
- Поддерживаемые `edge_type`: `inherits`, `imports`, `calls` (`app/modules/rag/indexing/code/edges/extractor.py:43`, `:58`, `:72`).
- `dst_ref`: собирается из AST-узла имени/атрибута (`app/modules/rag/indexing/code/edges/extractor.py:107`).
- `dst_symbol_id`: ищется в `qname_map` (`app/modules/rag/indexing/code/edges/extractor.py:89`).
- `resolution`: `"resolved"` если `dst_symbol_id` найден, иначе `"partial"` (`app/modules/rag/indexing/code/edges/extractor.py:102`).
#### C3_ENTRYPOINTS
- Назначение: точки входа (HTTP/CLI).
- Единица индексации: entrypoint.
- Источник данных: эвристики по decorators символов (`FastAPI`, `Flask`, `Typer/Click`) (`app/modules/rag/indexing/code/entrypoints/registry.py:23`).
- Схема записи:
- C3 metadata: `entry_id`, `entry_type`, `framework`, `route_or_command`, `handler_symbol_id`, `is_test`, `lang_payload`, `artifact_type` (`app/modules/rag/indexing/code/entrypoints/document_builder.py:17`).
- Где строится индекс: detectors `FastApiEntrypointDetector.detect`, `FlaskEntrypointDetector.detect`, `TyperClickEntrypointDetector.detect` (`app/modules/rag/indexing/code/entrypoints/fastapi_detector.py:11`, `flask_detector.py:9`, `typer_click_detector.py:9`).
- Где retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
- Как ранжируется: общий ranking SQL (`app/modules/rag/persistence/retrieval_statement_builder.py:59`), приоритет слоя `C3` в `layer_rank` = 0 (`app/modules/rag/persistence/retrieval_statement_builder.py:41`).
### 3.2 Docs layers
#### D1_MODULE_CATALOG
- Назначение: карточка модуля документации.
- Единица: документ на файл (если есть `id/type/domain` во frontmatter).
- Схема: `module_id`, `type`, `domain`, `status`, `version`, `tags`, `owners`, `links`, `source_path`, `summary_text`, `doc_kind` и поля связей (`calls_api`, `called_by`, ... ) (`app/modules/rag/indexing/docs/document_builder.py:10`).
- Build: `DocsIndexingPipeline.index_file` (`app/modules/rag/indexing/docs/pipeline.py:24`) -> `DocsDocumentBuilder.build_module_catalog` (`app/modules/rag/indexing/docs/document_builder.py:10`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
#### D2_FACT_INDEX
- Назначение: атомарные факты.
- Единица: fact.
- Схема: `fact_id`, `subject_id`, `predicate`, `object`, `object_ref`, `anchor`, `tags`, `source_path` (`app/modules/rag/indexing/docs/document_builder.py:86`).
- Build: `DocsIndexingPipeline._extract_facts/_facts_from_table/_facts_from_lists` (`app/modules/rag/indexing/docs/pipeline.py:55`, `:76`, `:118`) -> `build_fact` (`app/modules/rag/indexing/docs/document_builder.py:86`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
#### D3_SECTION_INDEX
- Назначение: секции документа.
- Единица: section chunk.
- Схема: `module_id`, `type`, `domain`, `tags`, `section_path`, `section_title`, `order`, `doc_kind`, `source_path`, `artifact_type` (`app/modules/rag/indexing/docs/document_builder.py:42`).
- Build: `MarkdownDocChunker.chunk` (`app/modules/rag/indexing/docs/chunkers/markdown_chunker.py:20`) -> `DocsDocumentBuilder.build_section` (`app/modules/rag/indexing/docs/document_builder.py:42`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
#### D4_POLICY_INDEX
- Назначение: policy-документы.
- Единица: policy doc.
- Схема: `policy_id`, `applies_to`, `rules`, `default_behaviors`, `doc_kind`, `section_path`, `source_path` (`app/modules/rag/indexing/docs/document_builder.py:64`).
- Build: только если `frontmatter.type == "policy"` (`app/modules/rag/indexing/docs/pipeline.py:36`) -> `build_policy` (`app/modules/rag/indexing/docs/document_builder.py:64`).
- Retrieval: общий SQL retrieval (`app/modules/rag/persistence/retrieval_statement_builder.py:9`).
## 4) Scoping & filtering (самое важное)
- `repo_id`/workspace scope при индексации:
- `RagService._resolve_repo_id` берет `project_id` из `rag_session`; fallback — сам `rag_session_id` (`app/modules/rag/services/rag_service.py:161`).
- `repo_id` пишется в документы/metadata (`app/modules/rag/services/rag_service.py:147`).
- Retrieval scope: SQL всегда фильтрует по `rag_session_id = :sid` (`app/modules/rag/persistence/retrieval_statement_builder.py:23`).
- `include_globs`, `exclude_globs`, `prefer_globs`:
- Формируются роутером в `RetrievalConstraintsFactory.build` (`app/modules/rag/intent_router_v2/retrieval/retrieval_constraints_factory.py:22`).
- В `RagDbAdapter.retrieve` применяются `include_globs`/`exclude_globs` через перевод в prefixes/LIKE (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:46`).
- Использование `prefer_globs` в retrieval-коде не найдено; найдено только формирование/тесты.
- `path_scope`:
- Формируется в `RetrievalFilterBuilder._path_scope` (`app/modules/rag/intent_router_v2/retrieval/retrieval_filter_builder.py:63`).
- В retrieval используется как prefixes: сначала точный scope, затем parent prefix, затем без scope (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:98`).
- До/после ранжирования:
- Path/layer/test exclude фильтры попадают в `WHERE` до `ORDER BY` (`app/modules/rag/persistence/retrieval_statement_builder.py:23`, `:58`, `:59`).
- `tests/**` penalty:
- Penalty вычисляется как `CASE WHEN lower(path) LIKE ... THEN 1 ELSE 0 END` при `prefer_non_tests=True` (`app/modules/rag/persistence/retrieval_statement_builder.py:176`).
- В `RagDbAdapter` `prefer_non_tests` зависит от `test_policy` (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:72`).
## 5) Merge policy (как собираются rag_rows)
- Top-K на слой задается в `retrieval_spec.layer_queries` (`app/modules/rag/intent_router_v2/models.py:125`) и заполняется `RetrievalSpecFactory` (`app/modules/rag/intent_router_v2/retrieval/retrieval_spec_factory.py:10`).
- Фактический SQL limit в `pipeline_intent_rag`: `limit = max(top_k)` по всем слоям, не отдельный budget на слой (`tests/pipeline_intent_rag/helpers/rag_db_adapter.py:56`).
- Смешивание слоев: один SQL по списку слоев `layer = ANY(:layers)` + общий `ORDER BY` (`app/modules/rag/persistence/retrieval_statement_builder.py:29`, `:59`).
- Политика merge: interleave/concat как отдельные алгоритмы не найдены; порядок задает SQL sorting.
- Дедупликация по `blob_sha`/`symbol_id` для `rag_rows` в `pipeline_intent_rag` не найдена.
- Общий budget:
- retrieval rows ограничены `LIMIT :lim` (`app/modules/rag/persistence/retrieval_statement_builder.py:60`);
- в `full_chain` в prompt уходит максимум 6 `rag_rows`, каждый `content` режется до 1200 символов (`tests/pipeline_intent_rag/helpers/llm_answerer.py:31`, `:34`).
## 6) Prompt builder / evidence packaging
- Формат передачи `rag_rows` в LLM (`pipeline_intent_rag/full_chain`):
- `GigaChatAnswerer._format_context` делает блоки `- path | title\\ncontent` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:35`),
- затем `answer` добавляет вопрос+контекст в `user_prompt` (`tests/pipeline_intent_rag/helpers/llm_answerer.py:17`).
- Evidence gate:
- в `pipeline_intent_rag` gate не найден;
- в direct code-explain есть `CodeExplainEvidenceGate(min_excerpts=2)` (`app/modules/chat/evidence_gate.py:16`), применяется в `CodeExplainChatService.handle_message` (`app/modules/chat/direct_service.py:46`).
- Шаблоны промптов:
- code explain: `app/modules/agent/prompts/code_explain_answer_v2.txt:1` (используется в `app/modules/chat/direct_service.py:50`, `app/modules/agent/engine/graphs/project_qa_step_graphs.py:169`);
- project/docs QA: `app/modules/agent/prompts/project_answer.txt:1` (используется в `app/modules/agent/engine/graphs/project_qa_graph.py:36`);
- general: `app/modules/agent/prompts/general_answer.txt:1` (используется в `app/modules/agent/engine/graphs/base_graph.py:62`).
## 7) Repro commands
- Построить новый индекс (новая `rag_session`) для локального репозитория:
```bash
python3 -m tests.pipeline_intent_rag reindex --repo-path /abs/path/to/repo
# optional:
python3 -m tests.pipeline_intent_rag reindex --repo-path /abs/path/to/repo --project-id my-project
```
Источник: `tests/pipeline_intent_rag/cli.py:143`, `:123`; README `tests/pipeline_intent_rag/README.md:54`.
- Прогнать тесткейсы:
```bash
python3 -m tests.pipeline_intent_rag run --mode router_only
python3 -m tests.pipeline_intent_rag run --mode router_rag --rag-session-id <uuid>
python3 -m tests.pipeline_intent_rag run --mode full_chain --rag-session-id <uuid>
```
Источник: `tests/pipeline_intent_rag/cli.py:135`, `:138`; README `tests/pipeline_intent_rag/README.md:49`.
- Получить `rag_rows` как в логах артефактов:
```bash
python3 -m tests.pipeline_intent_rag run --mode router_rag --rag-session-id <uuid> --test-name rag_probe
ls -t tests/artifacts/rag_probe_*.jsonl | head -n 1
tail -n 50 "$(ls -t tests/artifacts/rag_probe_*.jsonl | head -n 1)"
```
`rag_rows` сериализуются в JSONL через `PipelineResult.to_record` (`tests/pipeline_intent_rag/helpers/models.py:31`) и `ArtifactWriter` (`tests/pipeline_intent_rag/helpers/artifact_writer.py:20`), имя файла `<test_name>_<YYYYMMDD_HHMMSS>.jsonl` (`tests/pipeline_intent_rag/helpers/artifact_writer.py:16`).
## 8) Appendix: Code pointers
- Entrypoints:
- Router API: `app/modules/rag/intent_router_v2/router.py:35` (`IntentRouterV2.route`).
- Graph-id map: `app/modules/rag/intent_router_v2/intent/graph_id_resolver.py:4` (`GraphIdResolver`).
- CLI: `tests/pipeline_intent_rag/__main__.py:1`, `tests/pipeline_intent_rag/cli.py:178` (`main`).
- Ключевые классы:
- `IntentRouterV2`: `app/modules/rag/intent_router_v2/router.py:14`.
- `RagService`: `app/modules/rag/services/rag_service.py:20`.
- `CodeIndexingPipeline`: `app/modules/rag/indexing/code/pipeline.py:19`.
- `DocsIndexingPipeline`: `app/modules/rag/indexing/docs/pipeline.py:14`.
- `RagDbAdapter`: `tests/pipeline_intent_rag/helpers/rag_db_adapter.py:36`.
- `RetrievalStatementBuilder`: `app/modules/rag/persistence/retrieval_statement_builder.py:8`.
- Конфиги/переменные окружения:
- Test env: `tests/pipeline_intent_rag/.env.test:3`.
- Env loader: `tests/pipeline_intent_rag/helpers/env_bootstrap.py:13`.
- Run config keys: `tests/pipeline_intent_rag/helpers/pipeline_config.py:30`.
- Форматы данных (pydantic/dataclasses):
- Router schema: `app/modules/rag/intent_router_v2/models.py:43`, `:139`.
- RAG document dataclass: `app/modules/rag/contracts/documents.py:29`.
- Pipeline result JSONL model: `tests/pipeline_intent_rag/helpers/models.py:18`.
- Not found:
- Классы/функции `CodeQAGraph`, `DocsQAGraph` как реальные graph-реализации в `app/` не найдены (строки встречаются как `graph_id` map).
- `F0` layer не найден в `app/` и `tests/`.