import pytest import time from types import SimpleNamespace from app.config.ai_prompts import PROMPT_AI_PMO_QA, PROMPT_RAG_QUERY_SEMANTICS from app.services.adaptive_rag_service.mixin_answer_entry import AdaptiveRAGAnswerEntryMixin from app.services.adaptive_rag_service.mixin_answer_mainline import AdaptiveRAGAnswerMainlineMixin from app.services.adaptive_rag_service.mixin_answer_payload import AdaptiveRAGAnswerPayloadMixin from app.services.adaptive_rag_service.mixin_answer_precheck import AdaptiveRAGAnswerPrecheckMixin from app.services.adaptive_rag_service.mixin_answer_retrieval import AdaptiveRAGAnswerRetrievalMixin from app.services.adaptive_rag_service.mixin_conversation_utils_a_p1 import AdaptiveRAGConversationUtilsAMixinP1 from app.services.adaptive_rag_service.mixin_deep_guard_answer import AdaptiveRAGDeepGuardAnswerMixin from app.services.adaptive_rag_service.mixin_deep_guard_rules import AdaptiveRAGDeepGuardRulesMixin from app.services.adaptive_rag_service.mixin_core_intent import AdaptiveRAGCoreIntentMixin from app.services.adaptive_rag_service.mixin_deterministic_query_core import ( AdaptiveRAGDeterministicQueryCoreMixin, ) from app.services.adaptive_rag_service.mixin_manifest_feature_p2 import AdaptiveRAGManifestFeatureMixinP2 from app.services.adaptive_rag_service.mixin_manifest_feature_p1 import AdaptiveRAGManifestFeatureMixinP1 from app.services.adaptive_rag_service.mixin_multistep_core import AdaptiveRAGMultiStepCoreMixin from app.services.adaptive_rag_service.mixin_multistep_flow import AdaptiveRAGMultiStepFlowMixin from app.services.adaptive_rag_service.mixin_query_route_gateway import AdaptiveRAGQueryRouteGatewayMixin from app.services.adaptive_rag_service.mixin_requirement_ack_core import AdaptiveRAGRequirementAckCoreMixin from app.services.adaptive_rag_service.mixin_retrieval_router_db import AdaptiveRAGRetrievalRouterDBMixin from app.services.adaptive_rag_service.mixin_retrieval_vector import AdaptiveRAGRetrievalVectorMixin from app.services.adaptive_rag_service.mixin_followup_anchor import FollowupAnchorResolution from app.services.adaptive_rag_service.mixin_followup_anchor import AdaptiveRAGFollowupAnchorMixin from app.services.adaptive_rag_service.mixin_fast_answer_project_main import AdaptiveRAGFastAnswerProjectMainMixin from app.services.adaptive_rag_service.mixin_fast_answer_project_utils import AdaptiveRAGFastAnswerProjectUtilsMixin from app.services.adaptive_rag_service.internal_query_context import InternalContextPacket from app.services.adaptive_rag_service.mixin_target_resolution import AdaptiveRAGTargetResolutionMixin from app.services.adaptive_rag_service.shared_part_02 import RetrievedItem from app.services.adaptive_rag_service.shared_part_02 import ACTION_MODE_READ, DEEP_RETRIEVAL_STEPS, DEEP_VECTOR_TOP_K from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner def _make_internal_context_packet( *, query_type: str, answer_contract: str, target_ref: str | None = None, ) -> InternalContextPacket: target_resolution = {} if target_ref: target_resolution = { "target_entity_type": "project", "target_ref": target_ref, "resolution_mode": "deterministic", "confidence": 0.96, } return InternalContextPacket.model_validate( { "packet_version": "v1", "question": "", "scope": { "tenant_id": 1, "project_id": None, "session_id": 11, "conversation_scope_key": None, "reply_to_message_id": None, "root_message_id": None, }, "route_decision": { "route_mode": "internal", "effective_source_policy": "internal", }, "operation_contract": { "query_type": query_type, }, "target_resolution": target_resolution, "query_plan": { "plan_version": "v2", "query_type": query_type, "entity_type": "project", "answer_contract": answer_contract, "target_ref": target_ref, "filters": [], }, "executor_summary": {}, "verifier_summary": {}, "uncertainties": [], } ) class _FakeAdaptiveRAGService( AdaptiveRAGAnswerEntryMixin, AdaptiveRAGAnswerMainlineMixin, ): def __init__(self) -> None: self.db = object() self.tenant_id = 9 self.ack_calls = 0 self.base_calls = 0 self.base_kwargs: dict[str, object] = {} def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "deep" if str(thinking_mode or "").strip().lower() == "deep" else "normal" async def _prepare_answer_runtime(self, **kwargs): clean_q = str(kwargs.get("clean_q") or "").strip() action_mode = str(kwargs.get("action_mode") or "").strip().lower() return { "session_id": 101, "turn_index": 2, "context_session_id": 101, "conversation": [], "state_row": None, "resolved_question": clean_q, "effective_q": clean_q, "rewrite_meta": {"mode": "direct"}, "query_type": "explain", "effective_action_mode": action_mode, "anchor_resolution": None, } async def _handle_ack_first_requirement(self, **kwargs): self.ack_calls += 1 _ = kwargs return {"answer": "ack-required", "processing_stage": "ack_only"} async def _execute_base_answer_mainline(self, **kwargs): self.base_calls += 1 self.base_kwargs = dict(kwargs) return {"answer": "base-answer", "processing_stage": "qa"} @pytest.mark.asyncio async def test_answer_entry_read_mainline_skips_requirement_ack() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="现在有哪些阻塞?", asked_by=1, asked_via="pmflow_manual", action_mode="read", sync_requirement=True, ) assert result["processing_stage"] == "qa" assert service.ack_calls == 0 assert service.base_calls == 1 @pytest.mark.asyncio async def test_answer_entry_write_mode_uses_requirement_ack_gate() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="请同步写入一条风险", asked_by=1, asked_via="pmflow_manual", action_mode="write", sync_requirement=True, ) assert result["processing_stage"] == "ack_only" assert service.ack_calls == 1 assert service.base_calls == 0 @pytest.mark.asyncio async def test_answer_entry_high_accuracy_channel_respects_requested_normal_mode() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="最近有哪些项目?", asked_by=1, asked_via="pmflow", thinking_mode="normal", sync_requirement=True, ) assert result["processing_stage"] == "qa" assert service.ack_calls == 0 assert service.base_calls == 1 assert service.base_kwargs["active_thinking_mode"] == "normal" assert service.base_kwargs["deep_thinking"] is False assert int(service.base_kwargs["deep_retrieval_steps"]) == DEEP_RETRIEVAL_STEPS assert int(service.base_kwargs["deep_vector_top_k"]) == DEEP_VECTOR_TOP_K @pytest.mark.asyncio async def test_answer_entry_forwards_consultant_mode_preference_to_mainline() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="这个方案怎么做更稳妥?", asked_by=1, asked_via="pmflow", thinking_mode="normal", consultant_mode_preference="advise", ) assert result["processing_stage"] == "qa" assert service.base_calls == 1 assert service.base_kwargs["consultant_mode_preference"] == "advise" @pytest.mark.asyncio async def test_answer_entry_mixed_request_skips_requirement_ack_gate() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="现在进展如何,并且新增任务确认流", asked_by=1, asked_via="pmflow_manual", action_mode="read_write", sync_requirement=True, message_intent="mixed", ) assert result["processing_stage"] == "qa" assert service.ack_calls == 0 assert service.base_calls == 1 assert service.base_kwargs["active_thinking_mode"] == "normal" assert service.base_kwargs["deep_thinking"] is False @pytest.mark.asyncio async def test_answer_entry_feishu_write_mode_still_goes_base_mainline() -> None: service = _FakeAdaptiveRAGService() result = await service.answer_question( question="请在飞书里补一条风险", asked_by=1, asked_via="feishu", action_mode="write", sync_requirement=True, ) assert result["processing_stage"] == "qa" assert service.ack_calls == 0 assert service.base_calls == 1 assert service.base_kwargs["active_thinking_mode"] == "normal" assert service.base_kwargs["deep_thinking"] is False assert int(service.base_kwargs["deep_retrieval_steps"]) == DEEP_RETRIEVAL_STEPS assert int(service.base_kwargs["deep_vector_top_k"]) == DEEP_VECTOR_TOP_K @pytest.mark.asyncio async def test_requirement_ack_auto_mode_respects_structured_query_bundle(monkeypatch: pytest.MonkeyPatch) -> None: class _ModeService(AdaptiveRAGRequirementAckCoreMixin): pass async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-test", "semantic_version": "v1", "source_system": "adaptive_rag_ack", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.91, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "question"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) mode = await _ModeService._resolve_action_mode_async( requested_mode="auto", question="先不要创建任务,告诉我当前进展和风险", effective_question="先不要创建任务,告诉我当前进展和风险", asked_via="pmflow_manual", ) assert mode == ACTION_MODE_READ def test_query_runtime_planner_prefers_fast_path_for_list_question_even_if_requested_deep() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakePlannerService() decision = service._plan_query_runtime( question="现在有哪些项目?", effective_question="现在有哪些项目?", query_type="list", requested_thinking_mode="deep", enforce_scene_prompt_only=False, ) assert decision.answer_mode == "fast_path" assert decision.thinking_mode == "normal" assert decision.allow_fast_path is True def test_query_runtime_planner_bypasses_fast_path_when_consultant_mode_preference_present() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakePlannerService() decision = service._plan_query_runtime( question="现在有哪些项目?", effective_question="现在有哪些项目?", query_type="list", requested_thinking_mode="normal", enforce_scene_prompt_only=False, consultant_mode_preference="advise", ) assert decision.answer_mode == "retrieval_normal" assert decision.thinking_mode == "normal" assert decision.allow_fast_path is False def test_query_runtime_planner_ignores_legacy_consultant_mode_for_internal_existence_query() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakePlannerService() decision = service._plan_query_runtime( question="当前系统内是否有为采购经理提出的需求?", effective_question="当前系统内是否有为采购经理提出的需求?", query_type="existence", requested_thinking_mode="normal", enforce_scene_prompt_only=False, consultant_mode_preference="analytical", ) assert decision.answer_mode == "fast_path" assert decision.thinking_mode == "normal" assert decision.allow_fast_path is True def test_query_runtime_planner_prefers_fast_path_for_existence_question() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakePlannerService() decision = service._plan_query_runtime( question="系统里有没有A股项目?", effective_question="系统里有没有A股项目?", query_type="existence", requested_thinking_mode="normal", enforce_scene_prompt_only=False, ) assert decision.answer_mode == "fast_path" assert decision.thinking_mode == "normal" assert decision.allow_fast_path is True def test_internal_query_plan_builds_project_existence_contract() -> None: class _FakePlannerService( AdaptiveRAGAnswerMainlineMixin, AdaptiveRAGFastAnswerProjectUtilsMixin, ): pass service = _FakePlannerService() plan = service._build_internal_query_plan( question="系统里有没有A股项目?", project_id=None, query_semantics={ "query_type": "existence", "subject_focus": "project", "scope_hint": "global", "effective_source_policy": "internal", }, ) assert plan is not None assert plan["query_type"] == "existence" assert plan["entity_type"] == "project" assert plan["answer_contract"] == "yes_no_with_evidence" assert plan["retrieval_policy"]["facts_first"] is True assert plan["filters"] == [] def test_operation_contract_preserves_structured_existence_before_target_resolution() -> None: class _FakeService( AdaptiveRAGTargetResolutionMixin, AdaptiveRAGAnswerMainlineMixin, ): pass service = _FakeService() contract = service._build_operation_contract( question="帮我看看系统里面有没有开票系统。", query_semantics={ "query_type": "existence", "effective_source_policy": "internal", "object_mentions": ["开票系统"], }, ) assert contract["query_type"] == "existence" assert contract["answer_contract"] == "yes_no_with_evidence" assert contract["object_mentions"] == ["开票系统"] def test_operation_contract_promotes_model_config_reference_to_status() -> None: class _FakeService( AdaptiveRAGTargetResolutionMixin, AdaptiveRAGAnswerMainlineMixin, ): pass service = _FakeService() contract = service._build_operation_contract( question="我们系统当前接的是哪个模型配置?", query_semantics={ "query_type": "existence", "effective_source_policy": "internal", "object_mentions": ["模型配置"], "query_profile": { "referenced_objects": ["system_config", "model_routing"], }, }, ) assert contract["query_type"] == "status" assert contract["answer_contract"] == "entity_snapshot" assert contract["object_mentions"] == ["模型配置"] @pytest.mark.asyncio async def test_internal_target_resolution_prefers_project_candidate_for_existence_query() -> None: class _FakeService(AdaptiveRAGTargetResolutionMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _collect_internal_target_candidates(self, **kwargs): _ = kwargs return [ { "target_entity_type": "project", "target_ref": "project:6", "resolved_label": "自动开票系统", "score": 0.96, "source_kind": "semantic_facts", }, { "target_entity_type": "feature", "target_ref": "feature:/invoice", "resolved_label": "开票系统", "score": 0.82, "source_kind": "feature_catalog", }, ] service = _FakeService() resolution = await service._resolve_internal_target( question="帮我看看系统里面有没有开票系统。", project_id=None, query_semantics={"effective_source_policy": "internal"}, system_snapshot={}, operation_contract={ "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["开票系统"], }, ) assert resolution["target_entity_type"] == "project" assert resolution["target_ref"] == "project:6" assert resolution["resolved_label"] == "自动开票系统" assert resolution["resolution_mode"] == "deterministic" @pytest.mark.asyncio async def test_internal_target_resolution_prefers_session_followup_project_binding_before_semantic_filter() -> None: class _FakeService(AdaptiveRAGTargetResolutionMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _collect_internal_target_candidates(self, **kwargs): _ = kwargs return [] service = _FakeService() resolution = await service._resolve_internal_target( question="围绕“采购经理每日实时库存上报服务”回答:那个需求是否存在系统?", project_id=None, query_semantics={ "effective_source_policy": "internal", "subject_focus": "project", "query_profile": { "needs_private_context": True, "needs_freshness": False, "referenced_objects": ["project_object"], }, "session_followup_context": { "rewrite_mode": "state_subject", "last_subject": "采购经理每日实时库存上报服务", "last_entity_ids": [123], }, }, system_snapshot={}, operation_contract={ "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["采购经理每日实时库存上报服务"], }, ) assert resolution["target_entity_type"] == "project" assert resolution["target_ref"] == "project:123" assert resolution["resolved_label"] == "采购经理每日实时库存上报服务" assert resolution["resolution_mode"] == "deterministic" assert resolution["binding_source"] == "session_followup_context" def test_config_target_candidates_require_structured_referenced_objects() -> None: class _FakeService(AdaptiveRAGTargetResolutionMixin): pass service = _FakeService() rows = service._collect_config_target_candidates( query_semantics={ "query_profile": { "referenced_objects": ["system_config", "model_routing"], } }, operation_contract={ "object_mentions": ["当前模型配置"], }, ) assert len(rows) == 1 assert rows[0]["target_ref"] == "config:model_routing" assert rows[0]["resolved_label"] == "当前模型配置" def test_internal_query_runtime_metrics_include_contract_and_resolution_summaries() -> None: class _FakeService(AdaptiveRAGTargetResolutionMixin): pass service = _FakeService() metrics = service._inject_internal_query_runtime_summaries( retrieval_metrics={"db_query_ms": 12}, query_semantics={ "operation_contract": { "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["开票系统"], }, "target_resolution": { "target_entity_type": "project", "target_ref": "project:6", "resolved_label": "自动开票系统", "resolution_mode": "deterministic", "confidence": 0.96, }, "query_plan": { "plan_version": "v2", "query_type": "existence", "entity_type": "project", "target_ref": "project:6", "answer_contract": "yes_no_with_evidence", "filters": [{"field": "project_id", "operator": "equals", "value": 6}], }, }, ) assert metrics["db_query_ms"] == 12 assert metrics["operation_contract_summary"]["query_type"] == "existence" assert metrics["target_resolution_summary"]["target_ref"] == "project:6" assert metrics["query_plan_summary"]["plan_version"] == "v2" def test_internal_query_plan_uses_resolved_project_target_ref() -> None: class _FakePlannerService( AdaptiveRAGAnswerMainlineMixin, AdaptiveRAGFastAnswerProjectUtilsMixin, ): pass service = _FakePlannerService() plan = service._build_internal_query_plan( question="帮我看看系统里面有没有开票系统。", project_id=None, query_semantics={ "query_type": "explain", "subject_focus": "feature", "scope_hint": "global", "effective_source_policy": "internal", }, operation_contract={ "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["开票系统"], }, target_resolution={ "target_entity_type": "project", "target_ref": "project:6", "resolved_label": "自动开票系统", "resolution_mode": "deterministic", }, ) assert plan is not None assert plan["plan_version"] == "v2" assert plan["query_type"] == "existence" assert plan["entity_type"] == "project" assert plan["target_ref"] == "project:6" assert plan["filters"][0]["field"] == "project_id" assert plan["filters"][0]["value"] == 6 def test_internal_query_plan_skips_lexical_project_filter_fallback() -> None: class _FakePlannerService(AdaptiveRAGAnswerMainlineMixin): @staticmethod def _extract_project_lookup_keyword(question: str) -> str: # pragma: no cover - should not be called raise AssertionError(f"lexical keyword fallback should be skipped: {question}") @staticmethod def _extract_query_terms(question: str, *, preferred: str = "") -> list[str]: # pragma: no cover - should not be called raise AssertionError(f"lexical term fallback should be skipped: {question}, {preferred}") service = _FakePlannerService() plan = service._build_internal_query_plan( question="系统里有没有A股项目?", project_id=None, query_semantics={ "query_type": "existence", "subject_focus": "project", "scope_hint": "global", "effective_source_policy": "internal", }, operation_contract={ "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": [], }, ) assert plan is not None assert plan["filters"] == [] @pytest.mark.asyncio async def test_deterministic_query_prefers_project_created_at_answer() -> None: calls: list[str] = [] class _FakeDeterministicService(AdaptiveRAGDeterministicQueryCoreMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _try_fast_project_created_at_answer( self, *, clean_q: str, project_id, query_semantics=None, internal_context_packet=None, ): calls.append("created_at") _ = (clean_q, project_id, query_semantics, internal_context_packet) return {"answer": "创建于 2026-03-01 09:30。", "sources": [], "missing_fields": []} async def _try_fast_project_progress_answer( self, *, clean_q: str, project_id, query_semantics=None, internal_context_packet=None, ): calls.append("progress") _ = (clean_q, project_id, query_semantics, internal_context_packet) return {"answer": "进度回答", "sources": []} async def _try_fast_project_existence_answer( self, *, clean_q: str, project_id, query_semantics=None, internal_context_packet=None, ): calls.append("existence") _ = (clean_q, project_id, query_semantics, internal_context_packet) return {"answer": "存在性回答", "sources": []} service = _FakeDeterministicService() result = await service._run_deterministic_query( question="他是什么时候创建的?", project_id=7, query_type="status", internal_context_packet=_make_internal_context_packet( query_type="status", answer_contract="entity_snapshot", target_ref="project:7", ), ) assert result is not None assert "创建于 2026-03-01 09:30" in str(result["answer"]) assert calls[0] == "created_at" @pytest.mark.asyncio async def test_deterministic_query_runs_project_existence_for_existence_type() -> None: calls: list[str] = [] class _FakeDeterministicService(AdaptiveRAGDeterministicQueryCoreMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _try_fast_project_existence_answer( self, *, clean_q: str, project_id, query_semantics=None, internal_context_packet=None, ): calls.append("existence") _ = (clean_q, project_id, query_semantics, internal_context_packet) return {"answer": "有,匹配到 1 个项目。", "sources": []} service = _FakeDeterministicService() result = await service._run_deterministic_query( question="系统里有没有A股项目?", project_id=None, query_type="existence", query_semantics={"subject_focus": "project"}, internal_context_packet=_make_internal_context_packet( query_type="existence", answer_contract="yes_no_with_evidence", ), ) assert result is not None assert "匹配到 1 个项目" in str(result["answer"]) assert calls == ["existence"] @pytest.mark.asyncio async def test_deterministic_query_defers_internal_project_existence_to_semantic_snapshot() -> None: calls: list[str] = [] class _FakeDeterministicService(AdaptiveRAGDeterministicQueryCoreMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _try_fast_project_existence_answer(self, *, clean_q: str, project_id, query_semantics=None): calls.append("existence") _ = (clean_q, project_id, query_semantics) return {"answer": "有,匹配到 1 个项目。", "sources": []} service = _FakeDeterministicService() result = await service._run_deterministic_query( question="系统里有没有采购经理每日实时库存上报服务?", project_id=None, query_type="existence", query_semantics={ "subject_focus": "project", "source_policy": "internal", "effective_source_policy": "internal", "operation_contract": { "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["采购经理每日实时库存上报服务"], }, }, ) assert result is None assert calls == [] @pytest.mark.asyncio async def test_project_existence_answer_prefers_resolved_project_target() -> None: class _ScalarResult: def __init__(self, row): self._row = row def scalar_one_or_none(self): return self._row class _FakeSession: def __init__(self, row): self._row = row async def execute(self, stmt): _ = stmt return _ScalarResult(self._row) project_row = SimpleNamespace( id=72, title="企业知识库问答AI项目", name="企业知识库问答AI项目", status="active", ) class _FakeService(AdaptiveRAGFastAnswerProjectMainMixin, AdaptiveRAGFastAnswerProjectUtilsMixin): def __init__(self) -> None: self.db = _FakeSession(project_row) self.tenant_id = 1 service = _FakeService() result = await service._try_fast_project_existence_answer( clean_q="系统里有没有知识库?", project_id=None, query_semantics={ "query_type": "existence", "subject_focus": "unknown", "target_resolution": { "target_entity_type": "project", "target_ref": "project:72", "resolved_label": "企业知识库问答AI项目", }, }, ) assert result is not None assert "#72 企业知识库问答AI项目" in str(result["answer"]) assert result["sources"][0]["metadata"]["project_id"] == 72 @pytest.mark.asyncio async def test_project_existence_answer_uses_structured_mentions_without_lexical_extractors() -> None: class _FakeService( AdaptiveRAGFastAnswerProjectMainMixin, AdaptiveRAGFastAnswerProjectUtilsMixin, ): def __init__(self) -> None: self.db = object() self.tenant_id = 1 @staticmethod def _extract_project_lookup_keyword(question: str) -> str: # pragma: no cover - should not be called raise AssertionError(f"lexical keyword fallback should be skipped: {question}") @staticmethod def _extract_query_terms(question: str, *, preferred: str = "") -> list[str]: # pragma: no cover - should not be called raise AssertionError(f"lexical term fallback should be skipped: {question}, {preferred}") async def _try_fast_project_existence_answer_from_semantic_facts(self, *, query_terms: list[str], project_id): assert query_terms == ["A股项目"] assert project_id is None return { "answer": "有,当前匹配到 1 个相关项目:\n#101 A股趋势打分工具(状态:active)", "sources": [{"metadata": {"table": "semantic_facts", "match_count": 1}}], } async def _search_projects_by_keyword(self, *, keyword: str, project_id, limit: int): # pragma: no cover - should not be called raise AssertionError(f"text fallback should be skipped: {keyword}, {project_id}, {limit}") service = _FakeService() result = await service._try_fast_project_existence_answer( clean_q="系统里有没有A股项目?", project_id=None, query_semantics={ "query_type": "existence", "subject_focus": "project", "scope_hint": "global", "operation_contract": { "query_type": "existence", "object_mentions": ["A股项目"], }, }, ) assert result is not None assert "#101 A股趋势打分工具" in str(result["answer"]) @pytest.mark.asyncio async def test_execute_mainline_skips_snapshot_direct_for_project_existence_target() -> None: class _FakeContextCache: async def get_system_snapshot(self, **kwargs): _ = kwargs return {"platform_profile": {"feature_catalog": [{"name": "智能配置", "route": "/settings/ai"}]}} async def get_user_snapshot(self, **kwargs): _ = kwargs return {"projects": []} class _FakeService( AdaptiveRAGAnswerMainlineMixin, AdaptiveRAGTargetResolutionMixin, ): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.context_cache = _FakeContextCache() self.retrieval_called = False def _plan_query_runtime(self, **kwargs): _ = kwargs return SimpleNamespace( thinking_mode="normal", retrieval_answer_mode="retrieval_normal", budget=SimpleNamespace(max_steps=2, vector_top_k=3), allow_fast_path=True, reason="deterministic_query_type", ) def _build_followup_clarification_payload(self, **kwargs): _ = kwargs return None def _build_route_clarification_payload(self, **kwargs): _ = kwargs return None async def _build_meta_followup_payload(self, **kwargs): _ = kwargs return None async def _maybe_return_clarification_response(self, **kwargs): _ = kwargs return None async def _maybe_return_meta_followup_response(self, **kwargs): _ = kwargs return None def _get_active_external_qa_context(self): return {} def _build_operation_contract(self, *, question: str, query_semantics): _ = (question, query_semantics) return { "query_type": "existence", "answer_contract": "yes_no_with_evidence", "object_mentions": ["开票系统"], } async def _resolve_internal_target(self, **kwargs): _ = kwargs return { "target_entity_type": "project", "target_ref": "project:6", "resolved_label": "自动开票系统", "resolution_mode": "deterministic", "confidence": 0.96, "candidate_summary": [], } async def _run_deterministic_query(self, **kwargs): _ = kwargs return None async def _build_retrieval_response(self, **kwargs): self.retrieval_called = True assert kwargs["query_type"] == "existence" assert kwargs["query_semantics"]["target_resolution"]["target_ref"] == "project:6" return {"processing_stage": "retrieval"} def _build_snapshot_direct_answer(self, *args, **kwargs): raise AssertionError("snapshot direct answer should be skipped for project existence target") async def _maybe_return_snapshot_direct_response(self, **kwargs): raise AssertionError("snapshot direct response should be skipped for project existence target") async def _build_internal_model_config_fast_answer(self, **kwargs): _ = kwargs return None async def _try_glm_project_semantic_answer(self, **kwargs): _ = kwargs return None service = _FakeService() result = await service._execute_base_answer_mainline( clean_q="帮我看看系统里面有没有开票系统。", asked_by=1, asked_via="pmflow", project_id=None, source_chat_id=None, session_id=11, turn_index=3, context_session_id=11, conversation=[], state_row=None, effective_q="帮我看看系统里面有没有开票系统。", rewrite_meta={}, query_type="explain", query_semantics={ "source_policy": "internal", "effective_source_policy": "internal", "subject_focus": "feature", "object_mentions": ["开票系统"], }, retrieval_domains=["db", "pmflow"], scene_semantics={"scene_key": "ai_pmo", "snapshot_focus": "feature", "deep_intent": "none"}, thinking_mode="normal", active_thinking_mode="normal", deep_thinking=False, enforce_scene_prompt_only=False, deep_retrieval_steps=2, deep_vector_top_k=3, started=0.0, message_intent="question", mixed_signal_part=None, consultant_mode_preference=None, route_decision={ "route_mode": "internal", "effective_source_policy": "internal", "reply_contract": {"head": "内部知识库查询:", "tail_hint": None}, }, ) assert result["processing_stage"] == "retrieval" assert service.retrieval_called is True @pytest.mark.asyncio async def test_query_semantics_prefers_structured_model_output_over_hint_tables( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = (prompt, kwargs) return ( '{"intent":"question","question_part":"我感觉很多重复的项目在系统,你能列一下吗?",' '"statement_part":"","query_type":"list","retrieval_domains":["db","graph"],' '"subject_focus":"project","scope_hint":"global","feature_focus":"none"}' ) class _FakeService(AdaptiveRAGCoreIntentMixin, AdaptiveRAGDeterministicQueryCoreMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-query-type", "semantic_version": "v1", "source_system": "adaptive_rag", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.93, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "question"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) service = _FakeService() semantics = await service._resolve_query_semantics( message="我感觉很多重复的项目在系统,你能列一下吗?", source_system="pmflow", ) assert semantics["query_type"] == "list" assert semantics["retrieval_domains"] == ["db", "graph"] assert semantics["intent"] == "question" assert semantics["subject_focus"] == "project" assert semantics["scope_hint"] == "global" assert semantics["feature_focus"] == "none" @pytest.mark.asyncio async def test_query_semantics_accepts_existence_query_type_from_structured_output( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = (prompt, kwargs) return ( '{"intent":"question","question_part":"系统里有没有A股项目?",' '"statement_part":"","query_type":"existence","retrieval_domains":["db","graph"],' '"object_mentions":["A股项目"],' '"subject_focus":"project","scope_hint":"global","feature_focus":"none",' '"query_profile":{"needs_private_context":true,"needs_freshness":false,"referenced_objects":["project_object"],"confidence":0.81},' '"confidence":0.91}' ) class _FakeService(AdaptiveRAGCoreIntentMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-query-existence", "semantic_version": "v1", "source_system": "adaptive_rag", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.89, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "question"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) service = _FakeService() semantics = await service._resolve_query_semantics( message="系统里有没有A股项目?", source_system="pmflow", ) assert semantics["query_type"] == "existence" assert semantics["object_mentions"] == ["A股项目"] assert semantics["retrieval_domains"] == ["db", "graph"] assert semantics["query_profile"]["referenced_objects"] == ["project_object"] assert semantics["query_profile"]["needs_private_context"] is True @pytest.mark.asyncio async def test_query_semantics_reuses_cached_result_for_identical_request( monkeypatch: pytest.MonkeyPatch, ) -> None: planner_calls: list[dict[str, object]] = [] llm_calls: list[str] = [] class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = kwargs llm_calls.append(prompt) return ( '{"intent":"question","question_part":"系统里有没有采购经理每日实时库存上报服务?",' '"statement_part":"","query_type":"existence","retrieval_domains":["db","pmflow"],' '"object_mentions":["采购经理每日实时库存上报服务"],' '"subject_focus":"project","scope_hint":"global","feature_focus":"none",' '"source_policy":"internal","effective_source_policy":"internal",' '"query_profile":{"needs_private_context":true,"needs_freshness":false,"referenced_objects":["project_object"],"confidence":0.93},' '"confidence":0.96}' ) class _FakeService(AdaptiveRAGCoreIntentMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = cls planner_calls.append( { "message_text": message_text, "source_system": source_system, "metadata": dict(metadata or {}), } ) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-query-cache", "semantic_version": "v1", "source_system": "adaptive_rag", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.94, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "question"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) service = _FakeService() first = await service._resolve_query_semantics( message="系统里有没有采购经理每日实时库存上报服务?", source_system="pmflow", metadata={"project_id": 1}, ) second = await service._resolve_query_semantics( message="系统里有没有采购经理每日实时库存上报服务?", source_system="pmflow", metadata={"project_id": 1}, ) assert first == second assert len(planner_calls) == 1 assert len(llm_calls) == 1 @pytest.mark.asyncio async def test_query_semantics_prompt_contract_guides_internal_project_source_policy( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = kwargs if '"source_policy"' in prompt and '"effective_source_policy"' in prompt: return ( '{"intent":"question","question_part":"系统里有没有采购经理每日实时库存上报服务?",' '"statement_part":"","query_type":"existence","retrieval_domains":["db","pmflow"],' '"object_mentions":["采购经理每日实时库存上报服务"],' '"subject_focus":"project","scope_hint":"global","feature_focus":"none",' '"source_policy":"internal","effective_source_policy":"internal",' '"query_profile":{"needs_private_context":true,"needs_freshness":false,"referenced_objects":["project_object"],"confidence":0.93},' '"confidence":0.96}' ) return ( '{"intent":"question","question_part":"系统里有没有采购经理每日实时库存上报服务?",' '"statement_part":"","query_type":"existence","retrieval_domains":["db","pmflow"],' '"object_mentions":["采购经理每日实时库存上报服务"],' '"subject_focus":"project","scope_hint":"global","feature_focus":"none",' '"query_profile":{"needs_private_context":true,"needs_freshness":false,"referenced_objects":["project_object"],"confidence":0.93},' '"confidence":0.96}' ) class _FakeService(AdaptiveRAGCoreIntentMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-query-internal-project", "semantic_version": "v1", "source_system": "adaptive_rag", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.94, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "question"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) service = _FakeService() semantics = await service._resolve_query_semantics( message="系统里有没有采购经理每日实时库存上报服务?", source_system="pmflow", ) assert '"source_policy"' in PROMPT_RAG_QUERY_SEMANTICS assert '"effective_source_policy"' in PROMPT_RAG_QUERY_SEMANTICS assert semantics["source_policy"] == "internal" assert semantics["effective_source_policy"] == "internal" def test_feature_catalog_match_skips_lexical_query_term_fallback() -> None: class _FakeService(AdaptiveRAGManifestFeatureMixinP1): @staticmethod def _extract_query_terms(question: str, *, preferred: str = "") -> list[str]: # pragma: no cover - should not be called raise AssertionError(f"feature lexical term fallback should be skipped: {question}, {preferred}") service = _FakeService() rows = service._match_feature_catalog_entries( question="智能配置入口在哪?", system_snapshot={ "platform_profile": { "feature_catalog": [ { "name": "智能配置", "route": "/ai-config", "summary": "管理模型和提示词配置", "aliases": ["模型设置"], } ] } }, ) assert rows assert rows[0]["name"] == "智能配置" def test_feature_catalog_match_skips_generic_project_list_for_unrelated_project_mention() -> None: class _FakeService(AdaptiveRAGManifestFeatureMixinP1): pass service = _FakeService() rows = service._match_feature_catalog_entries( question="A股项目", system_snapshot={ "platform_profile": { "feature_catalog": [ { "name": "项目列表", "route": "/projects", "summary": "查看全部项目", }, { "name": "智能配置", "route": "/ai-config", "summary": "管理模型配置", }, ] } }, ) assert rows == [] def test_internal_answer_contract_verifier_blocks_total_count_substitution_for_existence() -> None: class _FakeVerifier(AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin): pass service = _FakeVerifier() verdict = service._verify_internal_answer_contract( query_type="existence", answer="当前系统里项目总数为 42 个。", sources=[ { "source": "db", "label": "Project database", "score": 0.99, "metadata": {"table": "projects"}, } ], route_decision={"route_mode": "internal", "effective_source_policy": "internal"}, internal_context_packet=_make_internal_context_packet( query_type="existence", answer_contract="yes_no_with_evidence", ), ) assert verdict["ok"] is False assert verdict["reason_code"] == "no_total_count_substitution" def test_internal_answer_contract_verifier_allows_project_snapshot_with_task_totals_for_existence() -> None: class _FakeVerifier(AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin): pass service = _FakeVerifier() verdict = service._verify_internal_answer_contract( query_type="existence", answer=( "有。项目ID 123,项目名“采购经理每日实时库存上报服务”,状态 active,负责人 系统管理员," "最近更新 2026-04-13T14:27:59.301650,任务总数 4,已完成 0,进行中 0,待处理 4。" ), sources=[ { "source": "db", "label": "Project database", "score": 0.99, "metadata": {"table": "projects", "matched_project_ids": [123]}, } ], route_decision={"route_mode": "internal", "effective_source_policy": "internal"}, internal_context_packet=_make_internal_context_packet( query_type="existence", answer_contract="yes_no_with_evidence", target_ref="project:123", ), ) assert verdict["ok"] is True assert verdict["reason_code"] is None def test_internal_answer_contract_verifier_allows_evidence_backed_existence_answer() -> None: class _FakeVerifier(AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin): pass service = _FakeVerifier() verdict = service._verify_internal_answer_contract( query_type="existence", answer="有,当前匹配到 1 个相关项目:#101 A股趋势打分工具(状态:active)", sources=[ { "source": "db", "label": "Project database", "score": 0.99, "metadata": {"table": "projects", "match_count": 1}, } ], route_decision={"route_mode": "internal", "effective_source_policy": "internal"}, internal_context_packet=_make_internal_context_packet( query_type="existence", answer_contract="yes_no_with_evidence", target_ref="project:101", ), ) assert verdict["ok"] is True assert verdict["reason_code"] is None @pytest.mark.asyncio async def test_classify_intent_prefers_structured_semantics_without_rule_split( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = (prompt, kwargs) return ( '{"intent":"mixed","question_part":"现在进展如何?","statement_part":"并新增任务确认流",' '"query_type":"status","retrieval_domains":["db","graph","pmflow"]}' ) class _FakeService(AdaptiveRAGCoreIntentMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() async def _fake_aplan_message(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return WorkflowSemanticPlanner.deserialize_bundle( { "bundle_id": "semantic-intent", "semantic_version": "v1", "source_system": "adaptive_rag", "primary_workflow": "query_flow", "workflows": [ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "requires_confirmation": False, "confidence": 0.88, "clarification_needed": False, "metadata": {}, } ], "message_gate": "auto", "reply_contract": {"mode": "text", "template_key": "workflow_bundle", "summary": "query_flow"}, "diagnostics": {"message_intent": "business_review"}, } ) monkeypatch.setattr(WorkflowSemanticPlanner, "aplan_message", classmethod(_fake_aplan_message)) service = _FakeService() result = await service.classify_intent("现在进展如何?并新增任务确认流") assert result == { "intent": "mixed", "confidence": 0.88, "question_part": "现在进展如何?", "statement_part": "并新增任务确认流", } @pytest.mark.asyncio async def test_vector_retrieval_skips_legacy_fallback_for_non_postgresql_session() -> None: class _FakeLLM: async def get_embedding_async(self, question: str, **kwargs): _ = (question, kwargs) return [0.1, 0.2, 0.3] class _FakeService(AdaptiveRAGRetrievalVectorMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() self.legacy_called = False def _map_domains_to_source_types(self, domains: list[str]) -> list[str]: return list(domains) def _is_postgresql_session(self) -> bool: return False async def _retrieve_vector_context_legacy(self, **kwargs): _ = kwargs self.legacy_called = True return ["legacy-hit"] service = _FakeService() result = await service._retrieve_vector_context( "查询项目文档", domains=["doc"], project_id=None, source_chat_id=None, ) assert result == [] assert service.legacy_called is False @pytest.mark.asyncio async def test_vector_retrieval_skips_legacy_fallback_when_pg_query_errors() -> None: class _FakeLLM: async def get_embedding_async(self, question: str, **kwargs): _ = (question, kwargs) return [0.1, 0.2, 0.3] class _FakeService(AdaptiveRAGRetrievalVectorMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() self.legacy_called = False def _map_domains_to_source_types(self, domains: list[str]) -> list[str]: return list(domains) def _is_postgresql_session(self) -> bool: return True async def _retrieve_vector_context_pg(self, **kwargs): _ = kwargs return None async def _retrieve_vector_context_legacy(self, **kwargs): _ = kwargs self.legacy_called = True return ["legacy-hit"] service = _FakeService() result = await service._retrieve_vector_context( "查询项目文档", domains=["doc"], project_id=None, source_chat_id=None, ) assert result == [] assert service.legacy_called is False @pytest.mark.asyncio async def test_deep_guard_answer_raises_on_empty_llm_response() -> None: class _FakeLLM: async def generate(self, prompt: str, **kwargs): _ = (prompt, kwargs) return "" class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "normal" if str(thinking_mode or "").strip().lower() != "deep" else "deep" async def get_project_manifest(self, project_id): _ = project_id return {} async def _select_scene_prompt(self, **kwargs): _ = kwargs return "external_trend", "请回答问题" def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() with pytest.raises(ValueError, match="adaptive_rag_answer_empty_response"): await service._generate_answer( question="最近AI行业有什么动态", retrieved=[], conversation=[], project_id=None, asked_via="feishu", ) @pytest.mark.asyncio async def test_deep_guard_answer_uses_low_first_routing_meta_for_deep_thinking() -> None: class _FakeLLM: def __init__(self) -> None: self.kwargs: dict[str, object] = {} async def generate(self, prompt: str, **kwargs): _ = prompt self.kwargs = dict(kwargs) return "low-tier answer" class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "deep" if str(thinking_mode or "").strip().lower() == "deep" else "normal" async def get_project_manifest(self, project_id): _ = project_id return {} async def _select_scene_prompt(self, **kwargs): _ = kwargs return "external_trend", "请回答问题" def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() result = await service._generate_answer( question="最近AI行业有什么动态", retrieved=[], conversation=[], project_id=None, asked_via="feishu", thinking_mode="deep", ) assert result == "low-tier answer" assert service.llm.kwargs["function_name"] == "adaptive_rag_answer_deep" routing_meta = service.llm.kwargs["routing_meta"] assert isinstance(routing_meta, dict) assert routing_meta["tier_alias"] == "low" assert routing_meta["planned_tier"] == "low" assert routing_meta["response_contract"] == "complete" assert routing_meta["fallback_policy"] == "low_to_high" assert routing_meta["execution_mode"] == "query" assert routing_meta["routing_decision"] == "force_low_first" @pytest.mark.asyncio async def test_deep_guard_answer_external_route_uses_external_presenter_without_internal_context() -> None: class _FakePresenter: def __init__(self) -> None: self.calls: list[dict[str, object]] = [] async def present(self, *, question: str, context_packet: dict, thinking_mode: str, answer_max_tokens=None): self.calls.append( { "question": question, "context_packet": dict(context_packet), "thinking_mode": thinking_mode, "answer_max_tokens": answer_max_tokens, } ) return { "answer_text": "只基于外部证据生成的回答", "prompt_variant_id": "external_summary_with_sources", } class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = object() self.presenter = _FakePresenter() self.prompt_variant = "unknown" def _normalize_thinking_mode(self, thinking_mode: str) -> str: _ = thinking_mode return "normal" async def get_project_manifest(self, project_id): # pragma: no cover - should not be called raise AssertionError(f"manifest should be skipped for external route: {project_id}") async def _resolve_answer_scene_semantics(self, **kwargs): # pragma: no cover - should not be called raise AssertionError(f"scene semantics should be skipped for external route: {kwargs}") async def _select_scene_prompt(self, **kwargs): # pragma: no cover - should not be called raise AssertionError(f"scene prompt should be skipped for external route: {kwargs}") def _build_external_answer_presenter(self): return self.presenter def _remember_answer_prompt_variant(self, prompt_variant_id: str) -> None: self.prompt_variant = prompt_variant_id def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() result = await service._generate_answer( question="最近 AI 编程模型有什么新动态?", retrieved=[], conversation=[], project_id=7, asked_via="feishu", query_semantics={"effective_source_policy": "external"}, external_context_packet={ "question": "最近 AI 编程模型有什么新动态?", "plan": { "answer_contract": "summary_with_sources", "time_range_hint": "最近", }, "adapter_attempts": [{"adapter": "live_search", "status": "success"}], "verified_evidence": [ { "source_type": "url", "source_label": "OpenAI Developers", "title": "Models - Codex | OpenAI Developers", "target_url": "https://developers.openai.com/codex/models", "snippet": "Latest: GPT-5.4", } ], "verifier": {"ok": True}, "uncertainties": [], }, ) assert result == "只基于外部证据生成的回答" assert service.prompt_variant == "external_summary_with_sources" assert len(service.presenter.calls) == 1 assert service.presenter.calls[0]["question"] == "最近 AI 编程模型有什么新动态?" @pytest.mark.asyncio async def test_retrieval_response_external_insufficient_evidence_keeps_external_clarify_contract() -> None: class _FakeDB: def add(self, obj) -> None: obj.id = 1 async def flush(self) -> None: return None async def commit(self) -> None: return None async def refresh(self, obj) -> None: _ = obj return None class _FakeContextCache: async def append_session_messages(self, **kwargs) -> None: _ = kwargs return None class _FakeService( AdaptiveRAGAnswerRetrievalMixin, AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin, AdaptiveRAGDeepGuardRulesMixin, ): def __init__(self) -> None: self.db = _FakeDB() self.context_cache = _FakeContextCache() self.tenant_id = 1 def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "deep" if str(thinking_mode or "").strip().lower() == "deep" else "normal" async def _execute_external_query_gateway(self, **kwargs): _ = kwargs return [], { "mode": "external_query_gateway_verifier_blocked", "external_context_packet": { "question": "OpenAI 最近发布了哪些新模型?", "plan": { "answer_contract": "summary_with_sources", }, "adapter_attempts": [{"adapter": "live_search", "status": "empty"}], "verified_evidence": [], "verifier": { "ok": False, "reason_code": "insufficient_evidence", "missing_fields": ["external_evidence"], }, "uncertainties": ["insufficient_evidence"], }, "tool_trace": [], "domains_used": ["url"], } async def _generate_answer(self, *args, **kwargs): _ = (args, kwargs) return "当前关于“OpenAI 最近发布了哪些新模型?”的外部证据不足。请补充更明确的对象、范围或继续提供可核验来源后,我再继续分析。" def _peek_answer_prompt_variant(self): return "external_clarify" def _peek_last_answer_routing_snapshot(self): return {} def _dedupe_sources(self, retrieved): _ = retrieved return [] def _build_qa_record_runtime_fields(self, **kwargs): return { "policy_version": "test", "citations": [], "sources_used": list(kwargs.get("sources") or []), "evidence_pack": {}, "tool_trace": list(kwargs.get("tool_trace") or []), } def _build_qa_record_external_fields(self, **kwargs): _ = kwargs return {} async def _update_session_state_after_turn(self, **kwargs): _ = kwargs return None def _build_response_payload(self, **kwargs): return kwargs service = _FakeService() result = await service._build_retrieval_response( clean_q="OpenAI 最近发布了哪些新模型?", effective_q="OpenAI 最近发布了哪些新模型?", asked_by=1, asked_via="feishu", project_id=None, source_chat_id=None, session_id=11, turn_index=2, context_session_id=11, conversation=[], state_row=None, rewrite_meta={"mode": "direct"}, query_type="explain", query_semantics={"effective_source_policy": "external"}, retrieval_domains=["url"], scene_semantics={"scene_key": "default", "deep_intent": "none"}, thinking_mode="normal", active_thinking_mode="normal", deep_thinking=False, forced_deep_reason="", deep_retrieval_steps=2, deep_vector_top_k=3, system_snapshot={}, user_snapshot=None, started=0.0, message_intent=None, mixed_signal_part=None, answer_mode="retrieval_normal", consultant_mode_preference=None, route_decision={ "route_mode": "external", "effective_source_policy": "external", "decision_origin": "query_semantics_contract", "reply_contract": {"head": "外部实时查询:", "tail_hint": None}, "query_profile": {"needs_freshness": True, "referenced_objects": ["public_api"]}, }, ) assert result["answer_mode"] == "clarify" assert result["grounding_status"] == "insufficient_evidence" assert result["sources"] == [] assert result["sources_used"] == [] assert "系统快照" not in result["answer"] assert "外部证据不足" in result["answer"] assert result["answer"].startswith("外部实时查询:") assert result["missing_fields"] == ["external_evidence"] @pytest.mark.asyncio async def test_retrieval_response_exposes_fit_brief_and_external_recommendation_blocks() -> None: class _FakeDB: def add(self, obj) -> None: obj.id = 2 async def flush(self) -> None: return None async def commit(self) -> None: return None async def refresh(self, obj) -> None: _ = obj return None class _FakeContextCache: async def append_session_messages(self, **kwargs) -> None: _ = kwargs return None class _FakeService( AdaptiveRAGAnswerRetrievalMixin, AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin, AdaptiveRAGDeepGuardRulesMixin, ): def __init__(self) -> None: self.db = _FakeDB() self.context_cache = _FakeContextCache() self.tenant_id = 1 self.fit_brief_seen = None def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "deep" if str(thinking_mode or "").strip().lower() == "deep" else "normal" def _to_float(self, value, default=0.0): try: return float(value) except Exception: return float(default) def _build_policy_version(self, *, prompt_variant_id: str | None = None) -> str: return f"retrieval-v2.1|routing:unknown|prompt:{prompt_variant_id or 'unknown'}" def _normalize_query_route_decision(self, payload): return dict(payload or {}) async def _build_project_fit_brief(self, **kwargs): _ = kwargs return { "project_summary": "当前项目:PMFlow AI 管家,聚焦项目内问答与需求流转。", "repo_summary": "当前仓库:FastAPI 后端 + React 前端。", "current_capabilities": ["AI 管家", "智能配置"], "constraints": ["只允许基于外部公开证据给出推荐。"], "recommendation_focus": ["判断 Responses API 是否适合当前项目接入"], } async def _execute_external_query_gateway(self, **kwargs): self.fit_brief_seen = kwargs.get("fit_brief") return [ RetrievedItem( source="url", content="Responses API 支持工具调用与结构化输出。", score=0.93, source_label="OpenAI Docs", metadata={ "target_url": "https://platform.openai.com/docs/guides/responses", }, ) ], { "mode": "external_query_gateway", "external_context_packet": { "question": "这个项目适不适合接 OpenAI Responses API?", "plan": { "answer_contract": "summary_with_sources", "topic": "Responses API 适配性", }, "adapter_attempts": [{"adapter": "live_search", "status": "success"}], "verified_evidence": [ { "source_type": "url", "source_label": "OpenAI Docs", "title": "Responses API guide", "target_url": "https://platform.openai.com/docs/guides/responses", "snippet": "Responses API supports tool calls and structured outputs.", } ], "verifier": {"ok": True}, "uncertainties": [], "retrieval_status": "evidence_found", }, "tool_trace": [], "domains_used": ["url"], } async def _generate_answer(self, *args, **kwargs): _ = (args, kwargs) return "外部公开资料显示 Responses API 值得关注,但目前还需要先做项目映射验证。" def _peek_answer_prompt_variant(self): return "external_summary_with_sources" def _peek_last_answer_routing_snapshot(self): return {} def _dedupe_sources(self, retrieved): _ = retrieved return [ { "source": "url", "label": "OpenAI Docs", "score": 0.93, "metadata": { "target_url": "https://platform.openai.com/docs/guides/responses", }, } ] def _build_qa_record_runtime_fields(self, **kwargs): return { "policy_version": "test", "citations": [], "sources_used": list(kwargs.get("sources") or []), "evidence_pack": {}, "tool_trace": list(kwargs.get("tool_trace") or []), } def _build_qa_record_external_fields(self, **kwargs): _ = kwargs return {} async def _update_session_state_after_turn(self, **kwargs): _ = kwargs return None service = _FakeService() result = await service._build_retrieval_response( clean_q="这个项目适不适合接 OpenAI Responses API?", effective_q="这个项目适不适合接 OpenAI Responses API?", asked_by=1, asked_via="feishu", project_id=17, source_chat_id=None, session_id=12, turn_index=1, context_session_id=12, conversation=[], state_row=None, rewrite_meta={"mode": "direct"}, query_type="explain", query_semantics={ "effective_source_policy": "external", "project_operating_context": { "current_project_id": 17, "current_repo_scope": "pmflow-main", "page_anchor": {"project_id": 17}, "thread_anchor": {}, "fit_scope": "current_project_and_repo", "decision_strategy": "answer_then_offer_plan", }, "execution_recipe": { "primary_lane": "external", "fit_brief_required": True, "external_evidence_required": True, "plan_handoff_mode": "offer_after_answer", }, }, retrieval_domains=["url"], scene_semantics={"scene_key": "default", "deep_intent": "none"}, thinking_mode="normal", active_thinking_mode="normal", deep_thinking=False, forced_deep_reason="", deep_retrieval_steps=2, deep_vector_top_k=3, system_snapshot={}, user_snapshot=None, started=time.perf_counter(), message_intent=None, mixed_signal_part=None, answer_mode="retrieval_normal", consultant_mode_preference=None, route_decision={ "route_mode": "external", "effective_source_policy": "external", "decision_origin": "query_semantics_contract", "reply_contract": {"head": "外部实时查询:", "tail_hint": None}, "query_profile": {"needs_freshness": True, "referenced_objects": ["public_api"]}, }, ) assert service.fit_brief_seen is not None assert result["fit_brief"]["project_summary"].startswith("当前项目:PMFlow AI 管家") assert result["fit_brief"]["current_capabilities"] == ["AI 管家", "智能配置"] assert result["external_recommendation"]["retrieval_status"] == "evidence_found" assert result["external_recommendation"]["impact_status"] == "indirect_only" assert result["external_recommendation"]["affected_signal_sources"] == [] assert "映射验证" in str(result["external_recommendation"]["external_action"]) @pytest.mark.asyncio async def test_retrieval_response_can_disable_fit_brief_external_recommendation( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeDB: def add(self, obj) -> None: obj.id = 2 async def flush(self) -> None: return None async def commit(self) -> None: return None async def refresh(self, obj) -> None: _ = obj return None class _FakeContextCache: async def append_session_messages(self, **kwargs) -> None: _ = kwargs return None class _FakeService( AdaptiveRAGAnswerRetrievalMixin, AdaptiveRAGAnswerPayloadMixin, AdaptiveRAGQueryRouteGatewayMixin, AdaptiveRAGDeepGuardRulesMixin, ): def __init__(self) -> None: self.db = _FakeDB() self.context_cache = _FakeContextCache() self.tenant_id = 1 self.fit_brief_seen = "unset" def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "deep" if str(thinking_mode or "").strip().lower() == "deep" else "normal" def _to_float(self, value, default=0.0): try: return float(value) except Exception: return float(default) def _build_policy_version(self, *, prompt_variant_id: str | None = None) -> str: return f"retrieval-v2.1|routing:unknown|prompt:{prompt_variant_id or 'unknown'}" def _normalize_query_route_decision(self, payload): return dict(payload or {}) async def _build_project_fit_brief(self, **kwargs): _ = kwargs raise AssertionError("fit brief builder should be bypassed when rollback flag is off") async def _execute_external_query_gateway(self, **kwargs): self.fit_brief_seen = kwargs.get("fit_brief") return [ RetrievedItem( source="url", content="Responses API 支持工具调用与结构化输出。", score=0.93, source_label="OpenAI Docs", metadata={ "target_url": "https://platform.openai.com/docs/guides/responses", }, ) ], { "mode": "external_query_gateway", "external_context_packet": { "question": "这个项目适不适合接 OpenAI Responses API?", "plan": { "answer_contract": "summary_with_sources", "topic": "Responses API 适配性", }, "adapter_attempts": [{"adapter": "live_search", "status": "success"}], "verified_evidence": [ { "source_type": "url", "source_label": "OpenAI Docs", "title": "Responses API guide", "target_url": "https://platform.openai.com/docs/guides/responses", "snippet": "Responses API supports tool calls and structured outputs.", } ], "verifier": {"ok": True}, "uncertainties": [], "retrieval_status": "evidence_found", }, "tool_trace": [], "domains_used": ["url"], } async def _generate_answer(self, *args, **kwargs): _ = (args, kwargs) return "外部公开资料显示 Responses API 值得关注,但目前还需要先做项目映射验证。" def _peek_answer_prompt_variant(self): return "external_summary_with_sources" def _peek_last_answer_routing_snapshot(self): return {} def _dedupe_sources(self, retrieved): _ = retrieved return [ { "source": "url", "label": "OpenAI Docs", "score": 0.93, "metadata": { "target_url": "https://platform.openai.com/docs/guides/responses", }, } ] def _build_qa_record_runtime_fields(self, **kwargs): return { "policy_version": "test", "citations": [], "sources_used": list(kwargs.get("sources") or []), "evidence_pack": {}, "tool_trace": list(kwargs.get("tool_trace") or []), } def _build_qa_record_external_fields(self, **kwargs): _ = kwargs return {} async def _update_session_state_after_turn(self, **kwargs): _ = kwargs return None monkeypatch.setenv("PMFLOW_ENABLE_FIT_BRIEF_EXTERNAL_RECOMMENDATION", "0") service = _FakeService() result = await service._build_retrieval_response( clean_q="这个项目适不适合接 OpenAI Responses API?", effective_q="这个项目适不适合接 OpenAI Responses API?", asked_by=1, asked_via="feishu", project_id=17, source_chat_id=None, session_id=12, turn_index=1, context_session_id=12, conversation=[], state_row=None, rewrite_meta={"mode": "direct"}, query_type="explain", query_semantics={ "effective_source_policy": "external", "project_operating_context": { "current_project_id": 17, "current_repo_scope": "pmflow-main", "page_anchor": {"project_id": 17}, "thread_anchor": {}, "fit_scope": "current_project_and_repo", "decision_strategy": "answer_then_offer_plan", }, "execution_recipe": { "primary_lane": "external", "fit_brief_required": True, "external_evidence_required": True, "plan_handoff_mode": "offer_after_answer", }, }, retrieval_domains=["url"], scene_semantics={"scene_key": "default", "deep_intent": "none"}, thinking_mode="normal", active_thinking_mode="normal", deep_thinking=False, forced_deep_reason="", deep_retrieval_steps=2, deep_vector_top_k=3, system_snapshot={}, user_snapshot=None, started=time.perf_counter(), message_intent=None, mixed_signal_part=None, answer_mode="retrieval_normal", consultant_mode_preference=None, route_decision={ "route_mode": "external", "effective_source_policy": "external", "decision_origin": "query_semantics_contract", "reply_contract": {"head": "外部实时查询:", "tail_hint": None}, "query_profile": {"needs_freshness": True, "referenced_objects": ["public_api"]}, }, ) assert service.fit_brief_seen == {} assert result["fit_brief"] == {} assert result["external_recommendation"] == {} def test_external_recommendation_does_not_treat_capability_name_as_direct_match_without_formal_anchor() -> None: result = AdaptiveRAGAnswerPayloadMixin._build_external_recommendation( external_context_packet={ "retrieval_status": "evidence_found", "verified_evidence": [ { "source_type": "url", "source_label": "OpenAI Docs", "title": "AI 管家接入 Responses API", "target_url": "https://platform.openai.com/docs/guides/responses", "snippet": "AI 管家可以通过 Responses API 编排工具调用。", } ], "verifier": {"ok": True}, }, fit_brief={ "project_summary": "当前项目:PMFlow AI 管家", "current_capabilities": ["AI 管家"], "signal_source_anchors": [], "constraints": [], "recommendation_focus": [], }, ) assert result["retrieval_status"] == "evidence_found" assert result["impact_status"] == "indirect_only" assert result["affected_signal_sources"] == [] def test_external_recommendation_uses_formal_signal_source_anchor_for_direct_match() -> None: result = AdaptiveRAGAnswerPayloadMixin._build_external_recommendation( external_context_packet={ "retrieval_status": "evidence_found", "verified_evidence": [ { "source_type": "url", "source_label": "OpenAI Docs", "title": "Responses API guide", "target_url": "https://platform.openai.com/docs/guides/responses", "snippet": "OpenAI Docs 说明 Responses API 支持结构化输出与工具调用。", } ], "verifier": {"ok": True}, }, fit_brief={ "project_summary": "当前项目:PMFlow AI 管家", "current_capabilities": ["AI 管家"], "signal_source_anchors": ["OpenAI Docs"], "constraints": [], "recommendation_focus": [], }, ) assert result["retrieval_status"] == "evidence_found" assert result["impact_status"] == "direct_match" assert result["affected_signal_sources"] == ["OpenAI Docs"] @pytest.mark.asyncio async def test_deep_guard_answer_prefers_member_workload_direct_answer() -> None: class _FakeResult: def __init__(self, rows=None, scalar=None): self._rows = rows or [] self._scalar = scalar def all(self): return self._rows def first(self): return self._rows[0] if self._rows else None def scalar_one_or_none(self): return self._scalar class _FakeDB: def __init__(self) -> None: self.calls = 0 async def execute(self, stmt, params=None): # noqa: ANN001 self.calls += 1 if self.calls == 1: return _FakeResult(rows=[SimpleNamespace(id=24, name="杨宇", username="yangyu")]) return _FakeResult(rows=[(265.56, 95.6, 60.0)]) class _FakeLLM: async def generate(self, prompt: str, **kwargs): # pragma: no cover - should not be called raise AssertionError("LLM should not be called for direct member workload answer") class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = _FakeDB() self.tenant_id = 1 self.llm = _FakeLLM() def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "normal" async def get_project_manifest(self, project_id): _ = project_id return {} async def _resolve_answer_scene_semantics(self, **kwargs): _ = kwargs return { "scene_key": "member_workload", "snapshot_focus": "none", "deep_intent": "none", "confidence": 0.96, "reason": "structured-scene", } async def _select_scene_prompt(self, **kwargs): _ = kwargs return "member_workload", "member workload prompt" def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() result = await service._generate_answer( question="杨宇现在负载情况如何?", retrieved=[], conversation=[], project_id=None, asked_via="feishu", ) assert "杨宇当前负载265.56%" in result assert "严重超载" in result @pytest.mark.asyncio async def test_deep_guard_answer_prefers_structured_member_workload_scene_without_keyword_hints() -> None: class _FakeResult: def __init__(self, rows=None): self._rows = rows or [] def all(self): return self._rows def first(self): return self._rows[0] if self._rows else None class _FakeDB: def __init__(self) -> None: self.calls = 0 async def execute(self, stmt, params=None): # noqa: ANN001 _ = (stmt, params) self.calls += 1 if self.calls == 1: return _FakeResult(rows=[SimpleNamespace(id=24, name="杨宇", username="yangyu")]) return _FakeResult(rows=[(82.0, 41.0, 50.0)]) class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = _FakeDB() self.tenant_id = 1 def _normalize_thinking_mode(self, thinking_mode: str) -> str: _ = thinking_mode return "normal" async def get_project_manifest(self, project_id): _ = project_id return {} async def _resolve_answer_scene_semantics(self, **kwargs): _ = kwargs return { "scene_key": "member_workload", "snapshot_focus": "none", "deep_intent": "none", "confidence": 0.95, "reason": "structured-scene", } async def _select_scene_prompt(self, **kwargs): # pragma: no cover - should not be called raise AssertionError("scene prompt should not be rendered for direct workload answer") def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() result = await service._generate_answer( question="杨宇现在忙不忙?", retrieved=[], conversation=[], project_id=None, asked_via="feishu", ) assert "杨宇当前负载82.00%" in result assert "负载偏高" in result @pytest.mark.asyncio async def test_structured_db_retrieval_prefers_structured_people_focus_without_keyword_hints() -> None: class _FakeScalarResult: def __init__(self, rows): self._rows = rows def scalars(self): return self def all(self): return self._rows class _FakeDB: async def execute(self, stmt, params=None): # noqa: ANN001 _ = (stmt, params) return _FakeScalarResult( [ SimpleNamespace(id=7, name="杨宇", role="backend", updated_at=None), SimpleNamespace(id=8, name="林宇航", role="pm", updated_at=None), ] ) class _FakeService(AdaptiveRAGRetrievalRouterDBMixin): def __init__(self) -> None: self.db = _FakeDB() self.tenant_id = 1 def _query_subject_focus(self, query_semantics): # noqa: ANN001 return str((query_semantics or {}).get("subject_focus") or "unknown") def _query_scope_hint(self, query_semantics): # noqa: ANN001 return str((query_semantics or {}).get("scope_hint") or "none") def _normalize_source_registry_metadata(self, *, source_type: str, metadata: dict, explicit_updated_at=None): _ = (source_type, explicit_updated_at) return dict(metadata) service = _FakeService() rows = await service._retrieve_structured_db( "他们都有哪些人?", project_id=None, query_semantics={ "subject_focus": "people", "scope_hint": "global", "feature_focus": "none", }, ) assert len(rows) == 1 assert "成员清单" in rows[0].content assert "杨宇" in rows[0].content def test_feature_evidence_prefers_structured_scene_without_keyword_hints() -> None: class _FakeService(AdaptiveRAGManifestFeatureMixinP2): def _query_subject_focus(self, query_semantics): # noqa: ANN001 return str((query_semantics or {}).get("subject_focus") or "unknown") def _query_feature_focus(self, query_semantics): # noqa: ANN001 return str((query_semantics or {}).get("feature_focus") or "none") def _collect_feature_evidence(self, *, question: str, retrieved, system_snapshot): _ = (question, retrieved, system_snapshot) return [ { "feature_name": "智能配置", "route": "/ai-config", "page_file": "frontend/src/pages/AIConfig.tsx", "apis": ["/api/v1/ai-config"], } ] service = _FakeService() text = service._enforce_feature_evidence_in_answer( question="这个模块怎么进?", answer_text="先看说明。", retrieved=[], system_snapshot={}, scene_semantics={"snapshot_focus": "feature"}, ) assert "证据补充(Feature Catalog)" in text assert "/ai-config" in text @pytest.mark.asyncio async def test_deep_guard_answer_feishu_direct_prefers_manifest_and_compact_focus_context() -> None: class _FakeLLM: def __init__(self) -> None: self.prompt = "" async def generate(self, prompt: str, **kwargs): _ = kwargs self.prompt = prompt return "信息不足,缺少:目标对象" class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 self.llm = _FakeLLM() def _normalize_thinking_mode(self, thinking_mode: str) -> str: return "normal" async def get_project_manifest(self, project_id): _ = project_id return { "manifest_text": "项目#7 Alpha(进行中);功能 18 个;KG 实体 12,关系 19。" } async def _render_scene_prompt(self, *, prompt_key: str, fallback_template: str, **kwargs): _ = prompt_key return str(fallback_template).format(**kwargs).strip() def _enforce_feature_evidence_in_answer(self, **kwargs): return str(kwargs.get("answer_text") or "") service = _FakeService() system_snapshot = { "assistant_profile": {"name": "PMFlow", "identity": "项目管理顾问"}, "platform_profile": { "backend": {"module_counts": {"projects": 28, "tasks": 146, "signals": 41}}, "feature_catalog": [ {"name": "项目管理", "route": "/projects"}, {"name": "信号接入", "route": "/signals"}, {"name": "任务看板", "route": "/board"}, {"name": "知识图谱", "route": "/kg"}, {"name": "训练中心", "route": "/training"}, ], }, } user_snapshot = { "current_user": {"name": "林宇航"}, "projects": [{"id": idx, "title": f"项目{idx}"} for idx in range(1, 15)], "tasks": [{"id": idx, "title": f"任务{idx}"} for idx in range(1, 10)], } result = await service._generate_answer( question="信息不全,继续提供", retrieved=[], conversation=[ {"question": "上一次你缺什么", "answer": "缺目标对象和验收口径"}, {"question": "现在呢", "answer": "还缺时间边界"}, ], project_id=7, system_snapshot=system_snapshot, user_snapshot=user_snapshot, asked_via="feishu", ) assert result == "信息不足,缺少:目标对象" assert "项目画像:" in service.llm.prompt assert "项目#7 Alpha(进行中)" in service.llm.prompt assert "系统摘要:" in service.llm.prompt assert "用户焦点:" in service.llm.prompt assert "林宇航" in service.llm.prompt assert '"projects"' not in service.llm.prompt assert "项目14" not in service.llm.prompt @pytest.mark.asyncio async def test_select_scene_prompt_applies_consultant_mode_preference_wrapper() -> None: class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _render_scene_prompt(self, *, prompt_key: str, fallback_template: str, **kwargs): _ = (fallback_template, kwargs) return f"PROMPT::{prompt_key}" service = _FakeService() scene_key, prompt = await service._select_scene_prompt( question="这个方案怎么做更稳妥?", project_id=7, asked_via="pmflow", message_intent=None, mixed_signal_part=None, conversation_text="无", context_blocks_text="无", system_snapshot={}, user_snapshot={}, system_snapshot_text="{}", user_snapshot_text="{}", relevance_max=0.61, query_type="explain", consultant_mode_preference="advise", ) assert scene_key == "default_consultant_advise" assert "PROMPT::PROMPT_RAG_ANSWER" in prompt assert "稳妥建议" in prompt def test_ai_pmo_qa_prompt_includes_five_lenses_and_runtime_state_guard() -> None: assert "事实源在哪" in PROMPT_AI_PMO_QA assert "输出模板在哪" in PROMPT_AI_PMO_QA assert "检索范围在哪" in PROMPT_AI_PMO_QA assert "执行器在哪" in PROMPT_AI_PMO_QA assert "反馈回流在哪" in PROMPT_AI_PMO_QA assert "静态资料" in PROMPT_AI_PMO_QA assert "运行态配置" in PROMPT_AI_PMO_QA @pytest.mark.asyncio async def test_select_scene_prompt_routes_business_review_to_ai_pmo_prompt() -> None: class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): def __init__(self) -> None: self.db = object() self.tenant_id = 1 async def _render_scene_prompt(self, *, prompt_key: str, fallback_template: str, **kwargs): _ = fallback_template return ( f"PROMPT::{prompt_key}|" f"business_review={kwargs.get('business_review_mode')}|" f"framework={kwargs.get('review_framework')}" ) service = _FakeService() scene_key, prompt = await service._select_scene_prompt( question="我要做一个大模型 API 聚合平台,帮我分析下要怎么做", project_id=None, asked_via="feishu", message_intent="business_review", mixed_signal_part=None, conversation_text="用户上一轮提到要做 API 聚合平台", context_blocks_text="无", system_snapshot={}, user_snapshot={}, system_snapshot_text="{}", user_snapshot_text="{}", relevance_max=0.51, query_type="explain", consultant_mode_preference="clarify", ) assert scene_key == "ai_pmo_business_review_consultant_clarify" assert "PROMPT::PROMPT_AI_PMO_QA" in prompt assert "business_review=true" in prompt assert "framework=five_lenses" in prompt def test_runtime_state_query_guard_blocks_static_catalog_evidence() -> None: class _FakeService(AdaptiveRAGDeepGuardRulesMixin): pass service = _FakeService() decision = service._evaluate_post_retrieval_guard( deep_intent="runtime_state_query", sources=[ {"source": "feature_catalog", "label": "Feature Catalog", "metadata": {}}, {"source": "graph", "label": "Knowledge Graph", "metadata": {"path": "1->uses->2"}}, ], relevance_max=0.96, ) assert decision["should_block_with_guard"] is True assert decision["guard_focus"] == "runtime_state" assert "无法仅凭静态资料确认" in service._build_evidence_guard_answer( "高级模型路由是否启用?", guard_focus=str(decision["guard_focus"]), ) def test_runtime_state_query_guard_allows_routing_config_evidence() -> None: class _FakeService(AdaptiveRAGDeepGuardRulesMixin): pass service = _FakeService() decision = service._evaluate_post_retrieval_guard( deep_intent="runtime_state_query", sources=[ { "source": "doc", "label": "Routing Config", "metadata": {"source_id": "backend/.control_plane/pmflow-routing-rules.json"}, } ], relevance_max=0.74, ) assert decision["should_block_with_guard"] is False def test_engine_process_guard_requests_auto_retry_on_snapshot_only_sources() -> None: class _FakeService(AdaptiveRAGDeepGuardRulesMixin): pass service = _FakeService() decision = service._evaluate_post_retrieval_guard( deep_intent="engine_process_query", sources=[ {"source": "system", "label": "System Snapshot", "metadata": {}}, {"source": "system", "label": "User Snapshot", "metadata": {}}, ], relevance_max=0.41, ) assert decision["auto_retry_reason"] == "snapshot_only" def test_chat_external_fact_scene_policy_preserves_anchored_external_scene_without_question_heuristics() -> None: class _FakeService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakeService() normalized = service._apply_chat_external_fact_scene_policy( question="不对吧,截止当前日期都已经有 codex5.4 了", project_id=None, scene_semantics={ "scene_key": "ai_pmo_correction", "snapshot_focus": "feature", "deep_intent": "correction_turn", "confidence": 0.95, }, anchor_prompt_variant="external_trend", ) assert normalized["scene_key"] == "industry_trend" assert normalized["deep_intent"] == "none" assert normalized["external_fact_chat"] is True def test_chat_external_fact_scene_policy_keeps_existing_external_scene() -> None: class _FakeService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakeService() normalized = service._apply_chat_external_fact_scene_policy( question="当前codex最新的模型用的是什么?", project_id=None, scene_semantics={ "scene_key": "industry_trend", "snapshot_focus": "none", "deep_intent": "none", "confidence": 0.92, }, anchor_prompt_variant=None, ) assert normalized["scene_key"] == "industry_trend" assert normalized["snapshot_focus"] == "none" assert normalized["external_fact_chat"] is True def test_chat_external_fact_scene_policy_keeps_non_external_scene_unchanged() -> None: class _FakeService(AdaptiveRAGAnswerMainlineMixin): pass service = _FakeService() normalized = service._apply_chat_external_fact_scene_policy( question="我们系统当前接的是哪个 codex 模型配置?", project_id=None, scene_semantics={ "scene_key": "ai_pmo", "snapshot_focus": "identity", "deep_intent": "none", "confidence": 0.92, }, anchor_prompt_variant=None, ) assert normalized["scene_key"] == "ai_pmo" assert normalized["snapshot_focus"] == "identity" assert normalized.get("external_fact_chat") is not True def test_external_fact_freshness_guard_rejects_internal_only_sources() -> None: class _FakeService(AdaptiveRAGDeepGuardRulesMixin): pass service = _FakeService() assert service._requires_external_freshness( question="codex 目前最高模型出到什么了", scene_semantics={"scene_key": "industry_trend", "requires_freshness": True}, ) is True assert service._has_external_grounding_sources( [ {"source": "graph", "label": "Knowledge Graph", "metadata": {"path": "1->belongs_to->2"}}, {"source": "feature_catalog", "label": "Feature Catalog", "metadata": {}}, ] ) is False answer = service._build_external_freshness_guard_answer("codex 目前最高模型出到什么了") assert "当前未接入实时外部数据" in answer assert "backend/app/services" not in answer @pytest.mark.asyncio async def test_second_pass_query_skips_internal_hints_for_external_freshness_only() -> None: class _FakeService(AdaptiveRAGMultiStepCoreMixin): async def entity_resolution(self, **kwargs): # pragma: no cover - should not be called raise AssertionError(f"entity_resolution should be skipped: {kwargs}") async def get_project_manifest(self, **kwargs): # pragma: no cover - should not be called raise AssertionError(f"get_project_manifest should be skipped: {kwargs}") service = _FakeService() revised, meta = await service._build_second_pass_query( question="codex 目前最高模型出到什么了", project_id=None, query_semantics={"external_freshness_only": True}, ) assert revised == "codex 目前最高模型出到什么了" assert meta["mode"] == "freshness_guard_skip_internal_hint" @pytest.mark.asyncio async def test_live_external_freshness_context_fetches_url_rows_before_guard() -> None: class _FakeService(AdaptiveRAGAnswerRetrievalMixin, AdaptiveRAGDeepGuardRulesMixin): async def _retrieve_live_external_context(self, *, question: str, max_results: int = 3, max_pages: int = 3): _ = (question, max_results, max_pages) return ( [ RetrievedItem( source="url", content="OpenAI 在官方文章中说明 GPT-5.4 正在 rollout 到 Codex。", score=0.93, source_label="URL[Introducing GPT-5.4]", metadata={ "url": "https://openai.com/index/introducing-gpt-5-4/", "target_url": "https://openai.com/index/introducing-gpt-5-4/", "title": "Introducing GPT-5.4", }, ) ], {"query": question, "result_count": 1, "fetched_pages": 1}, ) service = _FakeService() rows, meta = await service._maybe_fetch_live_external_freshness_context( question="当前codex最新的模型用的是什么?", effective_question="当前codex最新的模型用的是什么?", query_semantics={}, scene_semantics={"scene_key": "industry_trend", "requires_freshness": True}, ) assert len(rows) == 1 assert rows[0].source == "url" assert rows[0].metadata["target_url"] == "https://openai.com/index/introducing-gpt-5-4/" assert meta["mode"] == "live_external_context" @pytest.mark.asyncio async def test_live_external_freshness_context_prefers_external_query_gateway() -> None: class _FakeService(AdaptiveRAGAnswerRetrievalMixin, AdaptiveRAGDeepGuardRulesMixin): async def _execute_external_query_gateway( self, *, question: str, query_semantics: dict | None = None, result_limit: int = 3, max_pages: int = 3, ): _ = (question, query_semantics, result_limit, max_pages) return ( [ RetrievedItem( source="url", content="OpenAI 官方页面显示 Codex 最新模型是 GPT-5.4。", score=0.94, source_label="URL[Models - Codex | OpenAI Developers]", metadata={ "url": "https://developers.openai.com/codex/models", "target_url": "https://developers.openai.com/codex/models", "title": "Models - Codex | OpenAI Developers", }, ) ], { "mode": "external_query_gateway", "adapter": "live_search", "search_result_count": 1, "fetched_pages": 1, "query": "codex latest model", }, ) service = _FakeService() rows, meta = await service._maybe_fetch_live_external_freshness_context( question="当前codex最新的模型用的是什么?", effective_question="当前codex最新的模型用的是什么?", query_semantics={}, scene_semantics={"scene_key": "industry_trend", "requires_freshness": True}, ) assert len(rows) == 1 assert rows[0].metadata["target_url"] == "https://developers.openai.com/codex/models" assert meta["mode"] == "external_query_gateway" assert meta["adapter"] == "live_search" @pytest.mark.asyncio async def test_external_query_gateway_falls_back_to_live_search_when_mcp_has_no_evidence() -> None: from app.services.external_query_service.gateway import ExternalQueryGateway from app.services.external_query_service.registry import ExternalProviderRegistry from app.services.external_query_service.schemas import ( ExternalAdapterResult, ExternalEvidenceItem, ExternalQueryPlan, ) plan = ExternalQueryPlan( plan_version="v1", query_kind="model", answer_contract="summary_with_sources", freshness_required=True, topic="codex 最新模型", subject_entities=["Codex"], location_hint=None, time_range_hint="截至今天", preferred_domains=["openai.com"], search_queries=["codex latest model"], result_limit=3, requires_multi_source=False, clarify_on_ambiguity=False, ) class _Planner: async def build_plan(self, *, question: str, query_semantics: dict | None = None) -> ExternalQueryPlan: _ = (question, query_semantics) return plan class _MCPAdapter: adapter_name = "mcp" async def execute(self, plan: ExternalQueryPlan) -> ExternalAdapterResult: _ = plan return ExternalAdapterResult(adapter_name="mcp", status="unavailable", evidence=[], meta={"reason": "no_client"}) class _LiveSearchAdapter: adapter_name = "live_search" async def execute(self, plan: ExternalQueryPlan) -> ExternalAdapterResult: _ = plan return ExternalAdapterResult( adapter_name="live_search", status="success", evidence=[ ExternalEvidenceItem( source_type="url", source_label="OpenAI Developers", title="Models - Codex | OpenAI Developers", target_url="https://developers.openai.com/codex/models", snippet="Latest: GPT-5.4", retrieved_at="2026-04-15T10:00:00+08:00", freshness_score=0.98, authority_score=0.96, grounded_fields=["latest_model"], raw_metadata={"provider": "live_search"}, ) ], meta={"provider": "live_search"}, ) gateway = ExternalQueryGateway( planner=_Planner(), registry=ExternalProviderRegistry(adapters=[_MCPAdapter(), _LiveSearchAdapter()]), ) result = await gateway.execute( question="当前 codex 最新的模型用的是什么?", query_semantics={"query_profile": {"needs_freshness": True, "referenced_objects": ["public_model"]}}, ) assert result.adapter_name == "live_search" assert result.verifier["ok"] is True assert len(result.evidence) == 1 assert result.meta["fallback_used"] is True def test_external_route_source_filter_is_fail_closed() -> None: class _FakeService(AdaptiveRAGQueryRouteGatewayMixin): pass service = _FakeService() filtered = service._filter_sources_for_route( sources=[ {"source": "feishu", "label": "Feishu history", "score": 0.8}, {"source": "pmflow", "label": "PMFlow records", "score": 0.7}, ], route_decision={"effective_source_policy": "external"}, ) assert filtered == [] @pytest.mark.asyncio async def test_live_external_search_uses_proxy_aware_http_client(monkeypatch) -> None: captured: dict[str, object] = {} class _FakeResponse: def __init__(self, text: str) -> None: self.text = text def raise_for_status(self) -> None: return None class _FakeAsyncClient: def __init__(self, *args, **kwargs) -> None: _ = args captured.update(kwargs) async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb) -> None: _ = (exc_type, exc, tb) return None async def get(self, url: str, headers: dict[str, str]): captured["url"] = url captured["headers"] = headers return _FakeResponse('OpenAI Codex latest model') monkeypatch.setattr( "app.services.adaptive_rag_service.mixin_conversation_utils_a_p1.httpx.AsyncClient", _FakeAsyncClient, ) class _FakeService(AdaptiveRAGConversationUtilsAMixinP1): pass rows = await _FakeService()._search_live_external_results( query="OpenAI Codex latest model", max_results=1, ) assert captured["trust_env"] is True assert rows == [{"url": "https://example.com/post", "title": "OpenAI Codex latest model"}] @pytest.mark.asyncio async def test_live_external_fetch_uses_proxy_aware_http_client(monkeypatch) -> None: captured: dict[str, object] = {} class _FakeResponse: def __init__(self) -> None: self.headers = {"content-type": "text/html"} self.text = "