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