ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

litellm实战:统一LLM网关,一套API接入所有主流模型

litellm实战:统一LLM网关,一套API接入所有主流模型

litellm 这个名字,是我在某天凌晨盯着代码里第 300 行 if-else 时搜到的。当时项目里已经同时接了三个模型供应商,A 家的 temperature 要控制在 0 到 1,B 家的消息里 system prompt 要单独拆字段,C 家的错误码和限流策略又是一套全新的规则。光是把这些差异翻译成统一接口,就已经让我怀疑人生。litellm 解决的正是这个问题——它是一个开源的统一 LLM 网关,把各家主流模型服务商的调用方式,全部收拢成一套 OpenAI 兼容的 API。前端代码几乎不用改,只需要把请求地址指向 litellm 的服务端口。这篇文章我会从选型理由、环境配置、三种实际调用方式、踩坑复盘,到预算与限流,完整讲一遍我的真实使用经历。

1. 我为什么放弃自建适配层,all in 一个开源网关

1.1 多供应商接入期的“兼容地狱”

如果你业务里只用过一家模型供应商,大概率不会觉得 litellm 有用。但真实项目不是这样——一个功能往往要对比不同模型的效果,或者做多供应商容灾。一旦接入第二个、第三个 provider,所有你以为“理所应当”的接口标准都会被打破。

  • 鉴权方式不同:有的要求 Authorization Bearer,有的要自定义 header,还有的走 query 参数。
  • 消息结构不同:熟悉的 provider 习惯用 system 角色承载提示词,另一家则要求把 system 消息作为首个 user 消息的一部分,转写逻辑要自己写。
  • 参数范围不同:同一个 temperature 概念,有的允许 0 到 1,有的允许 0 到 2,甚至参数名都不一样。
  • 返回结构不同:文本在choices[0].message.content,另一家可能在output.text,错误信息格式更是五花八门。

这些差异单拆出来都不算难,难在它们会散落在整个业务代码里。每接一家新模型,就得顺着代码把之前写过的适配代码翻一遍。我记得当时项目里那个兼容层越写越厚,最后到了近 300 行,而它真正做的事不过是“把 A 的请求翻译成 B 的格式”。新来的同事看着这些代码一头雾水,改一个字段生怕碰坏另一边。

1.2 自研适配层和开源网关的对比

我在选型时确实考虑过自研——自己维护一个工具类,把所有 provider 封装起来。看起来可控性更强,但把利弊列出来之后就放弃了。

维度自研适配层litellm 这类开源网关
新模型接入成本读文档、写转换、联调排错,按天算社区已适配 100+ provider,通常加配置即可
重试/限流/预算全部自己实现内置能力,配置项即可控制
出问题时的排查自己代码最清楚,但边界情况都得自己扛有社区踩坑记录,但要多查资料
额外风险越写越复杂,容易变成团队隐藏负担引入一个中间层,多一个网络依赖点

我的结论是:对于只有一两个模型、想快速完成的小项目,自研一个几十行的适配层完全没问题。但当接口数量上来、团队里多人并行开发时,一个稳定且有社区支撑的网关明显更划算。litellm 并不会让请求变得更快,它能让你把时间花在业务上,而不是各家的格式差异上。

1.3 网关层到底替你做了什么

理解 litellm 最关键的一点是:它本身不提供模型,它只做转发和管理。你可以把它想象成一个前台接待处——你不需要分别打电话给各个部门,只需要到前台说一声“我要找 gpt-4o”或者“我要找 claude”,前台用相应的内部话术帮你传达,再把各部门的回复整理成统一的格式交给你。

这个“前台”替你承担了四件事:请求格式转换、路由与重试、额度与限流控制、调用记录与成本统计。对你或者你的业务代码来说,面对的永远是一个 OpenAI 风格的接口,剩下的事情全部收敛在代理层。

2. 启动前的两个决定:选 SDK 还是 Proxy,配置文件怎么组织

2.1 安装和两种运行形态

litellm 的安装很简单,一行命令就能装好核心依赖:

pip install 'litellm[proxy]'

这里有个容易忽略的细节:如果你只打算在 Python 代码里直接调用,装 litellm 就够;但如果你想把网关暴露给团队或者做成一个服务,一定要装带[proxy]的版本,否则后面启动代理服务时会找不到相关组件。Python 版本建议 3.8 以上,实测 3.10 和 3.11 都很稳。

litellm 有两种使用形态:

  • SDK 模式:在 Python 代码里import litellm,直接调用litellm.completion(),适合脚本、测试、个人工具。
  • Proxy 模式:通过配置文件启动一个本地 HTTP 服务,对外暴露 OpenAI 兼容 API,适合团队共享、前端调用、跨语言接入。

我个人建议:只要你不是一个人单机写脚本,直接上 Proxy 模式。它多花的精力只有写一个 YAML 配置文件,换来的是团队成员不需要关心 litellm 怎么安装。

2.2 config.yaml 的核心结构:model_list 的映射逻辑

Proxy 模式最关键的文件是 config.yaml,核心就是model_list。这个列表的每一项都包含两个重要字段:model_name是外部调用时用的别名,litellm_params才是真正调用下游模型的参数。

model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: my-claude litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-qwen litellm_params: model: ollama/qwen2.5:14b api_base: http://localhost:11434

这个配置文件传达了三个信息:第一,model_name是暴露给调用方的名字,完全可以自定义,不一定要和真实模型名一致;第二,litellm_params.model的标准写法是provider/模型标识,比如openai/gpt-4o、anthropic/claude-3-5-sonnet-20241022;第三,如果不带前面的 provider 前缀,litellm 会默认当作 OpenAI 的模型来处理。

很多人在这一步想当然,直接在 model_name 里写了个自定义名字,但调用时又传了真实模型标识,两边对不上,结果 404。这个坑我在后面排障章节会详细展开。

2.3 密钥的存放方式

先说我现在的结论:不要把 api_key 明文写进 YAML。litellm 支持os.environ/环境变量名这种写法,配置里只放引用,实际密钥放在环境变量里,这样配置文件可以提交到仓库,不担心泄露。

Proxy 模式还多了一个 master key 的概念。启动服务时用--master_key指定一个总控密钥,外部调用方通过这个 master key(或者基于它生成的虚拟 key)来访问网关。真实的下游 provider key 只存在于服务端环境变量里,永远不会出现在前端或业务代码里。

这个设计对安全很重要,后面我会在团队配置部分再展开。

2.4 启动和验证

配置文件写好后,启动命令:

litellm --config config.yaml --port 4000 --master_key sk-your-master-key --detailed_debug

首次启动建议加上--detailed_debug,方便看日志。服务起来后,先用这个命令确认网关正常暴露了哪些模型:

curl http://localhost:4000/v1/models -H "Authorization: Bearer sk-your-master-key"

正常的话会返回一个模型列表,里面就是你 config.yaml 里配置的各个 model_name。

3. 三种消费姿势:从调用脚本到跨语言业务代码

3.1 Python SDK 直连:最快看到效果的方式

当你不想起服务,只想在脚本里快速调一下 litellm 时,SDK 模式是最直接的:

import litellm litellm.set_verbose = True resp = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] ) print(resp.choices[0].message.content)

注意 SDK 模式下,model 参数直接写的是带 provider 前缀的真实模型标识,因为它没有 config 帮你做映射。set_verbose = True会在控制台打印请求的详细日志,排查问题时很有用。

3.2 通过 OpenAI SDK 指向 Proxy:生产环境最推荐的方式

我最推荐的是把 litellm 当成一个服务,然后业务代码用官方的 OpenAI SDK 去连它。代码里唯一的区别就是 base_url 和 api_key:

from openai import OpenAI client = OpenAI( api_key="sk-your-master-key", base_url="http://localhost:4000", ) resp = client.chat.completions.create( model="my-claude", messages=[{"role": "user", "content": "你好"}] ) print(resp.choices[0].message.content)

这个方式的好处在于:业务代码的依赖非常干净,只需要一套 OpenAI SDK,所有下游模型的差异都被网关藏起来了。业务团队不需要了解 provider 的鉴权、格式,只需要知道网关里注册了哪些 model_name 可以用。

3.3 curl、Node.js 以及任何 OpenAI 兼容客户端

因为 litellm 的代理接口是 OpenAI 兼容格式,所以任何能调 OpenAI API 的工具都能直接用。比如用 curl 测试:

curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "你好"}]}'

Node.js 里只需要把 openai 包的 baseURL 指向 litellm:

import OpenAI from "openai"; const client = new OpenAI({ apiKey: "sk-your-master-key", baseURL: "http://localhost:4000", });

这种跨语言一致性是选型时很重要的考量——团队里可能有人用 Python、有人写 Node、有人只是测试时用 curl,但面对的接口完全一样,排查问题的时候沟通成本会低很多。

3.4 为什么“OpenAI 兼容”是一个设计红利

回看我接入过的各家 SDK,OpenAI 的生态覆盖是最广的:各类 Agent 框架、开源应用、低代码平台,基本默认支持 OpenAI 格式。litellm 选择兼容 OpenAI 而不是自己发明一套格式,意味着这些生态里的工具可以零成本接入你的网关。

这个设计红利带来了一个直接的开发流程变化:新模型商的接入从“改业务代码 + 联调”变成了“配置加两行 + 验证一下”。我们后来接一个新 provider,整个改动通常在半小时内完成,其中一半时间还是花在审核配置和测试。

4. 排障复盘:模型名映射、流式输出、超时重试的三个实测教训

4.1 模型名映射不一致导致的 404

如果说一个坑必须排在最前面提,那就是模型名映射。现象是调用端报model not found或者直接 404。我第一次遇到时,第一反应是服务没起来,后来排查了一圈才发现问题出在模型名的对应关系上。

完整的排查链路:

  1. 先打/v1/models接口,确认网关当前暴露了哪些 model_name。
  2. 比对调用时传入的 model 字段和接口返回的列表,看是否一致。
  3. 看 config.yaml 里 model_list 的配置,确认 litellm_params.model 的 provider 前缀是否写对。
  4. 看 litellm 启动日志,日志里会显示请求实际路由到的下游 provider 和模型标识。

踩得最多的坑是:调用方在业务代码里习惯性地写了真实模型标识(比如claude-3-5-sonnet-20241022),而 config 里的 model_name 是自定义别名(比如my-claude)。两边没对上,自然路由不到。另一个坑是 provider 前缀漏写,比如直接写gpt-4o,litellm 默认认为你要调 OpenAI,但你本意是调另一个平台的同款模型。

经验:给团队维护一个“可调用模型清单”,统一发布在项目文档里。命名按用途走(比如 chat-smart、chat-fast),别按具体供应商的模型名走,这样以后换底层模型,对外别名不用变,业务代码也不用动。

4.2 流式输出不是加上 stream=True 就完事

业务里一旦需要打字机效果,就要用到流式输出。litellm 支持把下游模型的 stream 透传给调用方,但在实际接入时我发现有两个容易被忽视的问题。

第一,日志和计费统计会失真。当请求以流式返回时,litellm 对 token 的统计往往要等流结束才知道最终数量,如果你在代理层又做了消息聚合,成本统计的准确性会受到一些影响。第二,客户端断连问题比较麻烦。我遇到过前端页面被用户关闭后,上游模型还在继续生成,因为服务端没收到取消信号,连接一直挂着,白白消耗时间和额度。

我的处理建议是:如果业务端不需要实时打字机效果,尽量用非流式,逻辑简单、统计准确;如果确实需要流式,后端要做好超时控制,并在合适的时候主动取消,避免连接悬挂。

4.3 超时与重试:默认值只是起点

下游模型偶尔出现限流或者服务端错误是常态,litellm 自带重试机制,但没有哪套默认参数能适配所有业务。我的实践是这些:

  • 在配置里设置请求超时,避免个别慢模型拖垮整体响应。
  • 同一个 model_name 可以配置多个 provider 来源,当第一个失败时自动尝试下一个,这种 fallback 比单纯重试更实用。
  • 重试次数控制在 2 次以内。限流重试尤其要谨慎,因为上游可能已经计费,重试一次成本就翻倍。

举个实际例子,把一个重要接口的 fallback 从 A 模型切到 B 模型时,当时没有提前压测,结果 A 模型稳定但慢,B 模型快但偶发 5xx,fallback 反而让长尾错误变多了。后来我把 fallback 的目标模型固定为经过压测的备选,并将重试策略按 provider 分别设置,问题才解决。

5. 团队化使用后,预算、限流和密钥隔离成了主要战场

5.1 用虚拟密钥做预算隔离

litellm 的 Proxy 模式可以生成虚拟 key,并给 key 绑定模型范围和预算额度。这在实际团队里非常有用:CI 流程、测试环境、生产环境各用一把 key,CI 的预算设得低一些,防止某个测试脚本死循环把账单打爆;生产环境额度高,但依然有一个上限。

生成虚拟 key 的方式是通过/key/generate接口,或者管理界面操作。配置好之后,当某把 key 的消耗超过预算,litellm 会自动返回限流错误,而业务代码无需改动,这就是网关层的“安全阀”。

5.2 rpm/tpm 限流:按模型维度控制并发

除了预算,限流也是必须设置的。litellm 支持按模型、按 key 设置每分钟请求数和每分钟 token 数限制。这个功能的关键场景是保护一些成本较高或并发能力有限的模型,避免一个批量任务把全部额度吃光,其他业务的实时请求只能排队等待。

我自己一般会针对便宜模型和贵模型设置不同策略:便宜模型限流宽松,贵模型限流严格;低频的辅助任务用低优先级的 key,主流程用高优先级的 key。

5.3 网关安全:别把真实密钥暴露给调用方

团队化使用最容易出安全事故的地方,是把真实 provider key 下发给前端或业务服务。litellm 的密钥体系帮我们规避了这个问题:真实下游 key 只存在于网关服务端,外部调用方只持有 master key 或虚拟 key。即便虚拟 key 泄露,也可以在管理接口里秒级吊销,不影响其他调用方。

如果网关部署在公网,我强烈建议前置一层反向代理,加 IP 白名单或内部统一认证。litellm 本身提供了鉴权,但结合组织既有的认证体系会更稳妥。

5.4 成本统计和日志归档

litellm 对每次调用都有成本估算,这个数据如果不主动留档就浪费了。我们在网关层接入了日志管道,按 key 维度把每次调用的模型、token 数、估算成本记录下来,月底汇总后可以用来做各团队的成本分摊。这个功能对于多团队共享一个网关的场景特别实用,能省下大量对账时间。

最后说一点个人体会:网关不是配置越多越好。我见过有人把 model_list 配了几十个条目,各种 fallback、重试策略叠了一层又一层,最后出了问题自己都理不清。litellm 最大的价值是让模型接入收敛和可控,所以配置一定要保持精简——能用规则解决的不要堆特例。我在实践中的原则是:先跑通一个最小的模型链路,再加预算和限流,最后按需扩展 fallback。保持配置清晰,比追求功能齐全重要得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进