从“能跑”到“能救”生产级 AI Agent 的可观测性到底怎么建这两年我经手了不少 AI Agent 项目从早期的 Demo 验证到后来的线上正式环境有一个感受越来越强烈Agent 应用在开发环境里跑得再欢一旦上了生产就等于把一个“会自己瞎琢磨”的黑盒扔进了用户流量里。我遇到过最典型的一次事故是这样的某个客服助手在晚上 8 点突然开始对用户重复追问同一句话日志里只有一行“Agent finished”既不知道它经历了哪几轮推理也不知道它调用了哪个工具拿到了什么结果更不知道它为什么觉得还需要追问。我对着终端屏幕和这个黑盒大眼瞪小眼完全没有下手的地方。那次之后我下定决心把 Agent 的可观测性当作和功能开发同等重要的事情来做。这篇文章就聊聊我的思路和落地经验希望能让你少踩几个坑。1. AI Agent 为什么比传统服务更难观测在讨论怎么“观测”之前得先搞清楚一个核心问题为什么用传统那套日志、监控、链路追踪的手段来对付 AI Agent总有一种隔靴搔痒的感觉1.1 传统可观测性的“三板斧”为什么失灵传统后端服务的可观测性核心是三件套日志Logging、指标Metrics、链路追踪Tracing。这套组合拳在对付常规接口服务时非常有效因为请求路径是基本确定的用户来了过网关进服务查数据库返回结果。每一条路径几乎都是静态的没有太多“意外”会发生。但 AI Agent 不一样。它不是“处理请求”而是“完成任务”。在这个过程中它会自己规划步骤、选择工具、根据中间结果调整下一步动作。这就导致了一条请求链路里的路径数量是爆炸性的同一个问题Agent 今天可能先查知识库再调 API明天可能觉得直接调 API 更快后天可能因为某个工具返回了异常结果干脆放弃 A 方案转去执行 B 方案。这种不确定性的直接后果是你很难用“固定路径的耗时”去定义“正常”与“异常”。传统监控里如果接口 P95 响应时间超过 3 秒就是告警但 Agent 调用同一个工具可能这次 200ms 返回下次因为需要重试或思考更长直接花了 10 秒可它最终还是成功完成了任务。你能说它是异常吗这本质上是一种“非确定性执行”而传统观测工具的底层假设恰恰是“确定性路径”。1.2 Agent 的“黑盒”体现在哪几个层面我在实践中总结AI Agent 的黑盒感主要来自三个层面。第一个层面是意图黑盒。用户说了一句话Agent 内部把这个自然语言转化成一个或多个“意图”这个转化过程即使你能拿到大模型的原始输出也很难直观解释为什么它选择了这个意图而不是另一个。尤其是在多轮对话中上下文一旦累积模型的意图识别结果会受前文影响经常出现“过度解读”或“忽略关键信息”的情况。第二个层面是推理黑盒。Agent 的核心是 ReAct 模式下的“思考-行动-观察”循环。它的“思考”是一段自然语言里面可能包含了对任务的理解、计划的拆解甚至是对结果的预判。这些“思考”决定了后续的每一个动作但是在大模型推理的时候这些内容往往是一闪而过的。传统的日志记录只能看到它最终调了什么工具看不到它为什么决定调这个工具。没有这个过程排查性能问题和逻辑错误就完全靠猜。第三个层面是外部依赖黑盒。Agent 严重依赖工具调用和外部知识库检索。工具返回的数据格式是否正确、检索结果是否命中关键信息这些都会直接影响最终输出质量但传统的 Tracing 只能告诉你“这个 HTTP 调用耗时多久、返回了多少字节”根本无法解析出“检索结果里没有包含用户问的关键实体”这种语义层面的失败原因。1.3 生产环境里黑盒问题的放大效应在开发环境和测试环境里Agent 的黑盒问题往往被掩盖了。一方面流量小你可以在调试器里逐步看每一次模型调用另一方面测试人员通常会准备比较标准的测试用例期望值相对明确。但一上生产问题立刻被放大。第一是流量冲击真实用户的提问千奇百怪模型输出的不稳定被几何级放大第二是成本压力模型推理和外部工具调用都是真金白银黑盒状态下根本分不清哪笔费用是合理的、哪笔是 Agent 在“空转”时烧掉的第三是安全风险没有全链路的追踪如果 Agent 因为错误的工具调用返回了敏感信息或者在某个环节被注入了恶意指令连事后追溯的证据链都没有。我见过一个团队上线了 Agent 后一直相安无事直到某天用户投诉“AI 把我信用卡的账单金额记错了”。他们翻遍日志只找到了模型返回的最终文本完全找不到这个金额是从哪个数据库查出来、经过什么计算逻辑算出来的。最后只能人工模拟用户路径花了整整一天才定位到是参数传递时的字段错位。这种“找证据”的过程就是黑盒最真实的写照。2. 建立全链路可观测性的顶层设计思路既然传统三板斧不够用那我们到底要怎么建一套适合 Agent 的可观测性体系我的核心思路概括成一句话把 Agent 的每一次“思考”和“行动”都变成可检索、可回放、可度量的数据事件。2.1 明确观测对象不止是技术指标更是决策过程在设计可观测性体系之前首先要问自己我到底要观测什么如果只盯着 CPU、内存、延迟这些传统指标你最多能发现“Agent 变慢了”但无法回答“Agent 为什么变慢”。所以我将观测对象分成了四个层次执行层工具调用的耗时、成功/失败状态、Token 消耗、模型延迟。这是最接近传统监控的层次。决策层Agent 在每一步的“推理理由”也就是它为什么选择这个工具、为什么认为当前步骤可以结束。这是 AI 应用独有的观测维度。状态层Agent 的当前内存状态比如对话历史的长度、已收集到的关键信息字段、上下文窗口的占用率。体验层最终回答的质量包括是否触发安全策略、是否拒绝回答、用户是否对回答点了“踩”。只有在这四个层次上都建立数据采集才算得上“全链路”。否则你看到的永远只是冰山一角。2.2 技术选型自研还是基于 OpenTelemetry 扩展关于技术栈的选择我的建议是在 OpenTelemetryOTel的基础上做深度定制而不是从零自研。OTel 提供了一套标准化的事件模型和采集管道社区里也有很多现成的 Exporter 可以对接主流监控后端。但标准 OTel 主要针对的是 HTTP 请求、数据库调用这一类场景对于 Agent 的“推理轨迹”“Token 曝光事件”这些自定义数据类型需要利用它的 Attribute 和 Event 机制进行扩展。我见过一些团队嫌 OTel 太重想自己写一套 JSON 日志打印到文件里。早期确实轻快但等规模上来后面临的问题非常现实日志格式不统一导致查询极其痛苦缺乏标准的 Trace ID 传播机制导致无法串起整条链路甚至不同服务之间的时区都统一不了。当初省下的那点开发时间后面十倍百倍地还了回去。更推荐的做法是在 Agent 的框架代码里内嵌一个可观测性 SDK把关键事件以结构化数据的形式发送到统一的 Collector。这个 SDK 不需要自己造轮子可以直接基于 OTel 的 SDK 扩展预留出自定义事件类型的接口。2.3 追踪Trace粒度怎么定从“一次调用”细化为“一个步骤”在传统链路追踪里一个 Trace 通常对应一次外部请求。但在 Agent 场景里这个粒度太粗了。一个用户请求可能会触发 Agent 内部五轮思考、三次工具调用、一次知识库检索如果只把整条链路由一个 Trace ID 串起来那么在排查问题时你依然无法定位到是哪一个环节出了问题。我的做法是采用“两级 Trace 结构”请求级 TraceRoot Span代表用户的一次完整交互从接收输入到返回最终结果。它承载了请求 ID、用户 ID、会话 ID 和整体耗时。步骤级 SpanChild Span在 Agent 的每一次“思考-行动-观察”循环中单独生成一个 Span。每个 Span 记录本次循环的输入前一轮的观察结果、模型输出Action 内容、工具调用情况、新增的观察结果。这样设计之后排查问题时就可以先看请求级 Trace 的耗时分布快速锁定是哪个大阶段出了问题然后下钻到具体的步骤级 Span看那一步的模型输入输出和工具返回效率会提升非常多。3. 核心实现搭建生产级 Agent 可观测性体系说完了顶层设计接下来聊聊具体的落地实现。我尽量把关键环节的操作步骤和数据模型拆开讲清楚方便你直接参考落地。3.1 第一步统一的事件数据模型可观测性的地基是数据模型。没有统一的模型后面所有分析和可视化都是空中楼阁。我给每一条可观测事件定义了一个统一的结构包含几个核心字段字段名类型说明event_idstring事件唯一标识UUIDtrace_idstring请求级链路 ID贯穿整个交互过程span_idstring步骤级 Span IDevent_typestring事件类型如 think / action / observe / tool_call / safety_triggertimestampint64事件发生的时间戳统一用毫秒精度agent_idstringAgent 实例标识便于按版本聚合session_idstring会话标识input_datajson本步骤的输入数据output_datajson本步骤的输出/决策数据metajson附加元信息如 token 用量、模型名称、延迟等这个模型统一了 Agent 内部所有关键节点的数据格式。后续如果要接 BI 分析、告警规则、可视化大盘都从这个模型出发非常省事。注意event_id 和 span_id 必须完全唯一不能复用。否则在做链路拼接时会出现数据串线的诡异问题排查起来极其痛苦。3.2 第二步从“埋点”到“事件采集”的关键实现有了数据模型之后真正的工作在于在代码的哪些位置插入采集逻辑我在实践中总结了一套“关键埋点清单”基本覆盖了 Agent 运行的每一个重要节点入口事件用户请求到达时记录一次包含原始输入文本、用户标识、会话上下文长度。每次模型推理事件记录发送给模型的 Prompt或者至少是经过脱敏后的关键部分、模型返回的内容、Token 用量、推理耗时。每次工具调用事件记录工具名称、调用参数、返回值截断后的摘要、调用耗时、错误信息。状态变化事件记录每次思考循环结束后Agent 内部维护的状态字段比如已收集的关键信息、已完成的子任务变化。安全事件如果触发了安全过滤策略记录触发原因、命中的策略规则、被拦截的内容。结束事件最终输出内容、整体耗时、最终 Token 总消耗。埋点时容易犯的一个错是“只顾着集成点不顾数据量”。模型返回的完整 JSON 可能非常大如果全量存储一两个星期就能把磁盘打满而且查询性能也会严重退化。我的处理策略是重要数据全量存冗长数据存摘要模型日志分层存储。具体来说工具调用参数等结构化信息保留完整 JSON大模型返回的长文本只会保留前 500 个字符作为预览同时把全量结果写入对象存储或者单独的日志系统按 trace_id 建立索引需要做问题定位的时候再按需拉取。3.3 第三步把“思考过程”变成可搜索的数据Agent 的思考过程是自然语言如果不做处理它就只能以文本形式存在日志里无法参与结构化查询和统计分析。我采用的方案是对每一条 think 事件做一次轻量级的语义标签抽取。具体来说利用一个规则引擎加一个小的分类模型把思考内容打上标签比如“用户意图识别”“计划拆解”“工具结果分析”“错误修正”“停止条件判断”。这样在做大盘分析时就能统计出“Agent 在哪个环节耗时最多”“哪类思考最容易导致重复循环”。当然如果前期资源有限这一步也可以简化为纯规则匹配比如根据思考文本里的关键词“I need to”“Let me check”“I will use”来打标签效果虽然粗糙一些但足够支撑 90% 的定位需求。3.4 第四步链路追踪的完整串联有了事件有了 Span接下来最关键的一步是把它们串成一条完整的链路。我的做法是在自定义 SDK 里维护一个TracerContext对象每次 Agent 进入新的思考-行动循环时生成一个新的子 Span并绑定当前上下文的 trace_id 和父 span_id。这样所有事件自然构成一棵多叉树。这里有一个非常容易被忽略的坑模型调用和工具调用通常发生在异步任务里。如果你用的是 Python Asyncio 或者 Java 的 CompletableFuture在异步切换时如果不显式传递 trace 上下文会导致子 Span 丢失父 Span 信息链路直接断掉。最稳妥的做法是把 trace_id、parent_span_id 封装进一个 ContextVarPython或 ThreadLocalJava里在创建异步任务时显式拷贝并传递而不是依赖隐式继承。3.5 第五步可视化大盘与告警体系数据采集上来之后最后一步是把数据变成能看、能用的产物。我搭的可观测性大盘包含三个核心视图链路追踪视图支持按 trace_id 精确查询也支持按 session_id 聚合查看某个用户的所有历史交互链路。决策分析视图按思考标签聚合统计不同类型思考的耗时占比、频率分布、Token 消耗趋势快速定位 Agent 在哪类任务上“卡壳”。质量监控视图统计安全事件触发次数、工具调用失败率、用户对回答的负面反馈率从用户感知角度反向监控 Agent 的健康度。告警规则我建设了三个梯队。第一梯队是“基础可用性”比如工具调用失败率超过 50%、模型调用超时率上升、请求级 Trace 数量骤降可能是入口服务挂了第二梯队是“性能劣化”比如 P95 Token 消耗环比上涨 30%、平均思考步数超过设定阈值第三梯队是“质量劣化”比如安全事件触发率突增、用户负面反馈率上升。4. 从黑盒到全链路一次线上问题排查实录理论说了那么多我们来一个真实案例复盘。这是我之前经历过的一次线上问题用户反馈 Agent 回答问题的速度突然变得很慢平时 3 秒就能出结果最近经常要 20 秒以上。换作以前这种问题基本靠猜。有了全链路可观测性体系之后排查路径就清晰多了。4.1 问题发现与初步定位收到告警后我第一时间打开链路追踪视图拉取了最近一个小时的请求级 Trace。按照平均耗时倒序排列发现慢请求有一个共同特征它们的步骤级 Span 数量比其他请求多出不少平均有 12 个步骤而正常请求只需要 5 到 6 个步骤。这里就体现出了“步骤级 Span”的价值。如果是传统思路我只能看到“接口耗时 20 秒”但有了步骤级 Span能立刻定位到“耗时主要来源于步骤变多”而不是某一次模型调用变慢。4.2 下钻定位问题根因接下来点开几个慢 Trace 的步骤详情逐个检查每一步的“思考”标签和“工具调用”记录。我发现这些请求里Agent 在进行工具调用后返回的结果都被判定为“未命中”于是它又重新规划、重新尝试其他工具循环往复。进一步检查工具调用事件的返回摘要发现关键信息字段全是 null。再去查上游知识库服务的日志发现知识库那边最近发布了一个新版本某个字段的命名从user_name变成了name但 Agent 的查询逻辑还在用旧字段名导致检索结果永远是空的。这就是一个典型的“外部依赖黑盒”问题。没有全链路追踪可能需要排查一整天而且很难快速确定问题是出在 Agent 推理环节还是工具调用环节。有了结构化数据的支撑我花了不到十分钟就定位到了根因。4.3 修复与复盘定位到问题后修复其实很简单把字段名对齐即可。但更重要的价值在于复盘时留下的数据资产我在可观测性平台上回放了完整的异常链路确认了 Agent 在连续失败后没有触发“放弃策略”而是一直重试这显然是一个需要优化的系统行为。于是我们后续做了一个改进在 Agent 的规划模块中增加了一个“连续失败次数”的计数器一旦超过阈值就触发终止流程返回“暂时无法解答请稍后再试”而不是继续空转。这个改进上线后最直接的收益就是 Token 成本下降了不少用户体验也没有因为偶尔的“我没查到”而变差。5. 生产环境落地的常见问题与避坑技巧最后把我在实践中踩过的一些坑和关键技巧整理出来供大家参考。5.1 埋点代码污染业务逻辑怎么办这是很多团队在建设可观测性时最抵触的问题SDK 的埋点代码散落在业务代码里既丑陋又容易出错。我的建议是采用装饰器或中间件模式把埋点逻辑和业务逻辑解耦。比如在 Python 中定义一个trace_agent_step装饰器自动完成上下文传播和事件上报在 Java 里可以做一个 Agent 框架的拦截器在框架层统一埋点。这样业务代码只需要关注自身逻辑可观测性的“脏活”全部收敛在基础设施层。5.2 数据量太大、存储成本飙升怎么办Agent 可观测性数据是出了名的“重”——每个推理步骤都有模型输入输出Token 用量数据也不轻。如果不加控制成本会非常吓人。我的应对策略是分级存储热数据7 天内存储在 ClickHouse 或 ES 里支持快速查询和联排分析。温数据30 天内只保留摘要和关键标签冗长的模型输出丢到对象存储。冷数据超过 30 天只保留统计聚合结果比如每天每个 Agent 的平均耗时、平均 Token 消耗、工具调用成功率等原始数据定期清理。另外所有事件在采集端就做一次采样。对于 Debug 级别的高频事件如细粒度的 Token 计数只记录 10% 的流量而 Error 级别的事件必须全量采集。这套采样策略能在保证覆盖率的同时把成本控制在一个合理范围。5.3 非确定性导致告警频繁误报怎么处理Agent 的输出天然具有随机性同样的输入这次 P95 耗时是 3 秒下次可能变成 8 秒。如果用固定的阈值告警很容易被误报淹没最后团队就会变成“狼来了”状态对告警麻木。我采用的方案是动态基线告警。不设置固定阈值而是基于过去 7 天的同期数据计算时间序列的置信区间只有当当前指标偏离历史基线超过两倍标准差时才触发告警。这套机制上线后误报率明显下降。5.4 本地调试和生产排障怎么共享同一套工具最后分享一个体验上的小技巧。我们团队在开发环境也启用了同一套可观测性 SDK只是将上报目标指向了本地开发事件的数据库。这样一来开发者在本地调试 Agent 时看到的链路视图和线上排障时用的是同一套工具、同一个界面减少了很多“开发时说开发话、排障时说排障话”的认知割裂。而且这套开发的链路数据积累下来之后还能用来做回归测试把历史 Trace 里的输入用例重新跑一遍对比新的推理路径和工具调用序列能发现不少 Agent 改版后“悄悄变笨”的问题。6. 未来演进从可观测性到可预测性写到这里可观测性体系的基本框架已经完整。但我想再说一点个人体会纯属经验之谈。可观测性的终极目标不是让系统出错时更容易定位而是让错误更少发生。当你的 Agent 行为数据积累到一定量级之后你会发现你现在做的已经不只是“事后追责”而是在为“事前预测”打基础。比如你可以基于历史 Trace 数据训练一个分类器在 Agent 开始执行之前就预测“这次任务有 70% 的概率会失败”然后提前切换策略或降级处理。又或者你可以分析哪些输入类型的 Token 消耗特别高主动做输入压缩从源头上降低成本和延迟。这些能力听起来有点超前但它们的根基都是这套全链路的数据采集。可观测性体系建设本质上是在为 Agent 积累“行为记忆”。越早开始建记忆就越丰富后续的优化和预防也越有据可依。就我个人经验而言可观测性不是一个“做完就一劳永逸”的项目而是会随着 Agent 的迭代持续演进的工程方向。建议刚开始建设时不要贪大求全先把 Trace 串联起来把关键事件的数据格式定好把最基本的三个大盘搭出来再逐步叠加语义分析、动态告警和预测性能力。每一步都能立刻产生价值这样团队才会越用越有信心。
