
如果你写过两套以上大模型接口的对接代码大概率体会过那种被各家 API 格式来回折腾的烦躁认证方式不一样、请求结构不一样、返回字段不一样、流式协议不一样连限流报错都各说各话。我上个月维护模拟项目X 时光适配这些差异就补了一周胶水代码。后来把 litellm 接到项目里三天内砍掉大半适配逻辑还把多模型切换从“改代码发版”变成了“改一行配置重启”。这篇就围绕 litellm 做一次完整拆解它到底怎么统一接口、怎么部署成团队可用的网关服务以及我在实际配置里踩过的几个坑。1. 先看清楚 litellm 到底在解决什么灾难1.1 各家模型接口的“非标准”才是常态做 AI 应用的人迟早会碰上一件事今天接厂商 A 的模型写一套messages [{...}]和temperature 0.7明天接厂商 B 的模型发现它的调用方式虽然长得像但参数名改成了别的返回内容里choices的位置也变了再过几天团队说要加厂商 C结果 C 的鉴权头要放在 query 里流式输出的 chunk 字段完全对不上。这不是某一家的问题而是整个行业的现实模型服务商为了各自生态往往都会推出“兼容主流格式”的接口但兼容得又不彻底总在细节上留一些差异。当项目里同时维护三条调用链路时代码会快速变脏。最典型的几个差异点差异维度常见情况认证方式请求头放 Key、query 里带 token、签名算法不同请求体格式messages 结构、system 指令位置、参数命名不一致返回结构choices 层级不同、用量字段位置不同、错误码语义不同流式协议SSE 字段名不同、结束标记不同、chunk 合并方式不同限流语义返回 429 的时机不同、重试头字段不同、配额单位不同这些差异单看都不难处理难在它们会组合出现。每接一个新厂商你就要写一套新的“翻译层”每换一个模型你还要担心之前的处理逻辑有没有隐含假设。时间一长代码里全是if provider_a ... else if provider_b ...这种分支谁看了都头疼。1.2 litellm 的思路把所有模型塞进同一个“标准结构”litellm 的核心想法其实很朴素与其让每个业务方都去适配各家接口不如做一个统一的“翻译官”。业务代码只跟 litellm 约定的标准结构打交道litellm 在拿到请求后根据model参数前面的厂商前缀自动把请求转换成对应厂商需要的格式再把厂商返回的内容统一转回标准结构。打个比方这就像你去不同国家旅游不需要学会每个国家的插座形状只需要带一个万能转换插头。litellm 就是那个转换插头你这边永远是同一套插孔另一端它自己适应各国插座。具体到代码层面最直观的体验是用一个函数通过换model字符串就能切换不同后端。例如import litellm messages [{role: user, content: 你好请介绍一下你自己}] # 厂商 A 的模型 resp_a litellm.completion( modelprovider-a/chat-model-01, messagesmessages ) # 厂商 B 的模型只是改了 model 字符串 resp_b litellm.completion( modelprovider-b/chat-model-02, messagesmessages ) print(resp_a.choices[0].message.content) print(resp_b.choices[0].message.content)这里的provider-a、provider-b是厂商前缀后面跟的是对应厂商的模型 ID。litellm 就是靠这个前缀决定走哪套翻译逻辑的。这套“统一格式 前缀路由”的设计是后面所有高级功能的基础一旦输入输出结构统一了你就可以在中间层做负载均衡、重试、密钥管理、费用统计而业务方完全无感。1.3 什么时候不该用 litellm讲完好处也得泼盆冷水。litellm 不是银弹有些场景用它反而增加复杂度。项目只固定用一个厂商的模型且短期内没有切换计划那直接调官方接口更轻量没必要多套一层。你需要使用某个厂商独有的高阶能力比如某些特殊的多模态输入、细粒度的采样参数而 litellm 的通用结构没有完全覆盖这时候强行套壳反而束手束脚。团队里没人愿意维护中间层出了问题没人排查那引入一个新的抽象层只会变成新的黑盒。我当时选择 litellm是因为模拟项目X 明确要求支持多家模型并可按配置切换且后面还要接给其他小组用。这种“多后端、多调用方”的场景正是 litellm 的主场。2. litellm 的两条使用路径轻量 SDK 和团队网关litellm 本身有两种用法很多第一次接触的人会混淆。一种是把 litellm 当普通的 Python SDK 装进项目里直接调用另一种是把它跑成一个独立的网关服务业务代码通过 HTTP 接口访问它。两种模式解决的问题完全不同。2.1 SDK 直调模式最轻量的统一封装如果只是自己写脚本、做实验或者项目规模不大直接pip install litellm然后import litellm litellm.drop_params True response litellm.acompletion( modelprovider-a/chat-model-01, messages[{role: user, content: 写一段 Python 快排}], temperature0.3, )这样做的优点是零部署、改动小切换模型只需要改model参数。异步版本acompletion也能在async代码里直接用不用额外引其他库。但直调模式的短板也明显每个业务进程里都要配置厂商密钥。假设你有三个服务就要在三份环境变量里都配好密钥如果某个密钥要轮换你得挨个服务去改。而且每个服务都在自己进程里做重试、做超时逻辑容易重复也不好统一治理。2.2 网关模式把鉴权、路由、预算下沉到服务端litellm 还有一套网关模式也就是把它跑成一个独立的服务。所有业务方不再直接碰厂商密钥而是拿着 litellm 生成的虚拟密钥来访问网关网关负责背后真正的厂商调用、重试、密钥轮换、预算控制甚至把请求日志和费用统计集中展示。网关模式最大的价值在于“治理”密钥不会散落在各个业务代码里模型接入由中间层统一维护调用方拿到的只是一个个别名。比如内部统一定义一个模型名叫unified-assistant它实际指向哪个厂商的哪个模型完全由网关配置决定。今天指向厂商 A明天想切到厂商 B业务方一行代码都不用改。2.3 两种模式怎么选我自己的判断标准很简单如果只有一两个服务、两三个开发者SDK 直调完全够用如果服务数量超过三个或者要把模型能力开放给多个小组、多个项目就直接上网关否则后面密钥管理和费用分摊会让人崩溃。对比维度SDK 直调模式网关模式部署复杂度极低装库即可中等需要跑一个服务密钥管理分散在各个业务环境变量中集中管理子密钥可隔离多后端路由需要自己写逻辑内置支持预算与用量统计基本没有有 Dashboard 和预算上限适合场景本地脚本、单一项目团队协作、多服务接入3. 部署一套 litellm 网关服务我采用的 Docker 方案3.1 为什么不用裸进程一开始我在模拟项目X 里试过直接python -m litellm起服务能跑但很快发现几个问题进程挂了没人拉起来升级版本要手动操作环境变量靠 export 维护换台机器就漏配。后来我改成 Docker Compose 部署整个网关变成一组声明式配置新同事拉下来就能起密钥通过环境变量文件注入不落在代码里。3.2 config.yaml网关的“总装图”litellm 网关通过一个 YAML 文件描述模型列表和全局行为。下面是我调整过的一份精简配置。model_list: - model_name: unified-assistant litellm_params: model: provider-a/chat-model-01 api_key: os.environ/PROVIDER_A_KEY - model_name: unified-assistant litellm_params: model: provider-b/chat-model-02 api_key: os.environ/PROVIDER_B_KEY - model_name: cheap-assistant litellm_params: model: provider-c/chat-model-03 api_key: os.environ/PROVIDER_C_KEY litellm_settings: drop_params: true set_verbose: false num_retries: 2 request_timeout: 30 general_settings: master_key: sk-master-key-placeholder database_url: postgresql://litellm:litellmpostgres:5432/litellm几个关键点拆开讲model_list里的model_name是对外暴露的模型别名litellm_params.model才是真正传给厂商的模型 ID。这里我把unified-assistant同时映射到两个厂商的后端网关就会自动在这两个后端之间做负载均衡。api_key可以写os.environ/XXXlitellm 会从进程环境变量里读取避免密钥硬编码进配置文件。litellm_settings.drop_params这个参数很关键后面会详细说num_retries控制网关侧自动重试次数。general_settings.master_key是管理密钥用来生成虚拟密钥、访问管理接口database_url建议生产环境配一个独立的 PostgreSQL不要把数据存在默认的 SQLite 里不然多实例跑不起来。3.3 Docker Compose 编排把配置文件和 PostgreSQL 一起编排services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: litellm POSTGRES_PASSWORD: litellm POSTGRES_DB: litellm volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U litellm] interval: 5s timeout: 3s retries: 10 litellm: image: ghcr.io/berriai/litellm:main-stable ports: - 4000:4000 volumes: - ./config.yaml:/app/config.yaml environment: LITELLM_MASTER_KEY: sk-master-key-placeholder DATABASE_URL: postgresql://litellm:litellmpostgres:5432/litellm PROVIDER_A_KEY: ${PROVIDER_A_KEY} PROVIDER_B_KEY: ${PROVIDER_B_KEY} PROVIDER_C_KEY: ${PROVIDER_C_KEY} depends_on: postgres: condition: service_healthy volumes: pg_data:这里面我故意把LITELLM_MASTER_KEY和DATABASE_URL也写成环境变量是因为新版 litellm 会优先读环境变量避免和 config.yaml 里的值冲突。${PROVIDER_A_KEY}这类值来自你本地的.env文件提交代码时千万别把.env提交进去。3.4 启动后的自检清单跑起来之后先做几个基础检查# 1. 健康检查 curl http://localhost:4000/health # 2. 看模型列表需要带 master key curl http://localhost:4000/v1/models \ -H Authorization: Bearer sk-master-key-placeholder # 3. 发一个最简单的对话请求 curl http://localhost:4000/v1/chat/completions \ -H Authorization: Bearer sk-master-key-placeholder \ -H Content-Type: application/json \ -d { model: unified-assistant, messages: [{role: user, content: 说你好}] }如果你能在第三步拿到正常补全结果说明网关的核心链路已经通了。我第一次部署时卡在第二步提示模型不可用排查了一圈发现是 config.yaml 挂在容器里的路径不对litellm 根本没读到模型列表。这个细节容易踩建议启动后用/v1/models先确认网关到底加载了哪些模型。4. 多模型路由落地权重分发、自动容灾与参数透传4.1 同一个别名多个后端 自带负载均衡网关部署完只是第一步真正体现价值的是路由策略。回到那份config.yaml我把unified-assistant同时映射到厂商 A 和厂商 B。此时请求打到/v1/chat/completions时网关会按策略把请求分到其中一个后端。大部分场景下默认路由策略够用但如果某个厂商经常不稳定或者你想让便宜模型多扛点流量可以显式配置权重router_settings: routing_strategy: simple-shuffle model_group_alias: unified-assistant: provider-a/chat-model-01: 0.7 provider-b/chat-model-02: 0.3这里的意思是厂商 A 的模型承接 70% 流量厂商 B 承接 30%。simple-shuffle是随机轮询适合后端能力差不多的场景。如果某个模型总是超时或报错你可以在配置里把它权重降下来或者直接临时移除业务方无感知。4.2 自动重试与 fallback 的坑litellm 网关内置了重试和故障转移能力。简单理解请求失败后网关先按num_retries重试当前后端如果重试还失败会把请求自动转发到model_list里同一个别名下的另一个后端。这相当于给调用方做了兜底。但这里有个坑对“失败”的定义要小心。默认情况下HTTP 5xx 和 429 限流会触发重试/转移而 4xx比如你传的参数本身有问题不会。这符合直觉但实际遇到过一种情况某个后端厂商对长输入的模型有单次 token 上限超了就返回 400。网关不会把 400 当故障转移到其他后端于是请求一直失败。这种问题靠网关配置解决不了得在业务侧拆输入或者在模型选择上选支持更长上下文的型号。另一个坑是重试放大。如果业务方自己也做了重试网关侧又配了 2 次重试一次请求最多就可能变成 3 倍甚至 6 倍调用量。我在模拟项目X 里就见过费用翻倍的场景。后来统一约定重试只在一侧做业务侧只做超时网关侧做重试和故障转移。4.3 drop_params 到底是干嘛的这是我最想提醒的一个参数。不同厂商对采样参数的支持范围不一样厂商 A 可能支持frequency_penalty厂商 B 可能不支持厂商 A 的max_tokens在厂商 B 那里叫max_completion_tokens。当业务方传了一堆通用参数而某个后端厂商不认其中某个参数时网关会收到 400 错误。drop_params: true的作用就是如果发现某个参数在当前后端不被支持直接丢弃而不是报错。这个开关平时建议开尤其是当你有多家后端时。但也要知道它的副作用被丢弃的参数会让请求静默地偏离你的预期。比如你想严格限制输出长度结果max_tokens因为命名不兼容被丢了可能就失控。所以更稳妥的做法是在litellm_params里为不同后端单独映射参数而不是全部依赖drop_params兜底。我现在习惯在 config.yaml 里对模型做细分比如同样一个内部别名在不同后端上使用各自支持的参数写法。这虽然让配置变长但可预期性高得多。5. 密钥、预算与用量观测把成本管起来5.1 虚拟密钥给每个团队一把“子钥匙”网关跑起来之后不能让所有人都拿 master key 来调用。litellm 的虚拟密钥机制就是干这个的master key 负责管理虚拟密钥负责对外使用。curl http://localhost:4000/key/generate \ -H Authorization: Bearer sk-master-key-placeholder \ -H Content-Type: application/json \ -d { duration: 30d, models: [unified-assistant], max_budget: 20 }这条命令生成一个有效期 30 天、只能调用unified-assistant、总预算 20 美元的虚拟密钥。你可以给 A 团队生成一把 key 限 20 美元给 B 团队生成另一把限 50 美元所有调用都通过各自的 key 隔离。实际用下来我觉得密钥的粒度要按“项目 环境”切。比如“模拟项目X-测试环境”一把 key“模拟项目X-生产环境”另一把 key。这样万一某个 key 泄露可以单独吊销而不影响其他环境查费用时也能按 key 直接看出来哪个环境烧了多少钱。5.2 预算上限与限额字段除了max_budgetlitellm 还支持按请求频率限流{ max_budget: 20, rpm_limit: 100, tpm_limit: 50000 }rpm_limit是每分钟请求数上限tpm_limit是每分钟 token 数上限。适合用来防止某个异常服务把预算瞬间打爆。预算超限后的行为也可以配置是直接拒绝请求还是降级到备用模型还是只记录不拦截。我建议在测试环境先开“只记录”模式观察几天确认流量模型正常后再在生产开“拒绝请求”免得误伤正常业务。5.3 用量观测从日志到 Dashboardlitellm 网关自带一个管理 Dashboard浏览器打开http://localhost:4000/ui用 master key 登录后能看到每一把虚拟密钥的调用次数、token 用量、估算费用、请求日志。这在团队协作里很实用不用再看各家厂商控制台的账单直接在网关层面就能按项目、按模型拆分成本。有一点要提醒费用估算依赖模型的价格表。litellm 内置了不少模型的价格但如果你用的是内部部署的模型或者新发布的型号价格表可能没及时更新导致 Dashboard 里显示的费用不准。我建议定期核对一下特别是在月度成本复盘时。5.4 敏感数据保护请求日志别乱开网关默认会记录请求和响应内容方便排查问题。但如果你们的业务会传用户隐私数据这条必须谨慎。litellm 提供了关闭消息日志的开关litellm_settings: turn_off_message_logging: true打开后Dashboard 里仍然能看到每次调用的时间、模型、token 开销但不会保留请求和返回的具体内容。对大多数内部工具类项目这样既满足审计需要又减少隐私风险。我自己在模拟项目X 里就开了这个排查问题时宁愿手动重现也不敢把用户对话内容存在数据库里。6. 把 litellm 网关嵌进现成代码无感切换与多环境配置6.1 用基础 HTTP 客户端直接验证litellm 网关对外暴露的是兼容主流格式的/v1/chat/completions接口。这意味着你根本不需要引任何特殊库直接拿熟悉的 HTTP 客户端就能调import requests resp requests.post( http://localhost:4000/v1/chat/completions, headers{Authorization: Bearer sk-virtual-key-placeholder}, json{ model: unified-assistant, messages: [{role: user, content: 写一个邮件模板}], temperature: 0.5, }, timeout30, ) data resp.json() print(data[choices][0][message][content])很多项目里已经在用各类厂商 SDK它们大多数支持自定义base_url和api_key。你只需要把base_url指向http://your-gateway:4000/v1把api_key换成虚拟密钥业务代码几乎不用动。这也是“无感切换”的核心对上层应用而言它始终在跟同一个接口说话至于背后是厂商 A 还是厂商 B应用根本不知道。6.2 与现有工具链的兼容性思路如果你的项目里还有一层编排框架比如自研的 agent 调度、模型路由层只要这些框架允许配置自定义接口地址和 key就能把 litellm 网关插在中间。常见的接入模式是编排框架 → litellm 网关 → 各家厂商后端。这样编排框架只认识一个“厂商”而 litellm 负责处理背后的一切变化。我踩过的一个小坑是超时设置。网关默认请求超时可能是 30 秒但有些长文本生成任务可能超过这个时间。业务侧把超时设成 60 秒网关侧却先在 30 秒处掐断了导致调用方收到一个不完整的响应。后来我按模型类型区分超时复杂推理模型用更长的超时时间普通对话模型保持默认避免资源空转。6.3 多环境配置管理的小经验本地直接跑docker compose up用默认配置测试环境用另一套.env生产环境再用一套更严格限制的配置。litellm 的好处是这套配置大部分是文件化的可以版本管理。我现在的习惯是config.yaml 只放模型列表和通用行为所有密钥全部走环境变量生产成本控制、最大预算这些严格限制都放在生产专用的环境变量里本地和测试环境只用最小预算防止有人拿测试 key 刷量。另外每次改 config.yaml 后建议先跑一次/v1/models确认加载无误再切流量避免带病上线。最后分享一个我自己总结的小技巧接到新模型时先不急着写进业务代码而是在网关里临时加一个模型别名用 curl 直接对着网关测几轮确认它的参数兼容性、响应速度、错误行为都符合预期后再正式对外开放。这样把适配风险拦在网关层而不是让业务方去踩坑。这套流程下来我后来接新模型基本只改配置文件业务代码几乎不动。