from __future__ import annotations from app.schemas.rag import RAGAskRequest from app.schemas.pmflow_ai_turn_runtime import ( PMFlowAIAnswerState, PMFlowAIGoalFrame, PMFlowAIRuntimeAction, PMFlowAITargetScope, PMFlowAITurnEvidenceState, PMFlowAITurnObjectRef, PMFlowAITurnObjectState, PMFlowAITurnState, ) from app.services.pmflow_ai_turn_object_ledger import ( build_object_ledger_from_payload, merge_object_ledger_into_context_packet, object_ledger_from_context_capsules, upsert_object_ledger_capsule, ) from app.services.pmflow_ai_turn_state_reviser import PMFlowAITurnStateReviser from app.services.pmflow_ai_turn_runtime_service import PMFlowAITurnRuntimeService def _service() -> PMFlowAITurnRuntimeService: return PMFlowAITurnRuntimeService(object(), tenant_id=1) def test_packet_i_builds_customer_safe_object_ledger_from_turn_payload() -> None: payload = { "answer_mode": "turn_runtime_state_answer", "pmflow_ai_turn_state": { "customer_packet_draft": { "items": [ { "object_type": "project", "object_ref": "project:141", "title": "童析", "status": "进行中", } ] } }, "customer_interaction_context_packet": {"visible_objects": []}, } ledger = build_object_ledger_from_payload(payload=payload) assert ledger["current_focus_object"]["object_ref"] == "project:141" assert ledger["current_focus_object"]["title"] == "童析" assert ledger["last_shown_objects"] == [ {"object_type": "project", "object_ref": "project:141", "title": "童析", "object_id": 141, "status": "进行中"} ] merged = merge_object_ledger_into_context_packet(context_packet={}, object_ledger=ledger) assert merged["current_focus_object"]["object_ref"] == "project:141" assert merged["last_shown_objects"][0]["title"] == "童析" def test_packet_i_upserts_ledger_into_session_context_capsules() -> None: ledger = build_object_ledger_from_payload( payload={ "pmflow_ai_turn_state": { "customer_packet_draft": { "items": [{"object_type": "project", "object_ref": "project:141", "title": "童析"}] } } } ) capsules = upsert_object_ledger_capsule(capsules=[{"capsule_type": "other", "value": 1}], object_ledger=ledger) loaded = object_ledger_from_context_capsules(capsules) assert capsules[0]["capsule_type"] == "pmflow_ai_object_ledger" assert loaded["current_focus_object"]["object_ref"] == "project:141" assert capsules[1]["capsule_type"] == "other" def test_packet_i_session_object_ledger_binds_weak_followup_primary_object() -> None: ledger = { "packet_version": "pmflow_ai_object_ledger.v1", "current_focus_object": { "object_type": "project", "object_ref": "project:141", "title": "童析", }, "last_shown_objects": [], "recent_objects": [], } state = _service().build_initial_turn_state( request=RAGAskRequest(question="这个项目你觉得发展怎么样"), route_plan={}, conversation_context={"object_ledger": ledger}, turn_hypothesis={ "normalized_goal": "判断当前项目发展状态", "answer_shape": "strategic_judgement", "primary_object": {"binding_strength": "none"}, "goal_frames": [ { "frame_id": "followup-project-judgement", "operation_kind": "inspect_object", "target_scope": {"scope_type": "current_project", "binding_strength": "workspace"}, "target_object_requirement": "single", "answer_shape": "strategic_judgement", } ], "operation_gaps": [ { "key": "project_status_snapshot", "gap_type": "system_evidence_gap", "blocking": False, "user_actionable": False, "system_action_hint": "需要读取项目状态快照", "reason": "需要先读取绑定项目证据。", } ], "can_answer_with_assumptions": False, }, ) assert state.object_state.primary_object is not None assert state.object_state.primary_object.object_ref == "project:141" assert state.object_state.primary_object.binding_strength == "session_object_ledger" def test_packet_i_system_probe_wins_before_user_clarification_when_object_is_bound() -> None: ledger = { "packet_version": "pmflow_ai_object_ledger.v1", "current_focus_object": { "object_type": "project", "object_ref": "project:141", "title": "童析", }, } state = _service().build_initial_turn_state( request=RAGAskRequest(question="这个项目你觉得发展怎么样"), route_plan={}, conversation_context={"object_ledger": ledger}, turn_hypothesis={ "normalized_goal": "判断当前项目发展状态", "answer_shape": "strategic_judgement", "primary_object": {"binding_strength": "none"}, "goal_frames": [ { "frame_id": "project-development-judgement", "operation_kind": "inspect_object", "target_scope": {"scope_type": "current_project", "binding_strength": "workspace"}, "target_object_requirement": "single", "answer_shape": "strategic_judgement", } ], "operation_gaps": [ { "key": "project_status_snapshot", "gap_type": "system_evidence_gap", "blocking": False, "user_actionable": False, "system_action_hint": "需要读取项目状态快照", "reason": "需要读取绑定项目状态证据。", }, { "key": "user_metric_preference", "gap_type": "user_clarification_gap", "blocking": True, "user_actionable": True, "customer_question": "你更关心增长、交付还是风险?", "reason": "补充维度可提升判断质量。", }, ], "needs_user_input": True, "can_answer_with_assumptions": False, }, ) decision = _service().assess_sufficiency(state) assert decision.must_ask_user is False assert decision.next_action_type == "inspect_project_status" def test_packet_x_bound_project_blocks_external_search_until_project_operating_probe() -> None: ledger = { "packet_version": "pmflow_ai_object_ledger.v1", "current_focus_object": { "object_type": "project", "object_ref": "project:141", "title": "童析", }, } state = _service().build_initial_turn_state( request=RAGAskRequest(question="现在这个项目怎么样了,详细跟我说下"), route_plan={}, conversation_context={"object_ledger": ledger}, turn_hypothesis={ "normalized_goal": "获取当前项目整体状态与关键进展细节", "answer_shape": "composite_answer", "primary_object": {"binding_strength": "none"}, "object_binding_decision": {"decision": "accept_previous_focus"}, "goal_frames": [ { "frame_id": "wrong-external-composite", "operation_kind": "judge_direction", "target_scope": {"scope_type": "external_public", "binding_strength": "surface"}, "target_object_requirement": "none", "source_policy": "external", "answer_shape": "composite_answer", } ], "operation_gaps": [ { "key": "public_project_evidence", "gap_type": "freshness_gap", "blocking": False, "user_actionable": False, "system_action_hint": "external_search", "reason": "错误地要求外部公开证据。", } ], "can_answer_with_assumptions": True, }, ) decision = _service().assess_sufficiency(state) action = _service().select_next_minimal_action(state) assert state.object_state.primary_object is not None assert state.object_state.primary_object.object_ref == "project:141" assert decision.next_action_type == "inspect_project_status" assert action is not None assert action.action_type == "inspect_project_status" assert all(item.action_type != "external_search" for item in state.action_state.candidate_actions[:1]) def test_packet_z_final_answer_wrong_source_verifier_forces_project_probe() -> None: ledger = { "packet_version": "pmflow_ai_object_ledger.v1", "current_focus_object": { "object_type": "project", "object_ref": "project:141", "title": "童析", }, } state = _service().build_initial_turn_state( request=RAGAskRequest(question="现在这个项目怎么样了,详细跟我说下"), route_plan={}, conversation_context={"object_ledger": ledger}, turn_hypothesis={ "normalized_goal": "获取当前项目整体状态与关键进展细节", "answer_shape": "composite_answer", "primary_object": {"binding_strength": "none"}, "object_binding_decision": {"decision": "accept_previous_focus"}, "goal_frames": [ { "frame_id": "wrong-final-answer-frame", "operation_kind": "judge_direction", "target_scope": {"scope_type": "external_public", "binding_strength": "surface"}, "source_policy": "external", "answer_shape": "composite_answer", } ], "operation_gaps": [], "can_answer_with_assumptions": True, }, ) failure = _service()._verify_runtime_payload( turn_state=state, payload={ "_runtime_payload_kind": "final_answer", "answer": "当前无法提供该项目具体状态。", "source_policy": "external", "sources": [{"source_type": "external_public", "label": "外部公开来源"}], }, ) assert failure is not None assert failure.failure_type == "wrong_source_or_missing_project_operating_state" assert failure.next_action_hint == "inspect_project_status" assert failure.state_patch["needs_internal_evidence"] is True def test_packet_aa_verifier_recovery_keeps_required_project_probe_executable() -> None: frame = PMFlowAIGoalFrame( frame_id="wrong-source-frame", operation_kind="judge_direction", target_scope=PMFlowAITargetScope(scope_type="external_public", binding_strength="surface"), answer_shape="composite_answer", ) state = PMFlowAITurnState( selected_frame=frame, candidate_frames=[frame], answer_state=PMFlowAIAnswerState(answer_shape="composite_answer"), object_state=PMFlowAITurnObjectState( primary_object=PMFlowAITurnObjectRef( object_type="project", object_ref="project:141", title="童析", binding_strength="session_object_ledger", ) ), evidence_state=PMFlowAITurnEvidenceState(), ) failure = _service()._verify_runtime_payload( turn_state=state, payload={"_runtime_payload_kind": "final_answer", "answer": "当前无法提供项目具体状态。"}, ) PMFlowAITurnStateReviser().revise_from_verification(turn_state=state, failure=failure) decision = _service().assess_sufficiency(state) action = _service().select_next_minimal_action(state) assert failure is not None assert decision.next_action_type == "inspect_project_status" assert action is not None assert action.action_type == "inspect_project_status" assert state.completed is False def test_packet_j_new_project_topic_does_not_inherit_previous_project_focus() -> None: ledger = { "packet_version": "pmflow_ai_object_ledger.v1", "current_focus_object": { "object_type": "project", "object_ref": "project:141", "title": "童析", }, } state = _service().build_initial_turn_state( request=RAGAskRequest(question="解决信息投流公司大量员工混剪视频的问题,通过AI自动化混剪降低人工成本"), route_plan={}, conversation_context={"object_ledger": ledger}, turn_hypothesis={ "normalized_goal": "评估一个新的AI视频混剪自动化项目方向", "answer_shape": "strategic_judgement", "primary_object": {"binding_strength": "none"}, "object_binding_decision": { "decision": "create_new_focus", "rejected_object_ref": "project:141", "new_focus_title": "信息投流公司AI自动化混剪", }, "goal_frames": [ { "frame_id": "new-video-automation-direction", "operation_kind": "guide_execution", "target_scope": {"scope_type": "unspecified", "binding_strength": "none"}, "target_object_requirement": "none", "answer_shape": "strategic_judgement", } ], "operation_gaps": [ { "key": "company_context", "gap_type": "user_clarification_gap", "blocking": True, "user_actionable": True, "customer_question": "请补充典型混剪工作流。", "reason": "缺少业务上下文。", }, { "key": "old_project_scope_rejected", "gap_type": "scope_gap", "blocking": False, "user_actionable": False, "system_action_hint": "previous focus is unrelated to this new idea", "reason": "上一轮项目与本轮新想法无关。", }, ], "needs_user_input": True, "can_answer_with_assumptions": False, }, ) decision = _service().assess_sufficiency(state) action_types = [item.action_type for item in state.action_state.candidate_actions] assert state.object_state.primary_object is not None assert state.object_state.primary_object.object_ref == "" assert state.object_state.primary_object.object_type == "new_idea" assert state.object_state.primary_object.binding_strength == "active_workspace_strong" assert "inspect_project_status" not in action_types assert decision.next_action_type == "ask_clarification" def test_packet_j_off_topic_project_evidence_is_not_absorbed_into_new_idea_goal() -> None: state = _service().build_initial_turn_state( request=RAGAskRequest(question="解决信息投流公司大量员工混剪视频的问题,通过AI自动化混剪降低人工成本"), route_plan={}, turn_hypothesis={ "normalized_goal": "评估一个新的AI视频混剪自动化项目方向", "answer_shape": "strategic_judgement", "primary_object": {"binding_strength": "none"}, "object_binding_decision": {"decision": "create_new_focus", "rejected_object_ref": "project:141"}, "goal_frames": [ { "frame_id": "new-video-automation-direction", "operation_kind": "guide_execution", "target_scope": {"scope_type": "unspecified", "binding_strength": "none"}, "target_object_requirement": "none", "answer_shape": "strategic_judgement", } ], "can_answer_with_assumptions": True, }, ) updated = _service().absorb_evidence( turn_state=state, action=PMFlowAIRuntimeAction( action_id="inspect_project_status", action_type="inspect_project_status", reason="读取项目状态", tool_name="project_status_evidence_tool", ), payload={ "_runtime_payload_kind": "evidence", "runtime_evidence_tool_result": { "tool_name": "project_status_evidence_tool", "action_type": "inspect_project_status", "findings": [ { "fact": "童析 当前状态证据:项目状态 active", "source": "PMFlow 项目状态证据", "confidence": "high", } ], "sources": [{"source": "project_status", "display_label": "PMFlow 项目状态证据"}], "remaining_gaps": [], "confidence": "high", "summary": "读取项目状态证据,得到 1 条事实。", }, }, started_at=0, ) assert updated.evidence_state.known_facts == [] assert "off_topic_evidence_excluded" in updated.evidence_state.stale_or_missing