Рефакторинг

This commit is contained in:
2026-03-12 23:33:51 +03:00
parent 9066c292de
commit 15586f9a8c
133 changed files with 1011 additions and 894 deletions

View File

@@ -54,7 +54,7 @@
- **Target Architecture** описывает то, к чему проект идёт. - **Target Architecture** описывает то, к чему проект идёт.
- **MVP-now** описывает то, что реально доводится сейчас через тесты. - **MVP-now** описывает то, что реально доводится сейчас через тесты.
- **MVP-now** не включает UI-интеграцию и не требует полного runtime orchestration. - **MVP-now** не включает UI-интеграцию и использует единый stage-based runtime.
- **MVP-now** фокусируется на: - **MVP-now** фокусируется на:
- IntentRouterV2; - IntentRouterV2;
- code-first retrieval; - code-first retrieval;
@@ -72,9 +72,10 @@
**MVP-now** **MVP-now**
- isolated `CODE_QA` test pipeline; - единый `agent runtime`;
- IntentRouterV2 as canonical router; - `IntentRouterV2` как канонический router и retrieval planner;
- router-driven layered retrieval; - stage-based execution внутри `agent.runtime`;
- infrastructure `rag` только для indexing/retrieval/storage;
- evidence-first answer synthesis; - evidence-first answer synthesis;
- diagnostics-first tuning; - diagnostics-first tuning;
- no UI dependency; - no UI dependency;
@@ -870,9 +871,9 @@ flowchart TD
**Target Architecture** **Target Architecture**
- Router - Router
- Graphs / pipelines для `CODE`, `DOCS`, `CROSS_DOMAIN`, `GENERAL` - unified runtime
- CODE RAG + DOCS RAG - CODE RAG + DOCS RAG
- evidence gate - evidence gates
- synthesis layer - synthesis layer
- diagnostics - diagnostics
- генерация технической документации из code / docs / system analysis - генерация технической документации из code / docs / system analysis
@@ -880,8 +881,8 @@ flowchart TD
**MVP-now** **MVP-now**
- изолированный test-first пайплайн; - единый test-first runtime;
- цепочка: `user query → IntentRouterV2 → retrieval plan → layered retrieval → evidence gate → LLM answer → diagnostics`; - цепочка: `user query → IntentRouterV2 → retrieval planning → runtime retrieval → context normalization → evidence gate 1 → answer policy → LLM answer → evidence gate 2 → finalization/diagnostics`;
- основной домен: `CODE`; - основной домен: `CODE`;
- основные сценарии: - основные сценарии:
- `OPEN_FILE` - `OPEN_FILE`
@@ -889,35 +890,42 @@ flowchart TD
- `FIND_TESTS` - `FIND_TESTS`
- `FIND_ENTRYPOINTS` - `FIND_ENTRYPOINTS`
- `GENERAL_QA` - `GENERAL_QA`
- `TRACE_FLOW`
- `ARCHITECTURE`
- UI-интеграция не требуется для текущего этапа; - UI-интеграция не требуется для текущего этапа;
- docs retrieval не обязателен для текущего milestone; - docs retrieval не обязателен для текущего milestone;
- legacy `RouterService` не считается целевой архитектурой и в перспективе будет заменён. - legacy orchestration удалён из актуального execution path.
```mermaid ```mermaid
flowchart TD flowchart TD
U[User Query] --> R[IntentRouterV2] U[User Query] --> R[IntentRouterV2]
R --> P[Retrieval Plan] R --> P[Retrieval Planning]
P --> G[Layered Retrieval] P --> X[Runtime Retrieval]
G --> E[Evidence Gate] X --> C[Context Normalization]
E --> A[LLM Answer] C --> E1[Evidence Gate 1]
E --> D[Diagnostics] E1 --> AP[Answer Policy]
A --> D AP --> A[LLM Answer]
AP --> D[Diagnostics]
A --> E2[Evidence Gate 2]
E2 --> F[Finalization]
F --> D
``` ```
Текущий milestone — test-first и code-first; этот пайплайн настраивается изолированно до интеграции в полный agent runtime. Текущий milestone — test-first и code-first; этот runtime уже является каноническим execution path для MVP.
### 4.1.3. Канонический test-first пайплайн (CODE_QA) ### 4.1.3. Канонический MVP runtime (CODE-first)
Единая точка входа изолированного пайплайна — пакет `app.modules.rag.code_qa_pipeline`: Единая точка входа исполнения — пакет `app.modules.agent.runtime`:
- **Роутер:** только `IntentRouterV2`; legacy `RouterService` не используется. - **Роутер:** `app.modules.agent.intent_router_v2`; он отвечает и за routing, и за retrieval planning.
- **Контракты:** `RouterResult` (IntentRouterResult), `RetrievalRequest`, `RetrievalResult`, `EvidenceBundle`, `AnswerSynthesisInput`, `DiagnosticsReport`. - **LLM-слой:** `app.modules.agent.llm`; здесь живут `AgentLlmService`, `PromptLoader` и системные prompt assets.
- **Цепочка:** запрос → IntentRouterV2 → RetrievalRequest → layered retrieval (через адаптер) → нормализованный RetrievalResult → EvidenceBundle → evidence gate → AnswerSynthesisInput → diagnostics. - **Runtime:** `app.modules.agent.runtime`; внутри него stages разложены по подпакетам `retrieval`, `context`, `gates`, `answer_policy`, `generation`, `finalization`.
- **Evidence gate:** общая проверка достаточности evidence по сценарию (OPEN_FILE, EXPLAIN, FIND_TESTS, FIND_ENTRYPOINTS, GENERAL_QA); при недостатке — degraded/insufficient, без уверенного ответа. - **Цепочка:** запрос → `IntentRouterV2` → retrieval planning → runtime retrieval adapter → нормализованный context/evidence → evidence gate 1 → answer policy → LLM generation → evidence gate 2 → finalization → diagnostics.
- **Диагностика:** Level 1 (summary) и Level 2 (detail), машинно-читаемые коды причин (`failure_reasons`: `target_not_resolved`, `path_scope_empty`, `layer_c0_empty`, `insufficient_evidence`, `tests_not_found`, `entrypoints_not_found` и др.). - **Evidence gates:** pre/post проверки достаточности evidence и качества ответа по сценарию.
- **Запуск пайплайна в тестах:** `CodeQAPipelineRunner(router=..., retrieval_adapter=...)`; метод `run(user_query, rag_session_id)` возвращает `CodeQAPipelineResult` с полной диагностикой. - **Диагностика:** runtime возвращает machine-readable diagnostics и trace по стадиям.
- **RAG:** `app.modules.rag` больше не содержит agent use-case слоев; он остается инфраструктурой indexing/retrieval/storage.
Тесты: `tests/pipeline_setup/pipeline_intent_rag/test_canonical_code_qa_pipeline.py` (роутер → retrieval request, нормализованный результат, evidence gate, диагностика). Тесты: `pipeline_setup_v3` и связанные suite-ы проверяют канонический runtime и его stage-based execution.
## 4.2. Router ## 4.2. Router
@@ -926,7 +934,7 @@ Router определяет:
- intent; - intent;
- sub-intent; - sub-intent;
- confidence; - confidence;
- подходящий graph; - подходящий execution path;
- требования к retrieval plan. - требования к retrieval plan.
Целевые домены: Целевые домены:
@@ -935,9 +943,22 @@ Router определяет:
- `CROSS_DOMAIN` - `CROSS_DOMAIN`
- `GENERAL` - `GENERAL`
## 4.3. Graphs / pipelines ## 4.3. Runtime Stages
Graph — это специализированный сценарий обработки запроса. В текущем MVP execution path реализован не через graph engine, а через единый runtime с явными stage-компонентами.
Текущие стадии:
- `IntentRouterV2`
- `retrieval planning`
- `runtime retrieval`
- `context normalization`
- `evidence gate 1`
- `answer policy`
- `LLM generation`
- `evidence gate 2`
- `finalization + diagnostics`
Если сценарии в будущем начнут расходиться по структуре, а не только по policy-логике шагов, следующим шагом будет рассмотрен переход на graph-based orchestration.
Для MVP целесообразны как минимум: Для MVP целесообразны как минимум:
- `CodeOpenGraph` - `CodeOpenGraph`
@@ -1186,4 +1207,3 @@ DOCS и CROSS_DOMAIN остаются частью target architecture; в те
- богатые fact-индексы по всем доменам; - богатые fact-индексы по всем доменам;
- полный reference graph документации; - полный reference graph документации;
- глубокая автоматизация подготовки системной аналитики. - глубокая автоматизация подготовки системной аналитики.

View File

@@ -1,37 +1,37 @@
# Модуль agent # Модуль agent
## 1. Назначение ## 1. Назначение
Модуль обеспечивает выполнение code-QA пайплайна для pipeline_setup_v3 и интеграцию с chat-слоем через адаптер к контракту `AgentRunner`. Оркестрация основана на **IntentRouterV2** (RAG) и **CodeQaRuntimeExecutor** (роутинг → retrieval → evidence gate → генерация ответа). Модуль обеспечивает выполнение code-QA пайплайна для pipeline_setup_v3 и интеграцию с chat-слоем через адаптер к контракту `AgentRunner`. Оркестрация основана на **IntentRouterV2** (RAG) и **AgentRuntimeExecutor** (роутинг → retrieval → evidence gate → генерация ответа).
## 2. Состав модуля ## 2. Состав модуля
- **code_qa_runtime/** — рантайм выполнения code-QA: роутер интентов, retrieval, evidence gate, выбор промпта и генерация ответа (LLM). - **runtime/** — единственный orchestration-слой. На верхнем уровне содержит только файлы рантайма, а шаги исполнения вынесены в `runtime/steps/*` (`retrieval`, `context`, `gates`, `answer_policy`, `generation`, `finalization`, `explain`). Публичный API: `AgentRuntimeExecutor`, `RuntimeRetrievalAdapter`, `RuntimeRepoContextFactory`, модели `Runtime*`.
- **llm/** — сервис вызова LLM (GigaChat) с загрузкой системных промптов через `PromptLoader`. - **llm/** — сервис вызова LLM (GigaChat) с загрузкой системных промптов через `PromptLoader`.
- **prompt_loader.py** — загрузка текстов промптов из каталога `prompts/`. - **llm/prompt_loader.py** — загрузка системных промптов из `llm/prompts.yml`.
- **code_qa_runner_adapter.py** — адаптер `CodeQaRuntimeExecutor` к протоколу `AgentRunner` для использования из chat (async `run` → sync `execute` в executor). - **runtime/code_qa_runner_adapter.py** — адаптер `AgentRuntimeExecutor` к протоколу `AgentRunner` для использования из chat (async `run` → sync `execute` в executor).
## 3. Диаграмма зависимостей ## 3. Диаграмма зависимостей
```mermaid ```mermaid
classDiagram classDiagram
class CodeQaRuntimeExecutor class AgentRuntimeExecutor
class CodeQaRunnerAdapter class CodeQaRunnerAdapter
class AgentLlmService class AgentLlmService
class PromptLoader class PromptLoader
class IntentRouterV2 class IntentRouterV2
class CodeQaRetrievalAdapter class RuntimeRetrievalAdapter
CodeQaRunnerAdapter --> CodeQaRuntimeExecutor CodeQaRunnerAdapter --> AgentRuntimeExecutor
CodeQaRuntimeExecutor --> AgentLlmService AgentRuntimeExecutor --> AgentLlmService
CodeQaRuntimeExecutor --> IntentRouterV2 AgentRuntimeExecutor --> IntentRouterV2
CodeQaRuntimeExecutor --> CodeQaRetrievalAdapter AgentRuntimeExecutor --> RuntimeRetrievalAdapter
AgentLlmService --> PromptLoader AgentLlmService --> PromptLoader
``` ```
## 4. Точки входа ## 4. Точки входа
- **Тесты pipeline_setup_v3**: `AgentRuntimeAdapter` импортирует `CodeQaRuntimeExecutor`, `IntentRouterV2`, `CodeQaRepoContextFactory`, `CodeQaRetrievalAdapter`, `AgentLlmService`, `PromptLoader` напрямую из соответствующих пакетов. - **Тесты pipeline_setup_v3**: `AgentRuntimeAdapter` импортирует `AgentRuntimeExecutor`, `IntentRouterV2`, `RuntimeRepoContextFactory`, `RuntimeRetrievalAdapter`, `AgentLlmService`, `PromptLoader` из `app.modules.agent.runtime` и соответствующих пакетов.
- **Приложение (chat)**: `ModularApplication` собирает `CodeQaRuntimeExecutor` и оборачивает его в `CodeQaRunnerAdapter`; chat передаёт адаптер как `agent_runner` в `ChatModule`. - **Приложение (chat)**: `ModularApplication` собирает `AgentRuntimeExecutor` и оборачивает его в `CodeQaRunnerAdapter`; chat передаёт адаптер как `agent_runner` в `ChatModule`.
## 5. Промпты ## 5. Промпты
Используются только промпты, загружаемые из `prompts/`: Используются только промпты, загружаемые из `llm/prompts.yml`:
- **code_qa_*** — ответы по sub_intent (architecture, explain, find_entrypoints, find_tests, general, open_file, trace_flow, degraded, repair). - **code_qa_*** — ответы по sub_intent (architecture, explain, find_entrypoints, find_tests, general, open_file, trace_flow, degraded, repair).
- **rag_intent_router_v2** — классификация интента в IntentRouterV2. - **rag_intent_router_v2** — классификация интента в IntentRouterV2.
- **code_explain_answer_v2** — прямой code-explain в chat (direct_service). - **code_explain_answer_v2** — прямой code-explain в chat (direct_service).

View File

@@ -1,15 +0,0 @@
from app.modules.agent.code_qa_runtime.executor import CodeQaRuntimeExecutor
from app.modules.agent.code_qa_runtime.models import (
CodeQaDraftAnswer,
CodeQaExecutionState,
CodeQaFinalResult,
CodeQaValidationResult,
)
__all__ = [
"CodeQaDraftAnswer",
"CodeQaExecutionState",
"CodeQaFinalResult",
"CodeQaRuntimeExecutor",
"CodeQaValidationResult",
]

View File

@@ -1,73 +0,0 @@
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from app.modules.rag.code_qa_pipeline.contracts import (
AnswerSynthesisInput as CodeQaAnswerSynthesisInput,
)
from app.modules.rag.code_qa_pipeline.contracts import (
DiagnosticsReport as CodeQaDiagnosticsReport,
)
from app.modules.rag.code_qa_pipeline.contracts import (
EvidenceBundle as CodeQaEvidencePack,
)
from app.modules.rag.code_qa_pipeline.contracts import (
RetrievalRequest as CodeQaRetrievalRequest,
)
from app.modules.rag.code_qa_pipeline.contracts import (
RetrievalResult as CodeQaRetrievalResult,
)
from app.modules.rag.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext
class CodeQaDraftAnswer(BaseModel):
model_config = ConfigDict(extra="forbid")
prompt_name: str
prompt_payload: str
answer: str = ""
class CodeQaValidationResult(BaseModel):
model_config = ConfigDict(extra="forbid")
passed: bool = False
action: str = "return"
reasons: list[str] = Field(default_factory=list)
class CodeQaFinalResult(BaseModel):
model_config = ConfigDict(extra="forbid")
final_answer: str
answer_mode: str = "normal"
repair_used: bool = False
llm_used: bool = False
draft_answer: CodeQaDraftAnswer | None = None
validation: CodeQaValidationResult = Field(default_factory=CodeQaValidationResult)
router_result: IntentRouterResult | None = None
retrieval_request: CodeQaRetrievalRequest | None = None
retrieval_result: CodeQaRetrievalResult | None = None
evidence_pack: CodeQaEvidencePack | None = None
diagnostics: CodeQaDiagnosticsReport
runtime_trace: list[dict[str, Any]] = Field(default_factory=list)
class CodeQaExecutionState(BaseModel):
model_config = ConfigDict(extra="forbid")
user_query: str
rag_session_id: str
conversation_state: ConversationState = Field(default_factory=ConversationState)
repo_context: RepoContext = Field(default_factory=RepoContext)
router_result: IntentRouterResult | None = None
retrieval_request: CodeQaRetrievalRequest | None = None
retrieval_result: CodeQaRetrievalResult | None = None
evidence_pack: CodeQaEvidencePack | None = None
synthesis_input: CodeQaAnswerSynthesisInput | None = None
diagnostics: CodeQaDiagnosticsReport | None = None
answer_mode: str = "normal"
degraded_message: str = ""
final_result: CodeQaFinalResult | None = None

View File

@@ -0,0 +1,19 @@
from app.modules.agent.intent_router_v2.models import (
ConversationState,
IntentDecision,
IntentRouterResult,
QueryAnchor,
QueryPlan,
RepoContext,
)
from app.modules.agent.intent_router_v2.router import IntentRouterV2
__all__ = [
"ConversationState",
"IntentDecision",
"IntentRouterResult",
"IntentRouterV2",
"QueryAnchor",
"QueryPlan",
"RepoContext",
]

View File

@@ -0,0 +1,4 @@
from app.modules.agent.intent_router_v2.analysis.normalization import QueryNormalizer
from app.modules.agent.intent_router_v2.analysis.query_plan_builder import QueryPlanBuilder
__all__ = ["QueryNormalizer", "QueryPlanBuilder"]

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
import re import re
from app.modules.rag.intent_router_v2.models import AnchorSpan, QueryAnchor from app.modules.agent.intent_router_v2.models import AnchorSpan, QueryAnchor
from app.modules.rag.intent_router_v2.analysis.normalization_terms import KeyTermCanonicalizer from app.modules.agent.intent_router_v2.analysis.normalization_terms import KeyTermCanonicalizer
from app.modules.rag.intent_router_v2.analysis.symbol_rules import COMMON_PATH_SEGMENTS, PY_KEYWORDS from app.modules.agent.intent_router_v2.analysis.symbol_rules import COMMON_PATH_SEGMENTS, PY_KEYWORDS
from app.modules.rag.intent_router_v2.analysis.term_mapping import RuEnTermMapper from app.modules.agent.intent_router_v2.analysis.term_mapping import RuEnTermMapper
_FILE_PATTERN = re.compile(r"(?P<value>\b(?:[\w.-]+/)*[\w.-]+\.(?:py|md|rst|txt|yaml|yml|json|toml|ini|cfg)\b)") _FILE_PATTERN = re.compile(r"(?P<value>\b(?:[\w.-]+/)*[\w.-]+\.(?:py|md|rst|txt|yaml|yml|json|toml|ini|cfg)\b)")
_PATH_HINT_PATTERN = re.compile(r"(?P<value>\b(?:src|app|docs|tests)/[\w./-]*[\w-]\b)") _PATH_HINT_PATTERN = re.compile(r"(?P<value>\b(?:src|app|docs|tests)/[\w./-]*[\w-]\b)")

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import QueryAnchor from app.modules.agent.intent_router_v2.models import QueryAnchor
class AnchorSpanValidator: class AnchorSpanValidator:

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.analysis.followup_detector import FollowUpDetector from app.modules.agent.intent_router_v2.analysis.followup_detector import FollowUpDetector
from app.modules.rag.intent_router_v2.models import ConversationState, QueryAnchor from app.modules.agent.intent_router_v2.models import ConversationState, QueryAnchor
class ConversationAnchorBuilder: class ConversationAnchorBuilder:

View File

@@ -2,8 +2,8 @@ from __future__ import annotations
import re import re
from app.modules.rag.intent_router_v2.analysis.normalization import FILE_PATH_RE from app.modules.agent.intent_router_v2.analysis.normalization import FILE_PATH_RE
from app.modules.rag.intent_router_v2.analysis.symbol_rules import COMMON_PATH_SEGMENTS, PY_KEYWORDS from app.modules.agent.intent_router_v2.analysis.symbol_rules import COMMON_PATH_SEGMENTS, PY_KEYWORDS
_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]{2,}") _IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]{2,}")

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import QueryAnchor from app.modules.agent.intent_router_v2.models import QueryAnchor
class KeywordHintSanitizer: class KeywordHintSanitizer:

View File

@@ -13,7 +13,7 @@ SNAKE_RE = re.compile(r"(?<!\w)[a-z][a-z0-9]*(?:_[a-z0-9]+)+(?!\w)")
SPACE_BEFORE_PUNCT_RE = re.compile(r"\s+([,.:;?!])") SPACE_BEFORE_PUNCT_RE = re.compile(r"\s+([,.:;?!])")
SPACE_AFTER_PUNCT_RE = re.compile(r"([,.:;?!])(?=(?:[\"'(\[A-Za-zА-ЯЁа-яё]))") SPACE_AFTER_PUNCT_RE = re.compile(r"([,.:;?!])(?=(?:[\"'(\[A-Za-zА-ЯЁа-яё]))")
WS_RE = re.compile(r"\s+") WS_RE = re.compile(r"\s+")
QUOTE_TRANSLATION = str.maketrans({"«": '"', "»": '"', "": '"', "": '"', "": "'", "": "'"}) QUOTE_TRANSLATION = str.maketrans({"«": '"', "»": '"', "\u201c": '"', "\u201d": '"', "\u2018": "'", "\u2019": "'"})
class QueryNormalizer: class QueryNormalizer:

View File

@@ -0,0 +1,3 @@
from app.modules.agent.intent_router_v2.analysis.normalization import QueryNormalizer
__all__ = ["QueryNormalizer"]

View File

@@ -1,16 +1,16 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.analysis.anchor_extractor import AnchorExtractor from app.modules.agent.intent_router_v2.analysis.anchor_extractor import AnchorExtractor
from app.modules.rag.intent_router_v2.analysis.anchor_span_validator import AnchorSpanValidator from app.modules.agent.intent_router_v2.analysis.anchor_span_validator import AnchorSpanValidator
from app.modules.rag.intent_router_v2.analysis.conversation_anchor_builder import ConversationAnchorBuilder from app.modules.agent.intent_router_v2.analysis.conversation_anchor_builder import ConversationAnchorBuilder
from app.modules.rag.intent_router_v2.analysis.keyword_hint_builder import KeywordHintBuilder from app.modules.agent.intent_router_v2.analysis.keyword_hint_builder import KeywordHintBuilder
from app.modules.rag.intent_router_v2.analysis.keyword_hint_sanitizer import KeywordHintSanitizer from app.modules.agent.intent_router_v2.analysis.keyword_hint_sanitizer import KeywordHintSanitizer
from app.modules.rag.intent_router_v2.models import ConversationState, QueryAnchor, QueryPlan from app.modules.agent.intent_router_v2.models import ConversationState, QueryAnchor, QueryPlan
from app.modules.rag.intent_router_v2.analysis.negation_detector import NegationDetector from app.modules.agent.intent_router_v2.analysis.negation_detector import NegationDetector
from app.modules.rag.intent_router_v2.analysis.normalization import QueryNormalizer from app.modules.agent.intent_router_v2.analysis.normalization import QueryNormalizer
from app.modules.rag.intent_router_v2.analysis.sub_intent_detector import SubIntentDetector from app.modules.agent.intent_router_v2.analysis.sub_intent_detector import SubIntentDetector
from app.modules.rag.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request, is_test_related_token from app.modules.agent.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request, is_test_related_token
from app.modules.rag.intent_router_v2.analysis.term_mapping import RuEnTermMapper from app.modules.agent.intent_router_v2.analysis.term_mapping import RuEnTermMapper
class QueryPlanBuilder: class QueryPlanBuilder:

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import re import re
from app.modules.rag.intent_router_v2.analysis.normalization_terms import KeyTermCanonicalizer from app.modules.agent.intent_router_v2.analysis.normalization_terms import KeyTermCanonicalizer
_WORD_RE = re.compile(r"[A-Za-zА-Яа-яЁё-]+") _WORD_RE = re.compile(r"[A-Za-zА-Яа-яЁё-]+")

View File

@@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from app.modules.agent.llm import AgentLlmService from app.modules.agent.llm import AgentLlmService
from app.modules.agent.prompt_loader import PromptLoader from app.modules.agent.llm.prompt_loader import PromptLoader
from app.modules.rag.intent_router_v2.intent.classifier import IntentClassifierV2 from app.modules.agent.intent_router_v2.intent.classifier import IntentClassifierV2
from app.modules.rag.intent_router_v2.router import IntentRouterV2 from app.modules.agent.intent_router_v2.router import IntentRouterV2
from app.modules.shared.env_loader import load_workspace_env from app.modules.shared.env_loader import load_workspace_env
from app.modules.shared.gigachat.client import GigaChatClient from app.modules.shared.gigachat.client import GigaChatClient
from app.modules.shared.gigachat.settings import GigaChatSettings from app.modules.shared.gigachat.settings import GigaChatSettings

View File

@@ -0,0 +1,5 @@
from app.modules.agent.intent_router_v2.intent.classifier import IntentClassifierV2
from app.modules.agent.intent_router_v2.intent.conversation_policy import ConversationPolicy
from app.modules.agent.intent_router_v2.intent.graph_id_resolver import GraphIdResolver
__all__ = ["IntentClassifierV2", "ConversationPolicy", "GraphIdResolver"]

View File

@@ -3,9 +3,9 @@ from __future__ import annotations
import json import json
import re import re
from app.modules.rag.intent_router_v2.models import ConversationState, IntentDecision from app.modules.agent.intent_router_v2.models import ConversationState, IntentDecision
from app.modules.rag.intent_router_v2.protocols import TextGenerator from app.modules.agent.intent_router_v2.protocols import TextGenerator
from app.modules.rag.intent_router_v2.analysis.test_signals import has_test_focus from app.modules.agent.intent_router_v2.analysis.test_signals import has_test_focus
_CODE_FILE_PATH_RE = re.compile( _CODE_FILE_PATH_RE = re.compile(
r"\b(?:[\w.-]+/)*[\w.-]+\.(?:py|js|jsx|ts|tsx|java|kt|go|rb|php|c|cc|cpp|h|hpp|cs|swift|rs)(?!\w)\b", r"\b(?:[\w.-]+/)*[\w.-]+\.(?:py|js|jsx|ts|tsx|java|kt|go|rb|php|c|cc|cpp|h|hpp|cs|swift|rs)(?!\w)\b",

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import ConversationState, IntentDecision from app.modules.agent.intent_router_v2.models import ConversationState, IntentDecision
class ConversationPolicy: class ConversationPolicy:

View File

@@ -2,8 +2,8 @@ from __future__ import annotations
import logging import logging
from app.modules.rag.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext from app.modules.agent.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext
from app.modules.rag.intent_router_v2.router import IntentRouterV2 from app.modules.agent.intent_router_v2.router import IntentRouterV2
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import json import json
import logging import logging
from app.modules.rag.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext from app.modules.agent.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,4 @@
from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_spec_factory import RetrievalSpecFactory
from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_constraints_factory import RetrievalConstraintsFactory
__all__ = ["RetrievalSpecFactory", "RetrievalConstraintsFactory"]

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import EvidencePolicy from app.modules.agent.intent_router_v2.models import EvidencePolicy
class EvidencePolicyFactory: class EvidencePolicyFactory:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import LayerQuery, RepoContext from app.modules.agent.intent_router_v2.models import LayerQuery, RepoContext
class LayerQueryBuilder: class LayerQueryBuilder:

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import QueryAnchor, RetrievalConstraints, RetrievalProfile from app.modules.agent.intent_router_v2.models import QueryAnchor, RetrievalConstraints, RetrievalProfile
from app.modules.rag.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request from app.modules.agent.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request
class RetrievalConstraintsFactory: class RetrievalConstraintsFactory:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.models import ( from app.modules.agent.intent_router_v2.models import (
CodeRetrievalFilters, CodeRetrievalFilters,
ConversationState, ConversationState,
DocsRetrievalFilters, DocsRetrievalFilters,
@@ -8,7 +8,7 @@ from app.modules.rag.intent_router_v2.models import (
QueryAnchor, QueryAnchor,
RepoContext, RepoContext,
) )
from app.modules.rag.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request, is_test_related_token from app.modules.agent.intent_router_v2.analysis.test_signals import has_test_focus, is_negative_test_request, is_test_related_token
class RetrievalFilterBuilder: class RetrievalFilterBuilder:

View File

@@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.contracts.enums import RagLayer from app.modules.rag.contracts.enums import RagLayer
from app.modules.rag.intent_router_v2.retrieval.layer_query_builder import LayerQueryBuilder from app.modules.agent.intent_router_v2.retrieval_planning.layer_query_builder import LayerQueryBuilder
from app.modules.rag.intent_router_v2.models import ConversationState, QueryAnchor, RepoContext, RetrievalSpec from app.modules.agent.intent_router_v2.models import ConversationState, QueryAnchor, RepoContext, RetrievalSpec
from app.modules.rag.intent_router_v2.retrieval.retrieval_filter_builder import RetrievalFilterBuilder from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_filter_builder import RetrievalFilterBuilder
class RetrievalSpecFactory: class RetrievalSpecFactory:

View File

@@ -1,14 +1,14 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.intent_router_v2.intent.classifier import IntentClassifierV2 from app.modules.agent.intent_router_v2.intent.classifier import IntentClassifierV2
from app.modules.rag.intent_router_v2.intent.conversation_policy import ConversationPolicy from app.modules.agent.intent_router_v2.intent.conversation_policy import ConversationPolicy
from app.modules.rag.intent_router_v2.retrieval.evidence_policy_factory import EvidencePolicyFactory from app.modules.agent.intent_router_v2.retrieval_planning.evidence_policy_factory import EvidencePolicyFactory
from app.modules.rag.intent_router_v2.intent.graph_id_resolver import GraphIdResolver from app.modules.agent.intent_router_v2.intent.graph_id_resolver import GraphIdResolver
from app.modules.rag.intent_router_v2.logger import IntentRouterLogger from app.modules.agent.intent_router_v2.logger import IntentRouterLogger
from app.modules.rag.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext, SymbolResolution from app.modules.agent.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext, SymbolResolution
from app.modules.rag.intent_router_v2.analysis.query_plan_builder import QueryPlanBuilder from app.modules.agent.intent_router_v2.analysis.query_plan_builder import QueryPlanBuilder
from app.modules.rag.intent_router_v2.retrieval.retrieval_constraints_factory import RetrievalConstraintsFactory from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_constraints_factory import RetrievalConstraintsFactory
from app.modules.rag.intent_router_v2.retrieval.retrieval_spec_factory import RetrievalSpecFactory from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_spec_factory import RetrievalSpecFactory
class IntentRouterV2: class IntentRouterV2:

View File

@@ -1,3 +1,4 @@
from app.modules.agent.llm.service import AgentLlmService from app.modules.agent.llm.service import AgentLlmService
from app.modules.agent.llm.prompt_loader import PromptLoader
__all__ = ["AgentLlmService"] __all__ = ["AgentLlmService", "PromptLoader"]

View File

@@ -0,0 +1,27 @@
from pathlib import Path
import os
import yaml
class PromptLoader:
def __init__(self, prompts_path: Path | None = None) -> None:
base = prompts_path or Path(__file__).resolve().parent / "prompts.yml"
env_override = os.getenv("AGENT_PROMPTS_DIR", "").strip()
raw_path = Path(env_override) if env_override else base
self._path = raw_path / "prompts.yml" if raw_path.is_dir() else raw_path
self._prompts = self._load_prompts()
def load(self, name: str) -> str:
return str(self._prompts.get(name, "") or "").strip()
def _load_prompts(self) -> dict[str, str]:
if not self._path.is_file():
return {}
payload = yaml.safe_load(self._path.read_text(encoding="utf-8")) or {}
if not isinstance(payload, dict):
return {}
prompts = payload.get("prompts", payload)
if not isinstance(prompts, dict):
return {}
return {str(key): str(value or "") for key, value in prompts.items()}

View File

@@ -0,0 +1,280 @@
prompts:
code_explain_answer_v2: |
Объяснение кода осуществляется только с использованием предоставленного ExplainPack.
Правила:
- Сначала используйте доказательства.
- Каждый ключевой шаг в процессе должен содержать один или несколько идентификаторов доказательств в квадратных скобках, например, [entrypoint_1] или [excerpt_3].
- Не придумывайте символы, файлы, маршруты или фрагменты кода, отсутствующие в пакете.
- Если доказательства неполные, укажите это явно.
- В качестве якорей используйте выбранные точки входа и пути трассировки.
Верните Markdown со следующей структурой:
1. Краткое описание
2. Пошаговый процесс
3. Данные и побочные эффекты
4. Ошибки и граничные случаи
5. Указатели
Указатели должны представлять собой короткий маркированный список, сопоставляющий идентификаторы доказательств с местоположениями файлов.
code_qa_architecture_answer: |
Ты инженер, который объясняет устройство подсистемы только по наблюдаемым компонентам и связям из кода.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай архитектурное объяснение без лишней теории.
Строй ответ вокруг concrete facts из payload: `must_mention_components`, `must_mention_relations`, `must_use_relation_verbs`.
Если эти списки непустые, назови хотя бы часть компонентов и хотя бы одну наблюдаемую связь между ними.
Описывай не просто компоненты, а связи типа: создаёт, вызывает, регистрирует, читает, записывает, передаёт, оборачивает, импортирует, наследует.
Если связь не видна в payload, не додумывай её и не заменяй общими словами про управление подсистемой.
Методы и функции можно упоминать только как доказательство связи между компонентами, но не как основные "компоненты" ответа.
Затем коротко опиши границы ответственности, только если они реально видны в коде.
Не используй synthetic role labels как готовый пользовательский вывод, если они не поддержаны кодом.
Не придумывай скрытые слои и не расширяй архитектуру за пределы извлечённого контекста.
Не используй обязательные markdown-секции.
Не используй `semantic_hints` как primary explanation, особенно если `must_avoid_semantic_labels_as_primary_claims=true`.
Не используй raw retrieval labels вроде `dataflow_slice`, `execution_trace`, `trace_path` в финальном тексте.
Не используй абстрактные формулы вроде "главный компонент", "центральный управляющий компонент", "управляет потоками данных и состоянием системы", "этап пайплайна", если конкретная связь не раскрыта через наблюдаемые методы, поля или вызовы.
code_qa_degraded_answer: |
Ты формируешь осторожный деградированный ответ.
Нужно честно описать, что удалось подтвердить, а чего не хватает.
Не выдавай предположения за факты и не заполняй пробелы догадками.
code_qa_explain_answer: |
Ты senior Python-инженер и code reviewer, который объясняет устройство кода без домысливания.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Объясни, как работает сущность из вопроса пользователя, обычным инженерным текстом.
Начни с самого важного: что это за сущность и где она находится, если это видно.
Затем строй ответ вокруг concrete facts из payload: `must_mention_methods`, `must_mention_fields`, `must_mention_calls`, `must_mention_dependencies`, `must_mention_constructor_args`, `must_mention_files`.
Если эти списки непустые, назови хотя бы часть этих имён явно, а не заменяй их общей интерпретацией.
Если в `must_mention_methods` даны полные qname, можно назвать метод по короткому имени, но только если связь с целевой сущностью остаётся ясной.
Сначала идентифицируй сущность, затем назови только подтверждённые методы, аргументы, вызовы, поля и зависимости.
Если сигнатуры, аргументы, методы или вызовы не видны, прямо скажи, чего именно не видно, используя `fact_gaps`, и остановись на этом.
Не используй общие формулы без конкретных имён.
Если виден конструктор, метод или вызов, лучше назвать его явно, чем писать абстрактно про "инициализацию", "службы", "аргументы" или "компоненты".
Если вывод основан на косвенных признаках, явно пометь это как осторожный вывод.
Если сущность не найдена или evidence слабый, не пиши обычное объяснение — прямо скажи об этом и остановись.
Запрещено подменять concrete methods/fields/calls формулами вроде "принимает ряд аргументов", "имеет responsibilities", "используется в службах", "регистрирует основные службы", если в payload есть конкретные имена.
Не используй `semantic_hints` как основной каркас ответа. Они допустимы только как вторичное замечание и только если не противоречат C0/C1/C2.
Не используй обязательные секции и подзаголовки.
code_qa_explain_local_answer: |
Ты инженер, который объясняет локальный фрагмент кода без лишней теории и без перехода на уровень всей архитектуры.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай локальное объяснение по конкретному файлу, символу или короткому участку кода.
Сконцентрируйся на том, что делает этот участок, какие входы и выходы видны и какие ближайшие вызовы или зависимости заметны рядом.
Если виден только фрагмент, ограничь вывод тем, что прямо видно в этом фрагменте.
Не компенсируй нехватку локального контекста общими архитектурными фразами.
Не расписывай всю архитектуру проекта и не используй секции без необходимости.
code_qa_find_entrypoints_answer: |
Ты инженер, который находит подтверждённые точки входа и отдельно помечает только возможные кандидаты.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Найди точки входа, обработчики запуска или важные entrypoints.
Для подтверждённых HTTP route сначала называй их в прикладном виде: HTTP method и route path, например `GET /health`.
Затем коротко добавляй, где route объявлен и какой handler, функция, метод или контекст его обслуживает, если это видно.
Если во входе есть `required_entrypoints`, каждый такой route должен быть явно назван в ответе в виде `METHOD /path`.
Если во входе есть `confirmed_entrypoints` с `query_match=true`, не пиши, что route не найден, пока не перечислишь эти совпавшие подтверждённые route.
Подтверждённые entrypoints перечисляй первыми.
Кандидатов без явного route marker упоминай только если они действительно полезны, и явно помечай как кандидатов.
Не своди ответ к обсуждению декораторов вроде `@app.get`; пользователю важнее method, path и контекст.
Не используй искусственные секции, если ответ можно дать компактным списком или коротким абзацем.
Если кандидатов нет, не создавай отдельную строку или блок про их отсутствие.
Не заменяй `GET /health` абстрактной формулой вроде "route для health-check"; сначала всегда пиши method и path.
code_qa_find_tests_answer: |
Ты инженер, который ищет тестовое покрытие и различает прямые и косвенные тесты.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Найди связанные тесты и ответь, где они расположены.
Сначала назови прямые тесты, только если связь с сущностью подтверждается именем, импортом, вызовом или проверяемым поведением.
Если прямых тестов нет, прямо скажи это и только потом упомяни ближайшие косвенные тесты, если они есть.
Коротко поясни, что именно проверяется.
Не выдавай косвенные совпадения за подтверждённое покрытие и не используй отчётные секции без нужды.
Если косвенных тестов тоже нет, не добавляй отдельный пустой блок про их отсутствие.
code_qa_general_answer: |
Ты senior Python-инженер, который даёт обзорный ответ по подсистеме или проекту, но остаётся строго привязанным к коду из контекста.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай обзорный ответ по вопросу пользователя о коде, подсистеме или сценарии работы.
Сначала скажи, что можно уверенно подтвердить по коду, затем коротко укажи, какие файлы, классы, функции или route это подтверждают.
Если данных недостаточно, прямо скажи, чего именно не хватает.
Не подменяй обзор общими рассуждениями о типичной архитектуре таких систем.
Не используй секции без необходимости.
Не заполняй пробелы общими словами вроде "несколько модулей", "различные компоненты" или "ряд зависимостей", если конкретные имена не видны.
code_qa_open_file_answer: |
Ты технический ассистент, который помогает открыть конкретный файл и показать, что в нём реально видно.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Сосредоточься на указанном файле и отвечай коротко.
Обычно достаточно назвать путь файла и в 1-3 фразах сказать, какие конкретные сущности или элементы видны: класс, функция, метод, импорт, route, константа.
Не используй общие описания файла без конкретных имён.
Если в контексте виден только фрагмент файла, не добавляй общую фразу про то, что ответ основан на видимом фрагменте. Вместо этого просто ограничься тем, что реально видно.
Не превращай ответ в архитектурный обзор проекта.
Не используй секции и подзаголовки.
Если файла нет, ответь одной короткой фразой: `Файл <path> не найден.`
Не придумывай анализ отсутствующего файла.
code_qa_repair_answer: |
Ты исправляешь черновой ответ по коду после проверки groundedness.
Сделай ответ короче, точнее и строже по evidence payload.
Если проверка требует not_found или degraded формулировку, отрази это явно и убери спекуляции.
Если в `repair_focus` есть причины для `EXPLAIN`, перепиши ответ так, чтобы он назвал concrete methods, calls, fields, constructor args или dependencies из payload, а не общие responsibilities.
Если в `repair_focus` есть причины для `ARCHITECTURE`, перепиши ответ так, чтобы он назвал concrete components и связи с relation verbs из payload: создает, вызывает, читает, записывает, импортирует, наследует.
Если в `repair_focus` есть причины для `TRACE_FLOW`, перепиши ответ как последовательность concrete steps с явными methods/calls/edges из payload. Если виден только partial flow, так и скажи.
Если в `repair_focus` есть `semantic_labels_without_code_edges`, убери semantic role labels из основной формулировки, если они не подкреплены concrete code edges.
Если в `repair_focus` есть `contains_retrieval_artifacts` или `methods_as_primary_components`, убери raw retrieval labels и не выдавай методы за компоненты.
Если в `repair_focus` есть `overclaims_trace_completeness`, убери фразы про полный/полностью восстановленный flow, если payload не подтверждает это явно.
code_qa_trace_flow_answer: |
Ты инженер, который восстанавливает поток вызовов и движение данных только по доказуемой цепочке из контекста.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Проследи поток выполнения или поток данных по найденным артефактам.
Строй ответ вокруг `must_mention_flow_steps`, `must_mention_calls` и `must_mention_sequence_edges` из payload.
Старайся описывать шаги последовательно и коротко, без лишних подзаголовков: сначала, затем, после этого, в конце.
Не склеивай шаги, если между ними нет прямой связи в коде или явно подтверждённого отношения в извлечённых данных.
Если поток восстанавливается только частично, так и скажи, опираясь на `fact_gaps`, и не заявляй, что flow восстановлен полностью.
Не заменяй конкретные шаги общими словами вроде "обрабатывает запрос", "передаёт данные" или "инициализирует службы", если можно назвать конкретный вызов, метод или route.
Не используй сильные формулировки вроде "полностью восстанавливается", "полный поток виден", если payload показывает только часть цепочки.
rag_intent_router_v2: |
Ты intent-router для layered RAG.
На вход ты получаешь JSON с полями:
- message: текущий запрос пользователя
- active_intent: текущий активный intent диалога или null
- last_query: предыдущий запрос пользователя
- allowed_intents: допустимые intent'ы
Выбери ровно один intent из allowed_intents.
Верни только JSON без markdown и пояснений.
Строгий формат ответа:
{"intent":"<one_of_allowed_intents>","confidence":<number_0_to_1>,"reason":"<short_reason>"}
Правила:
- CODE_QA: объяснение по коду, архитектуре, классам, методам, файлам, блокам кода, поведению приложения по реализации.
- DOCS_QA: объяснение по документации, README, markdown, specs, runbooks, разделам документации.
- GENERATE_DOCS_FROM_CODE: просьба сгенерировать, подготовить или обновить документацию по коду.
- PROJECT_MISC: прочие вопросы по проекту, не относящиеся явно к коду или документации.
Приоритет:
- Если пользователь просит именно подготовить документацию по коду, выбирай GENERATE_DOCS_FROM_CODE.
- Если пользователь спрашивает про конкретный класс, файл, метод или блок кода, выбирай CODE_QA.
- Если пользователь спрашивает про README, docs, markdown или конкретную документацию, выбирай DOCS_QA.
- Если сигнал неочевиден, выбирай PROJECT_MISC и confidence <= 0.6.

View File

@@ -1,6 +1,6 @@
import logging import logging
from app.modules.agent.prompt_loader import PromptLoader from app.modules.agent.llm.prompt_loader import PromptLoader
from app.modules.shared.gigachat.client import GigaChatClient from app.modules.shared.gigachat.client import GigaChatClient
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

View File

@@ -1,15 +0,0 @@
from pathlib import Path
import os
class PromptLoader:
def __init__(self, prompts_dir: Path | None = None) -> None:
base = prompts_dir or Path(__file__).resolve().parent / "prompts"
env_override = os.getenv("AGENT_PROMPTS_DIR", "").strip()
self._dir = Path(env_override) if env_override else base
def load(self, name: str) -> str:
path = self._dir / f"{name}.txt"
if not path.is_file():
return ""
return path.read_text(encoding="utf-8").strip()

View File

@@ -1,17 +0,0 @@
Объяснение кода осуществляется только с использованием предоставленного ExplainPack.
Правила:
- Сначала используйте доказательства.
- Каждый ключевой шаг в процессе должен содержать один или несколько идентификаторов доказательств в квадратных скобках, например, [entrypoint_1] или [excerpt_3].
- Не придумывайте символы, файлы, маршруты или фрагменты кода, отсутствующие в пакете.
- Если доказательства неполные, укажите это явно.
- В качестве якорей используйте выбранные точки входа и пути трассировки.
Верните Markdown со следующей структурой:
1. Краткое описание
2. Пошаговый процесс
3. Данные и побочные эффекты
4. Ошибки и граничные случаи
5. Указатели
Указатели должны представлять собой короткий маркированный список, сопоставляющий идентификаторы доказательств с местоположениями файлов.

View File

@@ -1,31 +0,0 @@
Ты инженер, который объясняет устройство подсистемы только по наблюдаемым компонентам и связям из кода.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай архитектурное объяснение без лишней теории.
Строй ответ вокруг concrete facts из payload: `must_mention_components`, `must_mention_relations`, `must_use_relation_verbs`.
Если эти списки непустые, назови хотя бы часть компонентов и хотя бы одну наблюдаемую связь между ними.
Описывай не просто компоненты, а связи типа: создаёт, вызывает, регистрирует, читает, записывает, передаёт, оборачивает, импортирует, наследует.
Если связь не видна в payload, не додумывай её и не заменяй общими словами про управление подсистемой.
Методы и функции можно упоминать только как доказательство связи между компонентами, но не как основные "компоненты" ответа.
Затем коротко опиши границы ответственности, только если они реально видны в коде.
Не используй synthetic role labels как готовый пользовательский вывод, если они не поддержаны кодом.
Не придумывай скрытые слои и не расширяй архитектуру за пределы извлечённого контекста.
Не используй обязательные markdown-секции.
Не используй `semantic_hints` как primary explanation, особенно если `must_avoid_semantic_labels_as_primary_claims=true`.
Не используй raw retrieval labels вроде `dataflow_slice`, `execution_trace`, `trace_path` в финальном тексте.
Не используй абстрактные формулы вроде "главный компонент", "центральный управляющий компонент", "управляет потоками данных и состоянием системы", "этап пайплайна", если конкретная связь не раскрыта через наблюдаемые методы, поля или вызовы.

View File

@@ -1,3 +0,0 @@
Ты формируешь осторожный деградированный ответ.
Нужно честно описать, что удалось подтвердить, а чего не хватает.
Не выдавай предположения за факты и не заполняй пробелы догадками.

View File

@@ -1,32 +0,0 @@
Ты senior Python-инженер и code reviewer, который объясняет устройство кода без домысливания.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Объясни, как работает сущность из вопроса пользователя, обычным инженерным текстом.
Начни с самого важного: что это за сущность и где она находится, если это видно.
Затем строй ответ вокруг concrete facts из payload: `must_mention_methods`, `must_mention_fields`, `must_mention_calls`, `must_mention_dependencies`, `must_mention_constructor_args`, `must_mention_files`.
Если эти списки непустые, назови хотя бы часть этих имён явно, а не заменяй их общей интерпретацией.
Если в `must_mention_methods` даны полные qname, можно назвать метод по короткому имени, но только если связь с целевой сущностью остаётся ясной.
Сначала идентифицируй сущность, затем назови только подтверждённые методы, аргументы, вызовы, поля и зависимости.
Если сигнатуры, аргументы, методы или вызовы не видны, прямо скажи, чего именно не видно, используя `fact_gaps`, и остановись на этом.
Не используй общие формулы без конкретных имён.
Если виден конструктор, метод или вызов, лучше назвать его явно, чем писать абстрактно про "инициализацию", "службы", "аргументы" или "компоненты".
Если вывод основан на косвенных признаках, явно пометь это как осторожный вывод.
Если сущность не найдена или evidence слабый, не пиши обычное объяснение — прямо скажи об этом и остановись.
Запрещено подменять concrete methods/fields/calls формулами вроде "принимает ряд аргументов", "имеет responsibilities", "используется в службах", "регистрирует основные службы", если в payload есть конкретные имена.
Не используй `semantic_hints` как основной каркас ответа. Они допустимы только как вторичное замечание и только если не противоречат C0/C1/C2.
Не используй обязательные секции и подзаголовки.

View File

@@ -1,23 +0,0 @@
Ты инженер, который объясняет локальный фрагмент кода без лишней теории и без перехода на уровень всей архитектуры.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай локальное объяснение по конкретному файлу, символу или короткому участку кода.
Сконцентрируйся на том, что делает этот участок, какие входы и выходы видны и какие ближайшие вызовы или зависимости заметны рядом.
Если виден только фрагмент, ограничь вывод тем, что прямо видно в этом фрагменте.
Не компенсируй нехватку локального контекста общими архитектурными фразами.
Не расписывай всю архитектуру проекта и не используй секции без необходимости.

View File

@@ -1,29 +0,0 @@
Ты инженер, который находит подтверждённые точки входа и отдельно помечает только возможные кандидаты.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Найди точки входа, обработчики запуска или важные entrypoints.
Для подтверждённых HTTP route сначала называй их в прикладном виде: HTTP method и route path, например `GET /health`.
Затем коротко добавляй, где route объявлен и какой handler, функция, метод или контекст его обслуживает, если это видно.
Если во входе есть `required_entrypoints`, каждый такой route должен быть явно назван в ответе в виде `METHOD /path`.
Если во входе есть `confirmed_entrypoints` с `query_match=true`, не пиши, что route не найден, пока не перечислишь эти совпавшие подтверждённые route.
Подтверждённые entrypoints перечисляй первыми.
Кандидатов без явного route marker упоминай только если они действительно полезны, и явно помечай как кандидатов.
Не своди ответ к обсуждению декораторов вроде `@app.get`; пользователю важнее method, path и контекст.
Не используй искусственные секции, если ответ можно дать компактным списком или коротким абзацем.
Если кандидатов нет, не создавай отдельную строку или блок про их отсутствие.
Не заменяй `GET /health` абстрактной формулой вроде "route для health-check"; сначала всегда пиши method и path.

View File

@@ -1,24 +0,0 @@
Ты инженер, который ищет тестовое покрытие и различает прямые и косвенные тесты.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Найди связанные тесты и ответь, где они расположены.
Сначала назови прямые тесты, только если связь с сущностью подтверждается именем, импортом, вызовом или проверяемым поведением.
Если прямых тестов нет, прямо скажи это и только потом упомяни ближайшие косвенные тесты, если они есть.
Коротко поясни, что именно проверяется.
Не выдавай косвенные совпадения за подтверждённое покрытие и не используй отчётные секции без нужды.
Если косвенных тестов тоже нет, не добавляй отдельный пустой блок про их отсутствие.

View File

@@ -1,24 +0,0 @@
Ты senior Python-инженер, который даёт обзорный ответ по подсистеме или проекту, но остаётся строго привязанным к коду из контекста.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Дай обзорный ответ по вопросу пользователя о коде, подсистеме или сценарии работы.
Сначала скажи, что можно уверенно подтвердить по коду, затем коротко укажи, какие файлы, классы, функции или route это подтверждают.
Если данных недостаточно, прямо скажи, чего именно не хватает.
Не подменяй обзор общими рассуждениями о типичной архитектуре таких систем.
Не используй секции без необходимости.
Не заполняй пробелы общими словами вроде "несколько модулей", "различные компоненты" или "ряд зависимостей", если конкретные имена не видны.

View File

@@ -1,26 +0,0 @@
Ты технический ассистент, который помогает открыть конкретный файл и показать, что в нём реально видно.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Сосредоточься на указанном файле и отвечай коротко.
Обычно достаточно назвать путь файла и в 1-3 фразах сказать, какие конкретные сущности или элементы видны: класс, функция, метод, импорт, route, константа.
Не используй общие описания файла без конкретных имён.
Если в контексте виден только фрагмент файла, не добавляй общую фразу про то, что ответ основан на видимом фрагменте. Вместо этого просто ограничься тем, что реально видно.
Не превращай ответ в архитектурный обзор проекта.
Не используй секции и подзаголовки.
Если файла нет, ответь одной короткой фразой: `Файл <path> не найден.`
Не придумывай анализ отсутствующего файла.

View File

@@ -1,9 +0,0 @@
Ты исправляешь черновой ответ по коду после проверки groundedness.
Сделай ответ короче, точнее и строже по evidence payload.
Если проверка требует not_found или degraded формулировку, отрази это явно и убери спекуляции.
Если в `repair_focus` есть причины для `EXPLAIN`, перепиши ответ так, чтобы он назвал concrete methods, calls, fields, constructor args или dependencies из payload, а не общие responsibilities.
Если в `repair_focus` есть причины для `ARCHITECTURE`, перепиши ответ так, чтобы он назвал concrete components и связи с relation verbs из payload: создает, вызывает, читает, записывает, импортирует, наследует.
Если в `repair_focus` есть причины для `TRACE_FLOW`, перепиши ответ как последовательность concrete steps с явными methods/calls/edges из payload. Если виден только partial flow, так и скажи.
Если в `repair_focus` есть `semantic_labels_without_code_edges`, убери semantic role labels из основной формулировки, если они не подкреплены concrete code edges.
Если в `repair_focus` есть `contains_retrieval_artifacts` или `methods_as_primary_components`, убери raw retrieval labels и не выдавай методы за компоненты.
Если в `repair_focus` есть `overclaims_trace_completeness`, убери фразы про полный/полностью восстановленный flow, если payload не подтверждает это явно.

View File

@@ -1,25 +0,0 @@
Ты инженер, который восстанавливает поток вызовов и движение данных только по доказуемой цепочке из контекста.
Отвечай только по коду и структуре проекта, которые есть в контексте.
Пиши естественным инженерным языком, без искусственных markdown-секций и без повторов одной и той же мысли.
Если ответ можно дать в 1-3 фразах, не раздувай его.
Упоминай файлы, классы, функции, методы и связи только если они реально присутствуют в извлечённых данных.
Каждое содержательное утверждение по возможности привязывай к конкретному наблюдаемому имени или факту из контекста: пути файла, имени класса, функции, метода, аргумента, поля, route path, вызова или связи.
Если конкретные имена, параметры, вызовы или связи не видны, прямо скажи, чего именно не видно, вместо общих формулировок.
Не вводи новые сущности, зависимости или сценарии, которых нет в контексте.
Явно различай подтверждённые факты и осторожные выводы по косвенным признакам.
Если данных мало, честно скажи об этом вместо общего обзора.
Не используй жирные заголовки блоков, если пользователь их не просил.
Строго соблюдай контракт sub-intent и не подменяй локальный ответ архитектурным обзором.
Избегай расплывчатых и пустых формулировок вроде: "различные аргументы", "ряд аргументов", "различные подпакеты", "основные службы", "ключевой компонент", "играет роль", "представляет собой", если после них нет конкретики.
Не добавляй очевидные метафразы о том, что ответ основан на контексте или на видимом фрагменте, если это ничего не добавляет по сути.
Если сущность не найдена, остановись на факте not_found и не объясняй её предполагаемое назначение по одному только названию.
Не выводи пустые разделы, пустые списки и формулировки вида "кандидатов нет", если это не помогает ответу.
Проследи поток выполнения или поток данных по найденным артефактам.
Строй ответ вокруг `must_mention_flow_steps`, `must_mention_calls` и `must_mention_sequence_edges` из payload.
Старайся описывать шаги последовательно и коротко, без лишних подзаголовков: сначала, затем, после этого, в конце.
Не склеивай шаги, если между ними нет прямой связи в коде или явно подтверждённого отношения в извлечённых данных.
Если поток восстанавливается только частично, так и скажи, опираясь на `fact_gaps`, и не заявляй, что flow восстановлен полностью.
Не заменяй конкретные шаги общими словами вроде "обрабатывает запрос", "передаёт данные" или "инициализирует службы", если можно назвать конкретный вызов, метод или route.
Не используй сильные формулировки вроде "полностью восстанавливается", "полный поток виден", если payload показывает только часть цепочки.

View File

@@ -1,24 +0,0 @@
Ты intent-router для layered RAG.
На вход ты получаешь JSON с полями:
- message: текущий запрос пользователя
- active_intent: текущий активный intent диалога или null
- last_query: предыдущий запрос пользователя
- allowed_intents: допустимые intent'ы
Выбери ровно один intent из allowed_intents.
Верни только JSON без markdown и пояснений.
Строгий формат ответа:
{"intent":"<one_of_allowed_intents>","confidence":<number_0_to_1>,"reason":"<short_reason>"}
Правила:
- CODE_QA: объяснение по коду, архитектуре, классам, методам, файлам, блокам кода, поведению приложения по реализации.
- DOCS_QA: объяснение по документации, README, markdown, specs, runbooks, разделам документации.
- GENERATE_DOCS_FROM_CODE: просьба сгенерировать, подготовить или обновить документацию по коду.
- PROJECT_MISC: прочие вопросы по проекту, не относящиеся явно к коду или документации.
Приоритет:
- Если пользователь просит именно подготовить документацию по коду, выбирай GENERATE_DOCS_FROM_CODE.
- Если пользователь спрашивает про конкретный класс, файл, метод или блок кода, выбирай CODE_QA.
- Если пользователь спрашивает про README, docs, markdown или конкретную документацию, выбирай DOCS_QA.
- Если сигнал неочевиден, выбирай PROJECT_MISC и confidence <= 0.6.

View File

@@ -0,0 +1,20 @@
"""Публичный API runtime: оркестрация роутинг → retrieval → evidence gate → генерация ответа."""
from app.modules.agent.runtime.executor import AgentRuntimeExecutor
from app.modules.agent.runtime.models import (
RuntimeDraftAnswer,
RuntimeExecutionState,
RuntimeFinalResult,
)
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimeValidationResult
from app.modules.agent.runtime.steps.retrieval import RuntimeRepoContextFactory, RuntimeRetrievalAdapter
__all__ = [
"AgentRuntimeExecutor",
"RuntimeDraftAnswer",
"RuntimeExecutionState",
"RuntimeFinalResult",
"RuntimeRepoContextFactory",
"RuntimeRetrievalAdapter",
"RuntimeValidationResult",
]

View File

@@ -1,11 +1,11 @@
"""Адаптер CodeQaRuntimeExecutor к протоколу AgentRunner для интеграции с chat-слоем.""" """Адаптер AgentRuntimeExecutor к протоколу AgentRunner для интеграции с chat-слоем."""
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging import logging
from app.modules.agent.code_qa_runtime import CodeQaRuntimeExecutor from app.modules.agent.runtime import AgentRuntimeExecutor
from app.modules.contracts import AgentRunner from app.modules.contracts import AgentRunner
from app.schemas.chat import TaskResultType from app.schemas.chat import TaskResultType
@@ -13,9 +13,9 @@ LOGGER = logging.getLogger(__name__)
class CodeQaRunnerAdapter: class CodeQaRunnerAdapter:
"""Реализация AgentRunner через CodeQaRuntimeExecutor (sync execute в executor).""" """Реализация AgentRunner через AgentRuntimeExecutor (sync execute в executor)."""
def __init__(self, executor: CodeQaRuntimeExecutor) -> None: def __init__(self, executor: AgentRuntimeExecutor) -> None:
self._executor = executor self._executor = executor
async def run( async def run(

View File

@@ -1,60 +1,62 @@
"""Главный оркестратор runtime: роутинг → retrieval → evidence gate → генерация ответа (LLM)."""
from __future__ import annotations from __future__ import annotations
import logging import logging
from difflib import SequenceMatcher from difflib import SequenceMatcher
from time import perf_counter from time import perf_counter
from app.modules.agent.code_qa_runtime.answer_policy import CodeQaAnswerPolicy from app.modules.agent.runtime.models import RuntimeDraftAnswer, RuntimeExecutionState, RuntimeFinalResult
from app.modules.agent.code_qa_runtime.models import CodeQaDraftAnswer, CodeQaExecutionState, CodeQaFinalResult from app.modules.agent.intent_router_v2 import ConversationState, IntentRouterV2
from app.modules.agent.code_qa_runtime.post_gate import CodeQaPostEvidenceGate from app.modules.agent.intent_router_v2.models import SymbolResolution
from app.modules.agent.code_qa_runtime.prompt_payload_builder import CodeQaPromptPayloadBuilder from app.modules.agent.runtime.steps.retrieval import RuntimeRetrievalAdapter, RuntimeRepoContextFactory
from app.modules.agent.code_qa_runtime.prompt_selector import CodeQaPromptSelector from app.modules.agent.runtime.steps.context import (
from app.modules.agent.code_qa_runtime.repair import CodeQaAnswerRepairService build_answer_synthesis_input,
from app.modules.agent.code_qa_runtime.repo_context import CodeQaRepoContextFactory build_evidence_bundle,
from app.modules.agent.code_qa_runtime.retrieval_adapter import CodeQaRetrievalAdapter build_retrieval_request,
build_retrieval_result,
)
from app.modules.agent.runtime.steps.gates.pre.evidence_gate import evaluate_evidence
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimePostEvidenceGate
from app.modules.agent.runtime.steps.answer_policy import RuntimeAnswerPolicy
from app.modules.agent.runtime.steps.generation import RuntimePromptPayloadBuilder, RuntimePromptSelector, RuntimeAnswerGenerator
from app.modules.agent.runtime.steps.finalization import RuntimeAnswerRepairService, assemble_final_result
from app.modules.agent.llm import AgentLlmService from app.modules.agent.llm import AgentLlmService
from app.modules.rag.code_qa_pipeline.answer_synthesis import build_answer_synthesis_input
from app.modules.rag.code_qa_pipeline.diagnostics import build_diagnostics_report
from app.modules.rag.code_qa_pipeline.evidence_bundle_builder import build_evidence_bundle
from app.modules.rag.code_qa_pipeline.evidence_gate import evaluate_evidence
from app.modules.rag.code_qa_pipeline.retrieval_request_builder import build_retrieval_request
from app.modules.rag.code_qa_pipeline.retrieval_result_builder import build_retrieval_result
from app.modules.rag.intent_router_v2 import ConversationState, IntentRouterV2
from app.modules.rag.intent_router_v2.models import SymbolResolution
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
class CodeQaRuntimeExecutor: class AgentRuntimeExecutor:
def __init__( def __init__(
self, self,
llm: AgentLlmService | None, llm: AgentLlmService | None,
*, *,
router: IntentRouterV2 | None = None, router: IntentRouterV2 | None = None,
retrieval: CodeQaRetrievalAdapter | None = None, retrieval: RuntimeRetrievalAdapter | None = None,
repo_context_factory: CodeQaRepoContextFactory | None = None, repo_context_factory: RuntimeRepoContextFactory | None = None,
prompt_selector: CodeQaPromptSelector | None = None, prompt_selector: RuntimePromptSelector | None = None,
payload_builder: CodeQaPromptPayloadBuilder | None = None, payload_builder: RuntimePromptPayloadBuilder | None = None,
answer_policy: CodeQaAnswerPolicy | None = None, answer_policy: RuntimeAnswerPolicy | None = None,
post_gate: CodeQaPostEvidenceGate | None = None, post_gate: RuntimePostEvidenceGate | None = None,
) -> None: ) -> None:
self._llm = llm self._llm = llm
self._router = router or IntentRouterV2() self._router = router or IntentRouterV2()
self._retrieval = retrieval or CodeQaRetrievalAdapter() self._retrieval = retrieval or RuntimeRetrievalAdapter()
self._repo_context_factory = repo_context_factory or CodeQaRepoContextFactory() self._repo_context_factory = repo_context_factory or RuntimeRepoContextFactory()
self._prompt_selector = prompt_selector or CodeQaPromptSelector() self._prompt_selector = prompt_selector or RuntimePromptSelector()
self._payload_builder = payload_builder or CodeQaPromptPayloadBuilder() self._payload_builder = payload_builder or RuntimePromptPayloadBuilder()
self._answer_policy = answer_policy or CodeQaAnswerPolicy() self._answer_policy = answer_policy or RuntimeAnswerPolicy()
self._post_gate = post_gate or CodeQaPostEvidenceGate() self._post_gate = post_gate or RuntimePostEvidenceGate()
self._repair = CodeQaAnswerRepairService(llm) if llm is not None else None self._repair = RuntimeAnswerRepairService(llm) if llm is not None else None
self._generator = RuntimeAnswerGenerator(llm) if llm is not None else None
def execute(self, *, user_query: str, rag_session_id: str, files_map: dict[str, dict] | None = None) -> CodeQaFinalResult: def execute(self, *, user_query: str, rag_session_id: str, files_map: dict[str, dict] | None = None) -> RuntimeFinalResult:
timings_ms: dict[str, int] = {} timings_ms: dict[str, int] = {}
runtime_trace: list[dict] = [] runtime_trace: list[dict] = []
answer_policy_branch = "" answer_policy_branch = ""
decision_reason = "" decision_reason = ""
post_gate_snapshot: dict = {} post_gate_snapshot: dict = {}
state = CodeQaExecutionState( state = RuntimeExecutionState(
user_query=user_query, user_query=user_query,
rag_session_id=rag_session_id, rag_session_id=rag_session_id,
conversation_state=ConversationState(), conversation_state=ConversationState(),
@@ -164,7 +166,7 @@ class CodeQaRuntimeExecutor:
"output": post_gate_snapshot["output"], "output": post_gate_snapshot["output"],
} }
) )
return self._finalize( return assemble_final_result(
state, state,
draft=None, draft=None,
final_answer=decision.answer, final_answer=decision.answer,
@@ -175,8 +177,9 @@ class CodeQaRuntimeExecutor:
answer_policy_branch=answer_policy_branch, answer_policy_branch=answer_policy_branch,
decision_reason=decision_reason, decision_reason=decision_reason,
pre_gate_input=pre_gate_input, pre_gate_input=pre_gate_input,
gate_decision=gate_decision,
post_gate_snapshot=post_gate_snapshot, post_gate_snapshot=post_gate_snapshot,
resolved_target=self._resolved_target(state),
post_gate=self._post_gate,
) )
if self._llm is None: if self._llm is None:
answer_policy_branch = "llm_unavailable" answer_policy_branch = "llm_unavailable"
@@ -209,7 +212,7 @@ class CodeQaRuntimeExecutor:
"output": post_gate_snapshot["output"], "output": post_gate_snapshot["output"],
} }
) )
return self._finalize( return assemble_final_result(
state, state,
draft=None, draft=None,
final_answer="", final_answer="",
@@ -220,8 +223,9 @@ class CodeQaRuntimeExecutor:
answer_policy_branch=answer_policy_branch, answer_policy_branch=answer_policy_branch,
decision_reason=decision_reason, decision_reason=decision_reason,
pre_gate_input=pre_gate_input, pre_gate_input=pre_gate_input,
gate_decision=gate_decision,
post_gate_snapshot=post_gate_snapshot, post_gate_snapshot=post_gate_snapshot,
resolved_target=self._resolved_target(state),
post_gate=self._post_gate,
) )
state.synthesis_input = build_answer_synthesis_input(user_query, state.evidence_pack) state.synthesis_input = build_answer_synthesis_input(user_query, state.evidence_pack)
prompt_name = self._prompt_selector.select(sub_intent=state.retrieval_request.sub_intent, answer_mode=state.answer_mode) prompt_name = self._prompt_selector.select(sub_intent=state.retrieval_request.sub_intent, answer_mode=state.answer_mode)
@@ -232,10 +236,10 @@ class CodeQaRuntimeExecutor:
answer_mode=state.answer_mode, answer_mode=state.answer_mode,
) )
started = perf_counter() started = perf_counter()
draft = CodeQaDraftAnswer( draft = RuntimeDraftAnswer(
prompt_name=prompt_name, prompt_name=prompt_name,
prompt_payload=prompt_payload, prompt_payload=prompt_payload,
answer=self._llm.generate(prompt_name, prompt_payload, log_context="graph.project_qa.code_qa.answer").strip(), answer=self._generator.generate(prompt_name, prompt_payload),
) )
timings_ms["llm"] = self._elapsed_ms(started) timings_ms["llm"] = self._elapsed_ms(started)
runtime_trace.append( runtime_trace.append(
@@ -312,7 +316,7 @@ class CodeQaRuntimeExecutor:
"output": post_gate_snapshot["output"], "output": post_gate_snapshot["output"],
} }
) )
return self._finalize( return assemble_final_result(
state, state,
draft=draft, draft=draft,
final_answer=final_answer, final_answer=final_answer,
@@ -324,11 +328,12 @@ class CodeQaRuntimeExecutor:
answer_policy_branch=answer_policy_branch, answer_policy_branch=answer_policy_branch,
decision_reason=decision_reason, decision_reason=decision_reason,
pre_gate_input=pre_gate_input, pre_gate_input=pre_gate_input,
gate_decision=gate_decision,
post_gate_snapshot=post_gate_snapshot, post_gate_snapshot=post_gate_snapshot,
resolved_target=self._resolved_target(state),
post_gate=self._post_gate,
) )
def _retrieve(self, state: CodeQaExecutionState) -> list[dict]: def _retrieve(self, state: RuntimeExecutionState) -> list[dict]:
assert state.retrieval_request is not None assert state.retrieval_request is not None
if state.retrieval_request.sub_intent == "OPEN_FILE" and state.retrieval_request.path_scope: if state.retrieval_request.sub_intent == "OPEN_FILE" and state.retrieval_request.path_scope:
return self._retrieval.retrieve_exact_files( return self._retrieval.retrieve_exact_files(
@@ -364,72 +369,10 @@ class CodeQaRuntimeExecutor:
return {"status": "ambiguous", "resolved_symbol": None, "alternatives": close[:5], "confidence": 0.55} return {"status": "ambiguous", "resolved_symbol": None, "alternatives": close[:5], "confidence": 0.55}
return {"status": "not_found", "resolved_symbol": None, "alternatives": close[:5], "confidence": 0.0} return {"status": "not_found", "resolved_symbol": None, "alternatives": close[:5], "confidence": 0.0}
def _finalize(
self,
state: CodeQaExecutionState,
*,
draft: CodeQaDraftAnswer | None,
final_answer: str,
repair_used: bool,
llm_used: bool,
validation=None,
timings_ms: dict[str, int] | None = None,
runtime_trace: list[dict] | None = None,
answer_policy_branch: str = "",
decision_reason: str = "",
pre_gate_input: dict | None = None,
gate_decision=None,
post_gate_snapshot: dict | None = None,
) -> CodeQaFinalResult:
diagnostics = build_diagnostics_report(
router_result=state.router_result,
retrieval_request=state.retrieval_request,
retrieval_result=state.retrieval_result,
evidence_bundle=state.evidence_pack,
answer_mode=state.answer_mode,
timings_ms=timings_ms or {},
resolved_target=self._resolved_target(state),
answer_policy_branch=answer_policy_branch,
decision_reason=decision_reason,
evidence_gate_input=pre_gate_input or {},
post_evidence_gate=post_gate_snapshot or {},
)
result = CodeQaFinalResult(
final_answer=final_answer.strip(),
answer_mode=state.answer_mode,
repair_used=repair_used,
llm_used=llm_used,
draft_answer=draft,
validation=validation
or self._post_gate.validate(
answer=final_answer,
answer_mode=state.answer_mode,
degraded_message=state.degraded_message,
sub_intent=state.retrieval_request.sub_intent if state.retrieval_request else "",
user_query=state.user_query,
evidence_pack=state.evidence_pack,
),
router_result=state.router_result,
retrieval_request=state.retrieval_request,
retrieval_result=state.retrieval_result,
evidence_pack=state.evidence_pack,
diagnostics=diagnostics,
runtime_trace=list(runtime_trace or []),
)
LOGGER.warning(
"code qa runtime executed: intent=%s sub_intent=%s answer_mode=%s repair_used=%s llm_used=%s",
state.router_result.intent,
state.router_result.query_plan.sub_intent,
result.answer_mode,
result.repair_used,
result.llm_used,
)
return result
def _elapsed_ms(self, started: float) -> int: def _elapsed_ms(self, started: float) -> int:
return max(1, round((perf_counter() - started) * 1000)) return max(1, round((perf_counter() - started) * 1000))
def _build_pre_gate_input(self, state: CodeQaExecutionState) -> dict: def _build_pre_gate_input(self, state: RuntimeExecutionState) -> dict:
evidence = state.evidence_pack evidence = state.evidence_pack
retrieval = state.retrieval_result retrieval = state.retrieval_result
return { return {
@@ -445,7 +388,7 @@ class CodeQaRuntimeExecutor:
"path_scope": list(state.retrieval_request.path_scope) if state.retrieval_request else [], "path_scope": list(state.retrieval_request.path_scope) if state.retrieval_request else [],
} }
def _resolved_target(self, state: CodeQaExecutionState) -> str | None: def _resolved_target(self, state: RuntimeExecutionState) -> str | None:
if state.evidence_pack and state.evidence_pack.resolved_target: if state.evidence_pack and state.evidence_pack.resolved_target:
return state.evidence_pack.resolved_target return state.evidence_pack.resolved_target
if state.retrieval_result and state.retrieval_result.resolved_symbol: if state.retrieval_result and state.retrieval_result.resolved_symbol:
@@ -470,7 +413,7 @@ class CodeQaRuntimeExecutor:
def _hydrate_entrypoint_sources( def _hydrate_entrypoint_sources(
self, self,
state: CodeQaExecutionState, state: RuntimeExecutionState,
raw_rows: list[dict], raw_rows: list[dict],
retrieval_report: dict, retrieval_report: dict,
) -> tuple[list[dict], dict]: ) -> tuple[list[dict], dict]:
@@ -524,7 +467,7 @@ class CodeQaRuntimeExecutor:
merged["supplemental_requests"] = [*(base.get("supplemental_requests") or []), *(extra.get("requests") or [])] merged["supplemental_requests"] = [*(base.get("supplemental_requests") or []), *(extra.get("requests") or [])]
return merged return merged
def _fallback_mode(self, state: CodeQaExecutionState) -> str: def _fallback_mode(self, state: RuntimeExecutionState) -> str:
status = str(state.router_result.symbol_resolution.status if state.router_result and state.router_result.symbol_resolution else "") status = str(state.router_result.symbol_resolution.status if state.router_result and state.router_result.symbol_resolution else "")
if status == "ambiguous": if status == "ambiguous":
return "ambiguous" return "ambiguous"
@@ -532,7 +475,7 @@ class CodeQaRuntimeExecutor:
return "not_found" return "not_found"
return "degraded" return "degraded"
def _fallback_answer(self, state: CodeQaExecutionState) -> str: def _fallback_answer(self, state: RuntimeExecutionState) -> str:
symbol_resolution = state.router_result.symbol_resolution if state.router_result else None symbol_resolution = state.router_result.symbol_resolution if state.router_result else None
query_plan = state.router_result.query_plan if state.router_result else None query_plan = state.router_result.query_plan if state.router_result else None
target = next((item for item in list(query_plan.symbol_candidates or []) if item), "запрошенная сущность") if query_plan else "запрошенная сущность" target = next((item for item in list(query_plan.symbol_candidates or []) if item), "запрошенная сущность") if query_plan else "запрошенная сущность"

View File

@@ -1,4 +1,4 @@
"""Canonical test-first CODE_QA pipeline: router -> retrieval -> evidence -> synthesis -> diagnostics.""" """Legacy test-first CODE_QA pipeline: router retrieval evidence synthesis diagnostics. Prefer agent.runtime.executor."""
from __future__ import annotations from __future__ import annotations
@@ -7,23 +7,21 @@ from difflib import get_close_matches
from time import perf_counter from time import perf_counter
from typing import Any, Protocol from typing import Any, Protocol
from app.modules.rag.code_qa_pipeline.answer_synthesis import build_answer_synthesis_input from app.modules.agent.runtime.steps.context import (
from app.modules.rag.code_qa_pipeline.contracts import ( build_answer_synthesis_input,
build_diagnostics_report,
build_evidence_bundle,
build_retrieval_request,
build_retrieval_result,
EvidenceBundle, EvidenceBundle,
RetrievalRequest, RetrievalRequest,
RetrievalResult, RetrievalResult,
RouterResult, RouterResult,
) )
from app.modules.rag.code_qa_pipeline.diagnostics import build_diagnostics_report from app.modules.agent.runtime.steps.gates.pre.evidence_gate import evaluate_evidence
from app.modules.rag.code_qa_pipeline.evidence_bundle_builder import build_evidence_bundle
from app.modules.rag.code_qa_pipeline.evidence_gate import evaluate_evidence
from app.modules.rag.code_qa_pipeline.retrieval_request_builder import build_retrieval_request
from app.modules.rag.code_qa_pipeline.retrieval_result_builder import build_retrieval_result
class RetrievalAdapter(Protocol): class RetrievalAdapter(Protocol):
"""Protocol for retrieval in the CODE_QA pipeline; satisfied by RagDbAdapter."""
def retrieve_with_plan( def retrieve_with_plan(
self, self,
rag_session_id: str, rag_session_id: str,
@@ -70,8 +68,6 @@ class RetrievalAdapter(Protocol):
@dataclass(slots=True) @dataclass(slots=True)
class CodeQAPipelineResult: class CodeQAPipelineResult:
"""Result of one run of the canonical CODE_QA pipeline."""
user_query: str user_query: str
rag_session_id: str rag_session_id: str
router_result: RouterResult router_result: RouterResult
@@ -88,7 +84,7 @@ class CodeQAPipelineResult:
class CodeQAPipelineRunner: class CodeQAPipelineRunner:
"""Single entrypoint for the test-first CODE_QA pipeline; uses IntentRouterV2 only.""" """Legacy test-first CODE_QA pipeline entrypoint. Prefer AgentRuntimeExecutor."""
def __init__( def __init__(
self, self,
@@ -109,7 +105,6 @@ class CodeQAPipelineRunner:
run_retrieval: bool = True, run_retrieval: bool = True,
run_hydrate: bool = True, run_hydrate: bool = True,
) -> CodeQAPipelineResult: ) -> CodeQAPipelineResult:
"""Run the full pipeline: route -> retrieval -> evidence bundle -> gate -> synthesis -> diagnostics."""
timings: dict[str, int] = {} timings: dict[str, int] = {}
t0 = perf_counter() t0 = perf_counter()
router_result = self._router.route( router_result = self._router.route(
@@ -228,10 +223,10 @@ def _ms(started: float) -> int:
def _default_conversation_state() -> Any: def _default_conversation_state() -> Any:
from app.modules.rag.intent_router_v2 import ConversationState from app.modules.agent.intent_router_v2 import ConversationState
return ConversationState() return ConversationState()
def _default_repo_context() -> Any: def _default_repo_context() -> Any:
from app.modules.rag.intent_router_v2 import RepoContext from app.modules.agent.intent_router_v2 import RepoContext
return RepoContext() return RepoContext()

View File

@@ -0,0 +1,60 @@
"""Модели состояния и результата runtime-оркестратора."""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from app.modules.agent.runtime.steps.context.contracts import (
AnswerSynthesisInput,
DiagnosticsReport,
EvidenceBundle,
RetrievalRequest,
RetrievalResult,
)
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimeValidationResult
from app.modules.agent.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext
class RuntimeDraftAnswer(BaseModel):
model_config = ConfigDict(extra="forbid")
prompt_name: str
prompt_payload: str
answer: str = ""
class RuntimeFinalResult(BaseModel):
model_config = ConfigDict(extra="forbid")
final_answer: str
answer_mode: str = "normal"
repair_used: bool = False
llm_used: bool = False
draft_answer: RuntimeDraftAnswer | None = None
validation: RuntimeValidationResult = Field(default_factory=RuntimeValidationResult)
router_result: IntentRouterResult | None = None
retrieval_request: RetrievalRequest | None = None
retrieval_result: RetrievalResult | None = None
evidence_pack: EvidenceBundle | None = None
diagnostics: DiagnosticsReport
runtime_trace: list[dict[str, Any]] = Field(default_factory=list)
class RuntimeExecutionState(BaseModel):
model_config = ConfigDict(extra="forbid")
user_query: str
rag_session_id: str
conversation_state: ConversationState = Field(default_factory=ConversationState)
repo_context: RepoContext = Field(default_factory=RepoContext)
router_result: IntentRouterResult | None = None
retrieval_request: RetrievalRequest | None = None
retrieval_result: RetrievalResult | None = None
evidence_pack: EvidenceBundle | None = None
synthesis_input: AnswerSynthesisInput | None = None
diagnostics: DiagnosticsReport | None = None
answer_mode: str = "normal"
degraded_message: str = ""
final_result: RuntimeFinalResult | None = None

View File

@@ -0,0 +1,6 @@
"""Политика ответа: вызов LLM или короткий ответ по результату evidence gate."""
from app.modules.agent.runtime.steps.answer_policy.policy import RuntimeAnswerPolicy, RuntimePolicyDecision
from app.modules.agent.runtime.steps.answer_policy.short_answer_formatter import RuntimeShortAnswerFormatter
__all__ = ["RuntimeAnswerPolicy", "RuntimePolicyDecision", "RuntimeShortAnswerFormatter"]

View File

@@ -1,14 +1,16 @@
"""Политика принятия решения: вызывать LLM или вернуть короткий ответ по evidence gate."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from app.modules.rag.code_qa_pipeline.evidence_gate import EvidenceGateDecision from app.modules.agent.runtime.steps.gates.pre.evidence_gate import EvidenceGateDecision
from app.modules.rag.intent_router_v2.models import IntentRouterResult from app.modules.agent.intent_router_v2.models import IntentRouterResult
from app.modules.agent.code_qa_runtime.short_answer_formatter import CodeQaShortAnswerFormatter from app.modules.agent.runtime.steps.answer_policy.short_answer_formatter import RuntimeShortAnswerFormatter
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
class CodeQaPolicyDecision: class RuntimePolicyDecision:
answer_mode: str answer_mode: str
answer: str = "" answer: str = ""
should_call_llm: bool = True should_call_llm: bool = True
@@ -16,22 +18,22 @@ class CodeQaPolicyDecision:
reason: str = "evidence_sufficient" reason: str = "evidence_sufficient"
class CodeQaAnswerPolicy: class RuntimeAnswerPolicy:
def __init__(self, formatter: CodeQaShortAnswerFormatter | None = None) -> None: def __init__(self, formatter: RuntimeShortAnswerFormatter | None = None) -> None:
self._formatter = formatter or CodeQaShortAnswerFormatter() self._formatter = formatter or RuntimeShortAnswerFormatter()
def decide( def decide(
self, self,
*, *,
router_result: IntentRouterResult, router_result: IntentRouterResult,
gate_decision: EvidenceGateDecision, gate_decision: EvidenceGateDecision,
) -> CodeQaPolicyDecision: ) -> RuntimePolicyDecision:
sub_intent = router_result.query_plan.sub_intent.upper() sub_intent = router_result.query_plan.sub_intent.upper()
symbol_resolution = router_result.symbol_resolution symbol_resolution = router_result.symbol_resolution
if sub_intent == "OPEN_FILE" and "path_scope_empty" in gate_decision.failure_reasons: if sub_intent == "OPEN_FILE" and "path_scope_empty" in gate_decision.failure_reasons:
path_scope = list(getattr(router_result.retrieval_spec.filters, "path_scope", []) or []) path_scope = list(getattr(router_result.retrieval_spec.filters, "path_scope", []) or [])
target = path_scope[0] if path_scope else "запрошенный файл" target = path_scope[0] if path_scope else "запрошенный файл"
return CodeQaPolicyDecision( return RuntimePolicyDecision(
answer_mode="not_found", answer_mode="not_found",
answer=self._formatter.open_file_not_found(target), answer=self._formatter.open_file_not_found(target),
should_call_llm=False, should_call_llm=False,
@@ -39,7 +41,7 @@ class CodeQaAnswerPolicy:
reason="path_scope_empty", reason="path_scope_empty",
) )
if sub_intent == "EXPLAIN" and symbol_resolution.status == "not_found": if sub_intent == "EXPLAIN" and symbol_resolution.status == "not_found":
return CodeQaPolicyDecision( return RuntimePolicyDecision(
answer_mode="not_found", answer_mode="not_found",
answer=self._formatter.entity_not_found(self._target_label(router_result), symbol_resolution.alternatives), answer=self._formatter.entity_not_found(self._target_label(router_result), symbol_resolution.alternatives),
should_call_llm=False, should_call_llm=False,
@@ -47,7 +49,7 @@ class CodeQaAnswerPolicy:
reason="symbol_resolution_not_found", reason="symbol_resolution_not_found",
) )
if sub_intent == "EXPLAIN" and symbol_resolution.status == "ambiguous": if sub_intent == "EXPLAIN" and symbol_resolution.status == "ambiguous":
return CodeQaPolicyDecision( return RuntimePolicyDecision(
answer_mode="ambiguous", answer_mode="ambiguous",
answer=self._formatter.entity_ambiguous(self._target_label(router_result), symbol_resolution.alternatives), answer=self._formatter.entity_ambiguous(self._target_label(router_result), symbol_resolution.alternatives),
should_call_llm=False, should_call_llm=False,
@@ -57,14 +59,14 @@ class CodeQaAnswerPolicy:
if not gate_decision.passed: if not gate_decision.passed:
answer_mode = "insufficient" if "insufficient_evidence" in gate_decision.failure_reasons else "degraded" answer_mode = "insufficient" if "insufficient_evidence" in gate_decision.failure_reasons else "degraded"
reason = gate_decision.failure_reasons[0] if gate_decision.failure_reasons else "evidence_gate_failed" reason = gate_decision.failure_reasons[0] if gate_decision.failure_reasons else "evidence_gate_failed"
return CodeQaPolicyDecision( return RuntimePolicyDecision(
answer_mode=answer_mode, answer_mode=answer_mode,
answer=self._formatter.insufficient(gate_decision.degraded_message), answer=self._formatter.insufficient(gate_decision.degraded_message),
should_call_llm=False, should_call_llm=False,
branch="evidence_gate_short_circuit", branch="evidence_gate_short_circuit",
reason=reason, reason=reason,
) )
return CodeQaPolicyDecision(answer_mode="normal", branch="normal_answer", reason="evidence_sufficient") return RuntimePolicyDecision(answer_mode="normal", branch="normal_answer", reason="evidence_sufficient")
def _target_label(self, router_result: IntentRouterResult) -> str: def _target_label(self, router_result: IntentRouterResult) -> str:
candidates = [item.strip() for item in list(router_result.query_plan.symbol_candidates or []) if item and item.strip()] candidates = [item.strip() for item in list(router_result.query_plan.symbol_candidates or []) if item and item.strip()]

View File

@@ -1,7 +1,9 @@
"""Форматирование коротких ответов для режимов not_found, ambiguous, insufficient."""
from __future__ import annotations from __future__ import annotations
class CodeQaShortAnswerFormatter: class RuntimeShortAnswerFormatter:
def open_file_not_found(self, target: str) -> str: def open_file_not_found(self, target: str) -> str:
return f"Файл {target} не найден." return f"Файл {target} не найден."

View File

@@ -0,0 +1,35 @@
"""Контракты и билдеры контекста пайплайна: retrieval request/result, evidence bundle, diagnostics, synthesis."""
from app.modules.agent.runtime.steps.context.contracts import (
AnswerSynthesisInput,
CodeChunkItem,
DiagnosticsReport,
EvidenceBundle,
FailureReason,
RetrievalRequest,
RetrievalResult,
RouterResult,
)
from app.modules.agent.runtime.steps.context.retrieval_request_builder import build_retrieval_request
from app.modules.agent.runtime.steps.context.retrieval_result_builder import build_retrieval_result
from app.modules.agent.runtime.steps.context.evidence_bundle_builder import build_evidence_bundle
from app.modules.agent.runtime.steps.context.diagnostics import build_diagnostics_report
from app.modules.agent.runtime.steps.context.answer_synthesis import build_answer_synthesis_input
from app.modules.agent.runtime.steps.context.answer_fact_curator import build_curated_answer_facts
__all__ = [
"AnswerSynthesisInput",
"CodeChunkItem",
"DiagnosticsReport",
"EvidenceBundle",
"FailureReason",
"RetrievalRequest",
"RetrievalResult",
"RouterResult",
"build_retrieval_request",
"build_retrieval_result",
"build_evidence_bundle",
"build_diagnostics_report",
"build_answer_synthesis_input",
"build_curated_answer_facts",
]

View File

@@ -1,9 +1,11 @@
"""Курация фактов из EvidenceBundle для сценариев explain, architecture, trace_flow."""
from __future__ import annotations from __future__ import annotations
import re import re
from typing import Any from typing import Any
from app.modules.rag.code_qa_pipeline.contracts import CodeChunkItem, EvidenceBundle from app.modules.agent.runtime.steps.context.contracts import CodeChunkItem, EvidenceBundle
_CALL_RE = re.compile(r"([A-Za-z_][\w\.]*)\s*\(") _CALL_RE = re.compile(r"([A-Za-z_][\w\.]*)\s*\(")
_FIELD_RE = re.compile(r"self\.(\w+)") _FIELD_RE = re.compile(r"self\.(\w+)")

View File

@@ -1,16 +1,15 @@
"""Builds AnswerSynthesisInput from EvidenceBundle for LLM stage.""" """Сборка AnswerSynthesisInput из EvidenceBundle для этапа LLM."""
from __future__ import annotations from __future__ import annotations
from app.modules.rag.code_qa_pipeline.answer_fact_curator import build_curated_answer_facts from app.modules.agent.runtime.steps.context.answer_fact_curator import build_curated_answer_facts
from app.modules.rag.code_qa_pipeline.contracts import AnswerSynthesisInput, EvidenceBundle from app.modules.agent.runtime.steps.context.contracts import AnswerSynthesisInput, EvidenceBundle
def build_answer_synthesis_input( def build_answer_synthesis_input(
user_question: str, user_question: str,
bundle: EvidenceBundle, bundle: EvidenceBundle,
) -> AnswerSynthesisInput: ) -> AnswerSynthesisInput:
"""Build LLM input from EvidenceBundle; fast context (summary) + deep context (payload)."""
scenario = bundle.resolved_sub_intent or "EXPLAIN" scenario = bundle.resolved_sub_intent or "EXPLAIN"
target = bundle.resolved_target target = bundle.resolved_target
sufficient = bundle.sufficient sufficient = bundle.sufficient

View File

@@ -1,9 +1,4 @@
"""Typed contracts for the canonical CODE_QA test-first pipeline. """Типизированные контракты пайплайна: RouterResult, RetrievalRequest, RetrievalResult, EvidenceBundle, AnswerSynthesisInput, DiagnosticsReport."""
Defines RouterResult, RetrievalRequest, RetrievalResult, EvidenceBundle,
AnswerSynthesisInput, DiagnosticsReport and machine-readable failure reasons.
Used only by the test-first pipeline; legacy runtime is unchanged.
"""
from __future__ import annotations from __future__ import annotations
@@ -11,14 +6,10 @@ from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
# Re-export for pipeline: router output is IntentRouterResult from app.modules.agent.intent_router_v2.models import IntentRouterResult
from app.modules.rag.intent_router_v2.models import IntentRouterResult
# Type alias: router output is the source of truth for the test pipeline
RouterResult = IntentRouterResult RouterResult = IntentRouterResult
# --- Machine-readable failure reasons for diagnostics ---
FailureReason = Literal[ FailureReason = Literal[
"router_low_confidence", "router_low_confidence",
"target_not_resolved", "target_not_resolved",
@@ -45,8 +36,6 @@ FAILURE_REASONS: tuple[FailureReason, ...] = (
class RetrievalRequest(BaseModel): class RetrievalRequest(BaseModel):
"""Request for retrieval stage; built from RouterResult."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
rag_session_id: str rag_session_id: str
@@ -56,15 +45,12 @@ class RetrievalRequest(BaseModel):
keyword_hints: list[str] = Field(default_factory=list) keyword_hints: list[str] = Field(default_factory=list)
symbol_candidates: list[str] = Field(default_factory=list) symbol_candidates: list[str] = Field(default_factory=list)
requested_layers: list[str] = Field(default_factory=list) requested_layers: list[str] = Field(default_factory=list)
# Pass-through for existing adapter (retrieval_spec, retrieval_constraints, query_plan)
retrieval_spec: Any = None retrieval_spec: Any = None
retrieval_constraints: Any = None retrieval_constraints: Any = None
query_plan: Any = None query_plan: Any = None
class CodeChunkItem(BaseModel): class CodeChunkItem(BaseModel):
"""Single code chunk in normalized retrieval output."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
layer: str layer: str
@@ -77,8 +63,6 @@ class CodeChunkItem(BaseModel):
class LayerOutcome(BaseModel): class LayerOutcome(BaseModel):
"""Per-layer retrieval outcome for diagnostics."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
layer_id: str layer_id: str
@@ -88,8 +72,6 @@ class LayerOutcome(BaseModel):
class RetrievalResult(BaseModel): class RetrievalResult(BaseModel):
"""Normalized retrieval result; single structure for all scenarios."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
target_symbol_candidates: list[str] = Field(default_factory=list) target_symbol_candidates: list[str] = Field(default_factory=list)
@@ -108,8 +90,6 @@ class RetrievalResult(BaseModel):
class EvidenceBundle(BaseModel): class EvidenceBundle(BaseModel):
"""Canonical evidence bundle for answer synthesis; only source of truth for LLM stage."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
resolved_intent: str = "" resolved_intent: str = ""
@@ -129,8 +109,6 @@ class EvidenceBundle(BaseModel):
class AnswerSynthesisInput(BaseModel): class AnswerSynthesisInput(BaseModel):
"""Input for LLM answer synthesis; derived from EvidenceBundle."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
user_question: str = "" user_question: str = ""
@@ -146,11 +124,8 @@ class AnswerSynthesisInput(BaseModel):
class DiagnosticsReport(BaseModel): class DiagnosticsReport(BaseModel):
"""Full diagnostics for the pipeline; Level 1 summary + Level 2 detail."""
model_config = ConfigDict(extra="forbid") model_config = ConfigDict(extra="forbid")
# Level 1 — human-readable summary
intent_correct: bool | None = None intent_correct: bool | None = None
target_found: bool = False target_found: bool = False
layers_used: list[str] = Field(default_factory=list) layers_used: list[str] = Field(default_factory=list)
@@ -160,7 +135,6 @@ class DiagnosticsReport(BaseModel):
answer_policy_branch: str = "" answer_policy_branch: str = ""
decision_reason: str = "" decision_reason: str = ""
# Level 2 — detailed
router_result: dict[str, Any] = Field(default_factory=dict) router_result: dict[str, Any] = Field(default_factory=dict)
retrieval_request: dict[str, Any] = Field(default_factory=dict) retrieval_request: dict[str, Any] = Field(default_factory=dict)
per_layer_outcome: list[dict[str, Any]] = Field(default_factory=list) per_layer_outcome: list[dict[str, Any]] = Field(default_factory=list)

View File

@@ -1,10 +1,10 @@
"""Diagnostics for the CODE_QA pipeline: Level 1 summary and Level 2 detail.""" """Диагностика пайплайна CODE_QA: сводка уровня 1 и детали уровня 2."""
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any
from app.modules.rag.code_qa_pipeline.contracts import ( from app.modules.agent.runtime.steps.context.contracts import (
DiagnosticsReport, DiagnosticsReport,
EvidenceBundle, EvidenceBundle,
RetrievalRequest, RetrievalRequest,
@@ -27,7 +27,6 @@ def build_diagnostics_report(
evidence_gate_input: dict[str, Any] | None = None, evidence_gate_input: dict[str, Any] | None = None,
post_evidence_gate: dict[str, Any] | None = None, post_evidence_gate: dict[str, Any] | None = None,
) -> DiagnosticsReport: ) -> DiagnosticsReport:
"""Build full diagnostics: Level 1 summary + Level 2 detail + failure reasons."""
timings = dict(timings_ms or {}) timings = dict(timings_ms or {})
req = retrieval_request req = retrieval_request
res = retrieval_result res = retrieval_result
@@ -83,7 +82,6 @@ def build_diagnostics_report(
def build_level1_summary(report: DiagnosticsReport) -> dict[str, Any]: def build_level1_summary(report: DiagnosticsReport) -> dict[str, Any]:
"""Human-readable summary: intent, target, layers, sufficiency, answer mode."""
return { return {
"intent_correct": report.intent_correct, "intent_correct": report.intent_correct,
"target_found": report.target_found, "target_found": report.target_found,
@@ -98,7 +96,6 @@ def build_level1_summary(report: DiagnosticsReport) -> dict[str, Any]:
def build_level2_detail(report: DiagnosticsReport) -> dict[str, Any]: def build_level2_detail(report: DiagnosticsReport) -> dict[str, Any]:
"""Detailed diagnostics for tuning and tests."""
return { return {
"router_result": report.router_result, "router_result": report.router_result,
"retrieval_request": report.retrieval_request, "retrieval_request": report.retrieval_request,

View File

@@ -1,15 +1,14 @@
"""Builds EvidenceBundle from RetrievalResult and router context.""" """Сборка EvidenceBundle из RetrievalResult и результата роутера."""
from __future__ import annotations from __future__ import annotations
from app.modules.rag.code_qa_pipeline.contracts import EvidenceBundle, RetrievalResult, RouterResult from app.modules.agent.runtime.steps.context.contracts import EvidenceBundle, RetrievalResult, RouterResult
def build_evidence_bundle( def build_evidence_bundle(
retrieval_result: RetrievalResult, retrieval_result: RetrievalResult,
router_result: RouterResult, router_result: RouterResult,
) -> EvidenceBundle: ) -> EvidenceBundle:
"""Build EvidenceBundle from normalized retrieval and router result."""
intent = router_result.intent or "CODE_QA" intent = router_result.intent or "CODE_QA"
sub_intent = (router_result.query_plan and router_result.query_plan.sub_intent) or "EXPLAIN" sub_intent = (router_result.query_plan and router_result.query_plan.sub_intent) or "EXPLAIN"
resolved_target: str | None = None resolved_target: str | None = None

View File

@@ -1,12 +1,11 @@
"""Builds RetrievalRequest from RouterResult for the CODE_QA pipeline.""" """Сборка RetrievalRequest из RouterResult для пайплайна CODE_QA."""
from __future__ import annotations from __future__ import annotations
from app.modules.rag.code_qa_pipeline.contracts import RetrievalRequest, RouterResult from app.modules.agent.runtime.steps.context.contracts import RetrievalRequest, RouterResult
def build_retrieval_request(router_result: RouterResult, rag_session_id: str) -> RetrievalRequest: def build_retrieval_request(router_result: RouterResult, rag_session_id: str) -> RetrievalRequest:
"""Convert router output to RetrievalRequest; router is source of truth."""
query_plan = router_result.query_plan query_plan = router_result.query_plan
spec = router_result.retrieval_spec spec = router_result.retrieval_spec
path_scope = list(getattr(spec.filters, "path_scope", []) or []) path_scope = list(getattr(spec.filters, "path_scope", []) or [])

View File

@@ -1,10 +1,10 @@
"""Builds normalized RetrievalResult from raw retrieval rows and report.""" """Сборка нормализованного RetrievalResult из сырых строк retrieval и отчёта."""
from __future__ import annotations from __future__ import annotations
import re import re
from app.modules.rag.code_qa_pipeline.contracts import CodeChunkItem, LayerOutcome, RetrievalResult from app.modules.agent.runtime.steps.context.contracts import CodeChunkItem, LayerOutcome, RetrievalResult
from app.modules.rag.retrieval.test_filter import is_test_path from app.modules.rag.retrieval.test_filter import is_test_path
_ROUTE_RE = re.compile(r'@[\w\.]+\.(get|post|put|delete|patch|options|head)\(\s*["\']([^"\']+)["\']') _ROUTE_RE = re.compile(r'@[\w\.]+\.(get|post|put|delete|patch|options|head)\(\s*["\']([^"\']+)["\']')
@@ -16,7 +16,6 @@ def build_retrieval_result(
retrieval_report: dict | None, retrieval_report: dict | None,
symbol_resolution: dict | None, symbol_resolution: dict | None,
) -> RetrievalResult: ) -> RetrievalResult:
"""Convert raw adapter rows and optional report into normalized RetrievalResult."""
report = retrieval_report or {} report = retrieval_report or {}
sym = symbol_resolution or {} sym = symbol_resolution or {}
layers_seen: set[str] = set() layers_seen: set[str] = set()

View File

@@ -0,0 +1,36 @@
from __future__ import annotations
from importlib import import_module
__all__ = [
"CodeExcerpt",
"CodeExplainRetrieverV2",
"CodeGraphRepository",
"EvidenceItem",
"ExplainIntent",
"ExplainIntentBuilder",
"ExplainPack",
"LayeredRetrievalGateway",
"PromptBudgeter",
"TracePath",
]
def __getattr__(name: str):
module_map = {
"CodeExcerpt": "app.modules.agent.runtime.steps.explain.models",
"EvidenceItem": "app.modules.agent.runtime.steps.explain.models",
"ExplainIntent": "app.modules.agent.runtime.steps.explain.models",
"ExplainPack": "app.modules.agent.runtime.steps.explain.models",
"TracePath": "app.modules.agent.runtime.steps.explain.models",
"ExplainIntentBuilder": "app.modules.agent.runtime.steps.explain.intent_builder",
"PromptBudgeter": "app.modules.agent.runtime.steps.explain.budgeter",
"LayeredRetrievalGateway": "app.modules.agent.runtime.steps.explain.layered_gateway",
"CodeGraphRepository": "app.modules.agent.runtime.steps.explain.graph_repository",
"CodeExplainRetrieverV2": "app.modules.agent.runtime.steps.explain.retriever_v2",
}
module_name = module_map.get(name)
if module_name is None:
raise AttributeError(name)
module = import_module(module_name)
return getattr(module, name)

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import json import json
from app.modules.rag.explain.models import ExplainPack from app.modules.agent.runtime.steps.explain.models import ExplainPack
class PromptBudgeter: class PromptBudgeter:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from app.modules.rag.explain.models import CodeExcerpt, LayeredRetrievalItem from app.modules.agent.runtime.steps.explain.models import CodeExcerpt, LayeredRetrievalItem
class ExcerptPlanner: class ExcerptPlanner:

View File

@@ -4,7 +4,7 @@ import json
from sqlalchemy import text from sqlalchemy import text
from app.modules.rag.explain.models import CodeLocation, LayeredRetrievalItem from app.modules.agent.runtime.steps.explain.models import CodeLocation, LayeredRetrievalItem
from app.modules.shared.db import get_engine from app.modules.shared.db import get_engine

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import re import re
from app.modules.rag.explain.models import ExplainHints, ExplainIntent from app.modules.agent.runtime.steps.explain.models import ExplainHints, ExplainIntent
from app.modules.rag.retrieval.query_terms import extract_query_terms from app.modules.rag.retrieval.query_terms import extract_query_terms

View File

@@ -4,7 +4,7 @@ import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Callable from typing import TYPE_CHECKING, Callable
from app.modules.rag.explain.models import CodeLocation, LayeredRetrievalItem from app.modules.agent.runtime.steps.explain.models import CodeLocation, LayeredRetrievalItem
from app.modules.rag.retrieval.test_filter import build_test_filters, debug_disable_test_filter from app.modules.rag.retrieval.test_filter import build_test_filters, debug_disable_test_filter
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

View File

@@ -4,19 +4,19 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from app.modules.rag.contracts.enums import RagLayer from app.modules.rag.contracts.enums import RagLayer
from app.modules.rag.explain.intent_builder import ExplainIntentBuilder from app.modules.agent.runtime.steps.explain.intent_builder import ExplainIntentBuilder
from app.modules.rag.explain.layered_gateway import LayerRetrievalResult, LayeredRetrievalGateway from app.modules.agent.runtime.steps.explain.layered_gateway import LayerRetrievalResult, LayeredRetrievalGateway
from app.modules.rag.explain.models import CodeExcerpt, EvidenceItem, ExplainPack, LayeredRetrievalItem from app.modules.agent.runtime.steps.explain.models import CodeExcerpt, EvidenceItem, ExplainPack, LayeredRetrievalItem
from app.modules.rag.explain.source_excerpt_fetcher import SourceExcerptFetcher from app.modules.agent.runtime.steps.explain.source_excerpt_fetcher import SourceExcerptFetcher
from app.modules.rag.explain.trace_builder import TraceBuilder from app.modules.agent.runtime.steps.explain.trace_builder import TraceBuilder
from app.modules.rag.retrieval.test_filter import exclude_tests_default, is_test_path from app.modules.rag.retrieval.test_filter import exclude_tests_default, is_test_path
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
_MIN_EXCERPTS = 2 _MIN_EXCERPTS = 2
if TYPE_CHECKING: if TYPE_CHECKING:
from app.modules.rag.explain.graph_repository import CodeGraphRepository from app.modules.agent.runtime.steps.explain.graph_repository import CodeGraphRepository
from app.modules.rag.explain.models import ExplainIntent from app.modules.agent.runtime.steps.explain.models import ExplainIntent
class CodeExplainRetrieverV2: class CodeExplainRetrieverV2:

View File

@@ -2,12 +2,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from app.modules.rag.explain.excerpt_planner import ExcerptPlanner from app.modules.agent.runtime.steps.explain.excerpt_planner import ExcerptPlanner
from app.modules.rag.explain.models import CodeExcerpt, EvidenceItem, TracePath from app.modules.agent.runtime.steps.explain.models import CodeExcerpt, EvidenceItem, TracePath
from app.modules.rag.retrieval.test_filter import is_test_path from app.modules.rag.retrieval.test_filter import is_test_path
if TYPE_CHECKING: if TYPE_CHECKING:
from app.modules.rag.explain.graph_repository import CodeGraphRepository from app.modules.agent.runtime.steps.explain.graph_repository import CodeGraphRepository
class SourceExcerptFetcher: class SourceExcerptFetcher:

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from app.modules.rag.explain.models import LayeredRetrievalItem, TracePath from app.modules.agent.runtime.steps.explain.models import LayeredRetrievalItem, TracePath
if TYPE_CHECKING: if TYPE_CHECKING:
from app.modules.rag.explain.graph_repository import CodeGraphRepository from app.modules.agent.runtime.steps.explain.graph_repository import CodeGraphRepository
class TraceBuilder: class TraceBuilder:

View File

@@ -0,0 +1,6 @@
"""Финальная сборка: repair черновика и сборка RuntimeFinalResult."""
from app.modules.agent.runtime.steps.finalization.repair import RuntimeAnswerRepairService
from app.modules.agent.runtime.steps.finalization.result_assembler import assemble_final_result
__all__ = ["RuntimeAnswerRepairService", "assemble_final_result"]

View File

@@ -1,12 +1,14 @@
"""Сервис починки черновика ответа по результатам post-evidence gate (LLM repair)."""
from __future__ import annotations from __future__ import annotations
import json import json
from app.modules.agent.code_qa_runtime.models import CodeQaValidationResult from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimeValidationResult
from app.modules.agent.llm import AgentLlmService from app.modules.agent.llm import AgentLlmService
class CodeQaAnswerRepairService: class RuntimeAnswerRepairService:
def __init__(self, llm: AgentLlmService) -> None: def __init__(self, llm: AgentLlmService) -> None:
self._llm = llm self._llm = llm
@@ -14,7 +16,7 @@ class CodeQaAnswerRepairService:
self, self,
*, *,
draft_answer: str, draft_answer: str,
validation: CodeQaValidationResult, validation: RuntimeValidationResult,
prompt_payload: str, prompt_payload: str,
) -> str: ) -> str:
repair_focus = self._repair_focus(validation.reasons) repair_focus = self._repair_focus(validation.reasons)

View File

@@ -0,0 +1,79 @@
"""Сборка финального результата пайплайна: диагностика и RuntimeFinalResult."""
from __future__ import annotations
import logging
from typing import Any
from app.modules.agent.runtime.steps.context.diagnostics import build_diagnostics_report
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimePostEvidenceGate, RuntimeValidationResult
from app.modules.agent.runtime.models import RuntimeDraftAnswer, RuntimeExecutionState, RuntimeFinalResult
LOGGER = logging.getLogger(__name__)
def assemble_final_result(
state: RuntimeExecutionState,
*,
draft: RuntimeDraftAnswer | None,
final_answer: str,
repair_used: bool,
llm_used: bool,
validation: RuntimeValidationResult | None = None,
timings_ms: dict[str, int] | None = None,
runtime_trace: list[dict] | None = None,
answer_policy_branch: str = "",
decision_reason: str = "",
pre_gate_input: dict[str, Any] | None = None,
post_gate_snapshot: dict[str, Any] | None = None,
resolved_target: str | None = None,
post_gate: RuntimePostEvidenceGate | None = None,
) -> RuntimeFinalResult:
diagnostics = build_diagnostics_report(
router_result=state.router_result,
retrieval_request=state.retrieval_request,
retrieval_result=state.retrieval_result,
evidence_bundle=state.evidence_pack,
answer_mode=state.answer_mode,
timings_ms=timings_ms or {},
resolved_target=resolved_target,
answer_policy_branch=answer_policy_branch,
decision_reason=decision_reason,
evidence_gate_input=pre_gate_input or {},
post_evidence_gate=post_gate_snapshot or {},
)
if validation is None and post_gate is not None and state.retrieval_request is not None:
validation = post_gate.validate(
answer=final_answer,
answer_mode=state.answer_mode,
degraded_message=state.degraded_message,
sub_intent=state.retrieval_request.sub_intent,
user_query=state.user_query,
evidence_pack=state.evidence_pack,
)
elif validation is None:
validation = RuntimeValidationResult(passed=True, action="return")
result = RuntimeFinalResult(
final_answer=final_answer.strip(),
answer_mode=state.answer_mode,
repair_used=repair_used,
llm_used=llm_used,
draft_answer=draft,
validation=validation,
router_result=state.router_result,
retrieval_request=state.retrieval_request,
retrieval_result=state.retrieval_result,
evidence_pack=state.evidence_pack,
diagnostics=diagnostics,
runtime_trace=list(runtime_trace or []),
)
LOGGER.warning(
"agent runtime executed: intent=%s sub_intent=%s answer_mode=%s repair_used=%s llm_used=%s",
state.router_result.intent if state.router_result else None,
state.router_result.query_plan.sub_intent if state.router_result and state.router_result.query_plan else None,
result.answer_mode,
result.repair_used,
result.llm_used,
)
return result

View File

@@ -0,0 +1,11 @@
"""Pre- и post-evidence gates пайплайна."""
from app.modules.agent.runtime.steps.gates.pre.evidence_gate import EvidenceGateDecision, evaluate_evidence
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimePostEvidenceGate, RuntimeValidationResult
__all__ = [
"EvidenceGateDecision",
"evaluate_evidence",
"RuntimePostEvidenceGate",
"RuntimeValidationResult",
]

View File

@@ -0,0 +1,5 @@
"""Post-evidence gate: валидация черновика ответа и решение repair/return."""
from app.modules.agent.runtime.steps.gates.post.post_gate import RuntimePostEvidenceGate, RuntimeValidationResult
__all__ = ["RuntimePostEvidenceGate", "RuntimeValidationResult"]

View File

@@ -1,10 +1,22 @@
"""Post-evidence gate: валидация черновика ответа и решение repair/return."""
from __future__ import annotations from __future__ import annotations
import re import re
from app.modules.agent.code_qa_runtime.models import CodeQaValidationResult from pydantic import BaseModel, ConfigDict, Field
from app.modules.rag.code_qa_pipeline.answer_fact_curator import build_curated_answer_facts
from app.modules.rag.code_qa_pipeline.contracts import EvidenceBundle from app.modules.agent.runtime.steps.context.answer_fact_curator import build_curated_answer_facts
from app.modules.agent.runtime.steps.context.contracts import EvidenceBundle
class RuntimeValidationResult(BaseModel):
model_config = ConfigDict(extra="forbid")
passed: bool = False
action: str = "return"
reasons: list[str] = Field(default_factory=list)
_TOKEN_RE = re.compile(r"[a-zA-Zа-яА-Я0-9_/]+") _TOKEN_RE = re.compile(r"[a-zA-Zа-яА-Я0-9_/]+")
_VAGUE_PHRASES = ( _VAGUE_PHRASES = (
@@ -22,7 +34,7 @@ _VAGUE_PHRASES = (
_OPTIMISTIC_TRACE_CLAIMS = ("полностью восстанавливается", "полный поток выполнения", "полностью прослеживается") _OPTIMISTIC_TRACE_CLAIMS = ("полностью восстанавливается", "полный поток выполнения", "полностью прослеживается")
class CodeQaPostEvidenceGate: class RuntimePostEvidenceGate:
def validate( def validate(
self, self,
*, *,
@@ -32,25 +44,25 @@ class CodeQaPostEvidenceGate:
sub_intent: str, sub_intent: str,
user_query: str, user_query: str,
evidence_pack: EvidenceBundle | None, evidence_pack: EvidenceBundle | None,
) -> CodeQaValidationResult: ) -> RuntimeValidationResult:
normalized = (answer or "").strip() normalized = (answer or "").strip()
if not normalized: if not normalized:
return CodeQaValidationResult(passed=False, action="repair", reasons=["empty_answer"]) return RuntimeValidationResult(passed=False, action="repair", reasons=["empty_answer"])
if answer_mode in {"degraded", "insufficient"} and "недостат" not in normalized.lower(): if answer_mode in {"degraded", "insufficient"} and "недостат" not in normalized.lower():
return CodeQaValidationResult(passed=False, action="repair", reasons=["degraded_answer_missing_guardrail"]) return RuntimeValidationResult(passed=False, action="repair", reasons=["degraded_answer_missing_guardrail"])
if answer_mode == "not_found" and "не найден" not in normalized.lower(): if answer_mode == "not_found" and "не найден" not in normalized.lower():
return CodeQaValidationResult(passed=False, action="repair", reasons=["not_found_answer_missing_phrase"]) return RuntimeValidationResult(passed=False, action="repair", reasons=["not_found_answer_missing_phrase"])
if answer_mode == "ambiguous" and "не удалось однозначно разрешить" not in normalized.lower(): if answer_mode == "ambiguous" and "не удалось однозначно разрешить" not in normalized.lower():
return CodeQaValidationResult(passed=False, action="repair", reasons=["ambiguous_answer_missing_phrase"]) return RuntimeValidationResult(passed=False, action="repair", reasons=["ambiguous_answer_missing_phrase"])
if degraded_message and answer_mode != "normal" and len(normalized) < 24: if degraded_message and answer_mode != "normal" and len(normalized) < 24:
return CodeQaValidationResult(passed=False, action="repair", reasons=["answer_too_short"]) return RuntimeValidationResult(passed=False, action="repair", reasons=["answer_too_short"])
if answer_mode != "normal" or evidence_pack is None: if answer_mode != "normal" or evidence_pack is None:
return CodeQaValidationResult(passed=True, action="return") return RuntimeValidationResult(passed=True, action="return")
reasons = self._normal_answer_reasons(normalized.lower(), sub_intent.upper(), user_query, evidence_pack) reasons = self._normal_answer_reasons(normalized.lower(), sub_intent.upper(), user_query, evidence_pack)
if reasons: if reasons:
return CodeQaValidationResult(passed=False, action="repair", reasons=_dedupe(reasons)) return RuntimeValidationResult(passed=False, action="repair", reasons=_dedupe(reasons))
return CodeQaValidationResult(passed=True, action="return") return RuntimeValidationResult(passed=True, action="return")
def _normal_answer_reasons(self, answer: str, sub_intent: str, user_query: str, evidence_pack: EvidenceBundle) -> list[str]: def _normal_answer_reasons(self, answer: str, sub_intent: str, user_query: str, evidence_pack: EvidenceBundle) -> list[str]:
reasons: list[str] = [] reasons: list[str] = []

View File

@@ -0,0 +1,5 @@
"""Pre-evidence gate: проверка достаточности evidence перед вызовом LLM."""
from app.modules.agent.runtime.steps.gates.pre.evidence_gate import EvidenceGateDecision, evaluate_evidence
__all__ = ["EvidenceGateDecision", "evaluate_evidence"]

View File

@@ -1,23 +1,20 @@
"""Shared evidence sufficiency check for the CODE_QA test pipeline.""" """Проверка достаточности evidence для пайплайна CODE_QA."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from app.modules.rag.code_qa_pipeline.contracts import EvidenceBundle from app.modules.agent.runtime.steps.context.contracts import EvidenceBundle
@dataclass(slots=True) @dataclass(slots=True)
class EvidenceGateDecision: class EvidenceGateDecision:
"""Result of evidence sufficiency check."""
passed: bool passed: bool
failure_reasons: list[str] = field(default_factory=list) failure_reasons: list[str] = field(default_factory=list)
degraded_message: str = "" degraded_message: str = ""
def evaluate_evidence(bundle: EvidenceBundle) -> EvidenceGateDecision: def evaluate_evidence(bundle: EvidenceBundle) -> EvidenceGateDecision:
"""Check evidence sufficiency by scenario; prevent confident answer without support."""
sub = (bundle.resolved_sub_intent or "EXPLAIN").upper() sub = (bundle.resolved_sub_intent or "EXPLAIN").upper()
reasons: list[str] = [] reasons: list[str] = []

View File

@@ -0,0 +1,11 @@
"""Выбор промпта, сборка payload и вызов LLM для генерации ответа."""
from app.modules.agent.runtime.steps.generation.prompt_selector import RuntimePromptSelector
from app.modules.agent.runtime.steps.generation.prompt_payload_builder import RuntimePromptPayloadBuilder
from app.modules.agent.runtime.steps.generation.generator import RuntimeAnswerGenerator
__all__ = [
"RuntimePromptSelector",
"RuntimePromptPayloadBuilder",
"RuntimeAnswerGenerator",
]

View File

@@ -0,0 +1,18 @@
"""Тонкая обёртка над LLM для генерации ответа по имени промпта и payload."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.modules.agent.llm import AgentLlmService
class RuntimeAnswerGenerator:
"""Делегирует вызов LLM для генерации черновика ответа."""
def __init__(self, llm: AgentLlmService) -> None:
self._llm = llm
def generate(self, prompt_name: str, payload: str, *, log_context: str = "graph.project_qa.code_qa.answer") -> str:
return self._llm.generate(prompt_name, payload, log_context=log_context).strip()

View File

@@ -1,9 +1,11 @@
"""Сборка JSON-полезной нагрузки для системного промпта по synthesis_input и evidence_pack."""
from __future__ import annotations from __future__ import annotations
import json import json
import re import re
from app.modules.rag.code_qa_pipeline.contracts import AnswerSynthesisInput, EvidenceBundle from app.modules.agent.runtime.steps.context.contracts import AnswerSynthesisInput, EvidenceBundle
_LAYER_GUIDE = ( _LAYER_GUIDE = (
"- C0_SOURCE_CHUNKS: фактический код, это основной источник деталей.\n" "- C0_SOURCE_CHUNKS: фактический код, это основной источник деталей.\n"
@@ -15,7 +17,7 @@ _LAYER_GUIDE = (
_TOKEN_RE = re.compile(r"[a-zA-Zа-яА-Я0-9_/]+") _TOKEN_RE = re.compile(r"[a-zA-Zа-яА-Я0-9_/]+")
class CodeQaPromptPayloadBuilder: class RuntimePromptPayloadBuilder:
def build( def build(
self, self,
*, *,

View File

@@ -1,7 +1,9 @@
"""Выбор имени системного промпта по sub_intent и answer_mode."""
from __future__ import annotations from __future__ import annotations
class CodeQaPromptSelector: class RuntimePromptSelector:
_PROMPTS = { _PROMPTS = {
"ARCHITECTURE": "code_qa_architecture_answer", "ARCHITECTURE": "code_qa_architecture_answer",
"EXPLAIN": "code_qa_explain_answer", "EXPLAIN": "code_qa_explain_answer",

View File

@@ -0,0 +1,6 @@
"""Пакет выполнения retrieval: адаптер к RAG-репозиторию и фабрика контекста репозитория."""
from app.modules.agent.runtime.steps.retrieval.adapter import RuntimeRetrievalAdapter
from app.modules.agent.runtime.steps.retrieval.repo_context import RuntimeRepoContextFactory
__all__ = ["RuntimeRetrievalAdapter", "RuntimeRepoContextFactory"]

View File

@@ -1,3 +1,5 @@
"""Адаптер RAG-репозитория к runtime: retrieve_with_plan, retrieve_exact_files, consume_retrieval_report."""
from __future__ import annotations from __future__ import annotations
from time import perf_counter from time import perf_counter
@@ -35,7 +37,7 @@ class SessionEmbeddingDimensions:
return dim return dim
class CodeQaRetrievalAdapter: class RuntimeRetrievalAdapter:
def __init__(self, repository: RagRepository | None = None) -> None: def __init__(self, repository: RagRepository | None = None) -> None:
if repository is None: if repository is None:
from app.modules.rag.persistence.repository import RagRepository from app.modules.rag.persistence.repository import RagRepository

View File

@@ -1,10 +1,12 @@
"""Фабрика контекста репозитория (языки, слои RAG) для runtime."""
from __future__ import annotations from __future__ import annotations
from app.modules.rag.contracts.enums import RagLayer from app.modules.rag.contracts.enums import RagLayer
from app.modules.rag.intent_router_v2.models import RepoContext from app.modules.agent.intent_router_v2.models import RepoContext
class CodeQaRepoContextFactory: class RuntimeRepoContextFactory:
_KNOWN_LAYERS = [ _KNOWN_LAYERS = [
RagLayer.CODE_ENTRYPOINTS, RagLayer.CODE_ENTRYPOINTS,
RagLayer.CODE_SYMBOL_CATALOG, RagLayer.CODE_SYMBOL_CATALOG,

View File

@@ -1,8 +1,7 @@
from app.modules.agent.code_qa_runtime import CodeQaRuntimeExecutor from app.modules.agent.runtime import AgentRuntimeExecutor, RuntimeRetrievalAdapter
from app.modules.agent.code_qa_runtime.retrieval_adapter import CodeQaRetrievalAdapter from app.modules.agent.runtime.code_qa_runner_adapter import CodeQaRunnerAdapter
from app.modules.agent.code_qa_runner_adapter import CodeQaRunnerAdapter
from app.modules.agent.llm import AgentLlmService from app.modules.agent.llm import AgentLlmService
from app.modules.agent.prompt_loader import PromptLoader from app.modules.agent.llm.prompt_loader import PromptLoader
from app.modules.chat.direct_service import CodeExplainChatService from app.modules.chat.direct_service import CodeExplainChatService
from app.modules.chat.dialog_store import DialogSessionStore from app.modules.chat.dialog_store import DialogSessionStore
from app.modules.chat.repository import ChatRepository from app.modules.chat.repository import ChatRepository
@@ -10,8 +9,8 @@ from app.modules.chat.module import ChatModule
from app.modules.chat.session_resolver import ChatSessionResolver from app.modules.chat.session_resolver import ChatSessionResolver
from app.modules.chat.task_store import TaskStore from app.modules.chat.task_store import TaskStore
from app.modules.rag.persistence.repository import RagRepository from app.modules.rag.persistence.repository import RagRepository
from app.modules.rag.persistence.story_context_repository import StoryContextRepository, StoryContextSchemaRepository from app.modules.agent.runtime.story_context_repository import StoryContextRepository, StoryContextSchemaRepository
from app.modules.rag.explain import CodeExplainRetrieverV2, CodeGraphRepository, LayeredRetrievalGateway from app.modules.agent.runtime.steps.explain import CodeExplainRetrieverV2, CodeGraphRepository, LayeredRetrievalGateway
from app.modules.rag.module import RagModule, RagRepoModule from app.modules.rag.module import RagModule, RagRepoModule
from app.modules.shared.bootstrap import bootstrap_database from app.modules.shared.bootstrap import bootstrap_database
from app.modules.shared.event_bus import EventBus from app.modules.shared.event_bus import EventBus
@@ -45,8 +44,8 @@ class ModularApplication:
_giga_client = GigaChatClient(_giga_settings, GigaChatTokenProvider(_giga_settings)) _giga_client = GigaChatClient(_giga_settings, GigaChatTokenProvider(_giga_settings))
_prompt_loader = PromptLoader() _prompt_loader = PromptLoader()
self._agent_llm = AgentLlmService(client=_giga_client, prompts=_prompt_loader) self._agent_llm = AgentLlmService(client=_giga_client, prompts=_prompt_loader)
_retrieval = CodeQaRetrievalAdapter(self.rag_repository) _retrieval = RuntimeRetrievalAdapter(self.rag_repository)
_executor = CodeQaRuntimeExecutor(llm=self._agent_llm, retrieval=_retrieval) _executor = AgentRuntimeExecutor(llm=self._agent_llm, retrieval=_retrieval)
self._agent_runner = CodeQaRunnerAdapter(_executor) self._agent_runner = CodeQaRunnerAdapter(_executor)
self.direct_chat = CodeExplainChatService( self.direct_chat = CodeExplainChatService(
retriever=self.code_explain_retriever, retriever=self.code_explain_retriever,

View File

@@ -7,7 +7,7 @@ from app.modules.agent.llm import AgentLlmService
from app.modules.chat.evidence_gate import CodeExplainEvidenceGate from app.modules.chat.evidence_gate import CodeExplainEvidenceGate
from app.modules.chat.session_resolver import ChatSessionResolver from app.modules.chat.session_resolver import ChatSessionResolver
from app.modules.chat.task_store import TaskState, TaskStore from app.modules.chat.task_store import TaskState, TaskStore
from app.modules.rag.explain import CodeExplainRetrieverV2, PromptBudgeter from app.modules.agent.runtime.steps.explain import CodeExplainRetrieverV2, PromptBudgeter
from app.schemas.chat import ChatMessageRequest, TaskQueuedResponse, TaskResultType, TaskStatus from app.schemas.chat import ChatMessageRequest, TaskQueuedResponse, TaskResultType, TaskStatus
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

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