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