1. 多模型 LLM 服务为什么需要统一 Key 做可观测
很多团队一开始接大模型,都是每个应用各配一把 Key:客服机器人一把、代码助手一把、内部知识库一把。跑起来没问题,等到月底对账就傻眼了——账单是一个总数,你根本不知道钱花在哪个应用、哪个模型、哪条调用链上。这就是 LLM 服务监控最典型的盲区:传统 APM 只看 QPS、错误率、总延迟,而 LLM 的成本是按 Token 算的,体验是按首 Token 延迟(TTFT)算的,异常是按输出长度分布算的。
举个具体例子。你的客服应用一次请求消耗 800 Token,代码助手一次消耗 12000 Token,两者在 QPS 计数器里都是「1 次请求」。如果代码助手某天因为 Prompt 拼接出错,把整个代码库塞进了上下文,单次消耗飙到 80000 Token,QPS 曲线纹丝不动,但成本已经翻了好几倍。没有 Token 维度的监控,这种问题只能等账单出来才发现。
再比如流式输出场景。用户感知的是「多久开始出字」,而不是「多久全部出完」。一个请求等了 5 秒才吐第一个字,和 1 秒出字但生成了 30 秒,前者体验极差,后者完全可接受。但传统监控只看总延迟,这两个请求可能被算成同一档。
所以这篇要解决的问题很明确:用 TaoToken 作为统一 Key / API 通道,把多模型调用收敛到一个入口,然后在这个入口上采集三类指标——Token 消耗、延迟分布、异常检测。统一入口的好处是,你不需要在每个应用里重复埋点,只需要在网关层做一次采集,所有模型、所有应用的调用都会经过这里。
适合谁看:正在把 LLM 接入生产环境的后端 / 平台工程师,尤其是多应用共享 API Key、需要按应用拆分成本、需要定位「到底是哪个模型哪条链路慢」的团队。如果你还在单机调试阶段,监控的投入产出比不高,可以先跳过。
下面我会给出可复制的指标采集配置、看板字段、告警规则,并演示一次异常注入后的验证动作。全程用 TaoToken 的 API 通道作为统一入口,Base URL 是https://taotoken.net/api,模型对话、Coding Plan、API Keys 都在官网可以找到入口。
2. TaoToken 统一 Key 接入前置:把多模型收敛到一个通道
在讲监控之前,先把接入这步做扎实。因为监控的数据源就是调用日志,如果调用本身是散的,采集就会变成到处打补丁。
TaoToken 在这里扮演的角色是统一 API 通道:你拿到一把 Key,配置一个 Base URL,就可以在同一个通道里调用不同模型。对监控来说,这意味着所有调用都经过同一个出口,你可以在客户端 SDK 层统一拦截,也可以在网关层统一采集,不用为每个模型厂商单独写适配。
前置准备分三步。
第一步,拿到 API Key。进入控制台的 API Keys 页面创建一个 Key,建议按环境区分,比如prod-monitor、staging。创建后立刻复制保存,页面刷新后不会再完整显示。
第二步,确认 Base URL。所有请求走https://taotoken.net/api,不要带多余的路径后缀。OpenAI 兼容的 SDK 直接把这个地址填进base_url即可。
第三步,选定你要监控的模型。建议先固定 2 到 3 个主力模型,比如一个高性价比的小模型做分类 / 摘要,一个强模型做复杂推理。模型 ID 在模型对话页面可以查到,配置时原样填入。
这里有个容易踩的坑:很多人把 Key 直接写死在代码里,然后每个应用复制一份。这样监控采集时无法区分应用来源。正确做法是一把 Key 对应一个环境,应用维度通过请求头或 metadata 传递。比如在请求里加一个自定义 headerX-App-Name: customer-bot,网关采集时就能按应用拆分。
如果你用的是 Claude Code 这类编码工具,接入方式略有不同,需要配置 Base URL、Key、Model ID 三件套。Cline 走 MCP 配置时同理,settings.json里要把这三项写全,缺一个都会报连接失败。Codex 的auth.json也是同样的逻辑,Base URL 指向 TaoToken 通道,Key 填你创建的 Key,Model ID 填你要用的模型。
接入完成后,先别急着上监控。用一次最简单的请求验证通道是通的,确认返回正常,再开始埋点。否则监控里全是错误数据,排查起来会混淆「通道问题」和「业务问题」。
3. 可复制配置:指标采集、看板字段与告警规则
这一节是核心,给出可以直接抄的配置。我按「采集层 → 存储层 → 展示层 → 告警层」的顺序来写。
3.1 采集层:在调用封装里记录三类指标
不管你用什么语言,思路是一样的:封装一个 LLM 调用函数,在函数内部记录开始时间、首 Token 时间、结束时间、输入输出 Token 数、结果状态。下面是一个 Python 版本的配置片段,用prometheus_client暴露指标。
# llm_metrics.py from prometheus_client import Counter, Histogram import time # Token 消耗计数器,按模型、应用、类型分组 TOKEN_CONSUMED = Counter( "llm_tokens_consumed_total", "LLM Token 消耗总量", ["model", "app", "token_type"], # token_type: input / output ) # 首 Token 延迟直方图 TTFT = Histogram( "llm_time_to_first_token_seconds", "首 Token 延迟分布", ["model", "app"], buckets=[0.1, 0.3, 0.5, 1, 2, 3, 5, 10], ) # 端到端延迟直方图 E2E_LATENCY = Histogram( "llm_e2e_latency_seconds", "端到端延迟分布", ["model", "app"], buckets=[0.5, 1, 2, 5, 10, 30, 60, 120], ) # 调用结果计数器 CALL_RESULT = Counter( "llm_call_result_total", "LLM 调用结果统计", ["model", "app", "result"], # result: success / error / rate_limited ) # 输出 Token 长度分布,用于异常检测 OUTPUT_LENGTH = Histogram( "llm_output_token_length", "输出 Token 长度分布", ["model", "app"], buckets=[10, 50, 100, 200, 500, 1000, 2000, 4000], )然后在调用封装里这样用:
def call_llm(app: str, model: str, messages: list): start = time.time() ttft = None input_tokens = 0 output_tokens = 0 result = "success" try: stream = client.chat.completions.create( model=model, messages=messages, stream=True, ) for chunk in stream: if ttft is None and chunk.choices: ttft = time.time() - start if chunk.choices and chunk.choices[0].delta.content: output_tokens += 1 except Exception as e: result = "error" raise finally: total = time.time() - start TOKEN_CONSUMED.labels(model, app, "input").inc(input_tokens) TOKEN_CONSUMED.labels(model, app, "output").inc(output_tokens) if ttft is not None: TTFT.labels(model, app).observe(ttft) E2E_LATENCY.labels(model, app).observe(total) CALL_RESULT.labels(model, app, result).inc() OUTPUT_LENGTH.labels(model, app).observe(output_tokens)注意input_tokens这里需要从响应里取,不同 SDK 字段名不一样,OpenAI 兼容格式一般在usage.prompt_tokens。流式响应里 usage 可能只在最后一个 chunk 返回,要单独处理。
3.2 存储与看板字段
指标暴露在/metrics端点后,用 Prometheus 抓取,Grafana 展示。看板建议按三个面板组织:
| 面板 | 核心字段 | 用途 |
|---|---|---|
| 成本面板 | llm_tokens_consumed_total按 app/model 聚合 | 看哪个应用、哪个模型在烧钱 |
| 延迟面板 | llm_time_to_first_token_seconds的 P50/P90/P99 | 看体验劣化发生在哪个模型 |
| 异常面板 | llm_output_token_length的分布 +llm_call_result_total | 看输出长度突变、错误率上升 |
成本面板的 PromQL 可以这样写:
sum by (app, model) ( rate(llm_tokens_consumed_total[1h]) )延迟面板取 P90:
histogram_quantile(0.9, sum by (le, model) ( rate(llm_time_to_first_token_seconds_bucket[5m]) ) )3.3 告警规则
告警不要贪多,三条就够覆盖大部分场景。下面是一个 Prometheus 告警规则片段:
groups: - name: llm_alerts rules: - alert: LLMTTFTTooHigh expr: histogram_quantile(0.9, sum by (le, model) (rate(llm_time_to_first_token_seconds_bucket[5m]))) > 5 for: 3m labels: severity: warning annotations: summary: "模型 {{ $labels.model }} 首 Token 延迟 P90 超过 5 秒" - alert: LLMTokenSpike expr: sum by (app) (rate(llm_tokens_consumed_total[10m])) > 3 * sum by (app) (rate(llm_tokens_consumed_total[1h] offset 1h)) for: 5m labels: severity: critical annotations: summary: "应用 {{ $labels.app }} Token 消耗突增 3 倍" - alert: LLMErrorRateHigh expr: sum by (model) (rate(llm_call_result_total{result="error"}[5m])) / sum by (model) (rate(llm_call_result_total[5m])) > 0.05 for: 2m labels: severity: critical annotations: summary: "模型 {{ $labels.model }} 错误率超过 5%"这三条分别对应:体验劣化、成本异常、服务不可用。告警阈值根据你的业务基线调整,不要照搬。
4. 验证请求:注入一次异常并定位到调用链路
配置写完,必须验证它真的能抓到问题。我试过用「异常注入」的方式做验证,效果最直观。
4.1 正常基线
先跑 20 次正常请求,记录基线。用一个简单脚本:
import requests, time BASE = "https://taotoken.net/api" KEY = "你的Key" HEADERS = {"Authorization": f"Bearer {KEY}", "X-App-Name": "monitor-test"} for i in range(20): start = time.time() r = requests.post( f"{BASE}/v1/chat/completions", headers=HEADERS, json={ "model": "你的模型ID", "messages": [{"role": "user", "content": "用一句话解释什么是可观测性"}], "stream": False, }, ) print(i, r.status_code, round(time.time() - start, 2))跑完后在 Grafana 看板确认:Token 消耗曲线平稳,TTFT 直方图集中在 0.5 到 1 秒,错误率为 0。
4.2 注入异常
现在注入一个「超长输入」异常,模拟 Prompt 拼接出错:
long_context = "这是一段用于测试的填充文本。" * 5000 # 制造超长输入 r = requests.post( f"{BASE}/v1/chat/completions", headers=HEADERS, json={ "model": "你的模型ID", "messages": [{"role": "user", "content": long_context + "请总结上面内容"}], "stream": False, }, ) print(r.status_code)4.3 验证告警与定位
注入后等 1 到 2 分钟,观察三件事:
第一,成本面板上monitor-test这个应用的 Token 消耗曲线出现尖峰,LLMTokenSpike告警触发。
第二,延迟面板上该模型的 E2E 延迟 P90 明显抬升,因为超长输入的处理时间更长。
第三,异常面板上输出长度分布出现偏移,如果模型因为上下文过长而截断输出,llm_output_token_length会往小桶集中。
定位到具体调用链路的方法是:在结构化日志里按app=monitor-test和时间窗口过滤,找到那条超长请求的 trace ID,然后顺着 trace ID 看它经过了哪个应用、哪个模型、输入 Token 数是多少。这就是统一 Key 通道的价值——所有调用都有统一的app和model标签,不需要跨系统拼数据。
验证通过后,把注入脚本删掉,确认告警恢复。这一步很重要,否则你会一直收到误报。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
配置过程中最容易卡在几个报错上,我按实际遇到的频率排一下。
401 Unauthorized。最常见的原因是 Key 没带对,或者 Base URL 写成了带路径的形式。检查两点:请求头是不是Authorization: Bearer sk-xxx,Base URL 是不是干净的https://taotoken.net/api。如果你用的是 Claude Code 或 Cline,检查settings.json里的三件套是否齐全——Base URL、Key、Model ID 缺一个都会 401 或连接失败。
local proxy failed。这个报错通常出现在本地工具链里,比如 Cline 走 MCP 配置时。原因是本地代理配置和实际通道不匹配。解决方法是把 MCP 配置里的 Base URL 直接指向 TaoToken 通道,不要经过额外的本地转发。检查settings.json里有没有残留的localhost地址。
reading choices 报错。这个一般出现在流式解析时,chunk.choices为空数组导致索引越界。修复方式是加判空:
if chunk.choices and len(chunk.choices) > 0: content = chunk.choices[0].delta.content很多 SDK 在流结束时返回一个空 choices 的 chunk,不判空就会抛异常。
OAuth 相关报错。如果你用的是 Codex 的auth.json,报 OAuth 错误通常是因为认证方式选错了。Codex 支持多种认证,走 API Key 模式时要把auth.json里的字段改成 Key 模式,Base URL 指向 TaoToken 通道。不要混用 OAuth 和 API Key 两种模式。
指标采集不到数据。检查/metrics端点是否能访问,Prometheus 的 scrape 配置里 target 是否写对。另一个常见原因是调用封装里finally块没执行,比如进程被 kill 导致指标丢失。高频场景建议做本地聚合,每 10 秒批量上报,而不是每次调用都写。
TTFT 一直是空。非流式调用测不到 TTFT,这是设计如此。如果你的业务必须用非流式,那就只能监控总延迟,TTFT 面板会一直空着,不要以为是 bug。
排障时如果拿不准是通道问题还是代码问题,先用模型对话页面发一条最简单的请求,确认通道本身是通的。通道通了再查代码,能省很多时间。接入文档里有各语言的最小示例,对照着改比自己猜快。
6. 把监控接到长期编码与 Agent 工作流
监控配好之后,下一步是让它进入日常研发流程,而不是只在出问题时才看。
对于长期跑编码任务的团队,可以把 Token 成本和延迟指标接到 Coding Plan 的用量视图里,按天看趋势。如果某个 Agent 任务的 Token 消耗持续偏高,说明它的上下文管理有问题,需要优化 Prompt 或加缓存。
对于用 Claude Code 做日常开发的场景,建议把 Base URL、Key、Model ID 三件套固化到项目配置里,团队成员统一走 TaoToken 通道。这样每个人的调用都会带上统一的app标签,团队维度的成本拆分就自动有了。
告警的落地方式也要想清楚。不要所有告警都发到群里,会疲劳。建议分级:LLMTokenSpike和LLMErrorRateHigh发到值班群,LLMTTFTTooHigh只记录到看板,每天晨会看一眼趋势。异常检测的目标不是告警越多越好,而是能在成本超支、体验劣化、输出异常发生时第一时间定位到具体调用链路。
最后给一个实用技巧:把每次异常注入的验证脚本保留下来,做成一个chaos_test.py,每次改完监控配置就跑一遍。这样你能确认告警规则没有因为配置变更而失效。监控系统本身也需要被监控,这是很多人忽略的一点。
接入入口在 API Keys 页面,模型列表在模型对话页面,长期编码任务可以看 Coding Plan,接入细节在文档里。先把通道跑通,再把指标埋上,最后用异常注入验证一遍,这套流程走下来,你的 LLM 服务就有了最基本的可观测性底座。