第 10 章 · 可观测性
生产事故的常态不是"系统挂了",而是"用户说 Agent 答非所问,但你连它当时看到什么都不知道"。可观测性解决的是:任何一次 Agent 行为都可以事后完整还原。三件工具:日志(离散事件)、指标(聚合数值)、追踪(请求级因果树)。
10.1 日志:结构化与脱敏
Loguru/logging 的常规实践之外,Agent 系统的特殊纪律:
结构化优先。日志字段比自由文本可查一万倍:
logger.bind(
trace_id=trace_id, chat_id=chat_id, agent_id=agent_id,
tool="query_db", latency_ms=183, ok=True,
).info("tool executed")
敏感信息分级。日志会进聚合系统、会被很多人查到:
- 绝不入日志:API Key、用户 token、密码、完整身份证/手机号;
- 脱敏后入日志:用户消息(保留排查能力,遮蔽 PII);
- LLM 请求/响应用独立的日志通道(它们既敏感又是排查核心,单独存储与权限)。
trace-id 贯穿。一次用户请求经手:路由 → Agent 循环 → 模型调用 → 工具执行 → 子系统。每条日志带同一 trace_id,排查时一把捞全。中间件设置、contextvars 传播(跨异步任务)是标准实现。
10.2 追踪:Agent 的 trace 是一棵树
传统 Web 的 trace 是一条链;Agent 的 trace 是树——一次会话里嵌套着模型调用、工具执行、子智能体,每个都是带输入输出的 span:
trace: 会话 chat_abc (48.2s, $0.31)
├── span: llm call #1 (gpt-4o, 1.2s, in 2.1k tok / out 350 tok) → tool_call×2
├── span: tool: read_file (0.05s, ok, 4.2KB)
├── span: tool: web_search (2.1s, ok, 12 hits)
├── span: llm call #2 (2.8s, …) → tool_call×1
│ └── span: subagent: deep-research (31s, $0.22)
│ ├── span: llm call …×12
│ └── span: tool: web_search …×6
└── span: llm call #3 (3.1s) → final answer
从这棵树能直接读出的排查结论:花费大头在子智能体;第 2 次模型调用后的工具选择了 web_search 而不是内部工具(工具选择问题的线索);token 曲线逐轮上涨(上下文管理问题的线索)。
trace 上下文的传播是工程难点:Agent 是异步的(asyncio create_task 会复制 contextvars),子任务的 span 要挂回父 span。实现模式:Langfuse/OTel SDK + contextvar 保存当前 span,异步任务创建时自动继承——这就是为什么第 1 章强调 trace_id 用 contextvars 而不是函数参数层层传递。
10.3 指标:Agent 的金牌指标
Prometheus 指标四类型(Counter 只增、Gauge 瞬时值、Histogram 分布、Summary 分位)。Agent 系统在红金四信号(延迟/流量/错误/饱和度)之上,加三个金牌指标:
from prometheus_client import Counter, Histogram
llm_calls_total = Counter(
"agent_llm_calls_total", "LLM 调用总次数", ["model", "status"])
llm_latency_ms = Histogram(
"agent_llm_latency_ms", "LLM 调用耗时(ms)", ["model"],
buckets=(100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000))
llm_tokens_total = Counter(
"agent_llm_tokens_total", "LLM token 消耗", ["model", "direction"]) # in/out
task_success_total = Counter(
"agent_task_total", "Agent 任务数", ["agent", "status"]) # success/fail
first_token_latency = Histogram(
"agent_first_token_ms", "首 token 延迟", ["model"],
buckets=(200, 500, 1000, 2000, 4000, 8000))
task_cost = Histogram(
"agent_task_cost", "单任务估算成本", ["agent"],
buckets=(0.01, 0.05, 0.1, 0.5, 1, 5, 20))
三个金牌指标为什么是它们:
- 首 token 延迟:用户感知的"快慢"主要是它(等 30 秒出完整答案 vs 0.5 秒开始流式,体验天差地别);
- 任务成功率:Agent 的"可用性"不是 HTTP 200(模型胡说八道也是 200),是任务级成败;
- 单任务成本分布:均值没意义(长尾任务吃掉预算),要看直方图与 P95。
埋点点位的选择决定可维护性:在 LLM 调用层注册 hook(post-hook 拿到 usage/延迟),模型调用与工具执行自动记账——业务代码零侵入。标签纪律:标签基数必须有限(model/agent/status),绝不放 user_id/chat_id——基数爆炸拖垮时序库,且用户级指标撞隐私红线(用户级分析去 trace/日志里做)。
10.4 OpenTelemetry GenAI 语义约定
自创埋点模型是 2024 年的行业病,2026 年已经收敛:OTel GenAI 语义约定(gen_ai.*)定义了 LLM 调用、Agent、工具的 span 与属性标准,主流 APM(Datadog 等)已原生支持。
核心约定(节选):
| span / 属性 | 含义 |
|---|---|
span 名 chat {model} |
一次 LLM 调用 |
gen_ai.system |
供应商(openai / anthropic / …) |
gen_ai.request.model / response.model |
请求/实际服务模型(路由降级后会不同) |
gen_ai.usage.input_tokens / output_tokens |
token 用量 |
gen_ai.agent.name / gen_ai.agent.id |
Agent 身份 |
gen_ai.tool.name / gen_ai.tool.call.id |
工具执行 span |
gen_ai.prompt.{n}.content(敏感,按需关闭) |
消息内容 |
采用标准的价值:换 APM 供应商零迁移;与社区仪表盘/告警模板兼容;跨团队指标口径统一。自创的价值为零——除非你的场景确实超出约定覆盖。
10.5 SLO:把指标变成承诺
指标堆满看板不等于可观测,**SLO(服务等级目标)**把指标变成可执行的承诺:
SLO-1 可用性: Agent 任务成功率 ≥ 98%(30 天滚动)
SLO-2 响应性: P95 首 token 延迟 ≤ 2s
SLO-3 成本: 单任务 P95 成本 ≤ $0.5,月总成本 ≤ 预算
工程闭环:每条 SLO 对应错误预算(1 - SLO 的量)——错误预算烧穿(如成功率 97.5%)就冻结功能发布、全力修复。这让"要不要为发新功能冒险"从一个政治问题变成一个数字问题。
告警规则示例(PromQL 意识形态):
- alert: TaskSuccessRateLow
expr: sum(rate(agent_task_total{status="success"}[30m]))
/ sum(rate(agent_task_total[30m])) < 0.95
for: 10m # 持续 10 分钟才告警——过滤抖动
- alert: FirstTokenP95High
expr: histogram_quantile(0.95,
sum(rate(agent_first_token_ms_bucket[15m])) by (le)) > 3000
10.6 Agent 特有的排障剧本
常规排查(看日志/指标)之外,Agent 的四类特有故障与排查顺序:
- "答非所问":找 trace → 看该轮的输入上下文(是不是召回污染/历史串味)→ 看工具结果(是不是工具返回了错数据)→ 再看模型输出。顺序很重要:八成问题在上游数据不在模型;
- 死循环:指标上 llm_calls_total 涨但任务完成数不动 → 抓一个 trace 看重复模式 → 是工具报错固执重试还是目标漂移 → 分别对应注入重复检测提示 / 修预算收尾;
- 成本突增:token 曲线按天对比 → 定位是某类会话变长(上下文泄漏)还是某工具返回暴涨(数据面变化)——CostPage/成本账本按维度下钻;
- 间歇性失败:pass^k 暴露 → trace 里对比成败两次的同位差异 → 常见根因是工具偶发超时被模型吞掉或检索命中不稳定。
实现作业
- 给第 2-9 章的 Agent 加完整可观测:结构化日志(trace_id 用 contextvars 贯穿)、Prometheus 指标(10.3 的六件套)、trace 树(用 Langfuse 自托管或 OTel + Jaeger all-in-one 容器);
- 跑一个含子智能体的任务,从 trace 树读出:最贵的 span、工具调用次数、token 随轮数的曲线;
- 写两条 SLO 告警规则(成功率、P95 首 token),人为制造故障(把模型端点改错),验证告警在预期时间内触发;
- 用第 9 章的 pass^k 数据当"任务成功"信号源,接进成功率指标——把离线评测与在线监控打通。
深入材料
- OpenTelemetry GenAI 语义约定(github.com/open-telemetry/semantic-conventions-genai,2026 年起为独立仓库)
- Langfuse 文档(自托管 Agent 追踪的事实标准之一,trace 树与成本追踪的 UX 参考)
- Google SRE Book(SLO/错误预算思想的原典,免费在线)
读者留言
COMMENTS 暂无还没有留言,来说第一句?