Reflection:让 Agent 学会自我纠错
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
上一篇用跨品类场景验证了 TinyAgent 的组合编排能力——单任务用 Skill 保稳定,复合任务用 Plan-and-Execute 保完整。结尾留了一个悬念:如果 Agent 在执行过程中翻车了——工具返回错误、LLM 理解偏差、步骤结果不符预期——怎么办?回顾一下第 15 篇的 Re-plan 机制——当步骤执行抛出异常时,让 Planner 重新规划剩余步骤。这个机制只覆盖了“抛异常”这一种情况,还没有让 Agent 自己反思哪里做错了、结果够不够好。
这一篇,咱们给 TinyAgent 装上 Reflection(反思)机制——让 Agent 不只会执行,还能自我评估、自我纠错。
本项目中具体代码已上传 GitHub TinyAgent,大家 Clone 项目后,将代码分支切换到 1.13.x,默认主分支是最新代码。运行前复制
.env.example为.env,把自己的 API Key 填进去,默认阿里云百炼平台;.env已加入.gitignore,切分支时不会丢。
Agent 翻车的三种姿势
在聊怎么修之前,先把问题分个类。比特严选智能体在执行过程中可能遇到的错误,大致分三种:
1. 工具返回错误
这是最直接的一类——工具调用本身就报错了。
举个例子:用户说“帮我催一下快递,单号 SF0000000”,Agent 调 queryLogistics("SF0000000"),物流接口返回 {"error":"运单号不存在:SF0000000"}。原因可能是用户复制了错误的快递单号,也可能是商家已生成运单但物流公司尚未揽收。
这类错误的特征是工具返回值里带明确的错误信息,Agent 只要读懂错误就能做出合理的下一步——告知用户运单不存在,建议确认快递单号。
2. 推理偏差
比直接报错更隐蔽的是推理偏差——工具调用成功了,结果也拿到了,但 LLM 在理解和总结时偏离了用户的真实意图。
举个例子:用户说“我家养了两只长毛猫,帮我在 R1 和 R7 Pro 两款扫地机里挑一个”,Agent 调 compareProducts 拿到了两款的完整规格参数,但在总结时做了全维度平铺对比(价格、吸力、续航……),没意识到对养猫用户来说,滚刷防不防缠绕才是决定性因素。工具没错,LLM 也确实做了对比,但结果偏离了用户的真实处境。这类偏差不报错、也不抛异常,是反思重点要盯的对象——不过它到底能不能被反思纠回来,还得看关键信息藏在哪,这点后面 Demo 会掰开讲。
3. 多步任务中的连锁影响
在 Plan-and-Execute 模式下,一个步骤的失败可能影响后续步骤。
举个例子:用户说“帮我对比 AirX 耳机和 WatchFit 手表,顺便查一下订单 99999 到了没”。Planner 拆出三步:Step 1 对比耳机和手表(跨品类),Step 2 查订单,Step 3 综合回复。如果 Step 2 查不到订单,Step 3 在综合回复时需要知道这件事——否则它要么编造一个订单状态,要么干脆忽略这个子任务。
三种错误的共同点是:光靠 try-catch 和简单重试解决不了。工具错误需要判断是否值得重试(换个参数试试 vs 直接告知用户);推理偏差根本不会抛异常;连锁影响需要全局视角来调整后续步骤。这就是反思机制要解决的问题。

| 错误类型 | 表现 | 现有机制 | 为什么不够 |
|---|---|---|---|
| 工具返回错误 | 工具返回 error JSON | Re-plan 只在抛异常时触发 | 工具返回错误不一定抛异常,可能被当作“成功”处理 |
| 推理偏差 | 结果偏离用户意图 | 完全不检测 | 没有评估环节,步骤“完成”了就继续 |
| 连锁影响 | 后续步骤基于错误前提执行 | Re-plan 粗粒度处理 | 重规划时缺乏失败原因分析,容易重复犯错 |
现有的错误处理:够用但不够聪明
在加反思之前,先盘点一下 TinyAgent 已有的错误处理能力。
1. ReAct 层面
ReAct 循环里已经有三道防线:
- RepeatDetector:检测连续两次调用相同工具、相同参数。第一次重复时注入提醒,第二次重复时强制终止。防的是死循环。
- ProgressDetector:检测连续多圈推理内容高度相似(Jaccard 相似度 > 0.7),一旦命中就终止循环。防的是 LLM 空转:每圈都在输出推理文本,看起来在思考,但内容反复雷同、没有实质推进。
- 最大步数:
maxSteps兜底。不管什么原因,超过限定圈数就停。
这三道防线解决的是“Agent 卡住了怎么办”——检测到问题后机械地终止,不分析为什么卡住。
2. Plan-and-Execute 层面
PlanAndExecuteAgent 有一个 Re-plan 机制。以下是简化后的核心逻辑——planner 是 PlanAndExecuteAgent 在构造时创建的字段,负责生成和调整执行计划:
try {
String result = executeStep(step, plan, tools);
step.markCompleted(result);
} catch (Exception e) {
step.markFailed(e.getMessage());
if (replanCount < maxReplanCount) {
plan = planner.replan(plan);
}
}
这段代码的逻辑是:步骤执行抛异常 → 标记失败 → 让 Planner 重新规划剩余步骤。
问题在哪?两个:
一是触发条件太窄。只有 executeStep 抛异常才会触发 Re-plan。但很多错误不会以异常形式出现——工具返回 {"error":"订单不存在"} 时,executeStep 正常返回了一个字符串(LLM 把错误信息组织成了文本回复),try-catch 捕获不到。步骤被标记为 COMPLETED,但内容是一个错误描述。
二是重规划缺乏上下文。Re-plan 时,Planner 能看到哪些步骤完成了、哪些失败了,但不知道为什么失败、应该怎么调整。它只能基于步骤描述重新拆解,很可能生成一个跟之前差不多的计划——然后再次失败。
用一句话概括:现有的错误处理是“检测到异常 → 机械应对”,缺少“分析结果质量 → 理解失败原因 → 调整策略”这个反思环节。
Reflection:让 Agent 学会反思
1. 什么是 Reflection
Reflection(反思)的核心思想很简单:在执行完一个步骤后,不急着往下走,先让另一个 LLM 调用来评估这一步的结果是 否达成了目标。
打个比方:你在装修房子,每刷完一面墙,不是直接刷下一面,而是退后两步看看——颜色均不均匀?有没有漏刷的角落?如果发现问题,现在补一下比等全部刷完再返工成本低得多。
落到 Agent 上,Reflection 就是在每个步骤执行完后插入一个评估环节:
执行步骤 → 拿到结果 → 反思:结果达标吗?
├── 达标 → 继续下一步
├── 有偏差但可修 → 重试当前步骤(附带改进建议)
└── 无法修复 → 标记失败,触发重规划
2. 三种反思结论
Reflector 评估完一个步骤的结果后,会给出三种结论之一:
| 结论 | 含义 | 后续动作 |
|---|---|---|
| PASS | 结果充分满足步骤目标 | 标记完成,继续下一步 |
| RETRY | 结果有偏差,但可通过调整执行方式修正 | 将改进建议注入提示词,重试当前步骤 |
| REPLAN | 当前步骤遇到无法通过重试解决的问题 | 标记失败,带反思上下文触发重规划 |
三种结论对应三种不同的恢复策略——不是所有错误都需要重规划,也不是所有偏差都值得重试。Reflector 的价值就在于做这个判断。
3. 有反思 vs 无反思的执行流程
先用一张图对比有反思和无反思的执行流程差异。左边是原来的逻辑——执行完直接标记完成;右边加入了反思环节——执行完先评估,不达标才触发恢复:

关键区别在于:无反思模式下,只要 executeStep 不抛异常,结果就被直接接受;有反思模式下,即使步骤“成功”了,Reflector 也会检查结果内容——工具返回的错误信息、偏离用户意图的回答,都能被识别出来。
手写 Reflector
1. ReflectionResult:反思结果数据结构
先定义反思结果的数据结构。三 个字段:结论(PASS/RETRY/REPLAN)、分析(为什么做出这个判断)、建议(如果需要重试或重规划,应该怎么调整)。
public class ReflectionResult {
public enum Verdict {
PASS, // 执行结果满足步骤目标,继续下一步
RETRY, // 执行结果有偏差,可通过重试修正
REPLAN // 当前步骤无法通过重试修复,需要调整后续计划
}
private final Verdict verdict;
private final String analysis;
private final String suggestion;
public ReflectionResult(Verdict verdict, String analysis,
String suggestion) {
this.verdict = verdict;
this.analysis = analysis;
this.suggestion = suggestion;
}
// getter ......
}
结构很简单,核心是 Verdict 枚举——三种结论对应三种恢复策略,不需要更多分类。analysis 是给日志看的,suggestion 是给重试时注入提示词用的。