ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

LiteLLM 实战:用统一接口终结多模型接入混乱,实现路由与成本管控

LiteLLM 实战:用统一接口终结多模型接入混乱,实现路由与成本管控

大模型应用做到一定规模后,最让人头疼的往往不是模型效果,而是接入流程本身:A 家今天发了新 SDK,B 家明天改了限流策略,C 家的返回结构跟别家完全不一样,业务代码里的 if-else 越堆越厚,每换一次供应商都要动一遍代码。LiteLLM 这个开源项目就是为了终结这种混乱而生的——它把主流大模型供应商的接口统一成一套兼容格式,并内置了负载均衡、故障回退、费用统计、预算管控等网关级能力。无论你是正在做 AI 应用的开发者,还是需要给团队搭统一模型入口的平台工程师,这篇文章都值得看完。

1. 这个项目是什么:多模型调用的“总闸门”

1.1 为什么应用会卡在多供应商泥潭里

先说一个我自己的真实场景。早先做一个智能客服系统,最开始只接了一家模型服务,代码写得很舒服。后来为了效果对比和降本,陆续接入了另外两家,灾难就开始了:

  • 每家供应商的 SDK 都不一样,有的直接调用随手写的 HTTP 封装,有的必须用官方 SDK;
  • 请求参数五花八门,系统提示词放在哪个字段、温度参数叫什么名字,各家有各自的习惯;
  • 返回结构差异更大,有的把结果放在 choices 里,有的直接放在 text 字段,有的还要先解析 tool_calls;
  • 限流和超时策略全靠自己写重试,写浅了不稳定,写深了又容易把下游打爆。

代码里慢慢就出现了一堆if provider == "A"这种分支,加一个新供应商就要在所有调用点补条件。到后来,团队里已经没人敢动那段调用逻辑,生怕改漏一个分支导致线上事故。

LiteLLM 解决的就是这个问题。它把几十上百家模型供应商的调用统一成一套接口格式,你的业务代码只需要面向这套统一格式编程,背后是哪家供应商、什么模型、走什么协议,都由 LiteLLM 帮你翻译和转发。用句话总结:它就是多模型世界的总闸门,或者叫模型路由器。

1.2 LiteLLM 的能力边界

LiteLLM 不是一个大而全的 AI 平台,它的核心能力可以分成四块:

  • 统一接口:提供和业界标准兼容格式一致的 SDK 和 HTTP 代理,所有供应商的对话补全、嵌入、图片生成等能力都通过同一套方法调用。
  • 智能路由:支持在多个模型、多个供应商账号之间做负载均衡,主模型挂了自动回退到备用模型,限流时自动重试,不用业务侧关心。
  • 成本与权限:能统计每次调用的 token 消耗和费用,给不同团队、不同成员发放虚拟密钥,按预算上限管控。
  • 可观测扩展:可以通过回调把调用日志、token 数、延迟数据推到自己的监控系统里,方便做费用归因和告警。

也要说清楚它做不到什么。LiteLLM 本身不训练模型、不托管模型权重,它只是一个调度层,所有能力都来自上游供应商。如果你的上游模型本身效果不好,或者某个供应商直接停服了,LiteLLM 能做的只是尽快切换到备选,并不能凭空变出模型。也不要指望它帮你优化 prompt,它保证的是“接口稳定、路由可靠、账单清楚”,prompt 工程是另一门功课。

2. 五分钟跑通:安装、密钥与首次调用

2.1 安装与版本选择

LiteLLM 是个 Python 库,安装非常简单,干净的环境里一条命令就够:

pip install --upgrade litellm

如果你后面要用代理模式给团队提供 HTTP 接口,建议直接装带 proxy 依赖的版本,省得后面补包:

pip install 'litellm[proxy]'

我建议所有项目都用虚拟环境管理依赖,别直接往系统 Python 里装。原因很简单:LiteLLM 依赖比较多,升级 SDK 或系统包时很容易把版本搞乱,而大版本升级时有些配置项会改名,有一个隔离环境方便你随时重建。

有一点提醒:这个项目迭代速度很快,几乎每周都有版本更新。不要盲目追最新,先读一下当前版本的发布说明,再决定要不要升。特别是你已经在生产环境跑了代理服务的情况下,升级前务必在测试环境先验证一遍 YAML 配置和路由行为。

2.2 用 Python SDK 完成第一次请求

装好后,第一件事就是配密钥。LiteLLM 约定,每家供应商的 API 密钥通过对应的环境变量读取。你不需要在代码里写密钥,只要在启动前设置好环境变量:

export PROVIDER_A_API_KEY=sk-你的密钥

然后写一段最简单的调用:

import litellm response = litellm.completion( model="provider-a/model-alpha-v1", messages=[ {"role": "user", "content": "用一句话介绍你自己"} ], ) print(response.choices[0].message.content)

看到没有?不管背后是哪家供应商,返回的对象结构都是统一的:choices→message→content,跟业界标准兼容格式完全一致。你不需要关心 A 家返回的是text还是generated_text,也不需要手动解析tool_calls,LiteLLM 全帮你处理好了。

这里关键的model参数格式是:供应商前缀/模型名。前缀由 LiteLLM 统一规定,模型名必须和上游供应商实际提供的模型标识保持一致。比如provider-a/model-alpha-v1就表示调用供应商 A 的某个具体版本。

2.3 模型前缀与别名:代码稳定的关键

很多第一次用 LiteLLM 的人会忽略一个设计上的好处:模型前缀把“供应商”和“模型名”两层信息组织在了一起,切换供应商时你只需要改配置,不必改业务代码。

更推荐的做法是给模型起别名。业务代码里从头到尾只写smart-model这种业务语义的名字,具体映射到哪家供应商的哪个模型,全部放在配置层。后面 A 家涨价了,你想换成 B 家的模型,只需要改一个配置项,代码一行不动。

代理模式下,你可以在 YAML 配置文件里定义model_name作为对外暴露的名字,内部通过litellm_params.model指向真实的上游模型。纯 SDK 模式下也有对应的别名机制,但实际开发中我建议大家直接用代理模式管理别名,后面第 4 节会详细讲。

除了一般的补全调用,嵌入向量的用法也差不多:

response = litellm.embedding( model="provider-a/embedding-model-x", input=["需要嵌入的文本"], )

这样一套接口风格,从对话到向量检索,全项目都能统一写。做 AI 应用的同学会发现,代码里再也不用维护 N 套供应商封装了。

3. 网关级能力:回退、重试与负载均衡

3.1 故障回退怎么配才可靠

按上一节的写法,每次调用都直连某一家供应商,这只能算“统一接口”,还没体现出网关的价值。真正让你省心的是路由策略,尤其是故障回退。

我用一个例子说清楚。假设主模型是供应商 A 的model-alpha-v1,备用模型是供应商 B 的model-beta-v2,希望当主模型报错或超时时,请求自动打到备用模型上:

from litellm import Router router = Router( model_list=[ { "model_name": "smart", "litellm_params": { "model": "provider-a/model-alpha-v1", "api_key": "os.environ/PROVIDER_A_API_KEY", }, }, { "model_name": "smart", "litellm_params": { "model": "provider-b/model-beta-v2", "api_key": "os.environ/PROVIDER_B_API_KEY", }, }, ], fallbacks=[ { "model_name": "smart", "fallbacks": ["provider-b/model-beta-v2"], } ], timeout=30, num_retries=2, ) response = router.completion( model="smart", messages=[{"role": "user", "content": "你好"}], )

这段代码里,Router是 LiteLLM 提供的核心路由对象。model_list里有多条记录,它们共享同一个对外名字smart;fallbacks则声明了回退顺序。当第一条记录对应模型调用失败,Router 会按配置自动尝试备用模型。

回退配置有几个细节需要记住。第一,回退目标是供应商前缀/模型名,不是model_name,别写反了。第二,触发回退的条件包括上游返回限流(429)、服务不可用(5xx)、超时和网络错误,LiteLLM 默认帮你判断,不需要逐个枚举。第三,不同版本对 fallbacks 在 YAML 里的写法有过调整,升级大版本后务必对照官方文档看看配置项是否改名。

3.2 负载均衡与重试的心得

负载均衡稍微进阶一点,但理解了模型列表的写法就很简单:同一个model_name对应多个上游部署,LiteLLM 会把请求分发到不同部署上。

典型场景有两个。一个是同一个模型在多家供应商都有,你想把流量按比例分开,降低单家限流风险;另一个是同一家供应商开通了多个账号,想提高整体并发。这两种情况在model_list里都会表现为多行配置:

router = Router( model_list=[ { "model_name": "smart", "litellm_params": {"model": "provider-a/model-alpha-v1"}, "model_info": {"rpm": 1000}, }, { "model_name": "smart", "litellm_params": {"model": "provider-a/model-alpha-v1"}, "model_info": {"rpm": 1000}, }, ], routing_strategy="simple-shuffle", )

routing_strategy决定分发策略。默认的simple-shuffle是随机打散,适合大多数场景;如果希望根据实时负载选择最空闲的部署,可以用基于使用量的策略。这里要注意,基于负载的策略依赖model_info里的rpm(每分钟请求数)和tpm(每分钟 token 数)配置,不填的话无法准确评估负载。

重试参数num_retries建议设为 2 到 3 次。设置太多会导致整体延迟明显上升,设置太少又容易在限流尖峰时直接失败。我一般把cooldown_time一起配了,比如设在 30 秒,某个部署连续失败后会被临时冷却,避免每次请求都往一个已经出问题的目标上撞。

3.3 流式输出场景下的取舍

聊天应用基本都离不开流式输出。LiteLLM 对流式支持得不错,写法也很直觉:

response = litellm.completion( model="smart", messages=messages, stream=True, ) for chunk in response: delta = chunk.choices[0].delta if delta and delta.content: print(delta.content, end="")

值得提醒的是,流式场景下做故障回退是有代价的。主模型已经吐了一部分内容才报错,这时候切到备用模型,备用模型会从头开始生成,客户端会看到内容“前面一段没了,又重新说一遍”,体验上会有明显割裂。

我的习惯是:非流式场景放心开 fallback;流式场景要评估用户能否接受这种割裂。如果接受不了,就在流式接口上单独配置不启用回退,或者只在请求刚发出去的前几秒内允许回退,这个策略目前不是默认行为,需要你自己额外控制。另外一个容易踩的坑是流式调用超时时间设置,很多供应商流式场景首次返回延迟较长,固定 30 秒超时往往不够,建议单独调大。

4. 代理模式:给团队开一个统一 API 入口

4.1 启动代理与基础配置

只在自己代码里用 SDK,那叫自给自足;真正把 LiteLLM 的价值放大,一定要用它的代理模式。代理就是一个运行中的 HTTP 服务,对外暴露一个和业界标准兼容格式一致的接口,团队里所有应用只要配一个base_url和api_key就能接入,完全不用关心背后接了几家供应商。

启动代理前,先准备一个 YAML 配置文件,比如config.yaml:

model_list: - model_name: smart litellm_params: model: provider-a/model-alpha-v1 api_key: os.environ/PROVIDER_A_API_KEY - model_name: smart litellm_params: model: provider-b/model-beta-v2 api_key: os.environ/PROVIDER_B_API_KEY

然后设置主密钥并启动:

export LITELLM_PROXY_MASTER_KEY=sk-123456 litellm --config config.yaml --port 4000

看到日志里出现 health check 通过之类信息后,代理就起来了。用 curl 验证一下:

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

团队里其他服务接入时,直接用任意标准 SDK 指向这个地址就行。以 Python 为例:

from openai import OpenAI client = OpenAI( api_key="sk-123456", base_url="http://localhost:4000/v1", ) resp = client.chat.completions.create( model="smart", messages=[{"role": "user", "content": "你好"}], )

你有没有发现,团队成员根本不需要安装 LiteLLM,也不需要知道供应商细节,他们只知道“公司有一个统一的模型网关”,这比每个项目自己维护一套 SDK 封装舒服太多了。

4.2 虚拟密钥与权限隔离

代理模式最大的福利之一,是虚拟密钥。你可以用主密钥生成一批带权限限制的虚拟密钥,发给不同团队或不同应用使用。

生成一个限定额度的密钥:

curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-123456" \ -H "Content-Type: application/json" \ -d '{"max_budget": 50, "models": ["smart"], "metadata": {"team": "客服组"}}'

返回结果里会有一个key字段,把它当成普通 API 密钥发给对应团队即可。这样做的价值不只是安全隔离,更重要的是成本归因。每个人、每个团队用的都是独立密钥,谁花了多少钱一目了然,出了异常调用也能直接定位到具体密钥。

还可以限制虚拟密钥能访问哪些模型。比如客服团队只能调smart,搜索团队只能调embed,不让一个密钥拥有全部模型权限,这在多团队共用网关时非常重要。

4.3 成本统计与预算控制

成本统计是 LiteLLM 吸引很多团队的关键功能。代理模式下,每次请求的 token 消耗和费用都会被记录下来,你可以通过内置接口查询:

  • /spend/logs:按请求维度查看费用日志;
  • /key/info:查看某个密钥的累计消费和剩余配额;
  • /global/spend:看整个网关的总花费。

预算控制更直接,生成密钥时设置max_budget,这只密钥的消费一旦超过预算,后续请求会被网关直接拒绝,返回 429。这个机制对团队管理特别有用,比如给外部客户开放的试用密钥,设置 10 美元预算,用完就停,完全不用人工盯。

成本统计还有一个前提要注意:只有通过代理发起的请求才会自动计费。如果你只是把 LiteLLM 当 Python 库在代码里直接用,没有走代理,默认不会记录费用,需要额外配置回调。所以我前面才反复强调,团队化使用一定要走代理。

5. 进阶配置与可观测性

5.1 一个可落地的完整 YAML 配置模板

这里给一份经过我实际验证的配置模板,可以直接抄走再改:

model_list: - model_name: chat-main litellm_params: model: provider-a/model-alpha-v1 api_key: os.environ/PROVIDER_A_API_KEY timeout: 30 model_info: mode: completion - model_name: chat-backup litellm_params: model: provider-b/model-beta-v2 api_key: os.environ/PROVIDER_B_API_KEY timeout: 60 model_info: mode: completion - model_name: embed-main litellm_params: model: provider-a/embedding-model-x api_key: os.environ/PROVIDER_A_API_KEY model_info: mode: embedding router_settings: routing_strategy: simple-shuffle fallbacks: - model_name: chat-main fallbacks: - provider-b/model-beta-v2 retry_policy: TimeoutError: 2 RateLimitError: 3 cooldown_time: 30 litellm_settings: drop_params: true set_verbose: false

几个关键点解释一下:

  • api_key: os.environ/变量名是 LiteLLM 推荐的安全写法,它会从环境变量读取值,密钥不落盘、不进 Git 仓库。绝对不要直接写成明文。
  • model_info.mode用来区分补全和嵌入模型,写错了会导致嵌入模型在补全接口下被调用而报错。
  • router_settings.fallbacks的层级关系是“fallbacks 列表里每一项对应一个需要保护的 model_name”,后面接的具体是供应商前缀/模型名。
  • litellm_settings.drop_params开启后,如果某个模型不支持某个参数(比如嵌入模型不支持 temperature),LiteLLM 会自动把参数丢掉而不是报错,这在多模型混用时非常实用。

5.2 接入自建模型服务

很多团队会内部部署开源模型,这类模型服务只要提供标准兼容接口,LiteLLM 也能统一纳管。做法是把模型前缀写成openai/,再指定api_base指向你自己的服务地址:

model_list: - model_name: local-llm litellm_params: model: openai/local-model api_base: http://127.0.0.1:8000/v1 api_key: any-value model_info: mode: completion

这种写法相当于告诉 LiteLLM:这个模型的协议是业界标准兼容格式,但访问地址是内网自建的,别往公网发。自建模型的优势和公网供应商完全不同,但路由、回退、计费这些能力可以完全复用同一套配置。比如公网供应商限流时,可以把请求回退到自建模型,成本低还稳定。

接入自建模型后建议设置偏大的timeout,因为本地 GPU 推理速度受硬件影响明显,高峰期可能比公网还慢,超时设太短会误判失败。

5.3 把调用日志接入已有监控

LiteLLM 提供了回调机制,可以把每次调用的元数据推给外部系统。我常用的做法是自定义一个回调,把请求延迟、模型名、token 数、费用推送到团队现有的监控平台或日志系统。

在配置里可以这样挂回调:

litellm_settings: success_callback: ["custom_callback"] failure_callback: ["custom_callback"]

然后在代码里注册对应的回调函数。回调里能拿到的信息很完整,包括请求模型、响应延迟、输入输出 token 数、费用等。有了这些数据,你就能做几件非常实际的事情:给不同业务线做费用分摊、监控模型延迟的 P95 走势、对下游频繁限流的模型自动告警。

我一直觉得,网关类组件必须和监控打通,否则就像在家里装了一个不带电表的配电箱,电是从这里过的,但用了多少、哪个房间用的,完全不知道。回调机制就是给网关装电表的过程。

6. 常见问题与排查实录

6.1 高频报错对照表

整理了一份我在实际使用中遇到的高频问题对照表,遇到报错可以先来这里找找答案:

现象可能原因解决方案
返回 401/403供应商密钥错误,或密钥环境变量没设置成功检查启动代理的进程里是否真的 export 了对应变量,打印环境变量做一次确认
返回 404 model not found模型名写错,或前缀不是 LiteLLM 认识的前缀确认供应商前缀/模型名的格式,先在官方模型列表里查一遍
频繁 429触发了供应商限流,或者虚拟密钥超预算配置 fallback 和重试,检查密钥的 max_budget 是否已用完
400 且提示上下文超长prompts 超过了模型上下文窗口对输入做截断或压缩,或切换到上下文更大的模型
代理启动失败端口被占用,或 YAML 里 model_list 为空换端口,确认 YAML 语法和模型配置完整
调用成功但 /spend/logs 没数据没走代理模式,费用统计没有自动启用检查请求是否打到代理地址;代理模式下检查数据库连接是否正常
流式输出中途断开供应商流式接口异常,或超时设置太短调大流式超时,确认网络环境稳定性

6.2 我踩过的几个坑

说实话,LiteLLM 文档写得很全,但真正上手时还是有几个坑不容易绕过,这里分享给各位避免重蹈覆辙。

第一个坑:密钥明文写进了 YAML。团队里有人直接把api_key: sk-xxxx写在配置文件里,然后整个仓库推到代码托管平台,半天后密钥就被人盗刷了。这不是危言耸听,如果密钥泄露引发大量调用,账单会非常难看。解决办法只有一个:配置文件里永远用os.environ/变量名的形式,密钥只出现在部署环境的变量里。

第二个坑:回退目标配了同一个供应商的同一个账号。有次我给两个部署配了回退,结果上线后发现限流时回退也没用,因为两个部署本质上用同一个套餐额度,一个限流另一个也限。回退的意义是“鸡蛋别放一个篮子里”,所以回退目标最好选不同供应商,至少也要不同账号、不同套餐。

第三个坑:升级版本没看配置变更。LiteLLM 大版本升级时,路由配置的写法改过不止一次,我就吃过一次亏:升级后 fallbacks 没生效,花了很久排查才发现是配置格式变了。建议在正式升级前,先在测试环境把原配置完整跑一遍,仔细核对启动日志里的报警。

第四个坑:环境变量设置时机不对。即使你在终端里 export 了变量,如果代理是通过 nohup 或进程管理器启动的,很可能根本没有继承当前终端的环境变量。我建议在启动脚本里显式加载环境变量,并加一道保护:启动时检查关键变量是否存在,不存在就立刻报错退出,省得启动成功但所有请求都 401。

第五个坑:对自建模型打开了 drop_params。drop_params虽然好用,但过度使用会掩盖配置错误。上次某个本地模型明明不支持某个能力参数,开了 drop_params 后参数被静默丢弃,导致生成结果完全不符合预期,排查了很久才发现是参数被丢了。建议调试阶段关掉 drop_params,上线前再按需开启。

按照我个人的经验,LiteLLM 最适合的定位是团队级模型网关,而不是个人项目里顺手调用的工具库。一旦把多供应商调度、密钥管理、预算控制集中到一个网关里,后面接新模型、切换供应商、给团队分配额度都会省心很多。最后再分享一个小技巧:接任何一个新模型之前,先写一条带 timeout 的最小测试脚本,把非流式、流式、嵌入三种模式都跑一遍,确认接口通畅,这才把它加进正式配置,后面排查问题的时间能省一半。

RELATED READING

延伸阅读

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