AI Agent 可观测性实战:从日志、追踪到指标的全链路体系设计
前言
在传统软件系统中,可观测性(Observability)已经是一个非常成熟的领域——日志、指标、追踪三大支柱,配合 ELK、Prometheus、Jaeger 等工具链,形成了完整的方法论。
但在 AI Agent 系统中,可观测性面临全新的挑战:
- LLM 调用是非确定性的,同样的输入可能产生不同的输出
- Agent 的决策路径是动态的,每一步调用什么工具、走什么分支,事前无法预知
- 错误不再是”非对即错”,而是质量退化——回答可能没错,但质量下降了
- 性能问题不仅是”慢”,还有 Token 消耗、首字延迟、吞吐等新维度
本文将基于云萱 AI 原生工程平台的实践,详细拆解一套适用于 AI Agent 系统的可观测性体系,包括:
- Tracer 的多后端架构设计
- 4 ID 关联体系
- 12 种 Span 类型覆盖全场景
- Metrics 指标收集
- 如何用可观测性工具定位 Agent 缺陷
一、AI Agent 可观测性的特殊性
1.1 传统可观测性 vs AI 可观测性
| 维度 | 传统系统 | AI Agent 系统 |
|---|---|---|
| 执行路径 | 固定的代码路径 | 动态决策路径 |
| 输出确定性 | 100% 确定 | 概率性输出 |
| 错误定义 | 非对即错 | 质量退化 continuum |
| 性能指标 | 延迟、QPS | TTFT、TPS、Token 消耗 |
| 调试难度 | 可复现 | 难以复现 |
| 成本 | 计算资源 | Token 费用 |
1.2 AI Agent 需要观察什么?
在 AI Agent 系统中,除了传统的错误和延迟,你还需要回答这些问题:
-
功能正确性
- Agent 调用了正确的工具吗?
- 工具返回的结果被正确使用了吗?
- 最终回答和问题相关吗?
-
效率问题
- 完成一个任务需要多少步?
- 有没有重复调用、无效工具调用?
- LLM 调用花了多少钱?
-
质量退化
- 最近 Agent 的回答质量下降了吗?
- 是哪个环节导致的?(模型版本?Prompt 变更?工具变更?)
-
用户体验
- 用户需要等多久才能看到第一个字?
- 整个任务完成需要多长时间?
这就要求我们的可观测性系统不仅要”记录发生了什么”,还要能帮助理解为什么会这样。
二、整体架构:Tracer + Metrics + Logging
2.1 三层架构
我们的可观测性系统分为三层:
┌───────────────────────────────────────────────────────┐
│ 可观测性统一接口层 │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Tracer │ │ Metrics │ │ Logging │ │
│ │ (追踪) │ │ (指标) │ │ (日志) │ │
│ └────┬─────┘ └──────┬───────┘ └───────┬────────┘ │
└───────┼───────────────┼───────────────────┼───────────┘
│ │ │
┌───────▼───────────────▼───────────────────▼───────────┐
│ 多后端适配层 │
│ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ │
│ │ Console │ │LangSmith │ │Langfuse│ │ OTel │ │
│ │ (本地) │ │ (官方) │ │(开源) │ │(标准协议) │ │
│ └─────────┘ └──────────┘ └────────┘ └──────────┘ │
└───────────────────────────────────────────────────────┘
三层各司其职:
- Tracer:记录 Agent 的完整执行轨迹(每一步调用、每一次工具使用、每一个决策)
- Metrics:统计聚合指标(调用次数、成功率、耗时分布、Token 消耗)
- Logging:结构化日志,用于详细排查
2.2 统一 ID 体系
传统分布式追踪只有一个 trace_id,但在 AI Agent 系统中,我们需要4 个层级的 ID 来关联不同维度的数据:
| ID | 作用 | 生命周期 |
|---|---|---|
trace_id | 全局追踪 ID,贯穿一次完整的 Agent 执行 | 一次任务 |
workflow_id | 工作流 ID,关联一次端到端的工作流执行 | 一个工作流 |
session_id | 用户会话 ID,关联同一个用户的多次交互 | 一次会话 |
user_id | 用户 ID,用于按用户维度统计分析 | 长期 |
这种设计让我们可以从不同维度查询数据:
- 按
trace_id查单次执行的完整轨迹 - 按
workflow_id查某个工作流的所有执行 - 按
session_id查一次会话中的所有交互 - 按
user_id统计某个用户的使用情况
三、Tracer 设计详解
3.1 12 种 Span 类型
我们定义了 12 种 Span 类型,覆盖 AI Agent 的全场景:
| Span 类型 | 用途 | 触发时机 |
|---|---|---|
llm_call | LLM 调用 | 每次调用大模型 |
tool_call | 工具调用(开始) | Agent 决定调用工具时 |
tool_result | 工具返回(结束) | 工具执行完成时 |
agent_step | Agent 步骤 | 每个 Agent 推理步骤 |
rag_query | RAG 查询 | 每次向量检索 |
approval | 审批操作 | 人工审批节点 |
security_event | 安全事件 | 检测到安全风险时 |
stage_transition | 阶段流转 | Multi-Agent 阶段切换 |
checkpoint | 检查点 | 到达人工检查点 |
evaluation | 评估执行 | 运行评测时 |
error | 错误 | 发生异常时 |
custom | 自定义 | 用户自定义追踪 |
每种类型都映射到对应的 LangSmith / Langfuse run_type:
_SPAN_TYPE_TO_RUN_TYPE: dict[str, str] = {
"llm_call": "llm",
"tool_call": "tool",
"tool_result": "tool",
"agent_step": "chain",
"rag_query": "retriever",
# ...
}
这样,我们自己的 Tracer 定义了一套标准语义,然后自动映射到各个后端的概念上。
3.2 多后端架构
Tracer 支持 5 种后端,通过策略模式统一管理:
┌──────────────────┐
│ Tracer │
│ (统一接口) │
└─────────┬────────┘
│
┌───────────┼───────────┐
│ │ │
┌─────▼────┐ ┌───▼────┐ ┌───▼─────┐
│ _Base │ │Console │ │ Noop │
│ Backend │ │Backend │ │ Backend │
└─────┬────┘ └────────┘ └─────────┘
│
┌─────▼─────────┬──────────────┐
│ │ │
┌───▼──────┐ ┌──────▼─────┐ ┌─────▼────┐
│ LangSmith│ │ Langfuse │ │ OTel │
│ Backend │ │ Backend │ │ Backend │
│(生产级) │ │ (开源) │ │(标准协议) │
└──────────┘ └────────────┘ └──────────┘
基类定义了统一的接口:
class _BaseBackend:
"""追踪后端基类。"""
def start_span(self, span: TraceSpan) -> None: pass
def end_span(self, span: TraceSpan) -> None: pass
def flush(self) -> None: pass
每个后端实现这三个方法即可。新增后端时,只需要加一个子类,不需要改 Tracer 本身。
后端选择策略
- 本地开发 →
console后端,日志直接输出到终端 - 生产环境(LangChain 生态) →
langsmith后端,功能最完善 - 生产环境(自托管) →
langfuse后端,开源可私有化 - 企业级(标准协议) →
otel后端,对接现有 APM 系统(待实现) - 不需要追踪 →
noop后端,零开销
自动降级机制:如果配置的后端初始化失败,自动降级为 console 后端,保证主流程不受影响。
elif backend == "langsmith":
ls = _LangSmithBackend()
if ls.available:
return ls
logger.warning("[Tracer] LangSmith 不可用,降级为 console 后端")
return _ConsoleBackend()
3.3 Span 的数据模型
每个 Span 包含这些关键字段:
class TraceSpan(PlatformBaseModel):
"""追踪 span。"""
span_id: str = "" # Span 唯一 ID
trace_id: str = "" # 全局追踪 ID
parent_id: str = "" # 父 Span ID(构建树结构)
workflow_id: str = "" # 工作流 ID
session_id: str = "" # 会话 ID
user_id: str = "" # 用户 ID
span_type: str = "custom" # 12 种类型之一
name: str = "" # Span 名称
status: str = "success" # success / failed / blocked / pending
start_time_ms: float = 0.0 # 开始时间
end_time_ms: float = 0.0 # 结束时间
duration_ms: float = 0.0 # 持续时间
input_snapshot: str = "" # 输入快照(用于回放和调试)
output_snapshot: str = "" # 输出快照
metadata: dict[str, Any] = {} # 附加元数据
输入输出快照是 AI Agent 可观测性的关键——你不仅需要知道”调用了一次 LLM”,还需要知道”当时的输入是什么、输出是什么”,才能调试非确定性问题。
当然,生产环境中可以通过配置控制是否开启快照,避免敏感数据泄露和存储成本过高。
3.4 上下文管理器 API
为了让业务代码用起来最简洁,我们提供了上下文管理器形式的 API:
from app.observability.tracer import get_tracer
tracer = get_tracer()
tracer.new_trace()
# 最简单的用法
with tracer.span("处理用户请求", span_type="agent_step"):
# 业务逻辑
result = do_something()
# 带输入输出快照
with tracer.span(
"调用知识库检索",
span_type="rag_query",
input_snapshot=query,
) as span:
results = vector_store.search(query)
span.output_snapshot = str(results) # 手动设置输出
Span 的父子关系自动维护——栈式管理,进入一个 span 就 push,退出就 pop,嵌套的 span 自动建立父子关系。
也支持手动创建和结束:
span = tracer.start_span("工具调用", span_type="tool_call")
try:
result = tool.execute()
tracer.end_span(span, status="success", output_snapshot=str(result))
except Exception as e:
tracer.end_span(span, status="failed", metadata={"error": str(e)})
raise
四、Metrics 指标收集
4.1 四种指标类型
MetricsCollector 支持四种标准的指标类型:
| 指标类型 | 用途 | 示例 |
|---|---|---|
| Counter | 计数器(只增不减) | LLM 调用总次数、Token 总消耗 |
| Gauge | 仪表值(可升可降) | 当前活跃会话数、队列长度 |
| Histogram | 直方图(统计分布) | 延迟分布、Token 数分布 |
| Timer | 计时器(自动记录到直方图) | LLM 调用耗时、工具执行时间 |
from app.observability.metrics import MetricsCollector
metrics = MetricsCollector()
# Counter
metrics.increment("llm_call_total", 1)
metrics.increment("token_used", 1500)
# Gauge
metrics.set_gauge("active_sessions", 42)
# Histogram
metrics.observe("llm_latency_ms", 230.5)
# Timer
metrics.start_timer("tool_execution")
# ... do work ...
metrics.stop_timer("tool_execution")
4.2 统计分析
Histogram 自动计算 P50、P95、P99 等统计值:
stats = metrics.histogram_stats("llm_latency_ms")
# 返回: {
# "count": 100,
# "min": 50.0,
# "max": 500.0,
# "avg": 150.0,
# "p50": 120.0,
# "p95": 350.0,
# "p99": 480.0,
# }
这对于性能退化检测非常有用——当 P95 延迟突然升高时,可能是模型版本变更、API 限流或网络问题。
五、用可观测性定位 Agent 缺陷
有了完整的可观测性数据,我们是怎么定位 Agent 问题的呢?
5.1 典型缺陷定位流程
假设用户反馈”Agent 回答质量下降了”,我们的排查路径:
第一步:看 Metrics 宏观指标
- LLM 调用成功率 → 正常
- 平均响应时间 → 正常
- 工具调用成功率 → 正常
- Token 消耗 → 比上周增加了 30%
Token 消耗增加但成功率没变 → 说明 Agent 可能在”绕弯路”——调用更多步骤才能完成任务。
第二步:按 trace_id 抽样查看
找出最近几个耗时最长、Token 消耗最多的 trace,查看详细轨迹。
第三步:分析 Trajectory
发现某个 trace 中:
agent_step: 分析用户问题
→ tool_call: search_knowledge_base (第一次查询)
→ tool_result: 返回 5 个结果
→ agent_step: 阅读检索结果
→ tool_call: search_knowledge_base (第二次查询,换了关键词)
→ tool_result: 返回 5 个结果
→ agent_step: 综合回答
→ tool_call: search_knowledge_base (第三次查询...)
Agent 进行了 3 次重复的知识库检索——说明 RAG 检索质量有问题,第一次没找到答案,Agent 只好反复尝试。
第四步:定位根因
查看每次 rag_query 的 input_snapshot 和 output_snapshot,发现:
- 第一次查询关键词太宽泛
- 返回的结果虽然相关但不直接
- Agent 只好换关键词再查
根因:知识库的 Chunk 策略需要优化,或者检索算法需要调整。
5.2 可观测性能帮我们发现的问题
| 问题类型 | 怎么发现 | 看什么数据 |
|---|---|---|
| 工具调用错误率高 | tool_call 的 failed status 增多 | tool_result 的错误信息 |
| Agent 决策变慢 | agent_step 的 duration 变长 | LLM 调用耗时 + Token 数 |
| 回答质量下降 | evaluation 得分降低 | 人工抽检 + LLM-as-Judge |
| 安全风险增加 | security_event 数量突增 | 注入检测得分、攻击类型分布 |
| 成本失控 | token 消耗 counter 异常增长 | 每次调用的 Token 数分布 |
| RAG 质量差 | 重复 rag_query 次数增多 | 查询内容 + 返回结果 |
六、实践经验与踩坑
6.1 不要一开始就上复杂方案
很多团队一开始就想上全套 LangSmith + Langfuse + OTel,结果:
- 接入成本太高,迟迟用不起来
- 数据量爆炸,存储成本居高不下
- 团队还没建立”看数据”的习惯
我们的建议:从 console 后端开始,先把追踪点打好,养成看数据的习惯,再逐步升级后端。
6.2 输入输出快照要谨慎
快照是调试 AI 问题的神器,但也是”双刃剑”:
- ⚠️ 敏感数据泄露:用户输入可能包含隐私信息
- ⚠️ 存储成本高:一次 Agent 执行可能有几十次 LLM 调用,每次都存完整对话会很占空间
- ⚠️ 合规风险:某些行业有数据留存的法规要求
建议做法:
- 开发环境全开
- 测试环境采样(比如 10%)
- 生产环境默认关闭,需要时按 trace_id 动态开启
6.3 指标不要贪多
不是所有东西都需要做成 Metric。Metric 的价值在于趋势和告警——如果一个指标你不会设置告警,也不会经常看,那就不要加。
我们的经验是:先加 5 个最核心的指标,用起来了再慢慢加。
核心指标推荐:
- LLM 调用次数 / 失败率
- LLM 平均延迟(P50/P95)
- Token 总消耗(成本)
- 工具调用成功率
- 任务完成率(完整走完全流程的比例)
6.4 Trace 是给人看的,不是给机器看的
设计 Span 的 name 和 metadata 时,要想着”一个工程师凌晨 3 点被叫醒排查问题,他能不能快速看懂”。
好的 Span 命名:
- ✅
search_knowledge_base(清楚地知道在做什么) - ✅
generate_test_cases[TesterAgent](知道哪个 Agent 在做什么) - ❌
step_3(完全不知道在干嘛) - ❌
call_llm(太泛了,哪个模型?什么用途?)
七、未来演进方向
我们的可观测性体系还在持续演进,接下来的方向:
- OpenTelemetry 后端:对接企业现有 APM 系统,统一可观测性栈
- 智能异常检测:基于历史数据自动检测异常(延迟突增、质量下降、成本异常)
- 根因分析(RCA)辅助:自动关联多个 Span,给出”最可能的问题原因”
- Agent Trajectory 质量评分:自动评估 Agent 的决策路径质量(有没有走弯路、是否最优)
- Prompt 版本追踪:把 Prompt 版本也纳入 trace,方便分析”哪个 Prompt 版本效果最好”
结语
AI Agent 的可观测性不是”锦上添花”,而是生产环境的刚需。
没有可观测性的 Agent 系统,就像一辆没有仪表盘的汽车——你能开,但不知道速度多少、油还剩多少、发动机有没有过热。等出了问题,只能瞎猜。
希望这篇文章的实践经验,能帮你搭建起自己的 AI Agent 可观测性体系。