从一个真实故事开始
2024 年中,某家公司在 OpenAI 上一个月跑出 7 万美元账单——因为某个内部脚本把 GPT-4 调成死循环,没人发现。管理员只有"月底出账"这一个时机看见问题。事后复盘,补了两个最基本的事:每次调用都打 cost 日志,按业务/用户设预算硬限。这两件事 LiteLLM 都自带。
token 和价格是怎么算的
LLM 定价的基本单位是 1M token 多少美元,分 input 和 output 两种价:
cost = (input_tokens / 1e6) * input_price + (output_tokens / 1e6) * output_price
举例(2026 年初参考价,以实际为准):
| 模型 | input/M | output/M | 一次典型调用(2K in / 500 out) |
|---|---|---|---|
| gpt-4o | $2.50 | $10.00 | $0.010 |
| gpt-4o-mini | $0.15 | $0.60 | $0.0006 |
| claude-sonnet-4 | $3.00 | $15.00 | $0.0135 |
| claude-haiku-4 | $1.00 | $5.00 | $0.0045 |
| gemini-2.5-flash | $0.075 | $0.30 | $0.0003 |
| deepseek-chat | $0.14 | $0.28 | $0.00042 |
差距能到 50 倍。选错模型 = 白扔几万美元。这就是为什么 Router 里按业务选模型那么重要。
model_prices.json:LiteLLM 的价格表
LiteLLM 内部维护一份所有 provider × 所有模型的价格表 model_prices_and_context_window_backup.json,随版本更新。调用完它会自动:
- 从 response 拿
usage.prompt_tokens / completion_tokens。 - 在价格表里查
model(含 provider 前缀)。 - 算出
response_cost写进_hidden_params。
from litellm import completion resp = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "hello"}], ) print(resp.usage.prompt_tokens, resp.usage.completion_tokens) print(resp._hidden_params["response_cost"]) # 0.0000123 (美元) print(resp._hidden_params["response_ms"]) # 延迟 print(resp._hidden_params["model"]) # 真实路由到的模型
completion_cost():独立算价
如果你不是通过 LiteLLM 发起的调用,但想复用价格表:
from litellm import completion_cost # 给 response 对象算 cost = completion_cost(completion_response=resp) # 只给 token 数算 cost = completion_cost( model="claude-sonnet-4-5", prompt="...", # 或 prompt_tokens=2000 completion="...", # 或 completion_tokens=500 ) print(f"${cost:.6f}")
常用场景:估预算("下个任务 10w 请求大概多少钱")、做 A/B("A 模型 vs B 模型,谁的单位任务成本低")。
token_counter:调用前先估
发请求前你就想知道这个 prompt 多少 token,避免超 context:
from litellm import token_counter, get_max_tokens n = token_counter( model="gpt-4o", messages=[{"role": "user", "content": long_text}], ) ctx = get_max_tokens("gpt-4o") # 128000 if n > ctx * 0.8: text = truncate(long_text) # 自己的裁剪逻辑
底层用 tiktoken(OpenAI 模型)和各家官方 tokenizer。对 Anthropic / Gemini 是估算,不是 100% 精确——它们没公开 tokenizer,LiteLLM 用近似算法。
自定义价格:内部部署 / 企业折扣
你在公司自己部署了 Llama-3-70B,成本只算 GPU 折旧——$0.5/M;或者跟 Azure 谈到 20% 折扣。这些改官方表就不合适了,要注入自定义价格:
import litellm # 方式 1: 挂自定义模型的价格 litellm.register_model({ "my-vllm-llama3-70b": { "max_tokens": 8192, "input_cost_per_token": 0.0000005, # $0.5/M "output_cost_per_token": 0.0000015, # $1.5/M "litellm_provider": "openai", # 走 openai 兼容协议 "mode": "chat", } }) # 方式 2: 给现有模型打折 litellm.register_model({ "gpt-4o": { "input_cost_per_token": 0.0000020, # 原价 $2.5/M, 谈到 $2.0/M "output_cost_per_token": 0.0000080, } })
成本回调:把每次花费落盘
想做"每次调用实时打到 DB / Kafka / Prometheus",用 success_callback:
import litellm def track_cost(kwargs, completion_response, start_time, end_time): """每次成功调用后被自动叫到""" cost = completion_response._hidden_params.get("response_cost", 0) model = completion_response.model user = kwargs.get("user", "anonymous") tags = kwargs.get("metadata", {}).get("tags", []) db.insert({ "ts": end_time, "user": user, "model": model, "cost": cost, "latency_ms": (end_time - start_time).total_seconds() * 1000, "tags": tags, "input_tokens": completion_response.usage.prompt_tokens, "output_tokens": completion_response.usage.completion_tokens, }) litellm.success_callback = [track_cost] completion( model="gpt-4o", messages=msgs, user="alice@company.com", metadata={"tags": ["chat", "support"]}, )
把这三字段(user / model / tags)打透,下游任何仪表盘都够用了。
流式响应的 cost 追踪
流式响应默认不带 usage(所有 chunk 加起来才知道花了多少),这是个常见坑。两个解:
# 解法 1: 要求 provider 最后一 chunk 带 usage resp = completion( model="gpt-4o", messages=msgs, stream=True, stream_options={"include_usage": True}, # ← OpenAI/Azure 支持 ) chunks = [] for c in resp: chunks.append(c) # 解法 2: LiteLLM 的 stream_chunk_builder 重建完整 response from litellm import stream_chunk_builder full = stream_chunk_builder(chunks, messages=msgs) print(full._hidden_params["response_cost"])
这件事放进你的 success_callback 里自动处理,业务代码不用管。
Budget Manager:代码级限额
SDK 层的 BudgetManager 给用户/项目设个硬上限,超了就抛异常:
from litellm import BudgetManager bm = BudgetManager(project_name="my-app") bm.create_budget( total_budget=100.0, # 上限 $100 user="alice", duration="monthly", # daily/weekly/monthly/yearly ) # 调用前先查 if bm.get_current_cost(user="alice") >= bm.get_total_budget("alice"): raise Exception("budget exceeded") resp = completion(model="gpt-4o", messages=msgs, user="alice") # 调用后更新 bm.update_cost(completion_obj=resp, user="alice") bm.save_data() # 持久化
这是代码级的软限——业务代码要配合查。做硬限(网关拦截、超额 429),需要 Proxy Server,下一章讲。
按 tag 分摊成本
公司有"客服/搜索/内容生成/内部 RPA"几条线,每条线该独立核算。用 metadata.tags 区分、在 callback 里归类即可:
completion(model="gpt-4o", messages=msgs, user="alice", metadata={"tags": ["line:support", "feature:chat", "env:prod"]})
-- 下游分析 SELECT tag, SUM(cost) AS spend, COUNT(*) AS calls, AVG(latency_ms) AS avg_lat FROM llm_logs, UNNEST(tags) tag WHERE ts >= '2026-05-01' GROUP BY tag ORDER BY spend DESC;
Router 级的预算
Router 上有一个更有用的东西 budget_duration——给某个 deployment 独立设预算:
model_list = [{
"model_name": "expensive-o3",
"litellm_params": {"model": "openai/o3", "api_key": "..."},
"model_info": {
"base_model": "o3",
},
# 这个 deployment 每天最多烧 $50
"max_budget": 50.0,
"budget_duration": "1d",
}]
router = Router(model_list=model_list, redis_host="...")
效果:超预算时这个 deployment 自动从路由池里跳过,fallback 接管。比"业务代码自己查额度"稳得多——Redis 共享状态,多实例也准。
调用前估算 + 事后校准
一个健康的 LLM 团队,成本管理是两条腿走路:
| 估算(pre-call) | 校准(post-call) | |
|---|---|---|
| 工具 | token_counter + 价格表 | response_cost + callback |
| 用处 | 选模型、切长文档、拒超长 prompt | 实际账单、按业务分摊、监控 |
| 准确度 | ±5%,够决策 | 精确,等于账单 |
| 时机 | 请求发出前 | 请求完成后 |
生产级监控模板
一个最小但够用的 callback 组合(写 Prometheus + 日志 + 告警):
import time from prometheus_client import Counter, Histogram COST = Counter("llm_cost_usd_total", "LLM cost (USD)", ["model", "user"]) CALLS = Counter("llm_calls_total", "LLM calls", ["model", "status"]) LAT = Histogram("llm_latency_seconds", "LLM latency", ["model"]) def on_success(kwargs, resp, t0, t1): model = resp.model user = kwargs.get("user", "anon") cost = resp._hidden_params.get("response_cost", 0) COST.labels(model, user).inc(cost) CALLS.labels(model, "ok").inc() LAT.labels(model).observe((t1 - t0).total_seconds()) def on_failure(kwargs, exc, t0, t1): model = kwargs.get("model", "?") CALLS.labels(model, type(exc).__name__).inc() litellm.success_callback = [on_success] litellm.failure_callback = [on_failure]
在 Grafana 上做三张图:
- 实时 spend rate:
rate(llm_cost_usd_total[5m]) * 3600,美元/小时。超 $X/h 告警。 - Top 花钱用户 Top 10:方便揪出死循环脚本。
- 模型成本占比:饼图,确认预算分配是否合理。
常见坑位
- 流式忘了
stream_options:cost 永远是 0,以为免费。生产所有流式调用必须 include_usage 或用 stream_chunk_builder。 - 价格表过期:pip 版本锁死半年,provider 已经降价 2 次——你还在按老价格核算成本偏高。季度更新 LiteLLM 版本或定期 pull 最新 JSON。
- 没打 user 字段:所有调用归到 anonymous,出事了抓不到人。gateway 层强制塞
user。 - callback 阻塞主流程:同步写 DB,DB 挂了 LLM 也挂。callback 里所有 IO 放 async queue,失败不影响主调用。
- 用 GPT-4o 做分类任务:同样准确度 gpt-4o-mini 够用,省 10 倍。用 eval 数据逼自己选便宜模型。
- tokens 没裁剪就塞 context:用户粘了一整本小说,context 占满 120K,一次 $0.3。入口 token_counter 硬拦。
- 预算只设月,不设天:前 3 天烧完全月预算,后 27 天服务挂。日限 + 月限双设。
- cache_hit 的 cost 没归零:部分版本命中缓存 cost 仍返真调用的值,导致月末账单对不上。生产校验
cache_hit=True时手动把 cost 设 0。
本章小结
- LiteLLM 内置
model_prices_and_context_window_backup.json,自动算每次调用的response_cost - 四个核心 API:
token_counter(前)/completion_cost(事后)/register_model(自定义价)/success_callback(落盘) - 流式调用必须加
stream_options={"include_usage": True}或用stream_chunk_builder,否则 cost 永远 0 - 代码级预算用
BudgetManager,Router 级用max_budget + budget_duration带熔断 - metadata.tags 是做"按业务分摊成本"的标准做法
- 监控三图:实时 spend rate / Top 花钱用户 / 模型占比——没看板就是瞎飞
- 估算 + 校准两条腿:前面选模型、后面出账单,价格和实际出入 <5% 就算健康