| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- from __future__ import annotations
- from app.decision_projection import AgentDecisionProjector
- def test_direction_keeps_observed_reads_separate_from_explicit_basis():
- decision = AgentDecisionProjector().direction(
- {
- "id": "run:objective",
- "subtype": "objective",
- "decisionItems": ["讲清柔性排产为什么能减少浪费"],
- "constraints": ["数据可信", "结构清楚"],
- "inputs": [
- {"label": "选题", "relation": "direct-read"},
- {
- "label": "账号段落模式",
- "relation": "direct-read",
- "decisionUse": "explicit-basis",
- },
- ],
- "promptRef": "main:direction",
- }
- )
- assert decision["semanticKind"] == "decision"
- assert decision["decisionType"] == "direction"
- assert decision["authority"] == "final"
- assert decision["inputs"][0] == {
- "label": "选题",
- "summary": None,
- "observedRelation": "read-by-actor",
- "decisionUse": "not-recorded",
- "detailRef": None,
- }
- assert decision["inputs"][1]["decisionUse"] == "explicit-basis"
- input_block = next(
- block for block in decision["detail"]["blocks"] if block["title"] == "根据以下信息做出的决策"
- )
- assert input_block["items"][0]["note"] == "决策前直接读取;是否采用未记录"
- assert input_block["items"][1]["note"] == "决策前直接读取;明确作为决策依据"
- assert input_block["visualRole"] == "evidence"
- assert input_block["presentation"] == "list"
- assert input_block["collapsible"] is False
- final_block = next(
- block for block in decision["detail"]["blocks"] if block["title"] == "最终方向"
- )
- assert final_block["visualRole"] == "key-result"
- assert len(decision["card"]["secondary"]) <= 3
- assert decision["detail"]["detailKind"] == "agent-decision"
- def test_implementation_plan_uses_dedicated_routes_and_full_evidence():
- full_goal = "修复结构完整性。" * 40
- decision = AgentDecisionProjector().direction(
- {
- "id": "round:4:plan",
- "subtype": "implementation-plan",
- "decisionItems": ["方案 1 · 改:段落 2207、2208"],
- "explicitReasoning": "这是收敛阶段的定点修复,不需要竞争。",
- "inputs": [
- {"label": "本轮目标", "summary": full_goal, "relation": "standing-constraint"},
- ],
- "implementationPlan": {
- "mode": "单路",
- "routes": [{
- "pathIndex": 1,
- "pathType": "内容",
- "action": "改",
- "target": "段落 2207、2208 的 form_elements",
- "method": "产生元素",
- "emphasis": "结构完整性",
- }],
- },
- }
- )
- route_block = decision["detail"]["blocks"][0]
- evidence_block = decision["detail"]["blocks"][1]
- assert route_block["type"] == "implementation-plan"
- assert route_block["routes"][0]["target"] == "段落 2207、2208 的 形式信息"
- assert route_block["routes"][0]["emphasis"] == "结构完整性"
- assert evidence_block["title"] == "根据以下信息做出的决策"
- assert evidence_block["visualRole"] == "evidence"
- assert evidence_block["collapsible"] is False
- assert evidence_block["items"][0]["value"] == full_goal
- assert evidence_block["items"][0]["note"] == "持续约束;本轮必须遵守"
- def test_data_tradeoff_is_implementer_scope_and_sources_are_not_auto_adopted():
- decision = AgentDecisionProjector().data_tradeoff(
- {
- "id": "data-decision:12",
- "decision": "采用两条交叉印证的数据,暂不采用单一来源估算。",
- "reasoning": "两类来源结论一致。",
- "sources": [
- {"type": "行业报告", "title": "报告 A"},
- {"type": "搜索结果", "title": "结果 B"},
- ],
- }
- )
- assert decision["decisionType"] == "tradeoff"
- assert decision["subtype"] == "data-tradeoff"
- assert decision["actor"]["role"] == "implementer"
- assert decision["authority"] == "implementation-scope"
- assert {item["decisionUse"] for item in decision["inputs"]} == {"not-recorded"}
- assert decision["body"]["selected"] == []
- assert len(decision["card"]["secondary"]) == 2
- blocks = {block["title"]: block for block in decision["detail"]["blocks"]}
- assert blocks["参与判断"]["visualRole"] == "evidence"
- assert blocks["明确取舍"]["visualRole"] == "key-result"
- assert blocks["理由"]["visualRole"] == "reason"
- def test_data_tradeoff_uses_real_source_type_and_content_fields():
- decision = AgentDecisionProjector().data_tradeoff(
- {
- "id": "data-decision:13",
- "decision": "组合领域信息与账号模式。",
- "sources": [
- {
- "data_type": "领域信息",
- "data_content": "米豆腐热量约 50-60kcal/100g",
- }
- ],
- }
- )
- source = decision["detail"]["blocks"][0]["items"][0]
- assert source["label"] == "领域信息"
- assert source["value"] == "米豆腐热量约 50-60kcal/100g"
- def test_multipath_tradeoff_is_main_final_and_does_not_render_internal_ids():
- decision = AgentDecisionProjector().multipath_tradeoff(
- {
- "id": "multipath-decision:21",
- "branch_ids": [3, 4],
- "decision": "组合两个候选方案后合入主脚本。",
- "reasoning": "两路内容互补。",
- "reviewComparison": [
- {
- "recommendation": "建议组合",
- "finalDecision": "组合采用",
- "handling": None,
- }
- ],
- }
- )
- assert decision["authority"] == "final"
- assert decision["actor"] == {"role": "main", "label": "主 Agent"}
- assert decision["body"]["branchIds"] == [3, 4]
- business_text = str(decision["card"]) + str(decision["detail"]["blocks"])
- assert "branch_id" not in business_text
- assert "比较 2 个候选方案" in business_text
- def test_evaluator_is_recommendation_with_structured_business_blocks():
- decision = AgentDecisionProjector().evaluation(
- {
- "id": "multipath-review:40",
- "subtype": "multipath-evaluation",
- "subjects": ["候选方案 A", "候选方案 B"],
- "criteria": ["结构完整", "证据可信"],
- "itemConclusions": [
- {"subject": "候选方案 A", "conclusion": "通过"},
- {"subject": "候选方案 B", "conclusion": "部分通过"},
- ],
- "achievements": ["核心结构已经形成"],
- "problems": [{"severity": "一般", "summary": "结尾仍需收束"}],
- "conclusion": "部分通过",
- "recommendation": "优先采用候选方案 A。",
- }
- )
- assert decision["decisionType"] == "evaluation"
- assert decision["authority"] == "recommendation"
- assert decision["actor"]["role"] == "multipath-evaluator"
- assert [block["title"] for block in decision["detail"]["blocks"]] == [
- "评审对象",
- "评审标准",
- "逐项结论",
- "已达成",
- "问题",
- "总结论",
- "评审建议",
- ]
- conclusion = next(block for block in decision["detail"]["blocks"] if block["title"] == "总结论")
- assert conclusion["visualRole"] == "key-result"
- def test_creative_requires_safe_think_and_plan_and_never_infers_from_artifact():
- projector = AgentDecisionProjector()
- event = {
- "id": 70,
- "event_type": "tool_call",
- "event_name": "think_and_plan",
- "agent_role": "script_implementer",
- "status": "ok",
- "inputData": {
- "thought": "现有证据不足,抗性淀粉只作为待验证推导。",
- "action": "补充烹饪贴士段",
- "plan": "先写可确认内容,再标注待核验项。",
- },
- }
- assert projector.creative({"safeLink": False, "event": event}) is None
- assert projector.creative({"safeLink": True, "artifactRef": "artifact:9"}) is None
- decision = projector.creative(
- {
- "safeLink": True,
- "event": event,
- "task": "补齐烹饪贴士",
- "output": "形成候选脚本段落",
- "uncertainties": ["抗性淀粉结论需要进一步核验"],
- "artifactRef": "artifact:9",
- "promptRef": "implementer:70",
- }
- )
- assert decision is not None
- assert decision["decisionType"] == "creative"
- assert decision["authority"] == "implementation-scope"
- assert decision["artifactRef"] == "artifact:9"
- assert "待验证推导" in decision["body"]["reasoning"]
- assert decision["body"]["uncertainties"] == ["抗性淀粉结论需要进一步核验"]
- assert all(item["key"] != "task" for item in decision["card"]["secondary"])
- assert "创作任务" not in [block["title"] for block in decision["detail"]["blocks"]]
- def test_missing_decision_is_explicit_without_fabricated_reasoning():
- decision = AgentDecisionProjector().evaluation(
- {"id": "overall-review:missing", "subtype": "overall-evaluation"}
- )
- assert decision["completeness"] == "missing"
- assert decision["card"]["primary"]["value"] == "决策结论未记录"
- assert decision["body"]["recommendation"] is None
- assert decision["notices"] == [
- {"code": "record-missing", "message": "没有找到结构化决策结论。"}
- ]
- def test_business_copy_translates_internal_ids_without_deleting_business_numbers():
- projection = AgentDecisionProjector().evaluation(
- {
- "id": "event:9",
- "subtype": "multipath-evaluation",
- "achievements": [
- "branch3 完成 P2,参考 postid: 67c6c723,保留领域信息 31/32。"
- ],
- "recommendation": "建议合入 base。",
- }
- )
- business = str(projection["detail"]["blocks"])
- assert "方案 3" in business
- assert "段落 2" in business
- assert "账号内容样本" in business
- assert "主脚本" in business
- assert "领域信息 31/32" in business
- assert "branch3" not in business
- assert "postid" not in business
|