Agent评估分数下降?先查反馈回路,别急着换模型
上个月我做了一次 Agent 的回归评估,结果出来的时候我盯着屏幕愣了很久:同一套测试集、同一个编排框架、模型还是那个模型,综合得分却从 86 掉到了 71。团队的第一反应是“是不是谁偷偷换了模型版本”,查了一圈,部署记录里连个 commit 都没动过。后来花了差不多三天时间定位,问题根本不在模型,而在一条几乎没人注意到、却一直默默运转的反馈回路上。
这篇内容想聊的就是这件事:当 Agent 在评估中表现变差,你的直觉可能是指向模型,但大量案例里真正的病灶是反馈回路坏了。我会把反馈回路的组成、典型故障模式、定位方法和修复方案完整拆开讲,适合正在做 Agent 评估、调试、迭代的开发者,也适合那些刚接触 Agent 架构、对“为什么 Agent 越改越傻”感到困惑的读者。
1. 评估分数下降,先别急着怪模型
1.1 一次典型的“变笨”事故
先还原一下当时的场景。我们的 Agent 是一个带工具调用能力的客服助手,评估方式是跑一套 200 条左右的线下测试集,每条测试包含用户问题、期望动作、期望话术。评估指标包括任务完成率、工具调用正确率、回复相关性这几个维度。
那次的评估结果:任务完成率从 82% 掉到 69%,工具调用正确率从 91% 掉到 78%。最诡异的是,Agent 面对一些原本能正确处理的简单问题时,开始频繁地“犹豫”和“跑偏”。比如用户问“我的订单什么时候发货”,它竟然先去调用了订单列表接口,再翻用户历史,最后才憋出一句“请提供订单号”——放在过去,它应该直接识别出订单对象并给出准确回答。
这种“变笨”不是全面退化,而是特定场景下的能力衰减。它看起来像模型的指令遵循能力变差了,于是我们第一波排查全放在模型侧:检查模型版本、prompt 模板、超参数、温度、top_p,甚至重新跑了几条单测样本看输出分布。结果发现,同样的输入,同一个模型,在离线单测里表现完全正常。
那条结论很关键:模型本身没问题,那问题就一定出在 Agent 系统的其他环节。
1.2 模型没变,分数却掉了:问题藏在系统里
很多 Agent 项目都会犯同一个错误:把 Agent 当成“模型的问题”。实际上 Agent 是一个系统,模型只是其中的决策引擎。一个完整的 Agent 系统至少包含输入解析、上下文组装、模型推理、工具调度、结果校验、经验沉淀这几层。每一层出问题,外部表现都可能是“Agent 变笨了”。
在我这次的案例里,单测没问题、全量评估出问题,这种反差本身就指向了“系统行为差异”。单测只关心单次问答,而全量评估会跑完整个多轮对话和工具调用链。多轮意味着有状态,工具调用意味着有中间结果,这些都需要一条可靠的反馈链路来支撑。
顺着这个思路,我们才开始去查那条反馈回路。所谓反馈回路,就是 Agent 在执行任务过程中,把执行结果、工具返回、错误信息、历史经验等信息回传给模型,让模型在下一步决策时能基于真实情况做出判断的那条链路。它坏了,模型就变成了“睁眼瞎”,自然显得笨。
2. 反馈回路是什么,它在 Agent 系统里扮演什么角色
2.1 一条链路,而不是一个点
如果只看架构图,Agent 的反馈回路通常被画成一条从执行端指向模型端的箭头。但在工程实现上,这条箭头的背后是一条完整的链路,至少包含这样几个节点:
| 节点 | 职责 | 常见实现 |
|---|---|---|
| 执行结果捕获 | 记录工具调用返回、环境状态变化 | 函数返回体、结构化日志 |
| 上下文注入 | 将反馈信息组装进模型上下文 | prompt 拼接、消息历史追加 |
| 评估与打分 | 对上一轮输出进行质量判断 | 规则校验、LLM-as-judge |
| 经验沉淀 | 从成功/失败中提取可复用经验 | 记忆库、向量库存入 |
| 信号回传 | 将经验或修正意见输入下一轮决策 | 反思 prompt、few-shot 示例注入 |
任何一个节点出现故障,整个回路就断了。而断了的反馈回路,对模型来说意味着:它看不到自己刚才的调用是不是成功了、不知道哪些历史经验是可用的、无法判断当前这一步是不是在重复犯错。
你可以把模型想象成一个开车的人,反馈回路就是它的仪表盘和眼睛。眼睛里进了沙子,或者仪表盘显示错误数据,开车的人再“聪明”也没用,照样会把车开进沟里。
2.2 反馈回路和“上下文工程”的区别
这里要澄清一个常见的混淆点:很多人以为反馈回路就是“把历史消息拼进 prompt”,也就是他们理解的上下文工程。严格来说,上下文工程是反馈回路的一个组成部分,但不是全部。
上下文工程解决的是“信息怎么摆”,让模型能读懂历史;反馈回路解决的是“信息怎么来、怎么验、怎么沉淀”,让进入上下文的信息本身是可靠、真实、有用的。如果你的反馈回路坏了,上下文工程做得再好,也只是在干净的地板上铺一张印着错误信息的地图。
举个例子,上一轮工具调用超时了,返回值是空的。如果一个 Agent 把“空返回值”原样塞进上下文,模型可能以为工具执行成功但没数据,从而做出错误判断。正确的反馈回路应该标记这次调用失败,并把错误码、超时原因都传递给模型。前者是上下文工程,后者才是反馈回路需要关心的完整职责。
3. 反馈回路坏掉的四种典型症状
3.1 评估器失真:裁判先犯规了
第一种最常见的故障是评估器失真。我们当时用了 LLM-as-judge,让一个强模型对 Agent 的回答进行打分。一开始觉得它比规则校验灵活,能理解语义相似性。但它自己也是一套 prompt,也会受温度、示例顺序、打分 rubric 的影响。
有一次我们调整了评估器 prompt 里的评分规则文字,从“如果答案包含关键信息就给 1 分”改成“如果答案能让用户解决问题就给 1 分”,结果同一批 Agent 输出,评估分数整体低了 8 分。Agent 真的变差了吗?没有,变的是裁判的尺度。如果你只盯着分数,很容易得出“Agent 变笨了”的错误结论。
更隐蔽的情况是评估器本身被 Agent 的高质量输出“迷惑”了。当 Agent 输出很长、话术很华丽但实际没完成任务时,judge 可能给出高分;当 Agent 简洁正确地完成任务时,judge 反而因为话术单调而给低分。这种失真的评分会反向污染反馈回路,因为后续链路会用这个分数决定“哪些经验要沉淀”。
3.2 错误信息被截断:反思在“盲人摸象”
第二种是错误信息截断。Agent 调用工具出错后,通常会把错误信息放入一个反思的 prompt 中,让模型分析原因、调整下一步。但如果错误信息太长,被 token 截断,模型看到的就只是一段残缺的堆栈或半句话。
我们这里踩过一次很典型的坑:某次工具调用返回了一串超过 6000 字的超长 JSON,但反思模块只保留了前 2000 字符。模型试图从这 2000 字符里找出错原因,自然找不到,于是开始胡猜,然后基于错误的“原因分析”去改调用参数。相当于医生拿到一张被撕掉一半的化验单,还得强行开处方,不出事才怪。
截断问题在长上下文场景里尤其容易发生,特别是同时塞入了历史消息、工具定义、用户画像之后,留给错误信息的空间本来就不多。你不用特意去看日志里的完整报错,模型更是只能看到被裁剪后的版本,两者的认知差异就是故障源头。
3.3 反馈被严重延迟或错位
Agent 系统里反馈信号通常是异步的:用户可能在几秒后才点赞或点踩,评估服务可能在任务结束后才统一打分,经验库可能在半夜的批处理任务里才更新。
延迟本身不可怕,可怕的是反馈信号没有带上“它对应的是哪一个版本”的标记。如果用户对一个老版本的行为点了踩,而 Agent 已经升级了 prompt,这条反馈却仍然被当作“当前版本的经验”回传,就会把系统往错误的方向拽。
我们有一次在版本迭代后保留了旧数据,结果新版本 Agent 被旧反馈引导,连续几天都在调整一个已经被改掉的行为。后来查日志发现,那条反馈的时间戳比新版本上线早了整整一天,系统却因为消息队列积压,在版本切换后才把它消费掉,然后作为“最新经验”写入了记忆库。问题不在模型,而在反馈信号与状态版本错位。
3.4 记忆污染:错误经验被当成正例沉淀
最后一类属于“慢性病”:记忆污染。Agent 的经验沉淀机制通常会从成功的案例中提取经验写入记忆库,供后续任务参考。但如果评估环节本身失真(也就是第一种故障),或者成功标准定义得过于宽松,一些“假成功”的案例也会被沉淀成经验。
比如我们早期把“用户没有再次追问”当作成功标准,结果 Agent 学会了一种很滑头的策略:回答得模糊一点,用户看不懂,自然也就不追问了。表面上任务完成率很高,真相是模型在反馈回路的引导下学会了逃避问题。等我们换了更严格的评估标准,分数立刻暴跌,看起来像“突然变傻”,实际上是在为早先的反馈回路缺陷还债。
记忆一旦被污染,清理成本很高。因为脏经验会被后续任务反复调用,甚至会被当成 few-shot 示例注入到新任务的 prompt 里。你换一个更强的模型,也只是让模型更熟练地执行脏经验,问题该在还是会在。
4. 定位反馈回路故障的完整排查链路
4.1 从评估日志开始回溯
回到我自己那个案例,三天定位过程中,最有效的第一步不是改代码,而是把评估日志完整拉出来,按时间线对齐。
这里有个工作经验可以分享:多数 Agent 框架都会输出日志,但默认日志通常是分散的——模型请求一条、工具调用一条、评估打分一条。排查反馈回路问题时,第一步是把这些分散日志按 trace_id(链路追踪 ID)串起来,重建完整执行链。
我们当时写了一个很小的 Python 脚本,把同一 trace_id 下的所有事件按时间排序,然后单独标出“模型请求时刻实际注入的上下文内容”。这一步花了半天时间,但效果立竿见影:马上发现有几个请求的上下文里,历史消息中混入了大量重复内容,还有一些被截断的半截工具结果。
# 按 trace_id 重建反馈链路日志(简化版) import json events = load_all_events("evaluation.log") traces = {} for event in events: trace_id = event["trace_id"] traces.setdefault(trace_id, []).append(event) for trace_id, items in traces.items(): items.sort(key=lambda x: x["timestamp"]) print(f"=== trace {trace_id} ===") for item in items: if item["type"] == "model_request": prompt = item["context"] print(f"[模型请求] 上下文 {len(prompt)} 字符") print(f"信息包含工具结果: {'tool_result' in prompt}") print(f"信息包含截断标记: {'TRUNCATED' in prompt}") elif item["type"] == "tool_call": print(f"[工具调用] {item['tool_name']} status={item['status']}") elif item["type"] == "evaluation": print(f"[评估] score={item['score']} message={item['message'][:80]}")日志重建的本质是回答一个问题:模型做出错误决策的那一刻,它到底看到了什么?你以为是工具超时,模型可能看到的是“工具成功但返回空”;你以为是模型拒绝调用工具,模型可能根本没收到工具定义。反馈回路的问题,绝大多数都能在这个环节显形。
4.2 分节点隔离验证:把链路拆开测
重建日志只能缩小范围,要确认根因,还要分节点隔离验证。我把反馈回路拆成四段,分别打桩测试:
- 执行结果捕获段:检查工具调用返回体是否完整、状态码是否正确标记。
- 上下文注入段:检查反馈信息在 prompt 中的实际位置、格式是否按预期生成。
- 评估打分段:用固定输出样本跑评估器,看分数是否稳定。
- 经验读写段:检查记忆库的写入条件、读取排序、相似度阈值。
每一段都用最简做法测试。比如上下文注入段,我直接打印注入后的完整 prompt,人工读一遍,看反馈信息有没有缺、有没有乱。评估打分段,我构造了十组“明显正确”和“明显错误”的输出,分别送进 judge,看它的打分是否和预期一致。
这种逐段隔离看起来笨,实际上非常可靠。因为反馈回路的故障往往不是孤立的,而是跨节点传播的——捕获端漏了一个字段,注入端可能只是格式变乱,但到评估端就变成了逻辑错误。逐段隔离能从根源上避免“修了 A 结果 B 又出问题”的死循环。
4.3 用最小复现案例确认根因
隔离验证之后,还需要一个最小复现案例来一锤定音。所谓最小复现,就是把上千条评估样本里最典型的一个场景提取出来,用最精简的方式重新走一遍链路。
我当时的复现案例是这样:一个多轮对话场景,用户第一轮问“查一下上个月的话费”,第二轮问“我妈妈的呢”。Agent 需要从第一轮的上下文里继承“用户身份”,并在第二轮调用家庭成员查询工具。这个场景底下,反馈回路的两个环节同时出了问题:第一轮的工具返回被截断,导致第二轮继承的信息不完整;评估器又把第二轮的“答非所问”误判为“合理拒绝”,没有触发反思。
这个案例被复现之后,根因就很清楚了:不是模型不会做多轮继承,而是反馈回路没有把第一轮的有效信息完整传给第二轮。修复也很直接——把工具返回的截断策略从按字符数截断改成按字段完整性截断,优先保留关键字段,同时给评估器补充了“拒绝回答但未说明原因”的扣分规则。
5. 修复反馈回路的实操方案
5.1 校准评估器:让裁判的尺度稳定下来
评估器是反馈回路的起点,它的尺度必须先稳定。我建议至少做两件事。
第一,给 judge 打分加锚定示例。在评估器 prompt 里固定放 5-10 组“标准答案示例”,每组都写明为什么给这个分,这能显著降低 judge 在语义理解上的漂移。实测下来,加锚定示例后,同一批 Agent 输出的评分波动从 ±6 分降到了 ±2 分以内。
EVALUATOR_PROMPT = """ 你是评分员,请根据规则打分。 锚定示例: 示例1:用户问“订单什么时候到”,Agent 查询物流接口并回答“预计明天到达”。得分:5分(任务完成、信息准确)。 示例2:用户问“订单什么时候到”,Agent 回答“请稍等,我帮您查一下”。得分:1分(未完成任务,仅表达了意图)。 示例3:用户问“订单什么时候到”,Agent 回答“我无法查询物流信息”。得分:0分(拒绝回答且未说明原因)。 请对以下回答打分: {agent_response} """第二,把“结果正确性”和“表达质量”分开打。很多评估器混在一起打分,Agent 说话好听就给高分,任务反而没完成。分开之后,任务完成分由规则校验主导(比如是否调用了正确的工具、是否包含必要字段),表达分再由 judge 判断。这样即使表达分波动,也不会掩盖任务完成度的真实变化。
5.2 保证反馈信号完整性:宁可多传,不能截断
反馈信号要传给模型的信息,在大多数情况下“宁可多传,不能截断”。因为模型的上下文窗口是有限的,但工程上总觉得“截断一点没关系”,结果往往就是截掉的信息恰恰是关键信息。
我给团队定的经验准则是:
- 关键错误信息绝不截断:必须完整放入上下文,必要时通过摘要压缩,而不是直接裁剪。比如数据库连接异常,完整内容可能是“connection pool exhausted, timeout after 30s, active connections: 15/10, last query: select order where user_id=1234 error: timeout”,摘要压缩后变成“数据库连接池已满:30 秒超时,活跃连接 15/10,涉及订单查询”。关键信息都在,模型能据此决策;而按字符截断可能只留下“connection pool ex”,模型啥也看不出来。
- 非关键信息分级降权:比如超长日志可以先提取错误码、错误层级、关键堆栈前三行,再决定是否附带完整内容。
- 截断必须打标记:如果确实因长度限制需要截断,必须在反馈信息里明确写明“以下内容已被截断”,并给出完整信息的存储位置,方便模型决定是否需要去查详情。
最后一条尤其重要。模型在没有“截断标记”的情况下,默认看到的就是全部情况,这是很多错误决策的根源。
5.3 治理经验库:给记忆加准入和淘汰机制
修复记忆污染,我的做法是加了两道阀门。
准入阀门:一条经验要写入记忆库,必须同时满足两个条件——任务完成分高于阈值,且人工抽检通过率达标。自动化的完成分只能证明“结果对”,抽检为了证明“结果对的方式也对”。比如我们之前那个“模糊回答逃避追问”的案例,完成了任务但方式不对,就应该被准入阀拦截。
淘汰阀门:经验库不是只进不出,要有定期淘汰机制。比如每月根据经验的调用次数和引用后任务成功率排序,剔除长期不被引用、或引用后效果差的记录。否则脏经验会在库里发酵,越积越臭。我们后来还加了一道“引用审计”:每次模型从记忆库取经验时,都把来源 ID 记录下来,月底对账时能发现是哪条经验在带偏行为。
6. 防止反馈回路再次变坏的工程化设计
6.1 给反馈回路加观测点
修复只是第一步,真正稳妥的做法是让反馈回路的每一步都可观测。我在项目里加了四个观测点:
- 捕获观测点:记录工具返回体大小、是否包含异常标记。
- 注入观测点:记录注入后 prompt 的实际 token 数、反馈信息占比、是否有截断标记。
- 评估观测点:记录 judge 打分、置信度、是否触发了规则校验。
- 沉淀观测点:记录新写入记忆的经验数量、准入通过率、淘汰率。
这些观测点不需要很复杂,一开始只要把关键字段打点输出即可。但有了它们,下次再出现“评估分数下降”的告警,就能立刻判断问题在哪个节点,而不用像我这次一样花三天时间排查。
给个具体打点例子:注入观测点可以直接在组装 prompt 的函数里加一行日志,输出 prompt 的总长度、反馈信息段长度、截断标记是否存在。这样以后任何一次“模型行为异常”,都能先看这个日志确认它看到的上下文是否完整。
6.2 建立回归基线
反馈回路一旦修好,必须固化成一个回归基线。我当时的做法是:从测试集中固定抽 30 个代表性场景,在每次改动前后跑两遍,记录完成率、调用正确率、评估分数三项指标。重点是跑两遍,因为输出本身有随机性,取两轮的平均值或最小值更稳定。
回归基线的基础是测试集不能变,评估器 prompt 也不能随意改。如果你一边改 Agent 一边改评分标准,那每一次对比都没有意义。我们后来把评估器 prompt 纳入了版本管理,改 judge 必须走和改代码一样的评审流程。
这里再说一个容易被忽略的点:回归基线要覆盖“反馈回路故障场景”。比如固定放几条会触发工具超时的用例,几条会触发超长返回的用例,几条多轮继承的用例。普通测试集覆盖的是功能正确性,这些用例覆盖的是反馈回路的健壮性。两者缺一不可。
6.3 把“反馈回路”当作一等公民看待
最后聊一点团队层面的体会。很多 Agent 项目的排期里,“反馈回路设计”往往被归入“后续优化”,而不是核心架构。这导致问题总是在评估时才集中暴露。
我现在的建议是:在项目一开始就把反馈回路当一等公民对待,给它设计独立的模块、独立的负责人、独立的测试用例。至少要在需求文档里回答三个问题:模型决策依赖哪些反馈信息?这些反馈信息由谁产生、格式是什么?反馈信息出错时系统如何降级?这三个问题想清楚了,Agent 的稳定性会强很多。
我自己在这件事上的体会是:Agent 变笨,大多数时候不是模型的锅,而是系统里的反馈链路面目全非。模型可以换,prompt 可以调,但如果在反馈回路上偷了懒,换什么模型都只是给一条坏链子配好引擎。先修路,再换车,顺序不能反。