Self-Discovery:先选推理策略,再解题
有些失败不是能力问题,而是策略问题。
旅游助手接到"带老人的雨天少步行美食之旅"——它应该先分解约束?先验证路线可行性?先类比以前的成功案例?如果选错了策略,答案从一开始就走偏了。
Self-Discovery 让模型先从一个策略库里挑选合适的推理模块,然后按选定的模块解题。
一句话说清楚
Self-Discovery 把"直接解题"改成"先从策略库选模块、再按模块解题",让模型在动手之前先承诺一个方法。
解决什么问题
| 问题 | 表面看起来 | 实际风险 |
|---|---|---|
| 直接回答 | 快 | 用了错误的策略 |
| 策略全写在 prompt 里 | 全面 | 模型可能无视 |
| 策略库太大 | 灵活 | 选择变成随机 |
旅游助手收到一个复杂请求时,如果 prompt 里塞了 10 条策略("先查天气""先查预算""先分解约束"...),模型很可能只用其中一两条,剩下的形同虚设。Self-Discovery 强制模型在一个显式的"选择"步骤里做出承诺。
引入什么复杂度
- 需要维护一个策略模块库(module library)。
- 模块如果太抽象("逻辑思考""仔细分析"),选了也没用。
- 模型可能选了模块但解题时不用——需要在每个模块绑定检查点。
和其他模式的关系
| 模式 | 谁决定下一步 | 什么时候用 |
|---|---|---|
| Plan & Solve | 模型写步骤 | 任务需要拆步骤 |
| Self-Discovery | 模型选策略模块 | 问题在于选错方法 |
| LATS | 搜索控制器比较候选 | 多个方案需要评分 |
| Routing | 路由选外部流程 | 不同输入走不同 pipeline |
Self-Discovery 和 Routing 有相似之处——都是"先选再做"。区别是 Routing 选的是外部流程(由代码实现),Self-Discovery 选的是推理策略(由模型内部执行)。
这个模式改变了什么
| 谁 | 负责什么 |
|---|---|
| 模块库(Python) | 提供可选策略列表 |
| 模型(选择步骤) | 从库中选出本次任务要用的模块 |
| Python | 校验模块名合法 |
| 模型(解题步骤) | 按选定模块解题 |
- 谁决定下一步:两阶段——先选模块,再解题。
- 谁拥有状态:Python 持有可用模块列表和被选中的模块列表。
- 什么时候停止:解题步骤返回答案。
- trace 记录什么:
self_discovery.select(选了哪些模块)、self_discovery.solve。
走一遍真实轨迹
用户请求:"帮我规划杭州老人雨天行程,少步行,以美食为主。"
可用模块库:["decompose_constraints", "verify_route", "analogy", "check_walking_load", "weather_first"]
| 阶段 | 输出 |
|---|---|
| 选模块 | ["decompose_constraints", "check_walking_load", "weather_first"] |
| 校验 | 三个名称都在合法列表里 |
| 解题 | 先按 weather_first 检查天气 → 按 decompose_constraints 分解约束(老人、雨天、少步行、美食)→ 按 check_walking_load 验证步行量 → 输出路线 |
如果模型没有 Self-Discovery,它可能直接输出一条步行量大的路线,根本不检查"少步行"这个约束。
流程图
flowchart TD
T["用户任务"] --> L["读模块库"]
L --> S["模型选模块"]
S --> V["Python 校验模块名"]
V --> A["模型按选定模块解题"]
A --> O["答案"]
完整实现
from __future__ import annotations
from pathlib import Path
from agent_patterns_lab.patterns.self_discovery import self_discovery
from agent_patterns_lab.runtime import MockLLM, Tracer
def main() -> None:
tracer = Tracer()
# 可用模块库
available_modules = [
"decompose_constraints", # 分解约束条件
"verify_route", # 验证路线可行性
"analogy", # 类比以前成功案例
"check_walking_load", # 检查步行量
"weather_first", # 先查天气再规划
]
model = MockLLM(
[
# 第一次调用:选模块
'{"modules":["decompose_constraints","check_walking_load","weather_first"]}',
# 第二次调用:按模块解题
(
"【策略:weather_first】先查天气 → 杭州明天全天小雨。\n"
"【策略:decompose_constraints】约束分解:\n"
" - 老人 → 避免爬坡、台阶\n"
" - 雨天 → 室内为主\n"
" - 少步行 → 打车或公交\n"
" - 美食 → 安排特色餐厅\n"
"【策略:check_walking_load】步行量检查:\n"
" 全天步行 < 3000 步 ✓\n\n"
"推荐行程:\n"
"上午:知味观(早茶)→ 打车到中国茶叶博物馆\n"
"中午:龙井村午餐(龙井虾仁)\n"
"下午:宋城室内演出\n"
"晚上:外婆家(西湖醋鱼)"
),
]
)
result = self_discovery(
model,
task="帮我规划杭州老人雨天行程,少步行,以美食为主。",
available_modules=available_modules,
tracer=tracer,
)
print("=== 选中的模块 ===")
print(result.selected_modules)
print("\n=== 答案 ===")
print(result.answer)
trace_path = tracer.export_jsonl(Path(".traces") / "55_self_discovery_zh.jsonl")
print(f"\n[trace] {trace_path}")
if __name__ == "__main__":
main()
运行:
PYTHONPATH=src python examples/55_self_discovery.py
完整运行记录
=== 选中的模块 ===
['decompose_constraints', 'check_walking_load', 'weather_first']
=== 答案 ===
【策略:weather_first】先查天气 → 杭州明天全天小雨。
【策略:decompose_constraints】约束分解:
- 老人 → 避免爬坡、台阶
- 雨天 → 室内为主
- 少步行 → 打车或公交
- 美食 → 安排特色餐厅
【策略:check_walking_load】步行量检查:
全天步行 < 3000 步 ✓
推荐行程:
上午:知味观(早茶)→ 打车到中国茶叶博物馆
中午:龙井村午餐(龙井虾仁)
下午:宋城室内演出
晚上:外婆家(西湖醋鱼)
[trace] .traces/55_self_discovery_zh.jsonl
trace 事件:
self_discovery.select modules=["decompose_constraints","check_walking_load","weather_first"]
self_discovery.solve
踩坑与诊断
| 坑 | 现象 | trace 信号 | 修法 |
|---|---|---|---|
| 模块是口号 | "逻辑思考""仔细分析" | 选了但答案没变化 | 每个模块定义清楚:输入/输出/检查清单 |
| 选了但不用 | 答案里看不到策略痕迹 | trace 有 select 但 solve 输出和模块无关 | 每个模块绑检查点,prompt 要求引用模块名 |
| 模块太多 | 选择漂移 | 选了 5+ 模块 | 限制库大小(5-8 个)和最大选择数(3 个) |
| 选错模块 | 方向一开始就不对 | 最终答案和约束不匹配 | 允许 quick check 后重新选择 |
关键诊断技巧:检查答案里是否有每个选中模块的输出。如果选了 check_walking_load 但答案里没有步行量数字,说明模型"选了但没执行"。
工程备忘
成本公式:
模型调用 = 2(选模块 + 解题)
最便宜的规划模式之一。成本和 Plan & Solve 差不多,但解决的问题不同:Plan & Solve 解决"没有计划",Self-Discovery 解决"用错策略"。
生产注意事项:
- 模块库建议 5-8 个,每个模块需要有:名称、描述、输入/输出、检查清单。
- "analogy"这种抽象模块效果不稳定——具体模块(如
check_walking_load)更可靠。 - Self-Discovery 可以和其他模式组合:先用 Self-Discovery 选策略,再用 Plan & Solve 执行。
- 如果模块库需要动态更新(新任务类型出现),考虑把模块库放在检索系统里。
读完以后
Self-Discovery 适合"方法选择比执行更关键"的场景。
- 如果任务需要具体步骤规划,读 Plan & Solve。
- 如果需要在多个候选之间搜索,读 LATS。