PMFlow/backend/tests/test_pmflow_ai_goal_frame_p...

171 lines
6.9 KiB
Python

from __future__ import annotations
from app.schemas.pmflow_ai_turn_runtime import PMFlowAITurnState
from app.schemas.rag import RAGAskRequest
from app.services.pmflow_ai_turn_runtime_service import PMFlowAITurnRuntimeService
from app.services.pmflow_ai_turn_state_reviser import PMFlowAITurnStateReviser
def test_packet_e_goal_frames_drive_working_state_without_legacy_decision_fields() -> None:
state = PMFlowAITurnRuntimeService(object(), tenant_id=1).build_initial_turn_state(
request=RAGAskRequest(question="现在最新的 Agent 应用机会是什么"),
route_plan={"legacy_policy": "internal_only"},
turn_hypothesis={
"normalized_goal": "判断当前最新 Agent 应用机会",
"desired_outcome": "给出机会、风险和下一步",
"goal_frames": [
{
"frame_id": "agent-opportunity",
"operation_kind": "judge_direction",
"target_scope": {
"scope_type": "external_public",
"binding_strength": "surface",
"source": "turn_goal_assessment",
},
"target_object_requirement": "none",
"answer_shape": "strategic_judgement",
"confidence": 0.82,
}
],
"operation_gaps": [
{
"key": "fresh_public_market_evidence",
"gap_type": "freshness_gap",
"blocking": False,
"user_actionable": False,
"system_action_hint": "external_search",
"reason": "需要当前公开市场证据。",
}
],
"can_answer_with_assumptions": True,
"primary_object": {"binding_strength": "none"},
},
)
decision = PMFlowAITurnRuntimeService(object(), tenant_id=1).assess_sufficiency(state)
assert state.selected_frame is not None
assert state.selected_frame.operation_kind == "judge_direction"
assert state.answer_state.answer_shape == "strategic_judgement"
assert [gap.key for gap in state.operation_gaps] == ["fresh_public_market_evidence"]
assert decision.next_action_type == "external_search"
def test_packet_e_goal_frames_beat_conflicting_legacy_fields() -> None:
state = PMFlowAITurnRuntimeService(object(), tenant_id=1).build_initial_turn_state(
request=RAGAskRequest(question="暂存想法里有没有值得推进的"),
route_plan={},
turn_hypothesis={
"normalized_goal": "列出暂存想法",
"answer_shape": "status_answer",
"decision_type": "project_status",
"evidence_needs": ["project_status"],
"goal_frames": [
{
"frame_id": "saved-ideas",
"operation_kind": "list_collection",
"target_scope": {
"scope_type": "current_space",
"binding_strength": "surface",
"source": "turn_goal_assessment",
},
"target_collection": {
"object_type": "saved_idea",
"required": True,
},
"target_object_requirement": "none",
"answer_shape": "fact_list",
"confidence": 0.86,
}
],
"operation_gaps": [
{
"key": "saved_ideas",
"gap_type": "system_evidence_gap",
"system_action_hint": "inspect_saved_ideas",
"reason": "需要读取暂存想法集合。",
}
],
"can_answer_with_assumptions": True,
},
)
action = PMFlowAITurnRuntimeService(object(), tenant_id=1).select_next_minimal_action(state)
assert state.selected_frame is not None
assert state.selected_frame.operation_kind == "list_collection"
assert state.answer_state.answer_shape == "fact_list"
assert action is not None
assert action.action_type == "inspect_saved_ideas"
def test_packet_e_user_actionable_operation_gap_controls_clarification_gate() -> None:
state = PMFlowAITurnRuntimeService(object(), tenant_id=1).build_initial_turn_state(
request=RAGAskRequest(question="帮我处理一下"),
route_plan={},
turn_hypothesis={
"normalized_goal": "确认执行动作边界",
"goal_frames": [
{
"frame_id": "execution-boundary",
"operation_kind": "guide_execution",
"target_scope": {
"scope_type": "current_space",
"binding_strength": "none",
"source": "turn_goal_assessment",
},
"target_object_requirement": "authorization",
"answer_shape": "execution_guidance",
"confidence": 0.7,
}
],
"operation_gaps": [
{
"key": "execution_boundary_required",
"gap_type": "user_clarification_gap",
"blocking": True,
"user_actionable": True,
"customer_question": "请补充要处理的对象和动作边界。",
"reason": "执行前缺少对象和动作边界。",
}
],
"can_answer_with_assumptions": False,
},
)
decision = PMFlowAITurnRuntimeService(object(), tenant_id=1).assess_sufficiency(state)
assert decision.must_ask_user is True
assert decision.next_action_type == "ask_clarification"
assert state.operation_gaps[0].user_actionable is True
def test_packet_e_reviser_accepts_direct_goal_frame_payload() -> None:
state = PMFlowAITurnState()
PMFlowAITurnStateReviser().initialize_working_state(
turn_state=state,
hypothesis={
"goal_frames": [
{
"frame_id": "assistant-profile",
"operation_kind": "explain_assistant",
"target_scope": {
"scope_type": "current_space",
"binding_strength": "surface",
"source": "turn_goal_assessment",
},
"target_object_requirement": "none",
"answer_shape": "general_answer",
"confidence": 0.9,
}
],
"operation_gaps": [],
"decision_type": "project_status",
"evidence_needs": ["project_status"],
},
)
assert state.selected_frame is not None
assert state.selected_frame.operation_kind == "explain_assistant"
assert state.operation_gaps == []