ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

多模型接入太难了?用 litellm 统一 OpenAI 兼容格式,打造 LLM 网关

多模型接入太难了?用 litellm 统一 OpenAI 兼容格式,打造 LLM 网关

最近在处理一个多模型接入的需求时,我最大的感受是:真正让人崩溃的其实不是模型效果,而是调用方式五花八门。同样是聊天补全,有的需要messages数组,有的偏要prompt字符串;同样是工具调用,有的叫tools,有的叫functions;流式输出的事件结构更是各写各的。如果项目里要同时接几家模型,代码会迅速变成一堆 if-else 和适配层,维护成本直线上升。

这篇文章想聊的litellm,就是专门解决这个问题的开源工具。它的核心思路很朴素:把各种大模型 API 统一翻译成 OpenAI 风格的请求和响应,上层业务只写一套代码,底层模型随便换。简单说,它是一层“LLM 适配网关”。这篇文章不打算逐条翻译文档,而是结合我实际接入、部署、踩坑的经历,把litellm从 SDK 到团队网关的完整链路讲透,给正打算接入多模型、或者团队需要统一管理模型成本的同学一些参考。

1. 先搞清楚它到底解决什么问题

1.1 多模型接入时的真实痛感

我接过的第二家模型厂商时,对接代码就开始不干净了。每家 SDK 的对象结构、鉴权方式、错误码、重试策略都不一样,这些差异逼着你写大量胶水代码:把请求体转来转去,把返回值翻来翻去,还要对付各家流式格式的细微差别。这种适配代码通常是项目里最难维护、也最没人愿意碰的部分。

另一个痛点是业务方对“模型不可用”是零容忍的。比如某个模型服务方限流、故障或升级,如果没有备选方案,业务只能干等。而一旦想加一个备选供应商,又意味着再加一套适配逻辑。

1.2 litellm 的“翻译层”设计

litellm的思路是,上层统一接收 OpenAI 格式的请求,底层由它去做协议转换。你可以把它理解成一个“万能插座”:前端只认识一种接口,后端由它去接各家不同型号的“插头”。

它内部维护了大量模型提供商的转换逻辑,不仅处理最基础的文本补全,还覆盖 embeddings、image generation、audio transcription 等各类接口。调用时只需要传模型名,例如gpt-4o-mini、claude-3-5-sonnet-20241010、gemini/gemini-1.5-flash、ollama/llama3.1这些带前缀或不带前缀的名称,它就知道该走哪条转换链路、用哪套认证方式。

1.3 SDK 模式和代理模式的区别

litellm有两种用法,这个要一开始就分清楚,因为很多踩坑都源于用错了模式。

  • SDK 模式:在代码里import litellm,直接调用litellm.completion()。适合单个项目、个人脚本、快速原型。
  • 代理模式:把litellm起成一个独立服务,前端接 OpenAI 兼容接口,由这个服务统一转发到真实模型。适合团队共用、生产环境、成本管控、Key 管理。

两种模式共用同一套配置逻辑和模型命名规则,但部署形态完全不同。下文会先讲 SDK 模式,因为它是理解代理模式的基础。

2. 用 SDK 模式跑通第一行调用

2.1 安装和最小示例

安装很简单,pip install litellm即可。它的核心调用长这样:

import litellm response = litellm.completion( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个简洁的助手"}, {"role": "user", "content": "用一句话解释什么是API网关"} ] ) print(response.choices[0].message.content)

这段代码和 OpenAI SDK 长得几乎一样,因为litellm本来就是兼容 OpenAI 的请求结构。但关键在于,如果你把model换成anthropic/claude-3-5-sonnet-20241020,代码其余部分完全不用动,它就能把 OpenAI 格式的请求转成 Anthropic 系模型能认的格式,再把返回结果统一成 OpenAI 风格。

2.2 环境变量与模型命名规则

不同厂商的密钥通过环境变量传入,例如OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY。litellm读取模型名时,自带一套判断规则。

传入的模型名实际走向说明
gpt-4oOpenAI 系不带前缀,直接映射到对应厂商
anthropic/claude-3-5-sonnet-20241020Anthropic 系带anthropic/前缀
gemini/gemini-1.5-proGoogle 系带gemini/前缀
ollama/llama3.1本地 Ollama本地模型,不需要云厂商 Key
openai/自定义接口地址任意 OpenAI 兼容服务配合api_base可以指向本地部署

这个前缀规则非常关键。模型名不只是“名字”,同时包含了路由信息。我见过不少人第一次用的时候,把model="claude-3-5-sonnet"直接传给litellm,结果它尝试走某条默认通道,报了奇怪的鉴权错误,其实加个anthropic/前缀就通了。

2.3 响应对象的统一结构

litellm返回的是ModelResponse对象,字段尽量向 OpenAI 对齐。正文放在response.choices[0].message.content,token 用量在response.usage里,完成原因在response.choices[0].finish_reason。

有个小坑值得注意:有些模型会返回推理链路(reasoning 内容),litellm在转换时不一定把这类字段映射到标准content,而是放在response.choices[0].message的扩展属性里。如果你需要把思考过程单独展示给用户,最好在拿到返回后用model_dump()看一眼实际数据结构,不要假设所有字段名都跟 OpenAI 文档一模一样。

2.4 连续对话与流式输出

连续对话就是把历史消息整个带上,messages数组顺序传递即可。流式输出则需要把stream=True传进去:

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

细心的读者会发现,这个流式循环体的写法和 OpenAI SDK 一样。litellm会把不同厂商的流式事件统一成这个结构。至于各家流式事件原本长什么样,你不需要关心,知道在哪取增量文本就够了。

3. 把单机调用升级成团队网关

3.1 为什么团队需要一层网关

SDK 模式虽然方便,但它解决不了团队层面的问题:成员各自拿云厂商 Key 直连,账单分散、权限不可控、模型供应商故障时没有统一降级策略、每个成员对“哪个模型能跑”的理解也不一致。

网关模式的核心价值是“收敛入口”。所有请求都打到一个代理服务上,这个服务负责鉴权、转发、限流、重试、记录日志。业务方完全不用关心背后有几个模型供应商,他们只跟网关打交道,网关背后用什么模型只由平台方统一配置。

3.2 config.yaml 的基础骨架

代理模式的配置核心是一个 YAML 文件。下面是精简但能跑通的基础示例:

model_list: - model_name: gpt-4o-mini litellm_params: model: gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241020 api_key: os.environ/ANTHROPIC_API_KEY litellm_settings: drop_params: true set_verbose: false general_settings: master_key: sk-your-master-key database_url: os.environ/DATABASE_URL

model_list里每一项的model_name是暴露给调用方的名字,litellm_params.model是真实模型名。这给了平台方很大的自由度:对外叫claude-sonnet,内部实际路由到哪个具体版本,可以随时切换而不告诉业务方。

master_key是网关的总控密钥,所有请求都要带上它才能通过。database_url是可选配置,如果要用成本统计、虚拟密钥、预算管理等功能,需要配置一个 PostgreSQL 数据库。

3.3 用 Docker 或 Python 起服务

最简单的启动方式是用官方镜像:

docker run -d --name litellm-proxy \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e OPENAI_API_KEY=your-key \ -e ANTHROPIC_API_KEY=your-key \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml --port 4000

不用 Docker 的话,直接 Python 启动也可以:

python -m litellm --config config.yaml --port 4000

启动成功后,验证一下网关是否正常工作:

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

能看到配置的模型列表,说明网关已经正常工作了。然后就可以用标准的 OpenAI 客户端指定base_url=http://localhost:4000来发请求。

3.4 虚拟密钥与权限隔离

网关模式下,不建议直接把master_key发给所有人。litellm提供虚拟密钥机制:管理员通过管理 API 或 UI 生成一把带权限控制的子 Key,这把 Key 只能调用指定模型,还能设定预算上限。

生产环境常见做法是:每个团队或每条业务线一把虚拟 Key,模型权限只开放他们需要的那些,预算各自设上限。这样既避免账号共享,也方便从账单上看出哪条业务线花得多。

4. 模型路由、重试与 fallback 的实战配置

4.1 同一模型多实例自动路由

真实运营时,同一个模型可能会配置多个可用端点,比如同一家云厂商有两个区域,或者两家厂商都提供同规格模型。可以在model_list里多配几个条目,并设置权重:

model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY api_base: https://api1.example.com/v1 routing_strategy: weight: 2 - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY_2 api_base: https://api2.example.com/v1 routing_strategy: weight: 1

这里weight表示流量占比。权重配比只是最简单的一种,litellm还支持根据延迟、健康检查结果做动态路由。当某个端点连续报错或超时,代理会自动降低它的流量,故障实例权重降为 0 后就不再往它发新请求。

4.2 fallback:主模型挂了的兜底策略

fallback 是生产环境里最有价值的功能之一。它决定了主模型报错后,请求是否自动转给备选模型:

litellm_settings: fallbacks: [ { "gpt-4o-mini": ["claude-sonnet"] } ] num_retries: 3 request_timeout: 60

上面配置的意思是:当gpt-4o-mini请求失败(包括超时、限流、5xx 错误),自动把同样的消息转给claude-sonnet再试一次。num_retries是重试次数,request_timeout是单次请求的最大等待时间。

这个机制对业务方是透明的,他们只知道自己请求了网关,并不知道背后发生过一次故障切换。要注意的是,fallback 不会自动把“失败原因”通知给业务方,所以在比较关键的业务场景里,我建议在网关日志里做一次失败事件标记,方便事后复盘主模型当时的故障表现。

4.3 本地模型的接入方式

私有化部署、离线环境、数据不出内网等需求,都会用到本地模型。litellm对本地模型的支持很直接,只要本地模型服务对外提供 OpenAI 兼容接口:

model_list: - model_name: local-llama litellm_params: model: openai/llama3.1 api_base: http://localhost:8000/v1 api_key: dummy

这里的openai/前缀表示“走 OpenAI 兼容协议”,并不是说接的一定是某云厂商。api_base指向本地服务的地址即可,api_key随便填占位值。如果用的是 Ollama,也可以写model: ollama/llama3.1,由litellm直接走内置的 Ollama 转换逻辑。

我自己的体会是,本地模型接入最大的价值不是替代云厂商,而是作为云厂商故障时的兜底。即使效果差一些,业务也不会完全停摆。这个思路在稳定性要求高的场景里非常实用。

5. 成本统计与预算控制

5.1 每次请求的 token 是怎么算成钱的

litellm会在每次请求结束后记录prompt_tokens、completion_tokens、total_tokens,再根据模型名查出对应的单价,算出本次花费。前提是它知道这个模型的价格。

常用云厂商模型的单价库里基本都有。如果你接的是私有模型、内部自建模型,价格表里没有,就需要在model_list的litellm_params里手动补充:

model_list: - model_name: internal-model litellm_params: model: openai/custom-model api_base: http://localhost:8000/v1 api_key: dummy model_info: input_cost: 0.000002 output_cost: 0.000006 max_tokens: 8192

这里的单价单位是“美元/千 token”,按官方约定填就行。没配model_info的模型,费用统计会显示为 0,这在事后对账时会很迷惑。接到内部模型时,我总是第一步就把价格配上,省得后面补数据。

5.2 按用户、按 Key、按业务线看账单

网关模式下,费用天然跟虚拟 Key 绑定。谁花的钱、哪个 Key 花的钱,数据库里都有明细。如果一条业务线共用一把 Key,账单就按 Key 聚合;如果希望更细粒度,可以在请求参数里带上user字段,网关会把这个用户维度记录下来。

litellm的 UI 或管理 API 提供 spend 相关接口,最常见的用法是查“某把 Key 本月花了多少”:

curl http://localhost:4000/spend/logs \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json"

日志会返回每条请求的模型、token、花费、用户、时间等信息。把这些数据接进自己的监控系统,就能做一个面向老板的用量大盘。

5.3 预算额度与超限熔断

预算管控是团队引入网关最直接的经济理由。给虚拟 Key 设置月度上限后,超过限额的请求会被直接拒绝,返回 429。配置方式在生成虚拟 Key 时指定max_budget即可。

我给某个测试团队配置过一把月限额 50 美元的 Key。当它突然被业务方写了个死循环脚本狂刷时,预算熔断直接拦住了几万美元的潜在账单。这个机制听起来简单,实际救过我不止一次。

6. 接入过程中的几个常见坑

6.1 工具调用的字段兼容

各家工具调用格式差异很大,litellm会做转换,但不是所有新特性都同步支持。部分新款模型支持的strict模式、结构化输出等较新参数,在转换到不支持这些参数的老模型时,如果配置了drop_params,它会把参数默默丢弃;如果没有,请求会报参数错误。

我的建议是:关键请求一定要记录转换后的真实请求体。litellm支持在环境变量里开 debug 日志,确认工具调用参数有没有被正确转换。不要在没看日志的情况下就认为“工具调用一定没问题”。

6.2 流式输出的超时与断连

流式输出的连接如果长时间没有返回新 token,容易被判定为超时。很多模型思考时间本来就长,尤其是一些强推理模型,可能几十秒内一个 token 都不吐。这时候不要把timeout设得太小,否则会频繁触发重试,重试又进一步增加服务端压力。

更稳妥的做法是:把request_timeout调到 90 秒以上,同时依赖应用层的空闲超时保护,而不是依赖网关层短超时。短超时“保护”的结果往往是全链路雪崩,这个坑是我在生产环境踩过最痛的一次。

6.3 输出长度上限不一致

各家max_tokens上限差异巨大。有些模型单次输出上限是 4096,有些是 8192,有些能到 32k 甚至更大。如果你在请求里写死max_tokens=16000,但路由到了只支持 4096 的模型,请求会直接失败,而不是被悄悄截断。

我处理这类问题一般是在网关层做一次校验,不同模型名设置对应的上限档位,超出上限的请求直接提示参数错误,不让错误请求浪费转发链路。如果只是偶尔使用长输出场景,最简单的做法是统一把max_tokens设置成所有模型都支持的安全值,虽然损失部分模型能力,但换来了稳定性。

6.4 不要把网关内部细节暴露给业务方

我见过一个团队在业务代码里读取response.model字段来“判断当前用的是哪个模型”,这个字段在网关模式下返回的是网关内部真实模型名。一旦平台方做了 fallback 或路由切换,业务方会发现“明明调的是 A,返回里却写着 B”,引发误判。

正确的做法是,把网关的模型输出当成不透明黑盒。业务方只依赖内容结果和 token 用量,不要依赖返回里的模型名去做逻辑判断。这个经验说出来简单,但在真实项目中反复被违反。

7. 什么场景不建议用 litellm

任何工具都有边界,litellm也不是万能的。如果只是单个项目、固定一个模型、短期跑通 Demo,引入一层网关纯属增加部署和运维成本,直接用官方 SDK 更轻快。如果团队已经有完善的内部统一 SDK,所有模型都经过自家适配层,也没有共享网关、成本归集这类诉求,那没必要再从零迁到litellm。

对延迟极度敏感的场景也需要慎重。代理模式每多一跳,就多一次网络开销,虽然litellm本身转发效率不差,但相比直连模型厂商,总延迟肯定会略有增加。严格来说,网关适合“管理价值大于微秒级性能差异”的业务场景,而不是所有场景。

此外,如果某些云厂商刚发布的新模型特有的参数,litellm转换层可能还没有及时跟进。对于追求第一时间使用最新特性的团队,可能需要自己评估适配的滞后成本。这时候我更倾向于把它当成一个“大部分模型都能管,但极少数新特性需要等一等”的工具,而不是一个永远零滞后的万能层。

我自己现在的使用习惯是:个人实验用 SDK 模式,团队项目用代理模式,所有新的模型调研先跑 SDK,确认可用后再把它写进网关配置。这两者的组合让模型接入的边际成本变得很低,新模型只要文档支持,基本半天内就能让整个团队用上。如果你正被多模型适配和数据碎片化折磨,这层“翻译器”值得花一个下午试试。

RELATED READING

延伸阅读

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