120 lines
4.5 KiB
Python
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
|