跳转至

可观测性(LangSmith-LangFuse)

6.5 可观测性(LangSmith / LangFuse)


一、核心概念

Agent 上线后的第一个噩梦往往不是功能缺失,而是无法回答"刚才那次失败是哪里出的问题"

传统 Web 服务的日志很简单:一个 HTTP 请求对应一条日志,排查链路清晰。但 Agent 的一次用户请求,背后可能经历:LLM 调用 → 工具触发 → 向量检索 → 再次 LLM 调用 → 另一个工具 → 汇总回答,整个链路跨越 5–10 个独立操作,每步都有自己的延迟、Token 消耗和失败模式。没有专门的可观测性工具,你面对的就是一堆散落的 print() 日志,根本不知道这 12 秒延迟是卡在检索还是卡在 LLM 生成。

可观测性(Observability)在 LLM 应用中的核心价值是:把黑盒的 AI 执行过程,还原成可追溯、可测量、可告警的工程系统。 它借鉴了分布式链路追踪(Distributed Tracing)的思路,用 Trace → Span 两级结构记录一次完整的 Agent 执行:Trace 代表一次用户请求的全貌,Span 代表其中的每个原子操作(一次 LLM 调用、一次工具执行、一次检索)。在此基础上,你才能度量延迟分布、Token 成本、工具成功率,进而配置告警、驱动优化。

目前主流选择是 LangSmith(LangChain 官方出品,与 LangChain/LangGraph 深度集成)和 LangFuse(开源,框架无关,支持自托管)。两者核心理念相同,接入方式略有差异,下文会对比说明。


二、原理深讲

2.1 Trace 采集:Agent 执行链路的 Span 记录

工程动机

你不可能在 Agent 每个步骤里手动打日志,也不应该这样做——那会把业务逻辑和监控代码耦合在一起。可观测性工具的正确姿势是通过 SDK 自动 Hook 框架调用,或在关键位置用装饰器/上下文管理器手动包裹,业务代码基本不需要改动。

核心机制:Trace / Span / Run 三级结构

Trace(一次用户请求)
├── Span: LLM Call(GPT-4o,输入 1200 tokens,输出 350 tokens,耗时 2.1s)
├── Span: Tool Use - search_web(耗时 0.8s,返回 5 条结果)
├── Span: LLM Call(GPT-4o,输入 800 tokens,输出 120 tokens,耗时 1.3s)
└── Span: Tool Use - write_file(耗时 0.05s,成功)

每个 Span 记录:开始时间、结束时间、输入输出内容、模型名称、Token 消耗、错误信息(如有)。Trace 层聚合所有 Span,给出整体延迟和总 Token 成本。

sequenceDiagram
    participant User
    participant Agent
    participant ObsSDK as 可观测性 SDK
    participant Backend as LangSmith/LangFuse

    User->>Agent: 用户请求
    Agent->>ObsSDK: 开启 Trace(trace_id 生成)

    Agent->>ObsSDK: 开启 Span(LLM Call)
    Agent->>Agent: 调用 LLM
    Agent->>ObsSDK: 关闭 Span(记录 tokens/latency)

    Agent->>ObsSDK: 开启 Span(Tool: search)
    Agent->>Agent: 执行工具
    Agent->>ObsSDK: 关闭 Span(记录结果/状态)

    Agent->>ObsSDK: 关闭 Trace
    ObsSDK-->>Backend: 异步批量上报(不阻塞主链路)
    Agent->>User: 返回结果

LangSmith 接入方式(与 LangChain/LangGraph 自动集成,仅需设置环境变量):

# 仅需配置环境变量,LangChain 调用自动被追踪
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls_..."
os.environ["LANGCHAIN_PROJECT"] = "my-agent-prod"

# 以下代码无需改动,Trace 自动上报
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
response = llm.invoke("你好")  # 自动生成 Span

LangFuse 接入方式(框架无关,适合非 LangChain 技术栈或需要自托管):

from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context

langfuse = Langfuse(
    public_key="pk-lf-...",
    secret_key="sk-lf-...",
    host="https://cloud.langfuse.com"  # 或自托管地址
)

@observe()  # 装饰器自动创建 Span
def call_llm(prompt: str) -> str:
    # 任何 LLM 调用框架均可
    response = openai_client.chat.completions.create(...)
    # 手动记录 Token 信息(非 LangChain 时需要)
    langfuse_context.update_current_observation(
        usage={"input": response.usage.prompt_tokens,
               "output": response.usage.completion_tokens}
    )
    return response.choices[0].message.content

@observe(name="agent-run")  # 顶层 Trace
def run_agent(user_query: str):
    result1 = call_llm(user_query)        # 子 Span
    tool_result = execute_tool(result1)    # 另一个子 Span
    final = call_llm(tool_result)
    return final

工程建议:SDK 默认使用异步批量上报,不会阻塞主链路(对延迟影响 < 1ms)。生产环境务必验证这一点,不要误用同步上报模式。


2.2 核心指标:延迟 P99 / Token 消耗 / 工具调用成功率

为什么是 P99 而不是平均值

平均延迟会掩盖长尾问题。如果 95% 的请求在 2 秒内完成,但 5% 的请求需要 30 秒,平均值仍然看起来"正常",但用户体验已经被严重损害。P99(99th Percentile) 告诉你"最差的 1% 用户看到的延迟",是衡量实际体验更有效的指标。

三类核心指标及其工程含义

指标类型 具体指标 工程含义 异常信号
延迟 E2E P99 延迟 用户真实等待体验 突然升高 → 排查 LLM 供应商波动或工具超时
延迟 各 Span 延迟分布 定位瓶颈在哪个步骤 某 Span P99 远超平均 → 该工具/模型异常
成本 每请求 Token 消耗 直接影响 API 费用 突然升高 → Prompt 膨胀或检索结果过多
成本 按 Feature/User 分摊 识别高成本功能 某功能成本是均值 10 倍 → 需要 Prompt 优化
可靠性 工具调用成功率 Agent 任务完成能力 低于 95% → 工具本身稳定性或参数校验问题
可靠性 LLM 调用错误率 API 可用性 突增 → Rate Limit 或供应商故障

LangFuse 指标查询示例(通过 SDK 或 Dashboard):

# 通过 LangFuse SDK 查询聚合指标
traces = langfuse.get_traces(
    project_id="my-project",
    from_timestamp=datetime.now() - timedelta(hours=24)
)

# 计算 P99 延迟
latencies = [t.latency for t in traces.data if t.latency]
p99_latency = sorted(latencies)[int(len(latencies) * 0.99)]

# 统计 Token 成本
total_tokens = sum(
    t.usage.total_tokens for t in traces.data 
    if t.usage
)

工程建议:在 Trace 中打好标签(Tags)和元数据(Metadata),例如 user_idfeature_namemodel_version,这样你能按维度下钻分析(某个用户的成本异常,还是某个功能集中出错)。这一步在接入初期容易被跳过,等后期数据量大了再补标签代价极高。


2.3 告警配置:阈值触发与异常检测

工程动机

Dashboard 是给人看的,告警是给系统"自己发现问题"的。没有告警,你只能靠用户投诉或定时盯大盘来发现问题,两者都是被动且迟滞的。

告警体系设计

graph LR
    A[Trace 数据流] --> B[指标聚合引擎]
    B --> C{规则引擎}
    C -->|P99 延迟 > 15s| D[告警触发]
    C -->|错误率 > 5%| D
    C -->|工具成功率 < 90%| D
    D --> E[通知渠道]
    E --> F[Slack/PagerDuty]
    E --> G[Email]
    D --> H[告警记录]
    H --> I[根因分析面板]

LangSmith 告警配置(通过 Rules 功能,UI 配置为主):

# LangSmith 目前主要通过 UI 配置告警规则
# 也可通过 API 查询后接自建告警

from langsmith import Client
client = Client()

# 查询近 1 小时 P99 延迟,超阈值则触发自建告警
runs = client.list_runs(
    project_name="my-agent-prod",
    start_time=datetime.now() - timedelta(hours=1),
    error=False
)
latencies = [r.total_tokens for r in runs if r.end_time]
# 接入 Prometheus/Grafana Alertmanager 或直接推 Slack

LangFuse + Grafana 告警方案(适合自托管场景的生产级做法):

# LangFuse 支持将指标暴露给 Prometheus
# grafana/alerts/llm-alerts.yaml 示意

groups:
  - name: llm-agent-alerts
    rules:
      - alert: AgentHighP99Latency
        expr: |
          histogram_quantile(0.99, 
            rate(langfuse_trace_duration_seconds_bucket[5m])
          ) > 15
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Agent P99 延迟超过 15 秒"

      - alert: ToolCallHighErrorRate  
        expr: |
          rate(langfuse_span_errors_total{span_type="tool"}[5m]) /
          rate(langfuse_span_total{span_type="tool"}[5m]) > 0.05
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "工具调用错误率超过 5%"

工具链选型建议

场景 推荐方案 理由
LangChain/LangGraph 技术栈 LangSmith 零配置接入,UI 直接支持 Prompt 调试
框架无关 / 自研 Agent LangFuse 自托管 数据不出内网,成本可控
需要集成现有监控体系 LangFuse + Prometheus/Grafana 统一告警平台,避免多套系统
快速验证 MVP LangSmith Cloud 免费版 无需运维,上手 5 分钟

三、工程视角:常见误区与最佳实践

误区 1:只看平均延迟,忽视长尾正确做法:将 P50/P95/P99 都纳入监控面板。LLM 调用的延迟分布往往是重尾分布,P99 可能是 P50 的 5–10 倍。告警阈值应基于 P99,而非平均值。

误区 2:Trace 数据只记录输出,不记录输入正确做法:同时记录每次 LLM 调用的完整输入(Prompt + 上下文)。没有输入,出了问题无法复现,只能靠猜。注意:输入中可能含有用户 PII,上报前需要做脱敏处理,或选择支持本地存储的 LangFuse 自托管方案。

误区 3:接入可观测性后认为"一劳永逸"正确做法:告警阈值需要随着系统成熟而迭代。上线初期阈值设宽松(避免误报淹没告警),稳定后收紧。同时要定期检查告警是否被处理——"告警沉默"(团队习惯性忽略某条告警)是监控体系失效的早期信号。

误区 4:所有 Trace 全量保存,成本爆炸正确做法:根据业务阶段配置采样率(Sampling Rate)。开发/Staging 环境 100% 采样;生产环境高流量场景可降至 10–20%,但对错误请求保持 100% 采样(Error Sampling = 100%)。LangFuse 支持在 SDK 层配置采样率,LangSmith 则通过 Project 设置控制。

误区 5:把 LLM 输入输出当普通日志存,不做结构化正确做法:利用 Tags 和 Metadata 字段记录业务维度信息(功能模块、用户分层、模型版本、实验 ID)。没有这些维度,后期做效果分析和 A/B 对比时,你会发现数据虽然有,但无从切割。


四、延伸思考

🤔 思考题一:可观测性工具记录了完整的 Prompt 输入,这意味着用户的原始提问和业务数据全部上传到了第三方平台(LangSmith/LangFuse Cloud)。在金融、医疗等数据敏感行业,如何在"可观测性"和"数据合规"之间取得平衡?除了自托管,还有哪些折中方案?

🤔 思考题二:当 Agent 系统规模变大(日处理百万 Trace),可观测性平台本身会成为性能瓶颈和成本中心。这是否意味着"可观测性越完善越好"这一直觉是错误的?在实际工程中,如何对可观测性本身进行成本与收益的量化评估?