第 14 章 · Reflection 反思
一封发出去才发现写错的催款邮件
那个做跨境的卖家,最近让 Agent 帮他给拖欠货款的海外分销商写催款邮件。Agent 写得很漂亮:措辞得体、语气不卑不亢、还附上了账单编号。他扫了一眼觉得挺好,直接群发了。
结果第二天收到三封回信,都在问同一件事:邮件里写的应付金额,把上个月已经付清的那笔也算进去了。Agent 用的是“截至目前的累计订单额”,却忘了减掉已回款——它给出了一个看起来完全合理、实则算错了的答案,而且自己毫不知情。
人做完一件重要的事,会下意识回头核一遍:“这个数对吗?有没有漏?”这个“回头看”的动作,Agent 天生是缺的——它生成完就交差,不会自己停下来质疑。要补上这一课,靠的就是 Reflection(反思)。
反思是什么:给 Agent 装一个“回头看”的循环
Reflection 的核心,是在“生成结果”之后、“交付结果”之前,插入一个自我评估—修正(self-reflection)的循环:
- 产出(generate):Agent 先给出一个初步结果。
- 评估(evaluate/critique):让 Agent 批判性地审视这个结果——哪里可能错?符合要求吗?有没有漏项?
- 修正(revise):根据评估意见,改进结果。
- 重复:直到评估通过,或达到预设的次数上限。
放到催款邮件那个场景,理想的轨迹应该是:
产出 → 起草邮件,应付金额填 ¥86,400
评估 → 审一遍:金额是"累计"还是"未结"?账单口径对不对?
→ 发现问题:用了累计额,没扣掉已回款 ¥40,000
修正 → 改成未结金额 ¥46,400,附上已收款明细
评估 → 再审:金额对、口径清楚、语气得体
交付 → 通过,发出
那次翻车,缺的正是中间“评估”这一环。
为什么有效:生成者和批评者是两种心态
反思能起作用,背后有一个很关键的洞察:“生成”和“评判”是两种不同的心态(生成者 vs 批评者)。
- 处在“生成者(generator)”心态时,模型一门心思往前推,顺着自己的思路把答案铺完——它在乎的是“把话说圆”。
- 切换到“批评者(critic)”心态时,模型的任务变成“挑刺”——它在乎的是“哪里站不住脚”。
同一个模型,换个心态去看同一份输出,常常能发现它在生成时视而不见的问题。这就像自己写的文章自己校对总看不出错别字,但让你“假装是审稿人”逐句挑毛病,就能揪出来。反思,本质上就是强制模型做一次视角切换。
由此引出两种常见做法:
- 自我反思:同一个 Agent 先做、再以批评者身份自我审视、再改。轻量,一个 Agent 搞定。
- 双角色反思:一个 Agent 专职生成、另一个专职挑错,结果在两者间往返。这已经踏进了多 Agent 的门槛(Part 5 会展开),但视角分离更彻底、更不容易“自己护着自己”。
学术界把这类思路提炼成了几个经典范式,比如 Reflexion(把评估反馈作为“经验”存下来指导下一次尝试)和 Self-Refine(同一模型自我反馈、自我改进的迭代)。名字不用记死,记住它们共同的主张就够了:让模型评判并改进自己的输出,能显著提升质量。
最好的反思,往往来自真实反馈
这里要泼一盆冷水:让模型“自己评判自己”并不总靠谱——它可能一边写错、一边还夸自己写得对。所以在 pi 里,最有效的反思常常不是靠额外的 LLM 调用,而是靠真实反馈。
一个编码 Agent 的循环里,反思是天然内建的:
Action → 写一段计算未结货款的代码
Observation → 运行单元测试,失败:已回款那笔没扣
Thought → 我看看错在哪……哦,漏了 payments 表的关联
Action → 修改代码
Observation → 测试全绿
这里“运行测试”就是评估、“看报错再改”就是修正——整个反思循环没多花一次模型的“自我批评”调用,却比任何自我批评都可靠,因为测试结果是客观的,模型骗不了它。同理,类型检查、编译报错、接口返回的错误码、甚至给催款邮件跑一遍“金额=累计-已回款”的校验脚本,都是廉价又硬核的反馈来源。
一句话原则:能用真实反馈,就别用模型自评;实在没有客观信号时,才退而求其次让模型当批评者。
用 pi 实现:把评估插进生命周期
如果确实需要一个显式的“自我批评”步骤,pi 的扩展机制(第 8 章)让你能在关键生命周期点插入一个评估回合。比如在给出最终答案之前拦一道:
// 概念示意:交付前强制过一次"批评者"审查
export default function reflectionExtension(pi) {
pi.on("beforeFinalAnswer", async (draft, ctx) => {
// 让模型切换到"严格审阅者"视角,专门挑错
const critique = await ctx.ask(
`你是一名严格的审阅者,只负责挑错,不负责夸奖。
检查下面这份草稿是否有事实错误、计算错误或遗漏要求:\n${draft}`
);
if (critique.hasIssues) {
// 有问题:带着反馈打回,让主循环重做一轮
return { revise: true, feedback: critique.issues };
}
// 没问题:放行
});
}
beforeFinalAnswer 是一个生命周期点,我们在这里插入一次批判性检查,有问题就带着 feedback 打回重做。注意这里刻意把提示写成“只挑错、不夸奖”——就是为了把模型硬推进批评者心态,避免它敷衍地说一句“看起来不错”。
反思不是越多越好:给它装刹车
反思很像第 3 章讲的循环,同样有“停不下来”的风险。每多一轮反思,就多一份成本和延迟;无限反思还可能让 Agent 陷入“改来改去、越改越差”的纠结。所以必须设护栏:
- 限次数:最多反思 N 次(通常 1–3 次就够,边际收益递减很快)。
- 优先便宜反馈:能用测试/类型检查判定的,就别多花一次 LLM 调用。
- 明确停止条件:不是“感觉差不多了”,而是可判定的信号——测试全绿、检查项全过、评估无 issue。
动手看看
打开 pi 的编码 Agent,故意让它写一段有 bug 的小函数,同时给它一个会失败的测试。观察它的循环:它会不会在看到测试失败后,自己回头改代码、再跑一次?这就是最朴素、也最实用的反思——你没写任何“反思”代码,反思却发生了。
再做个对照实验:把测试拿掉,只让它“自己检查一遍代码有没有问题”。对比两种方式发现问题的准确率,你会更直观地体会到“真实反馈 > 模型自评”这句话的分量。
实战中的几个坑
坑一:模型自评“睁眼说瞎话”。
- 现象:让它自查,它信誓旦旦说“没问题”,结果错的照样错。
- 原因:还处在生成者心态,护着自己的输出;或评估提示太温和。
- 对策:优先用测试/类型等真实反馈;必须自评时,把提示写成“只挑错不夸奖”,强制切到批评者视角。
坑二:反思停不下来,改来改去。
- 现象:一轮轮反思,答案反复横跳,成本飙升还不收敛。
- 原因:没设次数上限,或停止条件是主观的“差不多了”。
- 对策:限最多 N 次,用可判定的客观信号(测试全绿)作为停止条件。
坑三:反思成本盖过收益。
- 现象:给每个小问题都套上评估回合,token 翻倍,效果没明显变好。
- 原因:对简单任务也无脑上反思。
- 对策:只对高价值、易出错的任务(金额、代码、对外文案)开反思;简单问答别套。
坑四:批评者和生成者是同一份上下文,挑不出刺。
- 现象:自我反思时,模型顺着原来的思路“确认”了错误答案。
- 原因:评估和生成共享上下文,思维定式带过去了。
- 对策:用双角色反思,让评估方在相对独立的上下文里看结果;或把待评估内容“洗”成干净输入再交给批评者。
对比:其他框架
Reflexion / Self-Refine 这类范式落地时,各框架大多用“再跑一轮评估”或“双角色”来实现:
| 框架 | 反思怎么做 | 特点 |
|---|---|---|
| LangGraph | 把反思画成显式循环:generate → reflect → revise,用条件边判断“是否再改” | 循环边界一目了然,最可控 |
| CrewAI | 常用“执行者 + 审阅者”双角色,输出在两者间往返 | 贴近团队协作心智 |
| PydanticAI | 无内建反思循环,但 schema 校验是天然的“廉价反馈”,不合规就重试 | 结构化输出即免费反思 |
| Agno | 用 Team 的生成者 + 评审者角色实现,框架不强制 | 灵活,需自己搭 |
| pi | 优先靠真实反馈(测试、报错),也可用扩展在 beforeFinalAnswer 插入显式自评 | 廉价反馈优先,显式自评可选 |
共性是“生成”与“评判”分离、循环直至达标;差别在于——是花一次模型回合去评判,还是尽量用测试/类型这类真实反馈把评判“外包”给客观世界,省钱又靠谱。
小结
- Reflection = 给 Agent 加一个自我评估—修正循环:在交付前插入“评估”一环,堵住“错了却不自知”。
- 有效的关键是生成者 vs 批评者的视角切换——同一模型换个心态挑刺,能发现生成时的盲点。
- 真实反馈优于模型自评:测试、类型检查、报错这类客观信号,比让模型夸自己可靠得多、也便宜得多。
- pi 里编码循环天然含反思;需要显式自评时,用扩展在
beforeFinalAnswer等生命周期点插入批评者回合。 - 反思要装刹车:限次数、优先便宜反馈、用可判定的客观信号作停止条件——反思不是越多越好。
规划给方向、反思纠错,还有一种不靠外部反馈、单凭“想清楚”就能让单个 Agent 变准的基础技巧——下一章的 Chain-of-Thought 思维链。