这半年我经手的项目里,大模型接入方式变得越来越“百花齐放”。有的模块走云厂商的 OpenAI 兼容接口,有的单独接了 Claude 的 API,还有一个老系统只能用 OpenAI 格式打一个本地推理服务。刚开始我也觉得每个模块各自包一层 SDK 没什么问题,直到代码里冒出了一长串 if-else,每个分支处理不同的请求体、不同的鉴权头、不同的超时策略,改一个字段要连带改四个地方。后来同事把 litellm 丢给我,说“别自己造轮子了”。我花了一个下午把流量切过去,整个体验让我挺意外。这篇文章就把我从安装、配置、跑通到生产部署踩坑的完整过程写出来,给准备统一管理大模型接口的团队一点参考。
1. 为什么要在业务代码和各家模型 API 之间插一层
1.1 我当时的代码长成了什么样
先看看当时让代码变得难维护的典型结构。业务里需要根据用户选择的模型名,把同一个 Prompt 发给不同厂商,结果就是一个“路由函数”:
def call_model(model: str, text: str): if model.startswith("gpt"): return call_openai(text) if model.startswith("claude"): return call_anthropic(text) if model.startswith("internal"): return call_internal_openai_compatible(text)这个函数看第一眼没问题,但它维护起来特别费劲。每加一个厂商,就要加一个 if 分支;每个分支都要处理鉴权方式、错误码、重试逻辑、超时时间,甚至连 token 统计的单位都不一样。更麻烦的是,很多业务方只想要一个稳定的调用接口,不想关心后端到底是哪家。等模型数量增加到十几个,这个“路由函数”就变成了整个项目里所有人都不敢碰的地方。
还有一层隐性成本是密钥管理。真实 API Key 散落在各个服务的环境变量里,新同事入职首先要挨个问“这个 key 是哪个平台的”“那个 key 有没有额度限制”。出了问题要定位是哪个人、哪个项目、哪一次调用超了预算,基本靠猜。
1.2 litellm 拆解了这个问题的哪三块
litellm 本质上做的是一个“大模型 API 网关”,它把市面上各种模型的调用方式统一成了一层。我最关注的是它解决的三块问题:
第一是统一调用格式。你只需要用 OpenAI 格式的model + messages结构发请求,litellm 会在内部把它翻译成对应厂商需要的格式,再转发出去。最直观的效果就是业务代码全部按同一种写法人,模型切换不需要改调用逻辑。
第二是统一密钥和权限管理。它允许你在代理层生成虚拟 Key,业务方拿着虚拟 Key 来调用,真实 Key 只存在配置里。这样你可以针对每个虚拟 Key 设置预算、速率、模型白名单,谁用了多少一眼就能查出来。
第三是统一可观测性。每一次请求的模型、token 用量、耗时、成本都能集中记录,还能回调到监控系统。这一点在多个模型混跑的时候非常重要,否则月底算成本的时候只能靠猜。
这里用生活类比可能好理解一些:你家里有电视机、空调、冰箱,每一台电器都有自己的遥控器,你肯定不想每个电器配一个 App。litellm 相当于把这些遥控器合并成一个万能遥控器,虽然每种电器的协议不一样,但你在万能遥控器上按的键是统一的。放在项目里的意义就是把“适配各种厂商 SDK”这件事从业务代码里剥离掉,业务只跟一个标准接口打交道。
2. 两种接入方式:SDK 模式与代理模式
2.1 SDK 模式:适合写脚本和实验
litellm 最开始的使用方式是在 Python 代码里直接调用 SDK 模式:
import litellm response = litellm.completion( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "你好"}] ) print(response["content"])你把 model 参数换成gpt-4o,请求就会发到 OpenAI;换成ollama/llama3.1,请求就会发到本地 Ollama 服务。SDK 模式的优点是轻量、灵活,特别适合写脚本、做实验或在集成测试里快速验证某个模型的效果。但它的问题是只面向 Python 生态,而且每个服务都要自己加载配置,密钥散落在不同进程里,不符合多团队、多语言场景的治理需求。
所以我一直觉得,个人用或者临时验证用 SDK 模式很舒服;但如果是整个团队、多个后端服务都要接大模型,就得看代理模式了。
2.2 代理模式:一套 OpenAI 兼容接口接管所有模型
代理模式是 litellm 最能打的部分。你启动一个服务,它对外暴露的是 OpenAI 格式的 HTTP 接口。这种做法非常聪明,因为 OpenAI 的请求格式已经是事实标准了,市面上几乎所有大模型 SDK、LangChain、各类开发框架都原生支持指定 base_url。
启动代理后,你就可以在任意语言里这样调用:
from openai import OpenAI client = OpenAI( api_key="sk-虚拟key", base_url="http://localhost:4000", ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], )这里模型名gpt-4o不一定是真的 OpenAI 模型,它可以是你在 litellm 配置里定义的“别名”。比如你可以在配置里让model_name: gpt-4o映射到 Anthropic 的某个模型,那调用方写的代码完全不用变,实际走后端都换了。这种设计对业务侧极其友好,本来要花半天改的模型迁移,变成只改代理配置文件。
两种模式的区别我做了一个表格方便对比:
| 维度 | SDK 模式 | 代理模式 |
|---|---|---|
| 接入方式 | Python 直接调用 | HTTP 代理服务 |
| 适用场景 | 脚本、实验、单机验证 | 多服务、多语言、团队协作 |
| 改动成本 | 业务代码直接依赖 litellm | 业务只改 base_url |
| 密钥管理 | 各服务自行管理 | 集中在代理层 |
| 可观测性 | 自行处理 | 代理统一记录 |
2.3 配置 model_list 时的命名陷阱
如果你已经决定用代理模式,第一个要理解的概念就是model_list。它是一份“协议路由表”,声明了哪个对外模型名对应哪个真实后端模型。这里最容易踩的坑是:对外名字的长度和语义。
我见过有人直接把model_list里所有真实模型名原封不动搬出来,比如:
model_list: - model_name: gpt-3.5-turbo litellm_params: model: openai/gpt-3.5-turbo这样做的问题在于,当你有多个平台的gpt-3.5-turbo兼容模型时,model_name会撞车,或者业务方想通过模型名表达“用便宜一点的版本”时根本做不到。
我现在的习惯是把model_name定义成业务语义,比如fast-llm、high-quality-llm、internal-llm,然后在litellm_params里才写真实模型。这样模型在后端怎么切换、替换、灰度,业务方完全不需要知道。迁移模型的时候只改代理层配置,业务代码一行都不用动。这个习惯帮我在后面几次模型切换里省了大量沟通成本。
3. 从零跑通完整链路:安装、配置、建虚拟密钥、调用
3.1 安装与启动
安装很简单,用 pip 装带 proxy 扩展的版本:
pip install "litellm[proxy]"然后准备环境变量。如果你用的是 OpenAI 和 Anthropic,就先导出对应的 Key:
export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..."接着可以直接用命令行启动一个最简单的代理:
litellm --model gpt-3.5-turbo --port 4000这样启动后,访问http://localhost:4000/chat/completions就能请求到 OpenAI。但这种方式只是临时验证,所有模型配置都写在命令行里,管理起来不方便。真正要长期用,必须走配置文件。
3.2 config.yaml 里的核心字段
新建一个config.yaml,把路由表、代理设置、全局配置都写进去:
model_list: - model_name: fast-llm litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: strong-llm litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY litellm_settings: drop_params: true set_verbose: false general_settings: master_key: sk-litemaster-123456这里有几个字段需要重点说明:
model_name是对外暴露的名字,业务方只认这个名字。litellm_params.model是真实模型标识,格式是厂商前缀/模型名,比如openai/gpt-4o-mini、anthropic/claude-3-5-sonnet、ollama/llama3.1。api_key: os.environ/OPENAI_API_KEY表示从环境变量里读 Key,不要把真实 Key 写进配置文件。
general_settings.master_key是代理自己的管理密钥。管理操作必须用它,比如生成虚拟 Key、查看日志、修改模型配置。这个 Key 千万不要泄露出去,它相当于代理服务器的 root 权限。
配置好之后启动:
litellm --config config.yaml --port 4000看到服务启动日志后,用 curl 先做一次最简单的验证:
curl http://localhost:4000/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-litemaster-123456" \ -d '{ "model": "fast-llm", "messages": [{"role": "user", "content": "你好"}] }'这一步只要能正常返回choices,说明代理本身的转发链路已经通了。
3.3 虚拟密钥与配额:把成本分到每个人头上
代理通了以后,千万别直接把 master_key 发给团队用,而是应该先生成虚拟 Key。虚拟 Key 本质上是一个映射,把业务方的请求映射到某个真实账号下,同时完成配额控制。
用管理 Key 调用生成接口:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-litemaster-123456" \ -H "Content-Type: application/json" \ -d '{ "models": ["fast-llm", "strong-llm"], "max_budget": 50 }'返回结果里包含key字段,这就是你要发给业务方的虚拟 Key。它限制了这个调用方只能使用fast-llm和strong-llm这两个模型,最多消耗 50 美元预算。超出之后,代理会直接拒绝请求,不会继续扣你真实账户里的钱。
这里有个体现设计理念的细节:虚拟 Key 的配额单位是美元,litellm 会根据模型定价自动估算成本。也就是说,你不需要自己去数 token,代理会帮你把 token 换算成钱。不过这个换算是建立在 litellm 自带的定价表基础上的。自建模型或冷门模型如果没有在定价表里,成本会被算成 0,后面我会专门说这个坑。
3.4 业务侧只改 base_url,其余不动
虚拟 Key 生成好了,业务侧的接入就非常轻量。比如一个原来直接调 OpenAI 的 Python 服务:
from openai import OpenAI client = OpenAI( api_key="sk-virtual-key-xxxx", base_url="http://litellm.internal:4000", # 指向 litellm 代理 ) resp = client.chat.completions.create( model="fast-llm", messages=[{"role": "user", "content": "帮我总结一下这份文档"}], ) print(resp.choices[0].message.content)业务方唯一要做的就是改base_url和api_key,然后把模型名换成你在代理上定义的model_name。这样做的好处是:Java、Go、Node.js 等其他语言的接入方式也一样,只要是能发 HTTP 请求、能按 OpenAI 格式构造消息的客户端都可以直接接进来。
如果你嫌手动构造请求麻烦,也可以把base_url配到 OpenAI SDK 的 global config 里。但我不推荐在依赖注入里隐藏这些东西,显式传参更清晰,出问题的时候也容易查。
4. 生产环境部署:稳定性、高可用和成本监控
4.1 数据库和缓存:别让 sqlite 拖垮并发
litellm 默认会把虚拟 Key、日志、预算数据存到本地 SQLite。如果只是本地测试没问题,但一旦服务被团队多人同时使用,并发一上来 SQLite 的写锁问题就非常明显,接口延迟会突然飙高,严重时直接卡死。我第一次在测试环境里跑出这个问题时,排查了半天才反应过来是数据库锁。
生产环境老老实实接 PostgreSQL:
general_settings: database_url: postgresql://user:password@postgres-host:5432/litellm这里我补充一个判断标准:什么时候必须换数据库?当调用量超过每秒几次而且有并发写日志时,就可以换了。早点换比后面迁移省事,因为历史日志量越大迁移成本越高。
缓存方面,如果团队里有大量重复的、接近相同的请求,可以考虑配 Redis 做语义缓存:
litellm_settings: cache: true cache_params: type: redis host: redis-host port: 6379 ttl: 3600缓存命中的请求不会真正打到模型厂商,所以响应速度和成本都会有明显改善。但它有一个很微妙的副作用:我们的监控里会出现一部分“没有 token 消耗”的请求,如果监控图形上看到成本条稳定但接口延迟波动很大,大概率就是缓存命中率在变化。
4.2 负载均衡与重试:同一模型配置多份后端
负载均衡是 litellm 另一个高频用法。假设你本地用 vLLM 部署了一个开源模型,只有一张卡、一个实例。当调用量上来时,单实例很快就扛不住。litellm 允许你在同一个model_name下面配置多个相同模型的后端,它会自动把请求分散到不同实例上:
model_list: - model_name: local-llm litellm_params: model: openai/local-model api_base: http://vllm-instance-1:8000/v1 - model_name: local-llm litellm_params: model: openai/local-model api_base: http://vllm-instance-2:8000/v1代理会在这个列表里做轮转。某个实例挂了,它会把流量路由到健康的实例上。对业务方来说,它们看到的还是同一个local-llm,完全无感知。
重试则在litellm_settings里统一配置:
litellm_settings: num_retries: 3 request_timeout: 60这里有一个我自己的经验教训:不要对所有请求都盲目开启自动重试。如果业务不是幂等的,比如“根据用户描述创建一条订单”,重试可能导致重复创建。大模型调用更是如此,同样的 Prompt 每次输出不一定一样。所以我现在只在模型调用场景里开启重试,并且把重试次数控制在 2 次以内。
4.3 预算封顶:防止一个脚本烧完整个月的额度
预算管理是 litellm 最让人安心的功能之一。你可以从三个层级去控制成本:
第一层是全局预算,给整个代理服务设置一个总预算,超过之后所有请求都拒绝。
第二层是按虚拟 Key 预算,上面已经演示过,用max_budget字段控制。
第三层是按用户维度预算,如果你需要通过 API 传入user字段来标识实际使用者,可以对每个用户单独设置预算。这层比较适合给客户提供服务计费或 SaaS 化场景。
实际运营里,我见过最典型的事故是:某个测试脚本写了个 while 循环忘关,直接跑了一个下午,把月额度烧了大半。当时唯一的防线就是虚拟 Key 的预算。所以我的看法是:不管团队规模多大,每一个虚拟 Key 都必须配套预算上限,不要觉得“自己人”就不需要限制。成本问题不是信任问题,而是防呆问题。
4.4 日志与回调:把调用记录送进监控系统
litellm 支持通过 callback 把调用记录送到各种监控和分析系统。比如你可以把所有调用日志同步到 Prometheus 监控指标,或者发送到日志平台做审计。这里我列一个最小化配置:
litellm_settings: success_callback: ["prometheus"]这个配置会把每次调用的耗时、token 数、模型名、是否成功等信息暴露成 Prometheus 指标。配合 Grafana 面板,你就能实时看到每个模型每天的成本曲线、每个团队调用的趋势。
回调还有一个用途是审计:把每一次请求的完整信息落库,出了问题能回溯到具体某次调用用的什么密钥、什么参数、产生了多少成本。虽然初期可能觉得配置略繁琐,但一旦团队规模变大,这个可追溯性比什么都值钱。
5. 排查实录:我踩过的五个坑
5.1 model_name 与模型前缀
第一次用代理模式时,我把model_list里的model_name配置成了和 litellm 默认模型名一模一样的字符串,但litellm_params里又写成了不带前缀的格式,比如:
# 错误的示例 model_list: - model_name: gpt-4o litellm_params: model: gpt-4o # 缺少 openai/ 前缀结果调用时一直报错说找不到模型。litellm 在解析model字段时,需要通过前缀来判断走哪个厂商的适配器。gpt-4o这种写法会被当作某种默认处理,在代理模式下非常容易造成路由失败。后来我把所有模型的litellm_params.model都统一加上前缀,比如openai/gpt-4o、anthropic/claude-x,问题就消失了。
这个坑在官方文档里其实提过,但第一次上手的人特别容易忽略。我的经验是:配置里所有真实模型都必须遵守provider/model-name这个格式,不要偷懒。
5.2 Azure 的 api_version 和 os.environ 变量
接 Azure 的 OpenAI 兼容接口时,问题又独特一些。你必须同时提供api_base、api_key、api_version三样东西,少一个都不行。我一度只配了前两样,结果所有请求都返回参数错误。
我的配置修正成这样:
model_list: - model_name: azure-llm litellm_params: model: azure/gpt-4o api_base: https://example-azure-instance.openai.azure.com/ api_key: os.environ/AZURE_API_KEY api_version: "2024-02-01"环境变量名的坑也值得说。配置里写os.environ/AZURE_API_KEY,意思是从名为AZURE_API_KEY的环境变量读取。我一开始写成os.environ/OPENAI_API_KEY,因为这个 Key 是同一个,但实际上 Azure 的 Key 字符串跟 OpenAI 的 Key 并不一样,环境变量名必须对应到正确的变量。排查环境变量问题的最好办法是先print(os.environ.keys()),确认变量真的存在,再让代理启动。
5.3 max_tokens 字段在不同厂商间的映射
不同厂商请求体里的字段名有差异,比如 OpenAI 是max_tokens,而一些厂商同类的字段叫max_tokens_to_sample。litellm 原则上会自动做字段映射,让你统一用 OpenAI 格式发请求。
但我在一个自建模型服务上遇到了特殊情况:这个服务没有做标准字段映射,它只认自己的字段名。如果我在请求体里传了max_tokens,它直接报错。当时的解决办法是配置drop_params: true,让 litellm 在转发时把不兼容的参数丢弃,再用模型自己的默认值。
如果你遇到“请求格式对,但还是报参数错误”的情况,先用 curl 直接打后端服务,确认后端到底接受哪些字段,再回头看 litellm 有没有帮你转换。不要把锅都甩到 litellm 上,很多问题其实是后端模型自身行为差异导致的。
5.4 流式请求成本统计为 0
监控里有个很奇怪的现象:部分流式请求调用的成本统计为 0,token 用量也没有正常累加。排查后我发现,问题出在流式响应是一块一块返回的,litellm 需要把所有流式块收齐之后才能得到完整的usage。如果使用了自定义回调,而回调事件里只处理了completion事件,没有处理stream结束事件,最终记录里就没有成本数据。
这个问题的解决思路不是去改 litellm 源码,而是要在配置回调时确认它是否支持流式场景。我最后走了 Prometheus 指标聚合路线,在代理层自己统计 token 增量,而不是依赖单次回调事件。如果你发现自己的成本报表缺了很多数据,先检查流式请求占比,再检查回调配置,大概率会找到原因。
5.5 虚拟 Key 默认模型权限
虚拟 Key 生成时如果不指定models参数,很多版本默认会允许访问所有配置的模型。这看起来方便,但隐患是你没法控制某个调用方只能访问特定模型。比如你只希望测试团队用便宜模型,但它的 Key 能调最强也最贵的模型,月底账单出来的时候才后悔。
我的做法是:所有虚拟 Key 在生成时都强制指定models白名单,即使只有一个人用也照做。同时配合预算上限,形成“最小权限 + 预算封顶”的组合。不要等出了事故再补配置,那时候往往已经产生额外成本了。
这里整理一下常见问题的排查优先级:
| 现象 | 优先检查项 |
|---|---|
| 找不到模型 | model_name 与 litellm_params.model 是否匹配、是否带前缀 |
| 鉴权失败 | master_key 权限、虚拟 Key 是否过期或超出配额 |
| 参数报错 | drop_params 是否开启、后端字段映射是否生效 |
| 成本统计为 0 | 回调是否处理流式场景、模型定价表是否内置 |
| 请求超时 | 后端实例是否过载、超时时间和重试次数配置 |
6. 基于 litellm 能继续深挖的几种玩法
如果你已经把基础代理跑通了,我建议再往下面几个方向扩展一下,它们都能用已经很成熟的 litellm 能力搭配实现,不需要自己从零造网关。
第一个是模型灰度与切换。当你把model_name做成业务语义后,发布新模型时只需要改代理配置,把同一model_name下的litellm_params.model换成新版本,小流量试一下,稳定后再全量。这个流程比改业务代码轻量得多,我实战里切换一次线上模型耗时不到十分钟。
第二个是基于 Router 做更细粒度的路由。litellm 提供了 Router 类,可以在代码里定义多个模型实例,按权重分配请求、按失败率自动摘除坏节点。如果你想实现“优先用便宜模型,超过它能力范围再切换到强模型”,Router 的配置模型是能支撑这套逻辑的。我自己就用它搭过一个简单的分层路由,整体改造量不大。
第三个是结合内部的知识库做预处理。代理层接到请求后,可以先做一层内容改写或检索增强,再把最终 Prompt 传给上游。litellm 中间环节的灵活性允许我们插入自定义逻辑,但这种场景需要评估是否值得把复杂度引进来,简单团队不建议一上来就搞,先把基础稳定了再说。
说一下我个人的实际感受:litellm 不复杂,但它把一个很容易被低估的“脏活”接走了。以前我们担心厂商 SDK 升级波及所有服务,现在这些风险全部被隔离在代理层。你不需要成为大模型 API 的专家,只要把代理配好,就能让整个团队在一个相对稳定的接口上工作。
如果你是团队里负责基础技术的人,建议从最基础的配置开始,先把一条链路跑通,再逐步加预算、加监控、加缓存。litellm 的配置项很多,但真正需要掌握的只有model_list、master_key、database_url、虚拟 Key、成本上限这几个核心概念。把这些搞透,后续再遇到厂商版本更新、新模型接入、成本失控这些事,你都能从容应对。