Skip to content

10.6 可观测性

在上一节中,我们讨论了"反馈闭环"——如何通过用户反馈与自动化评估信号不断迭代 Agent。然而,反馈的前提是看得见。如果 Agent 的每一次推理都像黑箱一样不可观测,那么任何优化都只是盲人摸象。本节将聚焦"可观测性"这一贯穿整个评估与优化体系的基础能力:如何让 Agent 的运行过程透明、可度量、可回溯。

仪表盘类比:想象你在驾驶一架飞机。如果仪表盘上没有任何读数——引擎温度、油耗、高度、速度——你无从判断飞行状态是否正常,更无从在异常时做出正确判断。Agent 系统的"仪表盘"就是可观测性体系:日志是飞行记录仪,指标是仪表上的数字读数,追踪是整条航线的回放。没有仪表盘的飞行,本质上是在赌命。


10.6.1 为什么 Agent 需要可观测性

传统 Web 应用的可观测性通常聚焦于 HTTP 请求延迟、数据库查询耗时、CPU/内存占用等基础设施指标。Agent 系统在此基础上多出几个独特的可观测性需求:

  1. LLM 调用是非确定性的——同样的输入可能产生不同输出,需要完整记录每次调用的输入、输出、模型参数与 Token 消耗。
  2. 多步推理链路长——一个用户请求可能触发数十次 LLM 调用与工具调用的组合,没有端到端追踪几乎无法定位问题。
  3. 成本与质量高度耦合——更强的模型更贵,但效果不一定成比例提升;需要将质量信号与成本信号关联分析。
  4. 用户感知差异大——同一套 Agent 在不同用户、不同场景下表现可能天差地别,需要按维度切片分析。

可以说,Agent 系统的可观测性不只是"运维问题",更是"产品问题"——它直接决定了你能否回答"我的 Agent 到底好不好用"这个核心问题。


10.6.2 可观测性三支柱

在云原生领域,可观测性(Observability)通常被拆解为三大支柱:

可观测性 (Observability)
├── 日志 (Logs)       -> 记录"发生了什么":离散事件,上下文丰富
├── 指标 (Metrics)    -> 量化"表现如何":聚合数值,适合告警
└── 追踪 (Traces)     -> 追踪"完整链路":请求在系统中的流转路径

三者各有侧重又互为补充。在 Agent 场景下,这三者的含义需要被进一步细化:

维度关注指标典型工具
LLM 调用延迟、Token 用量、成功率、模型版本LangSmith
工具调用调用次数、失败率、平均耗时LangFuse
检索质量召回率、相关性分数、命中率Phoenix (Arize)
对话质量用户满意度、任务完成率、轮次分布自定义
成本按模型/用户/功能的成本分摊LangSmith / LangFuse

下面我们逐一介绍主流的可观测性工具及其在 Agent 场景中的实战用法。


10.6.3 LangSmith 实战

LangSmith 是 LangChain 官方推出的 Agent 可观测性平台,与 LangChain 生态深度集成。它的核心能力是通过 @traceable 装饰器自动注入追踪上下文,无需手动埋点。

python
# 从 LangSmith SDK 导入客户端与追踪装饰器
from langsmith import Client, traceable
# 导入 LangChain 的 Tracer 回调,用于自动追踪链式调用
from langchain.callbacks.tracers.langchain import LangChainTracer
import os

# 开启 LangChain V2 追踪协议
os.environ["LANGCHAIN_TRACING_V2"] = "true"
# 设置 API Key(生产环境中应使用密钥管理服务而非硬编码)
os.environ["LANGCHAIN_API_KEY"] = "your-key"
# 指定项目名,所有追踪数据将归集到该项目下
os.environ["LANGCHAIN_PROJECT"] = "my-agent"

# 创建 LangSmith 客户端实例,用于查询和反馈
client = Client()

@traceable(run_type="chain", name="agent_execute")
def run_agent(query: str):
    """带追踪的 Agent 执行函数。
    
    @traceable 会自动捕获函数的输入、输出、执行时间,
    并注入一个 run_id 属性到函数对象上,可用于后续关联反馈。
    """
    # 这里是 Agent 的核心逻辑(示例中简化处理)
    result = {"answer": "处理结果", "tool_calls": 3}
    
    # 为本次运行记录一条结构化反馈(例如由评估器自动评分)
    client.create_feedback(
        run_id=run_agent.run_id,  # run_id 由 @traceable 自动注入
        key="task_complexity",    # 反馈的键名,如"任务复杂度"
        score=0.8                 # 分值,可表示 0~1 的归一化分数
    )
    return result

# 查询项目中的运行记录(可按执行顺序、错误状态等过滤)
runs = client.list_runs(
    project_name="my-agent",      # 限定项目
    execution_order=1,            # 只看顶层运行(不含子调用)
    error=False                   # 排除出错的运行
)

# 遍历并打印每次运行的关键指标
for run in runs:
    print(f"Run: {run.name}, Latency: {run.latency:.2f}s, Tokens: {run.total_tokens}")

关键解读

  • @traceable 是零侵入埋点的核心。它会自动包装函数,在调用前后创建一个"运行"(Run)记录,包含输入、输出、耗时、Token 等信息。
  • create_feedback 将人类评分或自动评估分数与某次运行关联起来,为后续的优化提供标注。
  • list_runs 支持丰富的过滤条件,是事后分析的基础入口。

10.6.4 LangFuse 集成

LangFuse 是一款开源的 Agent 可观测性平台,支持自托管,适合对数据隐私要求较高的场景。它的 API 设计与 LangSmith 类似,但提供了更细粒度的步骤级追踪。

python
# 从 LangFuse SDK 导入主客户端
from langfuse import Langfuse
# 导入装饰器与上下文工具,用于在函数内部更新追踪信息
from langfuse.decorators import observe, langfuse_context

# 初始化 LangFuse 客户端,传入密钥与服务地址
langfuse = Langfuse(
    secret_key="sk-lf-...",                     # 私钥,用于写入数据
    public_key="pk-lf-...",                      # 公钥,用于只读访问
    host="https://cloud.langfuse.com"            # SaaS 或自托管地址
)

@observe()  # @observe 会将函数标记为一个可追踪的"观察"节点
def agent_step(step_name: str, input_data: dict):
    """带 LangFuse 追踪的 Agent 单步执行。"""
    
    # 更新当前追踪的元信息(追踪级别:名称、标签、版本等)
    langfuse_context.update_current_trace(
        name=f"agent_{step_name}",               # 追踪名称,用于在面板中检索
        metadata={"version": "1.2.0"},            # 记录 Agent 版本,便于版本对比
        tags=["production", "agent"]              # 打标签,支持按标签筛选
    )
    
    # 更新当前观察节点的输入与步骤元数据
    langfuse_context.update_current_observation(
        input=input_data,                         # 记录输入数据
        metadata={"step": step_name}              # 记录步骤名
    )
    
    # 执行实际的步骤逻辑(此处为占位)
    result = process_step(input_data)
    
    # 记录输出与资源使用量(Token 与成本)
    langfuse_context.update_current_observation(
        output=result,                           # 记录输出结果
        usage={"tokens": 150, "cost": 0.002}    # 记录 Token 消耗与估算成本
    )
    
    return result

# 事后为某条追踪打分(可用于人工评估或自动评估反馈)
langfuse.score(
    trace_id="trace-xxx",                        # 目标追踪 ID
    name="user_satisfaction",                     # 评分维度名
    value=4.5,                                    # 分值(1~5 制)
    comment="回答准确"                             # 评语
)

与 LangSmith 的对比

对比维度LangSmithLangFuse
部署方式SaaS 为主SaaS + 开源自托管
生态绑定深度绑定 LangChain框架无关
成本按用量收费开源免费(自托管)
数据隐私数据存储在 LangChain 云端可完全私有化

对于数据不能出内网的企业场景,LangFuse 自托管是更稳妥的选择。


10.6.5 成本归因

Agent 的每一次 LLM 调用都涉及真金白银的消耗。如果不能精确地知道"钱花在了哪里",就无从进行成本优化。成本归因的目标是将总开销按模型用户功能模块等维度拆分,让每一分钱都可追溯。

python
# 导入数据类装饰器,用于定义简洁的数据结构
from dataclasses import dataclass, field
# 导入 defaultdict,用于按维度聚合成本
from collections import defaultdict

@dataclass
class CostTracker:
    """追踪每个请求的成本,支持多维度归因。"""
    
    model_costs: dict       # 模型单价表:{model_name: {"input": x, "output": y}}
    records: list = None    # 存储所有成本记录的列表
    
    def __post_init__(self):
        """dataclass 的初始化后钩子,用于初始化可变默认值。"""
        self.records = []   # 避免使用可变默认参数的陷阱
    
    def record(self, model: str, input_tokens: int, output_tokens: int,
               user_id: str = None, feature: str = None):
        """记录一次 LLM 调用的成本。
        
        参数:
            model: 使用的模型名称(如 gpt-4o)
            input_tokens: 输入 Token 数量
            output_tokens: 输出 Token 数量
            user_id: 发起请求的用户 ID(可选,用于按用户归因)
            feature: 功能模块名(可选,用于按功能归因)
        """
        # 计算输入成本:Token 数 / 1000 * 单价
        input_cost = input_tokens / 1000 * self.model_costs[model]["input"]
        # 计算输出成本:同理
        output_cost = output_tokens / 1000 * self.model_costs[model]["output"]
        # 总成本 = 输入 + 输出
        total_cost = input_cost + output_cost
        
        # 将本次调用的完整信息记录下来
        self.records.append({
            "model": model,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cost": total_cost,
            "user_id": user_id,
            "feature": feature
        })
        return total_cost  # 返回本次成本,方便即时展示
    
    def summary(self):
        """生成成本汇总报告,按模型和用户维度聚合。"""
        # 计算总成本
        total = sum(r["cost"] for r in self.records)
        
        # 按模型聚合成本
        by_model = defaultdict(float)
        # 按用户聚合成本
        by_user = defaultdict(float)
        
        for r in self.records:
            by_model[r["model"]] += r["cost"]          # 累加到对应模型
            by_user[r["user_id"] or "unknown"] += r["cost"]  # 累加到对应用户
        
        return {
            "total_cost": total,              # 总成本
            "total_requests": len(self.records),  # 总请求数
            "by_model": dict(by_model),        # 按模型分摊的成本
            "by_user": dict(by_user)          # 按用户分摊的成本
        }

# 使用示例
tracker = CostTracker({
    "gpt-4o": {"input": 0.0025, "output": 0.01},       # GPT-4o 单价(美元/千Token)
    "gpt-4o-mini": {"input": 0.00015, "output": 0.0006} # GPT-4o-mini 单价
})

# 记录一次 GPT-4o 调用:500 输入 Token + 300 输出 Token
tracker.record("gpt-4o", 500, 300, user_id="user_1", feature="chat")

# 打印成本汇总
print(tracker.summary())

成本优化的常见思路

  • 模型降级:对简单任务使用更便宜的模型(如将 GPT-4o 降级为 GPT-4o-mini),可节省 90% 以上成本。
  • 缓存复用:对重复性高的请求引入缓存层,避免重复调用 LLM。
  • Token 裁剪:通过 Prompt 压缩、历史消息摘要等方式减少输入 Token。

10.6.6 Phoenix (Arize) 可观测性

Phoenix 是 Arize 公司推出的开源可观测性工具,基于 OpenTelemetry 标准,特别擅长 Embedding 可视化与检索质量分析。

python
# 导入 Phoenix 主模块
import phoenix as px
# 导入 LangChain 的自动埋点器
from phoenix.trace.langchain import LangChainInstrumentor

# 启动 Phoenix 本地服务(默认端口 6006)
session = px.launch_app()

# 自动检测 LangChain 调用并注入追踪(零代码侵入)
LangChainInstrumentor().instrument()

# 如果需要手动记录自定义 Span,可以使用 OpenTelemetry API
from opentelemetry import trace

# 获取一个 Tracer 实例
tracer = trace.get_tracer(__name__)

# 创建一个名为 "rag_retrieval" 的 Span
with tracer.start_as_current_span("rag_retrieval") as span:
    # 在 Span 上设置自定义属性,用于后续分析
    span.set_attribute("num_docs", 5)              # 检索到的文档数
    span.set_attribute("retrieval_time_ms", 120)   # 检索耗时(毫秒)
    # ... 在此处执行实际的检索逻辑

Phoenix 的独特价值在于它提供了 Embedding 可视化 能力——你可以在面板中直观地看到检索文档在向量空间中的分布,快速发现"语义漂移"或"召回偏差"等问题。


10.6.7 自定义监控面板

在生产环境中,除了使用第三方平台,有时也需要搭建轻量级的自定义监控。下面的代码实现了一个滑动窗口式的 Agent 运行时监控器:

python
import time
# deque 是双端队列,设置 maxlen 后自动丢弃旧数据,适合滑动窗口
from collections import deque
from dataclasses import dataclass, field

@dataclass
class AgentMonitor:
    """Agent 运行时监控器,基于滑动窗口统计。"""
    
    window_size: int = 100  # 滑动窗口大小:保留最近 100 条记录
    
    # 延迟记录队列(自动保留最近 window_size 条)
    latencies: deque = field(default_factory=lambda: deque(maxlen=100))
    # Token 使用量记录队列
    token_usage: deque = field(default_factory=lambda: deque(maxlen=100))
    error_counts: int = 0       # 错误计数
    total_requests: int = 0     # 总请求数
    
    def record(self, latency: float, tokens: int, error: bool = False):
        """记录一次请求的指标。"""
        self.latencies.append(latency)   # 记录延迟(秒)
        self.token_usage.append(tokens)  # 记录 Token 消耗
        self.total_requests += 1          # 总请求数 +1
        if error:
            self.error_counts += 1       # 错误时累加错误计数
    
    def get_stats(self):
        """计算并返回当前窗口内的统计指标。"""
        if not self.latencies:
            return {"status": "no_data"}  # 无数据时返回占位
        
        latencies = list(self.latencies)  # 转为列表以便排序
        tokens = list(self.token_usage)
        
        return {
            "total_requests": self.total_requests,       # 累计请求数
            "error_rate": self.error_counts / max(self.total_requests, 1),  # 错误率
            "latency": {
                "avg": sum(latencies) / len(latencies),            # 平均延迟
                "p50": sorted(latencies)[len(latencies)//2],        # 中位数
                "p95": sorted(latencies)[int(len(latencies)*0.95)], # P95 延迟
                "p99": sorted(latencies)[int(len(latencies)*0.99)]  # P99 延迟
            },
            "tokens": {
                "avg_per_request": sum(tokens) / len(tokens),       # 平均 Token/请求
                "total": sum(tokens)                                # 总 Token 消耗
            }
        }

# 使用示例:模拟 50 次请求
monitor = AgentMonitor()
for i in range(50):
    # 模拟延迟(0~1 秒之间随机)、Token 用量递增、每 20 次出一次错
    monitor.record(time.time() % 1.0, 200 + i * 10, error=(i % 20 == 0))

# 输出统计结果
import json
print(json.dumps(monitor.get_stats(), indent=2))

指标解读

  • P50/P95/P99 延迟:分别表示 50%、95%、99% 的请求在多少时间内完成。P99 延迟是发现"长尾问题"的关键指标——如果 P99 远高于 P50,说明有少量请求异常缓慢。
  • 错误率:错误请求数 / 总请求数。通常生产环境要求错误率低于 1%。
  • 平均 Token/请求:反映 Agent 的 Token 效率,是成本控制的核心指标。

10.6.8 告警体系

光有监控还不够,还需要在指标异常时主动告警。以下是告警体系的设计要点:

告警类型触发条件建议阈值响应动作
错误率告警滑动窗口内错误率超阈值> 5%立即通知值班人员
延迟告警P95 延迟超阈值> 30s降级模型或限流
成本告警日成本超预算> 预算的 120%触发成本优化策略
Token 异常单次请求 Token 超阈值> 10000检查是否存在无限循环
可用性告警连续健康检查失败> 3 次自动切换到备用模型

告警设计的一条重要原则是:宁可少报,不可滥报。频繁的误报会导致"告警疲劳",使真正重要的告警被淹没。


10.6.9 常见误区

在实际工程中,团队在搭建 Agent 可观测性时容易陷入以下误区:

误区一:只记录成功请求

很多团队只追踪正常完成的请求,过滤掉错误请求的追踪数据。这恰恰丢掉了最有价值的信息——错误请求的追踪记录是定位 Bug 的第一手资料。正确做法是全量记录,在分析时再按需过滤。

误区二:日志等同于可观测性

有些团队认为"我们已经有日志了,所以可观测性没问题"。但日志只是三支柱之一。没有指标就无法快速感知系统整体状态,没有追踪就无法理解多步推理的因果链路。三者缺一不可。

误区三:可观测性是上线后才需要的事

不少团队在开发阶段完全不接入可观测性工具,等到上线后遇到问题才匆忙补上。此时往往缺乏历史基线数据,无法判断"是变差了还是一直如此"。正确做法是从第一天起就接入追踪,让可观测性成为开发流程的一部分。

误区四:追踪粒度不当

追踪粒度过粗(只记录顶层请求)会丢失子步骤信息,无法定位具体是哪一步出了问题;追踪粒度过细(记录每行代码)则会产生海量数据,拖慢系统性能。合理的做法是在 Agent 的每次 LLM 调用、工具调用、检索操作处设置追踪节点,既不过粗也不过细。

误区五:忽视成本维度

很多团队只关注延迟和正确率,忽视了成本可观测性。在 Agent 场景下,成本是与质量、延迟并列的第三维度——一个延迟很低、质量很好但每次调用花费 10 美元的 Agent,在实际部署中同样不可接受。


10.6.10 工具选型决策

面对众多可观测性工具,如何选择?以下是一份决策参考:

是否使用 LangChain 生态?
├── 是 -> 优先考虑 LangSmith(集成度最高)
│         └── 数据能否上云?
│             ├── 是 -> 直接使用 LangSmith SaaS
│             └── 否 -> 使用 LangFuse 自托管
└── 否 -> 是否需要 Embedding 可视化?
          ├── 是 -> Phoenix (Arize)
          └── 否 -> LangFuse(框架无关,功能全面)

在实际项目中,也可以组合使用多种工具:例如用 LangSmith 做开发期的追踪分析,用 LangFuse 做生产期的持久化记录,用 Phoenix 做 Embedding 诊断。


10.6.11 本节小结

本节围绕 Agent 可观测性展开了系统性讨论,核心要点如下:

要点说明
三支柱日志(Logs)、指标(Metrics)、追踪(Traces)构成可观测性的基石
Agent 特殊性非确定性、多步链路、成本耦合、用户感知差异要求更细粒度的可观测
LangSmithLangChain 官方追踪平台,@traceable 装饰器实现零侵入埋点
LangFuse开源替代方案,支持自托管,适合数据隐私要求高的场景
成本归因按模型/用户/功能维度追踪 Token 消耗,是成本优化的前提
Phoenix基于 OpenTelemetry 的开源方案,擅长 Embedding 可视化
自定义监控滑动窗口统计 + 分位数延迟 + 错误率,轻量级方案
告警体系错误率、延迟、成本、Token 异常等多维度告警,注意避免告警疲劳
常见误区全量记录、三支柱缺一不可、开发期即接入、追踪粒度适中、重视成本

一句话总结:可观测性不是"锦上添花",而是 Agent 系统的"生命体征监测仪"——没有它,你永远不知道系统是在健康运行还是在慢性崩溃。


至此,第 10 章"评估与优化"的内容已全部讲解完毕。我们从评估指标体系出发,依次讨论了自动评估、人工评估、A/B 测试、反馈闭环,最终落脚于可观测性——这条从"度量"到"看见"再到"改进"的完整链路,构成了 Agent 持续优化的闭环。

然而,无论 Agent 多么智能、可观测性多么完善,安全与风险始终是不可回避的底线问题。Agent 拥有了工具调用、代码执行、网络访问的能力,也意味着它拥有了造成真实世界损害的能力。在第 11 章中,我们将深入探讨 Agent 的安全与风险:从 Prompt 注入防御到工具权限管控,从越权防护到审计合规,确保 Agent 在发挥价值的同时不会成为安全隐患的放大器。