136 lines
4.0 KiB
Python
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
|