LLM API 可观测性是把每一次模型调用都转化为足够的结构化证据,以回答四个生产问题:
- 请求是否成功?
- 用户等待了多久?
- 请求消耗了什么,成本是多少?
- 系统为什么选择那个模型、重试,或者回退?
普通的 HTTP 监控是必要的,但还不够。即使是 200 OK,也可能包含无效 JSON、空回答、拒绝响应、损坏的工具调用,或者违反应用契约的输出。一次请求也可能在三次尝试后成功,但悄无声息地花费了预期四倍的成本。
实际目标不是记录每一个 prompt。目标是创建一个小而一致的遥测契约,把应用结果与模型、提供方、路由、延迟、token 使用量、重试和成本连接起来——同时不泄露用户数据。
本指南展示如何使用指标、链路、结构化日志、服务级目标、仪表盘和告警来构建该契约。
LLM API 可观测性必须解释什么
一个有用的可观测性系统,能让值班工程师快速从症状定位到原因。
| 生产问题 | 你需要的证据 |
|---|---|
| 为什么延迟飙升了? | 端到端时长、提供方时长、排队时间、首个 token 时间、模型、区域、重试次数 |
| 为什么成本上涨了? | 输入 token、输出 token、可用时的缓存 token、模型价格快照、尝试次数、被接受任务率 |
| 为什么用户看到了糟糕的结果? | 输出校验结果、schema 错误、拒绝状态、工具调用结果、评估分数、prompt 版本 |
| 为什么流量切换到了另一个模型? | 路由策略、选中的目标、回退原因、熔断器状态、提供方错误 |
| 事故是否是提供方特有的? | 提供方、模型、账号或部署、区域、状态码、提供方请求 ID |
| 我们能复现某一次请求吗? | 内部请求 ID、链路 ID、已清理的输入指纹、prompt 版本、模型参数 |
第一个设计原则很简单:衡量应用契约,而不仅仅是传输契约。
五层遥测
当你把所有信号都塞进一个仪表盘之外,LLM API 监控会更容易理解。
1. 请求指标
指标用于展示趋势并驱动告警。为以下内容记录计数器和直方图:
- 请求数量
- 端到端延迟
- 流式响应的首个 token 时间
- 提供方或模型调用延迟
- 成功、失败、取消和超时的请求
- HTTP 429 和 5xx 响应
- 重试和回退尝试
- 输入、输出和缓存 token
- 估算成本和对账后成本
指标应使用有界标签。好的标签包括 provider、model、route、environment、status 和 error_type。避免使用高基数标签,例如用户 ID、请求 ID、prompt 文本或完整 URL。
2. 分布式链路
链路(trace)会解释一条请求如何穿过你的 API、队列、检索层、工具调用、网关以及模型提供商。
一个实用的链路层级如下:
POST /support/reply
├── retrieve_customer_context
├── llm.route
│ ├── llm.attempt provider_a/model_primary
│ └── llm.attempt provider_b/model_fallback
├── validate_structured_output
└── persist_draft
每次模型尝试都应该是独立的 span。如果尝试了两个提供商,链路必须显示两次尝试,而不是把两者都隐藏在一个不透明的 llm.call span 里。
OpenTelemetry 的生成式 AI 语义约定 为生成式 AI 的 spans、指标和事件提供了一个有用的共享词汇。应将该约定版本视为遥测 schema 的一部分,这样当属性演进时,你就可以有计划地迁移。
3. 结构化日志
日志会捕获那些作为指标标签会过于昂贵或过于详细的离散决策和诊断上下文。
有用的事件包括:
llm.request.startedllm.route.selectedllm.retry.scheduledllm.fallback.selectedllm.response.validatedllm.request.completedllm.request.failed
每个事件都应包含相同的关联字段:request_id、trace_id、route、model、provider、prompt_version 和 attempt。
4. 质量和契约信号
质量不能仅从状态码推断。应尽可能添加确定性的校验器:
- JSON 解析成功
- 必需的 schema 字段存在
- 工具名称和参数被允许
- 在需要时,引用列表存在
- 输出长度在产品限制内
- 识别到拒绝或安全状态
- 业务规则检查通过
对于主观任务,之后再附加抽样的评估结果。让在线请求遥测和离线评估通过稳定的 request 或 sample ID 关联起来。
在替换生产模型之前,请使用可重复的 AI 模型评估工作流,而不是仅仅依赖聚合延迟和 token 价格。
5. 成本和业务结果
token 计数是使用信号,不是业务结果。把模型使用连接到你的产品所关心的单位:
- 每个被接受的支持回复成本
- 每个完成的编码任务成本
- 每张被审核者批准的生成产品图片成本
- 每个已丰富的合格线索成本
- 每次成功结构化提取成本
最有用的公式是:
每个被接受任务的有效成本 = 模型总成本 / 被接受任务数
这会暴露出虚假的节省。一个更便宜的模型如果导致更多重试、校验失败或人工返工,可能会提高有效成本。
每次模型调用的最低遥测契约
从一个版本化的事件 schema 开始。具体字段名可以遵循你的可观测性栈,但概念应保持稳定。
{
"schema_version": "llm-observability.v1",
"timestamp": "2026-07-30T09:00:00Z",
"request_id": "req_internal_01",
"trace_id": "7c4b...",
"environment": "production",
"feature": "support_reply",
"route": "support-default",
"provider": "provider-a",
"model": "model-primary",
"prompt_version": "support-reply-v12",
"attempt": 1,
"stream": true,
"status": "success",
"http_status": 200,
"latency_ms": 1840,
"time_to_first_token_ms": 410,
"input_tokens": 1640,
"output_tokens": 284,
"cached_input_tokens": 900,
"estimated_cost_usd": 0.0068,
"validator": "通过",
"fallback_reason": null,
"provider_request_id": "redacted-or-scoped-value"
}
不要将原始提示和响应设为必填字段。只有在有明确需要、已批准的保留策略、适当的访问控制以及安全的脱敏路径时,才存储它们。
应放在首个仪表板上的指标
不要一开始就放 40 个面板。先构建一个运营仪表板,用于回答用户是否在延迟和成本预算内获得了有效结果。
流量与成功
- 每分钟请求数
- 传输成功率
- 验证通过成功率
- 取消率
- 超时率
- 重试放大比率
- 回退率
验证通过成功率应作为主要可用性信号:
验证通过成功率 = 通过应用契约的请求 / 符合条件的请求
这比 2xx 响应 / 请求 更严格,也更有用。
延迟
跟踪分布,而不是平均值:
- 端到端 p50、p95 和 p99 延迟
- 提供方调用 p50、p95 和 p99 延迟
- 首个 token 时间 p50 和 p95
- 队列等待 p95
- 工具执行 p95
- 验证时长 p95
将流式和非流式路由分开。即使总完成时间很长,流式请求也可能因为较好的首个 token 时间而感觉响应迅速。
可靠性
- 按提供方和模型划分的 429 比率
- 按提供方和模型划分的 5xx 比率
- 网络错误率
- 格式错误或 schema 无效的响应率
- 工具调用失败率
- 熔断器开启状态
- 重试预算耗尽率
如果限流是常见原因,应使用有上限的 LLM 重试策略,用于 RPM 和 TPM 限制,而不是在每个应用工作线程中各自进行不协调的重试。
使用量与成本
- 按功能划分的输入和输出 token
- 每个已接受任务的 token 数
- 每个请求的估算成本
- 每个已接受任务的成本
- 重试成本
- 回退成本差异
- 每日支出与预算对比
- 成本估算与提供方账单或用量导出对比
同时保留 estimated_cost 和 reconciled_cost。前者支持近实时监控;后者在权威计费数据到达后修正估算值。
如何跟踪重试和回退路由
重试和回退通常是基础监控失效的地方。如果所有尝试共用一个状态字段,那么一次昂贵且已退化的请求看起来也可能是健康的。
为每次尝试记录以下字段:
| 字段 | 重要原因 |
|---|---|
attempt |
显示放大效应和决策顺序 |
target_id |
在不暴露密钥的情况下标识提供商、部署、区域和模型 |
reason |
区分超时、429、5xx、校验失败和策略路由 |
remaining_budget_ms |
证明路由器遵守了面向用户的截止时间 |
safe_to_repeat |
使幂等性决策显式化 |
output_started |
防止在流式输出已到达客户端后进行不安全的回退 |
contract_compatible |
确认下一个目标支持所需的 schema、工具和模态 |
生产环境中的 LLM API 回退路由实施手册 应当定义决策策略。可观测性随后应证明路由器按此执行。
TypeScript 监控埋点模式
下面的示例让遥测与特定模型 SDK 保持独立。它为每次尝试记录一个父路由跨度和一个子跨度。
import { context, SpanStatusCode, trace } from "@opentelemetry/api";
const tracer = trace.getTracer("ai-gateway");
type ModelAttempt = {
provider: string;
model: string;
reason: "primary" | "retry" | "fallback";
};
export async function runModelRoute(
attempts: ModelAttempt[],
callModel: (attempt: ModelAttempt) => Promise<{
text: string;
usage?: { inputTokens?: number; outputTokens?: number };
providerRequestId?: string;
}>,
) {
return tracer.startActiveSpan("llm.route", async (routeSpan) => {
routeSpan.setAttribute("app.llm.route", "support-default");
routeSpan.setAttribute("app.llm.attempt_limit", attempts.length);
try {
for (const [index, attempt] of attempts.entries()) {
const result = await tracer.startActiveSpan(
"llm.attempt",
{ attributes: {
"gen_ai.system": attempt.provider,
"gen_ai.request.model": attempt.model,
"app.llm.attempt": index + 1,
"app.llm.reason": attempt.reason,
} },
context.active(),
async (attemptSpan) => {
const startedAt = performance.now();
try {
const response = await callModel(attempt);
const valid = response.text.trim().length > 0;
attemptSpan.setAttribute("app.llm.validated", valid);
attemptSpan.setAttribute(
"gen_ai.usage.input_tokens",
response.usage?.inputTokens ?? 0,
);
attemptSpan.setAttribute(
"gen_ai.usage.output_tokens",
response.usage?.outputTokens ?? 0,
);
attemptSpan.setAttribute(
"app.llm.latency_ms",
performance.now() - startedAt,
);
if (!valid) {
throw new Error("response_validation_failed");
}
attemptSpan.setStatus({ code: SpanStatusCode.OK });
return response;
} catch (error) {
attemptSpan.recordException(error as Error);
attemptSpan.setStatus({
code: SpanStatusCode.ERROR,
message: (error as Error).message,
});
return null;
} finally {
attemptSpan.end();
}
},
);
if (result) {
routeSpan.setAttribute("app.llm.selected_attempt", index + 1);
routeSpan.setStatus({ code: SpanStatusCode.OK });
return result;
}
}
throw new Error("llm_route_exhausted");
} catch (error) {
routeSpan.recordException(error as Error);
routeSpan.setStatus({
code: SpanStatusCode.ERROR,
message: (error as Error).message,
});
throw error;
} finally {
routeSpan.end();
}
});
}
在生产环境中,请在 spans 旁边添加你的指标计数器和结构化日志事件。还要在可用时捕获提供商请求标识符;当向模型提供商上报事故时,它们通常至关重要。请不要将这些标识符放在公开的错误消息中。
没有 prompt 泄漏的日志
最安全的默认做法是先记录元数据。
默认记录
- 内部请求和链路 ID
- 提供商请求 ID
- 功能和路由名称
- 提供商、模型和部署别名
- 提示模板版本
- 诸如 temperature 和最大输出 token 数等参数
- Token 使用量
- 延迟和首个 token 的时间
- 错误类别和重试决策
- 校验器结果
- 已脱敏的工具名称
默认不要记录
- 原始提示或响应
- API 密钥或授权头
- 客户机密
- 检索到的文档
- 包含个人或受监管数据的工具参数
- 完整文件路径或数据库记录
- 签名 URL
当出于调试或评估需要捕获内容时,请单独抽样,在存储前进行脱敏,限制访问,对其加密,并设置较短的保留期限。安全 API 密钥管理指南涵盖了与密钥、日志、轮换和事件响应相关的配套控制措施。
面向 LLM 支持功能的 SLO
LLM 服务级目标应描述用户可见的功能,而不是提供商账户。
结构化支持回复功能的 SLO 示例:
| SLO | 示例目标 |
|---|---|
| 经验证可用性 | 符合条件的请求中有 99.5% 返回符合契约的输出 |
| 交互延迟 | 95% 在 1.5 秒内生成首个 token |
| 完成延迟 | 95% 在 8 秒内完成 |
| 成本护栏 | 99% 低于每次请求的成本上限 |
| 兜底收敛 | 在滚动一小时内,需要兜底的比例低于 3% |
这些数字只是示例,不是通用目标。应根据用户期望、任务复杂度、提供商行为和单位经济性来设定。
使用错误预算来决定何时放缓功能发布、收紧路由策略或切换流量。即使提供商达到了自己的可用性目标,由于排队、工具、校验或兜底行为增加了失败次数,你的产品仍可能达不到自己的 SLO。
对症状报警,用原因诊断
对用户影响进行分页通知。对可能的原因使用更低严重级别的告警或仪表盘注释。
值得分页通知的症状
- 经验证成功率跌破 SLO
- p95 首个 token 时间超过面向用户的阈值
- 耗尽路由率急剧上升
- 每个已接受任务的成本超过护栏
- 关键功能没有健康且兼容契约的目标
诊断信号
- 某个提供商的 429 速率上升
- 某个模型的 schema 失败率发生变化
- 重试放大增加
- 队列等待变长
- 熔断器打开
- 提示发布后 token 使用量发生变化
避免因为每一次提供商 5xx 都进行分页通知。如果兜底在起作用,且用户仍然在延迟预算内收到有效响应,那么该事件可能需要调查,但不必唤醒值班工程师。
三仪表盘运维模型
仪表盘 1:用户体验
按功能展示经验证可用性、延迟、首个 token 时间、任务完成情况和用户可见错误。
仪表板 2:路由与提供商
展示按模型和目标划分的流量占比、提供商错误、重试、回退、熔断器、路由耗尽以及延迟。
仪表板 3:使用情况与经济性
展示令牌数、估算支出、对账后的支出、每个已接受任务的成本、预算偏差以及按成本排序的顶部功能。
在这三个仪表板上都保留部署和提示词版本注释。否则,恰好在发布后立即开始的回归,可能看起来像随机的提供商波动。
上线检查清单
- 定义一个版本化事件模式。
- 在产品边界生成内部请求 ID。
- 在队列、工具和模型调用之间传播链路上下文。
- 为每一次模型尝试创建一个子跨度。
- 在返回时记录提供商请求 ID。
- 添加确定性的输出校验。
- 显式跟踪重试和回退原因。
- 根据版本化价格表计算估算成本。
- 将估算值与权威的使用情况或计费导出进行对账。
- 在提供商仪表板之前先构建一个用户结果仪表板。
- 为已验证成功率和延迟设置一个 SLO。
- 对提示、响应、密钥和敏感工具数据进行脱敏或排除。
- 针对超时、429、5xx、格式错误的输出以及路由耗尽运行故障测试。
- 在生产环境启用指标之前审查标签基数。
- 按风险对链路进行采样:对错误和缓慢请求的保留比例高于常规成功请求。
常见可观测性错误
把每个 200 都当作成功
添加契约校验器,并单独报告已验证成功率。
为每个请求记录原始提示词
这会带来隐私、安全、保留期和成本问题。优先使用元数据和受控采样。
把重试隐藏在一个持续时间里
为每次尝试创建一个跨度和一个事件,这样操作人员就能看到放大效应。
把模型名称作为唯一的路由标识
跟踪提供商、部署或账户别名、区域以及路由策略。同一个模型在不同目标上的表现可能不同。
仅对平均延迟发出告警
平均值会掩盖尾部问题。使用 p95 和 p99,并将首 token 时间与总完成时间分开。
永远相信估算成本
价格表、缓存处理方式和提供商计费都可能变化。将估算值与计费数据对账,并记录价格表版本。
让遥测标签无限增长
请求 ID 和客户标识符应放在链路或日志中,而不是指标标签中。
AI API 网关的帮助
否则,多提供商应用需要为身份验证、模型命名、重试、使用字段、错误以及计费导出分别维护适配器。网关可以通过为应用提供一个稳定的 API 边界,同时在内部遥测中保留提供商和模型细节,从而减少这类集成面。
Flatkey 提供一个 API 密钥、一个兼容 OpenAI 的端点,以及对主流提供商模型的访问。这使得即使工作负载使用不同的文本、图像或视频模型,也可以集中管理应用侧的遥测契约。网关并不能取代产品层面的可观测性:你的应用仍然必须记录功能、提示词版本、校验结果、用户可见延迟以及已接受任务的结果。
如果你的团队正在整合提供商,请先阅读AI API 网关架构指南,然后在将生产流量迁移之前,先把本文中的遥测契约补充进去。
常见问题
什么是 LLM API 可观测性?
LLM API 可观测性是对由模型驱动的功能所产生的指标、链路、日志、质量检查、使用情况和成本数据进行收集与关联。它既能解释提供商的行为,也能说明应用是否返回了有效的用户结果。
我应该监控 LLM API 的哪些内容?
监控已验证成功率、端到端延迟、首个 token 时间、提供商延迟、429 和 5xx 比率、重试、回退、token 用量、预估成本、每个被接受任务的成本,以及输出契约失败。
应该把提示词和响应存储在链路中吗?
默认不要。先存储元数据。只有在明确的调试或评估用途下,才在进行脱敏、访问控制、加密、采样和保留策略管理后捕获内容。
LLM 监控和 LLM 可观测性有什么区别?
监控告诉你某个已知指标跨过了阈值。可观测性则提供足够多的关联证据,帮助你调查应用、路由、提供商、模型、工具和输出契约中的新故障模式。
我该如何计算每次请求的 LLM 成本?
将可计费的输入、输出、缓存输入、媒体或其他使用单位乘以版本化价格表,然后再加上重试和回退尝试的成本。将估算结果与提供商或网关的计费数据对账。
我应该存储哪个请求 ID?
创建你自己的内部请求 ID 和链路 ID,然后在 API 返回提供商请求 ID 时也一并存储。内部 ID 用于连接你的系统;提供商 ID 则有助于外部支持和事故升级处理。
在事故发生前构建遥测契约
决定一次模型调用应记录什么的最佳时机,是生产流量到来之前。先从已验证成功、延迟分布、每次尝试一个 span、受限的指标标签、以元数据为先的日志,以及每个被接受任务的成本开始。然后通过强制触发你预期路由器需要处理的失败来测试系统。
这个基础会把模糊的报告——“AI 功能又慢又贵”——转化为可追踪的决策:哪个功能、哪条路由、哪个模型、哪次尝试、哪种失败、延迟多少、成本多少。
当你准备好比较一个兼容 OpenAI 的 API 背后多模型路由时,请了解Flatkey 定价。



