智能体可观测性实战:从传统APM到LLM语义监控 📅 发布时间:2026/9/20 6:18:55 👁 浏览次数: 你兴冲冲地把一个基于LangGraph的客服Agent上了生产第二天就翻车了用户说“你们机器人乱回答”但后端日志里HTTP 200、延迟正常、CPU平稳连一条error都没有。你打开基线和APM面板能看到QPS、P99、JVM堆内存就是不知道机器人为什么开始胡说八道。这就是今天想聊透的问题。大模型业务并不是“传统Web服务上套了个新壳”而是把可观测性的地基整个换掉了。传统APM监控的是“系统有没有在正常干活”而大模型业务更残酷——它要回答“系统干出来的活到底对不对”。这两个命题差的不是一星半点而是从工具链到数据模型都不兼容。下面我从底层原理开始拆尽量说人话最后给一套能直接落地的做法。1. 传统APM的两板斧为什么在大模型业务上失灵1.1 APM的基本假设一切故障都是可枚举的确定性故障传统APM走的三板斧是指标Metrics、日志Logs、链路Traces。它解决问题的逻辑是——我先定义一个“正常”的阈值比如响应时间小于500ms、错误率低于1%、内存使用率低于80%然后持续采集实时数据一旦破了阈值就报警然后沿着调用链找哪个节点出错。这套系统在过去二十年很好用因为传统后端是确定性的输入相同、代码相同输出必然相同。故障从哪儿来是有明确清单的——数据库连接满、Redis超时、下游服务返回500、线程池耗尽这些都能通过状态码和异常栈定位。但大模型业务上来就把这个盘面掀了。你说它挂了它没挂——LLM返回的JSON结构完全规范状态码永远是200链路里每个节点都是通的。但内容和用户问题之间出现了语义偏差。这种偏差没有异常栈、没有错误码、没有HTTP状态APM用“有没有报错”来判断健康度等于用“发动机故障灯亮不亮”来判断一辆车是不是跑偏了。打个比方传统APM盯着的是“水管通不通”而大模型业务首先关心“流出来的水能不能喝”。水管永远畅通但你没法保证LLM这次吐出来的不是含沙量超标的水。要命的是传统APM连“检测含沙量”的传感器都没有。1.2 从“调用链”到“推理链”观测对象彻底换了传统链路的观测单位是一次RPC调用从Nginx到网关到微服务到数据库是一条固定拓扑上的路径。每个节点有明确的角色依赖关系是静态的、预先定义好的。而智能体应用完全不是这样——一次用户提问会触发LLM推理、工具选择、文档检索、上下文拼装、多轮记忆读取这些步骤之间的依赖关系不是写死在代码里的而是由模型在运行时刻动态决定的。举个例子用户问“帮我查一下上个季度的退货率并分析原因”Agent可能先决定调用数据分析工具拿到SQL查询结果后又觉得缺了上下文再走一遍文档检索最后才组织回答。这条推理链只有跑完才知道长什么样而且换一个用户、换一个问法链路结构就完全不同。你没法在部署前画出一张“系统拓扑图”因为链路是模型自己长出来的。所以可观测的核心对象必须从“调用”升维到“推理”。我们不仅要看到“调用了哪个工具”还要看到“为什么调用了这个工具”“当时模型的思考过程是什么”“检索到了什么片段为什么选它”。传统APM的trace在这里最多算个空壳——它记录了节点的执行顺序但完全不理解节点之间的语义关系更无法判断这个“决策”质量如何。1.3 传统APM的计量单位请求数、字节、耗时而大模型业务要先计“token”APM依赖的维度是几个核心指标请求量、错误率、响应时间、吞吐量、资源占用。这些指标对基础设施层依然重要但放到大模型业务里它们只是“外围监控”不是“核心监控”。大模型业务的核心计量维度是token包括prompt token和completion token次要维度是模型名、温度、top_p这类采样参数再往下是工具function调用的名字、参数、结果更深层是embedding向量的维度、检索的召回率、重排的命中位置。你会发现这些维度传统APM压根没有字段去承载。你想在APM的聚合视图里看“按模型版本分组的平均首token耗时”和“工具误调用率”无论怎么打标签都别扭因为数据模型不匹配。真正的大模型可观测必须围绕LLM调用的生命周期来建模把一次调用拆成model_call、retrieve、tool_execute、agent_step等多个事件每个事件带上模型语义层面的属性。这不是在给老房子加装修而是得重新打地基。2. 智能体可观测的真正敌人不确定性和语义漂移2.1 概率性输出带来的“无异常故障”传统系统最大的优势是可复现。同一笔请求回归一遍结果必然一致出bug了盯住堆栈就能还原现场。但LLM是采样模型temperature 0时同一个prompt跑十次就有十个不同的答案质量有高有低甚至偶尔直接幻觉。这种不确定性在单体模型时代还能忍——用户多半不会拿同样的问题连问十遍。但Agent应用会把这种不确定性放大LLM决策刺激工具调用工具结果再次进入模型模型再次决策每一步的微小偏差都会在后续步骤中滚雪球最终表现为一个完全跑偏的结果。这种故障的可怕之处在于它没有任何系统级的报错但它确实发生了。你既不能靠“重启恢复”也不能靠“加机器扩容”解决因为问题根源不在资源在语义层面。可观测系统必须能捕捉这类“软故障”——不是看有没有抛异常而是看输出和预期之间的语义距离是不是拉大了。2.2 观测对象从“进程”变成“上下文”传统APM的观测粒度是进程和接口数据模型是围绕服务展开的。你有一个订单服务就有一个订单服务的指标面板。但Agent的核心业务单元是一个“对话上下文”用户说了一句话Agent带着历史记忆、检索到的文档块、工具返回的数据生成一个回答。这个上下文跨越多个进程、多个工具、多轮LLM调用它是逻辑上的一个整体物理上却散落在各处。于是可观测数据模型必须发生一次范式转移——以session/thread为主线把同一次任务的所有事件串起来形成一个“任务视图”。在这个视图下你要能看到用户意图、Agent每一步的思考、每次模型调用的输入输出和成本、每个工具的入参出参、每段检索内容以及它在最终答案里的贡献权重。整个看下来像一集有剧本的纪录片而不是一堆零散的报错堆栈。2.3 关键词评估Evaluation成了可观测的第四支柱传统可观测三支柱是Metrics、Logs、Traces。大模型业务要加上第四根柱子——Evals。原因很简单LLM输出没有确定性的对错你没法用一个状态码判断好坏。但产品要上线总得有个“到底好不好”的量化标准。于是出现了两类评估手段。第一类是离线评估在测试集上跑一批用例用RAGAS、PromptFoo这类框架打分验证某个prompt或模型版本是否更好第二类是在线评估在运行时用规则或LLM-as-a-Judge对线上输出做抽样打分实时监控质量趋势。Evals的意义在于把“模糊的质量”变成“可追踪的分数”。比如回答忠实度faithfulness从95%跌到80%说明检索或模型出问题了工具调用准确率下滑说明模型对工具的JSON Schema理解出了问题。这些分数和传统指标一样能设阈值、能报警、能做趋势分析。可观测平台如果只接trace不接eval就像一个有摄像头但没有安保人员的仓库——东西被搬走了你录下来了但没人意识到这是盗窃。3. 智能体可观测的落地实现从埋点到语义追踪3.1 底层原理OpenTelemetry GenAI语义约定说到落地绕不开OpenTelemetry。当前社区对LLM可观测的语义约定已经比较成熟核心思路是把一次LLM调用建模为一个新的Span类型命名为llm然后在这个Span上挂GenAI规范定义的Attribute。比如gen_ai.request.model模型名比如gpt-4o-minigen_ai.prompt.0.contentprompt内容gen_ai.completion.0.content模型输出内容gen_ai.usage.input_tokens / output_tokenstoken消耗gen_ai.response.finish_reason结束原因是stop还是length还是content_filter这些Attribute会在Trace中完整保留一次模型调用的上下文可用于后续的成本核算、质量分析、幻觉排查。比硬编码打日志好得多因为Otel的SDK天然支持上下文传播、采样、导出你只需要自定义一个span类型剩下的管道复用即可。Agent框架层面的约定也在快速演进。比如很多Agent框架会在执行具体步骤时生成agent_step、tool_execution、tool_result这些span属性里带toolname、input、output。这样一次Agent运行就能对应到一棵Span树顶部是一次task中间串联多个llm调用和工具调用树结构本身就反映了Agent的决策过程。这比传统APM的trace更能反映业务逻辑因为树的形状本身就是信息。3.2 实操用Python给你的Agent打上可观测补丁我以LangGraph为例展示怎么在自定义节点里埋入可观测事件。思路其实不复杂每个节点函数执行完手动往当前span上挂属性。如果你用的是OpenTelemetry官方Python SDK可以先初始化一个tracer。from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter trace.set_tracer_provider(TracerProvider()) trace.get_tracer_provider().add_span_processor( BatchSpanProcessor(OTLPSpanExporter(endpointhttp://your-collector:4317)) ) tracer trace.get_tracer(agent.observability)然后在LangGraph的节点里把关键事件包在span里并打上GenAI语义属性。def call_llm_node(state): with tracer.start_as_current_span(llm) as span: prompt state[prompt] response model.invoke(prompt) span.set_attribute(gen_ai.request.model, model_name) span.set_attribute(gen_ai.prompt.0.content, prompt[:500]) span.set_attribute(gen_ai.completion.0.content, response[:500]) span.set_attribute(gen_ai.usage.input_tokens, response.usage_metadata.get(input_tokens, 0)) span.set_attribute(gen_ai.usage.output_tokens, response.usage_metadata.get(output_tokens, 0)) span.set_attribute(gen_ai.response.finish_reason, response.stop_reason) return {response: response}注意几个地方。第一prompt和completion内容建议做截断别把整个上下文都塞进去既占带宽又泄露隐私第二token数量和finish_reason一定要记这是成本核算和异常截断排查的核心字段第三如果你用LangChain/LlamaIndex这种框架它们内置了LangChain Tracer或OpenTelemetry回调可以不用手写埋点但了解底层原理能让你自己扩展更灵活的工具链。工具调用的tracking也容易漏。很多时候模型没有乱答是工具返回了脏数据或者模型选错了工具。可以在工具执行外层包span记录toolname、请求参数和返回摘要。def execute_tool(tool_name, tool_args): with tracer.start_as_current_span(tool_execution) as span: span.set_attribute(tool.name, tool_name) span.set_attribute(tool.args, json.dumps(tool_args)[:300]) result tools[tool_name](**tool_args) span.set_attribute(tool.result_preview, str(result)[:300]) return result这样一来一次Agent运行就会在Trace里形成一条完整链路用户提问→LLM决策→选工具→工具执行→结果返回→LLM总结回答。任何一个节点出问题都能从Trace里定位。3.3 平台选型从LangSmith到Langfuse到自建Collector选平台之前先想清楚你的约束数据能不能出域、需要多深的定制、团队对开源栈的熟悉度。市面上三类方案各有各的适用面。LangSmith最省心原生支持LangChain/LangGraph生态trace、eval、数据集管理全家桶都有。如果你重度使用LangChain系框架基本是开箱即用缺点是闭源、数据在SaaS端敏感数据场景要先掂量。Langfuse开源自托管支持Otel接入、prompt管理、eval和成本分析适合数据合规要求高、想自己控制数据的团队。部署成本不高一个docker-compose就能跑起来社区活跃度这几年涨得很快。自建OpenTelemetry Collector 向量存储灵活度最高适合有平台工程能力的团队。把trace数据导出到ClickHouse或Jaeger把eval结果写入PostgreSQL再用Grafana做可视化。前期成本高但后续自由度也大因为你可以把所有业务自定义指标都塞进自己的数据模型。选型建议别馋平台全家桶先按自己的核心诉求排序。业务早期最需要的是“看清一次Agent的完整运行轨迹”那就先跑通trace链路中期需要“批量评估prompt和模型改动”再上eval平台后期才会上升到“统一的AI可观测体系”那时候再考虑自建。4. 评估体系设计把“质量”变成可量化的监控指标4.1 RAGAS体系五个指标吃透检索生成质量很多团队把trace接完就以为完工了结果产品出问题还是靠用户投诉发现。真正的智能体可观测必须把evaluation注入到监控链路里。RAGAS是目前用得比较广的RAG评估指标集我列一下核心几个每个都能对应到一类业务问题。faithfulness忠实度回答是否严格基于检索上下文有没有编造内容。幻觉问题直接看它。answer_relevancy答案相关性回答是否和问题对得上答非所问时这个分数会低。context_precision上下文精确率检索到的内容是否大部分是答案需要的检索质量不好的时候它先垮。context_recall上下文召回率检索有没有漏掉关键信息漏检删减时看它。tool_call_accuracy工具调用准确率Agent选工具和参数的准确度这是Agent独有的维度框架里可以自己定义。每个指标按0到1打分。我给个日常用得比较顺的阈值参考指标正常区间需要关注必须处理faithfulness0.850.7-0.850.7answer_relevancy0.80.6-0.80.6context_precision0.70.5-0.70.5context_recall0.750.55-0.750.55tool_call_accuracy0.90.75-0.90.75注意这个表要按你自己业务的容错度调整金融、医疗场景阈值会严很多闲聊助手则没那么苛刻。4.2 在线评估的三种实现方式离线评估好理解难的是线上实时做质量监控。目前主流做法有三种。第一种是LLM-as-a-Judge。用一个更强的模型比如GPT-4o或Claude当裁判给线上Agent的回答打分。优点是评估质量高、接近人工感受缺点是花钱、有延迟所以通常只能抽样做比如每个session抽10%到20%。抽样归抽样趋势效果已经够用——质量下坠不是瞬间的是缓慢漂移的10%的样本足够捕获拐点。第二种是规则启发式。定义若干硬规则比如回答是否包含“根据文档”“我不确定”这类拒答词是否偏离了预设的回复模板是否遗漏了必须包含的实体字段。规则评估几乎零成本响应即时适合做第一道粗筛。第三种是嵌入语义相似度。把用户问题和Agent回答分别做embedding计算余弦相似度低于阈值说明答非所问。这个方法性价比高适合没有预算调大模型裁判的团队尤其配合同义改写能撑住大部分相关性检测。实际项目里我会三层串起来用规则判异常相似度抓漂移LLM Judge做深度抽检。这三层的输出统一汇总到评估看板按小时粒度聚合和trace数据关联。这样发生质量事故时你不光能发现“这个小时answer_relevancy从0.82跌到0.6”还能通过trace去定位到底哪一步出了问题。4.3 成本与性能可观测token不只是钱的问题大模型业务的成本可观测比传统资源监控更敏感因为token支出直接和业务量挂钩稍有疏漏一个月多烧几万块很常见。我建议把成本拆成三个维度监控。一是按功能模块拆。每个Agent任务消耗多少prompt token和completion token用版本号和场景标签聚合能看出哪个功能模块在“吃钱”。比如也许你的数据分析Agent因为context塞了太多无关字段prompt token消耗异常高。二是按模型调用类型拆。大模型应用里常见一个链路多次调用模型意图识别一次、工具选择一次、答案生成一次。每种调用的成本权重完全不同token消耗模型不一样要在trace里给每一次调用打上调用类型标签方便统计和优化。三是按等待时间拆。首token延迟和端到端延迟必须分开看。用户感知的“慢”多半是首token延迟高而后端资源问题往往只会抬高端到端延迟。记录这两个指标并分别设阈值排查时能快速切分“模型推理慢”和“链路逻辑慢”。5. 常见问题排查与避坑实录5.1 现象Agent回答质量莫名下降但系统指标全绿这是我在多个项目里都碰到过的情况。用户反馈集中在“机器人开始乱说话”但打开APMQPS正常、错误率0、平均延迟没波动。这种故障的排查顺序应该先看eval分数趋势而不是基础设施指标。你需要加一个双层视角底层链路看系统健康度上层评估看业务质量两者是正交的必须同时看。第一步打开在线评估看板看faithfulness和context_precision有没有突然下滑。如果faithfulness跌了说明LLM生成时不在依赖检索内容大概率是prompt被改动后指令权重失衡或者检索结果质量太差导致模型选择性忽略如果context_precision跌了问题多半在检索侧——embedding模型换版本了、向量库索引没重建、文档更新后切分策略把关键段落切碎了。第二步去trace里捞几个低分sample看实际prompt和completion内容。这一步能帮你确认问题是“上下文就烂”还是“模型没按上下文答”。如果上下文里本身就混入了一段不相关文档那是检索侧的问题如果上下文是对的但模型瞎编那是prompt或模型参数的问题。顺序对了排查效率能差两倍以上。5.2 现象Trace链路有断裂看不到完整Agent流程自建Otel采集的大坑之一是Agent框架的异步任务和后台worker跑在独立线程上trace上下文传不过去。表现是主trace里只有入口调用后续的LLM和工具调用全部断链。这种情况通常是上下文传播没配置好或者用了异步执行的框架后没有手动传递context。解决思路有两个层级。第一层使用框架自带的callback或tracer像LangChain的LangChainTracer能自动传播上下文用它来接Otel先跑通链路第二层如果自己手写Agent编排必须在异步函数里用oteltrace.use_span()手动激活context。我在LangGraph里踩过这坑当你用astream做流式输出时事件分发是走后台队列的必须为每个worker显式设置父span的context否则所有子事件会变成无父级的孤儿trace。还有一个小细节Agent的循环结构会导致span层级过深像while循环展开后可能有几十层。查看平台默认会折叠超过一定深度的节点这时候需要在Agent代码里对循环步骤做个“标识步数”的逻辑比如在span attribute里加agent.step字段循环第几轮一眼就能看出来排查卡死或重复调用时非常有用。5.3 工具调用为什么不报错却结果不对另一个高频坑是工具的输入输出没打全。很多团队只记录toolname和最终结果忽略了入参和中间状态。结果模型选对了工具但传错了参数返回的就是垃圾数据你光看结果看不出问题在哪。必须在span attribute里同时记录序列化后的入参和返回预览。我习惯在入参里把核心参数截断到300字符返回结果截断到300字符既不会搞爆存储又能定位绝大多数参数传递错误。还有一类问题是工具调用本身有副作用比如写数据库、发消息。传统APM对这类外部副作用是只要HTTP 2xx就认为成功但Agent场景下模型可能在循环里重复调用同一个写操作。这时候要从trace里看工具调用次数和触发间隔设定单session调用上限防止Agent失控反复执行写操作。有些跑偏的Agent在无人监管时几分钟内调用了几十次下单接口这种事一旦发生等用户投诉已经晚了。5.4 数据量爆炸监控系统先把自己搞挂了大模型应用的数据量比传统应用大得多因为prompt和completion都包含大段文本。很多团队接完Otel后第一周就发现Collector内存飙高甚至Agent响应被采集拖慢。解决方式有三板斧按优先级排。一是采样这是必须做的。自采全量文本太奢侈生产环境建议用尾部采样tail-based sampling只保留出错的和高分值异常的trace或者按固定比例采样正常的trace。具体点说可以在Collector里配置tail_sampling策略保留errortrue、eval_score低于阈值的trace其他按10%比例随机采样。二是截断。每个LLM调用的内容不用全量记录。prompt保留前300-500字符加最后200字符因为中间往往是固定的system prompt价值不高completion保留前500字符足够判断质量。若真要全量记录就把它落到对象存储里不要在trace里塞。三是聚合。token用量、成本这些数据无需每个span都落库可以按分钟聚合后上报。用OpenTelemetry的metrics API报Counter/Histogram比逐条trace处理省几个数量级。6. 从“能跑”到“能管”智能体可观测的持续建设回看整个体系的搭建核心思路就一句话别把大模型业务当成传统服务的扩展而是把它当作一种全新的运行时来对待。传统APM的指标、日志、链路仍然有用但它们退居成了“地基”上层必须再盖起“语义观测”和“质量评估”这两层楼——trace要能还原模型每一次决策的思考和依据eval要持续给出质量分数两者交叉才能把“系统没坏”和“业务没错”统一到同一张监控大屏上。以我自己的经验从选型到落地团队最容易犯的错是过早追求平台全家桶花三周时间研究LangSmith还是Langfuse结果连最基础的trace都没跑通。我建议反过来先在现有代码里手动埋点用OpenTelemetry打通一条端到端trace再花一天时间搭一个简易的eval脚本跑通打分等这两件事都顺了再引入生产级平台。工具永远服务于你对问题的理解如果连“一次Agent运行的长什么样”都没亲眼看过上再贵的平台也是纸上谈兵。另外有个小技巧把可观测性和日志分开存储不要混在一张表里。trace数据量级大、访问模式是“按id精确查询”适合ClickHouse或专门的trace存储eval结果量小但需要做聚合分析适合放在PostgreSQL里。混在Elasticsearch里最终会互相拖累查询慢到你想骂人。这套体系搭完你会在某一天收到一条这样的报警“traffic正常但faithfulness连续30分钟低于0.7”。这时候你打开trace看到Agent在第三步把用户意图判断错了选中了错误的工具返回了荒谬的结果再往上是embedding模型版本升级后向量分布出现偏移导致检索召回跑偏。一路顺着查下去每个决策都有证据每个环节都有分数这才是智能体可观测该有的样子。