Files
agent/_process/components/v2_retrieval_policy_resolver_architecture.md
T

11 KiB
Raw Blame History

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.