Compare commits
21 Commits
1e376aff24
...
2b807623f1
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b807623f1 | |||
| 77851e99a7 | |||
| 7f22a00696 | |||
| acac19da71 | |||
| 4e3435ad92 | |||
| 2352f91cd3 | |||
| f62fb678b8 | |||
| 7387e5cc51 | |||
| 8b7b72967e | |||
| 0a25e42ea1 | |||
| 51378c5d66 | |||
| 15586f9a8c | |||
| 9066c292de | |||
| b1f825e6b9 | |||
| 095d354112 | |||
| 6ba0a18ac9 | |||
| 417b8b6f72 | |||
| 1ef0b4d68c | |||
| 2728c07ba9 | |||
| 9f1c67a751 | |||
| e8805ffe29 |
@@ -0,0 +1,14 @@
|
|||||||
|
# Analysis Assets
|
||||||
|
|
||||||
|
Этот каталог содержит служебные артефакты для аналитической и генеративной работы агента.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- `rules/` — правила построения документации, frontmatter и шаблоны документов.
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Каталог `.analysis/` отделен от `docs/`, чтобы:
|
||||||
|
- хранить служебные policy- и template-материалы вне пользовательской документации;
|
||||||
|
- передавать правила в LLM как отдельный policy-context;
|
||||||
|
- не смешивать документацию проекта и внутренние артефакты анализа.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Documentation Rules
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Этот файл фиксирует общие правила формирования, обновления и поддержки технической документации проекта.
|
||||||
|
|
||||||
|
Документация проекта должна создаваться как система атомарных, связанных между собой документов, пригодных:
|
||||||
|
- для чтения человеком;
|
||||||
|
- для сопровождения командой;
|
||||||
|
- для индексирования в RAG;
|
||||||
|
- для автоматического обновления агентом на основе кода и существующих артефактов.
|
||||||
|
|
||||||
|
Этот файл задает:
|
||||||
|
- общие принципы документационной архитектуры;
|
||||||
|
- правила декомпозиции документации;
|
||||||
|
- правила размещения файлов;
|
||||||
|
- требования к связям между документами;
|
||||||
|
- требования к качеству markdown-документов;
|
||||||
|
- правила генерации и обновления документации агентом.
|
||||||
|
|
||||||
|
Детальные шаблоны документов и правила frontmatter описываются отдельно:
|
||||||
|
- `.analysis/rules/frontmatter-rules.md`
|
||||||
|
- `.analysis/rules/templates/*.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Область действия
|
||||||
|
|
||||||
|
Правила из этого файла применяются ко всей проектной документации, размещаемой в:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/documentation/
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Frontmatter Rules
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Этот файл фиксирует правила YAML frontmatter для документов в `docs/documentation/`.
|
||||||
|
|
||||||
|
Frontmatter обязателен для каждого markdown-документа и нужен для:
|
||||||
|
- идентификации документа;
|
||||||
|
- определения типа документа;
|
||||||
|
- фиксации связей с кодом и другими документами;
|
||||||
|
- выделения сущностей, тегов и домена;
|
||||||
|
- поддержки индексирования в RAG.
|
||||||
|
|
||||||
|
Общие правила построения документации описаны в:
|
||||||
|
- `.analysis/rules/documentation-rules.md`
|
||||||
|
|
||||||
|
Шаблоны markdown body описаны в:
|
||||||
|
- `.analysis/rules/templates/*.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Общие правила
|
||||||
|
|
||||||
|
1. Frontmatter размещается в начале файла.
|
||||||
|
2. Формат — YAML между двумя строками `---`.
|
||||||
|
3. Все документы в `docs/documentation/` должны содержать frontmatter.
|
||||||
|
4. Поля должны быть стабильными и заполняться единообразно.
|
||||||
|
5. Не использовать произвольные поля без необходимости.
|
||||||
|
6. Если значение неизвестно и его нельзя уверенно вывести из evidence, поле лучше не заполнять, кроме обязательных полей.
|
||||||
|
7. Списковые поля должны оформляться как YAML-массивы.
|
||||||
|
8. Идентификаторы и ссылки должны быть стабильными и пригодными для машинной обработки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Базовый frontmatter
|
||||||
|
|
||||||
|
Каждый документ должен начинаться с frontmatter вида:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
id: api-orders-create
|
||||||
|
title: Метод создания заказа
|
||||||
|
doc_type: api_method
|
||||||
|
domain: orders
|
||||||
|
status: draft
|
||||||
|
owner: system-analyst
|
||||||
|
source_of_truth: code
|
||||||
|
related_docs:
|
||||||
|
- ui-order-create-page
|
||||||
|
- logic-order-validation
|
||||||
|
related_code:
|
||||||
|
- src/orders/api/create_order.py
|
||||||
|
entities:
|
||||||
|
- Order
|
||||||
|
- CreateOrder
|
||||||
|
tags:
|
||||||
|
- api
|
||||||
|
- orders
|
||||||
|
- create
|
||||||
|
---
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Rule: Use Document Templates From Fixed Paths
|
||||||
|
|
||||||
|
Агент должен создавать и обновлять техническую документацию только с опорой на шаблоны документов, расположенные в `.analysis/rules`.
|
||||||
|
|
||||||
|
Если агент формирует новый документ, он обязан:
|
||||||
|
|
||||||
|
- определить тип документа;
|
||||||
|
- выбрать соответствующий шаблон по фиксированному пути;
|
||||||
|
- сохранить структуру секций и базовых метаданных из шаблона;
|
||||||
|
- заполнять только те секции, которые подтверждены кодом и артефактами;
|
||||||
|
- не придумывать новые произвольные форматы, если для типа уже существует шаблон.
|
||||||
|
|
||||||
|
Пути к базовым шаблонам:
|
||||||
|
|
||||||
|
- `.analysis/rules/legacy/template_ui_page.md`
|
||||||
|
- `.analysis/rules/legacy/template_api_method.md`
|
||||||
|
- `.analysis/rules/legacy/template_logic_block.md`
|
||||||
|
|
||||||
|
Правило выбора шаблона:
|
||||||
|
|
||||||
|
- для документа типа `ui_page` использовать `.analysis/rules/legacy/template_ui_page.md`
|
||||||
|
- для документа типа `api_method` использовать `.analysis/rules/legacy/template_api_method.md`
|
||||||
|
- для документа типа `logic_block` использовать `.analysis/rules/legacy/template_logic_block.md`
|
||||||
|
|
||||||
|
Если для нужного типа шаблон отсутствует, агент должен:
|
||||||
|
|
||||||
|
1. использовать ближайший подходящий существующий шаблон как временную основу;
|
||||||
|
2. явно сохранить тип документа в `YAML frontmatter`;
|
||||||
|
3. не смешивать в одном документе несколько независимых сущностей.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Template: api_method
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
id: api-<stable-id>
|
||||||
|
title: <Human-readable title>
|
||||||
|
doc_type: api_method
|
||||||
|
status: draft
|
||||||
|
source_of_truth: code
|
||||||
|
domain: <domain-name>
|
||||||
|
owner: system-analyst
|
||||||
|
endpoint: <METHOD /path>
|
||||||
|
auth: <auth-mode-or-unknown>
|
||||||
|
idempotent: <true-or-false>
|
||||||
|
related_docs:
|
||||||
|
- <doc-id>
|
||||||
|
related_code:
|
||||||
|
- <path/to/file>
|
||||||
|
entities:
|
||||||
|
- <EntityName>
|
||||||
|
tags:
|
||||||
|
- api
|
||||||
|
---
|
||||||
|
|
||||||
|
# <API Method Title>
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Кратко опиши, какую системную задачу решает метод.
|
||||||
|
|
||||||
|
## Endpoint Summary
|
||||||
|
|
||||||
|
- Endpoint: `<METHOD /path>`
|
||||||
|
- Auth: `<auth-mode>`
|
||||||
|
- Idempotent: `<true/false>`
|
||||||
|
- Triggered by: `<ui/system/integration if known>`
|
||||||
|
|
||||||
|
## Technical Use Case
|
||||||
|
|
||||||
|
Опиши пошагово обработку запроса:
|
||||||
|
|
||||||
|
- вход в endpoint;
|
||||||
|
- ключевые проверки;
|
||||||
|
- вызовы логики;
|
||||||
|
- обращения к БД и внешним системам;
|
||||||
|
- формирование ответа.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
Вынеси сюда подтвержденные правила, которые дополняют основной сценарий:
|
||||||
|
|
||||||
|
- валидации;
|
||||||
|
- branching logic;
|
||||||
|
- побочные эффекты;
|
||||||
|
- ограничения по данным;
|
||||||
|
- условия ошибок.
|
||||||
|
|
||||||
|
## Request and Response Contract
|
||||||
|
|
||||||
|
Опиши контракт в кратком виде или дай ссылку на OpenAPI / контрактный файл.
|
||||||
|
|
||||||
|
## Related Logic Blocks
|
||||||
|
|
||||||
|
- [<Logic block title>](<path-or-doc-link>)
|
||||||
|
|
||||||
|
## Data Access and Integrations
|
||||||
|
|
||||||
|
- Reads DB: `<if known>`
|
||||||
|
- Writes DB: `<if known>`
|
||||||
|
- Integrates with: `<if known>`
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
Укажи только подтвержденные НФТ:
|
||||||
|
|
||||||
|
- timeout;
|
||||||
|
- audit;
|
||||||
|
- monitoring;
|
||||||
|
- security;
|
||||||
|
- idempotency rules.
|
||||||
|
|
||||||
|
## Related Code
|
||||||
|
|
||||||
|
- `<path/to/file>`
|
||||||
|
|
||||||
|
## Related Documents
|
||||||
|
|
||||||
|
- [<Related document>](<path-or-doc-link>)
|
||||||
|
```
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Template: logic_block
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
id: logic-<stable-id>
|
||||||
|
title: <Human-readable title>
|
||||||
|
doc_type: logic_block
|
||||||
|
status: draft
|
||||||
|
source_of_truth: code
|
||||||
|
domain: <domain-name>
|
||||||
|
owner: system-analyst
|
||||||
|
related_docs:
|
||||||
|
- <doc-id>
|
||||||
|
related_code:
|
||||||
|
- <path/to/file>
|
||||||
|
entities:
|
||||||
|
- <EntityName>
|
||||||
|
tags:
|
||||||
|
- logic
|
||||||
|
---
|
||||||
|
|
||||||
|
# <Logic Block Title>
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Кратко опиши, какую переиспользуемую или устойчивую логику реализует блок.
|
||||||
|
|
||||||
|
## Where Used
|
||||||
|
|
||||||
|
- Called from: `<ui/api/jobs/services if known>`
|
||||||
|
- Used by: `<list of known callers>`
|
||||||
|
|
||||||
|
## Technical Use Case
|
||||||
|
|
||||||
|
Опиши пошагово, как работает логический блок:
|
||||||
|
|
||||||
|
- входные данные;
|
||||||
|
- ключевые проверки;
|
||||||
|
- преобразования;
|
||||||
|
- обращения к данным;
|
||||||
|
- результат работы.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
Вынеси сюда устойчивые правила и ограничения:
|
||||||
|
|
||||||
|
- бизнес-правила;
|
||||||
|
- проверки;
|
||||||
|
- ветвления;
|
||||||
|
- ограничения на вход и выход;
|
||||||
|
- условия отказа.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Uses logic: `<other logic blocks if known>`
|
||||||
|
- Reads DB: `<if known>`
|
||||||
|
- Writes DB: `<if known>`
|
||||||
|
- Integrates with: `<if known>`
|
||||||
|
|
||||||
|
## Error Cases
|
||||||
|
|
||||||
|
Опиши значимые ошибки и условия их возникновения, если они подтверждены кодом.
|
||||||
|
|
||||||
|
## Related Code
|
||||||
|
|
||||||
|
- `<path/to/file>`
|
||||||
|
|
||||||
|
## Related Documents
|
||||||
|
|
||||||
|
- [<Related document>](<path-or-doc-link>)
|
||||||
|
```
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Template: ui_page
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
id: ui-<stable-id>
|
||||||
|
title: <Human-readable title>
|
||||||
|
doc_type: ui_page
|
||||||
|
status: draft
|
||||||
|
source_of_truth: code
|
||||||
|
domain: <domain-name>
|
||||||
|
owner: system-analyst
|
||||||
|
related_docs:
|
||||||
|
- <doc-id>
|
||||||
|
related_code:
|
||||||
|
- <path/to/file>
|
||||||
|
entities:
|
||||||
|
- <EntityName>
|
||||||
|
tags:
|
||||||
|
- ui
|
||||||
|
---
|
||||||
|
|
||||||
|
# <Page Title>
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Кратко опиши, какую пользовательскую задачу решает страница.
|
||||||
|
|
||||||
|
## Route and Entry Points
|
||||||
|
|
||||||
|
- Route: `<route-if-known>`
|
||||||
|
- Entry points: `<where user comes from>`
|
||||||
|
|
||||||
|
## Technical Use Case
|
||||||
|
|
||||||
|
Опиши пошаговый сценарий работы страницы как поток действий и системных реакций.
|
||||||
|
|
||||||
|
## UI Structure
|
||||||
|
|
||||||
|
Перечисли основные UI-элементы и для каждого укажи:
|
||||||
|
|
||||||
|
- назначение;
|
||||||
|
- источник данных;
|
||||||
|
- значение по умолчанию или placeholder;
|
||||||
|
- условия доступности или активации;
|
||||||
|
- поведение при взаимодействии;
|
||||||
|
- правила валидации.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
Вынеси сюда детальные правила, которые не стоит перегружать в use case:
|
||||||
|
|
||||||
|
- вызовы API;
|
||||||
|
- обработку ответов;
|
||||||
|
- локальные правила отображения;
|
||||||
|
- условия переходов;
|
||||||
|
- feature toggles.
|
||||||
|
|
||||||
|
## Related APIs
|
||||||
|
|
||||||
|
- [<API document title>](<path-or-doc-link>)
|
||||||
|
|
||||||
|
## Related Logic Blocks
|
||||||
|
|
||||||
|
- [<Logic block title>](<path-or-doc-link>)
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
Укажи НФТ, если они подтверждены:
|
||||||
|
|
||||||
|
- analytics events;
|
||||||
|
- observability;
|
||||||
|
- feature toggles;
|
||||||
|
- security constraints.
|
||||||
|
|
||||||
|
## Related Code
|
||||||
|
|
||||||
|
- `<path/to/file>`
|
||||||
|
|
||||||
|
## Related Documents
|
||||||
|
|
||||||
|
- [<Related document>](<path-or-doc-link>)
|
||||||
|
```
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- Purpose:
|
||||||
|
- Actor:
|
||||||
|
- Trigger:
|
||||||
|
- Endpoint:
|
||||||
|
- Main entities:
|
||||||
|
- Main logic:
|
||||||
|
- Main errors:
|
||||||
|
- Source of truth:
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
## Технический use case
|
||||||
|
|
||||||
|
### Основной сценарий
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Альтернативные ветки
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Функциональные требования
|
||||||
|
|
||||||
|
### Request validation
|
||||||
|
-
|
||||||
|
|
||||||
|
### Processing rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### State changes
|
||||||
|
-
|
||||||
|
|
||||||
|
### Side effects
|
||||||
|
-
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
- Method:
|
||||||
|
- Path:
|
||||||
|
- Auth:
|
||||||
|
- Idempotent:
|
||||||
|
- Timeout:
|
||||||
|
- Retry:
|
||||||
|
|
||||||
|
### Request
|
||||||
|
| Field | Type | Required | Constraints | Description |
|
||||||
|
|------|------|----------|-------------|-------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
### Response
|
||||||
|
| Field | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
### External contract refs
|
||||||
|
- OpenAPI:
|
||||||
|
- Schema:
|
||||||
|
- DTO / serializer:
|
||||||
|
- Additional refs:
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| error_id | http_code | when | client_behavior | retry |
|
||||||
|
|----------|-----------|------|-----------------|-------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## Нефункциональные требования
|
||||||
|
|
||||||
|
### Security
|
||||||
|
-
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
- Logs:
|
||||||
|
- Metrics:
|
||||||
|
- Traces:
|
||||||
|
- Audit:
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные блоки логики
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные сущности
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанный код
|
||||||
|
|
||||||
|
### Files
|
||||||
|
-
|
||||||
|
|
||||||
|
### Symbols
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
-
|
||||||
|
|
||||||
|
## История изменений
|
||||||
|
|
||||||
|
| Date | Source | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| | | |
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- Scope:
|
||||||
|
- Purpose:
|
||||||
|
- Main modules:
|
||||||
|
- Main domains:
|
||||||
|
- Main integrations:
|
||||||
|
- Key entrypoints:
|
||||||
|
- Key data flows:
|
||||||
|
- Source of truth:
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
## Границы системы
|
||||||
|
|
||||||
|
### In scope
|
||||||
|
-
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
-
|
||||||
|
|
||||||
|
## Архитектурная схема
|
||||||
|
|
||||||
|
## Основные модули
|
||||||
|
|
||||||
|
| module | responsibility | depends_on | key_code_refs |
|
||||||
|
|--------|----------------|------------|---------------|
|
||||||
|
| | | | |
|
||||||
|
|
||||||
|
## Основные доменные области
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Основные интеграции
|
||||||
|
|
||||||
|
| integration | direction | purpose | protocol / transport | related_docs |
|
||||||
|
|-------------|-----------|---------|----------------------|--------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## Основные потоки
|
||||||
|
|
||||||
|
### Flow 1
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Flow 2
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Архитектурные решения и ограничения
|
||||||
|
|
||||||
|
### Key decisions
|
||||||
|
-
|
||||||
|
|
||||||
|
### Constraints
|
||||||
|
-
|
||||||
|
|
||||||
|
### Risks
|
||||||
|
-
|
||||||
|
|
||||||
|
## Нефункциональные аспекты
|
||||||
|
|
||||||
|
### Security
|
||||||
|
-
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
-
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
- Logs:
|
||||||
|
- Metrics:
|
||||||
|
- Traces:
|
||||||
|
- Audit:
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
-
|
||||||
|
|
||||||
|
### Scalability
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные сущности
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанный код
|
||||||
|
|
||||||
|
### Files
|
||||||
|
-
|
||||||
|
|
||||||
|
### Symbols
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
-
|
||||||
|
|
||||||
|
## История изменений
|
||||||
|
|
||||||
|
| Date | Source | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| | | |
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- Domain:
|
||||||
|
- Purpose:
|
||||||
|
- Entity role:
|
||||||
|
- Main attributes:
|
||||||
|
- Lifecycle:
|
||||||
|
- Invariants:
|
||||||
|
- Related APIs:
|
||||||
|
- Related logic:
|
||||||
|
- Source of truth:
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
## Роль в доменной модели
|
||||||
|
|
||||||
|
## Атрибуты
|
||||||
|
|
||||||
|
| attribute | type | required | description | constraints |
|
||||||
|
|-----------|------|----------|-------------|-------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## Состояния и жизненный цикл
|
||||||
|
|
||||||
|
### Основные состояния
|
||||||
|
-
|
||||||
|
|
||||||
|
### Переходы состояний
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
## Инварианты и ограничения
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связи с другими сущностями
|
||||||
|
|
||||||
|
| entity | relation | description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Использование в системе
|
||||||
|
|
||||||
|
### Related API
|
||||||
|
-
|
||||||
|
|
||||||
|
### Related UI
|
||||||
|
-
|
||||||
|
|
||||||
|
### Related logic
|
||||||
|
-
|
||||||
|
|
||||||
|
### Related integrations
|
||||||
|
-
|
||||||
|
|
||||||
|
## Функциональные требования
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Нефункциональные требования
|
||||||
|
|
||||||
|
### Audit / history
|
||||||
|
-
|
||||||
|
|
||||||
|
### Security
|
||||||
|
-
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанный код
|
||||||
|
|
||||||
|
### Files
|
||||||
|
-
|
||||||
|
|
||||||
|
### Symbols
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
-
|
||||||
|
|
||||||
|
## История изменений
|
||||||
|
|
||||||
|
| Date | Source | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| | | |
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
```md
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- Purpose:
|
||||||
|
- Trigger:
|
||||||
|
- Inputs:
|
||||||
|
- Outputs:
|
||||||
|
- Main entities:
|
||||||
|
- Main dependencies:
|
||||||
|
- Side effects:
|
||||||
|
- Source of truth:
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
## Технический use case
|
||||||
|
|
||||||
|
### Основной сценарий
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Альтернативные ветки
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Функциональные требования
|
||||||
|
|
||||||
|
### Preconditions
|
||||||
|
-
|
||||||
|
|
||||||
|
### Processing rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### Validation rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### Output / result rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### Side effects
|
||||||
|
-
|
||||||
|
|
||||||
|
## Ограничения и условия вызова
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Нефункциональные требования
|
||||||
|
|
||||||
|
### Security
|
||||||
|
-
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
- Logs:
|
||||||
|
- Metrics:
|
||||||
|
- Traces:
|
||||||
|
- Audit:
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные API / UI / integration points
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные сущности
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанный код
|
||||||
|
|
||||||
|
### Files
|
||||||
|
-
|
||||||
|
|
||||||
|
### Symbols
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
-
|
||||||
|
|
||||||
|
## История изменений
|
||||||
|
|
||||||
|
| Date | Source | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| | | |
|
||||||
|
```
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- Purpose:
|
||||||
|
- Actor:
|
||||||
|
- Trigger:
|
||||||
|
- Route:
|
||||||
|
- Main API:
|
||||||
|
- Main entities:
|
||||||
|
- Main logic:
|
||||||
|
- Main states:
|
||||||
|
- Source of truth:
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
## Технический use case
|
||||||
|
|
||||||
|
### Основной сценарий
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Альтернативные ветки
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Описание UI
|
||||||
|
|
||||||
|
## UI Elements
|
||||||
|
|
||||||
|
| id | type | label | data_source | default / placeholder | validation | behavior |
|
||||||
|
|----|------|-------|-------------|------------------------|------------|----------|
|
||||||
|
| | | | | | | |
|
||||||
|
|
||||||
|
## Функциональные требования
|
||||||
|
|
||||||
|
### Input rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### State rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### Navigation rules
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client-side validation
|
||||||
|
-
|
||||||
|
|
||||||
|
## Нефункциональные требования
|
||||||
|
|
||||||
|
### Security
|
||||||
|
-
|
||||||
|
|
||||||
|
### Observability
|
||||||
|
- Logs:
|
||||||
|
- Metrics:
|
||||||
|
- Traces:
|
||||||
|
- Analytics:
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
-
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
-
|
||||||
|
|
||||||
|
### Feature toggles
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные API
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные блоки логики
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные сущности
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанный код
|
||||||
|
|
||||||
|
### Files
|
||||||
|
-
|
||||||
|
|
||||||
|
### Symbols
|
||||||
|
-
|
||||||
|
|
||||||
|
## Связанные документы
|
||||||
|
-
|
||||||
|
|
||||||
|
## История изменений
|
||||||
|
|
||||||
|
| Date | Source | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| | | |
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
При задачах на создание или обновление документации всегда:
|
||||||
|
1. Читай .analysis/rules/documentation-rules.md, .analysis/rules/frontmatter-rules.md и нужный шаблон из .analysis/rules/templates/.
|
||||||
|
2. Создавай и обновляй документы только в docs/documentation/.
|
||||||
|
3. Не создавай дублей: сначала ищи существующий документ, потом обновляй его.
|
||||||
|
4. Соблюдай принцип: один документ = одна сущность / один устойчивый аспект.
|
||||||
|
5. Каждый документ должен иметь YAML frontmatter, обязательные разделы Summary и Details и структуру по шаблону.
|
||||||
|
6. Все связи фиксируй явно: related_docs, related_code, entities, tags и typed-поля.
|
||||||
|
7. Используй только подтвержденный evidence из кода, контрактов, конфигов и существующей документации.
|
||||||
|
8. Не дублируй содержание между документами — используй ссылки.
|
||||||
|
9. Явно указывай связанный код и связанные документы.
|
||||||
|
10. Не выдумывай факты, если evidence недостаточно.
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
.env
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
|
||||||
|
# Runtime agent traces (local only; written by RequestTraceLogger)
|
||||||
|
runtime_traces/
|
||||||
|
|
||||||
|
# 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
|
||||||
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Agent Backend: Uvicorn (Debug)",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "uvicorn",
|
||||||
|
"args": [
|
||||||
|
"app.main:app",
|
||||||
|
"--host",
|
||||||
|
"0.0.0.0",
|
||||||
|
"--port",
|
||||||
|
"15000"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"envFile": "${workspaceFolder}/.env",
|
||||||
|
"env": {
|
||||||
|
"PYTHONPATH": "${workspaceFolder}/src"
|
||||||
|
},
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"**/__pycache__": true
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-2
@@ -3,12 +3,13 @@ FROM python:3.12-slim
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONPATH=/app/src
|
||||||
|
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY app ./app
|
COPY src ./src
|
||||||
|
|
||||||
EXPOSE 15000
|
EXPOSE 15000
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Запросы
|
||||||
|
1. Какие методы апи есть в проекте
|
||||||
|
2. Какие методы апи есть для healthcheck
|
||||||
|
3. Где документация на healthcheck
|
||||||
Binary file not shown.
@@ -0,0 +1,126 @@
|
|||||||
|
# Процессы работы с документацией (AS IS / TO BE)
|
||||||
|
|
||||||
|
|
||||||
|
## Основные артефакты системной аналитики
|
||||||
|
Системные аналитики работают с 3 артефактами:
|
||||||
|
|
||||||
|
- бизнес-требованиями
|
||||||
|
- системной аналитикой
|
||||||
|
- технической документацией
|
||||||
|
|
||||||
|
|
||||||
|
### Бизнес требования
|
||||||
|
Описывает бизнес и пользовательские требования, пользователькие use case, макеты экранов.
|
||||||
|
Сейчас не всегда оформляется как отдельный документ, часто этот шаг пропускается и требования фиксируются сразу в документе системной аналитики.
|
||||||
|
|
||||||
|
|
||||||
|
### Системная аналитика
|
||||||
|
Документ описыватет изменения в автоматизированной системе. Пишется системными аналитиками для разработчиков и тестировщиков. Так же этот документ проходит согласование с экспертами по архитектуре, безопасности, сопровождению.
|
||||||
|
|
||||||
|
Может описывать как целиком процесс (в случае реализации с нуля), так и инкремент, который вносит небольшие изменения в существующие процессы.
|
||||||
|
|
||||||
|
В данном документе содкржится вся информация по сути вносимых изменений, но отсутствует контекст о текущей реализации системы.
|
||||||
|
|
||||||
|
Состоит из разделов:
|
||||||
|
- Цели - короткое описание какую проблему и для кого решаем.
|
||||||
|
- Процесс AS IS и TO BE - фокус на изменения с точки зрения бизнес функций, без технической детализации.
|
||||||
|
- Ограничения - ограничения и допущения в реализации.
|
||||||
|
- Архитектура - описывает схему уровня контейнеров, основной фокус на интеграции между контейнерами и интеграционные сценарии.
|
||||||
|
- Функциональные требования - описывают изменения в системе.
|
||||||
|
- Нефункциональные требования - требования к аудиту, мониторингу, фичетоглам, пользовтелькой аналитике.
|
||||||
|
|
||||||
|
|
||||||
|
### Техническая документация
|
||||||
|
Техническая документация описывает реализацию системы. Эта информация используется командой разработки при проектировании и реализации новых фичей, понимании как работает система. Артефакт живет чуть впереди кода
|
||||||
|
Представялет из себя иерархическую модель документов, сейчас реализованную в конфлюенсе.
|
||||||
|
|
||||||
|
Есть несколько типов страниц, каждая из которы описывает определенный тип функциональности
|
||||||
|
|
||||||
|
- UI страницы
|
||||||
|
- API методы
|
||||||
|
- БД
|
||||||
|
- Логические блоки
|
||||||
|
|
||||||
|
#### UI страницы
|
||||||
|
Описывают экран на UI.
|
||||||
|
**Декомпозиция**
|
||||||
|
Как правило на страницу с описанием выносится целый макет/страница фронтального приложения, с одной основной интеграцией и опционально вспомогательными интеграциями.
|
||||||
|
Например - форма создания сущности. Есть вспомогательгные методы для полученяи правочников, использующихся при заполнении полей на форме, и вызов оснвного метода создания сущности.
|
||||||
|
|
||||||
|
Таким образом приложение декомпозируется на отдельные экраны, коотры свызываются между собой последовательно, но сами по себе являются независимыми
|
||||||
|
|
||||||
|
**Состав описания**
|
||||||
|
Все разделы обязательны.
|
||||||
|
Страница с описанием содержит:
|
||||||
|
- Краткое описание
|
||||||
|
- Технический use case
|
||||||
|
- Описание макета с декомпозицией на компоненты + их поведение
|
||||||
|
- Функциональные требования - описание интеграций и логики, специфичной для этой формы UI
|
||||||
|
- Нефункциональные требования - фичетоглы и события пользовательской аналитики
|
||||||
|
|
||||||
|
|
||||||
|
#### API методы
|
||||||
|
**Декомпозиция**
|
||||||
|
На каждый метод API заводится отдельная страница.
|
||||||
|
**Состав описания**
|
||||||
|
Все разделы обязательны.
|
||||||
|
Страница с описанием содержит:
|
||||||
|
- Краткое описание
|
||||||
|
- Технический use case
|
||||||
|
- Функциональные требования - описание интеграций и логики, специфичной для этой формы UI
|
||||||
|
- Нефункциональные требования - фичетоглы и события пользовательской аналитики
|
||||||
|
- Контракт метода - описание запроса и ответа. Для ответа так же приводится описание как заполнять поля.
|
||||||
|
|
||||||
|
#### БД
|
||||||
|
**Декомпозиция**
|
||||||
|
Сейсас это только странциа с описанием таблица. На каждую таблицу заводится отдельная страница.
|
||||||
|
**Состав описания**
|
||||||
|
Все разделы обязательны.
|
||||||
|
Страница с описанием содержит:
|
||||||
|
- Краткое описание
|
||||||
|
- Таблица с офисанием физической модели данных
|
||||||
|
|
||||||
|
#### Логические блоки
|
||||||
|
**Декомпозиция**
|
||||||
|
На отдельную страницу может быть вынесен общий переиспользуемый блок логики. Это позволяет не дублировать его на страницах документации. Как правило соответствует реализации общего компонента в коде.
|
||||||
|
**Состав описания**
|
||||||
|
Часть разделов в описании может отсутствовать.
|
||||||
|
- Краткое описание
|
||||||
|
- Технический use case
|
||||||
|
- Функциональные требования - описание интеграций и логики, специфичной для этой формы UI
|
||||||
|
- Нефункциональные требования - фичетоглы и события пользовательской аналитики
|
||||||
|
|
||||||
|
#### Прочие особенности процесса
|
||||||
|
##### Описание технических use cases
|
||||||
|
Сценарий описывает основные шаги процесса в разрезе участников, все технические детали, если их нельзя описать одним предложением, выносятся в разделы функциональных требований, нефункциональных требований, или даются ссылки на другие страницы (как правило это страницы с логическими блоками).
|
||||||
|
|
||||||
|
В технических use cases приводятся ссылки на страницы с описнаием вызываемых методов API. Особенно это актуально для страниц фронта, т.к. он использует наши методы API, которые есть в документации. Для интеграций с другими АС как правило приводистя ссылка на описание конфлюенса.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## AS IS
|
||||||
|
Сейчас все артефакты ведутся в конфлюенс. Одна страница содержит описанием одного аретфакта (бизнес требования, системная аналитика, страница документации), страницы организованы иерархически, используюстя ссылки для обозначения связей.
|
||||||
|
|
||||||
|
Проблемы:
|
||||||
|
- документация со временем теряет актуальность
|
||||||
|
- отсутствие автоматизации
|
||||||
|
- ручное ведение
|
||||||
|
---
|
||||||
|
|
||||||
|
## TO BE
|
||||||
|
|
||||||
|
Целевое состояние:
|
||||||
|
- аналитик продолжает писать артефакты бизнес-требований и системной аналитики
|
||||||
|
- агент генерирует и обновляет документацию по странице системной аналитики
|
||||||
|
- документация становится инженерным артефактом, который ведется в GIT
|
||||||
|
|
||||||
|
### Форматы
|
||||||
|
- Markdown
|
||||||
|
- OpenAPI
|
||||||
|
- Mermaid / PlantUML
|
||||||
|
|
||||||
|
### Роль агента
|
||||||
|
- использование документации как базы знаний - как для ответов на вопросы, так и для проектирования изменений в системе.
|
||||||
|
- внесение изменений в документацию по артефактам системной аналитики
|
||||||
|
- генерация из документации спецификаций OPENAPI и JSON-schema
|
||||||
|
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
Ниже обновленная версия с учетом гибридной модели интент роутера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Концепция агента
|
||||||
|
|
||||||
|
Агент проектируется как intent-driven система для работы с кодом и документацией, где пользовательский запрос сначала нормализуется и интерпретируется, затем по нему извлекается релевантный контекст из многослойного RAG, после чего специализированный task workflow выполняет целевую задачу. Агент не является единым “умным чатом”: логика разделена на маршрутизацию, retrieval и специализированные execution workflows. Проверка evidence, вызовы LLM и правила сборки ответа находятся внутри task workflows и зависят от типа задачи.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Компонентная модель
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
IDE[IDE Plugin / Client] --> API[API Layer]
|
||||||
|
API --> IR[IntentRouter V3]
|
||||||
|
IR --> RAG[Retrieval RAG]
|
||||||
|
|
||||||
|
RAG --> TW1[Task Workflow: Documentation Explain]
|
||||||
|
RAG --> TW2[Task Workflow: OpenAPI Generation]
|
||||||
|
RAG --> TW3[Task Workflow: Documentation Generation]
|
||||||
|
RAG --> TWN[Other Specialized Task Workflows]
|
||||||
|
|
||||||
|
TW1 --> OUT[Response / Artifact]
|
||||||
|
TW2 --> OUT
|
||||||
|
TW3 --> OUT
|
||||||
|
TWN --> OUT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Основной flow процесса
|
||||||
|
|
||||||
|
### Основной процесс
|
||||||
|
|
||||||
|
1. Пользователь отправляет запрос через IDE plugin или другой клиент.
|
||||||
|
2. `API Layer` принимает запрос и передает его в агент.
|
||||||
|
3. `IntentRouter V3`:
|
||||||
|
|
||||||
|
* нормализует запрос;
|
||||||
|
* детерминированно извлекает ключевые артефакты;
|
||||||
|
* с помощью LLM определяет тип задачи и параметры обработки;
|
||||||
|
* формирует параметры retrieval.
|
||||||
|
4. Выполняется извлечение данных из `Retrieval RAG`.
|
||||||
|
5. Извлеченный контекст передается в соответствующий `Task Workflow`.
|
||||||
|
6. Внутри workflow выполняется:
|
||||||
|
|
||||||
|
* подготовка контекста;
|
||||||
|
* evidence-проверки;
|
||||||
|
* вызовы LLM;
|
||||||
|
* формирование результата.
|
||||||
|
7. Результат возвращается пользователю.
|
||||||
|
|
||||||
|
### Sequence diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User as User / IDE Plugin
|
||||||
|
participant API as API Layer
|
||||||
|
participant Router as IntentRouter V3
|
||||||
|
participant RAG as Retrieval RAG
|
||||||
|
participant WF as Task Workflow
|
||||||
|
|
||||||
|
User->>API: request
|
||||||
|
API->>Router: agent call
|
||||||
|
Router->>Router: normalize + extract artifacts
|
||||||
|
Router->>Router: LLM routing (task / intent)
|
||||||
|
Router->>RAG: retrieval request
|
||||||
|
RAG-->>Router: retrieved context
|
||||||
|
Router->>WF: route result + context
|
||||||
|
WF->>WF: evidence logic + LLM calls
|
||||||
|
WF-->>API: final result
|
||||||
|
API-->>User: response
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Описание компонентов
|
||||||
|
|
||||||
|
### 4.1. IDE Plugin / Client
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Точка входа пользователя в агент.
|
||||||
|
|
||||||
|
**Как устроен**
|
||||||
|
Любой внешний клиент (IDE plugin, web UI и др.), который отправляет запрос и получает результат.
|
||||||
|
|
||||||
|
**Почему так**
|
||||||
|
Агент изначально проектируется как backend-система, независимая от интерфейса.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2. API Layer
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Обеспечивает внешний интерфейс взаимодействия с агентом.
|
||||||
|
|
||||||
|
**Как устроен**
|
||||||
|
Принимает запрос, валидирует его и передает во внутренний pipeline, затем возвращает результат.
|
||||||
|
|
||||||
|
**Почему так**
|
||||||
|
Позволяет изолировать транспортный слой от логики агента.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.3. IntentRouter V3
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Определяет, как должен обрабатываться пользовательский запрос и какой сценарий выполнения применить.
|
||||||
|
|
||||||
|
**Как устроен**
|
||||||
|
|
||||||
|
Гибридная модель из двух частей:
|
||||||
|
|
||||||
|
#### 1. Детерминированный слой
|
||||||
|
|
||||||
|
Выполняет:
|
||||||
|
|
||||||
|
* нормализацию запроса;
|
||||||
|
* извлечение ключевых артефактов:
|
||||||
|
|
||||||
|
* домены;
|
||||||
|
* типы сущностей (API, entity, component и т.д.);
|
||||||
|
* явные ссылки (endpoint, путь, имя);
|
||||||
|
* выделение базовых сигналов (например: explain / list / generate).
|
||||||
|
|
||||||
|
Этот слой задает **жесткие рамки интерпретации запроса**.
|
||||||
|
|
||||||
|
#### 2. LLM-роутинг
|
||||||
|
|
||||||
|
Использует:
|
||||||
|
|
||||||
|
* нормализованный запрос;
|
||||||
|
* извлеченные артефакты;
|
||||||
|
* описание доступных типов задач;
|
||||||
|
|
||||||
|
и определяет:
|
||||||
|
|
||||||
|
* тип задачи;
|
||||||
|
* общий сценарий обработки;
|
||||||
|
* параметры retrieval;
|
||||||
|
* ожидаемую форму ответа.
|
||||||
|
|
||||||
|
#### Итог
|
||||||
|
|
||||||
|
Router формирует:
|
||||||
|
|
||||||
|
* параметры retrieval;
|
||||||
|
* тип task workflow;
|
||||||
|
* контекст для дальнейшего выполнения.
|
||||||
|
|
||||||
|
**Почему решение такое**
|
||||||
|
|
||||||
|
Ранее использовался более детерминированный подход с фиксированными сценариями, который хорошо работал в узком наборе задач, но плохо масштабируется. Полностью LLM-based роутинг, наоборот, дает гибкость, но теряет предсказуемость и управляемость.
|
||||||
|
|
||||||
|
Поэтому выбран гибридный подход:
|
||||||
|
|
||||||
|
* детерминированный слой фиксирует ключевые артефакты и ограничения;
|
||||||
|
* LLM выполняет гибкую интерпретацию задачи.
|
||||||
|
|
||||||
|
Это позволяет:
|
||||||
|
|
||||||
|
* сохранить управляемость и стабильность;
|
||||||
|
* избежать взрывного роста количества сценариев;
|
||||||
|
* поддерживать сложные и нетиповые запросы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.4. Retrieval RAG
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Извлечь релевантный контекст для выполнения задачи.
|
||||||
|
|
||||||
|
**Как устроен**
|
||||||
|
Многослойная система хранения знаний (код, документация, факты, связи), из которой извлекается структурированный контекст в зависимости от параметров, заданных роутером.
|
||||||
|
|
||||||
|
**Почему так**
|
||||||
|
Разные задачи требуют разных типов данных, поэтому используется слойная модель вместо плоского поиска.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.5. Task Workflows
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Реализуют прикладную логику выполнения конкретного типа задачи.
|
||||||
|
|
||||||
|
**Как устроены**
|
||||||
|
Набор специализированных workflows, например:
|
||||||
|
|
||||||
|
* объяснение по документации;
|
||||||
|
* генерация OpenAPI;
|
||||||
|
* генерация документации;
|
||||||
|
* другие сценарии.
|
||||||
|
|
||||||
|
Внутри workflow находятся:
|
||||||
|
|
||||||
|
* обработка контекста;
|
||||||
|
* evidence-проверки;
|
||||||
|
* вызовы LLM;
|
||||||
|
* сборка результата.
|
||||||
|
|
||||||
|
**Почему так**
|
||||||
|
Логика проверки данных и генерации сильно зависит от задачи, поэтому она инкапсулируется в отдельных workflows, а не в одном универсальном слое.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.6. Output / Artifact
|
||||||
|
|
||||||
|
**Задача**
|
||||||
|
Вернуть результат пользователю.
|
||||||
|
|
||||||
|
**Как устроен**
|
||||||
|
Может быть:
|
||||||
|
|
||||||
|
* текстовый ответ;
|
||||||
|
* структурированный список;
|
||||||
|
* OpenAPI спецификация;
|
||||||
|
* документация;
|
||||||
|
* иной артефакт.
|
||||||
|
|
||||||
|
**Почему так**
|
||||||
|
Агент должен поддерживать не только ответы, но и генерацию инженерных артефактов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
Обновленная архитектура строится на следующем принципе:
|
||||||
|
|
||||||
|
* **детерминированное извлечение ключевых артефактов** задает рамки;
|
||||||
|
* **LLM выполняет гибкий роутинг внутри этих рамок**;
|
||||||
|
* **retrieval обеспечивает данные**;
|
||||||
|
* **task workflows реализуют прикладную логику и контроль качества**.
|
||||||
|
|
||||||
|
Это позволяет одновременно сохранить управляемость системы и обеспечить масштабируемость под новые типы задач.
|
||||||
@@ -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` - создание документации из системной аналитики на фичу
|
||||||
@@ -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,212 @@
|
|||||||
|
# Системная аналитика
|
||||||
|
|
||||||
|
## Общее описание
|
||||||
|
|
||||||
|
Документ описывает изменения в автоматизированной системе. Пишется системными аналитиками для разработчиков и тестировщиков и проходит согласование с экспертами по архитектуре, безопасности и сопровождению.
|
||||||
|
|
||||||
|
Документ может описывать как новый процесс, так и инкремент доработки существующей функциональности.
|
||||||
|
|
||||||
|
## Требования к заголовкам
|
||||||
|
|
||||||
|
- Заголовок должен отражать суть раздела.
|
||||||
|
- Заголовок не должен содержать лишнюю информацию, которая относится к метаданным (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`
|
||||||
|
|
||||||
|
Дополнительные метаданные для случаев изменения существующей документации:
|
||||||
|
|
||||||
|
- `action`
|
||||||
|
- `target_doc_id`
|
||||||
|
- `target_path`
|
||||||
|
|
||||||
|
#### 6.x для `ui_page`
|
||||||
|
|
||||||
|
Обязательная структура:
|
||||||
|
|
||||||
|
- `### Технический use case (тезисно)`
|
||||||
|
- `### Требования к UI`
|
||||||
|
- `### Функциональные требования`
|
||||||
|
- `### Нефункциональные требования`
|
||||||
|
|
||||||
|
Требования к разделу `### Требования к UI`:
|
||||||
|
|
||||||
|
- Внутри нужно отдельно описывать каждую UI-форму.
|
||||||
|
- Если есть интеграция, обязательно описать, как показывается ошибка.
|
||||||
|
- Если показываем список, обязательно описать, как показывается отсутствие данных.
|
||||||
|
|
||||||
|
Рекомендуемая детализация UI-форм:
|
||||||
|
|
||||||
|
- табличное представление,
|
||||||
|
- пустой список (empty state),
|
||||||
|
- ошибка (error state).
|
||||||
|
|
||||||
|
Правила описания UI-полей:
|
||||||
|
|
||||||
|
- Поля описывать списком (не таблицей).
|
||||||
|
- Общие правила (например, read-only, поведение при пустом значении) выносить в общий блок, не дублировать для каждого поля.
|
||||||
|
|
||||||
|
Отдельно нужно различать два сценария описания:
|
||||||
|
|
||||||
|
1. Если описывается новая UI-страница или новая самостоятельная UI-форма, раздел оформляется полноценно по шаблону `ui_page`.
|
||||||
|
- Нужно дать достаточный контекст для разработки и тестирования.
|
||||||
|
- Нужно подробно описывать структуру формы, состояния отображения, поведение полей, ошибки, empty state и пользовательские действия.
|
||||||
|
|
||||||
|
2. Если описывается доработка уже существующей страницы или существующей UI-формы, не нужно повторно копировать полное описание из действующей документации.
|
||||||
|
- Нужно учитывать уже существующее описание страницы в документации и аналитике.
|
||||||
|
- В аналитике нужно явно указать, что именно меняется в существующем сценарии: что добавляется, редактируется или удаляется.
|
||||||
|
- Нужно указывать точку изменения: в какой существующей странице, форме, блоке или сценарии вносится изменение.
|
||||||
|
- Нужно ссылаться на существующий документ или раздел, где базовое поведение уже описано.
|
||||||
|
- Нужно описывать только delta изменений, достаточную для реализации доработки и актуализации документации.
|
||||||
|
- Полное описание существующей страницы в таком разделе не дублируется.
|
||||||
|
- Для такой доработки в metadata нужно явно указывать `action: update`.
|
||||||
|
- Если изменение должно попасть в уже существующий markdown-документ, нужно явно указывать `target_doc_id` и/или `target_path`.
|
||||||
|
- `target_doc_id` должен совпадать с `id` существующего документа, который требуется обновить.
|
||||||
|
- Если `target_doc_id`/`target_path` не указаны, агент может ошибочно интерпретировать раздел как создание нового документа.
|
||||||
|
|
||||||
|
Нефункциональные требования для `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 (тезисно)`
|
||||||
|
- `### Функциональные требования`
|
||||||
|
- `### Нефункциональные требования`
|
||||||
|
|
||||||
|
`logic_block` удобно использовать для фиксации точечных изменений существующего сценария, если раздел не описывает новую самостоятельную страницу или новую самостоятельную форму, а только уточняет delta к уже существующей документации.
|
||||||
|
|
||||||
|
Если точечное изменение должно изменить существующий документ другого типа, `logic_block` для этого использовать нельзя. В этом случае metadata раздела должна указывать тип и идентификатор целевого существующего документа, который требуется обновить.
|
||||||
|
|
||||||
|
## Дополнительные правила по слоям
|
||||||
|
|
||||||
|
- Проверка ролевой модели пользователя обычно выполняется на уровне `ufs`.
|
||||||
|
- Для `pprb` аудит может не фиксироваться, если это правило принято для конкретной фичи/домена.
|
||||||
|
- Если проверка ролей вынесена в `ufs`, не дублировать этот шаг в сценарии `pprb`.
|
||||||
|
|
||||||
|
## Термины
|
||||||
|
|
||||||
|
- Аудит: события, которые фиксируют действия пользователя и позволяют ответить на вопрос «кто, что, когда сделал».
|
||||||
|
- Мониторинг: технические события/метрики для контроля стабильности и поиска сбоев.
|
||||||
@@ -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`.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
Нужно реализовать 2 вещи
|
||||||
|
|
||||||
|
Создать процесс внесения изменений в файл документации
|
||||||
|
Создать контекст этого процесса
|
||||||
|
|
||||||
|
Контекст наполнять атрибутами
|
||||||
|
что-то явно задано, фоллбэк через ллм
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Написать тестовую аналитику - круд над сущностью
|
||||||
|
фронт, ефс, ппрб
|
||||||
|
Все в своей БД
|
||||||
|
Атрибуты сущности задать в требованиях
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Аналитика имеет структуру
|
||||||
|
Внутри модули - один модуль на правку одного файла.
|
||||||
|
|
||||||
|
|
||||||
|
Модуль извлекается из аналитики парсером и из него формируется задача на редактирование файла
|
||||||
|
если парсер не сработал - фоллбэк ан ллм
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Процесс редактирования работает стандартно
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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.
|
||||||
@@ -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,28 @@
|
|||||||
|
# API Contract Rules
|
||||||
|
|
||||||
|
Этот rule описывает только тело секции `### Контракт`.
|
||||||
|
|
||||||
|
## Обязательные части
|
||||||
|
- request parameters (`header/query/path`)
|
||||||
|
- request body (если применимо)
|
||||||
|
- response body
|
||||||
|
- errors
|
||||||
|
- auth
|
||||||
|
- timeout
|
||||||
|
- retry/idempotency (если применимо)
|
||||||
|
|
||||||
|
## Правила заголовков внутри тела секции
|
||||||
|
- Не повторять заголовок `Контракт`.
|
||||||
|
- Запрещено выводить `## Контракт` и `### Контракт` внутри тела секции.
|
||||||
|
- Если нужны подзаголовки, использовать только уровень ниже родительской секции: `#### Запрос`, `#### Ответ`, `#### Ошибки`, `#### 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,34 @@
|
|||||||
|
# Functional Requirements Rules
|
||||||
|
|
||||||
|
Этот rule описывает только тело секции `### Функциональные требования`.
|
||||||
|
|
||||||
|
## Формат
|
||||||
|
- `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,19 @@
|
|||||||
|
# Tech Use Case Rules
|
||||||
|
|
||||||
|
Этот rule описывает только тело секции `### Технический use case`.
|
||||||
|
|
||||||
|
## Обязательные части
|
||||||
|
- название
|
||||||
|
- предусловия
|
||||||
|
- триггер
|
||||||
|
- основной сценарий
|
||||||
|
- альтернативный сценарий
|
||||||
|
- обработка ошибок
|
||||||
|
- постусловие
|
||||||
|
|
||||||
|
## Правила шага
|
||||||
|
- Один шаг = одно предложение до 15-20 слов.
|
||||||
|
- Формат шага: смысловое действие + техническая реализация (endpoint/топик/операция).
|
||||||
|
- Длинные технические детали выносить в FR и ссылаться на FR из шага.
|
||||||
|
- Для интеграционных шагов описание обработки ошибок обязательно.
|
||||||
|
- Запрещено повторять заголовок `### Технический use case` внутри тела секции.
|
||||||
@@ -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,56 @@
|
|||||||
|
# Documentation Rules V3
|
||||||
|
|
||||||
|
## 1. Общий контракт
|
||||||
|
- Документация строится на основе системной аналитики, но на более детальном уровне.
|
||||||
|
- Заголовки отражают только суть раздела; метаданные в заголовках запрещены.
|
||||||
|
- Метаданные указываются во frontmatter и/или отдельными строками в body.
|
||||||
|
- Структура документа определяется только template соответствующего типа.
|
||||||
|
- Правила написания конкретного раздела определяются только соответствующим `common-elements` файлом.
|
||||||
|
- Manifest типа документа хранится во frontmatter соответствующего template.
|
||||||
|
- Генератор секции всегда пишет только тело секции, а не сам заголовок секции.
|
||||||
|
- Дублирование заголовков запрещено: нельзя повторно выводить заголовок текущей секции внутри ее тела.
|
||||||
|
- Если 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. Специальные инварианты для `api_method`
|
||||||
|
- Во frontmatter обязательно должно присутствовать поле `endpoint`.
|
||||||
|
- Внутри `## Details` секция `### Контракт` должна присутствовать ровно один раз.
|
||||||
|
- Внутри тела секции `### Контракт` запрещено повторять заголовки `## Контракт` и `### Контракт`.
|
||||||
|
- Внутри `### Технический use case` запрещено повторять заголовок `### Технический use case`.
|
||||||
|
- Внутри `### Функциональные требования` запрещено повторять заголовок `### Функциональные требования`.
|
||||||
|
|
||||||
|
## 7. Формат 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`.
|
||||||
@@ -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,37 @@
|
|||||||
|
# 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: []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Дополнительные обязательные поля по типам документов
|
||||||
|
- Для `doc_type: api_method` поле `endpoint` обязательно.
|
||||||
|
- Значение `endpoint` должно содержать HTTP-метод и путь, например: `GET /orders/{orderId}`.
|
||||||
|
- Если в аналитике endpoint указан в заголовке раздела, use case, контракте или интеграционной схеме, его нужно перенести во frontmatter и не опускать.
|
||||||
|
|
||||||
|
## 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`
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-38
@@ -1,38 +0,0 @@
|
|||||||
from fastapi import FastAPI
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
|
|
||||||
from app.core.error_handlers import register_error_handlers
|
|
||||||
from app.modules.application import ModularApplication
|
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> FastAPI:
|
|
||||||
app = FastAPI(title="Agent Backend MVP", version="0.1.0")
|
|
||||||
modules = ModularApplication()
|
|
||||||
app.state.modules = modules
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=False,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
app.include_router(modules.chat.public_router())
|
|
||||||
app.include_router(modules.rag.public_router())
|
|
||||||
app.include_router(modules.rag.internal_router())
|
|
||||||
app.include_router(modules.agent.internal_router())
|
|
||||||
|
|
||||||
register_error_handlers(app)
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
|
||||||
async def startup() -> None:
|
|
||||||
modules.startup()
|
|
||||||
|
|
||||||
@app.get("/health")
|
|
||||||
async def health() -> dict:
|
|
||||||
return {"status": "ok"}
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
app = create_app()
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,20 +0,0 @@
|
|||||||
from app.core.constants import SUPPORTED_SCHEMA_VERSION
|
|
||||||
from app.core.exceptions import AppError
|
|
||||||
from app.schemas.changeset import ChangeItem, ChangeSetPayload
|
|
||||||
from app.schemas.common import ModuleName
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeSetValidator:
|
|
||||||
def validate(self, task_id: str, changeset: list[ChangeItem]) -> list[ChangeItem]:
|
|
||||||
payload = ChangeSetPayload(
|
|
||||||
schema_version=SUPPORTED_SCHEMA_VERSION,
|
|
||||||
task_id=task_id,
|
|
||||||
changeset=changeset,
|
|
||||||
)
|
|
||||||
if payload.schema_version != SUPPORTED_SCHEMA_VERSION:
|
|
||||||
raise AppError(
|
|
||||||
"unsupported_schema",
|
|
||||||
f"Unsupported schema version: {payload.schema_version}",
|
|
||||||
ModuleName.AGENT,
|
|
||||||
)
|
|
||||||
return payload.changeset
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from datetime import datetime, timezone
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from app.core.exceptions import AppError
|
|
||||||
from app.schemas.common import ModuleName
|
|
||||||
|
|
||||||
|
|
||||||
class ConfluenceService:
|
|
||||||
async def fetch_page(self, url: str) -> dict:
|
|
||||||
parsed = urlparse(url)
|
|
||||||
if not parsed.scheme.startswith("http"):
|
|
||||||
raise AppError("invalid_url", "Invalid Confluence URL", ModuleName.CONFLUENCE)
|
|
||||||
return {
|
|
||||||
"page_id": str(uuid4()),
|
|
||||||
"title": "Confluence page",
|
|
||||||
"content_markdown": f"Fetched content from {url}",
|
|
||||||
"version": 1,
|
|
||||||
"fetched_at": datetime.now(timezone.utc).isoformat(),
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
from app.modules.agent.engine.graphs.base_graph import BaseGraphFactory
|
|
||||||
from app.modules.agent.engine.graphs.docs_graph import DocsGraphFactory
|
|
||||||
from app.modules.agent.engine.graphs.project_edits_graph import ProjectEditsGraphFactory
|
|
||||||
from app.modules.agent.engine.graphs.project_qa_graph import ProjectQaGraphFactory
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"BaseGraphFactory",
|
|
||||||
"DocsGraphFactory",
|
|
||||||
"ProjectEditsGraphFactory",
|
|
||||||
"ProjectQaGraphFactory",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,58 +0,0 @@
|
|||||||
from langgraph.graph import END, START, StateGraph
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.progress import emit_progress_sync
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
|
|
||||||
|
|
||||||
class BaseGraphFactory:
|
|
||||||
def __init__(self, llm: AgentLlmService) -> None:
|
|
||||||
self._llm = llm
|
|
||||||
|
|
||||||
def build(self, checkpointer=None):
|
|
||||||
graph = StateGraph(AgentGraphState)
|
|
||||||
graph.add_node("context", self._context_node)
|
|
||||||
graph.add_node("answer", self._answer_node)
|
|
||||||
graph.add_edge(START, "context")
|
|
||||||
graph.add_edge("context", "answer")
|
|
||||||
graph.add_edge("answer", END)
|
|
||||||
return graph.compile(checkpointer=checkpointer)
|
|
||||||
|
|
||||||
def _context_node(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.default.context",
|
|
||||||
message="Готовлю контекст ответа по данным запроса.",
|
|
||||||
)
|
|
||||||
rag = state.get("rag_context", "")
|
|
||||||
conf = state.get("confluence_context", "")
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.default.context.done",
|
|
||||||
message="Контекст собран, перехожу к формированию ответа.",
|
|
||||||
)
|
|
||||||
return {"rag_context": rag, "confluence_context": conf}
|
|
||||||
|
|
||||||
def _answer_node(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.default.answer",
|
|
||||||
message="Формирую текст ответа для пользователя.",
|
|
||||||
)
|
|
||||||
msg = state.get("message", "")
|
|
||||||
rag = state.get("rag_context", "")
|
|
||||||
conf = state.get("confluence_context", "")
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"User request:\n{msg}",
|
|
||||||
f"RAG context:\n{rag}",
|
|
||||||
f"Confluence context:\n{conf}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
answer = self._llm.generate("general_answer", user_input)
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.default.answer.done",
|
|
||||||
message="Черновик ответа подготовлен.",
|
|
||||||
)
|
|
||||||
return {"answer": answer}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class DocsExamplesLoader:
|
|
||||||
def __init__(self, prompts_dir: Path | None = None) -> None:
|
|
||||||
base = prompts_dir or Path(__file__).resolve().parents[2] / "prompts"
|
|
||||||
env_override = os.getenv("AGENT_PROMPTS_DIR", "").strip()
|
|
||||||
root = Path(env_override) if env_override else base
|
|
||||||
self._examples_dir = root / "docs_examples"
|
|
||||||
|
|
||||||
def load_bundle(self, *, max_files: int = 6, max_chars_per_file: int = 1800) -> str:
|
|
||||||
if not self._examples_dir.is_dir():
|
|
||||||
return ""
|
|
||||||
files = sorted(
|
|
||||||
[p for p in self._examples_dir.iterdir() if p.is_file() and p.suffix.lower() in {".md", ".txt"}],
|
|
||||||
key=lambda p: p.name.lower(),
|
|
||||||
)[:max_files]
|
|
||||||
chunks: list[str] = []
|
|
||||||
for path in files:
|
|
||||||
content = path.read_text(encoding="utf-8", errors="ignore").strip()
|
|
||||||
if not content:
|
|
||||||
continue
|
|
||||||
excerpt = content[:max_chars_per_file].strip()
|
|
||||||
chunks.append(f"### Example: {path.name}\n{excerpt}")
|
|
||||||
return "\n\n".join(chunks).strip()
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
from langgraph.graph import END, START, StateGraph
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.file_targeting import FileTargeting
|
|
||||||
from app.modules.agent.engine.graphs.docs_graph_logic import DocsContentComposer, DocsContextAnalyzer
|
|
||||||
from app.modules.agent.engine.graphs.progress import emit_progress_sync
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DocsGraphFactory:
|
|
||||||
_max_validation_attempts = 2
|
|
||||||
|
|
||||||
def __init__(self, llm: AgentLlmService) -> None:
|
|
||||||
self._targeting = FileTargeting()
|
|
||||||
self._analyzer = DocsContextAnalyzer(llm, self._targeting)
|
|
||||||
self._composer = DocsContentComposer(llm, self._targeting)
|
|
||||||
|
|
||||||
def build(self, checkpointer=None):
|
|
||||||
graph = StateGraph(AgentGraphState)
|
|
||||||
graph.add_node("collect_code_context", self._collect_code_context)
|
|
||||||
graph.add_node("detect_existing_docs", self._detect_existing_docs)
|
|
||||||
graph.add_node("decide_strategy", self._decide_strategy)
|
|
||||||
graph.add_node("load_rules_and_examples", self._load_rules_and_examples)
|
|
||||||
graph.add_node("plan_incremental_changes", self._plan_incremental_changes)
|
|
||||||
graph.add_node("plan_new_document", self._plan_new_document)
|
|
||||||
graph.add_node("generate_doc_content", self._generate_doc_content)
|
|
||||||
graph.add_node("self_check", self._self_check)
|
|
||||||
graph.add_node("build_changeset", self._build_changeset)
|
|
||||||
graph.add_node("summarize_result", self._summarize_result)
|
|
||||||
|
|
||||||
graph.add_edge(START, "collect_code_context")
|
|
||||||
graph.add_edge("collect_code_context", "detect_existing_docs")
|
|
||||||
graph.add_edge("detect_existing_docs", "decide_strategy")
|
|
||||||
graph.add_edge("decide_strategy", "load_rules_and_examples")
|
|
||||||
graph.add_conditional_edges(
|
|
||||||
"load_rules_and_examples",
|
|
||||||
self._route_after_rules_loading,
|
|
||||||
{
|
|
||||||
"incremental": "plan_incremental_changes",
|
|
||||||
"from_scratch": "plan_new_document",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
graph.add_edge("plan_incremental_changes", "generate_doc_content")
|
|
||||||
graph.add_edge("plan_new_document", "generate_doc_content")
|
|
||||||
graph.add_edge("generate_doc_content", "self_check")
|
|
||||||
graph.add_conditional_edges(
|
|
||||||
"self_check",
|
|
||||||
self._route_after_self_check,
|
|
||||||
{"retry": "generate_doc_content", "ready": "build_changeset"},
|
|
||||||
)
|
|
||||||
graph.add_edge("build_changeset", "summarize_result")
|
|
||||||
graph.add_edge("summarize_result", END)
|
|
||||||
return graph.compile(checkpointer=checkpointer)
|
|
||||||
|
|
||||||
def _collect_code_context(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "collect_code_context", "Собираю контекст кода и файлов.", self._analyzer.collect_code_context)
|
|
||||||
|
|
||||||
def _detect_existing_docs(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(
|
|
||||||
state,
|
|
||||||
"detect_existing_docs",
|
|
||||||
"Определяю, есть ли существующая документация проекта.",
|
|
||||||
self._analyzer.detect_existing_docs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _decide_strategy(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "decide_strategy", "Выбираю стратегию: инкремент или генерация с нуля.", self._analyzer.decide_strategy)
|
|
||||||
|
|
||||||
def _load_rules_and_examples(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(
|
|
||||||
state,
|
|
||||||
"load_rules_and_examples",
|
|
||||||
"Загружаю правила и примеры формата документации.",
|
|
||||||
self._composer.load_rules_and_examples,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _plan_incremental_changes(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(
|
|
||||||
state,
|
|
||||||
"plan_incremental_changes",
|
|
||||||
"Планирую точечные изменения в существующей документации.",
|
|
||||||
lambda st: self._composer.plan_incremental_changes(st, self._analyzer),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _plan_new_document(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "plan_new_document", "Проектирую структуру новой документации.", self._composer.plan_new_document)
|
|
||||||
|
|
||||||
def _generate_doc_content(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "generate_doc_content", "Генерирую содержимое документации.", self._composer.generate_doc_content)
|
|
||||||
|
|
||||||
def _self_check(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "self_check", "Проверяю соответствие результата правилам.", self._composer.self_check)
|
|
||||||
|
|
||||||
def _build_changeset(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(state, "build_changeset", "Формирую итоговый набор изменений файлов.", self._composer.build_changeset)
|
|
||||||
|
|
||||||
def _summarize_result(self, state: AgentGraphState) -> dict:
|
|
||||||
return self._run_node(
|
|
||||||
state,
|
|
||||||
"summarize_result",
|
|
||||||
"Формирую краткий обзор выполненных действий и измененных файлов.",
|
|
||||||
self._composer.build_execution_summary,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _route_after_rules_loading(self, state: AgentGraphState) -> str:
|
|
||||||
if state.get("docs_strategy") == "incremental_update":
|
|
||||||
return "incremental"
|
|
||||||
return "from_scratch"
|
|
||||||
|
|
||||||
def _route_after_self_check(self, state: AgentGraphState) -> str:
|
|
||||||
if state.get("validation_passed"):
|
|
||||||
return "ready"
|
|
||||||
attempts = int(state.get("validation_attempts", 0) or 0)
|
|
||||||
return "ready" if attempts >= self._max_validation_attempts else "retry"
|
|
||||||
|
|
||||||
def _run_node(self, state: AgentGraphState, node_name: str, message: str, fn):
|
|
||||||
emit_progress_sync(state, stage=f"graph.docs.{node_name}", message=message)
|
|
||||||
try:
|
|
||||||
result = fn(state)
|
|
||||||
emit_progress_sync(state, stage=f"graph.docs.{node_name}.done", message=f"Шаг '{node_name}' завершен.")
|
|
||||||
LOGGER.warning("docs graph node completed: node=%s keys=%s", node_name, sorted(result.keys()))
|
|
||||||
return result
|
|
||||||
except Exception:
|
|
||||||
LOGGER.exception("docs graph node failed: node=%s", node_name)
|
|
||||||
raise
|
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
import json
|
|
||||||
from difflib import SequenceMatcher
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.docs_examples_loader import DocsExamplesLoader
|
|
||||||
from app.modules.agent.engine.graphs.file_targeting import FileTargeting
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
from app.schemas.changeset import ChangeItem
|
|
||||||
import logging
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DocsContextAnalyzer:
|
|
||||||
def __init__(self, llm: AgentLlmService, targeting: FileTargeting) -> None:
|
|
||||||
self._llm = llm
|
|
||||||
self._targeting = targeting
|
|
||||||
|
|
||||||
def collect_code_context(self, state: AgentGraphState) -> dict:
|
|
||||||
message = state.get("message", "")
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
requested_path = self._targeting.extract_target_path(message)
|
|
||||||
target_file = self._targeting.lookup_file(files_map, requested_path) if requested_path else None
|
|
||||||
docs_candidates = self._collect_doc_candidates(files_map)
|
|
||||||
target_path = str((target_file or {}).get("path") or (requested_path or "")).strip() or ""
|
|
||||||
return {
|
|
||||||
"docs_candidates": docs_candidates,
|
|
||||||
"target_path": target_path,
|
|
||||||
"target_file_content": str((target_file or {}).get("content", "")),
|
|
||||||
"target_file_hash": str((target_file or {}).get("content_hash", "")),
|
|
||||||
"validation_attempts": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def detect_existing_docs(self, state: AgentGraphState) -> dict:
|
|
||||||
docs_candidates = state.get("docs_candidates", []) or []
|
|
||||||
if not docs_candidates:
|
|
||||||
return {
|
|
||||||
"existing_docs_detected": False,
|
|
||||||
"existing_docs_summary": "No documentation files detected in current project context.",
|
|
||||||
}
|
|
||||||
|
|
||||||
snippets = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"Path: {item.get('path', '')}\nSnippet:\n{self._shorten(item.get('content', ''), 500)}"
|
|
||||||
for item in docs_candidates[:8]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Requested target path:\n{state.get('target_path', '') or '(not specified)'}",
|
|
||||||
f"Detected documentation candidates:\n{snippets}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
raw = self._llm.generate("docs_detect", user_input)
|
|
||||||
exists = self.parse_bool_marker(raw, "exists", default=True)
|
|
||||||
summary = self.parse_text_marker(raw, "summary", default="Documentation files detected.")
|
|
||||||
return {"existing_docs_detected": exists, "existing_docs_summary": summary}
|
|
||||||
|
|
||||||
def decide_strategy(self, state: AgentGraphState) -> dict:
|
|
||||||
message = (state.get("message", "") or "").lower()
|
|
||||||
if any(token in message for token in ("с нуля", "from scratch", "new documentation", "создай документацию")):
|
|
||||||
return {"docs_strategy": "from_scratch"}
|
|
||||||
if any(token in message for token in ("дополни", "обнови документацию", "extend docs", "update docs")):
|
|
||||||
return {"docs_strategy": "incremental_update"}
|
|
||||||
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Existing docs detected:\n{state.get('existing_docs_detected', False)}",
|
|
||||||
f"Existing docs summary:\n{state.get('existing_docs_summary', '')}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
raw = self._llm.generate("docs_strategy", user_input)
|
|
||||||
strategy = self.parse_text_marker(raw, "strategy", default="").lower()
|
|
||||||
if strategy not in {"incremental_update", "from_scratch"}:
|
|
||||||
strategy = "incremental_update" if state.get("existing_docs_detected", False) else "from_scratch"
|
|
||||||
return {"docs_strategy": strategy}
|
|
||||||
|
|
||||||
def resolve_target_for_incremental(self, state: AgentGraphState) -> tuple[str, dict | None]:
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
preferred_path = state.get("target_path", "")
|
|
||||||
preferred = self._targeting.lookup_file(files_map, preferred_path)
|
|
||||||
if preferred:
|
|
||||||
return str(preferred.get("path") or preferred_path), preferred
|
|
||||||
candidates = state.get("docs_candidates", []) or []
|
|
||||||
if candidates:
|
|
||||||
first_path = str(candidates[0].get("path", ""))
|
|
||||||
resolved = self._targeting.lookup_file(files_map, first_path) or candidates[0]
|
|
||||||
return first_path, resolved
|
|
||||||
fallback = preferred_path.strip() or "docs/AGENT_DRAFT.md"
|
|
||||||
return fallback, None
|
|
||||||
|
|
||||||
def _collect_doc_candidates(self, files_map: dict[str, dict]) -> list[dict]:
|
|
||||||
candidates: list[dict] = []
|
|
||||||
for raw_path, payload in files_map.items():
|
|
||||||
path = str(raw_path or "").replace("\\", "/").strip()
|
|
||||||
if not path:
|
|
||||||
continue
|
|
||||||
low = path.lower()
|
|
||||||
is_doc = low.startswith("docs/") or low.endswith(".md") or low.endswith(".rst") or "/readme" in low or low.startswith("readme")
|
|
||||||
if not is_doc:
|
|
||||||
continue
|
|
||||||
candidates.append(
|
|
||||||
{
|
|
||||||
"path": str(payload.get("path") or path),
|
|
||||||
"content": str(payload.get("content", "")),
|
|
||||||
"content_hash": str(payload.get("content_hash", "")),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
candidates.sort(key=lambda item: (0 if str(item.get("path", "")).lower().startswith("docs/") else 1, str(item.get("path", "")).lower()))
|
|
||||||
return candidates
|
|
||||||
|
|
||||||
def _shorten(self, text: str, max_chars: int) -> str:
|
|
||||||
value = (text or "").strip()
|
|
||||||
if len(value) <= max_chars:
|
|
||||||
return value
|
|
||||||
return value[:max_chars].rstrip() + "\n...[truncated]"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_bool_marker(text: str, marker: str, *, default: bool) -> bool:
|
|
||||||
value = DocsContextAnalyzer.parse_text_marker(text, marker, default="")
|
|
||||||
if not value:
|
|
||||||
return default
|
|
||||||
token = value.split()[0].strip().lower()
|
|
||||||
if token in {"yes", "true", "1", "да"}:
|
|
||||||
return True
|
|
||||||
if token in {"no", "false", "0", "нет"}:
|
|
||||||
return False
|
|
||||||
return default
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_text_marker(text: str, marker: str, *, default: str) -> str:
|
|
||||||
low_marker = f"{marker.lower()}:"
|
|
||||||
for line in (text or "").splitlines():
|
|
||||||
raw = line.strip()
|
|
||||||
if raw.lower().startswith(low_marker):
|
|
||||||
return raw.split(":", 1)[1].strip()
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
class DocsBundleFormatter:
|
|
||||||
def shorten(self, text: str, max_chars: int) -> str:
|
|
||||||
value = (text or "").strip()
|
|
||||||
if len(value) <= max_chars:
|
|
||||||
return value
|
|
||||||
return value[:max_chars].rstrip() + "\n...[truncated]"
|
|
||||||
|
|
||||||
def normalize_file_output(self, text: str) -> str:
|
|
||||||
value = (text or "").strip()
|
|
||||||
if value.startswith("```") and value.endswith("```"):
|
|
||||||
lines = value.splitlines()
|
|
||||||
if len(lines) >= 3:
|
|
||||||
return "\n".join(lines[1:-1]).strip()
|
|
||||||
return value
|
|
||||||
|
|
||||||
def parse_docs_bundle(self, raw_text: str) -> list[dict]:
|
|
||||||
text = (raw_text or "").strip()
|
|
||||||
if not text:
|
|
||||||
return []
|
|
||||||
|
|
||||||
candidate = self.normalize_file_output(text)
|
|
||||||
parsed = self._parse_json_candidate(candidate)
|
|
||||||
if parsed is None:
|
|
||||||
start = candidate.find("{")
|
|
||||||
end = candidate.rfind("}")
|
|
||||||
if start != -1 and end > start:
|
|
||||||
parsed = self._parse_json_candidate(candidate[start : end + 1])
|
|
||||||
if parsed is None:
|
|
||||||
return []
|
|
||||||
|
|
||||||
files: list[dict]
|
|
||||||
if isinstance(parsed, dict):
|
|
||||||
raw_files = parsed.get("files")
|
|
||||||
files = raw_files if isinstance(raw_files, list) else []
|
|
||||||
elif isinstance(parsed, list):
|
|
||||||
files = parsed
|
|
||||||
else:
|
|
||||||
files = []
|
|
||||||
|
|
||||||
out: list[dict] = []
|
|
||||||
seen: set[str] = set()
|
|
||||||
for item in files:
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
continue
|
|
||||||
path = str(item.get("path", "")).replace("\\", "/").strip()
|
|
||||||
content = str(item.get("content", ""))
|
|
||||||
if not path or not content.strip():
|
|
||||||
continue
|
|
||||||
if path in seen:
|
|
||||||
continue
|
|
||||||
seen.add(path)
|
|
||||||
out.append(
|
|
||||||
{
|
|
||||||
"path": path,
|
|
||||||
"content": content,
|
|
||||||
"reason": str(item.get("reason", "")).strip(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def bundle_has_required_structure(self, bundle: list[dict]) -> bool:
|
|
||||||
if not bundle:
|
|
||||||
return False
|
|
||||||
has_api = any(str(item.get("path", "")).replace("\\", "/").startswith("docs/api/") for item in bundle)
|
|
||||||
has_logic = any(str(item.get("path", "")).replace("\\", "/").startswith("docs/logic/") for item in bundle)
|
|
||||||
return has_api and has_logic
|
|
||||||
|
|
||||||
def similarity(self, original: str, updated: str) -> float:
|
|
||||||
return SequenceMatcher(None, original or "", updated or "").ratio()
|
|
||||||
|
|
||||||
def line_change_ratio(self, original: str, updated: str) -> float:
|
|
||||||
orig_lines = (original or "").splitlines()
|
|
||||||
new_lines = (updated or "").splitlines()
|
|
||||||
if not orig_lines and not new_lines:
|
|
||||||
return 0.0
|
|
||||||
matcher = SequenceMatcher(None, orig_lines, new_lines)
|
|
||||||
changed = 0
|
|
||||||
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
||||||
if tag == "equal":
|
|
||||||
continue
|
|
||||||
changed += max(i2 - i1, j2 - j1)
|
|
||||||
total = max(len(orig_lines), len(new_lines), 1)
|
|
||||||
return changed / total
|
|
||||||
|
|
||||||
def added_headings(self, original: str, updated: str) -> int:
|
|
||||||
old_heads = {line.strip() for line in (original or "").splitlines() if line.strip().startswith("#")}
|
|
||||||
new_heads = {line.strip() for line in (updated or "").splitlines() if line.strip().startswith("#")}
|
|
||||||
return len(new_heads - old_heads)
|
|
||||||
|
|
||||||
def collapse_whitespace(self, text: str) -> str:
|
|
||||||
return " ".join((text or "").split())
|
|
||||||
|
|
||||||
def _parse_json_candidate(self, text: str):
|
|
||||||
try:
|
|
||||||
return json.loads(text)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class DocsContentComposer:
|
|
||||||
def __init__(self, llm: AgentLlmService, targeting: FileTargeting) -> None:
|
|
||||||
self._llm = llm
|
|
||||||
self._targeting = targeting
|
|
||||||
self._examples = DocsExamplesLoader()
|
|
||||||
self._bundle = DocsBundleFormatter()
|
|
||||||
|
|
||||||
def load_rules_and_examples(self, _state: AgentGraphState) -> dict:
|
|
||||||
return {"rules_bundle": self._examples.load_bundle()}
|
|
||||||
|
|
||||||
def plan_incremental_changes(self, state: AgentGraphState, analyzer: DocsContextAnalyzer) -> dict:
|
|
||||||
target_path, target = analyzer.resolve_target_for_incremental(state)
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
"Strategy: incremental_update",
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Target path:\n{target_path}",
|
|
||||||
f"Current target content:\n{self._bundle.shorten((target or {}).get('content', ''), 3000)}",
|
|
||||||
f"RAG context:\n{self._bundle.shorten(state.get('rag_context', ''), 6000)}",
|
|
||||||
f"Examples bundle:\n{state.get('rules_bundle', '')}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
plan = self._llm.generate("docs_plan_sections", user_input)
|
|
||||||
return {
|
|
||||||
"doc_plan": plan,
|
|
||||||
"target_path": target_path,
|
|
||||||
"target_file_content": str((target or {}).get("content", "")),
|
|
||||||
"target_file_hash": str((target or {}).get("content_hash", "")),
|
|
||||||
}
|
|
||||||
|
|
||||||
def plan_new_document(self, state: AgentGraphState) -> dict:
|
|
||||||
target_path = state.get("target_path", "").strip() or "docs/AGENT_DRAFT.md"
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
"Strategy: from_scratch",
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Target path:\n{target_path}",
|
|
||||||
f"RAG context:\n{self._bundle.shorten(state.get('rag_context', ''), 6000)}",
|
|
||||||
f"Examples bundle:\n{state.get('rules_bundle', '')}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
plan = self._llm.generate("docs_plan_sections", user_input)
|
|
||||||
return {"doc_plan": plan, "target_path": target_path, "target_file_content": "", "target_file_hash": ""}
|
|
||||||
|
|
||||||
def generate_doc_content(self, state: AgentGraphState) -> dict:
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"Strategy:\n{state.get('docs_strategy', 'from_scratch')}",
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Target path:\n{state.get('target_path', '')}",
|
|
||||||
f"Document plan:\n{state.get('doc_plan', '')}",
|
|
||||||
f"Current target content:\n{self._bundle.shorten(state.get('target_file_content', ''), 3500)}",
|
|
||||||
f"RAG context:\n{self._bundle.shorten(state.get('rag_context', ''), 7000)}",
|
|
||||||
f"Examples bundle:\n{state.get('rules_bundle', '')}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
raw = self._llm.generate("docs_generation", user_input)
|
|
||||||
bundle = self._bundle.parse_docs_bundle(raw)
|
|
||||||
if bundle:
|
|
||||||
first_content = str(bundle[0].get("content", "")).strip()
|
|
||||||
return {"generated_docs_bundle": bundle, "generated_doc": first_content}
|
|
||||||
content = self._bundle.normalize_file_output(raw)
|
|
||||||
return {"generated_docs_bundle": [], "generated_doc": content}
|
|
||||||
|
|
||||||
def self_check(self, state: AgentGraphState) -> dict:
|
|
||||||
attempts = int(state.get("validation_attempts", 0) or 0) + 1
|
|
||||||
bundle = state.get("generated_docs_bundle", []) or []
|
|
||||||
generated = state.get("generated_doc", "")
|
|
||||||
if not generated.strip() and not bundle:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": "Generated document is empty.",
|
|
||||||
}
|
|
||||||
strategy = state.get("docs_strategy", "from_scratch")
|
|
||||||
if strategy == "from_scratch" and not self._bundle.bundle_has_required_structure(bundle):
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": "Bundle must include both docs/api and docs/logic for from_scratch strategy.",
|
|
||||||
}
|
|
||||||
if strategy == "incremental_update":
|
|
||||||
if bundle and len(bundle) > 1 and not self._is_broad_rewrite_request(str(state.get("message", ""))):
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": "Incremental update should not touch multiple files without explicit broad rewrite request.",
|
|
||||||
}
|
|
||||||
original = str(state.get("target_file_content", ""))
|
|
||||||
broad = self._is_broad_rewrite_request(str(state.get("message", "")))
|
|
||||||
if original and generated:
|
|
||||||
if self._bundle.collapse_whitespace(original) == self._bundle.collapse_whitespace(generated):
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": "Only formatting/whitespace changes detected.",
|
|
||||||
}
|
|
||||||
similarity = self._bundle.similarity(original, generated)
|
|
||||||
change_ratio = self._bundle.line_change_ratio(original, generated)
|
|
||||||
added_headings = self._bundle.added_headings(original, generated)
|
|
||||||
min_similarity = 0.75 if broad else 0.9
|
|
||||||
max_change_ratio = 0.7 if broad else 0.35
|
|
||||||
if similarity < min_similarity:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": f"Incremental update is too broad (similarity={similarity:.2f}).",
|
|
||||||
}
|
|
||||||
if change_ratio > max_change_ratio:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": f"Incremental update changes too many lines (change_ratio={change_ratio:.2f}).",
|
|
||||||
}
|
|
||||||
if not broad and added_headings > 0:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": "New section headings were added outside requested scope.",
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle_text = "\n".join([f"- {item.get('path', '')}" for item in bundle[:30]])
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"Strategy:\n{strategy}",
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Document plan:\n{state.get('doc_plan', '')}",
|
|
||||||
f"Generated file paths:\n{bundle_text or '(single-file mode)'}",
|
|
||||||
f"Generated document:\n{generated}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
raw = self._llm.generate("docs_self_check", user_input)
|
|
||||||
passed = DocsContextAnalyzer.parse_bool_marker(raw, "pass", default=False)
|
|
||||||
feedback = DocsContextAnalyzer.parse_text_marker(raw, "feedback", default="No validation feedback provided.")
|
|
||||||
return {"validation_attempts": attempts, "validation_passed": passed, "validation_feedback": feedback}
|
|
||||||
|
|
||||||
def build_changeset(self, state: AgentGraphState) -> dict:
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
bundle = state.get("generated_docs_bundle", []) or []
|
|
||||||
strategy = state.get("docs_strategy", "from_scratch")
|
|
||||||
if strategy == "from_scratch" and not self._bundle.bundle_has_required_structure(bundle):
|
|
||||||
LOGGER.warning(
|
|
||||||
"build_changeset fallback bundle used: strategy=%s bundle_items=%s",
|
|
||||||
strategy,
|
|
||||||
len(bundle),
|
|
||||||
)
|
|
||||||
bundle = self._build_fallback_bundle_from_text(state.get("generated_doc", ""))
|
|
||||||
if bundle:
|
|
||||||
changes: list[ChangeItem] = []
|
|
||||||
for item in bundle:
|
|
||||||
path = str(item.get("path", "")).replace("\\", "/").strip()
|
|
||||||
content = str(item.get("content", ""))
|
|
||||||
if not path or not content.strip():
|
|
||||||
continue
|
|
||||||
target = self._targeting.lookup_file(files_map, path)
|
|
||||||
reason = str(item.get("reason", "")).strip() or f"Documentation {strategy}: generated file from structured bundle."
|
|
||||||
if target and target.get("content_hash"):
|
|
||||||
changes.append(
|
|
||||||
ChangeItem(
|
|
||||||
op="update",
|
|
||||||
path=str(target.get("path") or path),
|
|
||||||
base_hash=str(target.get("content_hash", "")),
|
|
||||||
proposed_content=content,
|
|
||||||
reason=reason,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
changes.append(
|
|
||||||
ChangeItem(
|
|
||||||
op="create",
|
|
||||||
path=path,
|
|
||||||
proposed_content=content,
|
|
||||||
reason=reason,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if changes:
|
|
||||||
return {"changeset": changes}
|
|
||||||
|
|
||||||
target_path = (state.get("target_path", "") or "").strip() or "docs/AGENT_DRAFT.md"
|
|
||||||
target = self._targeting.lookup_file(files_map, target_path)
|
|
||||||
content = state.get("generated_doc", "")
|
|
||||||
if target and target.get("content_hash"):
|
|
||||||
change = ChangeItem(
|
|
||||||
op="update",
|
|
||||||
path=str(target.get("path") or target_path),
|
|
||||||
base_hash=str(target.get("content_hash", "")),
|
|
||||||
proposed_content=content,
|
|
||||||
reason=f"Documentation {strategy}: update existing document increment.",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
change = ChangeItem(
|
|
||||||
op="create",
|
|
||||||
path=target_path,
|
|
||||||
proposed_content=content,
|
|
||||||
reason=f"Documentation {strategy}: create document from current project context.",
|
|
||||||
)
|
|
||||||
return {"changeset": [change]}
|
|
||||||
|
|
||||||
def build_execution_summary(self, state: AgentGraphState) -> dict:
|
|
||||||
changeset = state.get("changeset", []) or []
|
|
||||||
if not changeset:
|
|
||||||
return {"answer": "Документация не была изменена: итоговый changeset пуст."}
|
|
||||||
|
|
||||||
file_lines = self._format_changed_files(changeset)
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"Documentation strategy:\n{state.get('docs_strategy', 'from_scratch')}",
|
|
||||||
f"Document plan:\n{state.get('doc_plan', '')}",
|
|
||||||
f"Validation feedback:\n{state.get('validation_feedback', '')}",
|
|
||||||
f"Changed files:\n{file_lines}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
summary = self._llm.generate("docs_execution_summary", user_input).strip()
|
|
||||||
except Exception:
|
|
||||||
summary = ""
|
|
||||||
if not summary:
|
|
||||||
summary = self._build_fallback_summary(state, file_lines)
|
|
||||||
return {"answer": summary}
|
|
||||||
|
|
||||||
def _build_fallback_bundle_from_text(self, text: str) -> list[dict]:
|
|
||||||
content = (text or "").strip()
|
|
||||||
if not content:
|
|
||||||
content = (
|
|
||||||
"# Project Documentation Draft\n\n"
|
|
||||||
"## Overview\n"
|
|
||||||
"Documentation draft was generated, but structured sections require уточнение.\n"
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"path": "docs/logic/project_overview.md",
|
|
||||||
"content": content,
|
|
||||||
"reason": "Fallback: generated structured logic document from non-JSON model output.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "docs/api/README.md",
|
|
||||||
"content": (
|
|
||||||
"# API Methods\n\n"
|
|
||||||
"This file is a fallback placeholder for API method documentation.\n\n"
|
|
||||||
"## Next Step\n"
|
|
||||||
"- Add one file per API method under `docs/api/`.\n"
|
|
||||||
),
|
|
||||||
"reason": "Fallback: ensure required docs/api structure exists.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
def _format_changed_files(self, changeset: list[ChangeItem]) -> str:
|
|
||||||
lines: list[str] = []
|
|
||||||
for item in changeset[:30]:
|
|
||||||
lines.append(f"- {item.op.value} {item.path}: {item.reason}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
def _build_fallback_summary(self, state: AgentGraphState, file_lines: str) -> str:
|
|
||||||
request = (state.get("message", "") or "").strip()
|
|
||||||
return "\n".join(
|
|
||||||
[
|
|
||||||
"Выполненные действия:",
|
|
||||||
f"- Обработан запрос: {request or '(пустой запрос)'}",
|
|
||||||
f"- Применена стратегия документации: {state.get('docs_strategy', 'from_scratch')}",
|
|
||||||
"- Сформирован и проверен changeset для документации.",
|
|
||||||
"",
|
|
||||||
"Измененные файлы:",
|
|
||||||
file_lines or "- (нет изменений)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def _is_broad_rewrite_request(self, message: str) -> bool:
|
|
||||||
low = (message or "").lower()
|
|
||||||
markers = (
|
|
||||||
"перепиши",
|
|
||||||
"полностью",
|
|
||||||
"целиком",
|
|
||||||
"с нуля",
|
|
||||||
"full rewrite",
|
|
||||||
"rewrite all",
|
|
||||||
"реорганизуй",
|
|
||||||
)
|
|
||||||
return any(marker in low for marker in markers)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class FileTargeting:
|
|
||||||
_path_pattern = re.compile(r"([A-Za-z0-9_.\-/]+?\.[A-Za-z0-9_]+)")
|
|
||||||
|
|
||||||
def extract_target_path(self, message: str) -> str | None:
|
|
||||||
text = (message or "").replace("\\", "/")
|
|
||||||
candidates = self._path_pattern.findall(text)
|
|
||||||
if not candidates:
|
|
||||||
return None
|
|
||||||
for candidate in candidates:
|
|
||||||
cleaned = candidate.strip("`'\".,:;()[]{}")
|
|
||||||
if "/" in cleaned or cleaned.startswith("."):
|
|
||||||
return cleaned
|
|
||||||
return candidates[0].strip("`'\".,:;()[]{}")
|
|
||||||
|
|
||||||
def lookup_file(self, files_map: dict[str, dict], path: str | None) -> dict | None:
|
|
||||||
if not path:
|
|
||||||
return None
|
|
||||||
normalized = path.replace("\\", "/")
|
|
||||||
if normalized in files_map:
|
|
||||||
return files_map[normalized]
|
|
||||||
low = normalized.lower()
|
|
||||||
for key, value in files_map.items():
|
|
||||||
if key.lower() == low:
|
|
||||||
return value
|
|
||||||
return None
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
from collections.abc import Awaitable, Callable
|
|
||||||
import inspect
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.progress_registry import progress_registry
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
|
|
||||||
ProgressCallback = Callable[[str, str, str, dict | None], Awaitable[None] | None]
|
|
||||||
|
|
||||||
|
|
||||||
async def emit_progress(
|
|
||||||
state: AgentGraphState,
|
|
||||||
*,
|
|
||||||
stage: str,
|
|
||||||
message: str,
|
|
||||||
kind: str = "task_progress",
|
|
||||||
meta: dict | None = None,
|
|
||||||
) -> None:
|
|
||||||
callback = progress_registry.get(state.get("progress_key"))
|
|
||||||
if callback is None:
|
|
||||||
return
|
|
||||||
result = callback(stage, message, kind, meta or {})
|
|
||||||
if inspect.isawaitable(result):
|
|
||||||
await result
|
|
||||||
|
|
||||||
|
|
||||||
def emit_progress_sync(
|
|
||||||
state: AgentGraphState,
|
|
||||||
*,
|
|
||||||
stage: str,
|
|
||||||
message: str,
|
|
||||||
kind: str = "task_progress",
|
|
||||||
meta: dict | None = None,
|
|
||||||
) -> None:
|
|
||||||
callback = progress_registry.get(state.get("progress_key"))
|
|
||||||
if callback is None:
|
|
||||||
return
|
|
||||||
result = callback(stage, message, kind, meta or {})
|
|
||||||
if inspect.isawaitable(result):
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
loop.create_task(result)
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from collections.abc import Awaitable, Callable
|
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
ProgressCallback = Callable[[str, str, str, dict | None], Awaitable[None] | None]
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressRegistry:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._items: dict[str, ProgressCallback] = {}
|
|
||||||
self._lock = Lock()
|
|
||||||
|
|
||||||
def register(self, key: str, callback: ProgressCallback) -> None:
|
|
||||||
with self._lock:
|
|
||||||
self._items[key] = callback
|
|
||||||
|
|
||||||
def get(self, key: str | None) -> ProgressCallback | None:
|
|
||||||
if not key:
|
|
||||||
return None
|
|
||||||
with self._lock:
|
|
||||||
return self._items.get(key)
|
|
||||||
|
|
||||||
def unregister(self, key: str) -> None:
|
|
||||||
with self._lock:
|
|
||||||
self._items.pop(key, None)
|
|
||||||
|
|
||||||
|
|
||||||
progress_registry = ProgressRegistry()
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
from langgraph.graph import END, START, StateGraph
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.progress import emit_progress_sync
|
|
||||||
from app.modules.agent.engine.graphs.project_edits_logic import ProjectEditsLogic
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectEditsGraphFactory:
|
|
||||||
_max_validation_attempts = 2
|
|
||||||
|
|
||||||
def __init__(self, llm: AgentLlmService) -> None:
|
|
||||||
self._logic = ProjectEditsLogic(llm)
|
|
||||||
|
|
||||||
def build(self, checkpointer=None):
|
|
||||||
graph = StateGraph(AgentGraphState)
|
|
||||||
graph.add_node("collect_context", self._collect_context)
|
|
||||||
graph.add_node("plan_changes", self._plan_changes)
|
|
||||||
graph.add_node("generate_changeset", self._generate_changeset)
|
|
||||||
graph.add_node("self_check", self._self_check)
|
|
||||||
graph.add_node("build_result", self._build_result)
|
|
||||||
|
|
||||||
graph.add_edge(START, "collect_context")
|
|
||||||
graph.add_edge("collect_context", "plan_changes")
|
|
||||||
graph.add_edge("plan_changes", "generate_changeset")
|
|
||||||
graph.add_edge("generate_changeset", "self_check")
|
|
||||||
graph.add_conditional_edges(
|
|
||||||
"self_check",
|
|
||||||
self._route_after_self_check,
|
|
||||||
{"retry": "generate_changeset", "ready": "build_result"},
|
|
||||||
)
|
|
||||||
graph.add_edge("build_result", END)
|
|
||||||
return graph.compile(checkpointer=checkpointer)
|
|
||||||
|
|
||||||
def _collect_context(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_edits.collect_context",
|
|
||||||
message="Собираю контекст и релевантные файлы для правок.",
|
|
||||||
)
|
|
||||||
return self._logic.collect_context(state)
|
|
||||||
|
|
||||||
def _plan_changes(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_edits.plan_changes",
|
|
||||||
message="Определяю, что именно нужно изменить и в каких файлах.",
|
|
||||||
)
|
|
||||||
return self._logic.plan_changes(state)
|
|
||||||
|
|
||||||
def _generate_changeset(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_edits.generate_changeset",
|
|
||||||
message="Формирую предлагаемые правки по выбранным файлам.",
|
|
||||||
)
|
|
||||||
return self._logic.generate_changeset(state)
|
|
||||||
|
|
||||||
def _self_check(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_edits.self_check",
|
|
||||||
message="Проверяю, что правки соответствуют запросу и не трогают лишнее.",
|
|
||||||
)
|
|
||||||
return self._logic.self_check(state)
|
|
||||||
|
|
||||||
def _build_result(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_edits.build_result",
|
|
||||||
message="Формирую итоговый changeset и краткий обзор.",
|
|
||||||
)
|
|
||||||
return self._logic.build_result(state)
|
|
||||||
|
|
||||||
def _route_after_self_check(self, state: AgentGraphState) -> str:
|
|
||||||
if state.get("validation_passed"):
|
|
||||||
return "ready"
|
|
||||||
attempts = int(state.get("validation_attempts", 0) or 0)
|
|
||||||
return "ready" if attempts >= self._max_validation_attempts else "retry"
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
import json
|
|
||||||
from difflib import SequenceMatcher
|
|
||||||
import re
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.file_targeting import FileTargeting
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
from app.schemas.changeset import ChangeItem
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectEditsSupport:
|
|
||||||
def __init__(self, max_context_files: int = 12, max_preview_chars: int = 2500) -> None:
|
|
||||||
self._max_context_files = max_context_files
|
|
||||||
self._max_preview_chars = max_preview_chars
|
|
||||||
|
|
||||||
def pick_relevant_files(self, message: str, files_map: dict[str, dict]) -> list[dict]:
|
|
||||||
tokens = {x for x in (message or "").lower().replace("/", " ").split() if len(x) >= 4}
|
|
||||||
scored: list[tuple[int, dict]] = []
|
|
||||||
for path, payload in files_map.items():
|
|
||||||
content = str(payload.get("content", ""))
|
|
||||||
score = 0
|
|
||||||
low_path = path.lower()
|
|
||||||
low_content = content.lower()
|
|
||||||
for token in tokens:
|
|
||||||
if token in low_path:
|
|
||||||
score += 3
|
|
||||||
if token in low_content:
|
|
||||||
score += 1
|
|
||||||
scored.append((score, self.as_candidate(payload)))
|
|
||||||
scored.sort(key=lambda x: (-x[0], x[1]["path"]))
|
|
||||||
return [item for _, item in scored[: self._max_context_files]]
|
|
||||||
|
|
||||||
def as_candidate(self, payload: dict) -> dict:
|
|
||||||
return {
|
|
||||||
"path": str(payload.get("path", "")).replace("\\", "/"),
|
|
||||||
"content": str(payload.get("content", "")),
|
|
||||||
"content_hash": str(payload.get("content_hash", "")),
|
|
||||||
}
|
|
||||||
|
|
||||||
def build_summary(self, state: AgentGraphState, changeset: list[ChangeItem]) -> str:
|
|
||||||
if not changeset:
|
|
||||||
return "Правки не сформированы: changeset пуст."
|
|
||||||
lines = [
|
|
||||||
"Выполненные действия:",
|
|
||||||
f"- Проанализирован запрос: {state.get('message', '')}",
|
|
||||||
"- Собран контекст проекта и выбран набор файлов для правок.",
|
|
||||||
f"- Проведен self-check: {state.get('validation_feedback', 'без замечаний')}",
|
|
||||||
"",
|
|
||||||
"Измененные файлы:",
|
|
||||||
]
|
|
||||||
for item in changeset[:30]:
|
|
||||||
lines.append(f"- {item.op.value} {item.path}: {item.reason}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
def normalize_file_output(self, text: str) -> str:
|
|
||||||
value = (text or "").strip()
|
|
||||||
if value.startswith("```") and value.endswith("```"):
|
|
||||||
lines = value.splitlines()
|
|
||||||
if len(lines) >= 3:
|
|
||||||
return "\n".join(lines[1:-1]).strip()
|
|
||||||
return value
|
|
||||||
|
|
||||||
def parse_json(self, raw: str):
|
|
||||||
text = self.normalize_file_output(raw)
|
|
||||||
try:
|
|
||||||
return json.loads(text)
|
|
||||||
except Exception:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def similarity(self, original: str, updated: str) -> float:
|
|
||||||
return SequenceMatcher(None, original or "", updated or "").ratio()
|
|
||||||
|
|
||||||
def shorten(self, text: str, max_chars: int | None = None) -> str:
|
|
||||||
limit = max_chars or self._max_preview_chars
|
|
||||||
value = (text or "").strip()
|
|
||||||
if len(value) <= limit:
|
|
||||||
return value
|
|
||||||
return value[:limit].rstrip() + "\n...[truncated]"
|
|
||||||
|
|
||||||
def collapse_whitespace(self, text: str) -> str:
|
|
||||||
return re.sub(r"\s+", " ", (text or "").strip())
|
|
||||||
|
|
||||||
def line_change_ratio(self, original: str, updated: str) -> float:
|
|
||||||
orig_lines = (original or "").splitlines()
|
|
||||||
new_lines = (updated or "").splitlines()
|
|
||||||
if not orig_lines and not new_lines:
|
|
||||||
return 0.0
|
|
||||||
matcher = SequenceMatcher(None, orig_lines, new_lines)
|
|
||||||
changed = 0
|
|
||||||
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
||||||
if tag == "equal":
|
|
||||||
continue
|
|
||||||
changed += max(i2 - i1, j2 - j1)
|
|
||||||
total = max(len(orig_lines), len(new_lines), 1)
|
|
||||||
return changed / total
|
|
||||||
|
|
||||||
def added_headings(self, original: str, updated: str) -> int:
|
|
||||||
old_heads = {line.strip() for line in (original or "").splitlines() if line.strip().startswith("#")}
|
|
||||||
new_heads = {line.strip() for line in (updated or "").splitlines() if line.strip().startswith("#")}
|
|
||||||
return len(new_heads - old_heads)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectEditsLogic:
|
|
||||||
def __init__(self, llm: AgentLlmService) -> None:
|
|
||||||
self._llm = llm
|
|
||||||
self._targeting = FileTargeting()
|
|
||||||
self._support = ProjectEditsSupport()
|
|
||||||
|
|
||||||
def collect_context(self, state: AgentGraphState) -> dict:
|
|
||||||
message = state.get("message", "")
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
requested_path = self._targeting.extract_target_path(message)
|
|
||||||
preferred = self._targeting.lookup_file(files_map, requested_path) if requested_path else None
|
|
||||||
candidates = self._support.pick_relevant_files(message, files_map)
|
|
||||||
if preferred and not any(x["path"] == preferred.get("path") for x in candidates):
|
|
||||||
candidates.insert(0, self._support.as_candidate(preferred))
|
|
||||||
return {
|
|
||||||
"edits_requested_path": str((preferred or {}).get("path") or (requested_path or "")).strip(),
|
|
||||||
"edits_context_files": candidates[:12],
|
|
||||||
"validation_attempts": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def plan_changes(self, state: AgentGraphState) -> dict:
|
|
||||||
context_files = state.get("edits_context_files", []) or []
|
|
||||||
user_input = json.dumps(
|
|
||||||
{
|
|
||||||
"request": state.get("message", ""),
|
|
||||||
"requested_path": state.get("edits_requested_path", ""),
|
|
||||||
"context_files": [
|
|
||||||
{
|
|
||||||
"path": item.get("path", ""),
|
|
||||||
"content_preview": self._support.shorten(str(item.get("content", ""))),
|
|
||||||
}
|
|
||||||
for item in context_files
|
|
||||||
],
|
|
||||||
},
|
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
|
||||||
parsed = self._support.parse_json(self._llm.generate("project_edits_plan", user_input))
|
|
||||||
files = parsed.get("files", []) if isinstance(parsed, dict) else []
|
|
||||||
planned: list[dict] = []
|
|
||||||
for item in files[:8] if isinstance(files, list) else []:
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
continue
|
|
||||||
path = str(item.get("path", "")).replace("\\", "/").strip()
|
|
||||||
if not path:
|
|
||||||
continue
|
|
||||||
planned.append(
|
|
||||||
{
|
|
||||||
"path": path,
|
|
||||||
"reason": str(item.get("reason", "")).strip() or "Requested user adjustment.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if not planned:
|
|
||||||
fallback_path = state.get("edits_requested_path", "").strip() or "docs/REQUESTED_UPDATES.md"
|
|
||||||
planned = [{"path": fallback_path, "reason": "Fallback path from user request."}]
|
|
||||||
return {"edits_plan": planned}
|
|
||||||
|
|
||||||
def generate_changeset(self, state: AgentGraphState) -> dict:
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
planned = state.get("edits_plan", []) or []
|
|
||||||
changeset: list[ChangeItem] = []
|
|
||||||
for item in planned:
|
|
||||||
path = str(item.get("path", "")).replace("\\", "/").strip()
|
|
||||||
if not path:
|
|
||||||
continue
|
|
||||||
current = self._targeting.lookup_file(files_map, path)
|
|
||||||
current_content = str((current or {}).get("content", ""))
|
|
||||||
user_input = json.dumps(
|
|
||||||
{
|
|
||||||
"request": state.get("message", ""),
|
|
||||||
"path": path,
|
|
||||||
"reason": item.get("reason", ""),
|
|
||||||
"current_content": current_content,
|
|
||||||
"previous_validation_feedback": state.get("validation_feedback", ""),
|
|
||||||
"rag_context": self._support.shorten(state.get("rag_context", ""), 5000),
|
|
||||||
"confluence_context": self._support.shorten(state.get("confluence_context", ""), 5000),
|
|
||||||
"instruction": "Modify only required parts and preserve unrelated content unchanged.",
|
|
||||||
},
|
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
|
||||||
raw = self._llm.generate("project_edits_apply", user_input).strip()
|
|
||||||
normalized = self._support.normalize_file_output(raw)
|
|
||||||
if not normalized:
|
|
||||||
continue
|
|
||||||
if current:
|
|
||||||
if normalized == current_content:
|
|
||||||
continue
|
|
||||||
if self._support.collapse_whitespace(normalized) == self._support.collapse_whitespace(current_content):
|
|
||||||
continue
|
|
||||||
reason = str(item.get("reason", "")).strip() or "User-requested update."
|
|
||||||
if current and current.get("content_hash"):
|
|
||||||
changeset.append(
|
|
||||||
ChangeItem(
|
|
||||||
op="update",
|
|
||||||
path=str(current.get("path") or path),
|
|
||||||
base_hash=str(current.get("content_hash", "")),
|
|
||||||
proposed_content=normalized,
|
|
||||||
reason=reason,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
changeset.append(ChangeItem(op="create", path=path, proposed_content=normalized, reason=reason))
|
|
||||||
return {"changeset": changeset}
|
|
||||||
|
|
||||||
def self_check(self, state: AgentGraphState) -> dict:
|
|
||||||
attempts = int(state.get("validation_attempts", 0) or 0) + 1
|
|
||||||
changeset = state.get("changeset", []) or []
|
|
||||||
files_map = state.get("files_map", {}) or {}
|
|
||||||
is_broad_rewrite = self._is_broad_rewrite_request(str(state.get("message", "")))
|
|
||||||
if not changeset:
|
|
||||||
return {"validation_attempts": attempts, "validation_passed": False, "validation_feedback": "Generated changeset is empty."}
|
|
||||||
|
|
||||||
for item in changeset:
|
|
||||||
if item.op.value != "update":
|
|
||||||
continue
|
|
||||||
source = self._targeting.lookup_file(files_map, item.path)
|
|
||||||
if not source:
|
|
||||||
continue
|
|
||||||
original = str(source.get("content", ""))
|
|
||||||
proposed = item.proposed_content or ""
|
|
||||||
similarity = self._support.similarity(original, proposed)
|
|
||||||
change_ratio = self._support.line_change_ratio(original, proposed)
|
|
||||||
headings_added = self._support.added_headings(original, proposed)
|
|
||||||
min_similarity = 0.75 if is_broad_rewrite else 0.9
|
|
||||||
max_change_ratio = 0.7 if is_broad_rewrite else 0.35
|
|
||||||
if similarity < min_similarity:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": f"File {item.path} changed too aggressively (similarity={similarity:.2f}).",
|
|
||||||
}
|
|
||||||
if change_ratio > max_change_ratio:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": f"File {item.path} changed too broadly (change_ratio={change_ratio:.2f}).",
|
|
||||||
}
|
|
||||||
if not is_broad_rewrite and headings_added > 0:
|
|
||||||
return {
|
|
||||||
"validation_attempts": attempts,
|
|
||||||
"validation_passed": False,
|
|
||||||
"validation_feedback": f"File {item.path} adds new sections outside requested scope.",
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"request": state.get("message", ""),
|
|
||||||
"changeset": [{"op": x.op.value, "path": x.path, "reason": x.reason} for x in changeset[:20]],
|
|
||||||
"rule": "Changes must match request and avoid unrelated modifications.",
|
|
||||||
}
|
|
||||||
parsed = self._support.parse_json(self._llm.generate("project_edits_self_check", json.dumps(payload, ensure_ascii=False)))
|
|
||||||
passed = bool(parsed.get("pass")) if isinstance(parsed, dict) else False
|
|
||||||
feedback = str(parsed.get("feedback", "")).strip() if isinstance(parsed, dict) else ""
|
|
||||||
return {"validation_attempts": attempts, "validation_passed": passed, "validation_feedback": feedback or "No feedback provided."}
|
|
||||||
|
|
||||||
def build_result(self, state: AgentGraphState) -> dict:
|
|
||||||
changeset = state.get("changeset", []) or []
|
|
||||||
return {"changeset": changeset, "answer": self._support.build_summary(state, changeset)}
|
|
||||||
|
|
||||||
def _is_broad_rewrite_request(self, message: str) -> bool:
|
|
||||||
low = (message or "").lower()
|
|
||||||
markers = (
|
|
||||||
"перепиши",
|
|
||||||
"полностью",
|
|
||||||
"целиком",
|
|
||||||
"с нуля",
|
|
||||||
"full rewrite",
|
|
||||||
"rewrite all",
|
|
||||||
"реорганизуй документ",
|
|
||||||
)
|
|
||||||
return any(marker in low for marker in markers)
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
from langgraph.graph import END, START, StateGraph
|
|
||||||
|
|
||||||
from app.modules.agent.engine.graphs.progress import emit_progress_sync
|
|
||||||
from app.modules.agent.engine.graphs.state import AgentGraphState
|
|
||||||
from app.modules.agent.llm import AgentLlmService
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectQaGraphFactory:
|
|
||||||
def __init__(self, llm: AgentLlmService) -> None:
|
|
||||||
self._llm = llm
|
|
||||||
|
|
||||||
def build(self, checkpointer=None):
|
|
||||||
graph = StateGraph(AgentGraphState)
|
|
||||||
graph.add_node("answer", self._answer_node)
|
|
||||||
graph.add_edge(START, "answer")
|
|
||||||
graph.add_edge("answer", END)
|
|
||||||
return graph.compile(checkpointer=checkpointer)
|
|
||||||
|
|
||||||
def _answer_node(self, state: AgentGraphState) -> dict:
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_qa.answer",
|
|
||||||
message="Готовлю ответ по контексту текущего проекта.",
|
|
||||||
)
|
|
||||||
user_input = "\n\n".join(
|
|
||||||
[
|
|
||||||
f"User request:\n{state.get('message', '')}",
|
|
||||||
f"RAG context:\n{state.get('rag_context', '')}",
|
|
||||||
f"Confluence context:\n{state.get('confluence_context', '')}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
answer = self._llm.generate("project_answer", user_input)
|
|
||||||
emit_progress_sync(
|
|
||||||
state,
|
|
||||||
stage="graph.project_qa.answer.done",
|
|
||||||
message="Ответ по проекту сформирован.",
|
|
||||||
)
|
|
||||||
return {"answer": answer}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user