做过Agent开发的朋友,大概率都经历过这类场景:让模型生成一段代码,第一版跑通主干逻辑,一测边界条件全是坑;让模型写一版文案,初稿看下来框架好像都有,细读却经不起推敲。生成结果的一次性输出,质量上下限差距极大,靠“多抽几次卡”碰运气,既不稳定也浪费成本。大模型Agent进阶到一定阶段,核心问题不再是“怎么把Prompt写得更花哨”,而是怎么让Agent具备自我审视、自我修正的能力。这就是反思模式(Reflection Pattern)要解决的问题,也是近期Agent工程化落地里我觉得性价比最高的一种架构升级。
这篇内容适合已经能独立搭出基础Agent、但觉得生成质量不稳定的开发者。我会从反思模式解决的问题讲起,拆解两条主流落地路线,然后给出一份可以直接抄走的代码实现,再结合我实际踩过的坑,聊聊让反思真正起效的关键配置。
1. 反思模式解决的核心问题,以及它和普通Agent的差异
1.1 “一次生成”用不好模型的真正原因
大模型本质上是一个自回归的Token预测器,输出是一口气从左到右生成的。这意味着在生成过程中,模型没有一个“回头检查”的机制。你可以把这种生成方式理解成“一口气写完一篇作文初稿”:想到哪写到哪,前面埋的伏笔后面忘了,结尾收不住,甚至中间出现明显的逻辑断点,写的人自己不觉得,因为写的时候没有机会停下来重读。
我做过一组粗测:让同一个模型分别完成“写一段带异常处理的Python函数”“整理一份会议纪要点”“根据需求列一份技术选型对比表”三类任务,每类跑20次,一次生成直接达到可用标准的比例,好的任务能到七成,差的三成都不到。这个波动不是模型能力问题,而是生成策略问题——模型没有在输出之后做二次校验和修正。如果不给Agent增加“看完初稿再改一遍”的环节,那输出质量基本就靠掷骰子。
1.2 反思模式到底是什么,它改变了什么
反思模式的核心思路一句话就能说清:让模型自己生成结果,再让模型(或另一个模型)对结果进行批评、找问题,最后根据批评意见重新生成。整个流程从“一次生成”变成“生成—评审—修改”的闭环。
这个思想来自几篇经典工作,其中大家常提到的有Self-Refine和Reflexion两条路线。前者是单任务内部反复打磨:输出初稿,Critic挑错,Generative根据意见重写,循环若干轮直到满意。后者更强调跨任务的经验积累:任务失败后,把失败原因和修正经验记入记忆,供后续任务参考,有点像运动员赛后复盘,把失利原因写进训练笔记,下次比赛避开同样的坑。
这里得把反思模式和另一套常见的Agent范式ReAct区分开。ReAct是“边想边做”:模型每走一步,先思考再行动,把行动结果作为下一轮思考的输入。反思模式则是“做完再回头审”:先把一个完整的输出产出来,再以评审者视角挑剔它。两者的组合关系是——你可以把一个反思循环嵌在ReAct的某个关键步骤后面,作为质量控制阀门。我后来在项目里常用的架构,就是ReAct做主干流程,反思模式做关键产出的质检关卡。
下面用一张表快速对比几类Agent架构倾向:
| 架构形式 | 核心特征 | 适用场景 | 短板 |
|---|---|---|---|
| 单轮生成 | 一次输出,不可自纠 | 简单问答、格式转化 | 复杂任务成功率低 |
| ReAct | 推理与行动交替,借助工具反馈推进 | 多步骤操作、检索问答 | 缺少对最终产出的整体审视 |
| 反思模式 | 生成—批评—重写闭环 | 代码生成、长文写作、方案设计 | 调用成本上升,需控制轮数 |
| ReAct + 反思 | 前者的执行能力叠加后者的质检能力 | 复杂业务链路Agent | 工程复杂度较高 |
1.3 反思模式的实际价值,不只是“改错字”
很多人第一次听说反思模式,以为它就是让模型“检查一遍再输出”,觉得一个“请检查并改进”的追加Prompt就能搞定。实际效果差距很大。反思模式真正解决的,是三类单轮生成绕不开的问题。
第一类是局部错误,比如代码里少考虑了一种边界条件、文案里数字前后对不上。这类问题靠“再想想”有时候能抓住,但更多时候模型会顺着原思路继续自我肯定。
第二类是结构性问题,比如技术方案缺少风险分析、文章结论缺少数据支撑。这种问题需要从全局视角审视,而在一次流式生成的过程中,模型根本不可能做到“跳出来看自己”。
第三类是忽略用户隐性约束,比如用户要求“输出格式简洁”,初版却给了一大段附带解释。反思Critic可以把“是否严格遵守用户约束”作为一个检查维度,显著降低这类失误。
我实际感受最明显的一个变化是:给Agent加了一层反思之后,它产出的方案文本明显从“第一版的草稿感”变成“能直接发出去的东西”,需要我人工返工的次数降了不少。这正是反思模式在工程上最核心的价值——把Agent从“赌运气”变成“有质检流程的生产工具”。
2. 反思模式怎么设计:两种主流落地路线
2.1 自我反思(Self-Refine):低成本快速入门
Self-Refine的操作方式是用同一个模型同时扮演生成者和评审者:第一轮让模型输出初稿,第二轮换一套评审者的Prompt让它对自己的初稿提出改进意见,第三轮把初稿和意见一起塞回去让它重写。结构最简单,不需要引入额外模型调用。
代价也很明显:模型在“自己批评自己”时,很容易陷入自我维护倾向——它倾向于觉得自己写的东西没有大问题,评审意见流于表面,经常出现“整体不错,个别细节可以优化”这类从头到尾等于没说的反馈。
我之前做过一次实验:让模型对自己刚生成的代码做Code Review,要求列出至少三个问题。结果它列出来的问题大多集中在变量命名、注释要不要补这类表层事项,真正致命的并发安全问题和错误处理缺失反而没抓到,这还不是模型能力不够,而是“评价者与创作者共享了一套思维盲区”——它写的时候没想到某类边界条件,审的时候同样不会想到。
所以Self-Refine适合的场景,是任务本身复杂度不高的场景,比如改邮件措辞、整理周报、优化文案的表达流畅度。这类任务里,模型的表层审视能力已经够用。
2.2 双角色互评(Critic-Actor):质量优先的选择
双角色互评,就是把“生成”和“评审”拆成两个独立角色,或者干脆用两个不同的模型实例实现。生成者负责产出,评审者负责挑刺,两者之间形成对抗张力。
这里有一个非常重要的实操细节:评审者应该使用和生成者不同的系统配置,甚至不同的模型。常见做法是生成者用偏高的Temperature(比如0.7)保证产出多样性和创造性,评审者用偏低的Temperature(比如0.2)保证评判稳定、意见集中。也可以刻意挑一个更便宜的轻量模型做评审,因为挑剔一个人的工作,往往不需要和创造它同级别的能力,只要能识别出明显问题就够了。
双角色互评的典型实现,是Critic输出一份结构化评审意见,包含:问题清单、严重程度分级、具体修改建议三部分。生成者拿到这份意见后,逐条确认并重写。这套路线的工程开销比Self-Refine多了一次模型调用(在后续双循环里可能更多),但在复杂任务上的质量提升非常明显。
用生活里的场景类比一下这两种模式:Self-Refine就像自己改自己的作文,写着写着容易觉得“我写得还行啊”;双角色互评则更像同桌互批,旁观者总能更容易发现你句子里的逻辑漏洞和错别字。
2.3 我推荐的组合:按任务复杂度分级选择
实际项目中,我不会把一套反思方案用到所有Agent上。通常是这样分的:
- 简单格式化任务,不加反思,单轮生成搞定;
- 中等复杂度的文本生成类任务,用Self-Refine,控制在一轮反思;
- 代码生成、技术方案、数据分析结论这类“错了代价很高”的任务,用双角色互评,至少两个模型角色。
这里做一个提醒:反思模式是叠加在原有Agent流程上的一套“质量增强机制”,它不改变任务本身的执行逻辑,也不该取代任务编排。反思是质检环节,不是业务环节,位置要放对。
3. 动手实现一个最小可跑的反思Agent
3.1 先规划整体结构
在动手写代码之前,先把结构想清楚。一个最小可用的反思Agent至少需要拆成三个能力:
- generate:生成初稿;
- critique:对结果做评审,返回问题列表和修改建议;
- refine:结合初稿和评审意见,产出修订稿。
再往外扩一点,还需要一个控制循环,负责判断“改几轮结束”。判断条件有两种思路:一是固定轮数,跑完两轮就停;二是引入评分机制,Critic在每次评审时顺便给初稿打一个总分,分数超过阈值就提前停止。我推荐用第二种,它省成本也更贴近真实质检流程——一个稿子已经90分了,没必要硬改到第三轮。
3.2 完整代码实现
写成纯Python类,把模型调用部分抽象成一个llm.chat()函数,方便你替换成自己在实际使用的模型接入方式。核心逻辑我直接贴出来:
import json class ReflectionAgent: def __init__(self, llm, max_rounds=3, quality_threshold=85): self.llm = llm # 传入模型调用函数 self.max_rounds = max_rounds self.quality_threshold = quality_threshold self.history = [] def generate(self, task, previous_critique=None): # 构造生成请求 if previous_critique is None: prompt = f"请完成以下任务:\n{task}\n直接输出结果。" else: # 把上一轮的评审意见一并提交,让模型知道要改什么 critique_text = json.dumps(previous_critique, ensure_ascii=False, indent=2) prompt = ( f"请根据以下评审意见,重新完成刚才的任务。\n" f"原始任务:{task}\n" f"你的历史产出:\n{self.history[-1] if self.history else '(无)'}\n" f"评审意见:\n{critique_text}\n" f"请基于评审意见重写结果,并在开头说明你主要修改了哪些点。" ) return self.llm.chat(prompt) def critique(self, task, output): # 构造评审请求 prompt = ( f"你是一名严格的评审专家,请对以下产出进行评审。\n" f"原始任务:{task}\n" f"待评审产出:\n{output}\n" f"请从【逻辑完整性】【边界条件】【格式规范】【对用户约束的遵守】四个维度评审。\n" f"要求:\n" f"1. 必须找出至少两个实质性问题,禁止说'整体不错'这类空话。\n" f"2. 每个问题必须给出具体的修改建议。\n" f"3. 最终输出JSON格式:{{\"score\": 0, \"issues\": [{{\"severity\": \"high/medium/low\", \"problem\": \"...\", \"suggestion\": \"...\"}}]}}" ) result = self.llm.chat(prompt) # 这里假设模型返回的是可直接解析的JSON return json.loads(result) def run(self, task, show_log=False): self.history = [] current_output = self.generate(task) for round_idx in range(self.max_rounds): if show_log: print(f"=== Round {round_idx + 1} ===") # 评审上一版产出 review = self.critique(task, current_output) self.history.append(current_output) score = review.get("score", 0) if show_log: print(f"当前评分:{score}") for issue in review.get("issues", []): print(f" [{issue['severity']}] {issue['problem']}") # 分数达标,提前收住 if score >= self.quality_threshold: if show_log: print("达到质量阈值,停止反思。") return current_output, review, round_idx + 1 # 未达标则带着意见重写 if round_idx + 1 < self.max_rounds: current_output = self.generate(task, previous_critique=review) return current_output, review, self.max_rounds这个类用起来很简单:
agent = ReflectionAgent(llm=my_llm_func, max_rounds=3, quality_threshold=85) output, review, rounds_used = agent.run("写一个重试三次、每次退避0.5秒的Python函数", show_log=True)3.3 关键设计里的三个细节
第一,为什么评审意见要JSON结构化。因为反思循环里,评审意见是生成阶段的输入,结构化格式能让生成模型更准确、更健壮地定位问题所在。自然语言的长篇评语容易让重写阶段跑偏,结构化的问题列表则清晰得多。
第二,为什么重写时要看上一版历史。如果不把上一版输出一起塞回生成Prompt,模型经常会在重写时把原本写得好的段落也顺手改了,导致修改完反而比上一版差。把历史产出作为上下文输入,相当于告诉模型:“这是你上一版的稿子,在这基础上改,别任性推倒重来。”
第三,为什么要有分数阈值卡控。如果你只设固定轮数,那第二轮已经改好了也会硬着头皮改第三轮,可能把对的改成错的。用评分做停止条件,让整个循环具备“早停”能力,质量和成本都能兼顾。
我实际跑这个最小实现时的一个典型效果是:让Agent写一个带过期时间缓存的Python函数,初版漏了并发场景下的双写问题,第一轮评审抓到了,第二轮重写后补上了加锁逻辑。两轮结束,评分从78分涨到91分。如果没有反思,这个并发问题大概率会带着上线。
4. 让反思真正有效的5个关键设置
4.1 评审Prompt一定要写成“找茬模式”
反思模式最容易翻车的地方,是Critic变成“点赞机器”。模型的默认倾向是顺从和鼓励,你要在评审Prompt里明确写死规则。
我常用的评审Prompt里有三句关键话:必须找出至少两个实质性问题;禁止使用“整体不错”“基本可以”等空话;每个问题必须给出可操作性的修改建议。这几句看起来简单,但有没有它们,评审质量完全是两回事。加之前Critic会给你输出“建议增加注释、完善错误处理”这种泛泛而谈,添加之后它会具体到“第17行字典读取缺少默认值,在Key不存在时会抛KeyError”。
4.2 分维度评审,避免意见太散
让Critic没有任何限制地自由批评,意见容易散成一盘沙。实际我会给评审设四个固定维度:
| 评审维度 | 检查内容举例 | 对应常见失败模式 |
|---|---|---|
| 逻辑完整性 | 结论是否有数据支撑、步骤是否闭环 | 方案缺结尾、推理链断裂 |
| 边界条件 | 空值、超时、并发、异常输入 | 代码只覆盖正常路径 |
| 格式规范 | 是否严格遵守要求的输出格式 | 标题层级混乱、JSON结构错误 |
| 用户约束 | 是否遗漏任务描述里的隐性约束 | 要求500字,产出800字 |
每个维度对应具体的检查点,Critic的输出就从“感觉哪里不对”变成“可定位、可修改”。这本质上就是把模糊的“质量”拆成可操作的检查清单。
4.3 反思轮数控制在2-3轮,多改无用
我自己的经验是,两到三轮是效果和成本的最佳平衡点,第四轮开始边际收益急剧下降,而且经常出现“把之前改对的又改错了”的现象。
有一回我测一个文案生成任务,把max_rounds开到5。结果第三轮分数91分,第四轮Critic硬是挑出一个“风格一致性”问题,让模型重写,第五轮的版本比第三轮明显更生硬。从那之后我把默认阈值设为85分、上限设为3轮,效果稳定很多。
这里给大家一个可参考的配比:简单任务一轮反思就够;中等等复杂度任务两轮;高复杂任务最多三轮,且第三轮必须以“不降低上一版质量”为前提来改。
4.4 生成和评审要敢于用不同Temperature
反思循环里如果两阶段用同一套参数,相当于“用同一副眼镜看自己和别人的作品”。我在实践中的配比是:生成阶段Temperature设0.7,让模型有发挥空间;评审阶段Temperature设0.2,让审查意见更稳定、更严格。
这里有一个小技巧值得分享:如果用的模型支持Seed参数,评审阶段最好把Seed固定下来。这样同一份产出,多次评审结果不会差异太大,方便调试Prompt。否则你会发现,你调了半天评审Prompt,实际只是遇到了随机波动。
4.5 历史上下文要精简,别把无关内容一并塞入
反思循环跑多轮之后,最容易出现的是上下文膨胀。每一轮都把完整历史对话丢进去,三轮跑完,上下文中全是重复内容,不仅浪费Token,还会干扰模型注意力。我见过一个真实案例,Agent在第三轮反思时反而开始模仿第一轮的错误写法,原因就是历史里的初稿信息量太足,把评审意见和重写要求给“淹没”了。
我现在的要求是:重写Prompt里只保留上一版产出、当前评审意见、原始任务这三样东西,CRITIC阶段的Prompt只保留原始任务和待评审产出,多余的对话历史一律不传递。别小看这个约束,它能让成本直降三分之一,还能让重写质量更稳定。
5. 实操中踩过的坑与排查思路
5.1 反思变成了“表扬大会”,评审意见形同虚设
现象:Critic每个维度都给了高分,问题列表空着或者只有一条无关痛痒的“建议增加注释”。查下来原因多半是评审Prompt没有强制找茬。
解决方式:除了在Prompt里写死“必须找出至少两个实质性问题”,还可以加一条兜底规则——如果评审意见里“严重程度为high”的问题数量为零,则强制总分上限为60分。这样Critic就不能靠“全给好话”糊弄过关,分数卡控也会给到它压力。
5.2 越改越差,第二版比第一版还难用
现象:初版虽然有瑕疵,整体框架是对的。结果第二版在评审意见的引导下,把原本没问题的段落也改了,最后出来一个“考虑过度”的版本。
这种情况我排查出来的原因通常是两个。一是重写Prompt里没有把“保留上一版优点”写进去,模型以为改就是要推翻重来。二是评审意见本身的方向不对,Critic抓的问题本身就是错的,生成模型又太听话,把对的按错的改了。
解决办法是给重写Prompt里加一句明确约束:只针对评审意见列出的问题进行修改,未涉及的部分保持原样。同时在循环里加一个“历史最佳”记忆机制——每次评审结束后,记录当前最高分及对应版本,所有轮次跑完直接返回分数最高的那一版,而不是最后一版。
5.3 来回震荡,两个版本互相否定死循环
现象:第一轮生成A版,评审说有问题改成B版;第二轮评审又说B版不如A版,改成A版;第三轮又回到最初的方案。来回空转,白白烧Token。
这类问题我在双角色互评里遇到得比较多,原因是生成者和评审者对某些问题的判断标准不一致,或者评审Prompt里设置的“评分维度”太主观。解决手段就是上面提到的“历史最佳存档”加“轮数上限”。只要控制在三到五轮内,最差结果也就是退回首轮版本,不会再空转下去。
5.4 成本直接翻倍,老板问为什么API账单涨了这么多
这是反思模式绕不开的话题。一个完整循环,一次任务就产生了几次模型调用,成本是单轮生成的三到五倍。如果所有Agent无差别开启反思,账单会非常感人。
我现在的成本控制策略是三级:第一级,简单任务不走反思;第二级,中等任务开一轮Self-Refine,选便宜的小模型当Critic;第三级,只有代码生成、技术方案评审这类高价值任务,才开完整的双角色互评。另外,可以在Critic阶段先做一次“是否需要反思”的预判——模型先判断这个问题有没有必要进入反思循环,明显简单的直接单轮输出。这个预判本身也是一次轻量模型调用,但价格极低,整体算下来能省不少。
5.5 反思模式不是万能的,别什么任务都往上套
最后说一个心态层面的坑。反思模式能提升质量,但它解决的是“输出质量不稳定”的问题,解决不了“模型本身就不具备某方面能力”的问题。如果模型完全不懂某个领域的知识,你让它反思一百遍也编不出正确答案,最多是把错误表达得更流畅。
另外一个要注意的是,对时效性要求极高的场景,反思模式也确实不适合。多一轮调用意味着多几百毫秒甚至几秒的延迟。用户问“现在几点”,你让他反思两轮再回答,体验就太差了。
我目前在项目里的默认配置是:核心产出路径上,必开一层反思;外围非关键路径上,能不开就不开。同时,每隔一段时间抽查反思意见的质量,如果Critic开始说空话,就说明它的Prompt需要重新调一调了。
反思模式带给我的最大价值,其实是把Agent从一个“生成工具”变成了一套“有质检的生产链路”。写代码时有了一双挑剔的眼睛盯着,出方案时有人专门揪着边界条件不放,这种感觉就像团队里多了一个不爱说话但非常严格的评审同事。一开始你会觉得多了一道流程,跑顺之后你会觉得,没这道流程反而不踏实。