PMFlow/backend/tests/test_workflow_semantic_plan...

120 lines
4.5 KiB
Python

from __future__ import annotations
import asyncio
from typing import Any
import pytest
from app.services.ai_pmo.semantic_context.decision_schema import SemanticDecision
from app.services.ai_pmo.workflow_semantic_planner import WorkflowSemanticPlanner
def _decision(*, workflows: list[dict[str, Any]], scene_metadata: dict[str, Any] | None = None) -> SemanticDecision:
return SemanticDecision(
topic_relation="new_topic",
mode_decision="answer_only",
workflow_plans=workflows,
scene_metadata=scene_metadata or {},
requested_actions=["workflow.bundle.compose"],
confidence=0.91,
)
def test_workflow_semantic_planner_plan_message_uses_model_decision(monkeypatch: pytest.MonkeyPatch) -> None:
async def _fake_decide(cls, *, message_text: str, source_system: str, metadata: dict[str, Any] | None = None):
assert message_text == "帮我创建一个项目,并把这个需求接入信号源"
assert source_system == "feishu"
return _decision(
workflows=[
{
"workflow_type": "new_project_flow",
"workflow_action": "create",
"target_scope": "global",
"operation_codes": ["project.create"],
"confidence": 0.93,
},
{
"workflow_type": "signal_source_flow",
"workflow_action": "onboard",
"target_scope": "global",
"operation_codes": ["signal_source.onboard"],
"confidence": 0.92,
},
]
)
monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide))
bundle = WorkflowSemanticPlanner.plan_message(
message_text="帮我创建一个项目,并把这个需求接入信号源",
source_system="feishu",
)
assert bundle.primary_workflow == "new_project_flow"
assert [item.workflow_type for item in bundle.workflows] == ["new_project_flow", "signal_source_flow"]
assert bundle.reply_contract.mode == "aggregate_card"
@pytest.mark.asyncio
async def test_workflow_semantic_planner_aplan_message_preserves_scene_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
async def _fake_decide(cls, *, message_text: str, source_system: str, metadata: dict[str, Any] | None = None):
assert message_text == "想跟你聊个新需求"
return _decision(
workflows=[
{
"workflow_type": "query_flow",
"workflow_action": "answer",
"target_scope": "global",
"operation_codes": ["query.answer"],
"confidence": 0.88,
"metadata": {"message_intent": "requirement_plan"},
}
],
scene_metadata={
"message_intent": "requirement_plan",
"consultant_mode_preference": "clarify",
"topic_switch": "new",
},
)
monkeypatch.setattr(WorkflowSemanticPlanner, "_decide_cold_start_semantics", classmethod(_fake_decide))
bundle = await WorkflowSemanticPlanner.aplan_message(
message_text="想跟你聊个新需求",
source_system="feishu",
)
assert bundle.primary_workflow == "query_flow"
assert bundle.workflows[0].metadata["message_intent"] == "requirement_plan"
assert bundle.diagnostics["topic_switch"] == "new"
def test_workflow_semantic_planner_sync_wrapper_fails_inside_running_loop() -> None:
async def _runner() -> None:
with pytest.raises(RuntimeError):
WorkflowSemanticPlanner.plan_message(message_text="test", source_system="feishu")
asyncio.run(_runner())
def test_workflow_semantic_planner_no_longer_exposes_legacy_semantic_helpers() -> None:
legacy_names = (
"plan_from_intent",
"classify_scene",
"is_action_request_capture",
"parse_followup_action",
"extract_project_lookup_keyword",
"wants_followup_continue",
"wants_followup_status",
"looks_like_project_create_request",
"looks_like_task_create_request",
"looks_like_signal_source_request",
"looks_like_external_connect_request",
"looks_like_contextual_write_confirmation",
"is_query_like_question",
"is_obvious_question",
)
for name in legacy_names:
assert not hasattr(WorkflowSemanticPlanner, name), name