import pytest from app.services.ai_pmo.ai_pmo_intent import ( INTENT_FOLLOWUP_CONTINUE, INTENT_QUERY, INTENT_TASK_CREATE, ROUTE_ACTION_ANSWER, ROUTE_ACTION_CANCEL, ROUTE_ACTION_CONTINUE, ROUTE_ACTION_CREATE, ROUTE_FAMILY_QUERY, ROUTE_FAMILY_TASK, ROUTE_QUERY_ANSWER, ROUTE_TASK_CREATE, CanonicalIntent, IntentResolver, RouteResolver, ) from app.services.ai_pmo.ai_pmo_kernel_common_core.imports import WORKFLOW_ROUTE_KEYS from app.services.ai_pmo.ai_pmo_typed_executor_execute.service import TypedExecutorExecuteMixin from app.services.ai_pmo.semantic_context.decision_schema import SemanticDecision from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner def _planner_decision(*, workflows: list[dict]) -> SemanticDecision: return SemanticDecision( topic_relation="new_topic", mode_decision="answer_only", workflow_plans=workflows, scene_metadata={}, requested_actions=["workflow.bundle.compose"], confidence=0.92, ) def test_intent_resolver_recognizes_task_create(monkeypatch: pytest.MonkeyPatch) -> None: async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): _ = (message_text, source_system, metadata) return _planner_decision( workflows=[ { "workflow_type": "task_flow", "workflow_action": "create", "target_scope": "project", "operation_codes": ["task.create"], "confidence": 0.92, } ] ) monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) intent = IntentResolver.resolve( text="请在项目#12 新建任务:修复登录超时", project_id=12, ) assert intent.name == INTENT_TASK_CREATE assert intent.project_id == 12 assert str((intent.metadata or {}).get("task_title") or "").startswith("修复登录超时") def test_route_resolver_builds_task_create_route() -> None: route = RouteResolver.from_canonical( canonical_intent=CanonicalIntent( name=INTENT_TASK_CREATE, confidence=0.9, project_id=3, signal_title="修复登录超时", signal_summary="修复线上登录超时问题", metadata={}, ), message_text="新增任务:修复登录超时", ) assert route.family == ROUTE_FAMILY_TASK assert route.action == ROUTE_ACTION_CREATE assert route.key == ROUTE_TASK_CREATE def test_intent_resolver_negated_write_statement_follows_semantic_query_bundle(monkeypatch: pytest.MonkeyPatch) -> None: async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): _ = (message_text, source_system, metadata) return _planner_decision( workflows=[ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "confidence": 0.86, } ] ) monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) intent = IntentResolver.resolve( text="先不要创建任务,告诉我当前进展和风险", project_id=7, ) assert intent.name == INTENT_QUERY assert str((intent.metadata or {}).get("reason") or "") != "negated_write_statement" def test_intent_resolver_followup_tokens_no_longer_override_semantic_query(monkeypatch: pytest.MonkeyPatch) -> None: async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return _planner_decision( workflows=[ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "confidence": 0.86, } ] ) monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) intent = IntentResolver.resolve( text="确认这条", project_id=9, ) assert intent.name == INTENT_QUERY def test_intent_resolver_requirement_initiation_query_bundle_does_not_upgrade_to_project_create( monkeypatch: pytest.MonkeyPatch, ) -> None: async def _fake_decide(cls, *, message_text: str, source_system: str, metadata=None): _ = (cls, message_text, source_system, metadata) return _planner_decision( workflows=[ { "workflow_type": "query_flow", "workflow_action": "answer", "target_scope": "global", "operation_codes": ["query.answer"], "confidence": 0.91, } ] ) monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide)) intent = IntentResolver.resolve( text="想做个新需求,关于给运营公司打造主播推流的需求。", project_id=None, ) assert intent.name == INTENT_QUERY assert intent.signal_title == "" def test_route_resolver_builds_query_answer_route() -> None: route = RouteResolver.from_canonical( canonical_intent=CanonicalIntent( name=INTENT_QUERY, confidence=0.92, project_id=7, signal_title="", signal_summary="当前进展和风险", metadata={}, ), message_text="当前进展和风险是什么?", ) assert route.family == ROUTE_FAMILY_QUERY assert route.action == ROUTE_ACTION_ANSWER assert route.key == ROUTE_QUERY_ANSWER def test_route_resolver_followup_action_requires_structured_metadata() -> None: action = RouteResolver.resolve_followup_route_action( canonical_intent=INTENT_FOLLOWUP_CONTINUE, message_text="先取消这个工作项", ) assert action == ROUTE_ACTION_CONTINUE def test_route_resolver_followup_action_no_longer_parses_message_text_with_context() -> None: action = RouteResolver.resolve_followup_route_action( canonical_intent=INTENT_FOLLOWUP_CONTINUE, message_text="先取消这个工作项", metadata={"followup_context_present": True, "target_work_item_id": 8}, ) assert action == ROUTE_ACTION_CONTINUE def test_kernel_common_imports_exposes_workflow_route_keys() -> None: assert "query.answer" in WORKFLOW_ROUTE_KEYS assert "task.create" in WORKFLOW_ROUTE_KEYS class _DummyTypedExecutor(TypedExecutorExecuteMixin): def __init__(self) -> None: self.called: dict[str, object] = {} async def _execute_task_create_intent(self, *, intent, message_text: str, operator_user_id): self.called = { "intent": intent.name, "message_text": message_text, "operator_user_id": operator_user_id, } return {"status": "succeeded", "task_id": 77} @pytest.mark.asyncio async def test_typed_executor_dispatches_task_create() -> None: executor = _DummyTypedExecutor() intent = CanonicalIntent( name=INTENT_TASK_CREATE, confidence=0.88, project_id=6, signal_title="补充埋点", signal_summary="补充行为埋点以支持漏斗分析", metadata={"task_title": "补充埋点"}, ) result = await executor.execute( intent=intent, message_text="请创建任务:补充埋点", sender="alice", operator_user_id=101, source_system="feishu", source_message_id="msg-1", ) assert result["status"] == "succeeded" assert result["task_id"] == 77 assert executor.called["intent"] == INTENT_TASK_CREATE assert executor.called["operator_user_id"] == 101