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(keylambda 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_id1234 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 可以调但如果在反馈回路上偷了懒换什么模型都只是给一条坏链子配好引擎。先修路再换车顺序不能反。