Agent 进生产后,别再盯着日志找错了
一个 Agent 在线上跑了两周,成功率看着有 99%。直到业务同学把一串真实会话扔过来,里面有一条用户说「帮我把昨天那个订单再处理一下」,Agent 调了查询工具,找到了三笔候选订单,挑了一笔退款,最后还很自信地回复「已经处理完成」。
日志没有报错。
链路追踪也很完整。
可用户要的根本不是退款,系统还产生了一个本不该有的副作用。
这就是很多 Agent 团队正在撞上的墙。传统服务的线上观测,核心是在问服务活着吗,接口慢不慢,有没有异常。Agent 的麻烦在于,它可以在所有基础设施指标都正常的时候,把一件事做得完全不对,而且通常还会说得很像对的。
Google 这周开源的 AQuA,也就是 Ambient Quality Agent,想处理的正是这个空档。它按计划从 Cloud Trace、Cloud Logging 或 BigQuery 抽取生产会话,经过抽样、评审、聚类、验证和跟踪,去识别真实流量里反复出现的 Agent 失败模式。
我对这件事的兴趣,不在于又多了一个 LLM judge,而在于它把质量检查的位置挪对了。不是把十几条固定样例跑到绿灯就宣布上线,而是承认真实会话本身才是最残酷的测试集。
对于在 Java、Spring、LangGraph 这类栈里做 Agent 的团队,这个思路比一套具体框架更值得拿走。
线上绿灯,不等于任务完成
很多团队的 Agent 监控,仍然是 Web 服务监控的自然延伸。
请求量、P95 延迟、5xx 比例、模型调用失败率、token 消耗。这些当然都得有。没有它们,你连系统是不是在工作都不知道。但它们只回答了一个很窄的问题,运行时有没有明显故障。
业务关心的却是另一回事。
用户的问题有没有被正确理解。工具选择是否合适。写操作有没有越界。回复里那些听起来很完整的承诺,是否和数据库里的终态一致。一次重试究竟是自愈,还是把同一张工单创建了两遍。
这几个问题都不能靠 HTTP 200 回答。
拿一个常见的 Spring Boot 订单助手举例。你可以把查询订单、创建售后单、退款申请包装成三个 Tool。一次 LangGraph 执行完成以后,服务端会留下模型输入输出、工具调用日志和 trace。若只看技术成功率,这次任务大概率已经通过了。
可质量检查至少还要过三关。
第一关是意图对齐。用户说「昨天那个订单」,这是一种依赖上下文的指代。模型找到了候选对象,不代表选对了对象。
第二关是执行正确。Tool 返回 200,不等于操作符合业务规则。退款金额、订单状态、幂等键、审批条件,都可能藏着错。
第三关是沟通真实。Agent 不能把「已提交申请」说成「退款已到账」,也不能把工具超时伪装成操作完成。
真正难的错误,往往不在某一个节点爆炸,而是每个节点都给出了看上去合理的局部答案。传统告警很难发现这种错误,因为系统并没有坏,它只是把业务做歪了。
质量数据不能只来自你的测试集
离线 eval 很重要,我不是在否定它。
上线前没有固定样例、回归集和安全用例,基本等于蒙眼开车。尤其是涉及写库、支付、权限变更的 Agent,测试环境里必须先把边界跑透。
问题在于,离线集天然会变旧。
产品一改 prompt,用户换一种说法,接入一个新 Tool,或者某个 CRM 字段开始出现空值,线上分布就变了。你在测试集中精心构造的「查询 2026 年 3 月订单」很好判。真实用户会说「上周买咖啡机那单是不是还在路上」,并且前面刚问过另外一台设备。那才是 Agent 最容易失足的地方。
AQuA 的做法值得借鉴,先从生产会话里做抽样,再让评审器对会话进行判断,之后把相似问题聚成簇。它不要求人从海量 trace 里靠关键词捞事故,而是试图把一堆零散的失败,翻译成可以排优先级的问题类别。
这一步看着朴素,实际很关键。
如果每周只收到二十条模糊反馈,比如「它又理解错了」「有时候会乱答」,团队会陷入一种很糟糕的状态。每次都修一个 prompt,每次都像修偶发 bug。两个月后,prompt 变成了一面贴满创可贴的墙,没人知道哪一条规则还在保护什么。
聚类能把反馈变成更可执行的句子。
例如,客服 Agent 在「同一用户存在多个未完成售后单」时经常选错订单。又例如,知识库检索在文档版本切换后的两天内持续引用旧政策。再例如,数据库写入成功但下游事件投递失败时,Agent 会过早宣布任务完成。
这三类问题的责任人、修复方式和风险都不同。第一类可能要补实体消歧和确认步骤。第二类是索引更新与版本过滤。第三类则需要把最终回复放到事务或补偿机制之后。
这才叫质量闭环。不是收集更多日志,而是把日志变成下一次工程决策的输入。
别把 LLM judge 当成自动真相机
这里很容易走偏。
一看到「让模型评审线上会话」,不少人会马上想到全自动打分。给一个 judge prompt,规定满分五分,定时跑完看 dashboard。这样做能快速起步,但不能直接承担发布闸门。
原因很简单,judge 也会误判,而且它尤其擅长被流畅的语言骗过去。
一个 Agent 写了一段非常像样的解释,judge 可能给高分。可数据库里那条工单是否真的创建,退款状态是否真的迁移,外部系统是否收到了 webhook,这些事实不能由另一个模型凭语气来裁决。
我更倾向于把评估拆成两种证据。
事实证据 来自确定性系统。工具参数、返回码、数据库终态、事件投递记录、审批轨迹、权限审计日志。这些东西应该由代码或规则校验。比如退款 Agent 的评测,不要只问 judge「回复是否有帮助」,还要检查 refund_request 是否存在、金额是否匹配、状态机是否允许这次迁移、同一幂等键是否只产生了一条记录。
语义证据 才交给模型。用户意图是否被读懂,回复有没有遗漏限制条件,检索结果是否足以支撑结论,Agent 是否在信息不足时不该自作主张。这些没有一个可靠的 if 语句,LLM judge 正好能提供规模化的近似判断。
两种证据混在一起,最后谁也不可信。
有些团队会说,这样成本太高。确实,生产会话不是每一条都值得经过多模型评审。我的建议是分层采样。
低风险的纯问答,可以按流量比例随机抽样。涉及写操作、低置信度、重试次数异常、工具链路过长、用户点击负反馈的会话,应该提高抽样权重。碰到支付、删除、权限变更这类高风险行为,甚至可以全量保留结构化审计记录,是否实时评审另说。
你不是在追求把一切看得一清二楚。你是在用有限预算,尽早看到最可能伤人的错误。
一条能落地到 Spring 和 LangGraph 的质量链
把这个思路落到现有服务里,不需要为了 AQuA 或任何新框架重写 Agent。先补一份能评估的会话事实,再建一条异步质量流水线,通常更现实。
先定义一次任务的证据包
每次 Agent run 结束时,除了最终文本,至少存下这些字段。
runId、用户或租户的脱敏标识、Agent 和 prompt 版本- 输入摘要与检索文档版本,不保存不必要的原始隐私数据
- 每次模型调用的模型名、耗时、token、停止原因
- Tool 名、参数摘要、返回状态、错误类型、幂等键
- 关键业务终态,例如工单号、订单状态、审批状态、事件 ID
- 最终回复、人工接管标记、用户反馈信号
这份证据包最好在一次请求结束时就结构化写出。Java 项目里可以在 Agent orchestration 外围放一个 RunRecorder,用 Spring 的 OncePerRequestFilter 只做请求关联,再由业务服务显式上报 Tool 结果。别试图从散落的文本日志里反向解析全部事实,那个坑会越填越深。
LangGraph 的节点边界也很适合做这件事。每个节点除了更新 state,还写一份不可变的 event。模型推理、检索、Tool 调用、人工确认和最终响应都有独立类型。这样后面做追踪、重放和评测时,不必猜一句日志究竟代表计划还是已经执行。
一条独立成段的建议,把「计划调用」和「已执行」当成两种事件。
不少事故就出在这里。模型说「我将创建工单」,系统把它记录成成功。或者 Tool 发起请求后超时,界面已经把文案展示给用户。事件语义分不清,后面的质量判断一定会失真。
抽样时别只按百分比
纯随机抽样适合看整体趋势,却很容易漏掉少数高风险流程。一个更实用的规则可以是风险分层。
| 会话特征 | 建议抽样 | 原因 |
|---|---|---|
| 纯知识问答且无负反馈 | 低比例随机 | 用于观察基础回答质量 |
| 多次检索或上下文窗口接近上限 | 提高比例 | 容易发生引用漂移和信息截断 |
| Tool 重试、超时或 fallback | 高比例 | 最终回复可能掩盖中间失败 |
| 创建、退款、删除、权限变更 | 全量留痕,重点复核 | 有真实业务副作用 |
| 用户点击无用或转人工 | 高比例 | 这是最便宜的线上失败信号 |
这里的「全量留痕」不等于把用户对话原封不动倒进分析库。生产环境里,脱敏和访问控制要前置。把身份证、手机号、地址、密钥片段塞给 judge,再用一句「内部使用」安慰自己,挺危险的。
比较稳妥的做法,是让质量流水线只读取必要字段。比如 judge 看到的是订单状态、商品类别、时间范围和工具结果摘要,而不是完整订单详情。需要人工复盘时,再通过权限受控的 runId 回到原始系统查询。
评审要给出可复核的理由
如果 judge 只返回一个 0 到 1 的分数,过一周就没人知道该怎么修。评审输出应该尽量结构化,至少包含失败标签、证据位置、置信度和建议进入哪个队列。
例如可以让它输出下面这样的对象。
1 | { |
注意,这只是审查意见,不是事实本身。evidence 里的每一条都应该能回链到前面存下来的事件,或者被确定性规则验证。评审器找不到依据时,应该允许它输出「不确定」,而不是强行制造一个看起来专业的判词。
坦率的讲,能承认不确定的质量系统,才比较适合处理不确定的 Agent。
聚类不是为了做一张漂亮图
失败标签积累到一定量后,才值得聚类。目标不是在 dashboard 上画出五个彩色气泡,而是要回答三个很现实的问题。
哪一类错误影响的用户最多。哪一类错误的单次损失最大。哪一类错误修一次能减少最多重复劳动。
可以先按 agentVersion + toolChain + failureLabel 做规则聚合,再把同组会话交给模型总结共性。不要一上来就对几万条原始对话做 embedding 聚类。那样很容易得到「用户表达不清」「模型回答不佳」这种谁都无法行动的结论。
好的错误簇应该像一张 bug ticket。
它要能说清触发条件、影响范围、代表性 run、预期行为和当前行为。比如「在 order-search-v3 中,当检索返回多个相同商品的订单且用户未提供日期时,Agent 在未发起澄清的情况下调用退款申请。过去 7 天有 18 例,均发生在 mobile 渠道。」
工程师拿到这个,能写测试。产品拿到这个,能决定是否增加确认 UI。业务拿到这个,也知道风险在哪里。
修复以后,旧错必须能被关掉
很多质量体系走到「发现问题」就停了。看板上错误很多,会议里也讨论过,三周后同一批错误还在。原因是问题和变更之间没有一条可追踪的线。
AQuA 提到的跟踪环节,我觉得应该成为每个生产 Agent 的基本动作。
每一个错误簇创建一个 issue 或质量卡片,关联代表性 run。修复时,把它们转成两类回归样例。
一类是确定性测试。给定同样的订单状态和 Tool 返回,系统必须要求澄清,或者不得发起写操作。
另一类是语义测试。给定类似但不完全相同的表达,Agent 仍然能识别模糊指代,正确解释自己的边界。
修复上线以后,继续观察同类标签的发生率。下降不等于彻底解决,因为流量结构可能正好变了。最好同时保留对照指标,例如每一千次高风险会话中的错误簇数量、人工接管率、被撤销操作数。这样才能分辨「用户少了」和「系统好了」。
别小看这一步。它把 prompt 调优从一种手感活,变成了有证据的工程迭代。
先从一条高价值链路开始
如果你的团队现在还没有任何 Agent eval 基础,不要试图一周内建成「全量智能质检平台」。这种项目很容易先把数据管道、权限申请和 dashboard 做得很完整,最后没人真的看它。
挑一条同时满足三个条件的链路就够了。
它有清晰的业务终态。它有真实用户量。它的错误代价不是零,但也不会因为第一版评审不完美就造成不可承受的风险。
例如,内部 IT 工单分流、知识库问答的引用准确性、售后申请的预检,都比直接从自动退款或生产发布开始更合适。
第一周,补齐 run 证据包和脱敏规则,确认每个 Tool 调用都能和业务终态关联。
第二周,建立一组二三十条人工看过的会话,先校准 judge 的标签和边界。不要急着相信第一次分数,先看它和人工意见差在哪里。
第三周,对高风险和负反馈会话做抽样,把结果按可行动的失败类型汇总。找一个最集中的错误簇,写回归用例,修一次。
第四周,比较修复前后的同类错误率,并把仍然解释不清的样例交给人复核。
这条路不酷,甚至有点像传统质量工程。但 Agent 进入业务系统之后,真正稀缺的从来不是又一个能聊天的界面,而是你敢不敢让它碰真实流程时,仍然能看见它何时跑偏。
AQuA 给出的信号很清楚,线上 Agent 的可观测性不能止于「它调用了什么」。下一层该问的是「它完成了什么,它伤到了谁,它为什么会反复这样」。
当你能稳定回答这三个问题,日志才不只是事故发生后的废墟,而会变成下一次把 Agent 做对的材料。
参考资料
- Google Developers Blog,AQuA 开源公告,AI HOT 摘要链接 https://aihot.news/items/col84h6b3pdq383v6iuju3a9v
- Google Developers Blog,AQuA 相关摘要称其可从 Cloud Trace、Cloud Logging 或 BigQuery 抽取生产会话,并执行抽样、评审、聚类、验证和跟踪流程




