Files
hunjing/backend/tests/application/planning/test_planning.py
T
2026-08-21 16:00:13 +08:00

136 lines
4.0 KiB
Python

"""洛书 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