Chapter 3 · 用工具替代心算
这一部分是本书最实用的部分——也是回报率最高的一部分。绝大多数生产 bug 都出在 A 档:算术错、JSON 字段漂移、SQL 语法错、不知最新事实。每一类都有一个确定性系统能把它彻底接管。
LLM 心算简单算术多半能靠 CoT 蒙对,但代价是几百到几千 tokens、几秒延迟,且偶尔在某步进位 / 四舍五入上飘;一旦步数变多(多步复利、日期时区、大数组统计),准确率随步数指数衰减(Faith and Fate,arXiv:2305.18654)。Toolformer(arXiv:2302.04761)/ PAL(arXiv:2211.10435)的解法:让模型生成"调用计算器"的指令、由确定性程序执行——错误率归零,CoT 也省了。
Item 6:任何 4 位以上算术都必须走工具
你不会让一个 7 岁小孩心算 4823 × 6791,那为什么要让 LLM 做?
核心
GSM8K 这种小学题,前沿模型 CoT 已经 95%+,Tool 的边际收益微弱;但换到 MATH / AIME / GSM-Hard 这类长链精确计算,CoT 准确率显著下滑,Tool 仍能稳住。模型越强,Tool 的优势从"对错"转移到"快、稳、便宜"——CoT 烧 1500 tokens、几秒延迟、偶尔飘;eval() 1 微秒、确定值、零成本。
反例 vs 正例
# Bad — 让 LLM 心算
prompt = "本金 38472.50,年利率 5.85% 按月复利,存 7 年 4 个月,到期总额?"
# 现代模型会写 500-1500 tokens 的 CoT 一步步推导;
# 看似都对,但同一道题问 30 次,会有 1-2 次在某步进位 / 四舍五入上飘;
# 而且每次都要烧几秒 + 几千 tokens,算一个 1 微秒就能得出的数字。
# Good — Tool calling
tool_schema = {"type": "function", "function": {"name": "compute",
"parameters": {"type": "object", "properties": {"expression": {"type": "string"}}}}}
# 让模型生成 expression = "38472.50 * (1 + 0.0585/12)**(7*12 + 4)"
# 由 Python 子进程执行,输出确定值。Things to Remember
- 永远不要让 LLM 心算关键数字——把所有数学题路由到 calculator 或 code_executor。
- 工具调用要在沙箱内(e2b / Modal / Firecracker),避免任意代码执行。
- 错误时把 exception 反馈给 LLM 重试 ≤ 3 次,仍失败则放弃。
Item 7:把日期、单位换算、统计计算交给确定性程序
"今天是星期几"是 LLM 最常错的题之一。
核心
日期算术、时区换算、单位换算(华氏/摄氏、英尺/米)、统计量(中位数、p95)都属于确定性问题,模型在这些任务上的错误率随计算复杂度指数上升。固定到 datetime、pint、numpy 这种库去做。
Things to Remember
- 日期 →
datetime;单位 →pint;统计 →numpy.percentile。 - 不要相信模型口算的"今天距离 2024-01-01 有多少天"。
Item 8:让代码生成走"生成 → 执行 → 反馈"闭环
一次写不对的代码,跑一次就知道。
引子
LLM 直接生成的代码,HumanEval 通过率约 70%-80%。引入"运行单元测试 → 把失败信息反馈给模型 → 再修"的闭环,可提升至 90%+。这就是 Self-Debugging(arXiv:2304.05128)和 Reflexion(arXiv:2303.11366)的核心:把执行器变成训练信号的代用品。
机制原理
LLM 在静态生成时无法访问"运行时反馈"这条最强信号。把 stdout / stderr / traceback 反馈回 prompt,模型实际上把"调试"这件事建模成"在 traceback 上做下一步预测"——这是它训练数据里大量见过的模式(StackOverflow 风格)。
反例 vs 正例
# Bad — 单次生成
code = llm.generate("写一个排序函数 …")
exec(code) # 直接跑,崩了就崩了
# Good — Reflexion 循环
for attempt in range(3):
code = llm.generate(prompt)
result = run_tests(code)
if result.passed:
break
prompt = original_prompt + f"\n\n上一次的代码失败:\n{result.traceback}\n请修复。"Sidebar:Reflexion 的内部机制
Reflexion 与 Chain-of-Thought 的区别在于记忆类型:CoT 是 working memory(当前 prompt 的上下文),Reflexion 是 episodic memory(写下"我上次为什么失败"作为下一次的额外上下文)。前者随 prompt 消失,后者通过外部存储跨 trial 累积——这是 Agent 与 prompt 工程的本质分界。
Things to Remember
- 代码生成就走 Generate → Execute → Feedback 三段式。
- 用结构化的失败描述喂回模型(traceback + 期望输出),不要只说 "it failed"。
- 限制最大重试次数防死循环;多次失败应 escalate 到人工。
延伸阅读
- Self-Debugging(arXiv:2304.05128)—— 让模型生成代码 → 跑单元测试 → 把失败信息回灌作为下一轮 prompt,HumanEval 通过率从 ~80% 升到 90%+。
- Reflexion(arXiv:2303.11366)—— Agent 失败后写一段"反思笔记"作为下一次的额外上下文(episodic memory),把试错经验跨 trial 累积起来。
- SWE-Agent(arXiv:2405.15793)—— Princeton 的工业级 coding agent:用 Agent-Computer Interface(ACI)让模型在真实 GitHub issue 上自主改代码,SWE-Bench 通过率显著领先。