生产环境下的Agent可观测性:从黑盒到全链路追踪实践
2026/9/6 14:01:22 网站建设 项目流程

1. 为什么说 Agent 的"黑盒"问题,比传统服务更棘手

做后端的人应该都有过这种经历:服务挂了,登录监控面板,看几条日志、查一下链路追踪,几分钟内就能定位到是哪台机器、哪个接口、哪条 SQL 出了问题。这套玩法在传统分布式系统里已经非常成熟,但到了 AI Agent 这里,整套方法论一下子就失灵了。

我最初接触 Agent 的时候,以为它就是一个封装了 LLM 调用的服务,顶多多几个 API 调用而已。真正把它部署到生产环境、面对真实用户流量之后,才发现完全不是一回事。Agent 不是"一个"请求处理单元,它是一套自主决策系统——它自己规划步骤、自己选工具、自己判断下一步干什么,甚至会在多轮工具调用之间维护一套内部状态。整个过程对开发者来说,就像在看一个装了轮子的黑盒子:给它一个输入,它给你一个输出,但中间它到底绕了哪些路、撞了几次墙、浪费了多少 Token,你一概不知。

举一个我实际踩过的例子。当时我们上线了一个客服问答 Agent,跑了两周,陆续有用户反馈"回答变慢了"。从监控面板上看,服务的 P99 延迟从 1.2 秒涨到了 8 秒,但 CPU、内存、下游接口的耗时指标全部都正常。我们排查了整整一个下午,最后通过把 Agent 每一步的轨迹都打出来才找到原因:它在处理某些问题时,会反复调用一个搜索工具,搜索结果不理想就换个关键词再搜,最多的一次循环了 14 轮,光工具调用的时间就占掉了 75%。这种问题,在传统服务里根本不存在——传统服务的依赖关系是静态的、可预先分析的,而 Agent 的依赖关系是运行时动态决定的,完全不可预判。

更麻烦的是正确性问题。传统服务的正确性判断非常明确:状态码对不对、返回结构对不对、数据是不是符合预期。Agent 没有这么清晰的判定边界——它可能正常返回了一段"看起来没问题"的文字,但实际内容是错的、是模型幻觉出来的,甚至是基于一个错误工具返回拼凑出来的。这种错误在传统监控体系里是零感知的。

所以在生产环境做 Agent,我最大的体会是:传统可观测性解决的是"系统哪里坏了",Agent 可观测性要解决的是"系统做了什么、为什么这么做、做得对不对"。前者是故障定位,后者是行为审计,这是两个维度的事。这篇文章要聊的,就是怎么把后者落地,从一层层黑盒里把 Agent 的决策过程、执行轨迹、Token 消耗、上下文状态全部捞出来,建成一套能支撑生产环境的全链路观测体系。

2. 给 Agent 插上仪表盘:可观测性的数据模型怎么设计

聊 Agent 可观测性之前,得先统一一个共识:我们到底要观测什么东西?如果沿用传统监控的思路——只采集 CPU、内存、QPS、延迟——那是远远不够的,Agent 的核心运行产物是"决策"和"行为",这些才是需要被观测的核心对象。

以我实际搭建的观测体系为例,我把 Agent 运行时产生的可观测数据分成四个层次,每个层次解决一类问题。

2.1 基础资源层:监控 Agent 的"身体指标"

这一层是传统监控的老本行,Agent 跑在容器里,底层资源消耗必须要盯:CPU 使用率、内存占用、GPU 显存占用(如果本地跑模型)、网络 IO、磁盘 IO。这些指标能回答"Agent 服务本身健康吗",但回答不了"Agent 在干什么"。

在传统 Web 服务里,CPU 飙高通常意味着有热点代码或死循环;但在 Agent 场景里,CPU 飙高的原因可能是模型推理量大、可能是一次性处理了超长上下文、也可能是某个工具做了密集计算。所以这一层只能作为兜底告警,不能作为问题定位依据。

2.2 调用链层:还原 Agent 的"思考轨迹"

这是 Agent 可观测性和传统可观测性最核心的差异点,也是工程量最大的部分。它要回答的是"Agent 一次完整任务是怎么一步步走完的"——包括它调用了哪些 LLM、传了什么系统提示词、用户输入是什么、模型输出了什么、它决定调用哪个工具、工具返回了啥、它基于工具的返回又做了哪些决策……整个过程像电影一样逐帧回放。

我强烈建议把所有 Agent 框架都带有的内部回调机制统一接入到 OpenTelemetry 的 Span 模型里,每执行一个步骤就产生一个 Span,Span 之间用父子关系串起来。这样最终在 Jaeger 或 Grafana Tempo 里看到的,就是一整棵完整的调用树。树上每个节点都标清楚这是什么类型的操作、耗时多长、消耗了多少 Token、输入输出是什么,一眼就能看出整个链路里最耗时的瓶颈在哪。

2.3 语义事件层:记录 Agent 的"关键决定"

调用链能还原轨迹,但有一些东西用 Span 记录并不合适——比如 Agent 在规划阶段生成的完整计划、它在自我纠错时对之前结论的修正、它从工具结果中提炼的关键信息。这些事件更适合以结构化日志的形式,按时间顺序单独记录一份,方便后续做语义层面的检索和分析。

我把这类事件统称为"决策事件流"。它们的特点是:不构成树形结构,更像是时间线;独立于单次请求存在,后续做数据集分析、行为回放、效果评估时都离不开它们。比如我记录 Agent 每次重试的原因分类(工具报错、结果不符合预期、上下文超限),连续统计几周之后,就能绘制出"Agent 在哪些场景下容易反复挣扎"的分布图,这对优化 prompt 和工具选择极其有价值。

2.4 业务指标层:衡量 Agent 的"产出质量"

业务指标层回答的是"Agent 做得好不好",这一层是很多团队最容易忽略的。传统监控局限在系统层面,而 Agent 的价值必须用业务结果来衡量:任务完成率、一次成功率、平均每任务工具调用次数、平均用户反馈评分、误拒绝率、误答率、上下文平均长度……

这些指标在最开始可能比较难定义,但一定要在体系设计之初就预留埋点。我见过太多团队先做基础监控,三个月后才想起来要统计业务指标,结果发现关键数据没采集,历史行为无从追溯,白费了功夫。

这四层数据层层递进、互相补充,共同构成了一套完整的 Agent 可观测性数据模型。基础资源层看"身体",调用链层看"动作",事件层看"决策",业务指标层看"成果"。

3. 全链路打通:从用户请求到 LLM 调用的追踪落地

数据模型想清楚了,接下来就是怎么把理论变成工程实现。这一节我会完整讲一遍我在生产环境里搭 Agent 全链路追踪的流程,包括数据采集、上下文传递和最终展示三个环节。

3.1 用 OpenTelemetry 标准化 Agent 追踪数据结构

Agent 追踪最大的难点在于:一个任务会产生多次 LLM 调用,多次工具调用,而且这些调用之间不是单纯的串行关系。为了完整描述这种复杂交互,我在 OTel 语义约定的基础上做了一套定制化的 Span 结构。

设计上我是这样处理的——每一条用户请求进来时,先创建一个根 Span,名字叫agent.request,标记上用户 ID、会话 ID、请求的原始内容。然后在这个根 Span 之下:

  • 每次与 LLM 的交互(包括第一次主调、中间的多轮工具结果回传、最后的汇总生成),各创建一个llm.call子 Span,记录模型名称、温度参数、请求和响应的 Token 数。
  • 每次工具执行(包含代码执行、API 调用、数据库查询),各创建一个tool.call子 Span,记录工具名称、入参、出参、执行耗时、错误信息。
  • Agent 框架自身的规划、反思、总结等阶段,各创建一个agent.step子 Span,记录阶段类型、阶段描述、耗时。

这三个子类型的概念是借鉴了一篇关于 Agent 全链路追踪的方法论文章,实际用下来会发现它跟 OTel 生态接得很顺,尤其是和 LLM 相关的语义约定(gen_ai.openai.api_basegen_ai.usage.prompt_tokens等)完全兼容,不需要额外改造。Span 的树形结构最终长这样:

agent.request (根) ├── agent.step (规划) │ └── llm.call (生成计划) ├── agent.step (执行第一步) │ └── tool.call (搜索工具) │ └── llm.call (解析工具结果) ├── agent.step (执行第二步) │ └── tool.call (代码解释器) │ └── llm.call (判断是否需要重试) └── agent.step (生成最终回答) └── llm.call (汇总生成)

值得注意的一个细节是:多个并行的工具调用不要简单塞成平级子 Span,要给它们创建一个虚拟的agent.step父 Span,用来聚合"这一批并行调用"的整体耗时。这样在追踪视图里你能一眼看出 Agent 在等什么,而不会被一堆毫无层级的平铺 Span 搞晕。

3.2 打通上下文传递:让每个 Span 都有完整的"因果链"

数据格式定好了,接下来是链路追踪里最让人头疼的部分——上下文传递。在传统微服务里,我们用 HTTP Header 传 trace ID 和 parent span ID 就能串起整条链路。Agent 不一样,它内部经历了 Python 函数调用、异步任务切换、外部 API 请求、可能还有消息队列的投递和消费,任何一个环节断了,链路就会分裂成好几截。

我先说几个最容易断连的场景,都是我在实际代码里踩过的坑:

第一个是异步任务。Agent 框架为了性能基本都会用 asyncio,但 Python 的异步上下文管理器和 OTel 的 context propagation 配合良莠不齐,很多 Agent 框架内部的 AsyncTask 并不会自动继承 trace context,子任务拿不到父任务的X-Trace-ID,链路在异步边界就断了。解决办法是必须在任务创建的地方显式otel.set_span_in_context(parent_span),把上下文对象传进去。

第二个是线程池。Python 的concurrent.futures.ThreadPoolExecutor默认不会自动拷贝 contextvar,而 OTel 的 context 恰恰是存在 contextvar 里的。结果就是主线程创建的 Span 在子线程里完全不可见。处理方式是写一个包装函数,在提交任务时手动拷贝 context。

第三个是LLM 提供商的回调。很多 LLM SDK(比如早期的 LangChain、LlamaIndex)会内部发起 HTTP 请求,但请求头里不一定带我们塞的 trace ID,得手动把包改成注入 headers。更隐蔽的是,有些模型提供商的 SDK 会在自己的线程池里做重试,重试请求会丢掉上下文。

这些问题没有一个能靠 OTel 开箱即用地解决,每个都要针对框架源码做适配。我给的建议是:把上下文传递当成 Agent 框架集成的一部分来做,而不是事后补救。你在选型 Agent 框架的时候,先验证它对 asyncio + 线程池 + 外部 SDK 三重场景下的 context 传播是否完整,否则后面补链路的成本会非常高。

3.3 数据展示:让研发和业务都能看懂追踪图

采集链路数据只是第一步,数据要能被团队真正用起来,展示层必须做得够直观。

我的做法是:把追踪数据分两套展示。一套是技术视角的 Jaeger,给研发看,重点展示完整的 Span 树、耗时分布、Token 消耗、错误堆栈;另一套是业务视角的自研仪表盘(一个简单的 Web 页面),给产品和运营看,重点展示"用户问了一句什么 → Agent 分几步答完 → 每步做了什么 → 最后输出是什么",把每步的耗时和决策原因用一句话概括出来。

这个业务视角的仪表盘绝对不能省。因为 Agent 产品和传统软件最本质的差别在于——它的行为不是程序员写死的,而是在运行时"涌现"出来的。只有让非技术同事也能逐帧看到 Agent 到底做了哪些事,他们才能提出真正有价值的优化意见(哪个工具该加限制、哪条 Prompt 引导不够明确等)。

4. 从追踪到洞察:可观测性怎么反哺 Agent 全生命周期

一通操作下来,工具链能跑通了,追踪也有数据了,但我不想停在"能看链路"这个阶段。做可观测性的最终目的,是把观测数据变成优化 Agent 的弹药,这就引出了第二层核心话题:怎么用数据反哺 Agent 的开发、评测和迭代。

4.1 数据集构建:每日追踪数据自动清洗成高质量评估集

做 Agent 的都知道,最大的痛点是"没有真实数据做评测"。用公开数据集测出来的效果和线上真实情况差着十万八千里。可观测性体系一旦建成,这个问题就自然解决了——线上的每一条真实请求轨迹,都是绝佳的评测样本。

我是这样做的:每天凌晨跑一个定时任务,把前一天的追踪数据从存储里拉出来,按会话聚合,过滤掉信息不完整的轨迹(比如用户中途取消的、系统报错中断的),再按业务类型打标分类,最终得到一批"用户真实提问 + Agent 完整行为轨迹"的样本。在标注工具里人工抽检一部分,标注上"回答正确、回答错误、部分正确"等标签,补上人的反馈之后,这批数据既可以用来做模型微调,也可以作为回归评测的 baseline。

这套流程坚持跑一个月之后,我们的评测数据集从一开始手写的 200 条,扩充到了 5000+ 条真实线上样本,评测结论的说服力和之前完全不是一个量级。

4.2 产品决策支持:从追踪数据里发现体验优化点

追踪数据里除了技术信息,还藏着大量产品层面的洞察。举两个真实案例。

第一个是上下文膨胀问题。追踪数据显示,超过 40% 的长会话请求里,上下文窗口的 60% 以上被历史对话内容占掉了——而这些历史内容对当前问题毫无帮助。这个数据直接推动了我们在产品侧加上了"上下文压缩"功能:当检测到上下文使用率达到阈值时,自动对历史会话做摘要压缩,把核心信息提炼保留,压缩后实时观察 Token 消耗和响应速度,效果立竿见影。

第二个是工具选择的合理性。追踪数据显示,Financial 类问答场景下,Agent 经常先调用一个通用搜索工具,再调用数据库工具,再调用计算工具,而明明数据库工具已经有了全部答案。这个发现倒逼我们调整了工具的 prompt 描述,让 Agent 在明显能从数据库取数的场景下优先走数据库通道,少绕弯子。调整后这类场景的工具调用平均次数从 5.2 次降到了 2.8 次,延迟缩短了一半以上。

4.3 成本与性能治理:可观测性直接对账预算

做 Agent 生产化,成本这块避不开。追踪数据天然记录了每笔请求消耗了多少 Token,按模型价格换算成钱,我们就可以得到"每条业务请求的推理成本"。一个月下来按业务线、按功能模块、按用户类型聚合,成本结构一目了然。

我建了一个每周要看的报表:各业务线的 Call 量、Token 总量、单请求平均成本、成本 TOP10 用户/会话。这个报表直接暴露了两类问题:一是某些功能模块的 Agent "废话太多"——回答结果明明一两百字就够,模型每次输出八九百字,白白烧掉 Token;二是某些用户在一次会话里触发了大量工具调用,单会话成本是正常用户的 20 倍,需要产品侧设置调用上限或转人工。

没有可观测性之前,这些成本黑洞全靠月底"看账单发现超支"才能察觉,现在成本预算在周维度就能精细管控。

5. Agent 生命周期管理:部署、版本、评测与回放

如果说前面几节解决的是"运维期"的观测,那 Agent 生命周期管理要解决的,就是 Agent 不停迭代过程中的"变更风险"。代码可以回滚,prompt 改了怎么回滚?模型版本换了怎么验证效果没有退化?这是生产级 Agent 区别于 demo 的另一个关键门槛。

5.1 统一"追踪"与"魔法棒":版本管理必须覆盖 Prompt、模型与工具

我用的 Agent 框架自带了一个叫"魔法棒"(LangSmith/LangFuse类产品的通用叫法)的调试工具,但只靠它管理不了生产环境的版本。我们必须有一套自己的版本管理机制,把每个 Agent 版本完整的"模型配置 + Prompt 模板 + 工具列表 + 参数设置"固化下来,部署哪个版本就导哪份配置。

具体做法是:每次变更(改 Prompt、换模型、加工具)都必须提交一条版本记录。线上流量打到哪个 Agent 版本由路由配置决定,切换版本就是改一条路由规则,改动要留痕、可回滚。这套逻辑和传统微服务的灰度发布非常像,但注意一个差异点:Agent 的版本切换不仅要看服务是否健康,还要看新版本的效果是否比旧版本好——这就需要对每个版本的请求流量做效果分层统计,而不只是看错误率。比如我们把流量切到 v2 之后,持续观测 v2 的"任务完成率"是否比 v1 下降,一旦低于阈值必须立刻灰回。

5.2 追踪数据驱动的回归评测体系

版本切换"效果好"的标准不能拍脑袋,必须有数据支撑。做法是在评测集上同时跑新旧两个版本,用一套统一的评估脚本对比结果。评估脚本的维度包括:回答语义相似度(可以用 embedding 计算)、关键信息召回率、工具调用合理性(该调用的工具是否调了、不该调的是否多调)、最终结果的正确性(靠规则或人工标注)。

追踪数据在这里的独特价值在于:评测集是从线上真实轨迹抠出来的——这意味着每次回归测试都是在复现真实用户场景,而不是在玩实验室里造出来的理想数据。我们在做一次大模型版本升级时,用追踪数据构建的评测集提前发现了新模型在"工具选择类任务"上出现明显退化——它倾向于跳过工具、直接凭记忆回答,而线上业务非常依赖工具获取实时信息。这个隐患在传统"用 200 条人写测试用例"的评测方式里极难发现,但追踪数据样本足够多时,这类退化模式藏不住。

5.3 离线回放:复制线上问题到本地,事半功倍

最后一个高频场景是"线上出问题了,开发怎么复现"。传统服务可以拿线上请求日志在本地重放 HTTP 请求,Agent 一样可以。

我的做法是:从追踪系统里把某条异常轨迹完整导出成 JSON——包括用户的原始输入、每一轮的 LLM 请求与响应、每个工具的参数与返回结果——然后写了一个回放工具,把这个 JSON 喂给本地 Agent 框架,逐步骤复现当时的执行流程。回放模式可以设置成"完全按原轨迹执行"(断点时可选择分支干预)或"仅输入原用户问题,自由重跑"(观察新版本表现)。这两种模式在排查 AI 问题时都非常好用:前者用来定位"哪一步决策导致了最终结果异常",后者用来验证"新版本是否已经修复了这个问题"。

6. 常见问题排查与经验复盘(附速查表)

踩了不少坑,沉淀下来一些规律。这里把高频问题统一整理成速查表,也顺带补几个常规博客里不会写太细的点。

6.1 常见陷阱速查

问题现象根因方向排查思路
链路在异步任务处断裂asyncio/线程池未继承 OTel context检查任务提交处是否显式传播 Context;给线程池写包装器,动态拷贝 contextvar 再提交
Token 消耗突增上下文长期对话未压缩、Agent 陷入工具循环用追踪数据统计每次请求的 Token 消耗曲线;查是否存在工具连续失败并重试超过 N 次的轨迹;检查上下文在达到阈值时是否触发了压缩策略
Span 树结构混乱框架自动创建的隐式 Span 和业务 Span 混在一起显式管理 Span 生命周期,关闭框架的自动 instrumentation,改为手动创建核心 Span;对同一个 Agent 的所有关键路径统一命名约定
工具调用有结果但最终回答错误模型在解析工具结果时产生幻觉或有信息遗漏把"LLM 原始输出 + 工具返回原文"同时记录在追踪数据里;用数据集分析该模式高频出现在哪类提示词下,针对性优化 prompt 中"基于以下工具结果回答"的引导语句
网络代理超时导致 Agent 卡住无配置超时,默认无限等待每个工具调用都显式配置超时上限(我一般设 15s),超时后自动跳过工具并让 Agent 重新决策

6.2 排查工具链的选型笔记

选工具链上我反而比最初预期保守得多——现在主流开源的方案已经足够用,自己去写一套成本非常高。我的选型组合是:OpenTelemetry 做数据采集和标准规范,Jaeger 或 Grafana Tempo 做追踪存储与展示,Elasticsearch(或 ClickHouse,取决于单量)存事件日志和 Trace 明细,Grafana 做指标大盘。这套组合的好处是全开源、社区生态活跃、招聘也好招人。

有一点需要提醒:因为 Agent 的追踪体量比传统服务大不少(一次复杂任务可能产生几十上百条 Span),存储量会涨得很快。务必在 Trace 采样策略上提前规划——线上全量采样很容易让存储成本翻几倍。我现在是"头部关键请求全量保留 + 普通请求按 1:10 采样",既保证了异常轨迹完整可查,又把存储成本控制在了可接受范围。

6.3 排查 Agent 问题的一点私人心得

传统服务排查讲究"快"和"稳":拿到日志,定位异常,修复,完事。Agent 排查要额外多想一步:"这个行为是偶发还是规律?是输入问题还是模型问题?是单点失败还是系统性退化?"我见过团队花了整整一周追一个 Agent 答非所问的问题,最后发现是历史对话中的一轮工具返回内容被错误地拼到了系统提示词里,影响了后续所有轮次的判断。这种问题,没有全链路数据追踪的话,几乎不可能找到根因——因为每个中间环节看起来都"正常"。

我的习惯是:线上只要出现 Agent 异常行为,第一件事不是改 prompt,而是先把那条轨迹完整导出来,逐帧看。改 prompt 靠猜,导出轨迹靠数据,后者几乎总是更靠谱。

7. 写在最后:可观测性不是一个工具,而是一条护城河

做生产级 Agent 这一年多,我越来越觉得,可观测性在 Agent 时代的地位,不应该只是"运维的一个环节",它应该是 Agent 产品从开发到迭代全流程里的基础设施——它连接了线上和线下、连接了技术和产品、连接了用户反馈和模型优化。

如果只挑一个最重要的建议:从你决定做 Agent 的第一天起,就把追踪数据的行为规范定下来。你可以暂时不上大盘、不做告警,但一定要保证每个关键节点都有数据。因为 Agent 的问题总是"事后才显现"的——那批在线上跑得歪歪扭扭的轨迹,最早只藏在几十个字段的 JSON 里。没有记录,就没有一切。有一个小技巧分享给你:给 Agent 的核心 Span 全部加上业务标签(用户群体、产品线、业务类型),半年之后你会发现这套标签是构建评测集和成本账单的基石,当时看似"多此一举"的一步,省掉的是后面无数个加班的夜晚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询