生产级Agent平台可观测性建设:从SLO到链路追踪实战
发布时间:2026/9/13 6:44:01 作者:尧图编辑部 阅读量:1,286

接手生产 Agent 平台的可观测性建设时我最深的感受是模型跑得好不好团队讨论得很热闹但平台本身是否健康、用户请求是否真的稳定反而没人能第一时间说清。所谓生产级 Agent 平台并不是把模型 API 封装一下就行它由模型网关、工具调用、知识库检索、多轮记忆、任务调度等多个环节组成任何一个环节抖动都可能让一次看似正常的请求变成一句冷冰冰的 “Agent couldnt generate a response”。要让这种系统有兜底感知核心就是围绕 SLO 搭起指标、日志、链路追踪三位一体的可观测体系。这篇文章会从 SLO 怎么定、指标怎么埋、日志怎么关联、追踪怎么串起来到排障时真正用得上的技巧完整走一遍。1. 生产 Agent 平台的可观测性为什么这么难1.1 Agent 系统与传统服务的本质差异传统后端服务的可观测性很好做。一个 HTTP 请求进来中间调几个数据库或缓存最后返回 200 或 500请求 ID 一路透传日志一查就能定位。但 Agent 平台不是这种模型。一次用户请求进来之后Agent 可能进入一个几十秒甚至几分钟的长任务循环先调用一次模型做意图理解再去检索知识库然后决定调用哪个工具拿到工具结果之后还得再交给模型做下一步判断。这个过程中任意一步都可能重试、超时、失败也可能在某个分支上直接结束。更麻烦的是传统请求的“成功”很好判断状态码是 2xx 就算成功。但 Agent 任务经常会出现“语义失败”。比如任务最终返回了 completed 状态但用户拿到的回答是“我不知道”或者工具调用根本没有产生有效结果。这种情况从系统层面看是成功从用户角度看是失败。如果平台只用 HTTP 状态码和请求完成率来衡量可用性一定会出现监控全绿、用户疯狂投诉的诡异局面。还有一个维度是并发和资源。传统服务可以通过副本数扩容来消化流量但 Agent 平台的瓶颈往往不在自身 CPU而在模型供应商的限流、工具服务的响应速度、向量库的召回耗时。这些外部依赖一旦抖动Agent 自身的资源占用可能很低任务却集体变慢。做过一次线上事故复盘就会发现根因往往不在 Agent 服务本身而在它编排的那个“外部世界”。1.2 SLO 不是指标越多越好而是用户可感知很多团队一开始做 SLO会把所有能采到的指标都放进去GPU 利用率、Token 消耗量、模型调用延迟、工具平均耗时、队列深度、内存水位……列了二三十项看起来非常全面但真到故障时看板根本看不过来。SLO 的本质不是运维指标而是“用户可感知的服务水平目标”。如果用户觉得“任务能在 30 秒内给我一个有价值的回答”是核心体验那你的 SLO 就该围绕这个体验去定而不是围绕某个模型上游的 P99 延迟去定。所以在设计 SLO 之前必须先把用户视角翻译成技术指标。对于 Agent 平台最常见的用户可感知维度有四个任务能不能启动、任务能不能在预期时间内完成、任务完成时有没有实际产出、任务过程中的工具调用是不是靠谱。这四个维度可以进一步映射成可用性、延迟、有效性和工具成功率。真正进入 SLO 清单的指标应该控制在五到七个以内每个都要能回答一个具体的用户问题而不是“看起来很有价值”的系统指标。我见过一个反面案例团队把“LLM API 调用成功率”设为核心 SLO结果模型供应商限流导致调用成功率掉了告警触发大家开始疯狂排查但用户侧的任务其实通过重试和降级策略完成了体验完全没有受损。这种把内部依赖指标当作用户 SLO 的做法只会带来告警噪音和无效的应急响应。SLO 的出发点永远是站在用户位置问一句这次请求你觉得行不行。2. 从需求到落地SLO 的设计与目标拆解2.1 先对齐 SLI延迟、成功率和质量怎么取舍SLO 必须建立在可量化的 SLI 之上。Agent 平台一般建议从四个候选 SLI 里选可用性 SLI所有进入系统的 Agent 任务中最终到达终止状态completed 或 failed且没有在调度层被丢弃的比例。推荐分子分母都排除“用户主动取消”这类人为终止任务否则 SLO 会被非技术行为稀释。延迟 SLI从任务进入平台到最终完成end-to-end latency以及从任务进入平台到用户看到首个 Token 的时间time-to-first-token。前者反映整体效率后者反映用户“开始等待”的感受。流式场景下后者更贴近体验。有效性 SLI任务完成后平台从 Agent 输出中解析出的最终结果是否非空、是否通过答案校验器比如 JSON 格式正确、回答包含关键引用。这一步很多团队会忽略但它是识别“语义失败”最重要的手段。工具调用成功率 SLIAgent 在任务过程中发起的工具调用中最终返回成功状态的比例。这个指标能快速暴露外部依赖问题但不能直接当作总体验指标因为 Agent 可能通过兜底逻辑绕开失败的工具。一句话总结选型思路可用性和延迟是必选有效性和工具成功率按业务场景选。如果是客服 Agent有效性 SLI 比延迟重要如果是自动化编码 Agent工具调用成功率几乎等同于服务质量。千万不能只盯着延迟否则你优化出来的可能是一个回复很快但经常胡说八道的 Agent。2.2 黄金信号在 Agent 场景下的变化传统 RED 方法关注 Rate、Errors、Duration在 Agent 平台里需要变通。Rate 不能只看每秒请求数还要看每秒任务启动数、每秒进入工具调用阶段的次数、每秒模型请求并发数。Errors 也不能只看最终失败要按阶段拆意图识别失败、工具调用超时、模型输出解析失败、上下文超长截断失败。Duration 同样需要分阶段单次 LLM 调用的耗时、工具执行的耗时、Agent 步骤之间的思考耗时、流式输出的网络耗时。另一个容易被忽视的是队列等待时间。Agent 平台通常有调度器任务先入队再执行。当模型上游被限流时队列会快速积压但任务本身的执行耗时可能没有变化。如果只监控任务执行耗时你会看到一个假象一切正常但用户等待时间翻倍。所以建议给 Agent 平台单独加一个 USE 模型的变体Utilization模型并发连接数占上限的比例、Saturation任务队列深度、等待中任务数、Errors超时、取消、限流错误。这套指标和 RED 体系结合才能覆盖 Agent 平台“调度 执行 外部依赖”的完整链路。2.3 制定 SLO 的具体步骤与误差预算明确了 SLI 之后SLO 就是给每个 SLI 设定目标和时间窗口。具体步骤可以这样走先拉一个月的历史数据计算当前各项 SLI 的真实基线。不要凭感觉写“99.9%”如果当前基线只有 98%那 99.9% 会让团队永远处于告警状态。根据业务优先级设定目标。客服场景可能要求“任务在 20 秒内完成”达到 95%编码场景可能要求“工具调用成功率”达到 99%。建议初期设置 2 到 3 个目标不要所有 SLI 都做成 SLO。为每个 SLO 推导错误预算。比如一个月约 100 万任务SLO 是 99% 的完成率那么一个月最多允许 1 万次失败任务。按 30 天折算每天大约允许 333 次失败把这个数值告诉研发团队他们就能量化地评估一次发布是否安全。设置错误预算燃烧告警。推荐使用多窗口法1 小时内错误预算消耗超过 5% 触发警告超过 14.4% 触发紧急告警6 小时内消耗超过 3% 触发警告超过 7.5% 触发紧急告警。这种算法比简单的“错误率高于 P99”更准确能把偶发抖动和持续恶化区分开。需要注意SLO 不是一成不变的。模型升级、工具调整、业务节奏变化都会影响基线。我习惯每个季度重新评估一次目标把“总是达不到的目标”调低把“太容易达到的目标”拉高让 SLO 真正成为驱动改进的牵引力而不是墙上的一张纸。3. 指标体系建设从采集到告警3.1 Agent 平台需要哪些基础指标指标设计的原则是“跟着请求生命周期走”。一个 Agent 任务从进入到结束至少需要覆盖以下关键节点指标名类型说明关键标签agent_task_totalCounter任务总数按状态分类status, workflow, versionagent_task_duration_secondsHistogram任务端到端耗时status, workflow, modelagent_task_error_totalCounter任务错误数error_type, phaseagent_llm_call_totalCounterLLM 调用次数model, provideragent_llm_duration_secondsHistogramLLM 调用耗时注意区分布鲁克model, modeagent_llm_first_token_secondsHistogram首 Token 延迟model, provideragent_tool_call_totalCounter工具调用次数tool, statusagent_queue_depthGauge任务队列深度queue, priorityagent_token_usage_totalCounterToken 消耗便于成本核算model, type(prompt/completion)这里要特别提醒不要给这些指标挂过多的业务标签。比如把“用户 ID”“Session ID”做成标签一到晚上大流量进来Prometheus 的时间序列数量会瞬间爆炸。用户粒度的数据应该放在日志或追踪里指标只保留聚合维度层面的标签最多挂到 workflow、model、tool、phase 级别。3.2 指标采集与存储选型Agent 平台的主流方案仍然是 Prometheus Grafana。业务服务通过 Prometheus SDK 暴露 /metrics再由 Prometheus 定时抓取。对于需要跨服务汇聚的指标建议先用 OpenTelemetry SDK 生成 metric再通过 OTel Collector 统一转发到 Prometheus避免每个语言栈自己实现一套采集逻辑。采集层有个容易被忽略的问题Agent 任务动辄几十秒到几分钟如果用传统的 counter 做增量统计有时任务还没结束就被 Prometheus 抓走了导致指标“半截”。所以长任务建议多埋 Histogram 和 UpDownCounter在任务真正结束时才提交最终结果而不是在任务开始时把“进行中”当作“已完成”。存储选型上小规模直接用 Prometheus 内置 TSDB 就够到了多集群、多租户阶段可以接入 Thanos 或 VictoriaMetrics。Grafana 做看板时多建几个和 SLO 直接对应的视图概览页放 SLI 趋势和错误预算消耗排障页放队列深度和外部依赖错误率。不要做一张塞满几十个 panel 的“大屏”那是给管理者看的仪式感不是给工程师用的排障工具。3.3 告警规则与降噪指标采集只是地基告警才是真正影响日常工作的东西。告警规则设计不好会出现两个极端要么噪音太多群里一天响几十次要么规则太松真正出问题时告警反而没触发。第一条经验不要对原始错误率直接设置阈值告警。比如“错误率 5% 告警”在流量低谷时一次假告警就会触发在流量高峰时错误率 4% 但影响了几千用户可能又不会触发。建议统一用错误预算燃烧率计算。上面已经给了多窗口的推荐值实际落地时可以先用 1 小时窗口做“紧急告警”6 小时窗口做“警告”不要一上来就搞 24 小时窗口的静态阈值。第二条经验每条告警必须携带跳转链路。Prometheus 的 Alertmanager 只负责发出告警但告警文本里至少要有 trace_id、task_id 和日志查询链接。否则告警发出去了人还要手动打开两三个系统去查这台机器、这个服务、这个时间段的日志等查完黄花菜都凉了。建议在 Agent 服务的异常日志里统一打点 task_id告警模板里用模板语法拼出日志平台的搜索 URL一键跳转。第三条经验用静默机制处理已知故障。模型供应商大范围故障时所有依赖该上游的任务都会失败告警会像瀑布一样涌进来。在维护窗口内建议按 provider 维度设置静默规则同时保留“上游故障识别”的独立看板。等故障恢复后再复盘不要让工程师把精力消耗在主告警之外的二三十条衍生告警上。4. 日志体系建设结构化、关联与闭环4.1 Agent 日志的独特挑战日志是排查 Agent 问题时最直接的依据但 Agent 平台的日志比传统服务更难处理。一个任务内部可能有多次模型的 Prompt 和 Response每次可能几百上千 Token如果全部打日志一天下来存储成本会非常惊人。更麻烦的是这些模型交互内容往往包含用户隐私和业务敏感数据比如用户上传的简历、代码仓库里的源码直接格式化输出到日志系统会带来严重的安全风险。所以日志设计的第一原则不是“尽可能全”而是“哪些必须记、哪些坚决不记”。事件类日志何时调用模型、耗时多少、工具结果是否成功必须记Payload 类日志Prompt 全文、模型原始回复、工具入参必须限制采样和脱敏。简单说日志负责回答“这个任务经历了什么”Payload 负责回答“这个环节具体说了什么”两者解耦。我还遇到过一种比较隐蔽的问题Agent 任务在子线程里异步执行开发人员在主线程打日志时日志系统已经拿不到当前上下文里的 trace_id 和 task_id。结果就是日志散落在各个服务里完全串不起来。要解决这个问题必须在框架层统一封装日志上下文管理器让子线程、协程、异步回调都自动继承父链路 ID而不是靠每个工程师自己记得在日志里加一个 task_id 字段。4.2 日志规范与关联 ID 设计无论底层用 Elasticsearch 还是 Loki统一推荐结构化 JSON 日志这样日志平台可以按字段索引和聚合。一个 Agent 平台的标准日志字段至少包含{ timestamp: 2025-03-01T12:00:01.123Z, level: INFO, service: agent-orchestrator, trace_id: d6f0a8c1e2b34f5a, task_id: task_8f91ab, session_id: sess_9932cd, event_type: llm_request, model: claude-sonnet-4, duration_ms: 1543, status: ok, message: LLM call completed }字段里的 event_type 是整个日志体系的核心。建议按 Agent 生命周期统一枚举task_start、task_end、llm_request、llm_response、tool_call、tool_result、memory_read、message_delivered、error、timeout。每个枚举值都有固定的校验字段这样做有两个好处一是日志平台可以直接按 event_type 统计事件分布二是后端拿到日志后可以直接渲染出一个任务的“时间线”而不需要逐条读 message。关联 ID 的传递要从最外层开始。用户请求经过 API 网关时网关生成一个全局唯一的 task_id同时透传给 Agent 编排服务、模型网关、工具服务。建议用 W3C traceparent 格式做链路传递再由 OpenTelemetry 自动把 trace_id 和 correlation id 打点到日志里。这里有一个实操细节不要用随机字符串做 task_id建议带时间前缀比如 task_20250301120001_8f91ab日志排障时能快速判断任务是哪一分钟进入的不用再多加一个时间字段。4.3 日志采集、清洗与存储采集方案推荐统一采用 Filebeat 或 Fluent Bit把服务本地日志转发到 Kafka再由日志处理管道写入 Elasticsearch 或 Loki。Agent 平台有较强的突发流量特性直接串行写入存储容易丢日志中间加一层 Kafka 做缓冲非常有必要。清洗管道主要做三件事。第一是字段规整把不同服务打点不规范的时间格式统一转成 RFC3339把缺失的 service 字段补上默认值。第二是隐私脱敏用正则或词表识别 JSON 里的 token、password、apiKey 字段命中直接替换成[REDACTED]。第三是日志裁剪一条日志超过预设大小比如 4KB时截断 message 字段但保留关键字段。这几个环节建议在 OTel Collector 或 Logstash 里做尽量不要在业务代码里反复改格式。存储层需要区分冷热。近 7 天日志放到热存储支持全文检索超过 7 天、小于 30 天的日志放到冷存储只保留字段索引不上全文超过 30 天的原始日志直接删除或按天汇总成指标后保存到 Prometheus。这样做不是为了省这几台机器而是为了控制排障时的查询范围——没有谁会花半小时去翻三个月前的单条日志真正需要的是“某个模型版本发布后错误率变化了多少”这样的聚合信息。5. 链路追踪一次 Agent 任务如何被串起来5.1 追踪层级设计链路追踪是 Agent 平台排障的关键也是大多数人刚开始最容易做错的部分。如果你只给 Agent 框架打一个大的 span那只能看到“这个任务耗时多少”根本定位不到到底是模型调用慢还是工具调用慢。反过来说如果给每一个小循环都打 span一个任务可能会生成上百个 span存储和查询成本都会失控。推荐按四层建模第一层是入口层用户请求触发的整个 Agent 执行过程对应一个根 span名称建议为agent.run属性里记录 task_id、user_id、workflow 名称。第二层是编排层Agent 框架的每次循环、每个阶段规划、工具调用、反思、记忆更新单独建 span名称类似agent.step、agent.tool_decision、agent.memory_update。第三层是模型调用层每次 LLM 请求都建立一个 child span名称统一为llm.call属性里记录 model、provider、prompt_tokens、completion_tokens、temperature。第四层是外部依赖层工具调用、知识库检索、缓存访问各自建 span名称建议直接用工具名比如tools.search_docs、tools.execute_sql。层级确定之后还要约定 span 的命名规范。不要用span-1、span-2这种难以理解的名字建议用小写点分格式同一个模块保持稳定。稳定命名看起来是小问题但等你要做跨请求聚合分析时如果每个开发都随手起名Trace 页面会变成一千个不同的 span 名称完全没法统计耗时分布。5.2 采样策略与 OpenTelemetry 实践Agent 任务因为 span 数量大必须认真考虑采样策略。全量采样理论上排障最方便但在生产环境基本撑不住尤其当每个任务包含几十次 LLM 调用和工具调用时存储成本会成倍增加。推荐的做法是“任务级采样 失败全采”。具体来说在 Agent 编排服务创建根 span 时根据 task_id 的 hash 决定本任务是否采样。正常任务按 10% 比例采样带有 error 状态的任务强制保留不进入采样过滤。通过 OpenTelemetry 的 Tail Sampling Processor 实现失败任务全采。Collector 在收到 span 后根据 status 属性判断是否属于失败链路如果是则保留整条 trace否则再按比例采样。对慢任务做 100% 保留。定义“慢任务”为端到端耗时超过 P95 的任务Tail Sampling Processor 里增加一个基于agent.run.duration_ms属性的过滤规则。这样才能保留那些最值得优化的长尾问题。使用 OpenTelemetry SDK 时建议把上下文传播设置为 W3C Trace Context。有些 Agent 框架自带的 tracing 默认只追踪 LLM 调用不追踪自己的编排逻辑需要额外手动创建 span。另外要注意异步任务里必须显式传入父 context否则会生成割裂的 trace两个子 span 不在同一条链路上。还有一个小技巧在 root span 的 attribute 里保存完整任务信息比如task_id、session_id、workflow_version、model_name。这样即使下游 span 采样丢失根 span 还在通过 task_id 仍能从日志平台把整个任务的日志捞出来。5.3 视图与排障哪些 Span 必须打标链路追踪系统建好之后还要定义排障视图。我最常用的几个查询和视图任务耗时分布按agent.run根 span 聚合duration_ms再按 workflow 分组看哪个业务线的任务最慢。Span 耗时 Top N列出单个 Agent 任务里耗时最长的 10 个 span。这是最快定位瓶颈的方式。有一次线上任务平均耗时从 12 秒涨到 20 秒查 Trace 后发现tools.search_docs的耗时从 3 秒涨到了 11 秒根因是知识库索引异常。首 Token 延迟单独看llm.call下的time_to_first_token如果这个指标很高说明模型供应商侧存在问题如果很低但整体耗时长问题大概率出在 Agent 编排工具调用等待上。错误跨服务分布统计 status ERROR 的 span 按 service / span kind 分组能快速看出错误到底集中在模型网关、工具服务还是编排器。排障时还要注意区分“等待”和“执行”。在 Trace 页面里一个agent.stepspan 耗时长可能是模型 API 响应慢也可能是 Agent 框架在做无用的循环思考。建议在 span 属性里加一个agent_step_type区分 thinking、waiting_for_tool、tool_executing、final_answer。这样就能一眼看出时间是花在“思考”还是“等待外部结果”避免盲目去优化模型 Prompt。6. 常见问题与排查技巧实录6.1 指标有但看不出问题归因断裂我遇到过最典型的场景是任务 P95 延迟突然走高但服务的 CPU、内存都正常Prometheus 面板上一片绿色。后来细查才发现模型供应商那边在流式输出过程中出现了持续几秒的网络抖动但我们的指标只统计了“模型调用总耗时”没有统计“首个 Token 到达后的分片间隔”。排障时根本无法判断延迟是模型生成慢还是网络传输慢。这个案例给我的教训是Agent 平台一定要增加流式体验相关的指标比如llm_first_token_seconds和llm_stream_interval。指标不仅能告诉你“慢”还要告诉你“慢在哪个阶段”。如果只埋一个总耗时指标所有问题都会被黑板擦成一团。6.2 日志太多没钱存怎么办日志成本是 Agent 平台不可回避的问题。按每天 10 万任务、每个任务 100 条日志、每条 1KB 算一天会产生 10GB 日志一个月就是 300GB这还不算模型交互的完整 Prompt。应对办法有两个方向一是只保留关键字段原始 JSON 里的 message 可以截断嵌套对象拍平日志体积能减少 40% 到 60%二是事件采样普通的成功任务日志按 10% 保存失败和慢任务日志 100% 保存。我更推荐的做法是“日志只留过程Payload 另存”。模型请求和响应的完整内容放到对象存储或专门的 Trace 存储里设置过期时间日志系统里只保留摘要字段比如 prompt 的字符数、模型回复的首 Token、工具返回的状态。排障时如果真要拿到原结果再通过 trace_id 去对象存储里拉取。这样既保住了排查能力又不会让日志系统变得又臃肿又昂贵。6.3 链路采样丢了关键 Span有一次我们线上爆发工具调用失败但 Trace 库里只能找到一半的链路另一半被吞吐量采样器丢掉了。后来我们对失败任务做了 Tail Sampling又加了一条规则任务状态是 failed 的一定保留整条 Trace工具调用失败的 span 也强制保留。因为失败任务数量相对少这个策略对存储成本的影响很小但给排障带来的收益巨大。这里要特别提醒如果使用门控采样Head Sampling一定要保证根 span 决定采样结果后子 span 能继承这个决定否则下游服务可能各自为政导致同一任务的部分 span 保留、部分丢失最终拼不出完整链路。6.4 告警风暴与 SLO 误报排查告警风暴通常不是因为指标太多而是因为告警规则没有经过“故障场景演练”。比如你把“任务完成率告警”设成 99%结果某个时间点上游数据库主从切换任务完成了但部分工具调用失败任务状态仍然是 completed导致完成率没掉、工具成功率却崩了。如果你的告警里没有覆盖工具成功率这个问题就可能被忽略直到用户投诉。所以我建议把 SLO 告警拆成两层一层是面向用户的核心 SLO 告警包括任务完成率和延迟另一层是针对 Agent 平台自身能力的子 SLO 告警包括工具调用成功率、模型调用成功率、队列积压。核心告警响应用户问题子 SLO 告警暴露内部隐患。两条线并行彼此之间不要互相淹没。最后再分享一个我个人反复验证过的经验可观测性体系不是设计出来的是被真实故障逼出来的。与其一开始铺开几十个指标、上百条日志规范不如先挑一次线上最严重的 Agent 事故从“那次事故我们漏了什么信息”出发反推指标体系、日志字段和 Trace Span 怎么补。把第一轮补齐之后再考虑 SLO 目标怎么定、告警规则怎么降噪。这样搭建起来的三件套每一块都能回答一个真实问题而不是躺在监控面板里的装饰品。