
1. 为什么要“解剖”AI Agent的可观测性最近这半年我明显感觉到一个趋势身边的同行们已经从“怎么把Agent跑起来”进化到了“Agent跑起来了但完全不知道它在干嘛”。我自己手上维护的几个Agent项目就遇到了类似的困境——用户问了一句“帮我分析一下这批数据的趋势”底层模型可能经过了意图识别、工具选择、参数填充、外部API调用、上下文拼接、结果校验这么多环节中间任何一步出了偏差最终回答都可能是错的。但问题在于传统的日志监控体系在Agent面前几乎等于半瞎。普通Web服务的观测链路是Request→Controller→Service→DAO路径相对固定我只要在中间件层埋点基本能还原一次请求的全过程。Agent不一样它的执行路径是模型动态决定的今天调用这个工具明天可能调用另一个参数也是模型现场生成的出错时的表现往往不是报错而是“一本正经地胡说八道”。这时候如果没有一套合适的可观测体系排查问题基本靠猜甚至靠玄学。所以我写这篇文章的核心目的就是想结合我自己的实操经验把AI Agent的可观测性这件事彻底拆开揉碎——包括它到底要观测什么、怎么埋点、如何把一次Agent运行的完整链路还原出来、以及拿到这些数据之后怎么用。这篇文章适合已经在做Agent开发、但被线上问题折磨过的工程师也适合刚入坑、想在设计阶段就把可观测性考虑进去的新手。废话不多说直接进入正题。2. 可观测性对Agent意味着什么2.1 从“请求日志”到“思维过程还原”传统后端服务的可观测性三支柱是Metrics、Logging、Tracing这套体系在Agent场景下依然适用但语意上要发生偏移。我之前跟团队里的小伙伴交流时打了个比方传统服务的日志像监控摄像头的录像录的是固定机位下的固定路径Agent的日志更像是给一个自由发挥的演员全程跟拍不仅要记录他说了什么输出还得记录他为什么这么说输入、推理依据、工具调用结果。具体来说Agent的一次完整运行至少包含这样几个被观测对象用户请求的原始输入、模型对意图的理解结果、Agent规划出的执行步骤、每一步选择的工具、传给工具的参数、工具返回的原始结果、上下文窗口中被塞入了什么内容、最终回复如何生成。任何一个环节的数据缺失都意味着事后无法复现当时的决策过程。这里有一个容易被忽视的点Agent的不可控性主要来自模型推理的随机性和工具调用的副作用。同样的用户请求连续跑十次可能有八次走同一个工具一次换了工具还有一次直接拒绝执行。如果没有完整的链路还原能力你根本没法判断线上一次错误反馈到底是偶发还是必然。2.2 观测的黄金指标那到底应该盯哪些指标我的经验是不要一上来就追求大而全先抓住四个最要命的端到端延迟从用户发出请求到收到完整回复的总耗时。Agent场景下这一步往往波动极大因为工具调用的网络延迟、模型推理的Token数都会直接影响总耗时。我见过一个项目端到端延迟P50只有3秒P99却飙到30秒原因是有个外部搜索接口偶尔会挂起。没有端到端指标这类问题根本发现不了。工具调用成功率Agent与外部世界交互的可靠程度。这个指标要按工具维度拆开看哪个工具失败率最高、失败后Agent有没有自动降级、有没有重试这些都是直接关系到用户体验的。上下文窗口使用率我把这个称为“隐形杀手”。LLM的上下文窗口不是无限的一旦塞入的内容逼近上限一方面响应质量会下降另一方面费用会直线上升。我见过有些Agent项目一次请求往上下文里塞了十几万Token的检索结果模型根本处理不过来回答质量一落千丈而开发者还以为是模型能力不行。无效规划率这是Agent场景特有的指标指Agent在思考过程中规划了步骤但最终没有执行或者执行了没产生价值。有些项目为了展示“智能感”会让Agent先展示一段规划但实际很多步骤是多余的。这个指标过高说明Agent的规划能力还有待优化同时也可能是Prompt设计有冗余。这四项是底座把这个底座搭稳了再谈更细粒度的观测才有意义。2.3 复杂性的四个来源为什么Agent的可观测性比普通服务难做我觉得根源在于四个层面的复杂性叠加。第一是规划的不确定性。普通服务走固定的代码路径Agent的每一步都是模型当场推理出来的。这意味着你无法预埋埋点只能在运行时动态追踪。第二是反馈循环的隐蔽性。Agent在执行过程中会把当前执行结果重新注入上下文影响后续决策。这个循环如果出了bug比如注入的结果格式不对Agent会在错误的上下文里继续规划而且往往不会报错只会给出质量越来越差的输出。第三是依赖的系统不在你的控制范围内。不管是OpenAI的API、Anthropic的API、开源模型的推理服务还是项目里接入的搜索、数据库、第三方工具任何一个环节抖动都会影响Agent的整体表现。而这类问题的排查往往需要结合外部服务的状态页和内部观测数据交叉验证。第四是状态的隐式传播。传统服务可以通过链路ID把一次请求的所有环节串起来Agent的上下文窗口充当了同样的角色但它承载的内容是非结构化的文本。要把这些文本和一次具体的决策关联起来需要更精细的埋点。理解了这四个复杂性来源你就明白为什么传统的日志打印三板斧行不通了——不是不想用是根本不够用。3. 从零搭建一套Agent可观测体系3.1 哪些关键节点必须埋点我之前拆过一个开源Agent框架也自己从零写过一套最后总结出一个经验埋点这件事宁可多不可少因为数据一旦丢了事后想补也补不回来。但“多”不代表“乱”关键节点是有规律可循的。我的做法是按照Agent的生命周期来划分埋点区域。第一阶段是接收输入记录原始用户消息、附带的多模态数据、用户身份、会话上下文。第二阶段是意图识别与规划记录模型返回的规划内容、置信度、用了哪些Prompt模板。第三阶段是工具调用记录工具名称、传入参数、调用开始时间、结束时间、返回值摘要、错误信息。第四阶段是结果生成记录最终回复的完整内容、Token消耗、结束原因。第五阶段是记忆操作如果项目里有长期记忆或短期记忆记录记忆的读取和写入动作。这五个阶段如果你都用结构化的方式记录下来一次Agent运行就是一条完整的“事件流”。我建议为每个事件定义一个统一的Schema至少包含event_id、trace_id、agent_id、session_id、event_type、timestamp、payload这几个字段。event_id全局唯一trace_id串起整条链路agent_id区分是哪个Agent在执行session_id区分是哪次对话payload用来存每个事件特有的内容。还有一个我自己踩过坑后的经验payload里不要存原始大文本。有一次我把完整的工具返回结果直接塞进日志系统结果一天产生了好几个GB的数据ES集群直接被干爆了。正确做法是存摘要和关键字段原始数据放到对象存储里通过event_id关联查。3.2 三个维度的数据如何组织我自己的架构习惯是把观测数据分成三个层Trace层、Session层、Eval层。Trace层解决的是“这一次调用经历了什么”。我推荐用OpenTelemetry作为底座把一次Agent的内核执行过程模拟成Span的树状结构。根Span是一次完整的Agent Run子Span是具体的工具调用、模型推理、外部API请求。OpenTelemetry的生态比较成熟后面想接入Jaeger、SkyWalking或者商业化APM都比较方便。Session层解决的是“跨多轮对话的长期状态”。Agent的能力不只是单轮用户往往会连续追问而每轮的决策可能依赖前面的历史。所以Session层要记录多轮之间的衔接包括当前上下文被截断了多少、历史摘要如何生成、长期记忆在什么条件下被检索和写入。Eval层解决的是“这次回答到底好不好”。这可能算是Agent可观测性里最容易被人忽略的一层。我见过不少团队把Trace和日志搭得漂漂亮亮但完全不知道回答质量如何。建议在Trace数据之上对每次回复做自动化评测包括事实一致性、有害性检测、工具使用合理性等。这一层数据反哺的是Prompt迭代和模型选型。3.3 追踪链路设计的具体实现链路追踪这块我以Python生态为例说一下我的落地方案。整体思想是把一次任务的所有相关Span串联成一个Trace关键操作都包裹成带上下文的方式。from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanExporter from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace.export import SimpleSpanProcessor resource Resource.create(service_nameagent-service) provider TracerProvider(resourceresource) # 生产环境建议用BatchSpanExporter做异步批量上报 otlp_exporter OTLPSpanExporter(endpointhttp://otel-collector:4317) provider.add_span_processor(SimpleSpanProcessor(otlp_exporter)) trace.set_tracer_provider(provider) tracer trace.get_tracer(__name__) def run_agent(user_input: str): with tracer.start_as_current_span(agent.run) as root_span: root_span.set_attribute(app.user_input, user_input[:500]) # 记录原始输入摘要 plan planner.plan(user_input) with tracer.start_as_current_span(agent.plan) as plan_span: plan_span.set_attribute(app.plan, plan.to_json()) # 记录规划结果 for step in plan.steps: with tracer.start_as_current_span(agent.tool_call) as tool_span: tool_span.set_attribute(app.tool_name, step.tool_name) tool_span.set_attribute(app.tool_input, json.dumps(step.arguments)) result execute_tool(step) tool_span.set_attribute(app.tool_result_summary, result.summary) # 不要把完整大结果写入Span response generate_response(plan) with tracer.start_as_current_span(agent.response) as resp_span: response_prefix response.choices[0].message.content[:500] resp_span.set_attribute(app.response_prefix, response_prefix) resp_span.set_attribute(app.finish_reason, response.choices[0].finish_reason) return response这段代码展示了埋点时的几个关键细节。一是每个关键步骤都对应一个独立的Span这样可以精确测量每一步的耗时二是Span属性里存的是摘要和关键标识不是完整的大段文本三是嵌套结构忠实反映了Agent执行的调用栈。如果项目里用的是LangGraph或者LangChain这类框架直接在回调里做埋点会比手动改代码省事得多。LangChain自带BaseCallbackHandlerLangGraph有节点级的回调机制把回调接到你的日志系统里就行。但我的建议是不管用不用框架核心业务逻辑里至少要有一层自己的埋点因为框架的回调覆盖不了你自定义的逻辑。4. 观测Agent推理与记忆的隐性过程4.1 模型调用的细节指标模型调用是Agent的大脑观测这一层能看到很多有意思的现象。除了常规的输入输出和Token消耗我还会额外记录几个指标。第一个是首Token延迟。这个指标决定了用户的“第一感知”比总延迟更能反映底层推理服务的加载状态。如果首Token延迟突然上涨多半是推理服务出了问题比如GPU排队、量化精度切换、推理框架版本更新。第二个是Token生成速率tokens/s。这个指标能帮你判断是模型推理成了瓶颈还是工具调用成了瓶颈。如果生成速率正常但端到端延迟还是高问题大概率出在工具调用上。第三个是采样参数。temperature、top_p、max_tokens这些参数直接决定了模型输出的随机性和长度底线。这些参数如果被线上代码误改影响是巨大的。我有一次排查线上Agent回答风格突然大变最后发现问题出在有人把temperature从一个配置文件里不小心调到了0.9。没有记录采样参数的指标这类问题会排查到崩溃。第四个是结束原因。finish_reason是stop还是length含义完全不同。如果是length说明回复被截断了用户看到的内容不完整这种情况经常被误判成模型能力不行实际上是max_tokens设置不合理。4.2 上下文和记忆的追踪方法上下文窗口管理这块我强烈建议至少记录以下数据当前轮次Prompt实际消耗的Token数、上下文中的历史消息条数、被截断策略删掉了多少Token、检索增强模块注入的内容大小和来源。实操层面的做法是在每次调用模型之前把当前上下文的构成信息序列化出来记录成一个独立的Span属性。比如“从长期记忆检索了3条记录共1200 Token”“从对话历史保留了最近5轮共2800 Token”“工具返回结果摘要占400 Token”。有了这些记录你才能回答“为什么这个月的Token费用涨了30%”这种来自老板的灵魂拷问。记忆操作的追踪稍微复杂一些因为记忆不是一次请求里的事件而是跨请求的长期副作用。我的做法是给记忆的写入和读取都打上独立的日志事件包含触发条件、检索到的记忆ID列表、相似度分数、最终是否被采纳。这些数据对优化记忆策略非常关键——我见过有些Agent项目做了长期记忆功能但实际运行时检索出来的记忆经常是无关的导致回答反而被带偏。没有记忆质量的观测数据这个坑根本发现不了。4.3 检索增强与工具返回的观测细节RAG场景里检索质量直接决定了回答质量。我建议在检索调用点记录以下内容检索的查询向量对应的原始文本、召回条数、每条召回文档的标题与来源、按相关性排序后的前N条、每条命中的相似度分数、重排模型的分数如果用了重排。工具返回的观测也很讲究。工具返回的内容细节和体量往往是性能瓶颈。我有一次排查一个Agent单轮请求P99能到40秒后来发现是某个工具会把整张百万行表的数据全量返回给Agent上下文瞬间爆炸。加了输出摘要、截断、聚合等策略之后P99降到8秒。所以工具返回值的“体积”本身就是一个重要的观测指标——包括原始字节数和Token化之后的Token数。5. 可观测数据怎么用起来5.1 比较基线版本和预测趋势观测数据不只是用来事后灭火的它还有一个特别实用的场景版本对比。我自己的习惯是每次改Prompt模板、换模型版本或调整工具策略之后都会跑一批相同的评测集然后对比两次运行的观测数据重点看关键指标的变化。这里有一个我自己踩过坑后的体会指标对比不要只看平均值而是要拆Percentile分布来看。有一次我优化了一个Agent的规划逻辑平均延迟下降了200毫秒但P99反而从8秒上升到了16秒。平均数据掩盖了部分极端场景的劣化如果不看分位数这次上线可能就成了线上事故。推荐把观测数据做成趋势面板按天或按周聚合这样能提前发现指标劣化的趋势而不是等问题爆发之后才看到。数据保留期限上原始Trace建议保留7天聚合指标保留30天更长期的数据导出到数仓用于模型评估和业务分析。5.2 排查问题的几种典型姿势有了完整观测数据排查问题的思路就会清晰很多。我整理了三个最常见的Agent线上问题的排查路径。现象一Agent回答突然乱答答非所问。第一反应应该是看这次运行的Trace重点看上下文被塞了什么内容。常见的坑包括检索模块把一堆无关文档注入了上下文、工具返回的格式被模型误读、上一轮的错误输出污染了历史记录。从Trace里一眼就能定位是在哪个环节开始歪掉的。现象二工具调用了但结果没被用到。这个问题靠普通日志很难发现因为日志只显示工具返回了结果不会显示模型有没有采纳。我的做法是在最终回复生成后做一次“引用检查”——扫描回复里是否包含工具返回结果中的关键实体或结论。这套逻辑本身可以作为Eval层的一个评测项。现象三同一问题线上时好时坏。这种现象多半跟模型采样参数和外部服务波动有关。我会对比正常和异常两次的完整Trace看模型推理的耗时、Tool调用的返回结果、上下文的差异。如果两次数据都正常再去看外部服务的状态页交叉验证是不是第三方API的抖动导致的。5.3 选型和踩坑开源方案还是自研最后聊下工具选型。我的整体观点是建立初期的可观测性不需要自己造轮子。自研的坑我已经踩过了。早期我试图用MySQL表格和定时任务去做链路追踪结果数据集一上来就顶不住查询又慢又难写。后来切到方案化工具效果立竿见影。现在业界比较成熟的思路是Agent执行状态捕获与持久化用LangSmith这类风格的工具链路分析和性能追踪用OpenTelemetry作为统一探针数据底座用开源的ClickHouse、Jaeger、Grafana这套组合。这套方案的好处是很轻架构清晰需要展示的时候也很方便。如果你对数据主权或者成本有更高要求OpenTelemetry加自建APM是一条比商业化产品更可控的路。但无论选哪套我有一条忠告不要把可观测性当成后置任务而是在设计Agent架构的第一天就同时规划好协议、埋点和仪表盘。后续维护的任务是持续打磨千万不要等线上出了事再做那会儿就晚了。6. 三个我踩过的典型坑第一个坑是埋点数据过于冗余。我最早做Agent追踪时习惯把调试用的全部数据都塞进去运行日志、模型完整输出、甚至开发阶段的自定义Debug信息全都塞到Trace里。结果就是Trace列表页面变得又臭又长真正需要审核的时候反而看不进去。后来我意识到分级很重要关键数据集要精细做调试数据要单独留Trace和日志要分开。现在我把详细日志都接到独立日志服务Trace里只保留关键节点和摘要看问题的时候也舒服多了。第二个坑是上下文数据没保留就慌忙清理。有一次排查一个Agent的线上问题查Trace的时候发现所有相关的上下文数据已经被清理掉了因为我的清理策略太激进——所有数据都是当天清。结果就是问题定位成了空壳既不知道上下文是什么也不知道它为什么出错。后来我调整了策略保留原始上下文的快照至少7天问题排查的数据基础就稳了。这里有个细节是尤其要注意的清理策略要区分原始上下文快照和派生指标原始快照影响的是排查能力派生指标影响的是持续展示两者保留期要分开设计。第三个坑是只观测主动行为忽略被动影响。早期做Agent我只关心“Agent调了哪些工具、调用了多久”但完全不看“这些工具调用对线上其他服务产生了什么影响”。后来有一次Agent在循环里反复调用同一个重查询直接把数据库压垮了。加了“工具调用频率”“单工具累计耗时”“超时重试次数”这些观测项之后这类问题才能提前拦截。建议给Agent设置工具调用的熔断上限比如单次任务最多调用N次工具超了就强制停止这一步能规避大量隐患。7. 结语可观测性不是成本是Agent的必修课做Agent可观测性这一年多我最大的体会是它不是一个可选的加分项而是Agent真正落地到生产环境前的硬门槛。传统服务的故障是异常Agent的故障是常态——模型会猜错意图、会选错工具、会一本正经地编造不存在的结论。面对这种几乎必然出现错误的系统观测能力的价值就体现在一个地方你越了解它在真实场景下的状态就越知道该怎么修复它该往哪个方向继续调优。可观测性不是等到出了问题才临时抱佛脚的工具更不是个可有可无的后置任务。它决定了你手里的Agent是“一台看得见内部结构的精密仪器”还是一只“永远捉摸不透的黑箱”。最后再分享一个小招很多团队把可观测性只理解成“给运维看的”但如果你在开发阶段就把埋点这件事做好了它对你自己的调试效率提升远超想象。上线后排查问题的时间从以前“猜半天”变成现在“翻链路十分钟就能定位”这是我认为Agent可观测性最值回票价的地方。如果你正在做Agent开发千万别跳过这一环——这事做得越早收益越大。