你上线了一个 Agent。它调用 3 个工具、跟用户聊 5 轮、搜了 2 次知识库——然后突然开始胡言乱语。你翻日志,看到的是 3 行 INFO: tool executed 和 1 行 ERROR: LLM returned unexpected response。但你不知道:是哪次调用出了问题?是 LLM 抽风了,还是工具返回了错误数据?用户说了什么触发性的词?下一次它还会这样吗?
传统监控告诉你「出事了」,但 Agent 监控需要告诉你为什么出事、在哪出事、还会不会出事。
这篇文章是 AI Agent 工程实战系列的第七篇,前面六篇分别讲了 RAG 检索精度提升、Agent 记忆系统设计、Tool Calling 可靠性与容错、推理延迟优化、状态管理与持久化、多 Agent 编排模式。这一篇讲 Agent 可观测性——把 APM 那一套完整迁移到 Agent 场景。
📌 本系列:一、RAG 检索精度提升实战 → 二、Agent 记忆系统设计 → 三、Tool Calling 可靠性与容错 → 四、推理延迟优化 → 五、状态管理与持久化 → 六、多 Agent 编排模式 → 七、Agent 可观测性(本篇)
一、Agent 可观测性为什么不是「往日志里多写几行」
先看一个真实例子。你的 Agent 运行这样一个流程:
用户提问 → LLM 决定调 API_A → API_A 返回 → LLM 分析结果 → 决定调 API_B → 调知识库 → 生成最终回答
传统做法:每个环节记一行日志。
[10:00:01] 用户输入: "查一下 Q3 销售数据和竞争对手动态"
[10:00:05] 调用 API_A: sales_api
[10:00:08] 返回: {data: [...]}
[10:00:10] 调用 API_B: competitor_api
[10:00:14] 返回: {data: [...]}
[10:00:16] 调用知识库: retriever
[10:00:18] 返回: chunks * 3
[10:00:22] 生成回答(200 tokens)
这堆日志回答不了四个关键问题:
- 因果关系:LLM 为什么决定调 API_A 而不是 API_C?日志里没有 LLM 的推理链。
- 延迟归属:总耗时 21 秒,知识库调用占了 4 秒——但这是网络延迟还是 LLM 响应慢?日志只有开始结束,没有内部阶段。
- 上下文传递:API_B 的参数是 LLM 根据 API_A 的结果动态决定的——如果错了,是 API_A 的数据质量有问题,还是 LLM 推理错了?日志里的独立条目看不出依赖链。
- 非确定性路径:同一输入运行两次,可能一次走了 3 个工具、一次走了 5 个。日志的行数变了,但你不知道哪条路径是「异常」的。
Agent 可观测性的核心差异就在这里:传统 APM 监控的是已知路径上的性能,Agent 监控的是未知路径上的行为。你不知道 Agent 下一步会调用什么工具,所以你不能预先埋好监控点——你需要在运行时动态捕获每一跳(hop)。
二、OpenTelemetry + GenAI Semantic Conventions:打 Trace 的正确姿势
要解决上述问题,你需要把 Agent 的每一次决策、每一个工具调用、每一次 LLM 请求都建模为 Span(跨度)——带 Parent-Child 关系、带属性、带时间线的一组结构化事件。
2.1 Span 设计:Agent 就是你的 Application
把 Agent 看作一个分布式系统,每次用户请求就是一个 Trace。Trace 内部的 Span 按层级组织:
Trace: user-session-abc123
├── Span: agent.run # Root Span — 整个请求生命周期
│ ├── Span: llm.invoke # LLM 调用(第一次:决定用什么工具)
│ │ ├── Span: gen_ai.choice # LLM 决定调用 tool_A
│ │ └── Span: gen_ai.response # LLM 原始输出
│ ├── Span: tool.invoke # 工具调用
│ │ ├── Span: tool_A.execute # 实际执行
│ │ │ ├── Span: http.request # HTTP 请求细节
│ │ │ └── Span: tool_A.parse # 结果解析
│ │ └── Span: tool_A.result_analysis # LLM 分析工具返回结果
│ ├── Span: llm.invoke # LLM 调用(第二次:根据结果决定下一步)
│ │ └── Span: gen_ai.choice # 决定调 tool_B
│ ├── Span: tool.invoke
│ │ └── Span: tool_B.execute
│ ├── Span: retriever.invoke # 知识库检索
│ │ └── Span: vector_search.query
│ ├── Span: llm.invoke # LLM 调用(第三次:生成最终回复)
│ │ └── Span: gen_ai.response
│ └── Span: agent.output # 输出给用户
这个 Span 树回答了所有问题:
- 谁调了谁? — Parent-Child 关系天然表达
- 哪里慢了? — 每个 Span 有开始/结束时间
- 为什么选这个工具? —
gen_ai.choiceSpan 记录了 LLM 的 raw_output - 上下文怎么传的? — 通过 Span 的
attributes标注输入输出
2.2 OpenTelemetry GenAI 属性标准化
2026 年 5 月,OpenTelemetry 在 v1.37+ 中正式推出了 GenAI Semantic Conventions(目前已接近稳定)。这套标准定义了 Agent/LLM 场景的关键属性:
from opentelemetry import trace
from opentelemetry.semconv.ai import GenAiAttributes
tracer = trace.get_tracer("agent-runtime")
with tracer.start_as_current_span("llm.invoke") as span:
span.set_attribute("gen_ai.request.model", "gpt-5.6")
span.set_attribute("gen_ai.request.max_tokens", 4096)
span.set_attribute("gen_ai.response.token_count", 847)
span.set_attribute("gen_ai.response.completion_tokens", 612)
span.set_attribute("gen_ai.tool.name", "sales_api")
span.set_attribute("gen_ai.tool.type", "function")
span.set_attribute("gen_ai.agent.id", "assistant-v3")
span.set_attribute("gen_ai.agent.turn", 2)
关键属性包括:
| 属性名 | 类型 | 用途 |
|---|---|---|
gen_ai.request.model |
string | 模型名称 |
gen_ai.request.max_tokens |
int | 最大 token 限制 |
gen_ai.response.token_count |
int | 总 token 用量 |
gen_ai.response.completion_tokens |
int | 输出 token 数 |
gen_ai.tool.name |
string | 工具名称 |
gen_ai.tool.type |
string | 工具类型(function/mcp/retriever) |
gen_ai.agent.id |
string | Agent 标识 |
gen_ai.agent.turn |
int | 对话轮次 |
gen_ai.choice.id |
string | LLM 选择的 tool call ID |
gen_ai.choice.reasoning |
string | LLM 的推理过程(可选) |
2.3 三种 Trace 方案对比
目前主流的 Agent Trace 方案有三类:
| 维度 | LangFuse | Arize Phoenix | traceAI (Future AGI) |
|---|---|---|---|
| 协议 | MIT | 开源 | Apache 2.0 |
| GitHub ⭐ | 19K+ | 10K+ | ~800 |
| 核心架构 | Observation 中心(V4) | OTel 原生(OpenInference) | 轻量 instrumentation 库 |
| Trace 存储 | 内置(PostgreSQL) | 内置(可挂接外部 backend) | 不自带存储,推 OTel Collector |
| 查询速度 | V4 提升 10x(cursor 分页) | 中等 | 依赖 backend |
| 跨 Agent Trace | 间接支持(Session) | 通过 OTel Context | 原生支持(跨边界 span) |
| 离线审计 | 支持(Export) | 支持(Parquet) | 支持(OTel 原生) |
| 自建可行 | ✅ 中等(PostgreSQL) | ✅ 简单(复用 OTel infra) | ✅ 灵活(只关心中间格式) |
| 学习曲线 | 低 | 中(需 OTel 基础) | 中(纯 instrumentation) |
选型建议:
- 已有 OTel 基础设施 → Arize Phoenix(最干净的集成路径)
- 多 Agent 跨边界追踪 → traceAI(原生跨 Agent span)
- 想要独立可视化 + 查询 → LangFuse(最成熟的开源 UI)
三、Tool Calling Trace 实战:一个完整例子
纸上谈兵够了,让我们看一个真实的 Trace 设计。假设你的 Agent 执行以下工作流:
用户: "查一下我们最大的三个客户是谁,然后看看他们最近一周的订单状态"
Agent 收到请求后,需要:调用 CRM 接口获取客户排名 → 调用订单系统获取订单状态 → 调用知识库了解合同条款 → 汇总成报告。
3.1 代码实现
from opentelemetry import trace
from opentelemetry.propagate import inject
import json, time
tracer = trace.get_tracer("agent-runtime")
tracer_provider = trace.get_tracer_provider()
def handle_user_request(user_input: str, session_id: str):
"""Agent 入口:处理用户请求"""
with tracer.start_as_current_span(
"agent.run",
attributes={
"session.id": session_id,
"agent.name": "customer-support-agent",
"agent.version": "3.2.0"
}
) as root_span:
# 第一步:LLM 分析用户意图
tools_to_call = llm_analyze_intent(user_input)
for tool_name, params in tools_to_call:
# 每个工具调用是一个子 Span
with tracer.start_as_current_span(
f"tool.invoke.{tool_name}",
attributes={
"gen_ai.tool.name": tool_name,
"gen_ai.tool.type": "function",
"tool.params": json.dumps(params)
}
) as tool_span:
if tool_name == "crm_top_customers":
result = crm_top_customers(params)
tool_span.set_attribute("tool.result.count", len(result))
# 工具的 HTTP 调用作为孙 Span
with tracer.start_as_current_span("http.request") as http_span:
http_span.set_attribute("http.method", "GET")
http_span.set_attribute("http.url", "https://crm.internal/api/top-customers")
http_span.set_attribute("http.status_code", 200)
elif tool_name == "order_status":
result = order_status(params)
tool_span.set_attribute("tool.result.orders", len(result))
elif tool_name == "contract_search":
with tracer.start_as_current_span("vector_search.query") as query_span:
query_span.set_attribute("retriever.top_k", 3)
query_span.set_attribute("retriever.score_threshold", 0.7)
result = vector_search(params)
# 最终 LLM 生成回答
with tracer.start_as_current_span("llm.final_response") as final_span:
response = llm_generate(tools_to_call, result)
final_span.set_attribute("gen_ai.response.token_count", response.token_count)
final_span.set_attribute("gen_ai.response.finish_reason", response.finish_reason)
root_span.set_attribute("agent.total_tools", len(tools_to_call))
root_span.set_attribute("agent.total_latency_ms", int(time.time() * 1000 - start_ms))
return response
3.2 Span 属性最佳实践
每个 Span 应该记录以下四类属性:
| 类别 | 属性 | 示例 |
|---|---|---|
| 身份 | gen_ai.agent.id |
customer-support-v3 |
session.id |
sess_abc123 |
|
trace.id |
OTel 自动生成 | |
| 环境 | deployment.environment |
production |
service.version |
3.2.0 |
|
host.id |
pod-7a8b |
|
| 输入/输出 | tool.params |
{"limit": 5} |
tool.result.count |
3 |
|
llm.response.content |
摘要(或 hash) | |
| 性能 | tool.latency_ms |
1204 |
llm.tokens.input |
1523 |
|
llm.tokens.output |
612 |
|
llm.ttft_ms |
420(首个 token 延迟) |
3.3 存储开销 Benchmark
Trace 数据量是个现实问题。我们做了个压力测试:一个 Agent 处理一次用户请求平均产生 15-25 个 Span。如果每天 10,000 次请求:
| 场景 | Span/请求 | 每日 Span 数 | 存储(未压缩) | 存储(压缩 5:1) |
|---|---|---|---|---|
| 简单 QA(1-2 工具) | 8-12 | ~100K | ~800 MB | ~160 MB |
| 复杂推理(3-5 工具) | 15-25 | ~200K | ~1.6 GB | ~320 MB |
| 多 Agent 协作 | 30-60 | ~500K | ~4 GB | ~800 MB |
关键发现:
- 单日 1 万请求在高复杂度场景下产生 4 GB 原始 Trace 数据——这比传统 API 网关的日志量少一个数量级
gen_ai.choice.reasoning(LLM 推理过程)是最大的存储贡献者——一个 reasoning 字段可达 500-2000 token- 建议生产环境对 reasoning 做 采样存储(如 1:10 采样率),或存储 hash + 全量存冷存储
四、LLM 调用监控:LangFuse / LangSmith 集成
Trace 解决了「发生了什么」,但 Agent 场景还有另一个维度——LLM 调用质量和 token 消耗。这是 LangFuse / LangSmith 这类平台擅长的事。
4.1 LangFuse 集成
LangFuse V4(2026 年 3 月发布)引入了 Observation 中心架构,让集成更灵活:
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
langfuse = Langfuse(
public_key="pk-...",
secret_key="sk-...",
host="https://cloud.langfuse.com"
)
@observe()
def agent_process(user_input: str):
# LangFuse 自动捕获 LLM 调用、token、延迟
langfuse_context.update_current_observation(
input=user_input,
session_id="sess_abc123",
metadata={"agent_version": "3.2.0"}
)
# LLM 调用
response = openai.chat.completions.create(
model="gpt-5.6",
messages=[...]
)
# 手动标记 token
langfuse_context.score_current_observation(
name="response_quality",
value=4, # 人工评分或自动评分
comment="正确回答了客户问题"
)
return response
# 查询 token 消耗
traces = langfuse.fetch_traces(
session_id="sess_abc123"
)
for t in traces:
print(f"Total tokens: {t.total_tokens}")
print(f"Cost: ${t.total_cost:.4f}")
关键能力:
- 自动捕获:
@observe()装饰器自动拦截 LLM 请求和响应 - Session 回放:按 session_id 聚合所有 trace,回放完整对话
- Token 消耗追踪:按模型、按 session、按时间聚合
- 人工/自动评分:给每条 trace 打质量分,用于后续分析和模型调优
4.2 LangSmith 集成(侧重评估)
LangSmith 与 LangFuse 定位不同——它更侧重评估和回归测试:
from langsmith import Client, traceable
client = Client()
@traceable(run_type="chain")
def agent_chain(user_input: str):
# LangSmith 的 trace 自动记录每一步
intent = analyze_intent(user_input)
result = execute_tools(intent)
return result
# 运行评估
results = client.evaluate(
dataset_id="customer-support-dataset",
targets=[agent_chain],
evaluators=[response_correctness, hallucination_check]
)
LangFuse vs LangSmith 选型:
| 维度 | LangFuse | LangSmith |
|---|---|---|
| 核心场景 | 生产监控 + Token 追踪 | 评估 + 回归测试 |
| 自建成本 | 中等(开源版自托管) | 高(主要 SaaS) |
| 离线审计 | 支持导出 | 有限 |
| 集成复杂度 | 低(装饰器模式) | 中(需 LangChain 生态) |
| 谁用 | SRE / 运维 | ML 工程师 |
我的建议:两个都用。LangFuse 跑生产监控,LangSmith 跑上线前的回归评估。
五、Agent 行为基线建模
这是 Agent 可观测性最被忽视的部分。你有了 Trace、有了 LLM 监控——但你怎么知道「今天这个 Agent 的表现是正常的」?
答案:基线建模。
5.1 什么是 Agent 行为基线
基线是一套统计学指标,描述 Agent 在「正常」状态下的行为模式。一旦偏离基线,就是告警信号。
核心指标:
| 指标 | 定义 | 正常范围(示例) | 异常信号 |
|---|---|---|---|
| Tool 调用频率 | 每次请求调用的工具数 | 2-4 次 | >8 次(绕圈子)或 0 次(LLM 拒绝调用) |
| Tool 调用延迟 P50 | 单工具响应的中位数延迟 | <500ms | >2s(服务降级) |
| Tool 调用延迟 P99 | 单工具响应的 99 分位延迟 | <3s | >10s(超时临界) |
| LLM 首个 token 延迟 | TTFT | <800ms | >3s(模型拥塞) |
| 错误率 | 工具调用失败占比 | <2% | >10% |
| Choice 切换率 | LLM 改变主意的频率 | 0-1 次/请求 | >3 次(不确定性过高) |
| 回答长度 | 最终回复 token 数 | 200-800 | >2000(啰嗦)或 <20(拒绝回答) |
| Tool 调用链深度 | Agent 调用栈深度 | 1-3 层 | >5 层(循环/级联) |
5.2 建立基线的工程方法
第一阶段:积累期(前 1000 次请求)
只记录不告警。用滑动窗口计算每日均值:
import statistics
from collections import deque
class AgentBaseline:
def __init__(self, window_size: int = 1000):
self.window_size = window_size
self.tool_count = deque(maxlen=window_size)
self.latency_p50 = deque(maxlen=window_size)
self.error_rate = deque(maxlen=window_size)
self.chain_depth = deque(maxlen=window_size)
def record(self, trace_data: dict):
self.tool_count.append(trace_data["tool_count"])
self.latency_p50.append(trace_data["latency_ms"])
self.error_rate.append(trace_data["has_error"])
self.chain_depth.append(trace_data.get("chain_depth", 1))
def get_baseline(self) -> dict:
return {
"tool_count_avg": statistics.mean(self.tool_count),
"tool_count_std": statistics.stdev(self.tool_count),
"latency_p50": statistics.median(self.latency_p50),
"error_rate": sum(self.error_rate) / len(self.error_rate),
"chain_depth_p95": sorted(self.chain_depth)[
int(len(self.chain_depth) * 0.95)
]
}
def is_anomalous(self, trace: dict) -> bool:
bl = self.get_baseline()
if trace["tool_count"] > bl["tool_count_avg"] + 3 * bl["tool_count_std"]:
return True # 工具调用数远超正常
if trace["error_rate"] > bl["error_rate"] * 3 and bl["error_rate"] > 0.01:
return True # 错误率突增 3 倍
return False
第二阶段:稳定期(1000+ 次请求后)
引入时间序列异常检测。用移动平均 + 标准差控制图(SPC — Statistical Process Control):
import numpy as np
from scipy import stats
class SPCAnomalyDetector:
def __init__(self, window_hours: int = 24):
self.window = window_hours
self.hourly_metrics = {} # {hour: [tool_call_counts]}
def update(self, hour: str, tool_calls: int, latency: float):
if hour not in self.hourly_metrics:
self.hourly_metrics[hour] = []
self.hourly_metrics[hour].append({
"tool_calls": tool_calls,
"latency": latency
})
def detect(self, current_hour: str) -> list:
"""返回当前小时的所有异常信号"""
alerts = []
recent_hours = list(self.hourly_metrics.keys())[-24:] # 最近 24h
all_tool_calls = []
all_latency = []
for h in recent_hours:
for m in self.hourly_metrics[h]:
all_tool_calls.append(m["tool_calls"])
all_latency.append(m["latency"])
if len(all_tool_calls) < 50:
return [] # 数据不足
# Z-score 异常检测
current_data = self.hourly_metrics.get(current_hour, [])
for m in current_data:
z_tool = (m["tool_calls"] - np.mean(all_tool_calls)) / np.std(all_tool_calls)
z_latency = (m["latency"] - np.mean(all_latency)) / np.std(all_latency)
if abs(z_tool) > 3:
alerts.append(f"工具调用数异常(z={z_tool:.1f}): {m['tool_calls']}")
if abs(z_latency) > 3:
alerts.append(f"延迟异常(z={z_latency:.1f}): {m['latency']}ms")
return alerts
5.3 行为漂移检测
基线需要定期刷新——因为模型升级、Prompt 变更、工具接口更新都会改变「正常」的定义。
推荐策略:
- 日级基线:滚动 7 天窗口,每日凌晨计算
- 版本标记:每次 Agent 版本更新后重建基线,旧版本基线保留作为对比
- 漂移触发:当连续 3 天的新基线均值偏离旧基线 2σ 以上,标记为行为漂移事件
六、运行时监控 vs 离线审计:两个层面
Agent 可观测性有两个截然不同的消费场景,需要不同的工具链。
6.1 运行时监控(实时层)
目标:秒级发现异常、自动恢复、阻断故障扩散
典型场景:
- LLM 返回格式错误 → 自动重试
- 工具调用超时 → 降级到缓存
- 错误率飙升 → 切换到备用模型
- Token 消耗异常 → 触发预算告警
工具链:
- Prometheus + Alertmanager(指标聚合 + 告警)
- Grafana(实时仪表盘)
- OTel Collector(Span 接收 + 采样)
关键指标(Prometheus 格式):
# Agent 请求总数
agent_requests_total{agent="customer-support", version="3.2.0"}
# Tool 调用延迟分布
agent_tool_latency_seconds_bucket{tool="crm_api", le="0.1"}
agent_tool_latency_seconds_bucket{tool="crm_api", le="0.5"}
agent_tool_latency_seconds_bucket{tool="crm_api", le="1.0"}
agent_tool_latency_seconds_bucket{tool="crm_api", le="+Inf"}
# LLM token 消耗
agent_llm_tokens_total{model="gpt-5.6", type="input"}
agent_llm_tokens_total{model="gpt-5.6", type="output"}
# Agent 错误率
agent_error_ratio{agent="customer-support"}
6.2 离线审计(分析层)
目标:回顾性分析、模式发现、合规审计、模型调优
典型场景:
- 复盘昨天哪些 Agent 请求出了问题
- 发现 Tool 调用链的常见模式(最常见的是哪 3 条路径?)
- 审计特定用户的所有 Agent 交互记录
- 用历史 Trace 生成回归测试集
工具链:
- ClickHouse(列式存储,适合 Trace 查询)
- Parquet + S3(长期归档)
- Jupyter Notebook + Pandas(分析)
典型查询(ClickHouse):
-- 找出今天所有「绕圈子」的 Agent 请求
SELECT session_id, tool_count,
arrayStringConcat(tool_names, ' -> ') as tool_chain
FROM agent_traces
WHERE toDate(timestamp) = today()
AND tool_count > 8
AND tool_names[tool_count] = tool_names[1] -- 最后调用的工具和第一个相同
ORDER BY tool_count DESC
LIMIT 20;
6.3 两级数据流架构
实时层(秒级) ← ← OTel Collector (采样 100%) ← ← Agent Runtime
↓ 采样 10% (reasoning 字段)
↓
离线层(小时级) → → ClickHouse / S3 Parquet → → Notebook 分析
↓
↓ → LangFuse / LangSmith(LLM 质量分析)
关键设计原则:
- 采样策略差异化:性能指标全量采集,LLM reasoning 字段采样存储
- 热/冷数据分离:7 天内热数据(PostgreSQL/ClickHouse),7 天后冷数据(S3 Parquet)
- Trace 与 Metrics 双通道:Metrics 给自动化(告警),Trace 给人(Debug)
七、告警策略设计:从「出事了」到「这里有问题」
Agent 告警与普通 APM 告警的最大区别:很多「异常」不是故障,只是 Agent 走了不同的路径。一个 Agent 多调了 2 个工具可能是绕路了,也可能是它发现需要额外的上下文。
7.1 四级告警体系
P0 — 完全不可用(立即电话/Slack 轮值)
| 条件 | 示例 |
|---|---|
| 所有请求的 Tool 调用成功率 < 50% | 依赖 API 全部超时 |
| 连续 5 分钟无任何成功响应 | LLM 端点 503 |
| Agent 输出包含敏感数据泄露特征 | PII 正则匹配命中 |
| 工具调用出现非预期的 side effect | 删除了生产数据 |
P1 — 功能降级(5 分钟内响应)
| 条件 | 示例 |
|---|---|
| 错误率 > 10%(基线 < 2%) | 工具降级 |
| P99 延迟 > 基线 3x | 模型拥塞 |
| Tool 调用链深度 > 5 | Agent 在绕圈子 |
| Token 消耗 > 日预算的 150% | Prompt 膨胀 |
P2 — 行为异常(工作时间内响应)
| 条件 | 示例 |
|---|---|
| Z-score > 3 的单个指标 | 某个用户的行为基线漂移 |
| Tool 调用频率日环比变化 > 50% | Prompt 变更导致行为变化 |
| LLM 拒绝率上升 > 5% | 偏好冲突或护栏误判 |
P3 — 信息性(记录,不告警)
| 条件 | 示例 |
|---|---|
| 模型版本自动切换 | 灰度发布 |
| 新工具调用模式出现 | 长期行为漂移信号 |
| Agent 配置变更 | 日志审计 |
7.2 减少误报的关键设计
Agent 告警最怕误报——因为 Agent 本来就「不确定」,天天告警会被忽略。
三个过滤器:
-
滑动窗口聚合:不基于单次请求告警,而是基于 5 分钟滑动窗口的聚合指标。一个请求走岔了不算异常,连续 10 个请求都岔了才算。
-
基线自适应:告警阈值不应是固定数字,而是基于基线的动态阈值。如果基线今天变了(如模型升级后 Tool 调用数从 3 增加到 5),告警阈值自动调整。
-
行为 vs 性能分离:性能告警(延迟、错误率)用固定阈值,行为告警(Tool 选择、链深度)用统计阈值。不要混在一起。
7.3 告警响应 Runbook
每个 P0 告警应附带一个 Trace 链接和预分析:
[P0 ALERT] Agent customer-support 错误率 68%(基线 1.2%)
Session: sess_abc123
Trace: https://traces.internal/trace/abc123def456
预分析:
- 最近 10 个请求全部调用 order_api 失败(HTTP 503)
- 其他 3 个 Agent(billing-support, data-analyst)同样受影响
- 根因推测: 订单服务后端故障
建议动作:
1. 将 Agent 的订单模块降级到缓存模式(命令: ./degrade order_cache)
2. 联系订单服务团队
3. 降级后密切监控错误率(预计 < 3%)
这个 Runbook 应该由告警系统自动生成——从 Trace 数据中提取关键信息填入模板。
写在最后
Agent 可观测性本质上是把分布式系统那一套经验迁移到非确定性执行的场景。Span 是分布式 Trace 那一套,Metrics 是 Prometheus 那一套,基线建模是 SPC 控制图那一套——但 Agent 场景让每一层都有了自己的「Agent 味道」。
我踩过的三个坑:
- 不要等出事了再打 Trace。Trace 基础设施应该在 Agent 上线第一天就部署好,因为「上线前的测试」和「生产中的非确定性路径」是两回事。
- 存了 reasoning 但没人看。除非你有专门的 LLM 调试团队,否则在生产环境对 reasoning 做采样存储就够了——全量存只会烧存储,不会提升排查效率。
- 告警阈值设太死。Agent 行为会随着模型升级、Prompt 迭代、工具变更而变化。固定阈值在一周内就会失效,基线必须自动刷新。
这篇暂停在工程层面。说到 Agent 运行时观测,我在做 ClawGuard 时最深的体会是:eBPF 层的无侵入观测和用户态的 Trace 观测应该是互补关系,不是替代关系。eBPF 告诉你「网络包长什么样」,Trace 告诉你「Agent 为什么发了这个包」。两者合在一起,才是真正的手中有图、心中不慌。
下一期:Agent 安全——从 Prompt 注入到供应链攻击的实战防御。这是这个系列的暗线,也该正式展开了。
本系列第七篇。全系列计划约 10 篇覆盖 Agent 工程实战的核心话题。