← 返回博客

AI 应用的可观测性设计:LLM 调用的监控、追踪与调试方法

AI 应用的可观测性比传统后端更复杂——LLM 调用的延迟波动大、耗材与结果强耦合、黑盒输出难以断言。本文从三个层面拆解 AI 应用的可观测性设计:LLM 调用的指标采集、调用链追踪与上下文还原、以及调试与评估的工作流——适合正在将 LLM 集成到生产系统的团队。

先说结论:AI 可观测性不是”加几个日志”就能解决的问题

把 LLM 调用集成到生产系统后,团队会很快发现:传统后端可观测性的三板斧(日志、指标、追踪)在 AI 场景下远远不够。

LLM 调用有三个特性让传统手段失效:

  1. 延迟波动大——同一个模型、同一个 Prompt,响应时间可能差 10 倍
  2. 成本与结果强耦合——响应慢的请求不仅体验差,还更贵(Token 更多)
  3. 输出无法断言——LLM 的文本输出不能用简单的 schema 验证”对错”

本文从三个层面展开:指标采集 → 追踪与上下文还原 → 调试与评估工作流。


1. 指标采集:LLM 可观测性的基础层

1.1 必须采集的指标

类别指标说明聚合方式
延迟TTFT首个 Token 到达时间P50/P95/P99
延迟TPOT每个输出 Token 的生成时间P50/P95/P99
延迟端到端延迟从请求发起到收到完整响应P50/P95/P99
成本输入 Token每次调用的输入 Token 数总和/日均/均次
成本输出 Token每次调用的输出 Token 数总和/日均/均次
成本单次调用成本按模型单价计算日均/月均
质量错误率按状态码/错误类型聚合总占比
质量用户反馈点赞/踩/举报比率
质量响应长度输出字符数均值/分布

1.2 指标采集的实现

// 一个简单的 LLM 调用指标包装器
async function tracedLLMCall(params: {
  model: string;
  messages: ChatMessage[];
  metadata?: Record<string, string>;
}): Promise<LLMResponse> {
  const start = performance.now();
  const traceId = crypto.randomUUID();

  try {
    const response = await openai.chat.completions.create({
      model: params.model,
      messages: params.messages,
      stream: true,
    });

    // 流式响应:记录 TTFT
    let ttft: number | null = null;
    let outputTokens = 0;
    let fullResponse = '';

    for await (const chunk of response) {
      if (!ttft) ttft = performance.now() - start;
      outputTokens += chunk.usage?.completionTokens ?? 0;
      fullResponse += chunk.choices[0]?.delta?.content ?? '';
    }

    const endToEnd = performance.now() - start;

    // 上报指标
    metrics.record('llm.ttft', ttft, { model: params.model });
    metrics.record('llm.e2e', endToEnd, { model: params.model });
    metrics.record('llm.output_tokens', outputTokens, { model: params.model });
    metrics.record('llm.cost', calculateCost(params.model, inputTokens, outputTokens), { model: params.model });

    return { response: fullResponse, traceId, usage: { inputTokens, outputTokens } };
  } catch (error) {
    metrics.record('llm.error', 1, { model: params.model, errorType: error.code });
    throw error;
  }
}

1.3 指标聚合的注意事项

  • 不要只看平均延迟——LLM 调用的延迟是长尾分布,P95 才有意义
  • 按模型和 Prompt 模式分组——不同模型、不同 Prompt 长度,指标差异很大,聚合在一起没意义
  • 成本指标必须与延迟指标关联——一个”慢”的请求往往也是”贵”的请求

2. 追踪与上下文还原

2.1 调用链追踪

AI 应用的一个典型调用链:

用户请求 → 网关 → Agent/编排层 → LLM 调用 → 工具调用 → LLM 调用 → 响应

传统追踪工具(如 Jaeger、Zipkin)可以追踪 RPC 调用,但 LLM 的上下文远比 RPC 复杂——我们需要知道:

  • 这次调用用了什么 Prompt
  • 返回了什么内容
  • 消耗了多少 Token
  • 是否有工具调用(Function Calling)的中间结果

2.2 实现方案

推荐在 OpenTelemetry 基础上,为 LLM 调用定义自定义 Span 属性:

import { Span, trace } from '@opentelemetry/api';

async function tracedLLM(
  model: string,
  messages: ChatMessage[],
  functions?: FunctionDefinition[]
): Promise<LLMResponse> {
  const tracer = trace.getTracer('llm-instrumentation');
  const span = tracer.startSpan('llm.call', {
    attributes: {
      'llm.model': model,
      'llm.request.messages': JSON.stringify(messages.map(m => ({ role: m.role, content: truncate(m.content, 500) }))),
      'llm.request.functions': functions ? JSON.stringify(functions.map(f => f.name)) : '',
    },
  });

  try {
    const response = await openai.chat.completions.create({ model, messages, functions });
    const usage = response.usage;

    span.setAttributes({
      'llm.response.tokens.input': usage?.promptTokens ?? 0,
      'llm.response.tokens.output': usage?.completionTokens ?? 0,
      'llm.response.total_tokens': usage?.totalTokens ?? 0,
      'llm.response.finish_reason': response.choices[0]?.finishReason ?? 'unknown',
      'llm.latency_ms': performance.now() - start,
    });

    // 将完整响应存储到独立 Trace Store(不写入 Span 属性,避免数据过大)
    await traceStore.put(span.spanContext().spanId, {
      messages,
      response: response.choices[0]?.message,
      usage,
    });

    span.setStatus({ code: SpanStatusCode.OK });
    return response;
  } catch (error) {
    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
    span.recordException(error);
    throw error;
  } finally {
    span.end();
  }
}

2.3 敏感数据处理

完整的 Prompt 和 Response 可能包含用户隐私数据。处理策略:

数据级别示例处理方式
元数据Model 名称、延迟、Token 数写入 Span 属性,可聚合
脱敏内容去除 PII 后的 Prompt 片段写入 Trace Store,保留 7 天
原始内容完整 Prompt 和 Response不写入 Trace Store,仅记录 hash 供审计

3. 调试与评估工作流

3.1 在线调试

当用户反馈”AI 回答错了”,传统日志最多告诉你”输入了 X,输出了 Y”——但你要回答的问题是”为什么 AI 给出了这个回答”。

需要的上下文:

  • 完整的 Prompt(System Prompt + User Message + 历史对话)
  • 模型的原始输出
  • Token 消耗和延迟
  • 如果有 Function Calling:工具调用链和中间结果
  • 温度等参数设置

推荐做法:在 Trace Store 中保留完整的调用上下文,支持按 User ID、Session ID、Trace ID 检索。每次用户反馈问题时,运维人员可以一键还原完整的调用现场。

3.2 回归测试(Golden Dataset)

模型升级或 Prompt 修改后,如何确保输出质量没有回退?

建立 Golden Dataset

golden-dataset/
  test-cases/
    - case-001.json    # 简单问答
    - case-002.json    # 多轮对话
    - case-003.json    # 工具调用
    - case-004.json    # 边界情况(空输入、超长输入)
  expected/
    - expected-001.json  # 期望输出(或判断标准)

每次变更后,运行回归测试:

# 跑 Golden Dataset 对比
llm-eval run --dataset golden-dataset/ --model gpt-4o-mini --output results/

# 对比与基准版本的差异
llm-eval diff --baseline results/v1.0/ --current results/v1.1/

3.3 自动评估指标

评估维度方法说明
回答准确率Judge LLM 打分用另一个 LLM 评估”是否准确回答了问题”
幻觉率事实一致性检查输出是否与给定上下文矛盾
指令遵循命令完成度检查是否按要求格式输出、是否遗漏了必填字段
安全合规内容安全检测是否有有害内容、是否泄露了系统 Prompt
语义相似度Embedding 距离输出与期望输出的向量距离

4. 工具链推荐

用途工具说明
指标采集OpenTelemetry + Prometheus标准指标管道的通用方案
LLM 追踪OpenTelemetry + 自定义 Span标准方案,需要自己定义 LLM Span 属性
专用 LLM 观测Langfuse / Arize Phoenix / LangSmith专门为 LLM 设计的观测平台,开箱即用
评估框架deep-eval / RagasGolden Dataset 评估与自动评分
调试 UILangfuse / Weights & Biases Prompts可视化查看 Prompt、Response、Token 消耗

总结

层面核心要点常见误区
指标按模型和 Prompt 模式分组,看 P95 不看平均只加了延迟监控,不知道成本和质量
追踪用 OpenTelemetry Span + 独立 Trace Store把完整 Prompt 写入日志(数据泄露风险)
调试保留完整调用上下文,支持一键还原现场只有”用户说错了”的反馈,没有可复现的上下文
评估建立 Golden Dataset,每次变更跑回归上线后从未评估过输出质量

AI 应用的可观测性投入不是”成本”——它是你唯一能在生产环境中回答”为什么 AI 会这样回答”的工具。 没有可观测性,每次用户反馈”AI 回答错了”都是一次盲人摸象式的排查。

相关阅读

需要 AI 应用的可观测性方案设计?联系我们,说清你的模型栈与规模,24 小时内回可行性。

常见问题

LLM 调用和普通 API 调用在可观测性上有什么本质区别?

三个区别:① 延迟分布不同——普通 API 调用延迟通常在 10-500ms,LLM 调用在 1-30s,且方差极大(同一个模型的同一次调用可能因上下文长度不同而差 10 倍),所以平均延迟指标毫无意义,需要看百分位分布;② 成本与结果强耦合——每次调用都按 Token 数计费,响应慢的请求不仅体验差还更贵,需要同时追踪延迟和 Token 消耗;③ 输出没法做断言——普通 API 有明确的返回结构可以做 schema 断言,LLM 的文本输出无法用简单规则验证正确性,必须引入额外的评估机制。

LLM 调用的哪些指标是最值得关注的?

三类核心指标:① 服务质量——TTFT(首个 Token 到达时间,反映首屏感知延迟)、TPOT(每个输出 Token 的生成时间,反映流式响应速度)、端到端延迟、错误率(按状态码和错误类型聚合);② 成本——输入 Token 数、输出 Token 数、单次调用成本、日均/月均总成本;③ 质量——用户反馈率(点赞/踩)、语义相似度评分(输出与期望答案的 Embedding 距离)、人工抽检通过率。质量指标最难采集但最重要——没有质量指标的成本优化是盲目的。

怎么追踪 LLM 调用的上下文——Prompt、Response、Token 消耗?

建议为每次 LLM 调用生成一个唯一的 trace ID,在应用层手动构造一个 Span 包裹调用:Span 内记录 system_prompt、user_message、assistant_response、input_tokens、output_tokens、model_name、latency_ms。这些 Span 上报到 OpenTelemetry Collector 或专门的 LLM 观测平台(如 Langfuse、Arize Phoenix)。关键设计:不要将完整的 Prompt/Response 写入日志或指标标签(可能包含敏感数据),而是存储到独立的 Trace 存储中,通过 trace ID 关联。

LLM 输出质量怎么在 CI/CD 中做自动化验证?

三层验证:① 格式层——如果输出要求是 JSON,先验证能否被解析,再验证字段完备性(用 Zod 或 Pydantic schema);② 语义层——用另一个 LLM(Judge LLM)评估输出质量,打分维度包括"是否回答了问题"、"是否有幻觉"、"是否遵循了指令";③ 回归层——维护一组固定的测试用例(Golden Dataset),每次模型升级或 Prompt 修改后跑全量,对比输出与基准输出的语义相似度,下降超过阈值则告警。不需要每次都跑全量,但每次 Prompt 变更和模型版本升级时是必跑的。

本文来自 AI Enable Harness 一线交付实践。需要同类系统或优化服务?

订阅博客更新

新文章发布后第一时间邮件通知。不定期发送,不推销。

订阅 →