127 lines
4.8 KiB
Python
127 lines
4.8 KiB
Python
import os
|
||
|
||
import pytest
|
||
|
||
from app.modules.rag.intent_router_v2 import GigaChatIntentRouterFactory
|
||
from app.modules.shared.env_loader import load_workspace_env
|
||
from tests.rag.asserts_intent_router import (
|
||
assert_domains,
|
||
assert_file_only_scope,
|
||
assert_intent,
|
||
assert_test_policy,
|
||
)
|
||
from tests.rag.intent_router_testkit import run_sequence
|
||
|
||
pytestmark = pytest.mark.intent_router
|
||
|
||
|
||
def _live_gigachat_enabled() -> bool:
|
||
load_workspace_env()
|
||
return os.getenv("RUN_INTENT_ROUTER_V2_LIVE", "").strip() == "1" and bool(os.getenv("GIGACHAT_TOKEN", "").strip())
|
||
|
||
|
||
def test_e2e_path_carryover_flow() -> None:
|
||
first, second, third = run_sequence(
|
||
[
|
||
"Посмотри файл app/core/config.py",
|
||
"Теперь объясни функцию load_config",
|
||
"Почему так?",
|
||
]
|
||
)
|
||
|
||
assert_file_only_scope(first, "app/core/config.py")
|
||
assert "app/core/config.py" in second.retrieval_spec.filters.path_scope
|
||
assert "app/core/config.py" in third.retrieval_spec.filters.path_scope
|
||
second_file_anchors = [anchor.value for anchor in second.query_plan.anchors if anchor.type == "FILE_PATH" and anchor.source == "conversation_state"]
|
||
assert second_file_anchors == ["app/core/config.py"]
|
||
assert "app/core/config.py" in second.query_plan.keyword_hints
|
||
assert "app/core" not in second.query_plan.keyword_hints
|
||
assert any(anchor.type == "FILE_PATH" and anchor.source == "conversation_state" and anchor.span is None for anchor in third.query_plan.anchors)
|
||
carried_symbols = [anchor.value for anchor in third.query_plan.anchors if anchor.type == "SYMBOL" and anchor.source == "conversation_state"]
|
||
assert carried_symbols in ([], ["load_config"])
|
||
assert third.query_plan.sub_intent == "EXPLAIN_LOCAL"
|
||
layer_ids = [item.layer_id for item in third.retrieval_spec.layer_queries]
|
||
assert "C3_ENTRYPOINTS" not in layer_ids
|
||
|
||
|
||
def test_e2e_docs_switch_from_code_topic() -> None:
|
||
first, second = run_sequence(
|
||
[
|
||
"Объясни как работает ConfigManager",
|
||
"А что про это сказано в документации?",
|
||
]
|
||
)
|
||
|
||
assert_intent(first, "CODE_QA")
|
||
assert_intent(second, "DOCS_QA")
|
||
assert second.conversation_mode == "SWITCH"
|
||
assert_domains(second, ["DOCS"])
|
||
carried = [
|
||
anchor
|
||
for anchor in second.query_plan.anchors
|
||
if anchor.type == "SYMBOL" and anchor.value == "ConfigManager" and anchor.source == "conversation_state"
|
||
]
|
||
assert carried
|
||
assert carried[0].span is None
|
||
assert "ConfigManager" in second.query_plan.expansions
|
||
assert "ConfigManager" in second.query_plan.keyword_hints
|
||
|
||
|
||
def test_e2e_tests_toggle_flow() -> None:
|
||
first, second = run_sequence(
|
||
[
|
||
"Покажи тесты для ConfigManager",
|
||
"А теперь не про тесты, а про прод код",
|
||
]
|
||
)
|
||
|
||
assert_intent(first, "CODE_QA")
|
||
assert_intent(second, "CODE_QA")
|
||
assert_test_policy(first, "INCLUDE")
|
||
assert_test_policy(second, "EXCLUDE")
|
||
assert first.query_plan.sub_intent == "FIND_TESTS"
|
||
assert second.query_plan.sub_intent == "EXPLAIN"
|
||
assert "tests" in second.query_plan.negations
|
||
assert not second.query_plan.expansions
|
||
assert second.evidence_policy.require_flow is False
|
||
|
||
|
||
def test_e2e_open_file_then_generic_next_steps_is_lightweight() -> None:
|
||
first, second = run_sequence(
|
||
[
|
||
"Открой файл app/core/config.py",
|
||
"Что дальше?",
|
||
]
|
||
)
|
||
|
||
assert_file_only_scope(first, "app/core/config.py")
|
||
assert_file_only_scope(second, "app/core/config.py")
|
||
assert second.query_plan.sub_intent in {"EXPLAIN_LOCAL", "NEXT_STEPS"}
|
||
layer_ids = [item.layer_id for item in second.retrieval_spec.layer_queries]
|
||
assert "C3_ENTRYPOINTS" not in layer_ids
|
||
assert second.evidence_policy.require_flow is False
|
||
assert "app/core/config.py" in second.query_plan.keyword_hints
|
||
|
||
|
||
@pytest.mark.skipif(
|
||
not _live_gigachat_enabled(),
|
||
reason="requires RUN_INTENT_ROUTER_V2_LIVE=1 and GIGACHAT_TOKEN in environment or .env",
|
||
)
|
||
def test_intent_router_live_smoke_path_carryover() -> None:
|
||
router = GigaChatIntentRouterFactory().build()
|
||
first, second = run_sequence(
|
||
[
|
||
"Открой файл app/core/config.py",
|
||
"Что дальше?",
|
||
],
|
||
router=router,
|
||
trace_label="intent-router-live",
|
||
)
|
||
|
||
assert_file_only_scope(first, "app/core/config.py")
|
||
assert "app/core/config.py" in second.retrieval_spec.filters.path_scope
|
||
assert second.query_plan.sub_intent in {"EXPLAIN_LOCAL", "NEXT_STEPS"}
|
||
layer_ids = [item.layer_id for item in second.retrieval_spec.layer_queries]
|
||
assert "C3_ENTRYPOINTS" not in layer_ids
|
||
assert second.evidence_policy.require_flow is False
|