如果你手上同时要接好几家大模型的 API估计你早就被“每家一套请求格式、鉴权方式、限流策略”磨到没脾气。litellm就是干这个的一个 Python 库也是一个可以独立部署的代理网关把各家大模型接口统一成一套调用方式屏蔽底层差异。它最常用的做法是提供一个OpenAI 兼容的 HTTP 接口你后端只管按同一套协议发请求底层到底是哪家模型、要不要重试、怎么控制预算全部交给 litellm 处理。这篇文章我会从“它到底解决了什么问题”讲起然后分别用 SDK 和代理网关两条路线带你跑通一个最小示例再深入配置、缓存、日志、常见问题排查最后把我实际踩过的坑和习惯做法分享出来。适合正在做多模型接入、AI 应用落地、或者想在企业内部搭一套统一 LLM 网关的开发者参考。1. LiteLLM 到底在解决什么问题1.1 大模型 API 的“方言”问题说是“统一”本质上就是解决大模型生态里的接口碎片化。今天一个应用要接大模型面临的往往是这样的局面有的服务商返回 JSON 里的choices字段有的返回content有的返回text有的传参叫max_tokens有的叫max_tokens_to_sample鉴权方式也五花八门有的是Authorization: Bearer有的还要额外加请求头。你为一个模型写的调用逻辑换到另一个模型通常得改一片。我最早遇到这个痛点是在做一个“模型对比评估”的内部工具。当时要同时测五六个模型每个模型的 SDK 用法都不一样代码里全是if provider xxx: ... else: ...这种分支。后来把 litellm 引进来所有模型统一走一个completion()方法参数结构固定返回结构也固定。切换模型从“改代码”直接变成“改一个字符串”整个项目清爽得多。1.2 一个“库”和一个“网关”两种形态litellm 有两种用法这是很多人一开始会混淆的地方。第一种是SDK 模式就是在你的 Python 代码里直接import litellm调用litellm.completion()、litellm.embedding()。这种模式适合单机脚本、数据处理任务、快速原型不需要额外起服务。第二种是Proxy 模式也叫代理网关。你启动一个 litellm 服务进程它会对外暴露一个/v1/chat/completions之类的 HTTP 端点你的客户端——不管是什么语言写的——只需要按照 OpenAI 兼容格式发请求。这种模式适合多语言团队、微服务架构因为网关把模型切换、密钥管理、预算控制都收敛到了服务端客户端不需要知道底层细节。两种模式各有使用场景下面我分别展开。对比项SDK 模式Proxy 模式集成方式pip 安装后直接调用部署独立服务通过 HTTP 访问客户端语言限制主要面向 Python任意语言只要发 HTTP 请求密钥管理分散在代码或环境变量中集中在网关配置和服务端环境变量团队协作适合个人脚本适合多人/多服务共享同一套模型入口成本与预算控制需要自己实现内置预算、限流、令牌统计很多人以为 litellm 只是“调用大模型的一个库”其实它的网关能力才是生产环境里最有价值的部分。把它理解成“通用遥控器”——你用同一个遥控器去控制不同的设备按键位置不变只是背后适配的电器不同。1.3 什么时候你才真正需要它不是所有项目都需要 litellm。如果项目只固定用一家模型短期也不打算换那直接官方 SDK 是最简单的。但下面这些情况litellm 的价值会非常明显你需要做多模型热切换或灰度比如先把 10% 流量切到新模型剩下的继续走旧模型你需要在多个供应商之间做故障降级一个挂了自动换下一个你希望给团队里多个服务提供统一的模型入口同时控制总预算你需要记录每一次请求的 token 消耗和费用做成本归因你不想给每个后端服务都配一份不同厂商的 API Key而是通过一个集中网关统一放行。我个人的建议是哪怕项目目前只接了一个模型也值得在封装层上按 litellm 的思路设计。因为大模型领域变化太快你永远不知道半年后会不会因为价格、效果或者合规原因要换供应商到时候已经铺好统一抽象层切换成本会低很多。2. 十分钟上手SDK 调通第一个模型2.1 安装与第一个调用先用 pip 安装 litellm。如果你后面打算跑代理网关建议直接装带服务依赖的版本pip install litellm[proxy]如果只想要核心 SDK不需要额外组件装litellm本体就够了pip install litellm装完先做一个最简单的文本生成请求。注意为了明确告诉 litellm 走哪一家服务商模型名前面最好带上provider/前缀例如openai/gpt-3.5-turboimport litellm response litellm.completion( modelopenai/gpt-3.5-turbo, messages[ {role: user, content: 用一句话介绍 litellm} ], api_keysk-你的密钥 ) print(response[choices][0][message][content])如果你不想在代码里显式传api_key也可以设置环境变量比如export OPENAI_API_KEYsk-xxx。litellm 会自动读取对应服务商的环境变量这样代码里就不需要出现任何密钥了。我第一次跑通这个例子的时候最大的感受是“跟调用 OpenAI 官方 SDK 几乎一样”。实际上 litellm 就是刻意保持了这个熟悉的形状messages数组、choices[0].message.content你之前写 OpenAI 调用的经验可以无缝迁移过来。2.2 切换模型只改一个字符串SDK 模式下最爽的一点就是换模型几乎等于改一个字符串。比如我想换成另一家服务商的模型response litellm.completion( modelanthropic/claude-3-haiku-20240307, messages[ {role: user, content: 用一句话介绍 litellm} ], api_keysk-ant-你的密钥 )注意这里模型名带了anthropic/前缀litellm 就知道应该按 Anthropic 的路由方式去请求也会把返回格式转换成统一结构。所以你的上层业务代码完全不需要改只是传入的model参数变了。更极端一点的场景你可以把模型名称放到配置中心或数据库里业务逻辑读取配置后再调用。这样发版都不需要动代码运营同学直接改配置就能切换模型。我们之前做“模型 A/B 测试”就是这么干的同一个接口一部分用户走了openai/gpt-4o另一部分走了anthropic/claude-3-5-sonnet-20240620两边返回的数据结构一摸一样下游处理逻辑完全共用。2.3 异步调用、重试与超时生产环境里光能“跑通”是不够的还得考虑性能与稳定性。litellm 的异步写法也很直观import litellm import asyncio async def main(): resp await litellm.acompletion( modelopenai/gpt-3.5-turbo, messages[{role: user, content: 你好}], api_keysk-你的密钥, timeout15, num_retries2 ) print(resp[choices][0][message][content]) asyncio.run(main())这里我加了两个参数timeout15表示单次请求超时 15 秒num_retries2表示失败后最多自动重试 2 次。官方提供方默认会有内置的重试策略但你最好根据自己的业务容忍度显式设置这两个参数。过高的重试次数会导致接口响应时间不稳定过短的超时又会在大模型推理稍慢时误杀请求。我一般习惯把超时设在 20 秒左右简单对话场景可以短一点生成长文或复杂推理场景要留足缓冲。如果需要在脚本里同时并发调用多个模型做对比可以用asyncio.gather把多个acompletion任务包起来。litellm 的异步实现是线程池包装但在大多数业务并发下已经够用真正高并发的场景建议走后面的代理网关模式让网关帮你管理连接池和并发控制。3. 代理网关模式用配置管理一切3.1 部署一个最小网关SDK 模式解决了“代码统一”的问题但生成环境里我不想把密钥和模型配置散落在每个服务的启动环境变量里。这时候就把 litellm 当作一个独立服务跑起来让它承担统一代理职责。先写一个最简单的config.yamlmodel_list: - model_name: text-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: text-fast litellm_params: model: anthropic/claude-3-haiku-20240307 api_key: os.environ/ANTHROPIC_API_KEY启动网关litellm --config config.yaml --port 4000启动日志出现Uvicorn running on http://0.0.0.0:4000之类的提示后就可以用标准的 OpenAI 风格请求来访问了curl http://localhost:4000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的虚拟密钥 \ -d { model: text-main, messages: [{role: user, content: 你好}] }注意两点第一这里的model用的是配置文件里的别名text-main不是真实的底层模型名第二请求头里的Authorization: Bearer如果网关没开鉴权随便填个非空字符串也能过但我强烈建议在生产环境开启认证后面会讲到。3.2 模型别名、负载均衡与降级配置代理模式的核心就是config.yaml。很多团队把它当成模型资源的“目录”对外是稳定别名对内是实际模型地址和密钥配置。这样底层供应商换了你不用通知所有调用方只需改网关配置。如果同一个别名下面配置多个模型实例litellm 会自动做负载均衡。比如我有两个服务商的同档模型希望轮流分摊流量model_list: - model_name: text-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: text-main litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY两个条目的model_name都是text-main客户端请求text-main时litellm 会在两个底层模型之间轮询分发。这是做成本优化的一个关键技巧把不同价位的模型配到同一个别名下配合预算策略就能在“效果”和“成本”之间做动态平衡。再说降级配置。生产环境里某个上游供应商可能出现区域性故障你希望自动切换到备用通道可以在配置里加fallbackslitellm_settings: fallbacks: - {text-main: [text-fast]}这样当主模型连续报错时litellm 会把请求自动降级到text-fast。注意降级不是“立即切换”它本身的触发条件和重试次数相关。我建议你把降级模型的名字放在同一个配置文件里并且明确区分“主力模型”和“兜底模型”避免关键时刻业务完全不可用。3.3 开启鉴权与虚拟密钥网关对外暴露服务后第一个要解决的就是“谁可以调用”的问题。litellm 代理网关支持虚拟密钥Virtual Keys你可以理解成它帮你把真实服务商密钥藏起来每次给下游签发一个“子密钥”。先启用鉴权配置里加general_settings: master_key: sk-master-123456master_key是管理密钥用来访问/key/generate等一系列管理接口。然后启动网关调用下面接口生成虚拟密钥curl http://localhost:4000/key/generate \ -H Authorization: Bearer sk-master-123456 \ -X POST返回的 JSON 里会有一个key字段比如sk-abc123...。下游客户端拿着这个虚拟 key 访问/v1/chat/completionslitellm 会验证身份后再去调用真实模型。虚拟 key 还能设置配额、速率限制和预算让不同团队或不同项目各用各的额度。我实际用下来这一步非常解决团队协作中的“密钥治理”问题。以前每个服务都要配一份不同厂商的真实密钥出问题后根本分不清是谁在调、调了多少。现在所有人都走网关每个人的虚拟 key 都带着自己的标记日志里可以直接按 key 归因到团队和项目。3.4 预算、限流与用量控制成本控制是 litellm 代理模式里最吸引人的功能之一。你可以在litellm_settings里配置总预算litellm_settings: max_budget: 2000 budget_duration: 30d意思是 30 天内这个网关所有请求的费用上限是 2000 美元超过之后 litellm 会拒绝新的请求。你也可以针对单个虚拟 key 设定预算比如给某个内部工具只放 100 美元试用额度防止有人写了个死循环把账单刷爆。配合限流设置可以给同一模型、同一 key、同一 IP 设置每分钟请求数限制。生产环境里我强烈建议开启“按虚拟 key 限流”而不是只依赖模型上游限流。原因是你的网关前面可能有多个应用共享同一底层模型任何一个应用的异常流量都可能连累其他应用有了网关层限流可以把失控应用隔离在入口处。4. 进阶功能缓存、日志与可观测性4.1 响应缓存省钱与降延迟大模型请求不是廉价的而且很多用户提问会很相似。比如客服知识库里“如何退款”这个问题可能一天被问几百次。如果每次都要调大模型生成一遍费用和延迟都不划算。litellm 支持在网关层开启语义缓存。在配置里加一行litellm_settings: cache: true开启后litellm 会对相同请求内容直接返回缓存的响应而不触发底层模型调用。不同服务商对缓存策略的细节略有差异但基本思路都是对请求的 messages 内容做哈希或语义匹配。我在实际项目里观察到对高频重复问题缓存命中后响应时间能从平均 8 秒直接掉到几十毫秒费用也明显下降。不过要小心长尾场景如果你的业务里每次对话上下文都高度个性化缓存命中率会很低反而可能因为缓存存储多一层开销。开缓存前最好先用日志分析一下请求重复度再决定要不要开。4.2 日志、成功率和耗时追踪litellm 支持把请求日志输出到本地文件也可以接入监控系统。最简单的启动时指定日志文件litellm --config config.yaml --port 4000 --detailed_debug--detailed_debug会打印每次请求的详细路由过程。但生产环境别一直开着这个级别日志量很大排查问题时再临时开启会更合适。更推荐的做法是用代码注册回调。比如在代理的入口脚本里写import litellm def log_event(kwargs, response, start_time, end_time): print(kwargs[model], kwargs[messages], response.usage, end_time - start_time) litellm.success_callback [log_event]回调里可以拿到本次请求用的模型、消息、token 用量和耗时。把这些数据写到 ClickHouse、Postgres 或 Prometheus就能做出一套完整的模型调用监控看板。毕竟想回答“这个月花了多少钱、哪个模型请求最多、平均耗时多少”这类问题没有日志数据就是拍脑袋。litellm 也支持直接输出 Prometheus 指标配合 Grafana 做可视化。这个对长期运营非常重要我建议每个准备长期使用网关的团队至少把请求量、错误率、P95 延迟、单模型成本这几个面板搭起来不用一开始做得太复杂先有数据再说。4.3 多云环境下的密钥统一管理代理模式最大的“隐藏福利”其实是密钥统一管理。假设团队里有五个后端服务每个服务都需要调用两三家模型厂商的 API。如果各自持有真实密钥一旦某家服务商要求轮换密钥你得挨个服务去更新环境变量再重启如果某个同事把密钥硬编码到代码里还会带来泄露风险。把 litellm 网关作为唯一的“出口”后所有真实服务商密钥只存在于网关所在的服务端环境变量或安全的密钥管理服务中。下游服务只需要一个 litellm 网关签发的虚拟 key权限和额度都可以随时回收。即使某个下游服务代码被拖走泄露的也只是虚拟 key通过管理接口立刻就能吊销。这一点在安全审计时非常加分。你不再需要问“到底谁手里有真实密钥”因为真实密钥只藏在网关层可泄露面小了很多。5. 生产环境实战常见问题与排查技巧5.1 模型名报错“Model not found”这是新手最容易踩的坑。LiteLLM 的模型名规则是provider/model-name例如openai/gpt-4o、anthropic/claude-3-haiku-20240307。如果你只写gpt-4olitellm 一般会默认按 OpenAI 处理但如果你写的是claude-3-haiku却没有带anthropic/前缀它可能去 OpenAI 的接口找自然找不到。排查思路先用 Python 简单调一下litellm.completion(model...)看报错信息里实际请求的 URL 是哪个。如果请求的是api.openai.com但你本意是另一家厂商那就是前缀写错了。生产环境里更要检查config.yaml的litellm_params.model字段是否写全而不是看model_name别名。5.2 鉴权一直 401如果在网关模式下客户端传了虚拟 key但请求返回 401优先检查general_settings.master_key是否设置正确。如果你忘记开启鉴权随便传个 key 是能通过的反而说明配置没生效。如果你直接使用 SDK 并设置api_key参数要确认环境变量是否真的被读取比如用命令echo $OPENAI_API_KEY看是否为空。还有一个常见问题配置文件里写api_key: os.environ/OPENAI_API_KEY但网关启动进程时环境变量没加载进去。这种情况我一般会在启动脚本里先 source 环境变量文件再用litellm --config config.yaml启动避免手动敲命令时漏掉。5.3 超时和重试策略导致响应变慢litellm 的默认重试逻辑在个别场景下会放大延迟。比如一个上游接口已经明显异常每次要等 30 秒超时才报错然后 litellm 又自动重试 2 次相当于一个请求拖了 90 秒才返回失败。更合理的做法是缩短单次超时时间并调低无效重试次数。我在一个内部工具里把超时设置为 10 秒重试设置为 0然后让网关层的降级配置接管失败请求。这样主模型超时后直接切换备用模型而不是反复打同一个已经异常的通道。重试和降级不要同时过度配置两者合在一起会让故障响应时间变得很长。5.4 预算统计与实际账单有出入litellm 的费用计算是基于内置的模型价格表以及每次响应返回的 token 用量。但要注意实际账单里可能还有额外费用比如缓存写入费用、输入缓存命中费用、网络出口流量费等litellm 不一定全部计入。我自己经历过一次看似“预算控得好好的”月底账单却超出预期。后来发现是模型价格表没有及时更新某个模型的新版本价格已经变了litellm 内置表还是旧价格。最稳妥的做法是定期手动核对 litellm 版本升级公告中的价格表变更同时以服务商后台的账单为准litellm 的预算更适合做“硬控制闸门”而不是绝对财务预测。5.5 网关日志过多导致磁盘爆炸--detailed_debug打开的调试日志量非常大尤其在高并发请求下一天可能写出几个 GB。我见过不止一次线上服务因为日志文件占满磁盘而挂掉的。建议生产环境把日志级别调到 INFO 或 WARNING并且配合 logrotate 做日志轮转。如果你已经把日志输出到标准输出再交给 Docker 或 K8s 的日志采集服务管理尽量别同时再写一份本地文件否则就是重复存储和重复计费。6. 避坑与建议我实际用下来的几点心得6.1 先在 SDK 模式下做验证再上代理网关我见过不少团队上来就直接搭网关结果配置里的模型名写错了请求路由不生效排查半天。其实最稳妥的方法是先在本地 Python 脚本里用 SDK 模式把你需要用到的模型全部跑一遍确认模型名、参数、返回结构都符合预期然后再把模型名迁移到config.yaml中。这个“先 SDK 后 Proxy”的顺序还有一个好处可以直接在本地用--detailed_debug看每个模型的实际请求和返回不用反复重启网关服务。配置问题定位会比在远程服务器上快很多。6.2 用环境变量管理密钥不要写进配置文件虽然配置文件里可以硬编码 API Key但我强烈建议不要这么干。你的config.yaml很可能会被提交到代码仓库、在同事之间分享、甚至粘贴到调试群里。一旦真实 key 泄露损失的不是一条 key 的钱而是整个账户的安全。正确做法是配置文件里写api_key: os.environ/某变量名密钥本体放在网关所在机器的环境变量、或者专门的密钥管理服务里。我甚至会为不同环节准备不同的 key开发环境一套生产环境一套单独可以吊销互不影响。6.3 降级配置要做“故障演练”很多人配好 fallbacks 之后就不管了觉得已经高枕无忧。但 fallback 触发条件往往和错误类型、重试次数相关你不实际演练根本不知道在真实故障下它会怎么表现。我建议你在大促或重要活动之前专门挑一个低峰期临时把主模型改成不存在的模型名或者故意把 API Key 改错然后发几个测试请求观察网关是否按预期把请求降级到了备用模型以及客户端侧的耗时变化。这个演练成本很低但能提前暴露降级逻辑里的配置错误比线上故障时手忙脚乱强得多。6.4 给模型名加语义清晰的别名网关模式下下游能看到的是model_name别名而不是底层真实模型名。你可以把别名按“用途”命名而不是按“厂商型号”命名。比如text-main、text-fast、embedding-general会比gpt-4o、claude-3-5这种名字更稳定。原因很简单当底层模型要从gpt-4o换成gpt-4o-mini时上游text-main不需要变客户端也不用发版。如果直接把真实模型名暴露给下游你每次调整模型组合都相当于一次接口变更会牵连大量调用方。别名虽然多写一步配置但长期维护轻松很多。6.5 版本升级不要太随意litellm 迭代速度很快新功能很诱人但我不建议在核心网关环境追最新。尤其是从大版本跨越升级时配置文件的兼容性可能会有变化。我吃过一次亏升级后原来的fallbacks写法失效请求不再降级直到一次故障演练才发现。现在我的习惯是升级前先看 changelog重点搜breaking change升级后立即跑一遍完整的冒烟测试包括多模型调用、鉴权、降级、预算这几个核心链路。日常开发预发环境可以追新生产环境适当保守一点。最后再分享一个小习惯我会在项目里放一个“网关健康检查”脚本每天定时用虚拟 key 请求一次text-fast模型并把成功率和耗时记录到监控面板。这样底层供应商有没有波动、网关配置有没有被误改第二天一早就能发现。litellm 把多模型接入抽象得足够简单之后这类基础设施型的小工具反而成了最值得投入的部分。