1. 为什么 LLM 应用上线后反而更让人睡不着觉
做过传统后端服务的同学大概都有个共识:接口上线之后,只要 CPU、内存、QPS、错误率这几条曲线不飘,晚上基本能睡个安稳觉。但换成 LLM 应用和 AI Agent,这套经验基本失效。我身边不少团队都遇到过类似场景——服务监控面板一片绿,用户却在群里反馈“答非所问”“卡住不动”“同一个问题昨天能答今天不行”。你去翻日志,只有一条孤零零的 HTTP 200,耗时 8 秒,看不出任何异常。
这就是 LLM 与 AI Agent 可观测性要解决的核心问题。传统 APM 关注的是“请求是否成功、耗时多少”,而 AgentOps 关注的是“这次推理到底经历了什么、模型为什么这么答、工具调用有没有跑偏、Token 花在哪了”。观测云这类平台之所以被拿来做 AgentOps 底座,是因为它本身具备指标、日志、链路、事件一体化的能力,能把 LLM 调用这种“黑盒”拆成可追踪的链路。
这篇文章面向的是已经在做或准备做 AI Agent 落地的工程师,包括后端、算法、SRE 和平台团队。我会从整体设计思路讲到具体埋点、指标设计、链路追踪、告警配置,再到踩过的坑,尽量给出一套可以直接抄作业的 AgentOps 运维体系。文中涉及观测云的具体配置,我会说明是基于常见实践的合理方案,实际字段名以你所用版本为准。
2. AgentOps 体系整体设计与选型思路
2.1 先搞清楚 AgentOps 和传统 APM 的边界
很多人一上来就想把 LLM 调用塞进现有的 APM 里,结果发现数据模型根本对不上。传统 APM 的 Trace 是“服务 A 调服务 B 调数据库”,层级浅、语义明确。而一个 AI Agent 的一次任务,可能是这样的结构:用户输入 → 意图识别 → 规划(Planner)→ 多次工具调用 → 多次 LLM 推理 → 结果聚合 → 输出。中间还可能嵌套子 Agent,形成树状甚至图状结构。
所以 AgentOps 的数据模型至少要覆盖四层:
- 会话层(Session):一次完整的人机交互,可能包含多轮对话。
- 任务层(Task/Run):Agent 为完成某个目标执行的一次完整流程。
- 步骤层(Step):规划、推理、工具调用、反思等单个动作。
- 调用层(Call):具体的一次 LLM 请求或一次工具 API 请求。
这四层如果用传统 APM 的父子 Span 硬套,会非常别扭。观测云支持自定义 Trace 结构和 Span 属性,这是它能做 AgentOps 底座的关键。我的建议是:会话和任务用 Trace 表示,步骤和调用用 Span 表示,LLM 特有的字段(模型名、Token 数、温度、Prompt 版本)作为 Span 属性挂上去。
2.2 为什么选观测云而不是自己搭一套
自己搭一套可观测体系不是不行,但成本主要在三个地方:数据存储、查询性能和告警联动。LLM 调用产生的数据量比普通接口大得多,一次推理的 Prompt 加 Completion 动辄几千 Token,如果全量存日志,存储成本会迅速失控。
观测云的优势在于它把指标、日志、链路、用户访问、事件放在同一个数据平台里,可以用同一套查询语言做关联分析。比如你想查“某个 Prompt 版本上线后,Token 消耗和错误率的变化”,在割裂的系统里要跨三个平台导数据,在观测云里就是一条查询的事。另外它的告警支持多条件组合,这对 AgentOps 很关键——单纯“错误率超过 5%”没意义,因为 LLM 的“错误”很多时候是语义层面的,需要结合业务指标判断。
选型时我建议重点看三个能力:是否支持自定义 Span 属性、是否支持高基数标签查询、是否能做基于日志内容的告警。这三点决定了你能不能把 Agent 的行为观测清楚。
2.3 数据采集的三种方式与取舍
采集 LLM 调用数据,常见有三种方式,各有适用场景:
| 采集方式 | 实现位置 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| SDK 埋点 | 应用代码内 | 字段最全、可控性高 | 侵入代码、需维护 | 核心业务链路 |
| 网关代理 | API 网关层 | 无侵入、统一 | 拿不到业务语义 | 统一计量、限流 |
| 日志解析 | 日志采集器 | 零侵入 | 字段缺失、延迟高 | 兜底、历史数据 |
我的实践经验是组合使用:核心 Agent 链路用 SDK 埋点保证字段完整,所有 LLM 出口流量在网关层再做一层代理采集用于计量和成本核算,日志解析作为兜底。这样即使某个服务忘了埋点,网关层也能兜住基础数据。
2.4 指标体系的分层设计
AgentOps 的指标不能只有技术指标,必须技术、成本、质量三条线并行。我通常分成四层:
- 基础设施层:CPU、内存、GPU 利用率、显存占用。
- 服务层:QPS、P95/P99 延迟、错误率、超时率。
- LLM 调用层:Token 消耗(输入/输出分开)、首 Token 延迟(TTFT)、生成速度(Tokens/s)、模型调用成功率。
- 业务质量层:任务完成率、工具调用成功率、用户反馈评分、重试率、幻觉率(人工标注或模型评估)。
这四层里,首 Token 延迟和 Token 消耗是最容易被忽视但最该盯的两个指标。TTFT 直接决定用户体感,Token 消耗直接决定账单。很多团队上线后第一个月账单超预算,就是因为没把 Token 按模型、按业务线拆开看。
3. 核心埋点与数据模型实操要点
3.1 Trace 与 Span 的字段设计
埋点做得好不好,直接决定后面能不能查得动。我踩过的最大坑是:一开始只记了模型名和耗时,结果出问题时根本定位不到是哪段 Prompt 导致的。后来重新设计了一套字段,分享给大家。
Trace 级别建议至少包含:
session_id:会话标识,用于串联多轮对话。user_id:用户标识(注意脱敏)。agent_name:Agent 名称或版本。task_type:任务类型,如问答、检索、代码生成。total_tokens:本次任务总 Token。total_cost:本次任务估算成本。status:成功、失败、超时、中断。
Span 级别按类型区分。LLM 调用 Span 建议包含:
llm.model:模型名称与版本。llm.prompt_version:Prompt 模板版本号,这个极其重要。llm.input_tokens/llm.output_tokens:分开记录。llm.temperature/llm.top_p:采样参数。llm.ttft_ms:首 Token 延迟。llm.finish_reason:结束原因,如 stop、length、tool_calls。
工具调用 Span 建议包含:
tool.name:工具名称。tool.input_size/tool.output_size:输入输出大小。tool.status:成功、失败、超时。tool.retry_count:重试次数。
提示:
llm.prompt_version这个字段一定要加。我遇到过线上回答质量突然下降,排查半天发现是有人改了 Prompt 模板没走发布流程。有了版本号,直接按版本对比指标就能秒定位。
3.2 Prompt 与 Completion 的脱敏处理
把完整 Prompt 和 Completion 存进可观测平台,风险和收益并存。收益是排查方便,风险是可能泄露用户隐私、密钥、内部数据。我的做法是分级存储:
- 默认只存 Prompt 的哈希值和长度,不存原文。
- 对确需排查的链路,开启采样存储,采样率控制在 1% 以内。
- 存储前做正则脱敏,过滤手机号、身份证、邮箱、API Key 等模式。
- 敏感业务线直接关闭原文存储,只保留结构化字段。
脱敏正则建议至少覆盖这几类:\d{11}(手机号)、\d{17}[\dXx](身份证)、[A-Za-z0-9]{32,}(长串密钥)、邮箱格式。这些规则在采集端做,不要等到存储端,否则数据已经落盘了。
3.3 上下文与多轮对话的关联
多轮对话的可观测性难点在于:每一轮看起来是独立请求,但实际共享上下文。如果不做关联,你看到的是几十条孤立的 LLM 调用,根本不知道哪几条属于同一次对话。
解决方案是引入session_id和turn_index。session_id标识整个会话,turn_index标识第几轮。这样在观测云里可以按session_id聚合,还原出完整的对话链路。更进一步,可以把每轮的context_tokens也记下来,观察上下文膨胀情况——很多 Agent 跑着跑着变慢变贵,就是因为上下文越滚越大没做裁剪。
3.4 工具调用的可观测性
AI Agent 和普通 LLM 应用最大的区别就是会调工具。工具调用出问题,往往比模型本身出问题更隐蔽。我建议对每次工具调用都记录完整的输入输出摘要(注意脱敏),并单独统计工具的成功率和延迟。
有个容易被忽略的点:工具调用的参数是模型生成的,可能格式错误。比如模型生成了一个不存在的参数名,或者参数类型不对。这类错误在传统监控里看不出来,因为 HTTP 请求可能根本没发出去。所以要在工具执行前做参数校验,并把校验失败单独记为一种错误类型。
4. 基于观测云的完整落地流程
4.1 环境准备与数据接入
假设你已经有一个跑起来的 AI Agent 服务,接下来要把它接入观测云。整体流程分四步:安装采集器、配置数据源、埋点上报、验证数据。
采集器方面,观测云提供 DataKit 作为统一采集入口。部署方式根据你的环境选,容器环境用 DaemonSet,物理机用主机安装。安装完成后,重点配置两个采集器:dk-trace用于接收链路数据,dk-log用于接收日志。如果你的 Agent 是 Python 写的,直接用 OpenTelemetry 的 Python SDK 上报到 DataKit 的 OTLP 端口即可。
配置示例(以 OTLP HTTP 为例):
# datakit.conf 片段 otlp: http: enable: true addr: "0.0.0.0:4318"应用侧配置环境变量:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" export OTEL_SERVICE_NAME="ai-agent-service" export OTEL_TRACES_EXPORTER="otlp"注意:DataKit 的 OTLP 端口默认可能未开启,需要手动在配置文件里打开。我第一次接入时折腾了半小时,最后发现是端口没开。
4.2 埋点代码的编写
以 Python 为例,用 OpenTelemetry 手动埋点。核心思路是:会话开始建 Trace,每个步骤建 Span,LLM 调用和工具调用作为子 Span。
from opentelemetry import trace from opentelemetry.trace import Status, StatusCode tracer = trace.get_tracer("agent.ops") def run_agent_task(session_id, user_input): with tracer.start_as_current_span("agent.task") as task_span: task_span.set_attribute("session_id", session_id) task_span.set_attribute("task_type", "qa") # 规划阶段 with tracer.start_as_current_span("agent.plan") as plan_span: plan = planner(user_input) plan_span.set_attribute("plan.steps", len(plan)) # LLM 调用 with tracer.start_as_current_span("llm.call") as llm_span: llm_span.set_attribute("llm.model", "your-model") llm_span.set_attribute("llm.prompt_version", "v1.2.3") resp = call_llm(user_input) llm_span.set_attribute("llm.input_tokens", resp.usage.input) llm_span.set_attribute("llm.output_tokens", resp.usage.output) llm_span.set_attribute("llm.ttft_ms", resp.ttft) # 工具调用 with tracer.start_as_current_span("tool.call") as tool_span: tool_span.set_attribute("tool.name", "search") result = call_tool(plan) tool_span.set_attribute("tool.status", "success") return result这段代码的关键在于 Span 的嵌套关系。agent.task是父 Span,agent.plan、llm.call、tool.call是子 Span。这样在观测云的链路视图里,你能看到一棵完整的执行树,每个节点的耗时、属性一目了然。
4.3 指标计算与上报
除了链路,指标也要单独上报。Token 消耗、TTFT、任务完成率这些适合做成指标,方便做趋势分析和告警。
from opentelemetry import metrics meter = metrics.get_meter("agent.ops") token_counter = meter.create_counter( "llm.tokens.total", description="Total tokens consumed" ) ttft_histogram = meter.create_histogram( "llm.ttft.ms", description="Time to first token" ) def record_llm_metrics(model, input_tokens, output_tokens, ttft): token_counter.add(input_tokens, {"model": model, "type": "input"}) token_counter.add(output_tokens, {"model": model, "type": "output"}) ttft_histogram.record(ttft, {"model": model})这里有个细节:Token 计数要按输入和输出分开打标签。因为输入和输出的单价不一样,混在一起算成本会失真。另外模型名作为标签,方便按模型维度做成本分摊。
4.4 观测云上的仪表盘搭建
数据上报之后,在观测云上建仪表盘。我一般建三个视图:
第一个是总览视图,放核心指标:QPS、P95 延迟、错误率、Token 消耗速率、成本速率。这个视图给值班同学看,一眼判断系统是否健康。
第二个是 LLM 专项视图,放 TTFT 分布、Token 消耗按模型拆分、Prompt 版本对比、finish_reason 分布。这个视图给算法和业务同学看,用于优化 Prompt 和模型选型。
第三个是 Agent 行为视图,放工具调用成功率、平均步骤数、任务完成率、重试率。这个视图给 Agent 开发者看,用于优化规划逻辑。
仪表盘的查询语句以观测云的 DQL 为例,统计某模型 Token 消耗:
L::llm.tokens.total {model = 'your-model'} [:: 1h] by type具体语法以你所用版本为准,核心思路是按标签聚合、按时间窗口统计。
4.5 告警规则配置
告警是 AgentOps 体系里最容易配错的部分。配太松没意义,配太紧天天误报。我的经验是分三级:
- P0 告警:服务不可用、错误率突增、Token 消耗异常飙升。这类直接电话通知。
- P1 告警:TTFT 超过阈值、工具调用失败率上升、任务完成率下降。这类走群通知。
- P2 告警:成本接近预算、Prompt 版本变更、模型切换。这类走日报。
Token 消耗告警特别值得说。不要用绝对值,要用同比和环比。比如“过去 1 小时 Token 消耗比过去 7 天同期均值高 50%”,这种规则能捕捉到异常,又不会因为业务自然增长而误报。
5. 常见问题与排查技巧实录
5.1 链路断链:为什么 Span 串不起来
这是接入初期最常见的问题。表现是观测云上看到一堆孤立的 Span,没有父子关系。原因通常有三个:一是跨服务调用时没有传递 Trace Context,二是异步任务没有正确继承上下文,三是 SDK 版本不兼容。
排查方法:先看单个服务内的 Span 是否正常嵌套,如果正常,问题就在跨服务传递。检查 HTTP 请求头里有没有traceparent,消息队列的消息属性里有没有带上下文。异步场景(比如 Agent 用线程池或协程并发调工具)要特别注意,OpenTelemetry 的上下文默认不跨线程传播,需要手动 attach。
5.2 数据量爆炸:如何控制成本
LLM 应用的数据量很容易失控。我见过一个团队,上线一周日志量涨了 20 倍,存储费用直接爆表。控制成本的核心是采样和分级。
- 链路数据:核心链路 100% 采集,非核心链路 10% 采样。
- 日志数据:结构化字段全量,原文按需采样。
- 指标数据:全量,但注意控制标签基数。
标签基数是隐形杀手。比如你把user_id作为指标标签,用户一多,时间线数量就爆炸。正确做法是user_id只放在链路和日志里,指标里用user_type这种低基数标签。
5.3 排查速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 回答质量下降 | Prompt 变更、模型切换 | 查 prompt_version、model 标签 |
| 响应变慢 | 上下文膨胀、工具超时 | 查 context_tokens、tool 耗时 |
| 成本突增 | 重试过多、上下文过长 | 查 retry_count、input_tokens |
| 任务中断 | 工具失败、超时 | 查 tool.status、finish_reason |
| 链路断链 | 上下文未传递 | 查 traceparent、异步传播 |
| 数据缺失 | 采集器异常、采样 | 查 DataKit 状态、采样率 |
5.4 几个独家避坑技巧
技巧一:给 Prompt 加版本号并纳入发布流程。Prompt 变更和代码变更一样,要走 review 和灰度。我见过太多因为改了一个词导致线上大面积翻车的案例。
技巧二:对模型输出做结构化校验。如果 Agent 依赖模型输出 JSON,一定要做 schema 校验,校验失败单独计数。这类错误在传统监控里完全不可见。
技巧三:建立成本预算告警。按业务线、按模型设置日预算和周预算,接近阈值就告警。LLM 的成本是线性的,不设预算很容易失控。
技巧四:保留最近 N 轮的原始数据用于复盘。采样可以,但最近 24 小时的原始数据建议全量保留,方便出问题时快速定位。
技巧五:把用户反馈接入可观测体系。点赞点踩、重新生成这些行为,都是质量信号。把它们和 Trace 关联起来,才能形成“技术指标 + 业务质量”的闭环。
6. 从能观测到能优化:AgentOps 的进阶方向
6.1 用可观测数据驱动 Prompt 优化
有了 Prompt 版本和质量的关联数据,优化就有了依据。比如你可以对比 v1.2 和 v1.3 两个版本的完成率、平均 Token 消耗、用户反馈评分,用数据决定哪个版本更好。这比拍脑袋改 Prompt 靠谱得多。
更进一步,可以把失败案例自动收集起来,形成评估集。每次 Prompt 变更前,先在这个评估集上跑一遍,看指标有没有退化。这套流程跑顺了,Prompt 迭代的效率会大幅提升。
6.2 多 Agent 协作的观测
当系统从单 Agent 演进到多 Agent,观测复杂度会指数级上升。这时候需要引入parent_agent和child_agent字段,把 Agent 之间的调用关系也画进链路里。观测云的链路视图支持这种树状结构,关键是埋点时要正确设置父子关系。
多 Agent 场景下,我建议额外关注两个指标:Agent 间通信延迟和任务分发均衡度。前者影响整体响应时间,后者影响资源利用率。
6.3 把可观测性左移到开发阶段
最好的 AgentOps 不是上线后才观测,而是开发阶段就埋好。我现在的做法是:本地开发时就把 Trace 打到本地 collector,开发同学自己就能看到每次调用的完整链路。这样问题在开发阶段就暴露了,不用等到线上。
本地 collector 可以用观测云的 DataKit 单机版,配置简单,资源占用低。开发同学改完代码,跑一次任务,直接在本地看链路,效率比翻日志高得多。
6.4 关于模型评估与可观测的结合
LLM 的“错误”很多时候不是技术错误,而是语义错误。比如回答虽然格式正确,但内容是错的。这类问题靠传统监控抓不到,需要引入模型评估。可以把评估结果作为一个指标上报,和 Trace 关联。这样你就能看到“哪些链路更容易产生低质量回答”,从而针对性优化。
评估可以是人工的,也可以是模型自动评估。自动评估成本低、覆盖广,但准确性有限;人工评估准确但成本高。我的建议是自动评估做全量监控,人工评估做抽样校准,两者结合。
7. 一些实际落地后的体会
这套 AgentOps 体系我在几个项目里落地过,最大的体会是:可观测性不是上线后补的,而是设计时就要考虑的。如果 Agent 的代码结构本身就不清晰,埋点会非常痛苦。反过来,如果一开始就把规划、推理、工具调用拆成独立的模块,埋点就是顺手的事。
另一个体会是,不要追求一步到位。我见过团队想一次性把所有指标都做全,结果拖了两个月还没上线。正确的做法是先接入链路和基础指标,跑起来之后再逐步补充。先有数据,再谈优化。
最后分享一个我觉得特别有用的小习惯:每次线上出问题,排查完之后,把排查过程用到的查询语句存下来,形成自己的“排查手册”。下次遇到类似问题,直接套用,效率翻倍。这套手册积累到一定程度,就是团队最宝贵的运维资产。