Compare commits

..

7 Commits

949 changed files with 31937 additions and 97712 deletions
+6
View File
@@ -1,3 +1,9 @@
.env
.venv
__pycache__
# Pipeline harness: per-run artifacts (md/json from tests.pipeline_setup_v3/v4)
tests/**/test_runs/**/*.md
tests/**/test_runs/**/*.json
tests/**/test_results/**/*.md
tests/**/test_results/**/*.json
+5
View File
@@ -0,0 +1,5 @@
{
"files.exclude": {
"**/__pycache__": true
}
}
+5 -5
View File
@@ -915,15 +915,15 @@ flowchart TD
### 4.1.3. Канонический MVP runtime (CODE-first)
Единая точка входа исполнения — пакет `app.modules.agent.runtime`:
Единая точка входа исполнения — пакет `app.core.agent.runtime`:
- **Роутер:** `app.modules.agent.intent_router_v2`; он отвечает и за routing, и за retrieval planning.
- **LLM-слой:** `app.modules.agent.llm`; здесь живут `AgentLlmService`, `PromptLoader` и системные prompt assets.
- **Runtime:** `app.modules.agent.runtime`; внутри него stages разложены по подпакетам `retrieval`, `context`, `gates`, `answer_policy`, `generation`, `finalization`.
- **Роутер:** `app.core.agent.intent_router`; он отвечает и за routing, и за retrieval planning.
- **LLM-слой:** `app.core.agent.llm`; здесь живут `AgentLlmService`, `PromptLoader` и системные prompt assets.
- **Runtime:** `app.core.agent.runtime`; внутри него stages разложены по подпакетам `retrieval`, `context`, `gates`, `answer_policy`, `generation`, `finalization`.
- **Цепочка:** запрос → `IntentRouterV2` → retrieval planning → runtime retrieval adapter → нормализованный context/evidence → evidence gate 1 → answer policy → LLM generation → evidence gate 2 → finalization → diagnostics.
- **Evidence gates:** pre/post проверки достаточности evidence и качества ответа по сценарию.
- **Диагностика:** runtime возвращает machine-readable diagnostics и trace по стадиям.
- **RAG:** `app.modules.rag` больше не содержит agent use-case слоев; он остается инфраструктурой indexing/retrieval/storage.
- **RAG:** `app.core.rag` больше не содержит agent use-case слоев; он остается инфраструктурой indexing/retrieval/storage.
Тесты: `pipeline_setup_v3` и связанные suite-ы проверяют канонический runtime и его stage-based execution.
+4
View File
@@ -0,0 +1,4 @@
# Запросы
1. Какие методы апи есть в проекте
2. Какие методы апи есть для healthcheck
3. Где документация на healthcheck
BIN
View File
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
# Intents
## Domains
- `DOCS`
- `GENERAL`
- `CODE` - временно отключен
## GENERAL
### Intent `GENERAL_QA`
Общий интент для вопросов без точного маршрута.
В дальнейшем может использоваться как fallback.
Subintents:
- `SUMMARY` - ответы на общие вопросы по SUMMARY
## DOCS
### Intent `ARCHITECTURE`
Обработка вопросов по архитектуре.
Subintents пока отсутствуют.
Интент запланирован, без реализации.
### Intent `DOC_EXPLAIN`
Объяснение по документации.
Subintents:
- `SUMMARY` - краткое объяснение темы по SUMMARY-блокам документации
- `FIND_FILES` - поиск файлов с релевантной информацией
- `EXPLAIN_API` - объяснение работы метода
- `COMPONENT_INTEGRATIONS` - перечень интеграций компонента, API, UI, сущности, внешних систем
- `ENTITY_INTEGRATIONS` - перечень интеграций сущности
В текущем узком MVP реально реализованы только:
- `SUMMARY`
- `FIND_FILES`
Для запросов по интеграциям целевым retrieval-слоем является `D6_INTEGRATION_INDEX`.
### Intent `OPENAPI_GENERATION`
Генерация OpenAPI-спеки.
Subintents:
- `FULL_SPEC` - создание полной спецификации
### Intent `DOC_GENERATION`
Редактирование документации.
Subintents:
- `FROM_FEATURE` - создание документации из системной аналитики на фичу
+345
View File
@@ -0,0 +1,345 @@
# RAG
## Состояние as is
RAG сейчас используется как общее ядро индексации и retrieval по коду и документации.
Основной storage - `rag_session` и многослойный индекс в БД.
## Основные части
- `RagService` - фасад индексации и retrieval
- `DocsIndexingPipeline` - индексация документации
- `CodeIndexingPipeline` - индексация кода
- `RagRepository` - persistence и retrieval
- `IntentRouterV2` - планирование retrieval: слои, фильтры, ограничения
- `RuntimeRetrievalAdapter` - выполнение retrieval в runtime
## Индексация
Индексация идет по двум направлениям:
- `DOCS`
- `CODE`
На вход подается snapshot или changes.
Для каждого файла выбирается подходящий pipeline.
На выходе формируются документы по слоям и сохраняются в RAG-хранилище.
## Структура БД
Все слои сохраняются в общую таблицу `rag_chunks`.
### Общие поля по слоям
| Поле БД | Назначение |
|---|---|
| `rag_session_id` | идентификатор сессии индексации |
| `path` | путь исходного файла |
| `content` | основной текст записи для retrieval |
| `layer` | идентификатор слоя |
| `title` | короткий заголовок записи |
| `lang` | язык исходного содержимого, в основном для code-слоев |
| `repo_id` | идентификатор репозитория или проекта |
| `commit_sha` | версия кода или документов на момент индексации |
| `span_start`, `span_end` | диапазон строк в исходном файле, если он есть |
| `embedding` | векторное представление записи |
| `metadata_json` | структурированные атрибуты конкретного слоя |
### Поля со смыслом слоя
Смысл конкретного слоя хранится в `metadata_json`.
Именно эти атрибуты определяют, какой объект был извлечен и как его интерпретировать в retrieval.
Домены и поддомены должны храниться в `metadata_json` явно.
## Слои DOCS
### `D0_DOC_CHUNKS`
Задача:
Хранит текстовые фрагменты документации для retrieval по содержимому разделов.
Формирование:
Документ сначала разбирается на frontmatter и body, затем body режется на секции через markdown chunker.
Для каждой секции создается отдельная запись слоя.
Нарезка идет по разделам документа.
Только в fallback-сценарии, когда markdown-структура не найдена, используется нарезка по фиксированным текстовым чанкам.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `document_id` | идентификатор документа-источника | `frontmatter.id`, иначе путь файла |
| `type` | тип документа из frontmatter | `frontmatter.type` |
| `module` | модуль документа | `frontmatter.module` |
| `domain` | домен документа | `frontmatter.domain` |
| `subdomain` | поддомен документа | `frontmatter.subdomain` |
| `tags` | теги документа | `frontmatter.tags` |
| `section_path` | полный путь секции в иерархии заголовков | результат `MarkdownDocChunker` |
| `section_title` | заголовок текущей секции | результат `MarkdownDocChunker` |
| `order` | порядок секции внутри документа | результат `MarkdownDocChunker` |
| `doc_kind` | классификация документа, например `readme`, `spec`, `runbook` | `DocsClassifier.classify(path)` |
| `source_path` | исходный путь документа | путь файла |
| `artifact_type` | тип артефакта, здесь `DOCS` | константа builder |
Связанные классы:
`DocsIndexingPipeline`, `DocsContentParser`, `MarkdownDocChunker`, `DocsDocumentBuilder`
### `D1_DOCUMENT_CATALOG`
Задача:
Хранит карточку документа как точку входа в документ и его краткое описание.
Формирование:
Источник данных - frontmatter `as is`, summary и doc kind, вычисленный классификатором документации.
В `metadata_json` копируются все `key-value` из frontmatter без нормализации и без fallback для frontmatter-атрибутов.
Дополнительно в `metadata_json` добавляются служебные поля `source_path`, `summary_text`, `doc_kind`.
Атрибут `document_id` добавляется только при наличии `frontmatter.id` (fallback до пути файла не применяется).
В `content` попадает summary документа, а не склейка всех частей документа в сплошной текст.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `*` frontmatter fields | все поля frontmatter в исходном виде | frontmatter документа |
| `document_id` | идентификатор документа, добавляется только если в frontmatter есть `id` | `frontmatter.id` |
| `source_path` | исходный путь документа | путь файла |
| `summary_text` | краткое содержание документа | секция `# Summary` |
| `doc_kind` | классификация документа, например `readme`, `spec`, `runbook` | `DocsClassifier.classify(path)` |
Связанные классы:
`DocsIndexingPipeline`, `DocsFrontmatterParser`, `DocsClassifier`, `DocsContentParser`, `DocsDocumentBuilder`
### `D2_FACT_INDEX`
Задача:
Хранит атомарные факты в форме `subject-predicate-object` для точного retrieval по утверждениям.
Формирование:
Факты извлекаются из frontmatter и секций документа, после чего каждая найденная тройка превращается в отдельную запись.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `fact_id` | идентификатор факта | вычисляется builder из содержимого факта и пути |
| `subject_id` | субъект факта | `DocsFactExtractor` |
| `predicate` | предикат или тип связи | `DocsFactExtractor` |
| `object` | значение или объект факта | `DocsFactExtractor` |
| `object_ref` | ссылка на объект, если она выделена отдельно | `DocsFactExtractor` |
| `anchor` | место в документе, откуда взят факт | `DocsFactExtractor` |
| `tags` | теги факта | `DocsFactExtractor` |
| `source_path` | исходный путь документа | путь файла |
Связанные классы:
`DocsIndexingPipeline`, `DocsFactExtractor`, `DocsDocumentBuilder`
### `D3_ENTITY_CATALOG`
Задача:
Хранит сущности, найденные в документации, чтобы искать документы и связи вокруг конкретной сущности.
Формирование:
Сущности извлекаются из frontmatter документа, после чего каждая сущность сохраняется отдельной записью.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `entity_name` | имя сущности | `DocsEntityExtractor` |
| `document_id` | идентификатор документа, где найдена сущность | `frontmatter.id`, иначе путь файла |
| `document_type` | тип документа-источника | `frontmatter.type` |
| `module` | модуль документа | `frontmatter.module` |
| `domain` | домен документа | `frontmatter.domain` |
| `subdomain` | поддомен документа | `frontmatter.subdomain` |
| `tags` | теги документа или сущности | `frontmatter.tags` |
| `source_path` | исходный путь документа | путь файла |
Связанные классы:
`DocsIndexingPipeline`, `DocsEntityExtractor`, `DocsDocumentBuilder`
### `D4_WORKFLOW_INDEX`
Задача:
Хранит workflow и сценарии из документации для ответов про flow, шаги и жизненный цикл процесса.
Формирование:
Workflow извлекаются из detail sections документа и сохраняются как отдельные сценарии.
Извлечение идет из структуры `Details -> ## Сценарий`.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `workflow_id` | идентификатор сценария | вычисляется builder из названия, anchor и документа |
| `document_id` | идентификатор документа-источника | `frontmatter.id`, иначе путь файла |
| `workflow_name` | название сценария | блок `Details -> ## Сценарий -> **Название**` |
| `preconditions` | предусловия сценария | блок `Details -> ## Сценарий -> **Предусловия**` |
| `trigger` | триггер или событие запуска | блок `Details -> ## Сценарий -> **Триггер**` |
| `main_flow` | основной сценарий | блок `Details -> ## Сценарий -> **Основной сценарий**` |
| `alternative_flow` | альтернативные ветки | блок `Details -> ## Сценарий -> **Альтернативный сценарий**` |
| `error_handling` | обработка ошибок | блок `Details -> ## Сценарий -> **Обработка ошибок**` |
| `postconditions` | постусловия | блок `Details -> ## Сценарий -> **Постусловие**` |
| `source_path` | исходный путь документа | путь файла |
Связанные классы:
`DocsIndexingPipeline`, `DocsWorkflowExtractor`, `DocsDocumentBuilder`
### `D5_RELATION_GRAPH`
Задача:
Хранит связи между документами и сущностями документации для navigation и related docs retrieval.
Формирование:
Связи извлекаются из frontmatter и сохраняются как отдельные relation edges.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `relation_id` | идентификатор связи | вычисляется builder из source, target, relation type и anchor |
| `source_id` | источник связи | `frontmatter.id` или source документа в extractor |
| `relation_type` | тип связи | `DocsRelationExtractor` |
| `target_id` | целевой объект связи | `DocsRelationExtractor` |
| `anchor` | место в документе, где обнаружена связь | `DocsRelationExtractor` |
| `source_path` | исходный путь документа | путь файла |
Связанные классы:
`DocsIndexingPipeline`, `DocsRelationExtractor`, `DocsDocumentBuilder`
### `D6_INTEGRATION_INDEX`
Задача:
Хранит прикладные интеграции компонента, API, UI, сущности или внешней системы.
Используется для ответов на вопросы вида "какие интеграции есть у компонента".
Формирование:
Интеграции извлекаются из блока `## Integrations` документа.
Одна интеграция должна превращаться в отдельную запись слоя.
Описание интеграции может быть развернутым, а структурированные атрибуты должны выделяться в словарь.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `integration_id` | идентификатор интеграции | вычисляется builder из source, target и anchor |
| `source_id` | идентификатор объекта, для которого описана интеграция | `frontmatter.id` документа-источника |
| `source_type` | тип исходного объекта | `frontmatter.doc_type` |
| `target` | целевой объект интеграции | блок `## Integrations` |
| `target_type` | тип целевого объекта, например `api`, `ui`, `entity`, `service`, `external_system` | блок `## Integrations` |
| `direction` | направление интеграции | блок `## Integrations` |
| `interaction` | тип взаимодействия | блок `## Integrations` |
| `via` | технический канал интеграции | блок `## Integrations` |
| `purpose` | назначение интеграции | блок `## Integrations` |
| `details` | дополнительные атрибуты интеграции в виде словаря | блок `## Integrations` |
| `domain` | домен документа | `frontmatter.domain` |
| `subdomain` | поддомен документа | `frontmatter.subdomain` |
| `source_path` | исходный путь документа | путь файла |
| `anchor` | место в документе, где описана интеграция | блок `## Integrations` |
Связанные классы:
`DocsIndexingPipeline`, `DocsIntegrationExtractor`, `DocsDocumentBuilder`
## Слои CODE
### `C0_SOURCE_CHUNKS`
Задача:
Хранит фрагменты исходного кода как базовый слой для цитирования, explain и точечной догрузки кода.
Формирование:
Исходный файл режется на кодовые чанки, и для каждого чанка создается отдельная запись.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `chunk_index` | порядковый номер чанка в файле | индекс чанка при нарезке |
| `chunk_type` | тип чанка, например функция, класс или текстовый блок | `CodeTextChunker` |
| `module_or_unit` | модуль, к которому относится chunk | вычисляется из пути файла |
| `is_test` | признак тестового файла | `is_test_path(path)` |
Связанные классы:
`CodeIndexingPipeline`, `CodeTextChunker`, `CodeTextDocumentBuilder`
### `C1_SYMBOL_CATALOG`
Задача:
Хранит символы кода: классы, функции и методы. Используется для поиска по именам и структуре кода.
Формирование:
Символы извлекаются `SymbolExtractor`, и каждый символ сохраняется как отдельная запись.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `symbol_id` | идентификатор символа | `SymbolExtractor` |
| `qname` | полное квалифицированное имя | `SymbolExtractor` |
| `kind` | тип символа: класс, функция, метод | `SymbolExtractor` |
| `signature` | сигнатура символа | `SymbolExtractor` |
| `parent_symbol_id` | родительский символ | `SymbolExtractor` |
| `package_or_module` | модуль или пакет символа | вычисляется из пути файла |
| `is_test` | признак тестового файла | `is_test_path(path)` |
Связанные классы:
`CodeIndexingPipeline`, `PythonAstParser`, `SymbolExtractor`, `SymbolDocumentBuilder`
### `C2_DEPENDENCY_GRAPH`
Задача:
Хранит связи между символами кода: вызовы, импорты, наследование. Используется для анализа зависимостей и flow.
Формирование:
Связи строятся `EdgeExtractor` по AST и списку символов, после чего каждая связь сохраняется отдельной записью.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `edge_id` | идентификатор связи | `EdgeExtractor` |
| `edge_type` | тип связи: вызов, импорт, наследование | `EdgeExtractor` |
| `src_symbol_id` | исходный символ | `EdgeExtractor` |
| `src_qname` | полное имя исходного символа | `EdgeExtractor` |
| `dst_symbol_id` | целевой символ, если он разрешен | `EdgeExtractor` |
| `dst_ref` | текстовая ссылка на целевой символ | `EdgeExtractor` |
| `resolution` | статус разрешения связи | `EdgeExtractor` |
| `is_test` | признак тестового файла | `is_test_path(path)` |
Связанные классы:
`CodeIndexingPipeline`, `EdgeExtractor`, `EdgeDocumentBuilder`
### `C3_ENTRYPOINTS`
Задача:
Хранит точки входа приложения: HTTP routes, CLI commands и другие entrypoints.
Формирование:
Детекторы ищут HTTP и CLI точки входа по символам файла, после чего каждый найденный entrypoint сохраняется отдельной записью.
Фиксация в БД:
| Атрибут в `metadata_json` | Описание | Источник |
|---|---|---|
| `entry_id` | идентификатор точки входа | detector entrypoint model |
| `entry_type` | тип точки входа | detector entrypoint model |
| `framework` | framework, в котором найдена точка входа | detector entrypoint model |
| `route_or_command` | route или команда | detector entrypoint model |
| `handler_symbol_id` | идентификатор обработчика | detector entrypoint model |
| `handler_symbol` | имя обработчика | detector entrypoint model |
| `declaring_symbol` | символ, в котором объявлен entrypoint | detector entrypoint model |
| `entrypoint_kind` | вид точки входа | detector entrypoint model |
| `http_method` | HTTP-метод | detector entrypoint model |
| `route_path` | путь маршрута | detector entrypoint model |
| `decorator_text` | текст декоратора или объявления | detector entrypoint model |
| `summary_text` | краткое описание точки входа | detector entrypoint model |
| `is_test` | признак тестового файла | `is_test_path(path)` |
| `lang_payload` | дополнительные данные детектора | detector metadata |
| `artifact_type` | тип артефакта, здесь `CODE` | константа builder |
Связанные классы:
`CodeIndexingPipeline`, `EntrypointDetectorRegistry`, `FastApiEntrypointDetector`, `FlaskEntrypointDetector`, `TyperClickEntrypointDetector`, `EntrypointDocumentBuilder`
### `C4_SEMANTIC_ROLES`
Задача:
Слой объявлен в enum и retrieval-планах как слой семантических ролей кода.
Формирование:
Слой формируется на основе символов, связей, dataflow slices и execution traces.
В текущем runtime этот слой не используется как активный маршрут, так как домен `CODE` отключен.
Фиксация в БД:
Смысловые атрибуты слоя сохраняются в `rag_chunks.metadata_json`.
Точное краткое описание состава этих атрибутов в текущем документе пока не зафиксировано.
Связанные классы:
`CodeIndexingPipeline`, `SemanticRoleBuilder`, `SemanticRoleDocumentBuilder`
@@ -0,0 +1,289 @@
## 1. Формат ведения технической документации агентом
## 1.1. Общие принципы
Техническая документация, формируемая агентом, должна строиться как система атомарных, не пересекающихся по смыслу документов, связанных между собой явными ссылками.
Ключевые принципы:
- один документ описывает одну сущность или один устойчивый технический аспект;
- документ не должен дублировать соседние документы;
- общая система знаний должна собираться через ссылки, а не через копипасту;
- структура документации должна быть пригодна как для чтения человеком, так и для индексирования в RAG.
## 1.2. Требования к заголовкам
- Заголовок должен отражать только суть раздела.
- Заголовок не должен содержать метаданные (`id`, `doc_type`, `application`, `platform`, `domain`, `sub_domain`).
- Метаданные указываются отдельными строками в теле раздела или в YAML frontmatter.
Пример:
- правильно: `## 6.2 Метод UFS получения списка заказов`
- неправильно: `## 6.2 Блок api_method (id=..., platform=ufs)`
## 1.3. Базовые типы документных единиц
Базовые типы:
- `ui_page`
- `api_method`
- `logic_block`
Дополнительно могут использоваться:
- `architecture_overview`
- `integration_doc`
- `domain_entity`
- `glossary_item`
- `index_page`
## 1.4. Принцип декомпозиции страниц / файлов
### Один устойчивый объект - один документ
Если объект можно переиспользовать или на него могут ссылаться другие документы, его нужно выносить в отдельный файл.
### Документы не должны пересекаться по смыслу
Если описание повторяется в нескольких местах, нужно выделять общий документ и ссылаться на него.
### Use case и детали живут раздельно
Сценарий описывает поток работы, а детали выносятся в функциональные требования, отдельные блоки логики или контрактные описания.
## 1.5. Иерархическая организация документации
Документация должна быть организована как иерархическое дерево каталогов и файлов.
Пример:
```text
docs/
ui/
api/
logic/
domains/
integrations/
architecture/
glossary/
errors/
```
## 1.6. Учет связей между документами
Связи должны быть явными.
Примеры:
- UI-страница ссылается на вызываемые API;
- API-документ ссылается на используемые блоки логики;
- логический блок ссылается на интеграции;
- документ по коду ссылается на системную аналитику, инициировавшую изменения.
## 1.7. Формат markdown-документов
Каждый документ состоит из:
1. YAML frontmatter;
2. Markdown body.
## 1.8. YAML frontmatter
### Обязательные поля
- `id`
- `title`
- `doc_type`
- `status`
- `domain`
- `sub_domain`
- `related_docs`
### Рекомендуемые поля
- `owner`
- `entities`
- `tags`
- `feature`
- `system_analytics_refs`
- `source_of_truth`
- `related_code`
### Допустимые значения `doc_type`
- `ui_page`
- `api_method`
- `logic_block`
- `architecture_overview`
- `integration_doc`
- `domain_entity`
- `glossary_item`
- `index_page`
### Допустимые значения `status`
- `draft`
- `in_review`
- `approved`
- `outdated`
- `generated`
- `active`
## 1.9. Синхронизация с системной аналитикой
Техническая документация строится на основе системной аналитики (features).
Обязательно учитывать:
- концептуальный уровень аналитики;
- детализацию технической документации;
- согласованность терминов, ролей и интеграционных цепочек.
Если атрибуты или детали отсутствуют в аналитике:
- определить их из текста аналитики;
- дополнить данными из репозитория (код, контракты, существующие документы);
- зафиксировать итог в документации как явные метаданные и требования.
## 1.10. Формат body-разделов для блока изменений
Для секции изменений (по аналогии с разделом `6` в аналитике) использовать единый формат.
Под корнем секции изменений указывать общие атрибуты:
- `domain`
- `sub_domain`
Для каждого подраздела `X.Y` указывать метаданные строками сразу после заголовка:
- `id`
- `doc_type`
- `application`
- `platform`
## 1.11. Различие аналитики и документации
- Аналитика - концептуальный уровень, упрощенный use case.
- Документация - детальный инженерный уровень.
Для документации:
- технический use case должен быть детализированным;
- функциональные требования расширяют use case и описывают детали интеграций, логики и поведения;
- функциональные требования не должны копировать шаги сценария без добавления новой информации.
Источник правил:
- `src/app/core/agent/processes/v2/doc_rules_v2/common-elements/tech-use-case.md`
- `src/app/core/agent/processes/v2/doc_rules_v2/common-elements/fr.md`
## 1.12. Требования к `ui_page`
Обязательная структура:
- `### Технический use case`
- `### Требования к UI`
- `### Функциональные требования`
- `### Нефункциональные требования`
### Требования к UI
Внутри обязательно отдельно описывать каждую форму UI:
- табличное представление;
- пустой список (empty state);
- ошибка (error state).
Обязательные правила:
- если есть интеграция, обязательно описать показ ошибки;
- если показывается список, обязательно описать показ отсутствия данных.
### UI-элементы
UI-поля и элементы в документации описываются строго в таблицах.
Обязательные колонки (заполнять там, где применимо):
- `Код элемента`
- `Название и описание`
- `Данные`
- `Поведение`
- `Валидация`
## 1.13. Пользовательская аналитика для `ui_page`
События пользовательской аналитики оформляются таблицей:
- `Название события`
- `Описание`
- `Точка вызова`
- `Payload`
## 1.14. Требования к `api_method`
Обязательная структура:
- `### Технический use case`
- `### Функциональные требования`
- `### Нефункциональные требования`
- `### Контракт`
### Технический use case
Оформляется детально по правилам `tech-use-case.md`.
Обязательные части:
- название
- предусловия
- триггер
- основной сценарий
- альтернативный сценарий
- обработка ошибок
- постусловие
### Функциональные требования
Оформляются по правилам `fr.md`:
- формат `FR.<номер>. <Название>`;
- FR расширяют use case;
- FR не дублируют шаги сценария без дополнительной ценности;
- для интеграционных шагов FR обязательны.
## 1.15. Нефункциональные требования для `api_method`
Разделять на подразделы:
- `#### Аудит` (если применимо)
- `#### Мониторинг`
### Мониторинг
Оформлять таблицей:
- `Метрика`
- `Описание`
- `Условие срабатывания`
Правила:
- в условиях указывать, при каких состояниях фиксируется событие;
- не использовать формулировку вида «точка измерения = метод»;
- базово закладывать метрики:
- `<METRIC_NAME>_SUCCESS`
- `<METRIC_NAME>_FAIL`
- `<METRIC_NAME>_BUSINESS_ERROR`
## 1.16. Распределение ответственности по слоям
- Проверка ролевой модели пользователя обычно выполняется в `ufs`.
- Для `pprb` аудит может не фиксироваться, если это согласовано правилами домена.
- Если проверка ролей вынесена в `ufs`, не дублировать этот шаг в use case `pprb`.
## 1.17. Контракты API
Контракт может быть:
- в markdown-таблицах;
- в OpenAPI;
- в отдельном контрактном файле.
Для markdown-контракта минимум:
- endpoint/method;
- request fields;
- required/optional;
- constraints;
- response;
- errors;
- auth;
- retry;
- timeout;
- idempotency.
## 1.18. Integrations-блок
Если у документа есть интеграции, выделять отдельный `## Integrations`.
Рекомендуемые атрибуты интеграции:
- `target`
- `target_type`
- `direction`
- `interaction`
- `via`
- `purpose`
- `details`
## 1.19. Общие требования к markdown body
- В документе должен быть один `H1`, совпадающий с `title`.
- Основные разделы - `H2`, подразделы - `H3`.
- Не допускать хаотичной вложенности заголовков.
- Вместо дублирования использовать ссылки на связанные документы.
- Сценарии, правила, ограничения и кодовые привязки держать раздельно.
@@ -0,0 +1,185 @@
# Системная аналитика
## Общее описание
Документ описывает изменения в автоматизированной системе. Пишется системными аналитиками для разработчиков и тестировщиков и проходит согласование с экспертами по архитектуре, безопасности и сопровождению.
Документ может описывать как новый процесс, так и инкремент доработки существующей функциональности.
## Требования к заголовкам
- Заголовок должен отражать суть раздела.
- Заголовок не должен содержать лишнюю информацию, которая относится к метаданным (id, doc_type, platform, application и т.д.).
- Метаданные указываются отдельными строками в теле раздела.
## Состав документа
Каждый раздел верхнего уровня оформляется заголовком уровня `#`.
### 1. Цели
- Коротко описать, какую проблему и для кого решаем.
- 1-2 предложения.
- Не дублировать критерии приемки.
### 2. Процесс AS IS и TO BE
- Фокус на пользовательских и бизнес-изменениях.
- Не указывать технические детали (платформы, API, внутренние интеграции).
### 3. Ограничения
- Ограничения и допущения в техническом и бизнесовом плане.
### 4. Критерии приемки
- Описывать с точки зрения пользователя.
- Не добавлять технические детали (платформы, API, внутренние компоненты).
### 5. Архитектура
Нужно указать:
- схему контейнеров,
- таблицу интеграций,
- сквозные интеграционные сценарии.
Слои:
- `ui` - web-приложение, клиент.
- `ufs` - BFF: аутентификация/авторизация, агрегация и маппинг данных.
- `pprb` - backend: API, БД, логика жизненного цикла сущностей.
#### Диаграмма
Mermaid-диаграмма должна содержать:
- основные контейнеры,
- названия приложений и платформ,
- интеграции между приложениями,
- названия вызываемых endpoint или топиков.
#### Таблица интеграций
Обязательные колонки:
- Код
- Название endpoint/топика
- Источник данных
- Потребитель данных
- Инициатор вызова
- Передаваемые данные
#### Сквозной интеграционный сценарий
- Нумерованный список вызовов вида: «Компонент 1 вызывает endpoint в Компонент 2».
- Только интеграционная цепочка, без детального разбора логики.
### 6. Описание изменений
Раздел состоит из подразделов уровня `##` (например, `6.1`, `6.2`, `6.3`).
Под корнем раздела `# 6` указываются общие метаданные:
- `domain`
- `sub_domain`
Для каждого раздела `6.x` обязательно указывать метаданные строками сразу после заголовка:
- `id`
- `doc_type`
- `application`
- `platform`
#### 6.x для `ui_page`
Обязательная структура:
- `### Технический use case (тезисно)`
- `### Требования к UI`
- `### Функциональные требования`
- `### Нефункциональные требования`
Требования к разделу `### Требования к UI`:
- Внутри нужно отдельно описывать каждую UI-форму.
- Если есть интеграция, обязательно описать, как показывается ошибка.
- Если показываем список, обязательно описать, как показывается отсутствие данных.
Рекомендуемая детализация UI-форм:
- табличное представление,
- пустой список (empty state),
- ошибка (error state).
Правила описания UI-полей:
- Поля описывать списком (не таблицей).
- Общие правила (например, read-only, поведение при пустом значении) выносить в общий блок, не дублировать для каждого поля.
Нефункциональные требования для `ui_page`:
- пользовательская аналитика оформляется таблицей с колонками:
- `Название события`
- `Описание`
- `Точка вызова`
- `Payload`
#### 6.x для `api_method`
Обязательная структура:
- `### Технический use case (тезисно)`
- `### Функциональные требования`
- `### Нефункциональные требования`
- `### Контракт метода`
Правило для функциональных требований:
- Если дополнительных требований нет (дублируют сценарий), писать: `Не выявлены`.
Нефункциональные требования:
- Разделять на подразделы:
- `#### Аудит` (если применимо)
- `#### Мониторинг`
Для `Мониторинг` использовать таблицу с колонками:
- `Метрика`
- `Описание`
- `Условие срабатывания`
Важно:
- В мониторинге описывать условия срабатывания, а не «точку измерения = метод».
- Базово закладывать 3 метрики:
- `<METRIC_NAME>_SUCCESS`
- `<METRIC_NAME>_FAIL`
- `<METRIC_NAME>_BUSINESS_ERROR`
Контракт метода:
- Для запроса: таблица параметров (`header/query/path`) с колонками: название, тип параметра, тип данных, обязательность, описание, пример.
- Для тела JSON (если есть): структура отдельной таблицей.
- Для ответа JSON: таблица с колонками: название, тип данных, обязательность, описание, заполнение, пример.
#### 6.x для `logic_block`
Обязательная структура:
- `### Технический use case (тезисно)`
- `### Функциональные требования`
- `### Нефункциональные требования`
## Дополнительные правила по слоям
- Проверка ролевой модели пользователя обычно выполняется на уровне `ufs`.
- Для `pprb` аудит может не фиксироваться, если это правило принято для конкретной фичи/домена.
- Если проверка ролей вынесена в `ufs`, не дублировать этот шаг в сценарии `pprb`.
## Термины
- Аудит: события, которые фиксируют действия пользователя и позволяют ответить на вопрос «кто, что, когда сделал».
- Мониторинг: технические события/метрики для контроля стабильности и поиска сбоев.
-790
View File
@@ -1,790 +0,0 @@
## 1. Формат ведения технической документации агентом
## 1.1. Общие принципы
Техническая документация, формируемая агентом, должна строиться как **система атомарных, не пересекающихся по смыслу документов**, связанных между собой явными ссылками.
Ключевые принципы:
- один документ описывает одну сущность или один устойчивый технический аспект;
- документ не должен дублировать соседние документы;
- общая система знаний должна собираться через ссылки, а не через копипасту;
- структура документации должна быть пригодна как для чтения человеком, так и для индексирования в RAG.
## 1.2. Базовые типы документных единиц
На первом этапе логично сохранить текущую семантику типов документов, но перенести ее в файловую модель.
Базовые типы:
- `ui_page`
- `api_method`
- `logic_block`
Позже могут добавиться:
- `architecture_overview`
- `integration_doc`
- `domain_entity`
- `glossary_item`
- `index_page`
## 1.3. Принцип декомпозиции страниц / файлов
### Один устойчивый объект — один документ
Если объект можно переиспользовать или на него могут ссылаться другие документы, его надо выносить в отдельный файл.
Примеры:
- отдельная UI-страница;
- отдельный API endpoint;
- отдельный блок логики;
- отдельный интеграционный сценарий.
### Документы не должны пересекаться по смыслу
Если описание повторяется в нескольких местах, нужно выделять общий документ и ссылаться на него.
Примеры:
- фронтальная страница не должна заново описывать логику API;
- документ по API не должен заново раскрывать общую логику переиспользуемого блока;
- вместо дублирования должен быть переход по ссылке.
### Use case и детальные правила живут раздельно
Сценарий описывает поток работы, а детали выносятся в функциональные требования, отдельные блоки логики или контрактные описания.
Это важно и для RAG-индексации:
- сценарии индексируются как workflows;
- отдельные правила — как facts;
- сущности и блоки — как entities.
## 1.4. Иерархическая организация документации
Документация должна быть организована как иерархическое дерево каталогов и файлов, а не как набор неструктурированных страниц.
Пример верхнего уровня:
```text
docs/
ui/
api/
logic/
domains/
integrations/
architecture/
glossary/
errors/
```
Пример организации:
```text
docs/
ui/
order-create-page.md
order-edit-page.md
api/
orders-create.md
orders-get.md
logic/
order-validation.md
order-enrichment.md
architecture/
system-overview.md
integration-landscape.md
errors/
catalog.yaml
```
## 1.5. Учет связей между документами
Связи должны быть **явными и поддерживаемыми агентом**.
Примеры:
- UI-страница ссылается на API, который она вызывает;
- API-документ ссылается на переиспользуемые логические блоки;
- логический блок ссылается на связанные интеграции;
- архитектурный обзор ссылается на набор конкретных модулей и документов;
- документ по коду может ссылаться на системную аналитику, которая инициировала изменение.
Именно эта сеть ссылок затем индексируется в слоях:
- `D1_DOCUMENT_CATALOG`
- `D3_ENTITY_CATALOG`
- `D4_WORKFLOW_INDEX`
- `D5_REFERENCE_GRAPH`
- `D6_DOC_CODE_LINKS`
## 1.6. Формат markdown-документов
Основной формат технической документации — `Markdown`.
Каждый документ состоит из двух частей:
1. **YAML frontmatter** — структурные метаданные;
2. **Markdown body** — основное содержимое по шаблону.
## 3.7. YAML frontmatter
Frontmatter нужен для:
- определения типа документа;
- идентификации документа;
- определения его места в иерархии;
- фиксации связей с кодом и другими документами;
- выделения сущностей и тегов;
- упрощения построения слоев `D1`, `D3`, `D5`, `D6`.
### Базовый frontmatter
```yaml
---
id: ui-order-create-page
title: Страница создания заказа
doc_type: ui_page
domain: orders
status: draft
related_docs:
- api-orders-create
- logic-order-validation
entities:
- Order
- CreateOrder
tags:
- ui
- orders
- creation
#owner: system-analyst
#source_of_truth: code
#related_code:
# - src/orders/ui/create_page.tsx
# - src/orders/api/orders_controller.py
---
```
### Обязательные поля
- `id` — стабильный уникальный идентификатор документа;
- `title` — человекочитаемый заголовок;
- `doc_type` — тип документа;
- `related_docs` — ссылки на связанные документы;
- `status` — статус документа;
- `domain` - домен фичи (Карточка клиента, Задачи, Сделки, Предложения)
- `sub_domain` - поддомен внутри основной сущности (Счета, ЗДА, ECM)
### Рекомендуемые поля
- `owner`
- `entities`
- `tags`
- `parent`
- `children`
- `feature`
- `system_analytics_refs`
- `business_refs`
- `updated_from`
- `reviewers`
- `source_of_truth`
- `related_code`
### Допустимые значения `doc_type`
- `ui_page`
- `api_method`
- `logic_block`
- `architecture_overview`
- `integration_doc`
- `domain_entity`
- `glossary_item`
- `index_page`
### Допустимые значения `status`
- `draft`
- `in_review`
- `approved`
- `outdated`
- `generated`
- `active`
### Допустимые значения `source_of_truth`
- `code`
- `doc`
- `system_analysis`
- `business_requirements`
- `mixed`
## 1.8. Typed frontmatter для разных типов документов
У каждого типа документа есть:
- **общие поля**;
- **тип-специфичные поля**.
### Пример для `api_method`
```yaml
---
id: api.create_invoice
doc_type: api_method
domain: billing
title: Создание инвойса
endpoint: POST /api/v1/invoices
auth: USER
idempotent: false
timeout_ms: 3000
links:
called_by:
- ui.invoice_form
uses_logic:
- logic.invoice_validation
writes_db:
- db.invoices
- db.invoice_items
integrates_with:
- int.crm_sync
related_docs:
- ui.invoice_form
- logic.invoice_validation
related_code:
- services/billing/api/create_invoice.py
entities:
- Invoice
- CreateInvoice
tags:
- invoice
- create
- billing
status: active
version: 1.3
source_of_truth: code
---
```
### Для `api_method` полезно поддерживать
- `endpoint`
- `sup_parameters`
- `role_model_actions`
- `monitoring_actions`
- `audit_actions`
- `idempotent`
- `timeout_ms`
- `links.called_by`
- `links.uses_logic`
- `links.writes_db`
- `links.integrates_with`
### Для `ui_page` позже полезно поддерживать
- `calls_api`
- `user_analitycs_actions`
- `sup_parameters`
- `role_model_actions`
- `entry_points`
- `uses_logic`
### Для `logic_block` полезно поддерживать
- `called_from`
- `uses_logic`
- `reads_db`
- `writes_db`
- `integrates_with`
## 1.9. Двухслойная структура документа: `Summary` + `Details`
LLM не должна каждый раз тонуть в полном документе. Поэтому каждый документ должен содержать два уровня представления.
### `Summary`
Короткая, строго структурированная спецификация. Это слой **быстрого контекста**.
Рекомендуемый объем:
- примерно 30–60 строк;
- без длинных пояснений;
- только ключевые факты.
Пример:
```md
## Summary
- Purpose: создание инвойса из формы
- Actor: пользователь
- Trigger: Submit
- Main API: POST /api/v1/invoices (api.create_invoice)
- Validation: required fields, amount > 0, date <= today
- Errors: 400(field errors), 409(duplicate), 503(retryable)
- Analytics: event invoice_submit, invoice_error
```
### `Details`
Полное раскрытие объекта:
- use case;
- функциональные требования;
- UI;
- API;
- ошибки;
- НФТ;
- связи;
- кодовые привязки.
## 1.10. Общие требования к markdown body
1. В документе должен быть один `H1`, совпадающий с `title`.
2. Основные разделы используют `H2`.
3. Подразделы внутри разделов используют `H3`.
4. Не должно быть хаотической вложенности заголовков.
5. Один раздел должен описывать одну смысловую часть.
6. Текст не должен дублировать соседние документы.
7. Вместо дублирования должны использоваться явные ссылки на связанные документы.
8. Сценарии, правила, ограничения и ссылки на код должны быть отделены друг от друга.
## 1.11. Базовый каркас markdown-документа
```md
---
id: api-orders-create
title: Метод создания заказа
doc_type: api_method
domain: orders
status: draft
source_of_truth: code
related_docs:
- logic-order-validation
- ui-order-create-page
related_code:
- src/orders/api/create_order.py
entities:
- Order
- CreateOrder
tags:
- api
- orders
---
# Метод создания заказа
## Summary
- Purpose: создание заказа
- Actor: пользователь
- Trigger: submit формы
- Main API: POST /orders
## Details
### Описание
### Технический use case
### Функциональные требования
### Нефункциональные требования
### Контракт
## 3.13. Специализированные шаблоны документов
### UI Page
```md
# <Название страницы>
## Summary
## Назначение
## Контекст
## Технический use case
## Описание UI
## UI Elements
## Функциональные требования
## Нефункциональные требования
## Связанные API
## Связанные блоки логики
## Связанный код
## Связанные документы
## История изменений
```
#### Требования к разделу `Описание UI`
Для каждого элемента желательно описывать:
- тип элемента;
- назначение;
- источник данных;
- default / placeholder;
- правила активации;
- поведение при взаимодействии;
- валидацию.
#### Требования к разделу `UI Elements`
UI-элементы должны храниться в **табличном** или **полуструктурированном** виде.
Пример:
```md
## UI Elements
| id | type | label | data_source | validation | behavior |
|--------|--------|---------|------------|------------|----------|
| amount | input | Amount | local | >0 | enables submit |
| submit | button | Create | - | - | calls api.create_invoice |
```
Если модель UI сложная, допустим sidecar-файл `ui_elements.yaml` или `ui_elements.json` рядом с основным документом.
### API Method
```md
# <Название API метода>
## Summary
## Назначение
## Контекст
## Технический use case
## Функциональные требования
## Contract
## Errors
## Нефункциональные требования
## Связанные блоки логики
## Связанный код
## Связанные документы
## История изменений
```
#### Требования к разделу `Contract`
Контракт может:
- быть кратко описан прямо в документе;
- ссылаться на OpenAPI;
- ссылаться на отдельный контрактный файл.
Для REST API целевым источником истины должен становиться `OpenAPI`.
### Reusable Logic Block
```md
# <Название блока логики>
## Summary
## Назначение
## Контекст
## Технический use case
## Функциональные требования
## Ограничения и условия вызова
## Нефункциональные требования
## Связанные API / UI / integration points
## Связанный код
## Связанные документы
## История изменений
```
## 3.14. Машинно-читаемые API-контракты
Для API контрактов **источником истины** должен становиться:
- `OpenAPI` — предпочтительно;
- либо временно строгий markdown/yaml-контракт, если OpenAPI еще нет.
Минимальный набор для API-контракта:
- `endpoint`
- `method`
- `request fields`
- `required / optional`
- `constraints`
- `response`
- `errors`
- `idempotency`
- `retry`
- `timeout`
- `auth`
## 3.15. Каталог ошибок
Ошибки, HTTP-коды, retry-правила и клиентское поведение не должны размазываться по разным документам.
Нужен единый каталог ошибок, например `docs/errors/catalog.yaml`.
Пример:
```yaml
errors:
- error_id: invoice_validation_failed
http_code: 400
internal_code: BILLING_400_01
when: invalid request fields
client_behavior: show field errors
retry: false
owner: billing
- error_id: invoice_duplicate
http_code: 409
internal_code: BILLING_409_01
when: duplicate invoice detected
client_behavior: show duplicate warning
retry: false
owner: billing
- error_id: crm_sync_unavailable
http_code: 503
internal_code: BILLING_503_02
when: downstream CRM unavailable
client_behavior: retry later
retry: true
owner: billing
```
В API- и logic-документах лучше ссылаться на `error_id`, а не заново подробно описывать каждую ошибку.
## 3.16. Требования к качеству документа для RAG
1. **Явные заголовки** — не использовать безымянные блоки текста.
2. **Атомарные утверждения** — не смешивать несколько правил в одном пункте, если их можно разделить.
3. **Явные сущности** — использовать стабильные названия компонентов, API, модулей, страниц.
4. **Явные ссылки** — не писать «этот метод», если можно указать конкретную ссылку или идентификатор.
5. **Минимум дублирования** — повторяющийся контент должен заменяться ссылками.
6. **Привязка к коду** — если документ описывает кодовый объект, это должно быть явно указано.
7. **Разделение сценариев и правил** — workflow и fact-like требования должны быть отделены.
## 3.17. Как структура markdown помогает RAG
- `frontmatter` + заголовки → `D1_DOCUMENT_CATALOG`
- `entities`, `tags`, устойчивые термины → `D3_ENTITY_CATALOG`
- атомарные функциональные и нефункциональные требования → `D2_FACT_INDEX`
- `Технический use case``D4_WORKFLOW_INDEX`
- `related_docs`, явные ссылки → `D5_REFERENCE_GRAPH`
- `related_code`, упоминания symbols и файлов → `D6_DOC_CODE_LINKS`
- `Summary` → быстрый retrieval и short-form context для LLM
## 3.18. Принципы генерации документации агентом
Когда документ пишет агент, он должен:
- сначала извлечь evidence из кода, системной аналитики и существующих документов;
- определить тип документа;
- заполнить frontmatter;
- построить markdown body по шаблону;
- явно указать связи с кодом и другими документами;
- не дублировать уже существующее описание, если можно сослаться на него.
---
## 4.4. Layered RAG
RAG строится как система специализированных слоев для двух основных доменов:
- `CODE RAG`
- `DOCS RAG`
Каждый graph извлекает контекст не из одного общего индекса, а из нужного набора слоев в зависимости от intent.
## 4.5. Evidence gate
Перед синтезом ответа или документа агент должен проверять, хватает ли опоры.
Примеры:
- найден ли symbol;
- найдено ли достаточное количество code chunks;
- есть ли supporting relations;
- есть ли document evidence;
- есть ли docs ↔ code mapping.
Если опоры недостаточно, агент должен:
- деградировать в упрощенный режим;
- честно фиксировать неполноту ответа;
- при необходимости уходить в fallback.
## 4.6. Synthesis layer
На этом этапе LLM:
- агрегирует найденные артефакты;
- формирует объяснение;
- пишет документ;
- структурирует результат под нужный шаблон.
LLM не должна быть основным источником фактов. Фактическая основа должна приходить из RAG и диагностируемого pipeline.
## 4.7. Diagnostics
Система должна сохранять диагностический след:
- какой graph был выбран;
- какие слои использовались;
- что было найдено;
- где retrieval был слабым;
- почему был выбран fallback;
- какие evidence стали основой ответа.
## 4.8. Сценарии: Target Architecture vs MVP-now
### 4.8.1. Target Architecture
#### CODE
- `OPEN_FILE` — открыть конкретный файл;
- `OPEN_SYMBOL` — открыть класс / функцию / метод;
- `EXPLAIN` — объяснить, как работает сущность или фрагмент;
- `FIND_TESTS` — найти релевантные тесты;
- `FIND_ENTRYPOINTS` — найти основные точки входа;
- `RELATED_CODE` — найти связанные сущности и ближайший контекст.
#### DOCS
- `DOC_SEARCH` — найти релевантный фрагмент документации;
- `DOC_EXPLAIN` — кратко объяснить, что сказано в документации по теме;
- `DOC_ENTITY_LOOKUP` — найти разделы, связанные с сущностью или компонентом;
- `GENERATE_DOCS_FROM_CODE` — сформировать документацию по коду с нуля для модуля, класса, функции, компонента или сценария.
#### CROSS-DOMAIN
- `FIND_IMPLEMENTATION_BY_DOC` — найти реализацию по описанию;
- `FIND_DOC_BY_CODE` — найти документацию по коду;
- `COMPARE_DOCS_AND_CODE` — базовое сопоставление документации и реализации.
#### GENERAL / FALLBACK
- `GENERAL_QA` — общий сценарий ответа на вопрос, если домен или интент не удалось определить уверенно.
### 4.8.2. MVP-now
В текущем цикле фокус на сценариях:
- `OPEN_FILE`
- `EXPLAIN`
- `FIND_TESTS`
- `FIND_ENTRYPOINTS`
- `GENERAL_QA`
DOCS и CROSS_DOMAIN остаются частью target architecture; в текущем цикле они не являются обязательной частью test-first MVP.
---
## 5. Структура слоев RAG
## 5.1. CODE RAG
### C0 — Source Chunks
**Назначение:** базовые фрагменты исходного кода.
**Единица:** chunk кода.
**Как формируется:** исходные файлы обходятся и режутся на chunk’и с учетом структурных границ.
**Статус в MVP:** да.
### C1 — Symbol Catalog
**Назначение:** каталог модулей, классов, функций, методов и других значимых сущностей.
**Единица:** symbol.
**Как формируется:** из AST и синтаксического разбора кода.
**Статус в MVP:** да.
### C2 — Symbol Relations
**Назначение:** связи между symbols.
**Единица:** relation.
**Как формируется:** вторым проходом по AST и структурным зависимостям.
**Статус в MVP:** да, в ограниченном виде.
### C3 — Entrypoints
**Назначение:** каталог точек входа системы.
**Единица:** entrypoint.
**Как формируется:** специализированными детекторами entrypoint-паттернов.
**Статус в MVP:** да, минимально.
### C4 — Execution Paths
**Назначение:** типовые пути исполнения.
**Единица:** path.
**Как формируется:** поверх `C2` и `C3` через производную трассировку.
**Статус в MVP:** нет.
### C5 — Test Mappings
**Назначение:** связи production code ↔ tests.
**Единица:** mapping.
**Как формируется:** по путям, именам, импортам и конвенциям проекта.
**Статус в MVP:** да, минимально.
### C6 — Code Facts
**Назначение:** нормализованные факты из кода.
**Единица:** fact.
**Как формируется:** поверх `C1C3` как производный слой.
**Статус в MVP:** нет.
## 5.2. DOCS RAG
### D0 — Document Chunks
**Назначение:** базовые фрагменты документации.
**Единица:** document chunk.
**Как формируется:** документы нормализуются и режутся на chunk’и с сохранением `section path`.
**Статус в MVP:** да.
### D1 — Document Catalog
**Назначение:** каталог документов и разделов.
**Единица:** `document node / section node`.
**Как формируется:** из структуры документов и их заголовков.
**Статус в MVP:** да.
### D2 — Fact Index
**Назначение:** атомарные факты из документации.
**Единица:** fact.
**Как формируется:** из `D0/D1` через правила, шаблоны и при необходимости LLM extraction с валидацией.
**Статус в MVP:** частично.
### D3 — Entity Catalog
**Назначение:** каталог сущностей и понятий документации.
**Единица:** entity / concept.
**Как формируется:** из устойчивых терминов, заголовков, словарей и нормализации повторяющихся сущностей.
**Статус в MVP:** да, минимально.
### D4 — Workflow Index
**Назначение:** процедуры, сценарии, последовательности шагов.
**Единица:** workflow.
**Как формируется:** из use case, процессных разделов и последовательных описаний шагов.
**Статус в MVP:** нет.
### D5 — Reference Graph
**Назначение:** граф ссылок между документами, секциями, сущностями и фактами.
**Единица:** reference link.
**Как формируется:** из явных и неявных cross-links между документами.
**Статус в MVP:** нет.
### D6 — Doc-Code Links
**Назначение:** мост между документацией и кодом.
**Единица:** `doc artifact ↔ code artifact link`.
**Как формируется:** из имен, aliases, путей, устойчивых терминов и других надежных соответствий.
**Статус в MVP:** да, минимально.
## 5.3. Layer scope: Target Architecture vs MVP-now
### 5.3.1. Target Architecture
Полная карта слоёв:
- **CODE:** C0C6 (Source Chunks, Symbol Catalog, Symbol Relations, Entrypoints, Execution Paths, Test Mappings, Code Facts)
- **DOCS:** D0D6 (Document Chunks, Document Catalog, Fact Index, Entity Catalog, Workflow Index, Reference Graph, Doc-Code Links)
### 5.3.2. MVP-now
**Обязательные сейчас:**
- `C0_SOURCE_CHUNKS`
- `C1_SYMBOL_CATALOG`
- `C2_SYMBOL_RELATIONS`
- `C3_ENTRYPOINTS`
**В облегчённом виде:**
- `C5_TEST_MAPPINGS` или `C5-lite`
**Не блокируют текущий этап:**
- `C4_EXECUTION_PATHS`
- `C6_CODE_FACTS`
- весь docs runtime (слои D0D6 в исполнении runtime)
Слои документации остаются частью target architecture; docs retrieval пока не обязателен для текущего code-first milestone.
---
## 6. Итоговая рамка MVP-now
Сейчас система должна стабильно работать в **test-first** режиме.
**Фокус:**
- CODE_QA;
- через тесты настраиваются:
- intent routing (IntentRouterV2);
- layered retrieval;
- evidence sufficiency;
- answer quality;
- diagnostics.
**Не входят в текущий milestone:**
- UI-интеграция;
- docs runtime;
- полная интеграция orchestration переносится на следующий этап после стабилизации test pipeline.
В целевой архитектуре по-прежнему заложены:
- уверенная работа с кодом, symbols, entrypoints, тестами;
- ответ по документации и мост docs ↔ code;
- генерация документации по коду;
- fallback при неуверенном роутинге.
В MVP-now сознательно **не включаются** самые дорогие части:
- полноценные execution paths для всей системы;
- богатые fact-индексы по всем доменам;
- полный reference graph документации;
- глубокая автоматизация подготовки системной аналитики.
+100
View File
@@ -0,0 +1,100 @@
# MVP: процесс v1
## 1. Общее описание
Запрос пользователя обрабатывается цепочкой API → рантайм агента → зарегистрированный процесс версии `v1` → один workflow из трёх последовательных шагов. Процесс **не** обращается к RAG и **не** маршрутизирует интенты: текст сообщения передаётся в LLM по фиксированному промпту. Ответ агента — результат генерации с лёгкой постобработкой (trim).
```mermaid
flowchart LR
subgraph api [API]
RS[RequestService]
end
subgraph runtime [Agent runtime]
AR[AgentRuntime]
PR[ProcessRunner]
end
subgraph v1 [Процесс v1]
P1[V1Process]
WG[V1FlowMainGraph]
end
subgraph wf [Workflow v1.flow_main]
S1[PrepareUserMessageStep]
S2[GenerateAnswerStep]
S3[FinalizeAnswerStep]
end
LLM[AgentLlmService]
RS --> AR
AR --> PR
PR --> P1
P1 --> WG
WG --> S1 --> S2 --> S3
S2 --> LLM
```
Клиент создаёт запрос с `process_version: v1`. `AgentRuntime` поднимает `RuntimeExecutionContext` (запрос, сессия, publisher, trace), выбирает `V1Process` из реестра и вызывает `run`. `V1Process` собирает `V1FlowContext` и прогоняет линейный граф: подготовка текста, один вызов LLM, финализация строки ответа. Итог попадает в `ProcessResult.answer` и дальше в ответ пользователю.
---
## 2. Шаги и контракты
### 2.1. Вход в процесс: `V1Process.run`
| | |
|--|--|
| **Название** | Запуск процесса v1 |
| **Задача** | Собрать контекст workflow и выполнить граф до готового ответа. |
| **Вход** | `RuntimeExecutionContext`: `request` (в т.ч. `message`), `session`, `publisher`, `trace`. |
| **Выход** | `ProcessResult` с полем `answer: str`. |
| **Как работает** | Создаётся `V1FlowContext` с `prompt_name` по умолчанию `v1_flow_main.answer`. Вызывается `V1FlowMainGraph.run`. Возвращается ответ из контекста workflow. |
Код: `src/app/core/agent/processes/v1/process.py`.
---
### 2.2. Шаг workflow: `PrepareUserMessageStep`
| | |
|--|--|
| **Название** | Подготовка сообщения пользователя |
| **Задача** | Сформировать строку, которая уйдёт в LLM как пользовательский ввод. |
| **Вход** | `V1FlowContext` с заполненным `runtime` и `prompt_name`. |
| **Выход** | Тот же контекст с `prepared_message: str`. |
| **Как работает** | Берётся `context.runtime.request.message` и обрезаются пробелы по краям (`strip`). Результат пишется в `prepared_message`. Других преобразований нет. |
Код: `src/app/core/agent/processes/v1/workflow/flow_main/steps/prepare_user_message_step.py`.
---
### 2.3. Шаг workflow: `GenerateAnswerStep`
| | |
|--|--|
| **Название** | Вызов LLM |
| **Задача** | Сгенерировать ответ по выбранному промпту и подготовленному сообщению. |
| **Вход** | `V1FlowContext` с `prepared_message`, `prompt_name`, `runtime.trace` для модуля LLM. |
| **Выход** | Контекст с `answer: str` (сырой ответ модели). |
| **Как работает** | Асинхронно в пуле потоков вызывается `AgentLlmService.generate(prompt_name, prepared_message, ...)`. В trace подключается модуль `workflow.v1.llm`. Идентификатор запроса передаётся в `log_context` для логов. |
Код: `src/app/core/agent/processes/v1/workflow/flow_main/steps/generate_answer_step.py`.
---
### 2.4. Шаг workflow: `FinalizeAnswerStep`
| | |
|--|--|
| **Название** | Финализация ответа |
| **Задача** | Нормализовать строку ответа перед выдачей пользователю. |
| **Вход** | `V1FlowContext` с заполненным `answer` после LLM. |
| **Выход** | Контекст с обновлённым `answer`. |
| **Как работает** | К ответу применяется `strip()` по краям. Другой логики нет. |
Код: `src/app/core/agent/processes/v1/workflow/flow_main/steps/finalize_answer_step.py`.
---
### 2.5. Транспорт: `WorkflowGraph` (v1)
Граф для v1 использует стандартный `WorkflowGraph`: на каждом шаге пишутся события `workflow_started`, `step_started`, `step_completed`, `workflow_completed` в `runtime_traces` через `context.runtime.trace`.
Код: `src/app/core/agent/utils/workflow/graph.py`, обёртка `V1FlowMainGraph` в `src/app/core/agent/processes/v1/workflow/flow_main/graph.py`.
+33
View File
@@ -0,0 +1,33 @@
Нужно реализовать 2 вещи
Создать процесс внесения изменений в файл документации
Создать контекст этого процесса
Контекст наполнять атрибутами
что-то явно задано, фоллбэк через ллм
Написать тестовую аналитику - круд над сущностью
фронт, ефс, ппрб
Все в своей БД
Атрибуты сущности задать в требованиях
Аналитика имеет структуру
Внутри модули - один модуль на правку одного файла.
Модуль извлекается из аналитики парсером и из него формируется задача на редактирование файла
если парсер не сработал - фоллбэк ан ллм
Процесс редактирования работает стандартно
+220
View File
@@ -0,0 +1,220 @@
# MVP: процесс v2
## 1. Общее описание
Процесс v2 в текущем MVP ориентирован в первую очередь на **документацию проекта**, но роутер также поддерживает `GENERAL / GENERAL_QA / SUMMARY` для общих обзорных вопросов. Для документных веток нужна активная RAG-сессия с проиндексированными документами.
Это **узкий MVP**, а не полная target architecture. Поддерживаются три маршрута:
- `GENERAL`
- `GENERAL_QA`
- `SUMMARY`
- `DOCS`
- `DOC_EXPLAIN`
- `SUMMARY`
- `FIND_FILES`
Запрос проходит следующие смысловые этапы:
1. проверка готовности сессии;
2. intent routing;
3. формирование retrieval-параметров;
4. retrieval из `DOCS RAG`;
5. минимальная сборка evidence;
6. запуск task-focused workflow нужной ветки;
7. формирование ответа.
```mermaid
flowchart TB
subgraph api [API]
RS[RequestService]
end
subgraph runtime [Agent runtime]
AR[AgentRuntime]
PR[ProcessRunner]
end
subgraph v2 [Процесс v2]
P2[V2Process]
IR[V2IntentRouter]
POL[V2RetrievalPolicyResolver]
AD[V2RagRetrievalAdapter]
RSR[RagSessionRetriever]
ASM[DocsEvidenceAssembler]
end
subgraph rag [Пакет rag]
RR[RagRepository]
end
subgraph wf [Workflow]
SUM[DocsExplainSummaryGraph]
FF[DocsExplainFindFilesGraph]
end
LLM[AgentLlmService]
RS --> AR --> PR --> P2
P2 --> IR --> POL --> AD --> RSR --> RR
AD --> ASM
ASM --> SUM
ASM --> FF
SUM --> LLM
```
Клиент указывает `process_version: v2`. Без `active_rag_session_id` в сессии процесс возвращает сообщение об ошибке. Иначе выполняется цепочка:
маршрутизация → `RetrievalPlan` → retrieval строк из `DOCS RAG` → минимальная сборка evidence → ветвление по `subintent` → запуск workflow.
### Реализованные домены, интенты и сабинтенты
В коде заданы константы `V2Domain`, `V2Intent`, `V2Subintent`. Сейчас процесс intentionally ограничен одной рабочей областью.
| Уровень | Значение (строка) | Реализация |
|--------|-------------------|------------|
| **Домен (routing_domain)** | `DOCS` | Единственный поддерживаемый домен: документация проекта. |
| **Интент** | `DOC_EXPLAIN` | Единственный интент: объяснение по документации. |
| **Сабинтент** | `SUMMARY` | Объяснение темы по SUMMARY-блокам документации. |
| **Сабинтент** | `FIND_FILES` | Поиск путей к документам, где описана нужная сущность или тема. |
Итого в текущем MVP реализована **одна** рабочая тройка домен×интент: `DOCS` + `DOC_EXPLAIN`, с **двумя** ветками по сабинтенту.
---
## 2. Этапы вне workflow (внутри `V2Process.run`)
### 2.1. `V2IntentRouter.route`
| | |
|--|--|
| **Название** | Маршрутизация запроса (v2) |
| **Задача** | Определить домен, интент, subintent и извлечь якоря из текста. |
| **Вход** | `user_query: str` (текст сообщения пользователя). |
| **Выход** | `V2RouteResult`: `routing_domain`, `intent`, `subintent`, `user_query`, `normalized_query`, `target_terms`, `anchors` (`V2RouteAnchors`), `confidence`. |
| **Как работает** | Router реализован по схеме **LLM-first**: `normalization``target_terms`/`anchors extraction``LLM router``deterministic validator``fallback`. LLM является **основным селектором маршрута**. Deterministic-слой больше не выбирает маршрут по умолчанию: он отвечает только за extraction, валидацию enum/комбинаций и fallback при сломанном или невалидном ответе LLM. В trace пишется событие `intent_routed`. |
Код: `src/app/core/agent/processes/v2/intent_router/router.py`, `modules/normalizer.py`, `modules/target_terms.py`, `modules/anchors.py`, `routers/llm.py`, `routers/validator.py`, `routers/fallback.py`.
---
### 2.2. `V2RetrievalPolicyResolver.resolve`
| | |
|--|--|
| **Название** | Политика retrieval для v2 |
| **Задача** | По результату роутинга выбрать профиль, список слоёв RAG и лимит строк выдачи. |
| **Вход** | `V2RouteResult`. |
| **Выход** | `RetrievalPlan`: `profile`, `layers`, `limit`, опционально `filters`. |
| **Как работает** | Это отдельный смысловой шаг между routing и retrieval. Он не ходит в БД и не извлекает данные, а только подготавливает параметры поиска. Для `FIND_FILES` выбирается один профиль слоёв и лимит, для `SUMMARY` — другой. Лог: `retrieval_plan_resolved`. |
Код: `src/app/core/agent/processes/v2/retrieval/policy_resolver.py`.
---
### 2.3. `V2RagRetrievalAdapter` → `RagSessionRetriever.retrieve`
| | |
|--|--|
| **Название** | Загрузка сырых строк из RAG по плану |
| **Задача** | Делегировать поиск в единственную реализацию retrieval в пакете `rag`. |
| **Вход** | `rag_session_id`, `query_text` (нормализованный запрос), `RetrievalPlan`. |
| **Выход** | `list[dict]` — строки чанков в формате `RagRepository.retrieve` (поля `path`, `layer`, `metadata`, и т.д.). |
| **Как работает** | Выполняется retrieval по уже сформированному плану: профиль, список слоёв и лимит. На этом шаге происходит только извлечение сырых строк из `DOCS RAG`. Лог: `rag_rows_fetched`. |
Код адаптера: `src/app/core/agent/processes/v2/retrieval/v2_rag_adapter.py`.
Код API: `src/app/core/rag/retrieval/session_retriever.py`.
---
### 2.4. `DocsEvidenceAssembler`
| | |
|--|--|
| **Название** | Сборка evidence для задачи |
| **Задача** | Превратить сырые строки retrieval в списки summary или кандидатов файлов с дедупом и скорингом. |
| **Вход** | Список строк `rows`, `V2RouteResult` (для `target_terms`). |
| **Выход** | `list[RetrievedSummary]` или `list[RetrievedFile]`. |
| **Как работает** | Это **минимальная evidence-проверка**, достаточная для MVP. Для `SUMMARY` отбрасываются записи без summary-текста и summary-like секции, затем применяется дедуп и простой скоринг по терминам. Для `FIND_FILES` остаются только релевантные пути документов, также с дедупом и простым скорингом. Здесь нет сложной многоступенчатой валидации: задача шага — отфильтровать очевидный шум и передать в workflow компактное evidence. Лог: `evidence_assembled`. |
Код: `src/app/core/agent/processes/v2/evidence/assembler.py`.
---
## 3. Шаги workflow
Текущие workflow являются **task-focused**: каждая ветка решает одну узкую прикладную задачу и не содержит общей универсальной логики для всех типов вопросов.
### 3.1. Ветка `SUMMARY`: `GenerateSummaryAnswerStep`
| | |
|--|--|
| **Название** | Сборка ответа по summary |
| **Задача** | Сформировать ответ пользователю по найденным SUMMARY-блокам или сообщить об отсутствии. |
| **Вход** | `DocsExplainSummaryContext`: `runtime`, `route`, `rag_session_id`, `prompt_name`, `documents` (список `RetrievedSummary`). |
| **Выход** | Контекст с `answer: str`, `prompt_input` при успешном вызове LLM. |
| **Как работает** | Workflow получает уже отобранные summary-документы. Если документов нет — возвращает честный fallback-ответ. Иначе собирает prompt input из запроса пользователя и найденных summary-блоков и вызывает LLM. Workflow не занимается retrieval и не строит retrieval-план: он решает только задачу генерации ответа по уже подготовленному evidence. |
Код: `src/app/core/agent/processes/v2/workflows/docs_explain_summary/steps/generate_summary_answer_step.py`.
Граф: `DocsExplainSummaryGraph` (`V2WorkflowGraph`).
---
### 3.2. Ветка `FIND_FILES`: `FinalizeFindFilesAnswerStep`
| | |
|--|--|
| **Название** | Сборка списка файлов |
| **Задача** | Вывести пользователю markdown-список путей к файлам документации. |
| **Вход** | `DocsExplainFindFilesContext`: `runtime`, `route`, `rag_session_id`, `files` (`RetrievedFile`). |
| **Выход** | Контекст с `answer: str`. |
| **Как работает** | Workflow получает уже собранный список файлов и формирует финальный ответ. Если файлов нет — возвращает fallback. Если файлы есть — отдает детерминированный список путей. Эта ветка intentionally не использует LLM, потому что задача сводится к выдаче путей, а не к генерации объяснения. |
Код: `src/app/core/agent/processes/v2/workflows/docs_explain_find_files/steps/finalize_find_files_answer_step.py`.
Граф: `DocsExplainFindFilesGraph` (`V2WorkflowGraph`).
---
### 3.3. Транспорт: `V2WorkflowGraph`
| | |
|--|--|
| **Название** | Workflow v2 с буфером trace |
| **Задача** | Выполнить шаги без пошаговых `step_started`/`step_completed` в trace; один раз сбросить сводку. |
| **Вход** | Контекст workflow (`DocsExplainSummaryContext` или `DocsExplainFindFilesContext`). |
| **Выход** | Обновлённый контекст. |
| **Как работает** | Для каждого шага: `trace_input` до `run`, затем `run`, затем `trace_output`; записи копятся в список. В trace уходят `workflow_started`, затем `workflow_trace_flushed` с массивом шагов, затем `workflow_completed`. Статусы пользователю публикуются через `publisher` как и раньше. |
Код: `src/app/core/agent/processes/v2/workflows/v2_workflow_graph.py`.
---
## 4. Сборка в приложении
В `ModularApplication` создаются `RagSessionRetriever`, `V2RagRetrievalAdapter`, `V2RetrievalPolicyResolver`, `DocsEvidenceAssembler` и передаются в `V2Process` (см. `src/app/core/application.py`).
---
## 5. Итоговая концептуальная схема текущего MVP
В концептуальном виде текущий `v2` работает так:
1. **Session check**
Проверка, что есть активная RAG-сессия проекта.
2. **LLM-first intent routing**
Нормализация, extraction (`target_terms`, `anchors`), затем основной выбор маршрута через LLM.
3. **Deterministic validation + fallback**
Проверка enum/комбинации маршрута и fallback только если LLM не ответил или вернул невалидный маршрут.
4. **Retrieval parameter planning**
Формирование профиля поиска, слоёв и лимитов.
5. **RAG retrieval**
Загрузка сырых строк из `DOCS RAG`.
6. **Minimal evidence assembly**
Дедуп, базовый скоринг, отбор полезных summary или файлов.
7. **Task-focused workflow**
Узкая ветка `SUMMARY` или `FIND_FILES`.
8. **Final response**
Либо explanation через LLM, либо детерминированный список файлов.
Это и есть актуальная архитектура **узкого MVP**, синхронизированная с текущей реализацией.
@@ -0,0 +1,346 @@
# V2IntentRouter Architecture
## 1. Архитектура
Текущий `V2IntentRouter` реализован как **LLM-first router**.
Deterministic-слой не выбирает маршрут по умолчанию и используется только для:
- preprocessing
- validation ответа LLM
- fallback, если LLM не ответил или вернул невалидный маршрут
Актуальные компоненты:
- `router.py`
Главная точка входа и оркестратор пайплайна.
- `modules/normalizer.py`
Нормализация текста запроса в `normalized_query`.
- `modules/target_terms.py`
Извлечение retrieval-oriented `target_terms`, `endpoint_paths`, `matched_aliases`, `alias_docs`.
- `modules/anchors.py`
Извлечение `anchors` и marker-сигналов для fallback и downstream retrieval.
- `routers/route_catalog.py`
Каталог допустимых маршрутов (`allowed_routes`).
- `routers/llm.py`
Основной LLM-router. Получает нормализованный запрос, `target_terms`, `anchors` и список допустимых маршрутов.
- `routers/validator.py`
Deterministic validator для enum-значений, комбинации маршрута и базовой нормализации `confidence`.
- `routers/confidence.py`
Пост-обработка confidence после ответа LLM.
- `routers/fallback.py`
Fallback-маршрутизация, если LLM не ответил или ответ не прошёл validator.
- `routers/prompts.yml`
Prompt-контракт для LLM-router.
## 2. Контракт
### Вход
- `user_query: str`
### Выход
`V2RouteResult`:
- `routing_domain: str`
- `intent: str`
- `subintent: str`
- `user_query: str`
- `normalized_query: str`
- `target_terms: list[str]`
- `anchors: V2RouteAnchors`
- `confidence: float`
- `routing_mode: str`
- `llm_router_used: bool`
- `reason_short: str`
`V2RouteAnchors`:
- `entity_names: list[str]`
- `file_names: list[str]`
- `endpoint_paths: list[str]`
- `target_doc_hints: list[str]`
- `matched_aliases: list[str]`
- `process_domain: str | None`
- `process_subdomain: str | None`
## 3. Поддерживаемые домены, интенты и сабинтенты
### Домены
- `DOCS`
- `GENERAL`
### Интенты
- `DOC_EXPLAIN`
- `GENERAL_QA`
### Сабинтенты
- `SUMMARY`
- `FIND_FILES`
### Допустимые маршруты
- `GENERAL / GENERAL_QA / SUMMARY`
- `DOCS / DOC_EXPLAIN / SUMMARY`
- `DOCS / DOC_EXPLAIN / FIND_FILES`
Эти маршруты централизованно заданы в `routers/route_catalog.py`.
## 4. Актуальный флоу
Пайплайн обработки запроса:
1. `router.py` принимает `user_query`.
2. `modules/normalizer.py` строит `normalized_query`.
3. `modules/target_terms.py` извлекает:
- `target_terms`
- `endpoint_paths`
- `matched_aliases`
- `alias_docs`
4. `modules/anchors.py` строит:
- `anchors`
- `file_markers`
- `architecture_markers`
- `logic_markers`
- `domain_markers`
- `endpoint_markers`
5. `router.py` собирает `QueryFeatures`.
6. `routers/llm.py` вызывается как **основной селектор маршрута**.
7. `routers/validator.py` проверяет:
- что значения входят в допустимые enum
- что комбинация маршрута разрешена
- что `confidence` можно привести к `float`
8. `routers/confidence.py` корректирует confidence на основе силы сигналов.
9. Если ответ LLM валиден, возвращается `V2RouteResult` с `routing_mode="llm_default"`.
10. Если LLM не ответил, вернул сломанный JSON или невалидный маршрут, `routers/fallback.py` строит fallback route:
- `FIND_FILES`, если есть `file_markers`
- `DOCS / DOC_EXPLAIN / SUMMARY`, если есть docs-oriented anchors
- иначе `GENERAL / GENERAL_QA / SUMMARY`
## 5. Компоненты по флоу
### `router.py`
- Задача
Оркестрировать полный routing pipeline.
- Как решает
Последовательно вызывает:
- normalizer
- target terms extractor
- anchor extractor
- LLM router
- validator
- confidence adjuster
- fallback router
- Вход
`user_query: str`
- Выход
`V2RouteResult`
### `modules/normalizer.py`
- Задача
Привести запрос к стабильной форме для анализа.
- Как решает
Схлопывает лишние пробелы через `" ".join(...split())`.
- Вход
`user_query: str`
- Выход
`normalized_query: str`
### `modules/target_terms.py`
- Задача
Построить **чистое retrieval-поле** `target_terms`.
- Как решает
Использует позитивную модель отбора и включает в `target_terms` только:
- endpoint paths
- identifier-like tokens
- alias canonical terms
- domain terms
Исключаются:
- question words
- intent words
- filler/noisy words
- marker words
- короткие токены `< 3`, если это не endpoint или alias
- битые path-like токены
Дополнительно:
- lowercase
- trim punctuation по краям
- dedupe
- ограничение до `7` элементов
- приоритет: endpoints → identifiers → aliases → domain terms
- Вход
`normalized_query: str`
- Выход
`TargetTermsAnalysis`:
- `target_terms`
- `endpoint_paths`
- `matched_aliases`
- `alias_docs`
### `modules/anchors.py`
- Задача
Построить `anchors` и marker-сигналы, не смешивая их с `target_terms`.
- Как решает
Извлекает:
- `entity_names` из PascalCase-like токенов
- `file_names` только по жёстким правилам:
- `*.md`, `*.yaml`, `*.yml`, `*.json`
- `docs/...`, `doc/...`, `documentation/...`
- `endpoint_paths` из `TargetTermsAnalysis`
- `target_doc_hints` из alias docs, endpoint map и marker-сигналов
Marker-сигналы живут отдельно:
- `file_markers`
- `architecture_markers`
- `logic_markers`
- `domain_markers`
- `endpoint_markers`
- Вход
- `normalized_query: str`
- `TargetTermsAnalysis`
- Выход
`AnchorAnalysis`
### `routers/route_catalog.py`
- Задача
Держать один источник истины для допустимых маршрутов.
- Как решает
Возвращает:
- список `allowed_routes` для payload LLM
- проверку допустимости комбинации `routing_domain + intent + subintent`
### `routers/llm.py`
- Задача
Выбрать маршрут через LLM как основной селектор.
- Как решает
Формирует JSON payload из:
- `normalized_query`
- `target_terms`
- `anchors`
- `allowed_routes`
Затем:
- вызывает LLM
- парсит JSON
- возвращает сырой candidate route без deterministic business-routing
- Вход
- `normalized_query: str`
- `target_terms: list[str]`
- `anchors: dict`
- Выход
`dict | None`
### `routers/validator.py`
- Задача
Deterministic validation ответа LLM.
- Как решает
Проверяет:
- что `routing_domain`, `intent`, `subintent` заполнены
- что комбинация маршрута входит в `route_catalog`
- что `confidence` можно привести к числу
- Вход
`dict | None`
- Выход
Валидированный `dict | None`
### `routers/confidence.py`
- Задача
Сделать confidence осмысленным после ответа LLM.
- Как решает
Корректирует confidence:
- `-0.1`, если нет strong anchors
- `-0.1`, если запрос короткий или vague
- `+0.05`, если есть явный signal (`file_markers`, `endpoint_paths`, `endpoint_markers`)
- затем clamp в диапазон `0.0..1.0`
- Вход
- `confidence: float`
- `QueryFeatures`
- Выход
`confidence: float`
### `routers/fallback.py`
- Задача
Построить deterministic fallback, если LLM невалиден.
- Как решает
Правила:
- есть `file_markers``DOCS / DOC_EXPLAIN / FIND_FILES`
- есть docs-signals (`endpoint_paths`, `target_doc_hints`, `matched_aliases`, marker groups) → `DOCS / DOC_EXPLAIN / SUMMARY`
- иначе → `GENERAL / GENERAL_QA / SUMMARY`
- Вход
- `user_query: str`
- `QueryFeatures`
- `anchors: V2RouteAnchors`
- `llm_attempted: bool`
- Выход
`V2RouteResult`
### `routers/prompts.yml`
- Задача
Задать LLM-router контракт ответа и guidance по confidence.
- Как решает
Ограничивает модель только `allowed_routes` и требует JSON с полями:
- `routing_domain`
- `intent`
- `subintent`
- `confidence`
- `reason_short`
## 6. Ключевые инварианты
- LLM является default router.
- Deterministic-слой не принимает основной routing decision.
- `target_terms` содержат только retrieval-useful terms.
- `anchors` не содержат `terms`.
- `/health` и другие endpoint paths не должны попадать в `file_names`, если это не файл с расширением.
- `file_names` содержат только реальные file/doc paths.
- Fallback используется только если LLM недоступен или вернул невалидный маршрут.
@@ -0,0 +1,316 @@
# V2RetrievalPolicyResolver Architecture
## 1. Роль компонента
`V2RetrievalPolicyResolver` это deterministic bridge между `V2IntentRouter` и docs-RAG retrieval.
Компонент работает поверх уже готового `V2RouteResult` и не делает повторную интерпретацию пользовательского текста:
- не вызывает LLM;
- не меняет `intent` и `subintent`;
- не ранжирует документы;
- не собирает evidence.
Его задача: собрать один `RetrievalPlan` с полями:
- `profile`
- `layers`
- `limit`
- `filters`
## 2. Зависимости
Актуальная реализация опирается на:
- `src/app/core/agent/processes/v2/retrieval/policy_resolver.py`
- `src/app/core/agent/processes/v2/anchor_signals.py`
- `src/app/core/agent/processes/v2/models.py`
- `src/app/core/rag/contracts/enums.py`
- `src/app/core/agent/processes/v2/retrieval/v2_rag_adapter.py`
- `src/app/core/rag/retrieval/session_retriever.py`
- `src/app/core/rag/persistence/repository.py`
- `src/app/core/rag/persistence/query_repository.py`
- `src/app/core/rag/persistence/retrieval_statement_builder.py`
## 3. Входной контракт
Resolver использует:
- `route.intent`
- `route.subintent`
- `route.anchors.entity_names`
- `route.anchors.file_names`
- `route.anchors.endpoint_paths`
- `route.anchors.target_doc_hints`
- `route.anchors.matched_aliases`
- `route.anchors.process_domain`
- `route.anchors.process_subdomain`
`route.target_terms` в текущей реализации profile/filter branching не влияет.
## 4. Верхнеуровневый branching
`resolve(route)` имеет три ветки:
1. `GENERAL_QA` -> `general_qa_grounded_summary`
2. `FIND_FILES` -> `file_lookup`
3. иначе -> docs summary branch
Инварианты:
- `GENERAL_QA` всегда остаётся general profile;
- `FIND_FILES` всегда остаётся `file_lookup`;
- resolver всегда возвращает один валидный `RetrievalPlan`.
## 5. Внутренняя декомпозиция
Текущая реализация разбита на два helper-класса.
### `_AnchorTermCollector`
Собирает термы для `prefer_like_patterns`.
Источники:
- basename из `target_doc_hints`
- `endpoint_paths`
- `file_names`
- `entity_names`
- `matched_aliases`
- `process_domain`
- `process_subdomain`
Все значения нормализуются в lower-case и превращаются в SQL-like patterns вида `"%term%"`.
Для `FIND_FILES` действует отдельное правило:
- если есть `target_doc_hints`, `prefer_like_patterns` строится только по basename hints;
- иначе используется общий набор collected terms.
### `_RouteFilterBuilder`
Собирает `filters` для трёх веток:
- `general_filters(route)`
- `summary_filters(route)`
- `find_files_filters(route)`
Дополнительно содержит path selection:
- `_summary_prefixes(route)`
- `_find_files_prefixes(route)`
- `_find_files_prefer_prefixes(route)`
## 6. Signal detection
Summary profile и часть path preferences зависят от `anchor_signal_types(route)`.
Сигналы вычисляются так:
- `FIND_FILES`
- если `route.subintent == FIND_FILES`
- `API_ENDPOINT`
- если есть `endpoint_paths`
- или в `target_doc_hints` / `file_names` / `matched_aliases` встречаются маркеры `"/api/"`, `"api"`, `"endpoint"`
- `ARCHITECTURE`
- если в `target_doc_hints` / `file_names` / `matched_aliases` встречаются `"/architecture/"`, `"architecture"`, `"arch"`
- `LOGIC_FLOW`
- если в `target_doc_hints` / `file_names` / `matched_aliases` встречаются `"/logic/"`, `"logic"`, `"workflow"`, `"flow"`, `"process"`
- `DOMAIN_ENTITY`
- если есть `entity_names`
- или в `target_doc_hints` / `file_names` / `matched_aliases` встречаются `"/domains/"`, `"domain"`, `"entity"`, `"component"`
Важно:
- `process_domain` и `process_subdomain` сейчас **не участвуют** в signal detection;
- они влияют только на filters и `prefer_like_patterns`.
## 7. Summary profile selection
Метод `_summary_profile(route)` использует:
- `meaningful = anchor_signal_types(route) - {FIND_FILES}`
Правило:
- если meaningful signal не ровно один -> `docs_summary_generic`
- если ровно один:
- `API_ENDPOINT` -> `docs_summary_api_endpoint`
- `ARCHITECTURE` -> `docs_summary_architecture`
- `LOGIC_FLOW` -> `docs_summary_logic_flow`
- `DOMAIN_ENTITY` -> `docs_summary_domain_entity`
Следствие:
- конфликт API + architecture -> generic;
- API + entity -> generic;
- weak/no signals -> generic.
## 8. Profiles, layers, limits
### `general_qa_grounded_summary`
- condition: `route.intent == GENERAL_QA`
- layers: `[D1_DOCUMENT_CATALOG, D0_DOC_CHUNKS]`
- limit: `8`
### `file_lookup`
- condition: `route.subintent == FIND_FILES`
- layers: `[D1_DOCUMENT_CATALOG, D3_ENTITY_CATALOG]`
- limit: `12`
### `docs_summary_api_endpoint`
- layers: `[D1_DOCUMENT_CATALOG, D2_FACT_INDEX, D0_DOC_CHUNKS]`
- limit: `8`
### `docs_summary_logic_flow`
- layers: `[D4_WORKFLOW_INDEX, D1_DOCUMENT_CATALOG, D0_DOC_CHUNKS]`
- limit: `8`
### `docs_summary_domain_entity`
- layers: `[D3_ENTITY_CATALOG, D1_DOCUMENT_CATALOG, D0_DOC_CHUNKS]`
- limit: `8`
### `docs_summary_architecture`
- layers: `[D1_DOCUMENT_CATALOG, D5_RELATION_GRAPH, D0_DOC_CHUNKS]`
- limit: `8`
### `docs_summary_generic`
- layers: `[D1_DOCUMENT_CATALOG, D0_DOC_CHUNKS]`
- limit: `8`
## 9. Filters by branch
### General branch
`general_filters(route)` возвращает:
- `prefer_path_prefixes = ["docs/architecture/", "docs/"]`
- `prefer_like_patterns = ["%readme.md%", "%overview%"]`
- `target_doc_hints = list(route.anchors.target_doc_hints)`
Это обзорный, но не узкий plan: hard `path_prefixes` здесь нет.
### Summary branch
`summary_filters(route)` всегда включает:
- `target_doc_hints`
- `metadata.domain`, если есть `process_domain`
- `metadata.subdomain`, если есть `process_subdomain`
- `prefer_path_prefixes`
- `prefer_like_patterns`
Дополнительно:
- если есть `API_ENDPOINT` signal, добавляется hard `path_prefixes = ["docs/api/", "docs/"]`
`prefer_path_prefixes` для summary:
- API -> `["docs/api/", "docs/"]`
- ARCHITECTURE -> `["docs/architecture/", "docs/"]`
- LOGIC_FLOW -> `["docs/logic/", "docs/architecture/", "docs/"]`
- DOMAIN_ENTITY -> `["docs/domains/", "docs/", "docs/api/"]`
- empty signals -> `["docs/"]`
Если сигналов несколько, prefixes объединяются и dedupe-ятся с сохранением порядка.
### FIND_FILES branch
`find_files_filters(route)` всегда включает:
- `target_doc_hints`
- `metadata.domain`, если есть `process_domain`
- `metadata.subdomain`, если есть `process_subdomain`
- `path_prefixes`
- `prefer_path_prefixes`
- `prefer_like_patterns`
`path_prefixes` для `FIND_FILES` выбираются по приоритету:
1. директории из `target_doc_hints`
2. директории из `file_names`, если путь начинается с `docs/`
3. signal-based fallback:
- API -> `["docs/api/", "docs/"]`
- ARCHITECTURE -> `["docs/architecture/", "docs/"]`
- LOGIC_FLOW -> `["docs/logic/", "docs/"]`
- DOMAIN_ENTITY -> `["docs/domains/", "docs/"]`
4. default -> `["docs/"]`
`prefer_path_prefixes` для `FIND_FILES`:
- начинается с `path_prefixes`
- если есть `process_domain` или `process_subdomain`, дополнительно добавляет:
- `"docs/domains/"`
- `"docs/logic/"`
## 10. Hard и soft сигналы в текущей реализации
В терминах текущего кода:
Hard-ish / narrowing filters:
- `path_prefixes`
- `metadata.domain`
- `metadata.subdomain`
Soft preferences:
- `prefer_path_prefixes`
- `prefer_like_patterns`
Отдельно:
- `target_doc_hints` всегда сохраняются в `RetrievalPlan.filters`, но **не маппятся напрямую** в `RagRepository.retrieve(...)` как SQL hard filter.
То есть сейчас `target_doc_hints` это не прямой DB filter, а downstream anchor для других шагов пайплайна и для deterministic exact-doc seeding logic.
## 11. Интеграция с retrieval stack
Следующий слой после resolver теперь исполняет plan не напрямую в `V2Process`, а через `V2RagRetrievalAdapter`.
`V2RagRetrievalAdapter.fetch_rows(...)` использует `RetrievalPlan` так:
- читает `filters["target_doc_hints"]` из самого плана;
- делает exact-path seed через `retrieve_exact_files(...)`;
- для missing hints делает substring fallback через `retrieve_chunks_by_path_substrings(...)`;
- затем делает обычный semantic retrieve через `RagSessionRetriever.retrieve(...)`;
- объединяет exact / substring / semantic rows через dedupe merge.
Это важный сдвиг: execution strategy теперь зависит от **контракта `RetrievalPlan`**, а не от скрытой route-specific логики внутри `V2Process`.
`RagSessionRetriever._map_filters()` прокидывает в `RagRepository.retrieve(...)`:
- `path_prefixes`
- `exclude_path_prefixes`
- `exclude_like_patterns`
- `prefer_path_prefixes`
- `prefer_like_patterns`
- `prefer_non_tests`
- `metadata_domain` из `filters["metadata.domain"]`
- `metadata_subdomain` из `filters["metadata.subdomain"]`
`RetrievalStatementBuilder.build_retrieve(...)` добавляет SQL predicates:
- `lower(metadata_json->>'domain') = :metadata_domain`
- `lower(metadata_json->>'subdomain') = :metadata_subdomain`
Таким образом:
- `process_domain/process_subdomain` реально участвуют в retrieval query;
- `target_doc_hints` реально участвуют в retrieval execution strategy на уровне adapter;
- `V2RetrievalPolicyResolver` определяет plan contract, а следующий шаг исполняет этот contract более буквально.
## 12. Актуальные ограничения
- Логика полностью deterministic.
- `target_terms` сейчас не участвуют в branching resolver.
- `process_domain/process_subdomain` не влияют на summary profile selection.
- API signal добавляет `path_prefixes` даже в generic summary, если среди конфликтующих сигналов присутствует API.
- `target_doc_hints` не являются прямым SQL filter внутри обычного `retrieve`, но используются adapter-уровнем для exact-path / substring seeding до semantic retrieval.
+37
View File
@@ -0,0 +1,37 @@
# Documentation Rules V3
Этот каталог содержит правила генерации технической документации из системной аналитики.
## Цель
- синхронизировать требования к документации с требованиями к аналитике (`04. Analitycs artefacts - features.md`);
- сохранить детальность техдокументации по сравнению с аналитикой;
- убрать дублирование структуры и manifest-слоя между разными файлами;
- собирать итоговый промпт из модулей: глобальные правила + template с manifest + блоки.
## Структура
- `documentation-rules.md` — верхнеуровневый регламент и порядок сборки.
- `global/` — общие правила (заголовки, frontmatter, слой ответственности, мост аналитика->документация).
- `common-elements/` — правила для общих блоков (`summary`, `details`, `use case`, `FR`, `NFR`, `UI`, `Contract`).
- `templates/` — единственный источник истины для структуры итоговой страницы и manifest-метаданных типа документа.
## Принцип сборки
Для конкретного документа агент собирает единый набор правил из:
1. `documentation-rules.md`
2. `global/*.md`
3. `templates/<doc_type>.template.md`
4. `common-elements/*.md`, указанных в frontmatter template
## Правило без дублирования
- `templates/` отвечают за структуру документа, порядок разделов и manifest-метаданные типа.
- `common-elements/` отвечают только за правила написания конкретного раздела.
- отдельный слой `types/` не нужен, если для типа документа используется один основной template.
## Формат template-manifest
Manifest оформляется в YAML frontmatter самого template.
Обязательные поля manifest:
- `doc_type`
- `required_common_elements`
Рекомендуемые поля:
- `special_rules`
@@ -0,0 +1,21 @@
# API Contract Rules
## Обязательные части
- request parameters (`header/query/path`)
- request body (если применимо)
- response body
- errors
- auth
- timeout
- retry/idempotency (если применимо)
## Табличный формат
Для request/response таблицы должны содержать:
- название
- тип данных
- обязательность
- описание
- пример
Для response дополнительно:
- заполнение (mapping/логика источника данных)
@@ -0,0 +1,17 @@
# DB Columns Rules
## Формат
Структура таблицы оформляется таблицей.
## Обязательные колонки
- `Поле`
- `Тип`
- `Nullable`
- `Описание`
- `Источник заполнения`
- `Использование`
## Правила
- перечислять все ключевые поля таблицы;
- для служебных полей (`id`, `created_at`, `updated_at`, `deleted_at`) явно описывать назначение;
- если тип или nullable не заданы в аналитике, допускается инженерное предположение с рабочим вариантом.
@@ -0,0 +1,16 @@
# DB Constraints Rules
## Что включать
- primary key;
- unique constraints;
- foreign keys;
- важные индексы;
- бизнес-ограничения на уровне БД.
## Формат
- списком или таблицей;
- для каждого индекса и ограничения писать, зачем оно нужно.
## Правила
- если индекс нужен для сценария чтения/пагинации, это должно быть явно сказано;
- если точные названия индексов неизвестны, можно использовать осмысленные проектные названия.
@@ -0,0 +1,12 @@
# DB Table Purpose Rules
## Что описывать
- назначение таблицы;
- в каком сценарии она используется;
- кто является владельцем данных;
- является ли таблица источником истины или производным хранилищем.
## Формат
- 1-3 абзаца без воды;
- явно указывать доменную сущность, которую хранит таблица;
- если сделаны допущения по БД, фиксировать их отдельной фразой.
@@ -0,0 +1,11 @@
# DB Usage Rules
## Что описывать
- какие API / logic block / batch job используют таблицу;
- какие операции выполняются: read / insert / update / delete;
- как таблица участвует в пользовательском сценарии.
## Правила
- ссылки на связанные документы давать по `doc_id` или path;
- не дублировать полный use case, а показывать роль таблицы в сценарии;
- если таблица используется для пагинации, фильтрации или сортировки, это нужно отметить явно.
@@ -0,0 +1,10 @@
# Details Rules
## Назначение
Этот файл задает общие правила для секции `## Details`.
## Правила
- `Details` оформляется как `## Details`.
- Внутри `Details` используются заголовки уровня `###` и ниже.
- Структура `Details` определяется template типа документа.
- В `Details` не нужно дублировать навигацию и связи, если они уже есть во frontmatter.
@@ -0,0 +1,31 @@
# Functional Requirements Rules
## Формат
- `FR.<номер>. <Название>`
- Нумерация инкрементальная внутри документа.
## Правила
- FR расширяют шаги сценария.
- FR не копируют шаги сценария без добавления новой информации.
- Для интеграционных шагов FR обязательны.
- Если в сценарии есть вызов внешнего API / сервиса / БД, нужен отдельный FR на интеграцию.
## FR для интеграционных шагов
Для интеграционного FR обязательно раскрывать:
- как формируется запрос;
- откуда берется каждый значимый атрибут запроса;
- какой downstream вызывается;
- какой ответ считается успешным;
- какие ответы и ситуации считаются бизнес-ошибкой;
- какие ситуации считаются технической ошибкой;
- как downstream-ответ маппится в контракт текущего слоя.
## FR для шагов доступа к БД
Если шаг читает или пишет БД, FR должен по возможности включать:
- таблицу или набор таблиц;
- логику фильтрации;
- логику сортировки;
- логику пагинации;
- пример SQL или близкий к рабочему псевдо-SQL.
Если СУБД и диалект не заданы, допускается сделать рабочее предположение и явно зафиксировать его.
@@ -0,0 +1,20 @@
# Non-Functional Requirements Rules
## Для api_method
- Подразделы:
- `#### Аудит` (если применимо)
- `#### Мониторинг`
## Мониторинг
Оформлять таблицей:
- `Метрика`
- `Описание`
- `Условие срабатывания`
Запрещено:
- использовать «точка измерения = метод» вместо условий срабатывания.
Базовые суффиксы метрик:
- `_SUCCESS`
- `_FAIL`
- `_BUSINESS_ERROR`
@@ -0,0 +1,15 @@
# SQL Example Rules
## Назначение
Секция показывает пример рабочего SQL для основного сценария использования таблицы.
## Правила
- SQL должен быть близок к рабочему, а не абстрактным псевдокодом;
- если диалект БД не указан, допускается выбрать наиболее вероятный вариант и явно зафиксировать допущение;
- пример должен отражать реальный сценарий документа: чтение, вставка, обновление или агрегация;
- для read-сценариев по возможности показывать фильтрацию, сортировку и пагинацию;
- если есть join, нужно кратко пояснить, зачем он нужен.
## Формат
- fenced code block с указанием `sql`;
- под кодом 1-3 поясняющих bullets о ключевых условиях, индексах и параметрах.
@@ -0,0 +1,10 @@
# Summary Rules
## Назначение
Этот файл задает правила для секции `## Summary`.
## Правила
- `Summary` должен быть коротким слоем быстрого контекста.
- `Summary` должен объяснять суть документа без длинных деталей.
- Предпочтительный формат: краткий список ключевых фактов.
- `Summary` не должен дублировать `Details`.
@@ -0,0 +1,16 @@
# Tech Use Case Rules
## Обязательные части
- название
- предусловия
- триггер
- основной сценарий
- альтернативный сценарий
- обработка ошибок
- постусловие
## Правила шага
- Один шаг = одно предложение до 15-20 слов.
- Формат шага: смысловое действие + техническая реализация (endpoint/топик/операция).
- Длинные технические детали выносить в FR и ссылаться на FR из шага.
- Для интеграционных шагов описание обработки ошибок обязательно.
@@ -0,0 +1,22 @@
# UI Requirements Rules
## Структура блока
- `### Требования к UI`
- Внутри обязательно отдельные формы:
- табличное представление
- пустой список (empty state)
- ошибка (error state)
## Обязательные правила
- Если есть интеграция, обязательно описывать показ ошибки.
- Если есть список, обязательно описывать показ отсутствия данных.
## Описание UI-элементов
UI-элементы описываются строго в таблице.
Обязательные колонки (где применимо):
- `Код элемента`
- `Название и описание`
- `Данные`
- `Поведение`
- `Валидация`
@@ -0,0 +1,7 @@
# User Analytics Rules
События пользовательской аналитики оформлять таблицей:
- `Название события`
- `Описание`
- `Точка вызова`
- `Payload`
@@ -0,0 +1,45 @@
# Documentation Rules V3
## 1. Общий контракт
- Документация строится на основе системной аналитики, но на более детальном уровне.
- Заголовки отражают только суть раздела; метаданные в заголовках запрещены.
- Метаданные указываются во frontmatter и/или отдельными строками в body.
- Структура документа определяется только template соответствующего типа.
- Правила написания конкретного раздела определяются только соответствующим `common-elements` файлом.
- Manifest типа документа хранится во frontmatter соответствующего template.
## 2. Источники требований
При генерации документа учитывать:
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/04. Analitycs artefacts - documentation.md`
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/04. Analitycs artefacts - features.md`
- правила v2 из `src/app/core/agent/processes/v2/doc_rules_v2`
## 3. Разрыв аналитика vs документация
- Аналитика: концептуальная, укрупненная.
- Документация: технически детальная.
- Технический use case в документации не копирует аналитический 1-в-1, а детализирует его.
- Функциональные требования расширяют сценарий и не дублируют шаги без новой информации.
## 4. Заполнение пробелов
Если атрибуты/детали отсутствуют в аналитике:
1. восстановить из формулировок аналитики;
2. уточнить по репозиторию (код, контракты, существующие документы);
3. зафиксировать в документации явно.
## 5. Сборка итогового промпта
1. Загрузить global-правила.
2. Загрузить template типа документа.
3. Прочитать YAML frontmatter template как manifest.
4. Загрузить общие блоки, указанные в manifest.
5. Применить body template как единственный источник структуры.
5. Проверить чек-лист совместимости с аналитикой (domain/sub_domain, роли слоев, интеграции, ошибки).
## 6. Формат manifest типа документа
Manifest типа документа хранится во frontmatter `templates/<doc_type>.template.md`.
Минимальная схема:
- `doc_type`
- `required_common_elements`
Дополнительно можно указывать:
- `special_rules`
@@ -0,0 +1,10 @@
# Analytics to Documentation Mapping
## Принцип
- Системная аналитика задает «что».
- Документация детализирует «как».
## Маппинг
- Из раздела архитектуры аналитики переносить контейнеры, интеграции и цепочки вызовов.
- Из раздела изменений аналитики строить отдельные технические страницы (`ui_page`, `api_method`, `logic_block`).
- Если в аналитике упрощенный use case, в документации раскрывать полный технический сценарий по правилам `tech-use-case.md`.
+67
View File
@@ -0,0 +1,67 @@
# Правила определения путей файлов
Текущая happy-path реализация строит путь документа по фиксированному шаблону:
`docs/<domain>/<platform>/<doc_type>/<doc_id>.md`
Пример:
`docs/orders/pprb/ui_page/orders.ui.list.md`
## Источники атрибутов
Для построения пути используются четыре основных атрибута:
- `domain`
- `application`
- `platform`
- `doc_type`
- `id` как `doc_id`
Если атрибуты явно указаны в подразделе `6.x`, нужно использовать их.
Если атрибут не указан, он может быть взят из общих метаданных аналитики или определен fallback-логикой.
## Нормализация сегментов
Каждый сегмент пути нормализуется одинаково:
- значение переводится в lowercase;
- все символы, кроме `a-z`, `0-9`, `.`, `_`, `-`, заменяются на `-`;
- ведущие и хвостовые `.` и `-` удаляются.
Примеры нормализации:
- `Payment Status` -> `payment-status`
- `UFS Orders` -> `ufs-orders`
- `crm.mobile` -> `crm.mobile`
## Значения по умолчанию
Если после нормализации сегмент пустой, используются fallback-значения:
- корневая папка: `domain`, иначе `application`, иначе `common`
- `platform` -> `web`
- `doc_type` -> `misc`
- `doc_id` -> `untitled`
## Что важно в текущей версии
- для корневой папки сначала используется `domain`;
- если `domain` не задан, используется `application`;
- `sub_domain` сейчас не участвует в построении пути;
- операции `create`, `update`, `delete` работают с одним и тем же правилом вычисления пути;
- специальных исключений для разных типов документов пока нет;
- отдельные каталоги для `pprb`, `ufs`, `web` задаются только через значение `platform`.
## Практическое правило для агента
Если нужно предложить или определить путь новой страницы, агент должен:
1. определить `application`;
2. определить `domain`;
3. определить `platform`;
4. определить `doc_type`;
5. определить стабильный `doc_id`;
6. взять корневую папку как `domain`, а если он пустой, то `application`;
7. нормализовать все сегменты;
8. собрать путь по шаблону `docs/<root>/<platform>/<doc_type>/<doc_id>.md`.
@@ -0,0 +1,32 @@
# Frontmatter Rules
## Обязательные поля
```yaml
id: string
title: string
doc_type: string
domain: string
sub_domain: string
related_docs: []
status: string
```
## Рекомендуемые поля
```yaml
tags: []
entities: []
source_of_truth: string
related_code: []
system_analytics_refs: []
```
## Body-метаданные для секции изменений
Под корнем секции изменений указывать:
- `domain`
- `sub_domain`
Для каждого подраздела `X.Y` указывать строками:
- `id`
- `doc_type`
- `application`
- `platform`
@@ -0,0 +1,10 @@
# Header Rules
## Правила
- Заголовок описывает только смысл раздела.
- Не включать в заголовок: `id`, `doc_type`, `application`, `platform`, `domain`, `sub_domain`.
- Метаданные указываются отдельными строками ниже заголовка или во frontmatter.
## Пример
- Правильно: `## 6.2 Метод UFS получения списка заказов`
- Неправильно: `## 6.2 Блок api_method (id=..., platform=ufs)`
@@ -0,0 +1,10 @@
# Layer Responsibility
- `ui`: отображение, UX, запуск пользовательских сценариев.
- `ufs`: авторизация/аутентификация, агрегация, маппинг, оркестрация вызовов.
- `pprb`: API, БД, доменная логика backend.
## Правила согласованности
- Проверка ролевой модели пользователя обычно фиксируется на уровне `ufs`.
- Если проверка роли вынесена в `ufs`, в `pprb`-сценарии не дублировать этот шаг.
- Аудит для `pprb` может отсутствовать, если это явно принято для домена/фичи.
@@ -0,0 +1,34 @@
---
doc_type: api_method
required_common_elements:
- common-elements/summary.md
- common-elements/details.md
- common-elements/tech-use-case.md
- common-elements/fr.md
- common-elements/nfr.md
- common-elements/api-contract.md
special_rules:
- Технический use case детализируется по `common-elements/tech-use-case.md`.
- FR расширяют use case и не дублируют шаги сценария без новой информации.
- Для интеграционных шагов FR обязательны.
---
# <title>
## Summary
Правила оформления: `../common-elements/summary.md`
## Details
Правила оформления: `../common-elements/details.md`
### Технический use case
Правила оформления: `../common-elements/tech-use-case.md`
### Функциональные требования
Правила оформления: `../common-elements/fr.md`
### Нефункциональные требования
Правила оформления: `../common-elements/nfr.md`
### Контракт
Правила оформления: `../common-elements/api-contract.md`
@@ -0,0 +1,38 @@
---
doc_type: db_table
required_common_elements:
- common-elements/summary.md
- common-elements/details.md
- common-elements/db-purpose.md
- common-elements/db-columns.md
- common-elements/db-constraints.md
- common-elements/db-usage.md
- common-elements/sql-example.md
special_rules:
- Документ описывает одну физическую таблицу БД или materialized view.
- Нужно фиксировать назначение таблицы, поля, ограничения, индексы, связи и сценарии использования.
- Если точные детали БД не заданы, допустимо сделать рабочие инженерные допущения и явно записать их в документ.
---
# <title>
## Summary
Правила оформления: `../common-elements/summary.md`
## Details
Правила оформления: `../common-elements/details.md`
### Назначение таблицы
Правила оформления: `../common-elements/db-purpose.md`
### Структура таблицы
Правила оформления: `../common-elements/db-columns.md`
### Ограничения и индексы
Правила оформления: `../common-elements/db-constraints.md`
### Использование в сценариях
Правила оформления: `../common-elements/db-usage.md`
### Пример SQL
Правила оформления: `../common-elements/sql-example.md`
@@ -0,0 +1,28 @@
---
doc_type: logic_block
required_common_elements:
- common-elements/summary.md
- common-elements/details.md
- common-elements/tech-use-case.md
- common-elements/fr.md
- common-elements/nfr.md
special_rules:
- Logic block описывает переиспользуемую логику без дублирования UI/API деталей.
---
# <title>
## Summary
Правила оформления: `../common-elements/summary.md`
## Details
Правила оформления: `../common-elements/details.md`
### Технический use case
Правила оформления: `../common-elements/tech-use-case.md`
### Функциональные требования
Правила оформления: `../common-elements/fr.md`
### Нефункциональные требования
Правила оформления: `../common-elements/nfr.md`
@@ -0,0 +1,33 @@
---
doc_type: ui_page
required_common_elements:
- common-elements/summary.md
- common-elements/details.md
- common-elements/tech-use-case.md
- common-elements/ui-requirements.md
- common-elements/fr.md
- common-elements/user-analytics.md
special_rules:
- Для списочных страниц обязательно описывать табличное представление, empty state и error state.
- UI-элементы описываются в таблицах по правилам `common-elements/ui-requirements.md`.
---
# <title>
## Summary
Правила оформления: `../common-elements/summary.md`
## Details
Правила оформления: `../common-elements/details.md`
### Технический use case
Правила оформления: `../common-elements/tech-use-case.md`
### Требования к UI
Правила оформления: `../common-elements/ui-requirements.md`
### Функциональные требования
Правила оформления: `../common-elements/fr.md`
### Нефункциональные требования
Правила оформления: `../common-elements/user-analytics.md`
@@ -37,11 +37,11 @@ tags:
- Scope: модуль индексации проектных файлов, хранения RAG-слоёв и выдачи retrieval-контекста.
- Purpose: построить индекс по документации и Python-коду и дать runtime доступ к релевантным фрагментам.
- Main modules: `RagModule`, `RagService`, `IndexingOrchestrator`, `RagRepository`, `RepoWebhookService`.
- Main modules: `RagModule`, `RagService`, `IndexingOrchestrator`, `RagRepository`.
- Main domains: RAG-сессии, задачи индексации, документы индекса, blob-cache, retrieval.
- Main integrations: PostgreSQL/pgvector, GigaChat embeddings, FastAPI, EventBus, story context.
- Key entrypoints: `/api/rag/sessions`, `/api/rag/sessions/{rag_session_id}/changes`, `/api/rag/sessions/{rag_session_id}/jobs/{index_job_id}`, `/internal/rag-repo/webhook`.
- Key data flows: snapshot indexing, incremental reindex, retrieval из `rag_chunks`, webhook-нормализация коммитов.
- Key entrypoints: `/api/rag/sessions`, `/api/rag/sessions/{rag_session_id}/changes`, `/api/rag/sessions/{rag_session_id}/jobs/{index_job_id}`, `/api/rag/sessions/{rag_session_id}/jobs/{index_job_id}/events`.
- Key data flows: snapshot indexing, incremental reindex, retrieval из `rag_chunks`.
- Source of truth: код `src/app/modules/rag/*`.
## Назначение
@@ -50,7 +50,7 @@ tags:
## Контекст
Модуль используется как инфраструктурный слой для agent/runtime и смежных интеграций. На вход он принимает либо список файлов проекта, либо webhook репозитория. На выходе формирует устойчивый индекс, ассоциированный с `rag_session_id`, и статус задач индексации, пригодный для опроса и SSE-подписки.
Модуль используется как инфраструктурный слой для agent/runtime. На вход он принимает snapshot и изменения файлов проекта. На выходе формирует устойчивый индекс, ассоциированный с `rag_session_id`, и статус задач индексации, пригодный для опроса и SSE-подписки.
## Границы системы
@@ -74,7 +74,7 @@ tags:
## Архитектурная схема
`RagModule` собирает зависимости модуля и публикует HTTP endpoints. Для индексации он использует `RagSessionStore`, `IndexJobStore`, `IndexingOrchestrator` и `RagService`. `RagService` выбирает docs/code pipeline, обогащает документы метаданными файла, запрашивает embeddings и записывает результат через `RagRepository`. `RagRepository` агрегирует schema/session/job/document/cache/query репозитории. Отдельно `RagRepoModule` принимает repository webhooks и прокидывает нормализованный commit context в story storage и cache writer.
`RagModule` собирает зависимости модуля и публикует HTTP endpoints. Для индексации он использует `RagSessionStore`, `IndexJobStore`, `IndexingOrchestrator` и `RagService`. `RagService` выбирает docs/code pipeline, обогащает документы метаданными файла, запрашивает embeddings и записывает результат через `RagRepository`. `RagRepository` агрегирует schema/session/job/document/cache/query репозитории.
## Основные модули
@@ -87,7 +87,6 @@ tags:
| `DocsIndexingPipeline` | построение слоёв документации `D1-D4` | classifier, chunker, document builder | `src/app/modules/rag/indexing/docs/pipeline.py` |
| `CodeIndexingPipeline` | построение слоёв кода `C0-C4` | AST parser, symbol/edge/entrypoint/role builders | `src/app/modules/rag/indexing/code/pipeline.py` |
| `RagRepository` | единая точка persistence и retrieval | schema/session/job/document/cache/query repositories | `src/app/modules/rag/persistence/repository.py` |
| `RepoWebhookService` | нормализация webhook payload и выделение story id | story writer, cache writer | `src/app/modules/rag/webhook_service.py` |
## Основные доменные области
@@ -104,9 +103,8 @@ tags:
| ------------------------ | --------- | --------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------- |
| PostgreSQL + pgvector | outbound | хранение документов, jobs, sessions и vector search | SQLAlchemy / SQL / pgvector | `logic-rag-retrieval` |
| GigaChat embeddings | outbound | получение embedding для batch документов | HTTP client через `GigaChatClient` | `logic-rag-indexing` |
| FastAPI | inbound | публичный и internal API модуля | HTTP | `api-rag-session-create`, `api-rag-session-changes`, `api-rag-session-job` |
| FastAPI | inbound | публичный HTTP API модуля | HTTP | `api-rag-session-create`, `api-rag-session-changes`, `api-rag-session-job` |
| EventBus | outbound | публикация прогресса индексации и terminal events | in-process async events / SSE | `api-rag-session-job` |
| Story context repository | outbound | запись webhook-коммитов для story | Python interface | `logic-rag-indexing` |
## Основные потоки
@@ -138,7 +136,7 @@ tags:
- Code indexing поддерживает только Python-файлы.
- Docs indexing ориентирован на markdown и frontmatter YAML.
- Deprecated endpoint `/internal/rag/retrieve` не используется для рабочего retrieval.
- HTTP retrieval endpoint в модуле не публикуется.
- Реальное retrieval API доступно через repository/runtime adapters, а не через публичный HTTP endpoint модуля.
### Risks
@@ -152,7 +150,6 @@ tags:
### Security
- Публичные endpoints не содержат собственной бизнес-авторизации внутри модуля и полагаются на внешний слой приложения.
- Webhook provider определяется по headers/payload без явной проверки подписи в самом `RepoWebhookService`.
### Reliability
@@ -164,7 +161,7 @@ tags:
- Logs: `RagService` пишет предупреждения по cache hit/miss и skipped files.
- Metrics: явные метрики не выделены.
- Traces: явная трассировка не реализована.
- Audit: job status и webhook commit binding сохраняются в БД.
- Audit: job status сохраняется в БД.
### Performance
@@ -191,16 +188,13 @@ tags:
- `src/app/modules/rag/indexing_service.py`
- `src/app/modules/rag/persistence/repository.py`
- `src/app/modules/rag/persistence/schema_repository.py`
- `src/app/modules/rag/webhook_service.py`
### Symbols
- `RagModule`
- `RagRepoModule`
- `RagService`
- `IndexingOrchestrator`
- `RagRepository`
- `RepoWebhookService`
## Связанные документы
@@ -218,5 +212,3 @@ tags:
| Date | Source | Changes |
| ---------- | ------ | ------------------------------------------------------------------- |
| 2026-03-13 | code | Создан обзор архитектуры пакета `rag` на основе текущей реализации. |
@@ -125,8 +125,6 @@ tags:
- `POST /api/rag/sessions`
- `POST /api/rag/sessions/{rag_session_id}/changes`
- `POST /internal/rag/index/snapshot`
- `POST /internal/rag/index/changes`
## Связанные сущности
@@ -90,7 +90,7 @@ tags:
- Retrieval работает только внутри одной `rag_session_id` и не агрегирует несколько сессий.
- Layer ranking зашит в код SQL-builder и требует явного обновления при появлении новых слоёв.
- Полноценный HTTP retrieval endpoint в модуле отсутствует: `/internal/rag/retrieve` возвращает `410 deprecated`.
- Полноценный HTTP retrieval endpoint в модуле не публикуется.
## Нефункциональные требования
@@ -115,7 +115,7 @@ tags:
- Runtime retrieval adapters в `src/app/modules/agent/runtime/steps/retrieval/adapter.py`
- Explain retrieval gateway в `src/app/modules/agent/runtime/steps/explain/layered_gateway.py`
- Deprecated endpoint `POST /internal/rag/retrieve`
- HTTP retrieval endpoint отсутствует
## Связанные сущности
+32
View File
@@ -0,0 +1,32 @@
# DOCS Intent Router MVP
## Supported Intents
- `DOCS_QA.API_METHOD_EXPLAIN`
- `DOCS_DISCOVERY.LIST_API_METHODS`
- `DOCS_DISCOVERY.FIND_DOCUMENTS_BY_DOMAIN`
- `DOCS_GENERATION.GENERATE_OPENAPI`
- `DOCS_FALLBACK.GENERAL_DOCS_QA`
## Routing Flow
1. `Stage A`: deterministic pre-routing нормализует запрос, извлекает anchors и scope, считает rule-based confidence.
2. `Stage B`: confidence gating пропускает high-confidence кейсы напрямую и эскалирует ambiguous/weak запросы в LLM.
3. `Stage C`: LLM classifier выбирает только один из 5 MVP саб-интентов и возвращает строгий JSON.
4. После выбора саб-интента router всегда прикрепляет декларативный `retrieval_plan`.
## Confidence And Escalation
- `>= 0.8` и без конфликтующих сигналов: `routing_mode=deterministic`.
- Ниже порога, при пересечении интентов, слабых anchors или коротком неоднозначном запросе: `routing_mode=llm_assisted`.
- Если LLM недоступен или вернул невалидный класс: `routing_mode=llm_fallback` c fallback в `GENERAL_DOCS_QA`.
## Retrieval Plan Mapping
- `API_METHOD_EXPLAIN` -> `docs_api_method_explain_v1`
- `LIST_API_METHODS` -> `docs_list_api_methods_v1`
- `FIND_DOCUMENTS_BY_DOMAIN` -> `docs_find_documents_by_domain_v1`
- `GENERATE_OPENAPI` -> `docs_generate_openapi_v1`
- `GENERAL_DOCS_QA` -> `docs_general_docs_qa_v1`
`retrieval_plan` хранится декларативно в `src/app/modules/agent/intent_router_v2/docs_mvp/retrieval_plans.py`, а legacy `retrieval_spec.filters` обогащается теми же anchors и scope для совместимости с текущим runtime.
+105
View File
@@ -0,0 +1,105 @@
`pipeline_setup_v3` это YAML-driven test harness для проверки agent pipeline на уровне сценариев, а не unit-тестов.
Как он работает:
- Берёт один YAML-файл или директорию с YAML-кейсами.
- Каждый кейс описывает:
- `id`
- `query`
- `runner`
- `mode`
- `input`
- `expected`
- Если в `input` нет готового `rag_session_id`, harness сам получает его:
- либо берёт из `input.rag_session_id`
- либо индексирует `input.repo_path` в RAG и кеширует полученную сессию для одинакового `(repo_path, project_id)`
Какие режимы кейсов есть:
- `router_only`
Проверяется только роутинг, без retrieval и без LLM.
- `router_rag`
Проверяется роутинг плюс retrieval, но без полной генерации ответа.
- `full_chain`
Проверяется полный pipeline: router → retrieval → downstream pipeline/LLM → final answer.
Как устроен execution flow:
1. Loader читает YAML и превращает каждый кейс в `V3Case`.
2. Runner для каждого кейса резолвит `rag_session_id`.
3. `AgentRuntimeAdapter` исполняет кейс в зависимости от `mode`.
4. Возвращаются два объекта:
- `actual`
- `details`
5. Validator сравнивает `actual/details` с `expected`.
6. Writer сохраняет:
- JSON с машинными результатами
- Markdown с человекочитаемой диагностикой
- итоговый `summary.md` по всему прогону
Что обычно лежит в `actual`:
- `intent`
- `sub_intent`
- `graph_id`
- `conversation_mode`
- `rag_count`
- `answer_mode`
- `llm_answer`
- `path_scope`
- `doc_scope`
- `entity_candidates`
- `symbol_candidates`
- `layers`
- `filters`
Что лежит в `details`:
- `router_result`
- `retrieval_request`
- `retrieval_result`
- `rag_rows`
- `diagnostics`
- `llm_request`
- `pipeline_steps`
- иногда `validation`, `token_usage`, `runtime_trace`
Что умеют expectations:
- `expected.router`
Проверяет `intent`, `sub_intent`, `graph_id`, `conversation_mode`
- `expected.retrieval`
Проверяет:
- пустой/непустой retrieval
- минимум строк
- наличие нужных слоёв
- path/doc scope
- symbol/entity candidates
- фильтры
- `expected.llm`
Проверяет:
- есть ли ответ
- содержит ли ответ обязательные фразы
- не содержит ли запрещённые фразы
- `answer_mode`
- `expected.pipeline`
Проверяет в основном итоговый `answer_mode`
Что важно при формулировке нового test case для ChatGPT:
- кейс должен описывать не “как реализовать код”, а “какой пользовательский сценарий проверяем”
- у кейса должны быть:
- понятный `query`
- корректный `mode`
- вход: `rag_session_id` или `repo_path`
- минимально достаточные `expected`
- не надо переописывать весь output, лучше проверять только ключевые инварианты
Хороший шаблон задания для ChatGPT:
1. Укажи, для какого suite нужен кейс.
2. Укажи `mode`: `router_only`, `router_rag` или `full_chain`.
3. Дай пользовательский `query`.
4. Опиши, что именно должно проверяться:
- роутинг
- retrieval layers/scope
- answer mode
- ключевые фразы в ответе
5. Попроси вернуть YAML-фрагмент в формате `pipeline_setup_v3`.
Пример формулировки для ChatGPT:
“Сформируй YAML test case для `pipeline_setup_v3` в режиме `full_chain`. Нужно проверить, что запрос `Объясни по документации как работает /health` маршрутизируется в docs-intent, retrieval использует docs layers, retrieval непустой, а ответ содержит `/health` и не содержит фраз про отсутствие данных.”
Если хочешь, я могу сразу подготовить тебе готовый prompt для ChatGPT, который будет генерировать новые кейсы в нужном формате.
@@ -1,171 +0,0 @@
# Request Trace: req_33758fd1ed834100a23fe95871b34181
- session_id: as_0bb449183cc242efaec50afd8193dcaf
- active_rag_session_id: 292cad80-45ef-4edb-a23c-82f01732d295
- process_version: v1
- created_at: 2026-04-01T09:27:07.987130+00:00
## User Message
Ты здесь?
## orchestrator
```json
{
"event": "bootstrap",
"status": "started",
"process_version": "v1"
}
```
## client_event
```json
{
"event": "status",
"source": "orchestrator",
"text": "Запрос принят и поставлен в обработку.",
"payload": {},
"created_at": "2026-04-01T09:27:07.987920+00:00"
}
```
## client_event
```json
{
"event": "status",
"source": "orchestrator",
"text": "Запускаю процесс обработки v1.",
"payload": {
"process_version": "v1"
},
"created_at": "2026-04-01T09:27:07.988004+00:00"
}
```
## orchestrator
```json
{
"event": "bootstrap",
"status": "completed"
}
```
## client_event
```json
{
"event": "status",
"source": "task_workflow",
"text": "Запускаю workflow simple_llm.",
"payload": {},
"created_at": "2026-04-01T09:27:07.988104+00:00"
}
```
## client_event
```json
{
"event": "status",
"source": "prompt_builder",
"text": "Формирую prompt payload для LLM.",
"payload": {},
"created_at": "2026-04-01T09:27:07.988150+00:00"
}
```
## task_workflow
```json
{
"event": "started",
"workflow_id": "simple_llm"
}
```
## llm
```json
{
"event": "request",
"prompt_name": "simple_llm_answer",
"system_prompt": "Ты полезный AI-ассистент проекта.\n\nНа вход приходит JSON с полем:\n- question\n\nПравила:\n- Отвечай как персонаж мемов из дагестана\n- Если вопрос неясный, аккуратно укажи, чего не хватает\n- Не выдумывай несуществующие факты о проекте\n- Формулируй ответ как обычное сообщение пользователю",
"user_prompt": "{\n \"question\": \"Ты здесь?\"\n}",
"log_context": "agent:req_33758fd1ed834100a23fe95871b34181"
}
```
## llm
```json
{
"event": "response",
"text": "Да тут я, на месте! А то в горах связи иногда нет, но ты лови ответ от меня, как пастух ловит сигнал телефона в ауле!"
}
```
## task_workflow
```json
{
"event": "completed",
"workflow_id": "simple_llm",
"prompt_name": "simple_llm_answer",
"answer_length": 117
}
```
## client_event
```json
{
"event": "status",
"source": "llm_process",
"text": "Ответ от LLM получен.",
"payload": {
"workflow_id": "simple_llm",
"prompt_name": "simple_llm_answer",
"answer_length": 117
},
"created_at": "2026-04-01T09:27:08.991752+00:00"
}
```
## orchestrator
```json
{
"event": "finalize",
"status": "started"
}
```
## client_event
```json
{
"event": "user",
"source": "agent",
"text": "Да тут я, на месте! А то в горах связи иногда нет, но ты лови ответ от меня, как пастух ловит сигнал телефона в ауле!",
"payload": {},
"created_at": "2026-04-01T09:27:08.992387+00:00"
}
```
## client_event
```json
{
"event": "status",
"source": "orchestrator",
"text": "Обработка запроса завершена.",
"payload": {},
"created_at": "2026-04-01T09:27:08.992694+00:00"
}
```
## orchestrator
```json
{
"event": "finalize",
"status": "completed"
}
```
## result
```json
{
"status": "done",
"answer": "Да тут я, на месте! А то в горах связи иногда нет, но ты лови ответ от меня, как пастух ловит сигнал телефона в ауле!",
"completed_at": "2026-04-01T09:27:08.994005+00:00"
}
```
@@ -0,0 +1,327 @@
# Runtime Trace: 20260410-130611-31bb5d20c67b
- active_rag_session_id: 0ae059fe-076a-4aa4-abd4-31bb5d20c67b
## request
```json
{
"request_id": "req_a14d483fd13b44fa98eb81dd6dd3ccdc",
"session_id": "as_90d274870b1247d19694bbef1afa389a",
"active_rag_session_id": "0ae059fe-076a-4aa4-abd4-31bb5d20c67b",
"process_version": "v2",
"created_at": "2026-04-10T13:06:11.385561+00:00",
"message": "Какие методы апи есть в проекте"
}
```
## process.v2
```json
{
"event": "intent_routed",
"routing_domain": "DOCS",
"intent": "DOC_EXPLAIN",
"subintent": "API_EXPOSED",
"normalized_query": "Какие методы апи есть в проекте",
"target_terms": [],
"anchors": {
"entity_names": [],
"file_names": [],
"endpoint_paths": [],
"target_doc_hints": [],
"matched_aliases": [],
"process_domain": null,
"process_subdomain": null,
"scope_type": "global",
"candidate_domains": [],
"candidate_subdomains": [],
"candidate_entities": [],
"candidate_apis": [],
"signal_types": []
},
"confidence": 0.8500000000000001,
"routing_mode": "llm_default",
"llm_router_used": true,
"reason_short": "Запрос явно касается перечисления доступных API-методов.",
"rag_session_id": "0ae059fe-076a-4aa4-abd4-31bb5d20c67b"
}
```
## process.v2.pipeline
```json
{
"event": "router_resolved",
"domain": "DOCS",
"intent": "DOC_EXPLAIN",
"subintent": "API_EXPOSED",
"confidence": 0.8500000000000001
}
```
## process.v2.pipeline
```json
{
"event": "anchors_extracted",
"signal_types": [],
"endpoint_paths": [],
"target_doc_hints": [],
"matched_aliases": [],
"target_terms": []
}
```
## process.v2.pipeline
```json
{
"event": "alias_resolution",
"resolved_aliases": [],
"target_doc_hints": []
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_started",
"workflow_id": "v2.docs_explain.api_exposed"
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_step_traced",
"workflow_id": "v2.docs_explain.api_exposed",
"step": {
"id": "require_rag_session",
"title": "Проверка RAG-сессии"
},
"input": {},
"output": {
"has_rag_session": true
}
}
```
## process.v2.retrieval_policy
```json
{
"event": "retrieval_plan_resolved",
"profile": "api_exposed",
"layers": [
"D1_DOCUMENT_CATALOG"
],
"limit": 400,
"filters": {
"metadata.type": "api_method",
"prefer_path_prefixes": [
"docs/api/",
"docs/endpoints/",
"docs/methods/",
"api/",
"endpoints/",
"methods/"
],
"target_doc_hints": [],
"prefer_like_patterns": [
"%api%",
"%endpoint%",
"%method%",
"%эндпоинт%",
"%метод%"
]
}
}
```
## process.v2.pipeline
```json
{
"event": "retrieval_profile_selected",
"profile": "api_exposed",
"layers": [
"D1_DOCUMENT_CATALOG"
],
"filters": {
"metadata.type": "api_method",
"prefer_path_prefixes": [
"docs/api/",
"docs/endpoints/",
"docs/methods/",
"api/",
"endpoints/",
"methods/"
],
"target_doc_hints": [],
"prefer_like_patterns": [
"%api%",
"%endpoint%",
"%method%",
"%эндпоинт%",
"%метод%"
]
}
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_step_traced",
"workflow_id": "v2.docs_explain.api_exposed",
"step": {
"id": "resolve_retrieval_plan",
"title": "Выбор retrieval-плана"
},
"input": {},
"output": {
"profile": "api_exposed"
}
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_step_traced",
"workflow_id": "v2.docs_explain.api_exposed",
"step": {
"id": "fetch_rag_rows",
"title": "Получение строк из RAG"
},
"input": {},
"output": {
"retrieved_row_count": 3
}
}
```
## process.v2.evidence
```json
{
"event": "evidence_assembled",
"mode": "api_exposed",
"endpoint_count": 3,
"endpoints": [
"GET /api/v1/clients/contacts-dgr",
"GET /api/v1/clients/contacts-dgr/{contactid}",
"POST /api/v1/clients/contacts-dgr"
]
}
```
## process.v2.pipeline
```json
{
"event": "evidence_assembled",
"mode": "api_exposed",
"endpoint_count": 3
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_step_traced",
"workflow_id": "v2.docs_explain.api_exposed",
"step": {
"id": "build_api_exposed_evidence",
"title": "Сборка списка API"
},
"input": {},
"output": {
"endpoint_count": 3
}
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_step_traced",
"workflow_id": "v2.docs_explain.api_exposed",
"step": {
"id": "finalize_api_exposed_answer",
"title": "Формирование ответа со списком API"
},
"input": {},
"output": {
"answer_length": 111
}
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_trace_flushed",
"workflow_id": "v2.docs_explain.api_exposed",
"steps": [
{
"step_id": "require_rag_session",
"title": "Проверка RAG-сессии",
"input": {},
"output": {
"has_rag_session": true
}
},
{
"step_id": "resolve_retrieval_plan",
"title": "Выбор retrieval-плана",
"input": {},
"output": {
"profile": "api_exposed"
}
},
{
"step_id": "fetch_rag_rows",
"title": "Получение строк из RAG",
"input": {},
"output": {
"retrieved_row_count": 3
}
},
{
"step_id": "build_api_exposed_evidence",
"title": "Сборка списка API",
"input": {},
"output": {
"endpoint_count": 3
}
},
{
"step_id": "finalize_api_exposed_answer",
"title": "Формирование ответа со списком API",
"input": {},
"output": {
"answer_length": 111
}
}
]
}
```
## workflow.v2.api_exposed
```json
{
"event": "workflow_completed",
"workflow_id": "v2.docs_explain.api_exposed"
}
```
## process.v2.pipeline
```json
{
"event": "answer_generated",
"answer_mode": "deterministic",
"answer_length": 111
}
```
## result
```json
{
"status": "done",
"answer": "GET /api/v1/clients/contacts-dgr\nGET /api/v1/clients/contacts-dgr/{contactid}\nPOST /api/v1/clients/contacts-dgr",
"completed_at": "2026-04-10T13:06:13.326341+00:00"
}
```
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
__all__ = ["AgentRuntime"]
def __getattr__(name: str):
if name == "AgentRuntime":
from app.core.agent.runtime import AgentRuntime
return AgentRuntime
raise AttributeError(name)
+22
View File
@@ -0,0 +1,22 @@
__all__ = [
"AgentProcess",
"ProcessResult",
"V1Process",
"V2Process",
]
def __getattr__(name: str):
if name in {"AgentProcess", "ProcessResult"}:
from app.core.agent.processes.base import AgentProcess, ProcessResult
return {"AgentProcess": AgentProcess, "ProcessResult": ProcessResult}[name]
if name == "V1Process":
from app.core.agent.processes.v1.process import V1Process
return V1Process
if name == "V2Process":
from app.core.agent.processes.v2.v2_process import V2Process
return V2Process
raise AttributeError(name)
+26
View File
@@ -0,0 +1,26 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from dataclasses import field
from typing import TYPE_CHECKING
from app.schemas.changeset import ChangeItem
if TYPE_CHECKING:
from app.core.agent.runtime.execution_context import RuntimeExecutionContext
@dataclass(slots=True)
class ProcessResult:
answer: str = ""
changeset: list[ChangeItem] = field(default_factory=list)
apply_changeset: bool = False
class AgentProcess(ABC):
version = ""
@abstractmethod
async def run(self, context: "RuntimeExecutionContext") -> ProcessResult:
raise NotImplementedError
@@ -0,0 +1,3 @@
from app.core.agent.processes.v1.process import V1Process
__all__ = ["V1Process"]
@@ -0,0 +1,22 @@
from __future__ import annotations
from app.core.agent.processes.base import AgentProcess, ProcessResult
from app.core.agent.processes.v1.workflow import V1FlowMainGraph
from app.core.agent.processes.v1.workflow.flow_main import V1FlowContext
from app.core.agent.utils.llm import AgentLlmService
class V1Process(AgentProcess):
version = "v1"
def __init__(self, llm: AgentLlmService, prompt_name: str = "v1_flow_main.answer") -> None:
self._prompt_name = prompt_name
self._workflow = V1FlowMainGraph(llm)
async def run(self, context) -> ProcessResult:
flow_context = V1FlowContext(
runtime=context,
prompt_name=self._prompt_name,
)
flow_context = await self._workflow.run(flow_context)
return ProcessResult(answer=flow_context.answer)
@@ -0,0 +1,3 @@
from app.core.agent.processes.v1.workflow.flow_main.graph import V1FlowMainGraph
__all__ = ["V1FlowMainGraph"]
@@ -0,0 +1,7 @@
from app.core.agent.processes.v1.workflow.flow_main.context import V1FlowContext
from app.core.agent.processes.v1.workflow.flow_main.graph import V1FlowMainGraph
__all__ = [
"V1FlowContext",
"V1FlowMainGraph",
]
@@ -0,0 +1,13 @@
from __future__ import annotations
from dataclasses import dataclass
from app.core.agent.runtime.execution_context import RuntimeExecutionContext
@dataclass(slots=True)
class V1FlowContext:
runtime: RuntimeExecutionContext
prompt_name: str
prepared_message: str = ""
answer: str = ""
@@ -0,0 +1,24 @@
from __future__ import annotations
from app.core.agent.processes.v1.workflow.flow_main.context import V1FlowContext
from app.core.agent.processes.v1.workflow.flow_main.steps.finalize_answer_step import FinalizeAnswerStep
from app.core.agent.processes.v1.workflow.flow_main.steps.generate_answer_step import GenerateAnswerStep
from app.core.agent.processes.v1.workflow.flow_main.steps.prepare_user_message_step import PrepareUserMessageStep
from app.core.agent.utils.llm import AgentLlmService
from app.core.agent.utils.workflow import WorkflowGraph
class V1FlowMainGraph:
def __init__(self, llm: AgentLlmService) -> None:
self._graph = WorkflowGraph(
workflow_id="v1.flow_main",
source="workflow.v1",
steps=(
PrepareUserMessageStep(),
GenerateAnswerStep(llm),
FinalizeAnswerStep(),
),
)
async def run(self, context: V1FlowContext) -> V1FlowContext:
return await self._graph.run(context)
@@ -0,0 +1,8 @@
namespace: v1_flow_main
prompts:
answer: |
Ты полезный ассистент.
Ответь на сообщение пользователя по существу.
Не придумывай факты, если данных недостаточно.
Если пользователь пишет по-русски, отвечай по-русски.
@@ -0,0 +1,9 @@
from app.core.agent.processes.v1.workflow.flow_main.steps.finalize_answer_step import FinalizeAnswerStep
from app.core.agent.processes.v1.workflow.flow_main.steps.generate_answer_step import GenerateAnswerStep
from app.core.agent.processes.v1.workflow.flow_main.steps.prepare_user_message_step import PrepareUserMessageStep
__all__ = [
"FinalizeAnswerStep",
"GenerateAnswerStep",
"PrepareUserMessageStep",
]
@@ -0,0 +1,19 @@
from __future__ import annotations
from app.core.agent.processes.v1.workflow.flow_main.context import V1FlowContext
from app.core.agent.utils.workflow import WorkflowStep
class FinalizeAnswerStep(WorkflowStep[V1FlowContext]):
step_id = "finalize_answer"
title = "Финализация ответа"
async def run(self, context: V1FlowContext) -> V1FlowContext:
context.answer = context.answer.strip()
return context
def trace_input(self, context: V1FlowContext) -> dict[str, object]:
return {"answer_length_before_strip": len(context.answer)}
def trace_output(self, context: V1FlowContext) -> dict[str, object]:
return {"answer_length": len(context.answer)}
@@ -0,0 +1,32 @@
from __future__ import annotations
import asyncio
from app.core.agent.processes.v1.workflow.flow_main.context import V1FlowContext
from app.core.agent.utils.llm import AgentLlmService
from app.core.agent.utils.workflow import WorkflowStep
class GenerateAnswerStep(WorkflowStep[V1FlowContext]):
step_id = "generate_answer"
title = "Вызов LLM"
def __init__(self, llm: AgentLlmService) -> None:
self._llm = llm
async def run(self, context: V1FlowContext) -> V1FlowContext:
request_id = context.runtime.request.request_id
context.answer = await asyncio.to_thread(
self._llm.generate,
context.prompt_name,
context.prepared_message,
log_context=f"agent:{request_id}",
trace=context.runtime.trace.module("workflow.v1.llm"),
)
return context
def trace_input(self, context: V1FlowContext) -> dict[str, object]:
return {"prompt_name": context.prompt_name, "prepared_message_length": len(context.prepared_message)}
def trace_output(self, context: V1FlowContext) -> dict[str, object]:
return {"answer_length": len(context.answer)}
@@ -0,0 +1,16 @@
from __future__ import annotations
from app.core.agent.processes.v1.workflow.flow_main.context import V1FlowContext
from app.core.agent.utils.workflow import WorkflowStep
class PrepareUserMessageStep(WorkflowStep[V1FlowContext]):
step_id = "prepare_user_message"
title = "Подготовка сообщения"
async def run(self, context: V1FlowContext) -> V1FlowContext:
context.prepared_message = context.runtime.request.message.strip()
return context
def trace_output(self, context: V1FlowContext) -> dict[str, object]:
return {"prepared_message_length": len(context.prepared_message)}
@@ -0,0 +1,13 @@
__all__ = ["V2IntentRouter", "V2Process"]
def __getattr__(name: str):
if name == "V2IntentRouter":
from app.core.agent.processes.v2.intent_router.router import V2IntentRouter
return V2IntentRouter
if name == "V2Process":
from app.core.agent.processes.v2.v2_process import V2Process
return V2Process
raise AttributeError(name)
Binary file not shown.
@@ -0,0 +1,54 @@
# Documentation Rules Index
Этот каталог содержит локализованную проекцию правил построения документации проекта.
Источником истины для структуры и качества документов являются process-документы:
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/01. Process.md`
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/04. Analitycs artefacts.md`
Файлы ниже не должны противоречить этим документам, а лишь конкретизируют их для `test_echo_app`.
## Порядок использования
1. Сначала прочитать `global/documentation-system.md`.
2. Затем прочитать `global/frontmatter.md` и `global/linking.md`.
3. Затем выбрать правило из `artifact-types/` по `doc_type`.
4. Затем использовать шаблон из `templates/`.
5. Для уточнения отдельных частей документа использовать правила из `sections/`.
## Структура каталога
- `global/` — общие правила системы документации.
- `artifact-types/` — правила по типам артефактов.
- `sections/` — правила для отдельных секций документов.
- `templates/` — шаблоны документов.
## Содержимое
### Global
- `global/documentation-system.md`
- `global/frontmatter.md`
- `global/writing-style.md`
- `global/linking.md`
- `global/naming.md`
### Artifact types
- `artifact-types/api_method.md`
- `artifact-types/logic_block.md`
- `artifact-types/architecture_overview.md`
- `artifact-types/domain_entity.md`
- `artifact-types/ui_page.md`
- `artifact-types/integration_doc.md`
### Sections
- `sections/summary.md`
- `sections/details.md`
- `sections/tech-use-case.md`
- `sections/fr.md`
- `sections/api-contract.md`
- `sections/requirements-format.md`
### Templates
- `templates/api_method.template.md`
- `templates/logic_block.template.md`
- `templates/architecture_overview.template.md`
- `templates/domain_entity.template.md`
@@ -0,0 +1,40 @@
# API Method Rules
## Назначение
Этот файл задает правила для документов типа `api_method`.
## Когда использовать
Использовать для описания одного HTTP endpoint или одного отдельного API метода.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
Внутри `## Details` обязательны:
- `### Описание`
- `### Сценарий`
- `### Функциональные требования`
- `### Нефункциональные требования`
- `### Контракт`
## Особые правила
- Во frontmatter обязательно указывать `endpoint` (например: `POST /api/v1/clients/contacts-dgr`).
- Сценарий оформляется как технический use case.
- Функциональные требования маркируются `FR-*`.
- Нефункциональные требования маркируются `NFR-*`.
- Контракт должен быть пригоден для последующей сборки OpenAPI.
- Если у метода есть интеграции, они выносятся в `### Интеграции`.
- Ошибки и HTTP-коды либо описываются в `### Ошибки`, либо ссылаются на централизованный каталог ошибок.
## Ошибки оформления
- Нельзя заменять контракт общим текстовым описанием.
- Нельзя смешивать несколько endpoint в одном документе.
- Нельзя хранить связи и навигацию вне frontmatter.
@@ -0,0 +1,31 @@
# Architecture Overview Rules
## Назначение
Этот файл задает правила для документов типа `architecture_overview`.
## Когда использовать
Использовать как входной документ для понимания системы, модуля или сервиса.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
## Что описывать в Details
- границы системы
- основные компоненты
- ключевые взаимодействия
- интеграционные сценарии
- главные ограничения
- ссылки на дочерние документы по API, logic, domain и другим артефактам
## Ошибки оформления
- Нельзя дублировать в архитектурном обзоре полные API-контракты.
- Нельзя делать архитектурный обзор единственным документом на всю систему без декомпозиции.
@@ -0,0 +1,30 @@
# Domain Entity Rules
## Назначение
Этот файл задает правила для документов типа `domain_entity`.
## Когда использовать
Использовать для описания одной доменной сущности, ее смысла, состояния и роли в системе.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
## Что описывать в Details
- смысл сущности
- ключевые атрибуты
- состояния или инварианты
- использование сущности в системе
- интеграции с API, workflow или внешними потребителями, если они важны для понимания модели
## Ошибки оформления
- Нельзя смешивать несколько независимых сущностей в одном документе.
- Нельзя подменять доменную сущность описанием endpoint или workflow.
@@ -0,0 +1,25 @@
# Integration Doc Rules
## Назначение
Этот файл задает правила для документов типа `integration_doc`.
## Когда использовать
Использовать для описания интеграции между системами, сервисами или внешними провайдерами.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
## Что описывать в Details
- цель интеграции
- участвующие стороны
- направление обмена
- ключевой сценарий взаимодействия
- ограничения и риски
@@ -0,0 +1,31 @@
# Logic Block Rules
## Назначение
Этот файл задает правила для документов типа `logic_block`.
## Когда использовать
Использовать для описания одного законченного блока логики, workflow или процесса.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
## Что описывать в Details
- назначение логического блока
- входы и выходы
- последовательность выполнения
- интеграции
- ключевые ограничения
- состояние и ошибки, если они важны для понимания блока
## Ошибки оформления
- Нельзя описывать весь модуль целиком, если логика распадается на несколько независимых блоков.
- Нельзя превращать документ в пересказ исходного кода построчно.
@@ -0,0 +1,24 @@
# UI Page Rules
## Назначение
Этот файл задает правила для документов типа `ui_page`.
## Когда использовать
Использовать для описания одной пользовательской страницы, экрана или отдельного UI-сценария.
## Обязательная структура
Документ должен содержать:
- YAML frontmatter
- `# <title>`
- `## Summary`
- `## Details`
## Что описывать в Details
- назначение страницы
- пользовательский сценарий
- основные блоки интерфейса
- связанные API и сущности
@@ -0,0 +1,71 @@
# Documentation Rules
Этот каталог оформляет MVP документации проекта в атомарном формате.
## Базовая структура
- Каждый документ содержит YAML frontmatter.
- В документе должен быть один `H1`, совпадающий с `title`.
- Основные разделы оформляются как `## Summary` и `## Details`.
- Внутри `Details` используются заголовки уровня `###` и ниже.
- Связи, сущности и навигация описываются во frontmatter через `related_docs`, `links`, `entities`, `parent`, `children`.
## Summary
- Краткий explain-слой быстрого контекста.
- Должен позволять быстро понять назначение документа без чтения `Details`.
- Предпочтительный формат: компактный список ключевых фактов без длинных абзацев.
## Details
- Раскрывает полное описание объекта.
- Структура `Details` зависит от типа документа.
- Сценарии, ограничения, интеграции, ошибки и кодовые привязки должны быть разнесены по отдельным подразделам.
## API documents
Для `api_method` внутри `## Details` обязательны разделы:
- `### Описание`
- `### Сценарий`
- `### Функциональные требования`
- `### Нефункциональные требования`
- `### Контракт`
Если у метода есть интеграции и ошибки, также обязательны:
- `### Интеграции`
- `### Ошибки`
- `### Связанный код`
- `### История изменений`
### Сценарий
Сценарий оформляется как технический use case и содержит:
- название
- предусловия
- триггер
- основной сценарий
- альтернативный сценарий
- обработку ошибок
- постусловие
### Требования
- Функциональные требования маркируются как `FR-1`, `FR-2`, ...
- Нефункциональные требования маркируются как `NFR-1`, `NFR-2`, ...
- Идентификаторы требований локальны в рамках одного документа.
### Контракт
Контракт должен быть пригоден для последующей сборки OpenAPI-спецификации и включать:
- входные параметры
- выходные параметры
- структуру JSON-сообщений
- обязательность полей
- типы и ограничения
- описание полей
- правила заполнения
- примеры данных
- auth
- idempotency
- timeout
- ошибки и их HTTP-коды
@@ -0,0 +1,38 @@
# Documentation System
## Назначение
Этот файл задает общую модель документации проекта.
## Базовая модель
Каждый документ должен состоять из двух слоев:
- YAML frontmatter
- контент
Контент всегда состоит из двух обязательных разделов:
- `## Summary`
- `## Details`
Над ними должен быть один заголовок `# <title>`, совпадающий со значением `title` во frontmatter.
## Принципы
- Документы должны быть атомарными.
- Один документ описывает одну тему.
- Вместо дублирования между документами используются явные ссылки.
- Связи и навигация должны быть формализованы.
- Документы должны быть пригодны для чтения человеком и для RAG.
- Документы должны быть пригодны для частичного обновления без деградации структуры.
## Типы документов
На уровне проекта поддерживаются типы:
- `api_method`
- `logic_block`
- `architecture_overview`
- `domain_entity`
- `ui_page`
- `integration_doc`
- `index_page`
- `glossary_item`
@@ -0,0 +1,68 @@
# Frontmatter Rules
## Назначение
Этот файл описывает единый контракт YAML frontmatter для всех документов.
## Обязательные поля
```yaml
id: string
title: string
doc_type: string
domain: string
sub_domain: string
related_docs: []
status: string
```
## Поля совместимости и рекомендуемые поля
```yaml
type: string
name: string
module: string
layer: string
updated_at: YYYY-MM-DD
tags: []
entities: []
parent: string | null
children: []
links: {}
source_of_truth: string
related_code: []
system_analytics_refs: []
```
## Правила
- `id` должен быть стабильным и уникальным в пределах документации проекта.
- `title` — человекочитаемый заголовок.
- `doc_type` — канонический тип документа.
- `domain` и `sub_domain` определяют бизнес-контекст документа.
- `related_docs` хранит явные связи с другими markdown-документами.
- `status` хранит жизненный цикл документа: например `draft`, `approved`, `active`.
- `type` допустимо дублировать как alias для tooling-совместимости с индексаторами.
- `name` — короткое системное имя документа.
- `module` — модуль или подсистема.
- `layer` — слой системы.
- `updated_at` хранится в формате `YYYY-MM-DD`.
- Для документов с `doc_type: api_method` поле `endpoint` является обязательным.
## Связи и навигация
- `entities` описывает сущности, связанные с документом.
- `parent` и `children` описывают иерархию.
- `links` описывает typed graph связей между документами, кодом и интеграциями.
## Формат links
```yaml
links:
called_by:
- ext.health_probe
uses_logic:
- logic.some_flow
integrates_with:
- ext.some_system
```
@@ -0,0 +1,33 @@
# Linking Rules
## Назначение
Этот файл описывает, как связывать документы между собой.
## Иерархия
- `parent` используется для родительского документа.
- `children` используется для прямых дочерних документов.
- Иерархия должна быть осмысленной и стабильной.
- Для общей точки входа допустим `index_page`.
## Графовые связи
Для `related_docs` используются ссылки на соседние документы.
Для `links` рекомендуется использовать typed-ключи:
- `called_by`
- `uses_logic`
- `reads_db`
- `writes_db`
- `integrates_with`
- `used_by`
- `exposes_api`
- `uses_entities`
## Правила использования
- Если документ логически входит в другой, использовать `parent`/`children`.
- Если связь нужна для навигации между равноправными документами, дублировать ее в `related_docs`.
- Если связь отражает поведение, интеграции или переиспользование, фиксировать ее в `links`.
- Детальное описание интеграций хранить в body документа, а не только во frontmatter.
@@ -0,0 +1,24 @@
# Naming Rules
## Назначение
Этот файл описывает правила именования документов, файлов и идентификаторов.
## Правила для файлов
- Имена файлов должны быть в kebab-case.
- Имя файла должно отражать одну тему.
- Для шаблонов использовать суффикс `.template.md`.
## Правила для id
- `id` строится в формате `<type-group>.<name>`.
- Примеры:
- `api.send_message_endpoint`
- `logic.telegram_notification_loop`
- `architecture.telegram_notify_app`
## Правила для title
- `title` должен быть кратким и человекочитаемым.
- В `title` допускаются пробелы и естественный язык.
@@ -0,0 +1,19 @@
# Writing Style
## Назначение
Этот файл задает правила стиля для текстового наполнения документации.
## Правила стиля
- Текст должен быть лаконичным.
- Формулировки должны быть точными и техническими.
- Summary должен быть кратким explain-слоем.
- Details должен раскрывать суть без лишней воды.
- Нежелательно смешивать несколько тем в одном документе.
- Если детали относятся к другому артефакту, их нужно выносить в отдельный документ.
## Язык
- Основной язык документации — русский.
- Технические термины, названия классов, API, RAG, OpenAPI, runtime и другие устоявшиеся identifiers можно оставлять на английском.
@@ -0,0 +1,24 @@
# API Contract Rules
## Назначение
Этот файл описывает, как оформлять подраздел `## Контракт` в API-документах.
## Что должно быть описано
- входные параметры
- выходные параметры
- JSON-структуры запросов и ответов
- обязательность полей
- типы полей
- ограничения
- описание назначения полей
- примеры данных
- auth
- idempotency
- timeout
- ошибки и их HTTP-коды
## Правило качества
Контракт должен быть достаточно формальным, чтобы по нему можно было собрать OpenAPI-спецификацию.
@@ -0,0 +1,13 @@
# Details Section Rules
## Назначение
Этот файл задает общие правила для секции `## Details`.
## Правила
- `Details` оформляется как `## Details`.
- Внутри `Details` используются заголовки уровня `###` и ниже.
- Структура Details зависит от типа документа.
- В Details не нужно повторно дублировать навигацию и связи, если они уже есть во frontmatter.
- Интеграции, ошибки и кодовые привязки должны быть выделены в отдельные подразделы, если они существенны для понимания документа.
@@ -0,0 +1,37 @@
# Functional requrements rules
## Назначение
Этот файл описывает, как оформлять функциональные требования в подраздел `### Функциональные требования` в документах.
## Правила
- Функциональное требование (FR) расширяет и дополняет шаги, описанные в сценарии.
- Функциональное требование (FR) не должно копировать шаг сценария не неся дополнительной информации.
- Название функционального требования формируется следующим образом - "FR.<номер>. <Название>", где
- <номер> идет инкрементально внутри конкретного документа, начинается с 1.
- <Название> - кратко описывает что делает требование, суть действий (от 3 до 7 слов)
## Пример целевого описания сценария
### Примеры названия FR
- Получение данных клиента из АС ЕПК
- Проверка уровня доступа
- Сценарий построения списка связанных предложений
### Примеры описания FR
FR.1. Получение данных клиента из АС ЕПК
1. Сформировать запрос к эндпоинту POST /api/v1/path/to/resourse в АС ЕПК
- Заголовки
- <тут идет описание заголовков и того как они формируются>
- Параметры запроса
- <тут идет описание параметров и того как они формируются>
- Тело запроса
- <тут идет описание структуры объекта JSON или payload в другмо формате так как это задано требованиями>
2. Обработать ответ от АС ЕПК
Успешный ответ - <взять из описания вызываеого api критерии успешного ответа >
Ничего не найдено - <взять из описания вызываеого api критерии успешного овтета, опционально (если применимо)>
Ошибка - <взять из описания вызываеого api критерии успешного ответа >
@@ -0,0 +1,16 @@
# Requirements Format Rules
## Назначение
Этот файл задает формат для функциональных и нефункциональных требований.
## Функциональные требования
- Использовать коды `FR-1`, `FR-2`, `FR-3` и так далее.
- Каждое требование должно описывать отдельный обязательный аспект поведения.
- Идентификаторы локальны в пределах одного документа.
## Нефункциональные требования
- Использовать коды `NFR-1`, `NFR-2`, `NFR-3` и так далее.
- Требования должны описывать характеристики качества, ограничения и эксплуатационные свойства.
@@ -0,0 +1,13 @@
# Summary Section Rules
## Назначение
Этот файл задает правила для секции `## Summary`.
## Правила
- Summary должен быть коротким explain-слоем быстрого контекста.
- Summary должен объяснять суть документа без лишних деталей.
- Summary должен быть пригоден для explain и быстрого чтения.
- Предпочтительный формат: список ключевых фактов `Purpose`, `Actor`, `Trigger`, `Errors`, `Related ...` и т.д.
- Для крупных документов допустим более длинный summary, если он остается структурированным.
@@ -0,0 +1,66 @@
# Scenario Rules
## Назначение
Этот файл описывает, как оформлять технический USE CASE в подраздел `### Сценарий` в документах.
## Обязательные части
- название
- предусловия
- триггер
- основной сценарий
- альтернативный сценарий
- обработка ошибок
- постусловие
## Правила
- Основной и альтернативные сценарии состоят из шагов.
- Каждый шаг описывается одним предложением не более 15-20 слов, и состоит из двух частей. Первая часть описывает что мы делаем по смыслу, чтобы это было понятно человеку без низкоуровневых технических деталей. Например: авторизует запрос, получает данные клиента, запрашивает справочники. Вторая часть описывает как это реализовано технически - вызывает эндпоинт /path/to/resource в системе <название системы>.
- В описании шага не должно быть длинных технических деталей. Если техничсекую реализацию нельхзя описатьодним предложенеим (в лимите длины описания шага), то необхлодимо это вынести в отдельное функциональное требование FR.<номер>. <Название> и описать в нем технические детали. А в шаге сослаться на это требование через "Описание приведено в FR.<номер>. <Название>"
- Для шагов авторизации обязателен доп шаг с описанием обработки ошибки.
- Для шагов с интеграцией обязателен доп шаг с описанием обработки ошибки.
- Для шагов с проверкой условий обязательны доп шаги с описанием переходов по сценарию.
- Название "FR.<номер>. <Название>" формируется следующим образом:
- <номер> идет инкрементально внутри конкретного документа, начинается с 1.
- <Название> - кратко описывает что делает требование, суть действий.
- Для каждого шага при необходимости нужно прописать логику действий в случае ошибки или если логика шага определяет несколько сценариев разивития при выполнении заданных условий.
- Для шагов, которые описывают интеграцию с другой системой необходимо указать название точки интеграции (название эндпоинта, название топика и так далее) и сделать ссылку на FR.<номер>. <Название> с описанием шагов интеграции - как сформировать запрос/сообщение, как обработать ответ, политику ретраев.
- Сценарий собирается из тезисов, приведенных системной аналимтике в свободной формулировке
- Функциональные требования "FR.<номер>. <Название>" не должны дублировать шагов сценария в use case. Они содержат детали, которые вынесены из юзкейса чтобы не делать его тяжелым. Если шаг юзкейса описывается одним предложением в лимите длины, то FR делать не нужно.
- FR обязательно описывается для шага с интеграцией
- FR Не описывается для шага авторизации.
## Пример целевого описания сценария
### Примеры шагов сценария
Пример 1
- Авторизует запрос пользователя по наличию у него экшена ролевой модели CI02792632.ContactsDGR.Detail
- В случае ошибки - завершить сценарий с кодом UNAUTHORIZED
Пример 2
- Запрашивает данные клиента - вызывает /api/v1/clients/{client-id}/info
- В случае ошибки - завершить сценарий с кодом CLIENT_INFO_REQUEST_FAIL
Пример 3
- Возвращает ответ в формате <название DTO>
### Примеры названия FR
- Получение данных клиента из АС ЕПК
- Проверка уровня доступа
- Сценарий построения списка связанных предложений
@@ -0,0 +1,85 @@
---
id: api.example_method
type: api_method
doc_type: api_method
name: example_method
title: HTTP API /example
module: example_module
layer: application
domain: example_domain
sub_domain: example_subdomain
endpoint: POST /api/v1/example
related_docs: []
status: draft
updated_at: 2026-03-20
source_of_truth: code
parent: null
children: []
tags: []
entities: []
links: {}
---
# HTTP API /example
## Summary
Краткое описание метода.
## Details
## Описание
Короткое описание сути метода.
## Сценарий
**Название:**
**Предусловия:**
-
**Триггер:**
-
**Основной сценарий:**
1.
**Альтернативный сценарий:**
1.
**Обработка ошибок:**
1.
**Постусловие:**
-
## Функциональные требования
**FR-1.**
## Нефункциональные требования
**NFR-1.**
## Контракт
### Входные параметры
| Параметр | Где передается | Тип | Обязательность | Ограничения | Описание | Пример |
|---|---|---|---|---|---|---|
| | | | | | | |
### Выходные параметры
| Поле | Тип | Обязательность | Ограничения | Описание | Заполнение | Пример |
|---|---|---|---|---|---|---|
| | | | | | | |
### Интеграции
### Ошибки
### Связанный код
### История изменений
@@ -0,0 +1,48 @@
---
id: architecture.example_system
type: architecture_overview
doc_type: architecture_overview
name: example_system
title: Обзор архитектуры Example System
module: example_module
layer: system
domain: example_domain
sub_domain: example_subdomain
related_docs: []
status: draft
updated_at: 2026-03-20
source_of_truth: mixed
parent: null
children: []
tags: []
entities: []
links: {}
---
# Обзор архитектуры Example System
## Summary
Краткое описание архитектуры.
## Details
### Описание
### Контекст
### Границы системы
### Компоненты
### Интеграционные сценарии
### Интеграции
### Ограничения
### Связанный код
### Связанные документы
### История изменений
@@ -0,0 +1,48 @@
---
id: domain.example_entity
type: domain_entity
doc_type: domain_entity
name: example_entity
title: Пример доменной сущности
module: example_module
layer: domain
domain: example_domain
sub_domain: example_subdomain
related_docs: []
status: draft
updated_at: 2026-03-20
source_of_truth: code
parent: null
children: []
tags: []
entities: []
links: {}
---
# Пример доменной сущности
## Summary
Краткое описание сущности.
## Details
### Описание
### Модель данных
### Состояния и инварианты
### Технический use case
### Функциональные требования
### Нефункциональные требования
### Интеграции
### Связанный код
### Связанные документы
### История изменений
@@ -0,0 +1,50 @@
---
id: logic.example_block
type: logic_block
doc_type: logic_block
name: example_block
title: Пример блока логики
module: example_module
layer: application
domain: example_domain
sub_domain: example_subdomain
related_docs: []
status: draft
updated_at: 2026-03-20
source_of_truth: code
parent: null
children: []
tags: []
entities: []
links: {}
---
# Пример блока логики
## Summary
Краткое описание блока логики.
## Details
### Описание
### Контекст
### Технический use case
### Функциональные требования
### Нефункциональные требования
### Интеграции
### Ограничения и условия вызова
### Ошибки и деградации
### Связанные API
### Связанный код
### История изменений
@@ -0,0 +1,50 @@
---
id: ui.example_page
type: ui_page
doc_type: ui_page
name: example_page
title: Пример UI-страницы
module: example_module
layer: presentation
domain: example_domain
sub_domain: example_subdomain
related_docs: []
status: draft
updated_at: 2026-03-20
source_of_truth: mixed
parent: null
children: []
tags: []
entities: []
links: {}
---
# Пример UI-страницы
## Summary
Краткое описание страницы и её назначения.
## Details
### Назначение страницы
### Пользовательский сценарий
### Основные блоки интерфейса
### Связанные API и сущности
### Функциональные требования
### Нефункциональные требования
### Ограничения и граничные случаи
### Ошибки и валидации
### Связанный код
### Связанные документы
### История изменений
@@ -0,0 +1,3 @@
from app.core.agent.processes.v2.intent_router.router import V2IntentRouter
__all__ = ["V2IntentRouter"]
@@ -0,0 +1,19 @@
from __future__ import annotations
from dataclasses import dataclass
@dataclass(slots=True)
class QueryFeatures:
normalized_query: str
target_terms: list[str]
endpoint_paths: list[str]
file_names: list[str]
matched_aliases: list[str]
target_doc_hints: list[str]
file_markers: list[str]
architecture_markers: list[str]
logic_markers: list[str]
domain_markers: list[str]
endpoint_markers: list[str]
scope_type: str = "unknown"
@@ -0,0 +1,11 @@
from app.core.agent.processes.v2.intent_router.modules.anchors import AnchorAnalysis, V2AnchorExtractor
from app.core.agent.processes.v2.intent_router.modules.normalizer import V2QueryNormalizer
from app.core.agent.processes.v2.intent_router.modules.target_terms import TargetTermsAnalysis, V2TargetTermsExtractor
__all__ = [
"AnchorAnalysis",
"TargetTermsAnalysis",
"V2AnchorExtractor",
"V2QueryNormalizer",
"V2TargetTermsExtractor",
]
@@ -0,0 +1,247 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from app.core.agent.processes.v2.intent_router.modules.target_terms import TargetTermsAnalysis
from app.core.agent.utils.process_v2.models import V2RouteAnchors
@dataclass(slots=True)
class AnchorAnalysis:
anchors: V2RouteAnchors
file_markers: list[str]
architecture_markers: list[str]
logic_markers: list[str]
domain_markers: list[str]
endpoint_markers: list[str]
class _MarkerScanner:
_FILE_MARKERS = (
"в каком файле",
"в каком документе",
"в каких файлах",
"где находится",
"где описан",
"где описана",
"где описаны",
"покажи файл",
"какие файлы",
"найди файл",
"найди файлы",
"покажи документ",
"где описано",
"документ с описанием",
)
_ARCHITECTURE_MARKERS = (
"архитектура",
"архитектур",
"architecture",
"arch overview",
"как устроено приложение",
"как устроен сервис",
"основные части системы",
"из чего состоит",
)
_LOGIC_MARKERS = (
"цикл",
"loop",
"flow",
"workflow",
"process",
"worker",
"как работает отправка уведомлений",
"логика отправки",
"background job",
"runtime loop",
)
_DOMAIN_MARKERS = ("runtime health", "health model", "статусы здоровья", "сущность", "entity", "здоровье runtime")
_ENDPOINT_MARKERS = (
"endpoint",
"api",
"route",
"method",
"метод api",
"метод",
"метода",
"ручка",
"эндпоинт",
"маршрут",
"роут",
)
def scan(self, lowered_query: str) -> dict[str, list[str]]:
return {
"file_markers": self._matching(lowered_query, self._FILE_MARKERS),
"architecture_markers": self._matching(lowered_query, self._ARCHITECTURE_MARKERS),
"logic_markers": self._matching(lowered_query, self._LOGIC_MARKERS),
"domain_markers": self._matching(lowered_query, self._DOMAIN_MARKERS),
"endpoint_markers": self._matching(lowered_query, self._ENDPOINT_MARKERS),
}
def _matching(self, query: str, markers: tuple[str, ...]) -> list[str]:
return [marker for marker in markers if marker in query]
class _EntityNameExtractor:
_ENTITY_RE = re.compile(r"\b[A-Z][A-Za-z0-9_]+\b")
_IGNORE = {"arch"}
def extract(self, query: str) -> list[str]:
items: list[str] = []
for match in self._ENTITY_RE.finditer(query):
candidate = match.group(0).strip()
if candidate and candidate.lower() not in self._IGNORE and candidate not in items:
items.append(candidate)
return items
class _FileNameExtractor:
_TOKEN_RE = re.compile(r"`([^`]+)`|([A-Za-z0-9_./-]+)")
_WITH_EXTENSION_RE = re.compile(r".+\.(md|yaml|yml|json)$", re.IGNORECASE)
_DOC_PATH_RE = re.compile(r"^(docs|doc|documentation)/.+")
def extract(self, query: str) -> list[str]:
items: list[str] = []
for match in self._TOKEN_RE.finditer(query):
candidate = next((item for item in match.groups() if item), "")
normalized = str(candidate or "").strip().strip("`'\"")
if self._is_file_name(normalized):
self._append_unique(items, normalized.lower())
return items
def _is_file_name(self, token: str) -> bool:
if not token:
return False
if token.startswith("/") and "." not in token:
return False
if self._WITH_EXTENSION_RE.fullmatch(token):
return True
return self._DOC_PATH_RE.fullmatch(token) is not None
def _append_unique(self, items: list[str], value: str) -> None:
if value and value not in items:
items.append(value)
class _ProcessAnchorExtractor:
_DOMAIN_KEYWORDS = {
"billing": "billing",
"notifications": "notifications",
}
_SUBDOMAIN_KEYWORDS = {
"invoice": ("billing", "invoice"),
"invoices": ("billing", "invoice"),
"delivery_loop": ("notifications", "delivery_loop"),
"delivery": ("notifications", "delivery_loop"),
}
def extract(self, lowered_query: str) -> tuple[str | None, str | None]:
domain = next((value for token, value in self._DOMAIN_KEYWORDS.items() if token in lowered_query), None)
subdomain: str | None = None
for token, mapping in self._SUBDOMAIN_KEYWORDS.items():
if token in lowered_query:
domain = domain or mapping[0]
subdomain = mapping[1]
break
return domain, subdomain
class V2AnchorExtractor:
def __init__(
self,
marker_scanner: _MarkerScanner | None = None,
entity_extractor: _EntityNameExtractor | None = None,
file_name_extractor: _FileNameExtractor | None = None,
process_anchor_extractor: _ProcessAnchorExtractor | None = None,
) -> None:
self._marker_scanner = marker_scanner or _MarkerScanner()
self._entity_extractor = entity_extractor or _EntityNameExtractor()
self._file_name_extractor = file_name_extractor or _FileNameExtractor()
self._process_anchor_extractor = process_anchor_extractor or _ProcessAnchorExtractor()
def extract(self, normalized_query: str, terms: TargetTermsAnalysis) -> AnchorAnalysis:
lowered_query = normalized_query.lower()
markers = self._marker_scanner.scan(lowered_query)
process_domain, process_subdomain = self._process_anchor_extractor.extract(lowered_query)
anchors = V2RouteAnchors(
entity_names=self._entity_extractor.extract(normalized_query),
file_names=self._file_name_extractor.extract(normalized_query),
endpoint_paths=list(terms.endpoint_paths),
target_doc_hints=self._target_doc_hints(
endpoint_paths=terms.endpoint_paths,
api_like_terms=terms.api_like_terms,
alias_docs=terms.alias_docs,
architecture_markers=markers["architecture_markers"],
logic_markers=markers["logic_markers"],
domain_markers=markers["domain_markers"],
),
matched_aliases=list(terms.matched_aliases),
process_domain=process_domain,
process_subdomain=process_subdomain,
)
return AnchorAnalysis(
anchors=anchors,
file_markers=markers["file_markers"],
architecture_markers=markers["architecture_markers"],
logic_markers=markers["logic_markers"],
domain_markers=markers["domain_markers"],
endpoint_markers=markers["endpoint_markers"],
)
def _target_doc_hints(
self,
*,
endpoint_paths: list[str],
api_like_terms: list[str],
alias_docs: list[str],
architecture_markers: list[str],
logic_markers: list[str],
domain_markers: list[str],
) -> list[str]:
hints = list(alias_docs)
endpoint_map = {
"/health": "docs/api/health-endpoint.md",
"/send": "docs/api/send-message-endpoint.md",
"/actions/{action}": "docs/api/control-actions-endpoint.md",
}
for endpoint in endpoint_paths:
for hint in self._endpoint_hint_variants(endpoint):
self._append_unique(hints, hint)
hint = endpoint_map.get(endpoint)
self._append_unique(hints, hint)
for term in api_like_terms:
for hint in self._api_like_hint_variants(term):
self._append_unique(hints, hint)
if architecture_markers:
self._append_unique(hints, "docs/architecture/telegram-notify-app-overview.md")
if logic_markers:
self._append_unique(hints, "docs/logic/telegram-notification-loop.md")
if domain_markers:
self._append_unique(hints, "docs/domains/runtime-health-entity.md")
return hints
def _endpoint_hint_variants(self, endpoint: str) -> list[str]:
normalized = str(endpoint or "").strip().lower()
if not normalized:
return []
slug = normalized.strip("/").replace("/", "-").replace("{", "").replace("}", "")
leaf = next((part for part in reversed(slug.split("-")) if part and part != "id"), "")
hints: list[str] = [normalized]
for value in (slug, leaf):
if not value:
continue
hints.extend([value, f"{value}-endpoint", f"{value} endpoint"])
return list(dict.fromkeys(hints))
def _api_like_hint_variants(self, term: str) -> list[str]:
normalized = str(term or "").strip().lower().lstrip("/")
if not normalized:
return []
return [normalized, f"/{normalized}", f"{normalized}-endpoint", f"{normalized} endpoint"]
def _append_unique(self, items: list[str], value: str | None) -> None:
normalized = str(value or "").strip()
if normalized and normalized not in items:
items.append(normalized)
@@ -0,0 +1,6 @@
from __future__ import annotations
class V2QueryNormalizer:
def normalize(self, user_query: str) -> str:
return " ".join(str(user_query or "").strip().split())
@@ -0,0 +1,176 @@
"""Build an in-memory DOCS scope index from D1/D3 catalog rows (no chunk retrieval).
Parses metadata from ``D1_DOCUMENT_CATALOG`` and ``D3_ENTITY_CATALOG`` rows produced by the
existing RAG indexer—no additional layers or chunk scans.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
def _norm_text(value: object) -> str:
return re.sub(r"\s+", " ", str(value or "").strip().lower())
def _split_multi(value: object) -> list[str]:
if value is None:
return []
if isinstance(value, list):
raw = value
else:
raw = re.split(r"[;,|]", str(value))
out: list[str] = []
for item in raw:
s = str(item).strip()
if s:
out.append(s)
return out
@dataclass(slots=True)
class DocsScopeCatalog:
"""Flattened terms from D1_DOCUMENT_CATALOG and D3_ENTITY_CATALOG for lexical grounding."""
domain_values: set[str] = field(default_factory=set)
subdomain_pairs: list[tuple[str, str]] = field(default_factory=list) # (domain, subdomain)
entity_records: list[dict[str, object]] = field(default_factory=list)
api_records: list[dict[str, object]] = field(default_factory=list)
def build_docs_scope_catalog(rows: list[dict]) -> DocsScopeCatalog:
"""Derive searchable terms from catalog layers only (existing RAG index rows)."""
catalog = DocsScopeCatalog()
for row in rows:
layer = str(row.get("layer") or "")
meta = row.get("metadata")
if not isinstance(meta, dict):
meta = {}
path = str(row.get("path") or "")
title = str(row.get("title") or "")
content = str(row.get("content") or "")
if layer == "D1_DOCUMENT_CATALOG":
_ingest_d1_row(catalog, path=path, title=title, content=content, metadata=meta)
elif layer == "D3_ENTITY_CATALOG":
_ingest_d3_row(catalog, path=path, title=title, metadata=meta)
return catalog
def _ingest_d1_row(
catalog: DocsScopeCatalog,
*,
path: str,
title: str,
content: str,
metadata: dict,
) -> None:
doc_type = _norm_text(metadata.get("type") or metadata.get("doc_type"))
domain = _norm_text(metadata.get("domain"))
subdomain = _norm_text(metadata.get("subdomain"))
name = _norm_text(metadata.get("name"))
summary = _norm_text(metadata.get("summary_text"))
endpoint = _norm_text(metadata.get("endpoint"))
entities = [_norm_text(e) for e in _split_multi(metadata.get("entities"))]
tags = [_norm_text(t) for t in _split_multi(metadata.get("tags"))]
if domain:
catalog.domain_values.add(domain)
if domain and subdomain:
catalog.subdomain_pairs.append((domain, subdomain))
blob = " ".join(x for x in (name, title, summary, content) if x)
for ent in entities:
if ent:
catalog.entity_records.append(
{
"name": ent,
"domain": domain or None,
"subdomain": subdomain or None,
"source_layer": "D1_DOCUMENT_CATALOG",
"path": path,
"blob": blob,
}
)
for tag in tags:
if tag and len(tag) >= 3:
catalog.entity_records.append(
{
"name": tag,
"domain": domain or None,
"subdomain": subdomain or None,
"source_layer": "D1_DOCUMENT_CATALOG",
"path": path,
"blob": blob,
}
)
is_api_method = doc_type == "api_method" or "api_method" in path.lower()
if is_api_method or endpoint:
ep = endpoint or _endpoint_from_title(title)
if ep:
catalog.api_records.append(
{
"endpoint": ep,
"domain": domain or None,
"source_layer": "D1_DOCUMENT_CATALOG",
"path": path,
"title": title,
}
)
def _ingest_d3_row(
catalog: DocsScopeCatalog,
*,
path: str,
title: str,
metadata: dict,
) -> None:
entity_name = str(metadata.get("entity_name") or "").strip()
domain = _norm_text(metadata.get("domain"))
subdomain = _norm_text(metadata.get("subdomain"))
module = _norm_text(metadata.get("module"))
source_path = str(metadata.get("source_path") or "").strip()
tags = [_norm_text(t) for t in _split_multi(metadata.get("tags"))]
if domain:
catalog.domain_values.add(domain)
if domain and subdomain:
catalog.subdomain_pairs.append((domain, subdomain))
blob = " ".join(
_norm_text(x)
for x in (entity_name, title, module, source_path, " ".join(tags))
if x
)
if entity_name:
catalog.entity_records.append(
{
"name": _norm_text(entity_name),
"domain": domain or None,
"subdomain": subdomain or None,
"module": module or None,
"source_layer": "D3_ENTITY_CATALOG",
"path": path or source_path,
"blob": blob,
}
)
def _endpoint_from_title(title: str) -> str:
t = str(title or "").strip()
if not t:
return ""
upper = t.upper()
for method in ("GET ", "POST ", "PUT ", "PATCH ", "DELETE "):
if method in upper:
idx = upper.index(method)
rest = t[idx:].split()
if len(rest) >= 2 and rest[1].startswith("/"):
return _norm_text(rest[1])
m = re.search(r"(\/[a-z0-9_./{}-]+)", t, re.IGNORECASE)
return _norm_text(m.group(1)) if m else ""
@@ -0,0 +1,443 @@
"""Deterministic scope resolution from query + derived DOCS catalog (pre-LLM).
Matches the user query against catalog terms (exact / normalized). Optional embedding-based
retrieval can extend candidates later; final ``scope_type`` never relies on embeddings alone.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from app.core.agent.processes.v2.intent_router.modules.scope_catalog import DocsScopeCatalog
from app.core.agent.processes.v2.intent_router.modules.target_terms import TargetTermsAnalysis
from app.core.agent.utils.process_v2.models import ScopeCandidate, V2ScopeType
_SCORE_EXACT = 1.0
_SCORE_NORMALIZED = 0.88
_SCORE_SOFT = 0.72
_STRONG_THRESHOLD = 0.85
_ENUM_MARKERS_RU = (
"какие ",
"какие\n",
"какой ",
"какого ",
"список",
"перечисли",
"перечислить",
"все api",
"все методы",
"какие api",
"какие методы",
"каких ",
)
_SINGLE_SEGMENT_ENDPOINT_ALLOWLIST = frozenset(
{
"/health",
"/send",
"/healthz",
"/ready",
"/live",
"/metrics",
}
)
_PROJECT_WIDE_MARKERS = (
"в проекте",
"в системе",
"в приложении",
"по проекту",
"во всем проекте",
"overall",
"in the project",
)
@dataclass(slots=True)
class ScopeResolution:
scope_type: str = V2ScopeType.UNKNOWN
candidate_domains: list[ScopeCandidate] = field(default_factory=list)
candidate_subdomains: list[ScopeCandidate] = field(default_factory=list)
candidate_entities: list[ScopeCandidate] = field(default_factory=list)
candidate_apis: list[ScopeCandidate] = field(default_factory=list)
strong_domain: str | None = None
strong_subdomain: str | None = None
strong_entity_names: list[str] = field(default_factory=list)
strong_endpoint_paths: list[str] = field(default_factory=list)
catalog_loaded: bool = False
def _catalog_has_index_terms(catalog: DocsScopeCatalog) -> bool:
return bool(
catalog.domain_values
or catalog.subdomain_pairs
or catalog.entity_records
or catalog.api_records
)
def plausible_doc_endpoint_paths(paths: list[str]) -> list[str]:
"""Drop spurious ``/token`` paths from api-like heuristics (e.g. ``/billing`` after ``api``)."""
out: list[str] = []
for raw in paths:
p = str(raw or "").strip().lower()
if not p.startswith("/"):
continue
segments = [s for s in p.split("/") if s]
if len(segments) >= 2:
out.append(p)
continue
if len(segments) == 1 and p in _SINGLE_SEGMENT_ENDPOINT_ALLOWLIST:
out.append(p)
continue
return out
def resolve_docs_scope(
normalized_query: str,
terms: TargetTermsAnalysis,
catalog: DocsScopeCatalog | None,
) -> ScopeResolution:
"""Lexical scope resolution; embeddings never set final scope alone (not used here)."""
resolution = ScopeResolution()
if catalog is None:
return resolution
if not _catalog_has_index_terms(catalog):
return resolution
resolution.catalog_loaded = True
query_l = _norm_query(normalized_query)
if not query_l:
resolution.scope_type = V2ScopeType.UNKNOWN
return resolution
_collect_domain_candidates(query_l, catalog, resolution)
_collect_subdomain_candidates(query_l, catalog, resolution)
_collect_entity_candidates(query_l, catalog, resolution)
_collect_api_candidates(query_l, catalog, resolution)
_dedupe_candidates(resolution)
endpoint_paths = plausible_doc_endpoint_paths(list(terms.endpoint_paths))
strong_api = _pick_strong(resolution.candidate_apis)
strong_entity = _pick_strong(resolution.candidate_entities)
strong_sub = _pick_strong(resolution.candidate_subdomains)
strong_dom = _pick_strong(resolution.candidate_domains)
resolution.strong_endpoint_paths = list(dict.fromkeys(endpoint_paths))
if endpoint_paths:
resolution.scope_type = V2ScopeType.ENTITY
resolution.strong_entity_names = _merge_unique(resolution.strong_entity_names, _entities_for_endpoints(endpoint_paths, catalog))
return resolution
if strong_api and strong_api.score >= _STRONG_THRESHOLD:
resolution.scope_type = V2ScopeType.ENTITY
resolution.strong_endpoint_paths = _merge_unique(resolution.strong_endpoint_paths, [strong_api.value])
return resolution
strong_sub_pre = _pick_strong(resolution.candidate_subdomains)
if (
strong_sub_pre
and strong_sub_pre.score >= _STRONG_THRESHOLD
and _subdomain_aligned_with_query(query_l, strong_sub_pre.value)
):
resolution.scope_type = V2ScopeType.SUBDOMAIN
parts = _split_subdomain_value(strong_sub_pre.value)
if parts:
resolution.strong_domain = parts[0]
resolution.strong_subdomain = parts[1]
return resolution
if strong_entity and strong_entity.score >= _STRONG_THRESHOLD:
resolution.scope_type = V2ScopeType.ENTITY
resolution.strong_entity_names = _merge_unique(
resolution.strong_entity_names,
[strong_entity.value],
)
return resolution
if strong_sub and strong_sub.score >= _STRONG_THRESHOLD:
resolution.scope_type = V2ScopeType.SUBDOMAIN
parts = _split_subdomain_value(strong_sub.value)
if parts:
resolution.strong_domain = parts[0]
resolution.strong_subdomain = parts[1]
return resolution
if strong_dom and strong_dom.score >= _STRONG_THRESHOLD:
resolution.scope_type = V2ScopeType.DOMAIN
resolution.strong_domain = strong_dom.value
return resolution
if _is_global_enumeration(query_l, has_strong_any=bool(_any_strong(resolution))):
resolution.scope_type = V2ScopeType.GLOBAL
return resolution
resolution.scope_type = V2ScopeType.UNKNOWN
return resolution
def promote_target_terms(
raw_terms: list[str],
terms: TargetTermsAnalysis,
resolution: ScopeResolution,
) -> list[str]:
"""Keep only high-confidence terms in ``target_terms``; weak matches stay in candidate_* only."""
if not resolution.catalog_loaded:
return list(raw_terms)
out: list[str] = []
strong_values = {c.value for c in _all_candidates(resolution) if c.score >= _STRONG_THRESHOLD}
strong_values |= {c.value for c in _all_candidates(resolution) if c.match_type == "exact"}
strong_entity = set(resolution.strong_entity_names)
endpoints = set(terms.endpoint_paths)
aliases = set(terms.matched_aliases)
for term in raw_terms:
t = str(term or "").strip()
if not t:
continue
tl = t.lower()
if t in endpoints or tl in {e.lower() for e in endpoints}:
_append_unique(out, tl if tl.startswith("/") else t)
continue
if t in aliases or tl in {a.lower() for a in aliases}:
_append_unique(out, tl)
continue
if tl in strong_values or t in strong_entity:
_append_unique(out, tl)
continue
if _is_explicit_identifier(t) and tl in strong_entity:
_append_unique(out, tl)
continue
# Drop weak/ungrounded terms (remain only in candidates on anchors)
return out
def _all_candidates(resolution: ScopeResolution) -> list[ScopeCandidate]:
return [
*resolution.candidate_domains,
*resolution.candidate_subdomains,
*resolution.candidate_entities,
*resolution.candidate_apis,
]
def _any_strong(resolution: ScopeResolution) -> bool:
return any(c.score >= _STRONG_THRESHOLD for c in _all_candidates(resolution))
def _pick_strong(candidates: list[ScopeCandidate]) -> ScopeCandidate | None:
if not candidates:
return None
return max(candidates, key=lambda c: (c.score, len(c.value)))
def _norm_query(q: str) -> str:
return re.sub(r"\s+", " ", str(q or "").strip().lower())
def _append_unique(items: list[str], value: str) -> None:
if value and value not in items:
items.append(value)
def _merge_unique(a: list[str], b: list[str]) -> list[str]:
return list(dict.fromkeys([*a, *b]))
def _is_explicit_identifier(token: str) -> bool:
return bool(re.fullmatch(r"[A-Za-z][A-Za-z0-9_]+", token))
def _split_subdomain_value(value: str) -> tuple[str, str] | None:
parts = str(value or "").split("::", 1)
if len(parts) == 2 and parts[0] and parts[1]:
return parts[0].strip().lower(), parts[1].strip().lower()
return None
def _subdomain_aligned_with_query(query_l: str, composite: str) -> bool:
"""True when both domain and subdomain tokens match the query (substring / token match)."""
parts = str(composite or "").split("::", 1)
if len(parts) != 2:
return False
dom, sub = parts[0].strip().lower(), parts[1].strip().lower()
s_dom, _ = _match_score(query_l, dom)
s_sub, _ = _match_score(query_l, sub)
return s_dom > 0 and s_sub > 0
def _entities_for_endpoints(endpoint_paths: list[str], catalog: DocsScopeCatalog) -> list[str]:
found: list[str] = []
eps = {e.lower() for e in endpoint_paths if e}
for rec in catalog.entity_records:
blob = str(rec.get("blob") or "").lower()
name = str(rec.get("name") or "").strip().lower()
if not name:
continue
if any(ep and ep in blob for ep in eps):
_append_unique(found, name)
return found
def _collect_domain_candidates(query_l: str, catalog: DocsScopeCatalog, resolution: ScopeResolution) -> None:
for dom in catalog.domain_values:
if not dom:
continue
score, mtype = _match_score(query_l, dom)
if score <= 0:
continue
resolution.candidate_domains.append(
ScopeCandidate(
value=dom,
score=score,
source_layer="D1_DOCUMENT_CATALOG",
match_type=mtype,
)
)
def _collect_subdomain_candidates(query_l: str, catalog: DocsScopeCatalog, resolution: ScopeResolution) -> None:
seen: set[str] = set()
for dom, sub in catalog.subdomain_pairs:
if not dom or not sub:
continue
composite = f"{dom}::{sub}"
if composite in seen:
continue
seen.add(composite)
score_dom, _ = _match_score(query_l, dom)
score_sub, mt_sub = _match_score(query_l, sub)
phrase = _phrase_score(query_l, dom, sub)
if phrase > 0:
score = phrase
mt = "normalized"
elif score_dom > 0 and score_sub > 0:
score = min(score_dom, score_sub)
mt = mt_sub
else:
# Avoid promoting a (domain, subdomain) pair when only the domain token matches.
score = 0.0
mt = mt_sub
if score <= 0:
continue
resolution.candidate_subdomains.append(
ScopeCandidate(
value=composite,
score=score,
source_layer="D1_DOCUMENT_CATALOG",
match_type=mt,
)
)
def _collect_entity_candidates(query_l: str, catalog: DocsScopeCatalog, resolution: ScopeResolution) -> None:
for rec in catalog.entity_records:
name = str(rec.get("name") or "").strip().lower()
if not name or len(name) < 2:
continue
blob = str(rec.get("blob") or "").lower()
layer = str(rec.get("source_layer") or "")
score, mtype = _match_entity(query_l, name, blob)
if score <= 0:
continue
resolution.candidate_entities.append(
ScopeCandidate(value=name, score=score, source_layer=layer, match_type=mtype)
)
def _collect_api_candidates(query_l: str, catalog: DocsScopeCatalog, resolution: ScopeResolution) -> None:
for rec in catalog.api_records:
ep = str(rec.get("endpoint") or "").strip().lower()
if not ep:
continue
layer = str(rec.get("source_layer") or "")
score, mtype = _match_score(query_l, ep.replace(" ", ""))
if score <= 0:
continue
resolution.candidate_apis.append(
ScopeCandidate(value=ep, score=score, source_layer=layer, match_type=mtype)
)
def _phrase_score(query_l: str, dom: str, sub: str) -> float:
if _contains_token(query_l, dom) and _contains_token(query_l, sub):
return max(_SCORE_NORMALIZED, 0.9)
joined = re.sub(r"\s+", " ", f"{dom} {sub}".strip())
if joined in query_l or query_l in joined:
return _SCORE_NORMALIZED
return 0.0
def _match_entity(query_l: str, name: str, blob: str) -> tuple[float, str]:
score, mt = _match_score(query_l, name)
if score > 0:
return score, mt
if name in blob and len(name) >= 4:
# cross-language hints: name appears in catalog blob; small boost if query token overlaps blob
q_tokens = set(query_l.split())
b_tokens = set(blob.split())
overlap = q_tokens & b_tokens
if overlap and (q_tokens & {name} or name[:4] in query_l):
return _SCORE_SOFT, "normalized"
return 0.0, "normalized"
def _match_score(query_l: str, value: str) -> tuple[float, str]:
v = str(value or "").strip().lower()
if not v:
return 0.0, "normalized"
v_compact = v.replace(" ", "")
q_compact = query_l.replace(" ", "")
if v == query_l or v_compact == q_compact:
return _SCORE_EXACT, "exact"
if _contains_token(query_l, v) or _contains_token(query_l, v.replace("/", " ")):
return _SCORE_EXACT, "exact"
if v in q_compact or v_compact in q_compact:
return _SCORE_NORMALIZED, "normalized"
if v in query_l:
return _SCORE_NORMALIZED, "normalized"
# prefix / slug
for token in query_l.split():
if token.startswith(v[: min(4, len(v))]) and len(v) >= 4:
return _SCORE_SOFT, "normalized"
return 0.0, "normalized"
def _contains_token(hay: str, needle: str) -> bool:
if not needle:
return False
return f" {needle} " in f" {hay} "
def _dedupe_candidates(resolution: ScopeResolution) -> None:
resolution.candidate_domains = _dedupe_list(resolution.candidate_domains)
resolution.candidate_subdomains = _dedupe_list(resolution.candidate_subdomains)
resolution.candidate_entities = _dedupe_list(resolution.candidate_entities)
resolution.candidate_apis = _dedupe_list(resolution.candidate_apis)
def _dedupe_list(items: list[ScopeCandidate]) -> list[ScopeCandidate]:
best: dict[str, ScopeCandidate] = {}
for c in items:
key = f"{c.value}|{c.source_layer}"
prev = best.get(key)
if prev is None or c.score > prev.score:
best[key] = c
return sorted(best.values(), key=lambda c: (-c.score, c.value))
def _is_global_enumeration(query_l: str, *, has_strong_any: bool) -> bool:
if has_strong_any:
return False
if any(m in query_l for m in _PROJECT_WIDE_MARKERS) and any(
m in query_l for m in ("какие", "какой", "список", "перечисли", "метод", "api")
):
return True
if any(query_l.strip().startswith(m.strip()) for m in _ENUM_MARKERS_RU if len(m.strip()) > 2):
if any(k in query_l for k in ("метод", "api", "ручк", "эндпоинт")):
return True
return False

Some files were not shown because too many files have changed in this diff Show More