AI 学习笔记(十五):LLM 应用可观测性(Observability)最小实践

上一阶段我们把“接入模型 -> 串流程 -> 做评估与发布门禁”跑通了,下一步进入生产运维实践。
这第一篇我选择从 Observability 开始,因为没有可观测性,后面的成本治理、事故响应、质量回归都容易变成“拍脑袋 + 猜”。

你可以把目标定得很朴素:让每一次线上请求,都能回答三个问题

  1. 发生了什么(What)
  2. 为什么会这样(Why)
  3. 影响有多大、该不该回滚(So what)

1. 先把链路拆开:一次 LLM 请求到底发生了什么

不拆链路就很难观测。一个常见的线上请求,通常至少包含这些阶段:

  1. 入口:HTTP 请求 / 鉴权 / 参数校验
  2. 上下文:会话状态、用户画像、权限与灰度策略
  3. 检索:query 改写、向量检索、过滤、重排
  4. 组装:提示词模板选择、上下文拼接、提示词长度控制
  5. 调用:请求模型、处理重试/超时/熔断
  6. 后处理:结构化输出校验、引用标注、内容安全、降级兜底
  7. 出站:返回响应、写缓存/写库、异步事件

可观测性要做的事,本质上是:给这些阶段打点,并且串起来

2. 结构化日志:先把“最小字段集合”落下来

日志是可观测性里最划算的一步,但前提是你别写“人类可读的散文”,而是写机器可聚合的 JSON

建议每条日志都至少有:

  • request_id:一次请求的唯一 ID(贯穿全链路)
  • trace_id:如果你接入了 tracing,就把它也写进日志(方便互跳)
  • event:事件名(例如 retrieval.search / llm.call.success / llm.call.error
  • latency_ms:这个阶段耗时
  • error_*:失败就必须有稳定的错误分类字段(别只记 message)

再加上 LLM/RAG 特有的关键字段(按你的系统裁剪):

  • provider / model:供应商与模型名
  • prompt_version:提示词模板版本(不要只在代码里写死)
  • prompt_hash:提示词最终文本 hash(用于排查同模板不同拼接导致的漂移)
  • input_tokens / output_tokens:token 用量(有就记)
  • cost:成本(可以先不精确,至少能聚合到“趋势”)
  • retrieval_topkretrieval_doc_ids:检索关键结果(注意脱敏与截断)
  • guardrail_hit / fallback_used:是否触发安全策略/降级

2.1 一段最小的 JSON 日志示例(Node.js)

下面这段不引入任何依赖,直接可用,适合作为“最小落地版本”的基线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import crypto from "node:crypto";

export function newRequestId() {
return crypto.randomUUID();
}

export function logEvent(event) {
console.log(
JSON.stringify({
ts: new Date().toISOString(),
...event,
})
);
}

在调用模型的地方,把成功/失败都打出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const request_id = newRequestId();
const start = Date.now();

try {
const resp = await client.responses.create({
model: "gpt-4.1-mini",
input: "Hello",
});

logEvent({
level: "info",
event: "llm.call.success",
request_id,
provider: "openai",
model: "gpt-4.1-mini",
prompt_version: "chat-v1",
latency_ms: Date.now() - start,
input_tokens: resp?.usage?.input_tokens,
output_tokens: resp?.usage?.output_tokens,
});
} catch (err) {
logEvent({
level: "error",
event: "llm.call.error",
request_id,
provider: "openai",
model: "gpt-4.1-mini",
prompt_version: "chat-v1",
latency_ms: Date.now() - start,
error_name: err?.name,
error_message: String(err?.message || err),
});
throw err;
}

2.2 日志里最容易踩的坑

  1. 不要记录原始敏感信息:用户输入、知识库原文、检索结果正文,默认都要脱敏/截断/加密,必要时只存 hash。
  2. 不要只打成功日志:失败日志必须稳定、可聚合,避免“线上报错了但找不到发生了什么”。
  3. 不要没有版本:prompt/template、检索策略、重排模型都要有版本号,否则你很难判断“是不是刚发布的改动导致的”。

3. 指标(Metrics):先抓住 8 个就够

指标的价值是“看趋势 + 做告警”,所以别一上来就做一大堆。

我建议先把下面 8 个做出来:

  1. request_total:总请求量
  2. request_error_total:入口错误量(参数/鉴权/系统异常)
  3. llm_call_total:模型调用次数(注意重试会放大)
  4. llm_call_error_total:模型调用错误次数(含超时/限流/5xx)
  5. llm_latency_ms:模型调用耗时分布(至少 P50/P95)
  6. tokens_in_total:输入 token 总量
  7. tokens_out_total:输出 token 总量
  8. cost_total:成本总量(可以先按粗粒度估算)

有了它们,你就能在一个看板里回答:

  • 最近 1 小时错误率有没有异常
  • P95 时延是不是突然变高
  • 成本是不是随请求量线性增长,还是“单位请求成本”变贵了

3.1 一个“够用”的告警基线

告警不要太复杂,先从最能救命的开始:

  • 错误率llm_call_error_total / llm_call_total 连续 5 分钟 > 阈值
  • 时延:P95 llm_latency_ms 连续 5 分钟 > 阈值
  • 成本cost_total(或单位请求成本)短时间内陡增

阈值别拍脑袋:可以先用一周的历史数据算基线,再逐步收紧。

4. 追踪(Tracing):让“慢在哪里”一眼看出来

日志能定位“发生了什么”,但当你遇到“整体变慢”时,最常见的问题是:

慢在检索?慢在重排?慢在组装提示词?还是慢在模型?

这时 tracing 很好用。最小原则是:

  • 每个 request_id 都能串起 retrieval / prompt_build / llm_call / postprocess 的耗时
  • trace_id 写回日志,方便从异常日志跳到链路图

如果你已经有 OpenTelemetry 基础设施,建议直接用它;如果没有,也可以先用“阶段耗时日志”顶上,先把慢点找出来。

5. 回放包(Replay Packet):用来复现、做离线回归

很多 LLM 线上问题的难点是:你很难在事后复现当时的上下文

建议为“采样的失败请求”保存一个最小回放包(注意合规与成本):

  • request_id / ts / route
  • prompt_version / prompt_hash / 关键参数(temperature、top_p 等)
  • retrieval_doc_ids + 每段内容的 hash(避免保存原文)
  • model / provider
  • final_answer(必要时脱敏)
  • error_class / fallback_used

它的价值在于:

  • 事故复盘时可以回放:到底是检索漂了还是 prompt 漂了
  • 评估体系可以复用:把线上坏样本沉淀进评测集,形成“回归基线”

6. 一天内可落地的最小 Checklist

如果你想把这篇变成可执行任务,可以按这个顺序落地:

  1. 给所有入口请求加 request_id(并贯穿到检索与模型调用)
  2. 统一 JSON 日志格式,至少覆盖:eventlatency_mserror_class
  3. provider/model/prompt_version 写进日志
  4. 把 token 用量写进日志(没有就先空着)
  5. llm_call_total/llm_call_error_total/llm_latency_ms 先做成可视化
  6. 做 3 条告警:错误率、时延、成本
  7. 对失败请求做回放包采样(先只存 hash + 元信息)

做到这里,后续的成本治理和事故响应就有“抓手”了。

总结

LLM 应用的可观测性,不是为了“看起来专业”,而是为了两件事:

  1. 线上出问题能快速定位和止血
  2. 能把线上问题沉淀成可回归的工程资产(评测集、基线、门禁)

下一篇我打算继续写“生产运维实践”的第二部分:成本治理(Cost Governance)最小实践,把 token、缓存、模型路由、降级策略变成可控的成本曲线。

参考资料

本文永久链接: https://www.mulianju.com/learning-notes/ai-learning-notes-llm-observability-minimal-practice/