☰
LiteLLM 实战:一个接口统一接入多家大模型,还能做代理与成本追踪
2026/10/13 4:48:42 网站建设 项目流程

1. 我为什么给团队引入 LiteLLM:多模型接入的“第一性问题”

1.1 一个接口调遍所有模型,这不只是“少写两行代码”

如果你同时接过 OpenAI、Claude、Gemini 三家的 API,应该能体会到那种烦躁:每家都有自己的消息格式、参数命名、错误码、限流策略。上午刚把 GPT-4o 的流式输出调通,下午需求变了要换 Claude,你发现它的system字段、max_tokens语义都不一样,代码又得改一遍。这种体验做一次两次还能忍,一旦公司里有三五个项目、每个项目都要接两三家模型,维护成本就会变成一场灾难。

LiteLLM 解决的就是这个“第一性问题”:它把 100 多家模型提供方(OpenAI、Anthropic、Google Gemini、Azure、Bedrock、Hugging Face 等)全部翻译成同一套 API 格式。你在代码里写model="gpt-4o"也好,写model="claude-3-5-sonnet-20241022"也好,对上层应用来说,请求体和响应结构完全一致。这样换来换去,只是改一个字符串的事,而不是重写一个调用层。

更关键的是,它不只是把参数“翻译”一下。LLM 调用中的很多细节——token 计数、上下文长度、工具调用格式、流式事件结构、限流错误分类——在不同厂商之间差异极大。LiteLLM 把这些全部做了归一化处理。我之前接 Azure 的 OpenAI 接口时最头疼的就是api_version参数,稍不留神就 404;用 LiteLLM 之后,api_base和api_version都塞进litellm_params里,应用层完全不感知。

1.2 LiteLLM 的核心定位:SDK 加代理层,两层各管什么

很多人以为 LiteLLM 只是个 Python 库,其实它分两层,理解这两层是使用它的前提。

第一层是SDK,即你在 Python 代码里直接import litellm然后调用completion()。它适合单机脚本、后端服务内部使用,解决的是“代码层面统一调用”的问题。这层最轻量,装个 pip 包就能跑,不引入额外服务。

第二层是Proxy Server,一个独立部署的服务进程,暴露一个 OpenAI 兼容的 HTTP 接口。前端、后端、数据团队都可以拿它当 OpenAI 的平替来用,只需把base_url指向代理地址。真正的模型路由、密钥管理、配额控制、成本统计全部在代理层完成。这层才是我认为最有价值的部分,因为它把“个人写代码方便”上升到了“团队协作有秩序”的层面。

所以,如果你只是自己写个脚本调模型,用 SDK 就够了;如果你们是一个团队、多个服务、多种模型混用,那 Proxy 层基本是绕不开的。这篇文章我会把两层的用法、配置、坑都过一遍,重点放在工程化落地上。

2. 十分钟上手:SDK 的统一调用与参数翻译

2.1 从 OpenAI 到 Claude、Gemini,单行切换的代价

先看最基本的用法。下面这段代码,就是用 LiteLLM 调用 GPT-4o:

import litellm response = litellm.completion( model="gpt-4o", messages=[ {"role": "user", "content": "用一句话解释什么是依赖注入"} ] ) print(response.choices[0].message.content)

注意,这里的返回对象response是OpenAI 格式的,也就是说它跟openai库返回的响应结构完全一致:choices[0].message.content、usage.prompt_tokens、finish_reason,没有区别。如果你已经有基于 OpenAI SDK 写的代码,迁移到 LiteLLM 几乎可以无痛替换。

现在要切到 Claude,改一行:

response = litellm.completion( model="claude-3-5-sonnet-20241022", messages=[ {"role": "user", "content": "用一句话解释什么是依赖注入"} ] )

切到 Gemini 的话稍微多写一步——Gemini 模型一般要加gemini/前缀:

response = litellm.completion( model="gemini/gemini-1.5-pro", messages=[ {"role": "user", "content": "用一句话解释什么是依赖注入"} ] )

这种前缀规则在 LiteLLM 里很典型,你可以理解为它的“命名空间约定”:anthropic/、vertex_ai/、bedrock/、azure/、huggingface/。对应哪家厂商就带哪个前缀。它们的作用是告诉 LiteLLM 应该用哪一套参数翻译规则去解析你的请求。少加或者加错前缀,最常见的结果就是请求被当成 OpenAI 格式发给错误的端点,然后返回一堆莫名其妙的 404。

而这套切换逻辑背后,是 LiteLLM 内部的 Provider 适配层。每个 provider 都有自己的参数映射表:max_tokens在 OpenAI 里叫max_tokens,在 Anthropic 里叫max_tokens但语义略有差异,在 Gemini 里可能叫max_output_tokens。你不需要关心这些,LiteLLM 会帮你翻译过去,同时把响应统一成 OpenAI 结构返回。这一点极其重要,因为上层只要依赖一套结构,后续换模型就真的只改配置不改代码。

2.2 异步、流式、函数调用,统一不只是“名字换个前缀”

实际业务里很少只用同步调用。LiteLLM 的异步接口也是现成的,用acompletion就行:

import asyncio from litellm import acompletion async def ask(): response = await acompletion( model="gpt-4o", messages=[{"role": "user", "content": "给我三个Python学习的建议"}], ) return response.choices[0].message.content asyncio.run(ask())

流式输出同样做了统一。你传stream=True,拿到的是一个生成器,逐块 yield 增量内容:

response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "写一首关于秋天的五言绝句"}], stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)

这里有个我在真实项目中踩过的坑:不同厂商的流式 chunk 结构不一样,OpenAI 的delta.content,Anthropic 的delta.text,Gemini 的candidates[0].content.parts[0].text。如果你直接用原生 SDK,代码里至少要写两套流式解析逻辑。用 LiteLLM 之后,底层翻译层会把这些差异抹平,你拿到的一律是 OpenAI 格式的 chunk。这也是很多团队愿意引入它的直接原因——流式解析太碎人了。

再进一步,函数调用(Function Calling / Tool Calling)也做了统一。OpenAI 的tools参数、Anthropic 的tools参数、Gemini 的tools参数在语义上有差异,但 LiteLLM 用一套 OpenAI 风格的tools定义,剩下的翻译它负责。实际跑过一遍你会发现,工具调用的响应结构(tool_calls字段)在不同模型间确实能对齐到同一套格式,这给 Agent 类应用减少了大量适配工作。不过要注意的是,各家对工具调用的能力和限制并不一样,有些小模型对工具的遵循程度很差,这个不是 LiteLLM 能解决的,选型时还是得看模型本身的能力。

2.3 环境变量与异常处理,容易踩的第一组坑

LiteLLM 读取 API Key 的方式很直接——从环境变量拿。官方约定是每家厂商对应一个标准环境变量名:

export OPENAI_API_KEY="sk-xxxx" export ANTHROPIC_API_KEY="sk-ant-xxxx" export GEMINI_API_KEY="xxxxx" export AZURE_API_KEY="xxxxx"

如果你在调用时没有显式传api_key,LiteLLM 就会去这些环境变量里找。这个小机制用起来很顺手,但也埋了一个隐雷:每个模型默认关联一套环境变量,如果你同时接了多个 OpenAI 兼容服务,比如既用官方 OpenAI 又用某个国内大模型的 OpenAI 兼容端点,环境变量就会冲突。官方建议是用不同的 provider 前缀和api_base区分,或者干脆在调用时显式传参:

response = litellm.completion( model="openai/gpt-4o", api_key="sk-本地服务的key", api_base="https://某个兼容端点/v1", messages=[{"role": "user", "content": "你好"}], )

我这里特意把model写成openai/gpt-4o而不是gpt-4o,因为一旦指定了api_base,带上 provider 前缀会更明确,避免 LiteLLM 按默认 OpenAI 地址去请求。

异常处理是另一个容易被忽略的点。LiteLLM 把各家的错误统一成自己的异常体系,最常用的几个是:

  • litellm.RateLimitError:限流了,可能是 QPS 超了,也可能是额度不够
  • litellm.AuthenticationError:密钥无效或权限不足
  • litellm.ContextWindowExceededError:上下文超长
  • litellm.Timeout:请求超时

建议在业务代码里对这几个异常分类处理,尤其是限流和上下文超长,前者要做退避重试,后者要触发上下文裁剪或改用更长上下文的模型。不要所有异常都一把梭,否则线上排查问题时会很痛苦。

3. 真正改变协作方式的是代理层(Proxy Server)

3.1 配置驱动:model_list 与 litellm_params

SDK 解决了单机问题,但团队协作需要更规范的东西。LiteLLM Proxy 干的事,就是把“用哪个模型、配哪个密钥、走哪个地址”全部变成一份 YAML 配置,启动一个服务,其他人只用 OpenAI 兼容客户端就能用。

一个最小的配置是这样的:

model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY

这里model_name是给外部客户端看的“别名”,litellm_params是真正传给 LiteLLM 的参数。启动命令很简单:

litellm --config config.yaml --port 4000

然后任何服务都可以把它当 OpenAI 用,只需要改两个字段:

from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="sk-anything", # 代理层会校验这个 key ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) print(resp.choices[0].message.content)

我特别想强调model_name和model的区分。前者是暴露出去的别名,后者是真实路由目标。这个解耦带来一个好处:你可以让所有客户端都写model="gpt-4o",但实际上后端路由到别的模型,只要改配置文件就行,客户端完全不用动。这对于灰度切换、模型降级、供应商替换来说是极强的灵活性。

3.2 虚拟 Key:团队共享一个模型,却能各自限额

代理层配置好之后,你面临的第一个现实问题就是:密钥怎么管理。

把真正的 OpenAI Key 直接发给所有开发人员,风险太大了。万一有人不小心把 Key 提交到 Git 仓库,账单分分钟爆炸。LiteLLM 的答案是虚拟 Key(Virtual Keys):你在代理层用主密钥生成一堆子密钥,每个子密钥可以限定可访问的模型、预算上限、过期时间等,对外只暴露子密钥。

生成虚拟 Key 的调用是这样的:

curl -X POST "http://localhost:4000/key/generate" \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 10.0, "budget_duration": "1d" }'

返回结果里会包含一个新的api_key,比如sk-abcdefg。这个 key 只能调gpt-4o这个模型,每天最多花 10 美元。到期或者超出预算,代理层会自动拒绝请求。

这套机制对团队管理的价值在于:权限从“一把钥匙全家用”变成了“每个人一个 budget 配额”。谁用量异常,一眼就能从日志里看到;某个测试环境密钥泄露了,直接在代理层吊销,不用动真实上游 Key。我实际用下来,这个功能被团队引用最多,因为大家都被“Key 泄露”这件事吓怕过。

3.3 预算、限流与用量审计:把 API Key 变成内部资源

虚拟 Key 背后其实是一套完整的预算和限流体系。除了上面说的 per-key 预算,代理层还能做全局频率控制,防止某个服务把上游 QPS 打满。

限流配置可以放在litellm_settings里:

litellm_settings: max_parallel_requests: 100 num_retries: 3 request_timeout: 30 cooldown_time: 30

max_parallel_requests是代理层能同时处理的最大请求数,超过的会排队或直接拒绝;num_retries和request_timeout是请求上游失败的自动重试次数和超时时间;cooldown_time是某个 provider 连续失败后进入冷却的时间,避免雪崩式重试把上游打挂。

用量审计则靠/spend/logs这类接口。代理层每完成一次请求就会记录一次花费(前提是模型和单价能对上,如果用了自定义模型,可能需要在配置里手动声明价格)。你可以查某个 key 今天花了多少钱、调了哪些模型、每次请求的 prompt 和 completion token 是多少。这些数据对成本分摊、异常预警都非常有用。

我自己的习惯是每周拉一次 spend 汇总,按 key 分组看趋势。有一次发现某个 key 的日花费突然翻了五倍,查日志发现是同事写了个定时任务,循环调模型做数据清洗,忘了加缓存。如果没有这种审计能力,这种问题可能要等到账单出来才发现。

4. 工程化能力:重试、回退、负载均衡与缓存

4.1 故障回退:主模型挂了怎么自动切换

单点依赖任何一家大模型供应商都是高风险的事。对方一次区域性故障,你的服务就跟着瘫痪。LiteLLM 的 fallback 机制可以让主模型不可用时自动切到备选模型。

配置方式如下:

model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: gpt-4o-fallback litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY litellm_settings: fallbacks: gpt-4o: - gpt-4o-fallback

这里的意思是:客户端请求gpt-4o,如果 OpenAI 那边出现限流、超时、5xx 等可重试错误,代理层会自动改发gpt-4o-fallback,也就是 Claude 模型。对客户端来说完全无感,它只知道自己调gpt-4o成功了。

我在实际配置中有一条经验:fallback 的目标模型尽量在能力和上下文上跟主模型对齐,否则用户会明显感觉到输出质量下降。比如 GPT-4o 挂掉后切到一个 7B 小模型,虽然请求是成功了,但回答质量一落千丈,业务方可能反而更难受。Claude 3.5 Sonnet 或 Gemini 1.5 Pro 这类同级别模型才是合适的备胎。

另外,fallback 级别可以设置得更多层。你可以配主模型 → 备选1 → 备选2,形成一条链路。不过层数越多,单次请求的延迟就越不可控,因为每一层都要等前一层超时。实际用两层就够了,最多三层,再往上就是过度设计。

4.2 负载均衡:多 Key 分摊要有“配速”而不是抢跑

当你的用量大到单个 Key 的 QPS 不够用,或者你想把成本分散到多个账号上时,负载均衡就派上用场了。做法是给同一个model_name配置多个litellm_params,每个持有不同来源的 Key。

model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 - model_name: same-model-name litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2

注意第二个条目的model_name跟第一个完全一样,它们会被 LiteLLM 合成为同一个逻辑模型的多个上游路由。默认情况下 LiteLLM 会用加权轮询或最少连接的方式分发请求,让多个 Key 的用量尽量均匀。

不过这里我要提一个很多人容易犯的错误:负载均衡不是简单的“轮流发”。如果两个 Key 分别属于不同的账号,而其中一个账号的速率限制明显更低,平均分发会导致那个账号频繁触发限流。LiteLLM 内置了 usage-based routing 策略,会参考每个上游近期的成功率、延迟、剩余额度来做智能分发,而不是无脑轮询。所以最好在router_settings里显式声明路由策略:

router_settings: routing_strategy: "usage-based-routing"

在启用这个策略之前,建议先让系统积累一段时间的历史调用数据,否则没有历史参考时,它只能退化成随机分发。

4.3 缓存与流控:降低成本和延迟的取舍

另一个我强烈建议开启的功能是缓存。LLM 调用贵在两个地方:一是 token 费用,二是延迟。如果业务里有大量重复或相近的请求——比如客服系统的常见问题答案、内部工具的固定提示词——命中缓存能省掉一大笔钱。

LiteLLM 的缓存可以在配置里直接打开:

litellm_settings: cache: true cache_params: type: redis host: localhost port: 6379 ttl: 3600

缓存命中后,同样的 prompt 和参数组合会直接返回缓存结果,不再请求上游模型。需要注意,缓存 key 默认基于请求的 model、messages、temperature 等参数生成,所以如果你在 prompt 里带了时间戳、用户 ID 这类易变内容,命中率会很低。设计提示词时,把高频稳定部分和高变部分分开,缓存才有意义。

更有意思的是 LiteLLM 还支持语义缓存,可以用 embedding 相似度来命中“问法不同但语义相同”的请求。这个功能很惊艳,但配置和调参成本也更高,建议先从严格缓存(exact cache)开始,命中率稳定了再考虑升级。流控和缓存是一对搭档:缓存降低压力,限流防止突发打满上游。两者一起用,成本结构会健康很多。

5. 运维高光时刻:成本追踪与可观测性

5.1 调用日志与 spend 统计

Proxy 起来之后,如果只是“能用”,那离“好用”还差一截。你要能回答几个问题:今天的 token 消耗是多少?每个模型各花了多少钱?哪个业务线消耗最凶?有没有异常暴涨?

LiteLLM 自带一个简单的管理界面和一组统计接口。请求/spend/logs可以看到每一笔调用的 token 和费用明细:

curl "http://localhost:4000/spend/logs?api_key=sk-abcdefg"

返回的结果包含模型名、prompt tokens、completion tokens、总费用、请求时间等。把这些数据拉回来,配合简单的脚本就能做日报、周报。

要注意的是,费用计算依赖模型单价表。对官方主流模型,LiteLLM 内置了价格字典,不用手动配;但对自定义模型、自托管模型,它并不知道单价,需要你在litellm_settings里补充:

litellm_settings: model_cost_map: "my-custom-model": input_cost_per_token: 0.000001 output_cost_per_token: 0.000002

如果不配,自定义模型的花费会显示为 0,spend 统计就会失真。我见过有人在年报时才发现有个模型已经默默烧了一个季度,就是因为当时没配单价。这个步骤容易漏,但我建议任何自定义模型一接入就先补上。

5.2 自定义日志、监控与告警的接法

内置接口能满足基本诉求,但真正的生产环境通常还要接自己的监控体系。LiteLLM 支持通过success_callback和failure_callback挂接自定义处理函数。

在 SDK 模式下:

import litellm def my_callback(kwargs, completion_response, start_time, end_time): print(f"model: {kwargs['model']}, cost: {kwargs.get('response_cost')}") litellm.success_callback = [my_callback]

每次成功的请求都会触发这个函数,你可以拿到模型名、耗时、花费等信息,然后推到日志平台、发到告警群、或写入数据库。在 Proxy 模式里,回调函数一般放在模块里 import,然后在配置中用litellm_settings.callbacks指定。

另外 LiteLLM 也支持把指标打到 Prometheus,配合 Grafana 做可视化。监控面板上我至少会放三块:请求成功率、平均延迟、费用趋势。连续五分钟成功率低于某个阈值就告警,这能帮你提前发现上游故障和 Key 失效,而不是等用户投诉。

5.3 部署形态选择:本地进程、Docker 与配置管理

最后说下部署。Proxy 本质上是一个 FastAPI 服务,最简单的跑法是本机起进程。但生产环境强烈建议容器化,官方镜像可以直接用:

docker run \ -d \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e OPENAI_API_KEY=sk-xxxx \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml

镜像名会随版本更新,建议固定到具体 tag,而不是用latest。配置文件和密钥不要打进镜像,用环境变量或挂载卷传入,这是最基本的安全习惯。

多环境管理上,我推荐一套环境一套配置文件的策略:config-dev.yaml、config-staging.yaml、config-prod.yaml。不同环境的模型列表、预算限制、回调目标都不一样,不要试图用一份配置全覆盖,否则你会被各种“为什么 dev 环境把 prod 的 key 打满”的问题折磨。

6. 长期使用后的经验清单与避坑建议

6.1 模型命名规约是第一份文档

用了几个月的 LiteLLM 之后,我最大的感触是:这不是一个技术问题,而是一个管理问题。

团队成员会在model_name上各写各的,有人写gpt-4o,有人写gpt-4o-latest,还有人写gpt4o。虽然代理层都能配出来,但当你去查 spend 日志时,模型维度是碎的,统计起来非常麻烦。我建议从第一天就定一个命名规范:输出到客户端的模型名、内部配置里的模型名、供应商侧的模型 ID,三者分开,但要在配置里做好对应关系注释。哪怕是个人项目,也值得花十分钟把这份映射关系写清楚。

6.2 上下文长度与 token 计数别只看模型名

LiteLLM 会帮你把max_tokens、temperature等参数做翻译,但它不会替你做上下文管理。GPT-4o 的 128K 上下文在文档里写得清清楚楚,但实际请求里,你传给它的系统提示、历史消息、工具定义都会占上下文空间。一旦超了,LiteLLM 会抛ContextWindowExceededError。

我的做法是:在业务层做一层 token 预估,快接近上限时就自动做消息裁剪——砍掉最老的历史对话,保留最新的用户意图。不要等到异常抛出来再补救,那种体验对用户来说已经是灾难了。文本分块、摘要压缩这种老一套信息检索手段,在 LLM 应用里依然管用,只是要适配 token 计数的精确度。

6.3 自托管模型与自定义 API 的接入技巧

如果你要接入的不是主流厂商,而是某个自托管服务、某个开源模型的私有化部署、或者某个提供 OpenAI 兼容接口的平台,LiteLLM 一样能接。核心思路是:给它一个名字,指定api_base指向你的服务,必要时通过custom_llm_provider显式声明供应商类型。

response = litellm.completion( model="my-local-llama3", api_base="http://127.0.0.1:8000/v1", custom_llm_provider="openai", messages=[{"role": "user", "content": "你好"}], )

这里custom_llm_provider="openai"表示用 OpenAI 兼容协议去请求本地服务。大部分开源 server 都实现了 OpenAI 兼容端点,所以这条路径基本通用。接入之后,你可以享受统一的请求格式、错误归一、代理层管理,同时底层用的还是自己的模型。要注意的是,自托管模型的性能指标(上下文窗口、QPS)要自己在价格表里声明,别让 LiteLLM 自动按某个内置模型去猜,否则 token 计数和费用统计都会出错。

6.4 最后的一点运维心得

说到最后,我想分享一个实际的运维体会:LiteLLM 这类工具最大的价值不在“省代码”,而在“留退路”。模型厂商的定价、能力、稳定性都在剧烈变化,今天最优的模型三个月后可能就不再便宜。你把所有模型调用都收敛到一个代理层之后,更换供应商、切模型、控成本,都变成配置修改,而不是代码重写。

但代价是,你要监控这个代理层本身。它挂了,所有模型调用都会断。所以生产环境至少做两个实例,前面挂个负载均衡,配置文件和上游密钥要走正式的变更流程。我在一个小团队里踩过“代理层一直很稳,没人管它,结果一次磁盘写满把全站请求带崩”的坑。像这种基础设施型组件,稳定是第一优先级的,再花哨的功能也不如一台永远不死机的进程重要。

LiteLLM 不是银弹,但它把那些最容易让人烦躁的集成细节全部收敛到了一处。从一个脚本到一套平台,它都有对应的位置。如果你正在为“接模型”这件事头疼,不妨先跑一个最小 Demo,从一个模型、一个代理、一个虚拟 Key 开始,体会一下“所有请求都走同一道门”的感受。等你想换模型的时候,你会感谢自己当初多花了这半小时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询