347 lines
9.9 KiB
Markdown
347 lines
9.9 KiB
Markdown
# 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 недоступен или вернул невалидный маршрут.
|