LLM 可观测性:用 OpenTelemetry 给每笔 API 调用算账

LLM 可观测性:用 OpenTelemetry 给每笔 API 调用算账

上个月底对账,运营跑来问我:这个月大模型 API 花了 12000 美元,比上个月多了 40%,是哪个功能多花了钱?

我打开 OpenAI 的 Usage 页面,看到一条总消费曲线和按模型分的明细。能看出 GPT-4o 花得最多,但到底是哪个功能、哪个用户群体、哪个 prompt 版本导致的增长?看不出来。

这是大模型 API 使用中一个普遍的盲区:你知道总共花了多少钱,但不知道钱花在了哪里

传统 Web 服务的可观测性已经很成熟了——Prometheus 采指标、Jaeger 跟踪链路、ELK 收日志。但 LLM 调用有自己的特殊性,现有的监控体系不能直接套用。

LLM 调用的"特殊"在哪

成本和请求内容强相关。 传统 API,一个 GET 请求的成本是固定的,不管你查什么。但 LLM 调用的成本取决于输入和输出的 Token 数。同一个功能,用户输入 50 个字和 5000 个字,成本差了两个数量级。

延迟不可预测。 传统 API 的 P99 延迟大致稳定。LLM 调用的延迟取决于输出长度、模型负载、是否命中缓存。同一个接口,快的时候 500ms,慢的时候 15 秒,波动范围很大。

输出质量无法自动度量。 传统 API 返回的是结构化数据,对就是对、错就是错。LLM 返回的是自然语言,"好不好"很难用代码自动判断。你需要额外的评估手段——用户反馈、人工抽检、或者用另一个模型打分。

这些特殊性意味着:LLM 可观测性不能只看"调没调通""延迟多少",还得看"花了多少钱""用了多少 Token""缓存命中没有""输出质量如何"。

OpenTelemetry + LLM:怎么接

OpenTelemetry(OTel)是目前最主流的可观测性框架,已经是 CNCF 毕业项目。它的好处是厂商无关——你可以把数据发到 Jaeger、Grafana、Datadog,随时切换。

但 OTel 原生不懂 LLM。一次 LLM 调用在 OTel 里只是一个普通的 HTTP span,你能看到 URL、状态码、耗时,但看不到 Token 数、模型名称、缓存命中率这些 LLM 特有的信息。

这就是 OpenLLMetry 这类项目干的事:在 OTel 的基础上,定义了一套 LLM 专用的语义约定(semantic conventions),自动把 LLM 调用的元数据写进 span attributes。

# 安装 OpenLIT(一个 OpenLLMetry 的实现)
# pip install openlit

import openlit
openlit.init()  # 自动 instrument OpenAI、Anthropic、LangChain 等

# 之后所有 LLM 调用会自动生成 OTel span,包含:
# - gen_ai.system: "openai"
# - gen_ai.request.model: "gpt-4o"
# - gen_ai.usage.prompt_tokens: 1500
# - gen_ai.usage.completion_tokens: 800
# - gen_ai.usage.cost: 0.0115

几行代码,你的 LLM 调用就进入了 OTel 的生态。可以在 Grafana 里画 dashboard,在 Jaeger 里看调用链路,在告警系统里设阈值。

四个值得盯的指标

装好之后,不要什么都监控——指标太多等于没有指标。建议聚焦这四个:

1. 每功能/每用户的 Token 消耗。 这是成本归因的基础。在每次 LLM 调用时,打上业务标签(功能名称、用户 ID、租户 ID),然后按标签聚合。

你会发现类似这样的分布:客服模块占 45% 的 Token 消耗,内容生成占 30%,代码助手占 15%,内部工具占 10%。当某个模块的消耗突然上升,你能立刻定位。

2. 缓存命中率。 如果你做了 Prompt Caching,命中率是检验效果的核心指标。OpenAI 和 Anthropic 的响应里都有缓存相关的字段,提取出来做成时序指标。命中率低于 60% 就该检查 prompt 结构了。

3. 首字延迟(TTFT)的分位数。 均值没用,要看 P50、P90、P99。TTFT 直接影响用户的"等待感"。如果 P99 超过 3 秒,意味着每一百个用户里有一个人要等很久。按模型、按时段拆开看,能发现高峰期哪个模型扛不住。

4. 错误率和重试率。 429(限流)、500(服务端错误)、超时——分别统计。429 多了说明你需要提限额或者做请求削峰;500 多了可能是上游的问题,需要启动 fallback;超时多了看看是不是 prompt 太长导致生成时间超限。

在网关层做,还是在应用层做

可观测性的埋点可以放在两个地方。

应用层埋点:在你的业务代码里,用 OpenLLMetry 之类的库自动 instrument。好处是能打业务标签(用户 ID、功能名称),坏处是每个服务都要接入一遍。

网关层埋点:如果你有 LLM 网关(不管是自建的还是用聚合平台的),在网关上做统一日志和指标采集。好处是一个点就覆盖所有调用,坏处是网关层不了解业务语义——它知道哪个 API Key 调了什么模型,但不知道这次调用是哪个功能发起的。

推荐两层都做。网关层做全局的 Token 统计、错误率、延迟监控;应用层做业务级别的成本归因和质量评估。两层数据通过 trace_id 关联,在排查问题时可以从网关的全局视图下钻到具体的业务调用。

一个实际的 Grafana Dashboard 结构

分享一下我们内部在用的 dashboard 布局,供参考:

第一行:全局概览

  • 今日总 Token 消耗(输入/输出分开)
  • 今日总成本(美元)
  • 当前小时请求数
  • 错误率

第二行:成本归因

  • 按功能模块的成本饼图
  • 按模型的成本柱状图
  • 过去 7 天的成本趋势线

第三行:性能

  • TTFT P50/P90/P99 时序图
  • 按模型的请求延迟对比
  • 缓存命中率趋势

第四行:异常

  • 429 限流次数
  • 500 错误次数
  • 超时次数
  • 重试次数

这个布局一页能看完,每天早上扫一眼就知道系统状态。出了问题再往下钻。

成本告警怎么设

告警不要设在绝对值上(比如"日消费超过 500 美元"),因为业务增长会让这个阈值不断失效。建议设在增长率上:

  • 今日消费比过去 7 天同时段均值高 50% → 告警
  • 某功能模块的 Token 消耗比昨天同时段高 100% → 告警
  • 缓存命中率比过去 24 小时均值低 20% → 告警

这样不管业务怎么增长,异常波动都能抓到。


参考链接

  • OpenTelemetry 官网:https://opentelemetry.io/
  • OpenLIT(LLM Observability):https://github.com/openlit/openlit
  • Traceloop OpenLLMetry:https://github.com/traceloop/openllmetry
  • OTel LLM 语义约定:https://opentelemetry.io/blog/2024/llm-observability
← 返回博客列表