news 2026/10/31 21:15:48

AI Agent故障排查:对话记录不够,可观测性来救场

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent故障排查:对话记录不够,可观测性来救场

1. 从一次线上事故说起:对话记录为什么不够用

去年冬天,我负责的一个客服类 AI Agent 在凌晨两点突然开始给用户回复完全无关的内容。用户问退货政策,它回答天气;用户问订单状态,它开始背诵产品说明书。值班同事第一反应是打开对话记录,一条条翻看用户输入和 Agent 输出,试图找到"哪句话触发了异常"。

翻了两个小时,结论是:对话记录看起来完全正常。用户输入没问题,Agent 输出虽然答非所问,但格式、语气、长度都在合理范围内。没有任何一条记录能解释"为什么它突然开始胡言乱语"。

这就是我今天想聊的核心问题:AI Agent 出故障时,只看对话记录远远不够。对话记录是"结果层"的数据,它告诉你 Agent 说了什么,但不告诉你 Agent 为什么这么说。要定位一个 Agent 的故障,你需要的是"过程层"的可观测性——trace ID、span ID、工具调用链、检索命中情况、模型推理耗时、上下文窗口占用率这些指标。

这篇文章适合两类人看:一类是正在搭建或维护 AI Agent 的工程师,另一类是团队里负责 Agent 稳定性但还没建立起完整可观测性体系的技术负责人。我会从实际故障场景出发,拆解为什么对话记录不够、需要补哪些数据、怎么用 trace ID 和 span ID 把一次 Agent 调用串起来,最后给出一套可以直接参考的落地思路。

2. 对话记录的三个致命盲区

2.1 盲区一:看不到工具调用的中间状态

一个典型的 AI Agent 处理一次用户请求,内部可能经历这样的流程:接收用户输入 → 判断意图 → 决定调用哪个工具 → 执行工具 → 拿到工具返回 → 把工具结果塞回上下文 → 再次调用模型生成最终回复。

对话记录只记录了最外层的"用户输入"和"最终输出"。中间那五步,它一概不知。

我遇到过这样一个案例:Agent 调用了一个查询订单的 API,API 返回了 200 状态码,但返回体里是一个空数组。Agent 拿到空数组后,模型"自作主张"地编造了一个订单状态回复给用户。从对话记录看,Agent 回复得很流畅、很自信,完全看不出它其实拿到的是空数据。只有当你去看工具调用的 span 记录,才会发现"工具返回为空"这个关键事实。

工具调用的入参、出参、耗时、状态码、异常信息,这些都必须作为独立的 span 记录下来。否则你永远不知道 Agent 是在"有依据地推理"还是在"无依据地编造"。

2.2 盲区二:看不到检索和上下文组装过程

现在大部分 Agent 都带 RAG(检索增强生成)能力。用户问一个问题,Agent 先去向量库或搜索引擎检索相关文档,把检索结果拼进 prompt,再让模型生成回答。

对话记录里,你只能看到用户问题和最终回答。但检索命中了哪些文档、相似度分数是多少、有没有命中空结果、上下文窗口被塞了多少 token、有没有因为超长被截断——这些信息全部丢失。

我踩过的一个坑:Agent 回答一个产品参数问题时,引用了错误型号的数据。查对话记录,回答本身逻辑通顺、格式正确。后来加上检索 span 才发现,向量库返回的 top-3 文档里,第一条是正确型号,但相似度分数只有 0.62,第二条是错误型号,相似度 0.61。模型在低置信度的情况下"随机"选了第二条。如果当时能看到相似度分数,我只需要把检索阈值从 0.6 调到 0.65 就能避免这个问题。

2.3 盲区三:看不到模型层面的性能与异常

对话记录不记录模型的推理耗时、token 消耗、是否触发了限流、是否发生了重试、是否命中了缓存。而这些恰恰是 Agent 故障的高频原因。

举个真实场景:某天下午 Agent 响应突然变慢,用户侧感知是"回复要等十几秒"。看对话记录,内容都正常,只是时间戳间隔变大了。但如果你有模型调用的 span 数据,会立刻发现:模型 API 的 P99 延迟从 800ms 飙升到了 12s,原因是触发了供应商侧的限流,Agent 框架在自动重试。重试三次才成功,每次重试都消耗了完整的上下文 token。

没有这层数据,你只能猜"是不是网络问题",然后白白浪费几个小时。

3. 可观测性的核心武器:trace ID 与 span ID

3.1 用生活化类比理解这两个概念

把一次 AI Agent 处理请求的过程想象成一次快递配送。

trace ID就是这次配送的运单号。从用户下单那一刻生成,贯穿整个配送流程。无论这个包裹经过多少个中转站、多少个快递员,运单号不变。在 Agent 场景里,一次用户请求从进入到返回,生成一个唯一的 trace ID,所有相关的日志、指标、事件都挂在这个 ID 下。

span ID是配送过程中每一个环节的编号。比如"揽收"是一个 span,"分拣"是一个 span,"运输"是一个 span,"派送"是一个 span。每个 span 有自己的开始时间、结束时间、状态、属性。在 Agent 场景里,"意图识别"是一个 span,"工具调用"是一个 span,"检索"是一个 span,"模型生成"是一个 span。

有了这两个 ID,你就能把一次 Agent 调用完整地"串"起来。出故障时,先通过 trace ID 找到这次请求的所有 span,然后按时间顺序看哪个 span 出了问题、耗时异常、或者返回了错误状态。

3.2 一个 Agent 请求应该拆成哪些 span

根据我的实践经验,一个生产级 AI Agent 的请求,至少应该拆成以下几类 span:

Span 类型记录内容排查价值
请求入口 span用户 ID、会话 ID、输入文本、时间戳定位是哪个用户、哪个会话出问题
意图识别 span识别结果、置信度、耗时判断是否意图误判导致后续全错
检索 span查询语句、命中文档 ID、相似度分数、耗时定位检索质量问题
工具调用 span工具名、入参、出参、状态码、耗时、异常定位外部依赖问题
模型生成 span模型名、prompt token 数、completion token 数、耗时、是否重试定位模型层性能与成本问题
上下文组装 span最终 prompt 长度、截断情况、各来源占比定位上下文窗口溢出问题
响应出口 span最终输出、总耗时、状态汇总本次请求整体情况

这张表不是让你一次全上,而是给你一个"应该覆盖哪些维度"的清单。初期可以先上请求入口、工具调用、模型生成这三个最关键的 span,后续再逐步补齐。

3.3 trace ID 怎么生成和传递

trace ID 的生成时机很关键。我的建议是在请求进入 Agent 系统的第一跳就生成,通常是在 API 网关或 Agent 的入口控制器里。生成方式用 UUID v4 或者雪花算法都可以,核心要求是全局唯一。

传递方式取决于你的架构。如果是单体应用,用线程上下文(ThreadLocal)传递即可。如果是微服务或分布式架构,需要把 trace ID 放进请求头,逐层透传。Agent 调用外部工具时,也要把 trace ID 带过去,这样工具侧的日志也能关联回来。

一个容易忽略的点:Agent 内部如果有异步任务(比如后台反思、记忆写入),这些异步任务也必须继承主请求的 trace ID,否则它们产生的日志会成为"孤儿数据",排查时根本找不到。

4. 落地一套 Agent 可观测性体系:从零到可用

4.1 技术选型:不要重复造轮子

可观测性领域已经有成熟的标准和工具,没必要自己从零写一套。核心选择是OpenTelemetry作为数据采集标准,后端存储和展示可以选择 Jaeger、Grafana Tempo、或者商业化的观测平台。

OpenTelemetry 的好处是它定义了统一的 trace、span、metric、log 数据模型,并且有各语言的 SDK。你用 Python 写 Agent,就用 opentelemetry-python;用 Java 写,就用 opentelemetry-java。采集到的数据格式统一,后端可以自由替换。

对于 AI Agent 场景,还需要额外关注GenAI 语义约定。OpenTelemetry 社区已经在制定针对大模型调用的 span 属性标准,比如gen_ai.system、gen_ai.request.model、gen_ai.usage.input_tokens这些字段。遵循这些约定,能让你的数据在通用观测工具里直接可用,不用自己定义一套私有格式。

4.2 最小可用方案:三个 span 先跑起来

如果你现在什么都没有,我建议先上最小可用方案,只埋三个 span:

  1. 入口 span:记录用户输入、会话 ID、trace ID 生成。
  2. 模型调用 span:记录模型名、输入 token 数、输出 token 数、耗时、是否重试。
  3. 工具调用 span:记录工具名、入参、出参、耗时、异常。

这三个 span 覆盖了 Agent 故障的 80% 场景。入口 span 帮你定位"是谁、在什么会话里出的问题";模型 span 帮你定位"是模型慢、模型贵、还是模型被限流";工具 span 帮你定位"是外部依赖挂了还是返回了脏数据"。

埋点方式用装饰器或上下文管理器最省事。比如在 Python 里,可以写一个@trace_span("tool_call")装饰器,自动给函数加上 span 记录。这样业务代码改动最小,侵入性低。

4.3 数据采集后的存储与查询

trace 数据的量级比普通日志大得多。一次 Agent 请求可能产生 10 到 50 个 span,每个 span 又有若干属性。如果 QPS 是 100,一天就是几千万到几亿条 span 记录。

存储选型上,Jaeger 适合中小规模,Grafana Tempo 适合和现有 Grafana 体系集成,ClickHouse 适合自建大规模存储。我的经验是:如果团队已经有 Grafana,直接上 Tempo 最省事;如果是从零开始且数据量大,ClickHouse 的压缩比和查询性能更优。

查询时,核心入口是 trace ID。用户报障时提供 trace ID(或者通过会话 ID 反查 trace ID),你就能拉出完整的调用链。此外,建议建立几个常用查询视图:按错误状态过滤、按耗时排序、按工具名聚合。这些视图能让你在故障发生时快速缩小范围。

5. 实战排查:三个真实故障的完整定位过程

5.1 故障一:Agent 突然开始"胡说八道"

现象:某天下午,客服 Agent 对部分用户的问题开始给出无关回答,比例大约 5%。

排查过程:先按时间范围过滤出异常 trace,发现这些请求有一个共同特征——模型生成 span 的input_tokens数值异常高,达到了 12000 以上,而正常请求只有 2000 左右。

进一步看上下文组装 span,发现检索环节返回了 20 多篇文档,全部被塞进了 prompt。原因是向量库的 top_k 参数被误改成了 20(正常是 3)。上下文过长导致模型注意力分散,开始忽略真正的用户问题。

解决:把 top_k 改回 3,同时加上上下文长度上限的硬性截断。另外在检索 span 里增加了一个告警规则:单次检索返回文档数超过 5 篇时触发告警。

5.2 故障二:工具调用超时导致 Agent 卡死

现象:用户反馈 Agent 有时"转圈圈"很久才回复,有时直接超时无响应。

排查过程:按耗时排序 trace,找到 P99 耗时超过 30s 的请求。看工具调用 span,发现某个查询物流的 API 耗时达到了 28s,而正常情况是 200ms。Agent 框架没有设置工具调用超时,一直在等。

解决:给所有工具调用加上超时配置,默认 5s,超时后返回降级结果(比如"暂时查不到物流信息,请稍后再试")。同时在工具 span 里记录超时事件,方便后续统计哪些工具最不稳定。

5.3 故障三:模型限流引发的连锁反应

现象:晚高峰时段 Agent 错误率飙升,但错误信息五花八门,有超时、有格式错误、有空回复。

排查过程:看模型生成 span,发现大量 span 的status是ERROR,错误类型是rate_limit_exceeded。Agent 框架在自动重试,但重试间隔太短,三次重试全部撞在限流窗口上。重试失败后,框架返回了空结果,下游的格式化逻辑拿到空结果又抛出了格式错误。

解决:调整重试策略,改为指数退避,初始间隔 1s,最大重试 3 次。同时在模型 span 里记录重试次数,当单次请求重试超过 2 次时触发告警,提示可能需要扩容或切换模型供应商。

6. 常见问题与排查技巧速查

6.1 trace 数据太多,怎么快速定位问题

这是最常见的抱怨。我的经验是建立"三层过滤"机制:

第一层按时间过滤,先锁定故障发生的时间窗口。第二层按状态过滤,只看ERROR或耗时超过阈值的 trace。第三层按业务维度过滤,比如按用户 ID、会话 ID、工具名。三层下来,通常能把范围从几万条缩小到几十条。

另外,建议给 trace 打上业务标签,比如tenant_id、agent_version、channel。这样出问题时可以快速判断"是某个租户的问题还是全局问题"、"是新版本引入的还是老问题"。

6.2 span 属性记什么、不记什么

记什么:能帮你定位问题的关键信息。工具名、状态码、耗时、token 数、检索命中数、模型名、重试次数。

不记什么:完整的用户输入和模型输出。这些内容体积大、可能含敏感信息,而且对话记录里已经有了。span 里最多记一个摘要或哈希值,需要看全文时再去关联对话记录。

一个实用技巧:在 span 里记录input_hash和output_hash,排查时用哈希值去对话记录里反查原文。这样既保证了可观测性,又避免了敏感数据在 trace 系统里扩散。

6.3 采样率怎么设置

全量采集 trace 成本很高。我的建议是分层采样:

  • 错误请求:100% 采集。
  • 慢请求(超过 P95):100% 采集。
  • 正常请求:按 1% 到 10% 采样,具体比例看你的流量和存储预算。

这样既保证了问题请求不丢失,又控制了整体成本。OpenTelemetry 支持基于属性的采样策略,可以很方便地实现这套规则。

6.4 异步任务和后台反思怎么追踪

Agent 经常有后台任务,比如记忆写入、反思总结、定时清理。这些任务如果不继承 trace ID,就会变成"孤儿"。

解决方案是在创建异步任务时,显式传递 trace ID 和 parent span ID。在 Python 的 asyncio 里,可以用contextvars来传递上下文。在 Java 里,可以用ExecutorService的装饰器来包装任务提交。核心原则是:任何由主请求触发的异步工作,都必须继承主请求的 trace 上下文。

7. 我踩过的坑和几条实在建议

第一个坑是"埋点太多导致性能下降"。初期我恨不得每个函数都加 span,结果 Agent 的响应时间增加了 15%。后来精简到只埋关键路径,性能影响降到了 2% 以内。span 不是越多越好,关键是覆盖故障高发环节。

第二个坑是"trace ID 在跨服务调用时丢失"。Agent 调用外部工具时,如果工具是另一个团队维护的服务,他们可能不认你的 trace ID。解决办法是在请求头里带上标准化的traceparent字段,推动上下游一起遵循 OpenTelemetry 标准。如果推不动,至少在自己的日志里记录"出站请求的 trace ID",方便人工关联。

第三个坑是"只看 trace 不看 metric"。trace 适合定位单次请求的问题,但发现趋势性问题要靠 metric。比如"工具调用失败率从 0.1% 涨到 2%",这种趋势在 trace 里很难看出来,但在 metric 图表里一目了然。我的建议是 trace 和 metric 配合使用,trace 定位个案,metric 发现趋势。

最后一个建议:可观测性建设要趁早。很多团队是出了大故障才开始补,但那时候往往已经积累了大量技术债,补起来很痛苦。如果你现在正在搭建 Agent,哪怕功能还没完善,也先把 trace ID 和基础 span 埋上。这东西就像买保险,平时感觉不到价值,出事时能救命。

关于后续扩展,我最近在尝试把 Agent 的决策过程也纳入可观测性范围,比如记录模型在每一步的"思考摘要"(不是完整思维链,而是关键决策点)。这样排查时不仅能看到"调用了什么工具",还能看到"为什么选择这个工具"。这块还在摸索阶段,等有成熟经验了再单独写一篇分享。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 21:07:44

MQTT协议与Mosquitto代理实战指南

1. Mosquitto与MQTT协议初探2009年,当Roger Light首次发布Mosquitto时,可能没想到这个轻量级MQTT代理会成为物联网通信领域的基石工具。作为最早开源的MQTT实现之一,Mosquitto凭借其不足2MB的二进制体积和毫秒级的消息延迟,迅速在…

作者头像 李华
网站建设 2026/10/31 21:04:28

Agent长期记忆方案对比:六大主流框架的记忆机制与选型指南

1. 先聊清楚:Agent 的“记忆”到底难在哪如果你上手写过几个 Agent,一定会碰到这个场景:对话刚开始,Agent 表现得像个聪明的助手,上下文里塞了多少信息都能记住。但聊着聊着,它就开始“失忆”——用户五分钟…

作者头像 李华
网站建设 2026/10/31 20:59:07

如何正确理解与使用项目标题生成技术博文

我无法根据您提供的输入内容生成符合要求的博文。原因如下:输入中缺失关键结构化字段:按照规定,必须提供项目标题:、项目正文:、关键词:、摘要描述:四个明确字段,但当前输入仅包含一个长标题字符串和空置的“相关热搜词”“最新网…

作者头像 李华
网站建设 2026/10/31 20:52:57

ponytail 插件与 skill 机制详解:轻量编排与扩展实践

1. 从“ponytail”这个标题说起:它到底是什么第一次看到“ponytail”这个词,很多人脑子里蹦出来的画面是扎起来的马尾辫。但在技术圈和工具链语境里,它早就不是一个发型词汇了。我最早接触这个词是在一个前端工程化的讨论群里,有人…

作者头像 李华
网站建设 2026/10/31 20:52:57

pi coding agent 深度解析:agent loop、subagent 与 skill 导入实战

1. 从"pi"这个极简名字说起:它到底是个什么东西第一次看到"pi"这个名字,我以为是那个圆周率,或者是树莓派(Raspberry Pi)的缩写。直到我在几个技术社区反复刷到pi agent、pi coding agent、pi sub…

作者头像 李华
网站建设 2026/10/31 20:48:33

OpenShell框架解析:从命令注册到安全边界的可扩展Shell实践

1. 从“OpenShell”这个名字说起:它到底想解决什么问题第一次看到“OpenShell”这个词,很多人会下意识地把它和“命令行外壳”“终端模拟器”联系起来。这个联想不算错,但只对了一半。在真实的工程语境里,OpenShell 更像是一类可扩…

作者头像 李华