"""洛书 Deliberative 规划器测试。""" from app.application.planning import ( DeliberativePlan, PlanStep, build_planning_prompt, format_plan_for_prompt, parse_plan, should_plan, ) def test_parse_plan_full() -> None: """完整 JSON 计划解析。""" raw = """```json { "title": "调研项目X", "assumptions": ["需要模型网关RAG能力"], "risks": ["外部API可能超时"], "steps": [ {"id": 1, "title": "搜索文档", "description": "搜索关于项目X的文档", "tool": "web_search", "risk_level": "L2"}, {"id": 2, "title": "阅读结果", "description": "阅读搜索结果", "tool": "read_file", "risk_level": "L1"}, {"id": 3, "title": "汇总", "description": "汇总分析", "tool": null, "risk_level": "L1"} ] } ```""" plan = parse_plan(raw) assert plan is not None assert plan.title == "调研项目X" assert len(plan.assumptions) == 1 assert len(plan.risks) == 1 assert len(plan.steps) == 3 assert plan.steps[0].tool == "web_search" assert plan.steps[2].tool is None assert plan.steps[0].risk_level == "L2" def test_parse_plan_no_fence() -> None: """无 ``` 围栏的 JSON 也能解析。""" raw = '{"title": "简单计划", "steps": [{"id": 1, "title": "执行", "description": ""}]}' plan = parse_plan(raw) assert plan is not None assert plan.title == "简单计划" assert len(plan.steps) == 1 def test_parse_plan_missing_title() -> None: """缺少标题 → None。""" raw = '{"steps": [{"id": 1, "title": "执行", "description": ""}]}' assert parse_plan(raw) is None def test_parse_plan_empty_steps() -> None: """空步骤列表 → None。""" raw = '{"title": "空计划", "steps": []}' assert parse_plan(raw) is None def test_parse_plan_invalid_json() -> None: """非法 JSON → None。""" assert parse_plan("根本不是JSON") is None def test_parse_plan_not_dict() -> None: """返回非对象 → None。""" assert parse_plan("[1, 2, 3]") is None def test_should_plan_triggers() -> None: """包含关键词的请求应触发规划。""" triggers = [ "搜索一下项目X的背景", "帮我调研一下最新技术", "分析这个方案的优缺点", "比较A和B的差异", "研究一下这个问题", "search for relevant docs", "research the market", ] for t in triggers: assert should_plan(t), f"should plan: {t}" def test_should_plan_no_triggers() -> None: """简单请求不应触发。""" no_triggers = [ "你好", "再见", "今天天气怎么样", "帮我写首诗", ] for t in no_triggers: assert not should_plan(t), f"should not plan: {t}" def test_build_planning_prompt() -> None: """规划 prompt 包含必要结构。""" prompt = build_planning_prompt("搜索资料", memory_context="用户关心项目X") assert "NO_PLAN_NEEDED" in prompt assert "搜索资料" in prompt assert "用户关心项目X" in prompt def test_format_plan_for_prompt() -> None: """计划格式化后包含步骤和元信息。""" plan = DeliberativePlan( title="测试计划", steps=[ PlanStep(id=1, title="第一步", description="做A", tool="tool1", risk_level="L2"), PlanStep(id=2, title="第二步", description="做B", risk_level="L1"), ], assumptions=["假设1"], risks=["风险1"], ) text = format_plan_for_prompt(plan) assert "测试计划" in text assert "第一步" in text assert "tool1" in text assert "第二步" in text assert "假设1" in text assert "风险1" in text def test_format_plan_empty_assumptions_and_risks() -> None: """无假设/风险时格式化正常。""" plan = DeliberativePlan( title="简单", steps=[PlanStep(id=1, title="做", description="")], ) text = format_plan_for_prompt(plan) assert "简单" in text assert "做" in text