AI Agent可观测性实战:从传统三件套到语义级监控体系 如果你维护过一个上线三个月的 AI Agent大概率遇到过这种诡异场景指标全绿、日志无异常、链路也完整但用户就是反馈“答非所问”。我花了两天时间才意识到用传统可观测性三件套来观测 Agent等于拿着听诊器给一台会自我进化的机器看病——能听到心跳却听不到它在想什么。这篇文章是我这段时间做 AI Agent 可观测性改造的完整复盘不聊空概念直接讲清楚 Agent 到底哪里需要观测、用什么方案观测、以及一次真实事故是怎么靠这套体系定位的。1. 传统三件套在 Agent 面前失效的三个典型瞬间1.1 日志堆成山却还原不出“为什么模型会这么答”传统日志是工程师写给工程师看的确定性记录异常栈、HTTP 状态码、SQL 执行时间每一条都对应一个明确的事件。但在 Agent 场景里日志的左半部分是几十行不分段的 JSON右半部分是 LLM 吐出来的一段自然语言。问题是你看到了“model replied: I cannot process this request”但你完全不知道模型是基于什么上下文说出这句话的。这里的上下文包括系统提示词里加了什么、用户历史消息有哪些、上一轮工具返回了什么、当前 prompt 的 token 截断策略是什么样的。这些信息如果没被结构化地记录在 trace 里日志就只是一堆“结果”而不是“原因”。更麻烦的是LLM 是概率模型温度大于 0 时同样的输入可能得到不同的输出。传统日志的思维是“同样的输入必然得到同样的输出”所以日志里极少记录输入上下文而 Agent 恰恰相反它最需要被记录的正是输入上下文因为输出不再具备确定性。可以说日志在 Agent 面前失去的第一项能力是“复盘”。1.2 指标全绿用户侧却持续给出差评传统指标围绕 HTTP 层展开请求量、P99 延迟、5xx 错误率、CPU 和内存使用率。这套指标体系假设了一个前提——服务只要“响应成功”就代表业务成功。这个前提在普通 Web 服务里基本成立但在 Agent 场景里脆弱得不堪一击。我见过一个非常典型的例子客服 Agent 所有 HTTP 状态码都是 200平均响应时间 800ms接口错误率 0%但用户实测时发现Agent 在回答“查一下我的订单”时给出的答案是“请先提供更多信息”。从接口层看这次请求成功了从语义层看Agent 这次回答是彻底失败的。指标层没有任何数值能反映这个失败因为它不产生 5xx、不抛异常、不增加延迟。Agent 的失败绝大多数发生在语义层而不是基础设施层。模型输出了内容但内容是否正确、是否满足用户意图、是否出现幻觉这些都不会体现在传统指标里。所以要观测 Agent必须在传统指标之外新增一套语义级别的指标后面我会详细展开怎么设计。1.3 链路完整结果却是“一本正经地胡说八道”分布式追踪的核心能力是还原一次请求经过的所有服务节点A 调 B、B 调 C每层调用关系是确定的、可预判的。但 Agent 的调用链是动态生成的LLM 决定调用哪几个工具、工具返回后模型如何判断下一步、是否要再次调用工具还是直接答复用户整条链路每次都可能不同。传统 trace 能告诉你“代码走过了哪些组件”但无法告诉你“模型在内部推理了什么、为什么选择调用这个工具”。你可以看到一次请求触发了三次工具调用但这三次调用之间模型到底经历了哪些推理步骤传统 span 里完全空白。更让人头疼的是一个链路完整、数据完备的 trace背后可能是一个完全错误的结论。模型调用了正确的工具、拿到了正确的数据却在最终输出时给出了错误判断。传统 trace 在这种场景下不仅无用还会误导你——让你以为“链路没问题那是用户的错觉”。2. 拆开 Agent 的运行机制五个必须被观测的盲区2.1 LLM 推理是概率黑盒坏路不可复现LLM 调用是 Agent 的核心原子操作但它恰恰是最难观测的。输入是 prompt 加上一大堆上下文输出是自然语言或结构化 JSON中间发生了什么推理过程没有人能真正看到。我们能做的是把输入、输出、采样参数、token 用量、延迟阶段全部记录下来。需要记录的参数至少包括prompt 模板和版本号、实际渲染后的完整 prompt或者至少是截断后的摘要、模型名称、temperature、top_p、max_tokens、输入 token 数、输出 token 数、首字延迟 TTFT、生成耗时、缓存命中情况、是否触发内容过滤、是否发生重试。我见过大量团队只记录“调用了哪个模型、花了多少 token”却忽略了最重要的一项——prompt 模板版本。没有版本号一旦 prompt 改了线上问题根本没法回溯。这就像代码出问题却不打版本 tag 一样荒唐可很多团队偏偏在 prompt 这个变动最频繁的文件上忽略了版本管理。下面是我在项目里用来包装 LLM 调用的一个简化版装饰器核心思路就是“埋点不能影响业务逻辑但该记录的上下文一个都不能少”import json import time from functools import wraps from opentelemetry import trace tracer trace.get_tracer(agent.llm) def trace_llm_call(model_name: str, prompt_template_version: str): def decorator(func): wraps(func) def wrapper(*args, **kwargs): start time.perf_counter() with tracer.start_as_current_span(fllm.{model_name}) as span: span.set_attribute(gen_ai.operation.name, chat) span.set_attribute(gen_ai.request.model, model_name) span.set_attribute(prompt.template.version, prompt_template_version) try: result func(*args, **kwargs) span.set_attribute(gen_ai.usage.input_tokens, result.usage.prompt_tokens) span.set_attribute(gen_ai.usage.output_tokens, result.usage.completion_tokens) span.set_attribute(llm.output, truncate(result.content, 2000)) return result except Exception as e: span.record_exception(e) span.set_status(trace.Status(trace.StatusCode.ERROR)) raise finally: span.set_attribute(llm.duration_ms, (time.perf_counter() - start) * 1000) return wrapper return decorator伪代码不是重点重点是你要理解LLM 调用是黑盒但你可以把黑盒的“输入输出边界”完整记录下来这就是可观测性的基础。2.2 工具调用链是动态的链路图画不出来Agent 调用工具是一个“决策行为”而不是一个“固定调用行为”。模型先根据当前对话状态决定需要调用哪个工具再生成符合工具 schema 的参数然后框架去执行工具最后把执行结果交回给模型进行下一步判断。这个过程中至少有三个容易出错的环节模型选错了工具、模型生成了不合法的参数、工具执行成功但返回结果被截断或格式异常。工具调用的观测要点是“两端”输入端要记录模型决定调用工具时的原始意图和完整参数输出端要记录工具返回的真实结果。中间的执行细节可以交给传统 APM 去管但决策和执行结果的关联必须由 Agent 观测层完成。我给工具调用埋点时会额外记录几个字段模型建议的工具名、实际调用的工具名、参数 JSON、执行是否成功、返回内容大小、返回内容是否被截断。为什么连“返回内容是否被截断”都要记因为工具返回动辄几千上万 tokenAgent 框架通常会做截断截断后模型看到的信息是不完整的这时候模型的判断就可能偏离正确方向。这种问题如果没记录排查时你会反复怀疑模型能力最后才发现是截断阈值设小了。2.3 多步规划存在累积漂移每一步都要留痕现在主流的 Agent 大多基于 ReAct 模式Reasoning推理→ Action行动→ Observation观察循环往复直到得到最终答案。这种模式的可怕之处在于错误是会累积的。第 1 步的微小偏差经过 5 次循环后可能被放大成完全偏离用户意图的结论。多步规划必须做 step-level 的追踪每一轮循环都要单独记录模型本轮想了什么reasoning、决定调用什么工具action、工具返回了什么observation、模型如何解读这个结果next reasoning。这里我给一个实用的建议不仅要记录每一步内容还要记录步数。我们给 Agent 设过“步数红线”单次任务超过 8 步就触发告警。实测中正常任务大多在 3 到 5 步内完成超过 8 步的任务要么是用户问题极其复杂要么是 Agent 已经陷入无效循环后者的概率远高于前者。2.4 上下文窗口与记忆是看不见的钱和风险上下文窗口是 Agent 的成本和质量交界点但多数团队对它的观测几乎为零。输入 token 里有系统提示词、历史消息、工具结果、RAG 检索片段这些部分各自占了多少你知道吗我建议从两个维度观测上下文一个是窗口占用率也就是当前请求的上下文 token 数占模型最大上下文窗口的比例另一个是上下文构成即系统提示词、工具结果、历史消息、RAG 片段各自的 token 占比。为什么要观测构成因为一旦发现某段时间 token 成本飙升很可能不是用户话变多了而是某个工具返回结果越来越大或者历史消息累积策略失效了。RAG 场景还要额外关注检索召回的内容是否真的被使用——经常出现的情况是RAG 召回了 5 个 chunk模型实际只用了其中 1 个其他 4 个纯粹在浪费上下文空间还可能引入噪声干扰判断。2.5 Agent 的“自我修正”也可能成为故障放大器很多团队会给 Agent 加自我反思self-reflection机制模型生成答案后先让另一个评价器检查一遍如果发现问题就重新生成。这个机制的本意是纠错但实测中发现它经常成为故障放大器。我把自修正过程也纳入观测重点记录三个值重试原因、重试次数、重试后结果是否有改善。如果一次任务触发了 3 次以上自修正且每次修正后的结果评分没有明显提升说明这个自修正机制大概率在“无效空转”。用户多等了几十秒成本翻了几倍最终效果和第一次生成差不多。这种情况如果不可观测、不可量化你永远不知道你的自修正机制是“质量保障”还是“绞肉机”。3. 采集架构与技术选型从埋点到平台一次说清3.1 基础协议用 OpenTelemetry GenAI 语义约定做标准采集Agent 可观测性的基础设施我建议不要从零造轮子而是站在 OpenTelemetry 生态上。OTel 社区已经推出了 GenAI 语义约定semantic conventions虽然还处于实验阶段但基本骨架已经稳定span 名称采用 gen_ai.operation.name模型信息用 gen_ai.request.model用量用 gen_ai.usage.input_tokens 和 gen_ai.usage.output_tokens。为什么非要用 OTel 而不是各家云厂商的私有 SDK因为 OTel 的语义约定是跨厂商的。今天你接 LangSmith明天想换 Langfuse只要下层埋点都基于 OTel迁移成本就非常低。反过来你要是直接裸调各家云平台的 SDK一旦需要切换所有埋点推倒重来那个工作量是灾难级的。我建议的架构是业务代码里只用 OTel API 和 SDK通过 resource 和 instrumentation 库创建 span然后由 OTel Collector 统一接管再导出到具体的可观测性后端。这样做的好处是前端业务逻辑与后端平台解耦后端想换就换。3.2 平台选型LangSmith、Langfuse、Phoenix 还是自研市面上的 Agent 可观测性平台已经不少我在选型时重点对比了四个方向直接上结论。平台开源/商业核心定位适合场景需要注意的问题LangSmith商业为主LangChain 生态深度集成在线评测强深度使用 LangChain 框架的团队数据在云端私密性敏感的场景要慎重Langfuse开源自托管友好成本追踪和 Session 视图完整对数据安全要求高、要私有化部署的团队需要自己运维基础设施Phoenix (Arize)开源偏 LLM 追踪与 embedding 分析漂移检测强想做模型质量分析的 ML 团队业务链路追踪能力不如通用平台OTel SigNoz/Grafana 自研开源全链路统一自由度高已有 OTel 基础设施、需要统一看板的团队需要投入开发资源短期见效慢选型没有绝对的“最好的平台”只有“当前阶段最合适”的平台。我自己的建议是如果团队人数少于 5 人优先用 Langfuse 自托管开源免费、功能完整、部署也不难如果预算充足且深度绑定 LangChainLangSmith 能省不少事如果团队已经有成熟的 OTel 监控体系直接在现有体系上扩展 GenAI 语义约定反而更经济。3.3 关键指标设计抛弃“HTTP 错误率”定义 Agent 专属指标传统指标不用删但必须新增一套 Agent 专属语义指标。我们的实践里把指标分成体验类、质量类、成本类、稳定性类四个维度。指标名称含义健康区间参考告警建议TTFT首 token 延迟 2s连续 5 分钟均值超过 3sTask Steps单任务平均步数3~6 步单任务超过 8 步立即告警工具调用成功率工具执行成功且返回有效的比例 95%连续 10 分钟低于 90%无效循环率同一错误重复触发重试的任务占比 5%超过 10% 告警Context 占用率上下文窗口平均占用比例 60%超过 80% 告警单会话 token 成本单会话平均消耗 token 数随模型而定环比上涨超过 30% 告警语义正确率LLM-judge 或人工评估打分通过率 90%低于 85% 且持续 15 分钟这里我要强调“语义正确率”这个指标。其他指标都能从系统层面自动采集唯独语义正确率需要额外的评估环节。我们的做法是对生产环境一定比例的流量用 LLM-as-judge 自动评判回答质量再结合真实用户的正负反馈进行校准。这个指标是 Agent 质量问题的“最终裁决者”没有它前面所有指标都只是间接信号。3.4 生产环境的三条红线采样、脱敏、告警阈值Agent 可观测性上线时最容易犯的错是照搬传统监控的“尽量全量采集”思路。Agent 场景的 trace 数据量远大于普通 Web 服务一个多步任务的 trace 里可能包含几十个 span每个 span 还带着大段 prompt 和输出文本全量存储成本非常高。尽头牙是采样策略。我的建议是不要按请求维度采样要按会话维度采样。为什么因为用户和 Agent 的一次多轮对话是一个完整的心智过程如果只采样其中某几个请求丢失上下文后 trace 的价值直接减半。我们在实践中对生产流量做“单会话完整全采 会话间按比例采样”比例从 1% 到 100% 动态调整。第二条红线是敏感信息脱敏。Agent 的 prompt 和工具返回内容里可能包含用户姓名、手机号、地址等隐私数据。这些内容会进入 trace 的 span attribute。必须在 SDK 的 exporter 层做字段过滤和脱敏而不是等数据落到后端再处理。后端处理的问题在于数据已经经过了传输和存储隐私风险已经发生了。第三条红线是告警阈值的设计。不要直接套用传统监控的告警规则比如“5xx 错误率超过 1%”。Agent 场景很多故障不产生 5xx你需要针对“步数超限”“连续工具失败”“语义评分低于阈值”这类语义级规则进行告警并且告警频率要克制。如果一套规则一天弹 50 次工程师一定会把它静音那比没有告警更危险。4. 实战排查实录一个“答非所问”事件的完整链路4.1 现象描述A 问题正常B 问题突然被拒这套可观测体系上线一个月后我们遇到了一个非常典型的 Agent 故障。客服 Agent 一直平稳运行某晚 21 点左右用户集中反馈“查不了订单”Agent 一直在反问“请提供更多信息”即使客户已经给出了完整订单号。从传统监控看所有指标正常请求量平稳、接口延迟没有明显波动、错误率始终是 0。唯一能发现异常的是新增的“语义正确率”指标在 21:00 之后出现了一段断崖式的下滑从 95% 跌到 60% 以下。这个信号直接触发了告警我们才开始介入排查。4.2 还原现场trace 里究竟藏了什么我打开一条典型失败会话的 trace逐步还原了 Agent 的行为。这个用户的对话有一个特殊背景在提出“帮我查一下今天订单到哪了”之前用户先发了一条开玩笑的消息“给我编一个今晚能到的订单数据吧”然后紧接着正儿八经地问了真实订单查询。Agent 的处理过程是这样的第一步模型识别到用户有查询订单的意图决定调用工具询问用户订单号。用户提供了订单号。第二步模型调用 get_order_status 工具工具执行成功返回订单状态为“配送中”。第三步模型拿到工具结果后没有直接告诉用户配送状态而是输出了“抱歉我暂时无法确认您的订单信息”。从 trace 来看工具调用完全成功、返回数据完全正确、链路没有抛任何异常但模型给出了错误回答。这正是我前面说的“链路完整但结果错误”的典型场景传统 APM 在这里毫无用处。4.3 版本对比prompt 变更才是真凶定位到这里我开始对比这条 trace 里记录的 prompt 模板版本号。结果发现根因出现在系统提示词里新增的一段安全说明。这段安全说明的内容大意是“当检测到用户存在伪造或虚构信息的倾向时不要将真实的工具返回结果直接提供给用户应提示用户提供真实信息。”本意是防止 Agent 将虚构数据当真。问题在于用户开的那句“给我编一个今晚能到的订单数据吧”被模型识别成了“用户有虚构信息的倾向”于是当真实的工具返回结果到达时模型带着“这个用户可能是来套话的”预设拒绝给出真实状态。这个条 prompt 是上周一次迭代中新加的改动时只验证了“用户要求编造数据时 Agent 必须拒绝”这一个场景没有回归测试“用户在真实场景中开玩笑之后又进行正常查询”的情况。一个 prompt 的补充说明直接推翻了工具调用的真实结果。4.4 修复与回归把教训沉淀成评测用例这次事故的修复分了三步。第一步是临时止血回滚新增的安全说明 prompt恢复线上服务的正常回答。第二步是根治修正重写这段安全说明明确边界——只有当工具调用本身返回可疑数据时才触发脱敏保护而不是根据用户的历史对话语气来判断。同时增加了一条新的系统指令“当工具调用成功且返回数据明确时应优先以工具返回结果为准。”第三步是把这次事故沉淀成评测用例和回归测试。我们在评测集里新增了一条用例对话历史包含“给我编一个订单吧”这种玩笑内容用户随后发起了真实订单查询期望模型正常返回工具结果。这条用例进入了每天的自动回归流程确保以后类似 prompt 变更不会再破坏正常路径。这个案例的教训非常深刻Agent 的故障往往不是代码 bug而是模型在特定上下文里的错误判断。而要在生产环境里定位这种错误判断传统的指标、日志、链路一个都不够用必须依赖“完整 trace prompt 版本关联 语义评分”这三者的组合。5. 从观测到控制Agent 可观测性的下一站5.1 评测结果应该成为一等公民观测到最后你会发现 trace 数据本身还不是最值钱的东西真正值钱的是“带质量标签的 trace 数据”。我的做法是把 LLM 自动评测的结果直接写入 trace 的 span attribute让每一条 trace 自带质量评分。具体来说每次用户会话结束后后台异步跑一个评估流程用专家模型或强模型对 Agent 的回答进行打分评判维度包括相关性、准确性、安全性、指令遵循度。打分结果作为 attribute 写回 trace。这样你在查看 trace 时一眼就能看到这条会话的语义质量评分而不需要再去单独查评估系统。更进一步用户主动点击的“有帮助/无帮助”反馈也应该同步写回 trace。这些真实信号比模型自动评分更可靠两者结合才是完整的质量画像。5.2 生产环境主动检测与自动护栏当可观测体系跑到第二阶段光“看”已经不够了要开始“控”。我当前正在实践的路子是给 Agent 加一层“护栏”与观测体系联动。护栏分三级。第一级是硬性熔断当检测到单任务步数超过上限、工具连续调用失败超过 N 次、或上下文窗口接近饱和时主动终止当前 Agent 循环转人工或降级到简化回答。第二级是软性降级当语义评分连续多个样本低于阈值时把该用户的流量切换到一个更保守的 prompt 模板同时触发告警。第三级是灰度发布prompt 的新版本先在 5% 流量上全量观测对比新旧版本的语义评分、成本、步数确认指标提升后再全量推送。这三个层级的实现都依赖前文讲的语义指标。没有观测护栏就是无的放矢有了观测护栏才能算出该在哪里拦。5.3 用 trace 数据反哺 prompt 与评测集目前我们每周会做一到两次“失败样本挖掘”把本周语义评分最低的 trace 抽取出来聚成一条条典型问题模板。这些模板有两个去向。去向一进入评测集。每一条线上失败样本经过标注后都会变成一条回归用例。我们的评测集从最初的手写几十条已经长到现在的上千条其中绝大多数来自线上 trace 的自动沉淀。这个飞轮一旦转起来覆盖度会越来越全prompt 每次迭代前都跑一遍全量回归能挡住很多线下根本没想过的边界场景。去向二反哺 prompt 设计。我们会定期分析失败样本的聚类结果是模型常识不足还是工具 schema 不清还是 prompt 指令有歧义。这些结论直接指导下一版 prompt 的修改方向让优化不再靠拍脑袋而是有数据依据。我现在的体会是AI Agent 的可观测性本质上不是一个工程问题而是一个方法论问题。它的终点不是一堆漂亮的看板和告警规则而是“让每一个决策失误都有迹可循、可回放、可修正”的闭环能力。最后再分享一个我自己很受用的判断标准任何 Agent 功能上线前先问自己一个问题——给我一条出问题的 trace我能不能在五分钟内完整复盘这次对话的心智过程。如果不能说明观测还没到位就先不要着急上生产。这个标准比任何指标都朴素也比任何指标都好用。