最近有个多模型评测类的内部项目压到我手里,需要同时接好几家大模型的API,比较它们在中文语义理解、代码生成、长文档总结上的差异,还要给业务方一个统一入口。第一天我就被折腾得够呛:有的服务商是OpenAI风格接口,有的是自家SDK,有的要签名鉴权,有的返回结构完全不一样。把几家接完,光适配层就写了一千多行。后来,我发现了一个叫litellm的轻量方案,它把主流模型服务的接口规范全部收敛成一个OpenAI风格的端点,我原来那套适配层代码直接删掉大半,做运维的同学也只需要维护一份配置,不用碰每一家的SDK。这篇文章主要写给正在做AI应用集成、或者想在团队里搭一个统一模型网关的开发者看。你能获得三样东西:litellm能做什么、为什么能省掉大量重复适配工作,以及一条从零到生产可用的完整落地路径。我还会把实际使用中遇到的一些反直觉问题,比如不同模型的参数差异、密钥解析顺序、流式输出与计费统计的坑,原原本本摆出来。1. 为什么需要一层统一的API代理1.1 各家模型接口差异到底有多头疼先说一个很具体的场景。你手里有三家模型服务的调用权限:一家是标准的OpenAI兼容接口,传model、messages、temperature就行;一家需要自定义加密签名,请求头里要带X-Timestamp和一段动态计算的签名;还有一家走的是gRPC,连HTTP协议都不是一回事。评审的时候,要求说得很轻巧:"把这三家接入统一评测平台就行"。真正动手,你才发现每家都有自己的一套"脾气"。鉴权方式不同:有的是Bearer Token,有的是API Key拼在Header,有的要动态签名。参数名字不同:有的用max_tokens,有的用max_output_tokens,有的干脆用max_new_tokens,同一层意思,写法差出三种。返回结构不同:消息字段有的叫choices[0].message,有的叫output.text,有的叫candidates[0].content。做一层封装几乎是必然的。这种问题在只有一两个模型时还好,一旦团队要接十个、二十个模型,每个模型写一套适配代码,你会发现自己不是在写业务,而是在做"API翻译官"。更麻烦的是,任何一个服务商调整了字段,你的解析逻辑跟着改,真正要写的业务反而没时间写。1.2 litellm如何抽象这个差异litellm的思路其实非常朴素:把所有模型接口都"翻译"成业界最通用的OpenAI风格。你不管是调OpenAI系、Anthropic系、Gemini系还是国内几个主流模型,对外都暴露同一个/chat/completions端点,消息格式统一成messages结构,返回统一成choices结构。业务方完全不需要关心背后是哪一个服务商。对比一下没有litellm和有litellm的区别:维度传统方式litellm方式接入新模型写一套SDK初始化和适配代码在配置里加一条litellm_params请求协议各家SDK都有,团队要学好几套只认OpenAI格式,已有代码零改动密钥管理散落在各服务环境变量里集中在litellm配置层统一注入故障切换手动改代码切换服务商配置fallbacks自动降级当初我刚开始用的时候,心里嘀咕"这不就是一个中间层吗,能有多大差别"。真正用上之后才意识到,它最大的价值不是省那几行代码,而是把"接模型"这个动作从一次开发任务,降级成了一个配置动作。后续业务想再加一个模型,不用发版,不用改代码,运维改一改配置就能让新模型上线,这对团队协作效率的提升是非常明显的。1.3 代理模式带来的额外好处litellm除了做协议转换,天然还带了一个代理层。这意味着你可以在这个位置上做很多以前需要单独开发的横切能力:请求日志统一沉淀。谁在什么时间调用了哪个模型、消耗了多少token,全都可以从代理层拉出来。模型调用频率控制。给不同业务方设置调用速率上限,防止某个调用方把预算打爆。数据格式统一出口。业务方即使未来换了供应商,因为始终走litellm这一层,对上层没有感知。我把这层理解成"API总线的USB Hub":你的设备不需要管对面是键盘还是鼠标,只要插上同一个口就能用。litellm就是这个Hub,各种模型是外设,业务代码是电脑。这也是为什么后来团队新接手的人能很快上手,统一入口带来的心智负担降低非常多。2. litellm的核心能力拆解2.1 兼容OpenAI的入口设计litellm最核心的设计决策,就是全面拥抱OpenAI的消息格式。这意味着团队里已有的、基于OpenAI SDK写的代码,基本上可以无缝切过来。你只需要改两样东西:base_url指向litellm代理地址,api_key用litellm自己发的代理密钥。其余代码几乎不用动。以Python为例,通过代理调用和直连OpenAI在代码层面几乎一样:from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="sk-litellm-proxy-key" ) response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": "介绍一下你自己的能力边界"} ] ) print(response.choices[0].message.content)我团队里有几个同事一开始不熟悉这个设计,第一次用的时候还担心是不是要学一套新SDK。实际跑通之后才发现,原本的调用代码一行没改,只是环境变量里的base_url变了。开发体验的平滑过渡,是litellm能在工程里迅速落地的关键。2.2 密钥管理与多模型路由密钥管理是一个很容易被低估的能力。我在生产环境里见过不少团队,把各家模型的API Key直接硬编码在业务代码的配置里,甚至明文存在代码仓库里,这是非常危险的。litellm把这些密钥统一收口,支持从环境变量、配置文件、密钥管理服务里读取。config.yaml里的配置大体长这样:model_list: - model_name: gpt-3.5-turbo litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY - model_name: gemini-pro litellm_params: model: gemini/gemini-pro api_key: os.environ/GEMINI_API_KEY这里有一个很重要的解析规则:model_name是暴露给业务方的逻辑名称,litellm_params.model才是实际要请求的模型路径,前缀openai/、anthropic/、gemini/是指定provider的路由标识。业务方只知道模型叫gpt-3.5-turbo或claude-3-5-sonnet,至于你后面接的是哪一家,他们完全不用关心。反过来,你可以在不改业务代码的前提下,把model_name后面的实际模型换成别的厂商,业务方无感。2.3 重试、降级与负载均衡模型服务商偶尔抽风是常态,连个稳定的可用性保障都没有。业务方的诉求很简单:你给我一个能稳定返回结果的模型通道,至于哪个服务商不可用、要不要换一个,是平台层该关心的事。litellm在配置里就能搞定这块。举个最常用的降级场景:router_settings: fallbacks: [{"gpt-3.5-turbo": ["claude-3-5-sonnet"]}]这条配置的意思是,当gpt-3.5-turbo请求失败时,自动把请求转给claude-3-5-sonnet。从调用方的视角看,它只知道自己发出了一个请求,收到了一个正常的成功响应,中间那一次失败已经被透明处理掉了。生产环境中,这种能力比多写几行异常处理代码实在得多。负载均衡方面,同一个模型可以配置多个密钥或不同厂商的端点,litellm会自动做轮询。比如你两个服务商都有gpt-4o能力,那在model_list里写两条litellm_params,分别指向两家端点,代理层会按负载情况分发请求,这样就不容易因为单一配额打满导致整个链路不可用。2.4 预算控制与用量观测AI项目里成本失控是一个很常见的翻车点。有些评测任务在测试阶段看着没啥,一上量,大模型账单直奔天上去,老板问你钱花哪儿了,你答不上来。litellm把用量观测和预算限额做在了代理层,你可以给每个调用方或每个模型设置rpm(每分钟请求数)、tpm(每分钟token数)和max_budget(最高预算)。一旦超过预算上限,代理层会直接拒绝新请求,不会继续烧钱。同时我可以通过代理的/spend端点或对应的Dashboard,看每个模型累计算了多少成本、每次请求平均消耗多少token。做内部结算和成本分摊时,这些数据非常有用。没有这层观测之前,我都不知道一个业务方的定时任务每晚悄悄跑了几万次评测请求,多亏预算告警功能拉住了失控的调用。3. 从零跑通一个代理服务3.1 安装与环境准备litellm是Python包,建议装在一个独立的环境或者直接用官方容器。本机测试时,建一个虚拟环境是最省心的:python -m venv .venv source .venv/bin/activate pip install "litellm[proxy]"这里我特别想提醒:安装时最好带上[proxy]扩展,它会把代理服务需要的额外依赖一起装上。只装纯litellm包也能用,但缺少部分代理层功能,后面跑起来才会发现各种ModuleNotFoundError,反而不如一开始就装完整。如果你想快速用容器起,官方镜像也提供得很完整,一条命令就能把整个服务端拉起来:docker run -p 4000:4000 -v $(pwd)/config.yaml:/app/config.yaml ghcr.io/berriai/litellm:main-latest容器方式的好处是和生产环境隔离干净,本地验证完配置直接推上去就行。你在本机先跑起来,后面接代码、调降级链路都很方便。3.2 写一份可用的config.yaml配置文件是litellm的核心。我先列一个最小可用配置模板,你看着改就行:model_list: - model_name: gpt-3.5-turbo litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY general_settings: master_key: sk-your-proxy-master-keymaster_key是代理自己的身份验证密钥,业务方调用时需要带这个key。这样你对外只暴露一个代理入口,真实的模型服务商密钥全部藏在代理后面。不要小看这层隔离,它意味着即使某个业务方的代码或者环境变量泄露了,攻击者拿到的也只是一个代理key,而拿不到真正的模型厂商密钥。启动代理也很简单:export OPENAI_API_KEY="sk-xxxx" export ANTHROPIC_API_KEY="sk-ant-xxxx" litellm --config ./config.yaml --port 4000启动日志里如果能看到类似"Uvicorn running on http://0.0.0.0:4000"的字样,说明代理已经起来了。3.3 验证代理是否生效代理起来之后,我习惯先用一个最简单的请求做冒烟测试。不写代码,直接用curl最方便:curl --location 'http://localhost:4000/v1/chat/completions' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer sk-your-proxy-master-key' \ --data '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "你好,请回复:代理已连通"}] }'如果返回的是一个标准OpenAI风格响应,里面带choices[0].message.content,说明链路已经通了。这时候再用OpenAI SDK去连http://localhost:4000/v1,也就顺理成章了。我建议把接口验证放在配置调整之前,先确认基础链路再优化高级功能,后续排查问题会清爽很多。很多时候你觉得是降级没生效,其实只是冒烟测试就没通过,基础链路本身是断的。4. 接入自己代码的三种姿势4.1 直接把OpenAI SDK的base_url指向代理这是最推荐的方式。如果业务里已经在用OpenAI SDK,那你离接入litellm只差一个环境变量:from openai import OpenAI client = OpenAI( base_url="http://your-litellm-host:4000/v1", api_key="sk-your-proxy-master-key" ) resp = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "用一句话说明缓存的作用"}], temperature=0.7 ) print(resp.choices[0].message.content)这里要注意一点:model参数填的是model_name,不是实际的物理模型名。如果你填了anthropic/claude-3-5-sonnet这种带provider前缀的名字,代理也能识别,但那就失去了model_name做逻辑映射的意义。关键是让调用方只感知逻辑名,平台层才能自由地切换实际模型。我团队有个习惯:接口文档里只暴露model_name列表,具体物理模型由平台配置决定,业务侧完全黑盒。4.2 Python集成方式(不跑独立代理)如果你的诉求不是搭一个网关,只是在代码内部想用一个统一接口调用多个模型,可以不启动代理进程,直接用litellm的Python能力。它内置了类似OpenAI的completion接口:import asyncio from litellm import Router model_list = [ { "model_name": "gpt-3.5-turbo", "litellm_params": { "model": "openai/gpt-3.5-turbo", "api_key": "sk-xxxx" } }, { "model_name": "claude-3-5-sonnet", "litellm_params": { "model": "anthropic/claude-3-5-sonnet", "api_key": "sk-ant-xxxx" } } ] router = Router(model_list=model_list) async def main(): resp = await router.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "讲一个简短的冷笑话"}] ) print(resp.choices[0].message.content) asyncio.run(main())这种方式特别适合做评测脚本、内部工具链和比较实验。比如你想跑一个"同一道题,让三个模型分别作答然后对比"的脚本,用Router写起来非常顺手。它和代理模式的区别在于:代理模式是独立的服务,给别人提供网络入口;Router模式是进程内库,只服务当前进程,不对外暴露HTTP端口。日常脚本和测试任务,用Router就够了;要给多团队共用,必须上代理模式。4.3 多模型自动切换的工程实践实际项目里,单纯一个"点对点调用"其实不够,核心价值在于自动切换。我参与的那个评测平台,一个任务会按规则选择模型:优先用主力模型A,A不可用时降级到B,再不行就落到C。人工写这套逻辑,每次新增模型都要改代码。用litellm之后,降级策略全部落在配置里:router_settings: fallbacks: [ {"gpt-3.5-turbo": ["claude-3-5-sonnet", "gemini-pro"]} ]这样业务代码仍然是单次调用,复杂的故障转移逻辑由代理层接管。我还做过一个实验:故意把主力模型对应的服务商密钥改成错的,然后发起一次请求,代理自动把请求转到了二号模型,响应体里会有额外的字段告诉你实际走了哪个模型。这种透明降级,对在线服务尤为重要——调用方不需要感知故障,只要最终拿到结果就是成功。如果你还需要更精细的控制,比如想根据请求内容里的某个业务维度动态指定模型,litellm也可以在请求参数里按需传fallbacks,灵活度比全局配置更高。这个因人因场景而异,先掌握全局降级,再按需加精细化控制,是最稳妥的上手路径。5. 生产环境部署中的关键配置5.1 模型降级链怎么写才可靠降级链是生产环境里最值得花时间打磨的配置。我见过不少人只写了一条fallbacks,以为万事大吉,但实际跑起来发现降级触发率低得可怜。原因往往出在"什么才算失败"这个判断上。litellm默认把网络错误、服务商返回5xx、超时都算作可降级失败,但有些服务商为了表示"请求太频繁",会返回429状态码,这算限流不算宕机,需要单独对待。我在生产配置里会把超时时间调短一些,例如设置timeout: 30,这样如果上游30秒没返回,代理快速标记失败并走降级,而不是让用户长时间等待。另一个关键是降级链的顺序。把最稳、最便宜的模型放在前面,把保底模型放在最后。有的团队喜欢把最强的模型放前面,结果每次降级都落到弱模型,评测结果方差特别大,反而不利于一致性。降级链的排序,本质上是一个"稳定性和效果"的平衡决策,建议根据实际评测数据来调整。5.2 限流与成本上限的设置上线前务必配置限流和预算,特别是新开放的模型入口,不然一个内部工具大规模跑起来,月底账单能让人心梗。我配置过一套很实用的模板:router_settings: rpm: 60 tpm: 100000 max_budget: 200.0 budget_duration: 30d上面的含义是:每分钟最多60次请求,每分钟token总量上限10万,每个30天周期内最高消耗200元成本。超限后请求直接被拒。这里我还想提醒一句:预算上限要设成某个额度,而不是设成无限大。有人怕设低了影响业务,先设个"9999元",结果月底账单真就奔着9999去了。预算是用来主动约束行为的,不是用来安慰自己的。限流层面的另一个细节是并发控制。litellm的rpm和tpm是做速率限制,但真正的高并发场景还需要配合连接池和超时参数使用。我之前遇到过在线上并发上来后大量超时,一开始误以为限流没生效,后来把并发和超时参数一起调优,问题才缓解。速率限制只是保护层,真正的容量规划还是要靠自己压测。5.3 安全隔离:密钥不能进代码很多AI项目的密钥泄露事故,不是被攻击者黑进去的,而是自己从代码仓库里翻出来的。用litellm之后,你可以在代理层做一次彻底的密钥收敛:业务方接触不到真实模型密钥,代理密钥按团队分配,甚至可以定期轮换。真实模型密钥全部通过环境变量注入代理进程,这样安全等级提升一大截。生产环境推荐把配置文件和密钥分离:代码库里只放config.yaml的模板,真正的api_key用os.environ/变量名引用,并通过系统的环境变量机制注入。我见过有人图省事,直接把api_key: sk-xxxx写在提交到仓库的配置文件里,这是非常危险的习惯,换工作、离职交接、开源代码复制粘贴时,密钥都有可能随机泄露,务必从第一天就养成用环境变量引用的习惯。6. 我踩过的坑和一些建议6.1 参数兼容性比想象中更隐蔽litellm统一了消息结构,但不同模型对采样参数的容忍度还是有差别的。最典型的是temperature,有的服务商支持小数,有的只认0到1之间的档位,传个1.5过去,有的模型直接报错,有的会静默截断。还有response_format,我有一个阶段在项目里统一用了{"type": "json_object"},结果切到某些模型时无法返回JSON,排查了很久才发现是部分服务商不支持这个字段,需要改成提示词约束的方式。所以我现在的建议是:在模型列表里维护一张"能力差异表",把是否支持temperature、是否支持json_object、是否支持图像输入、最大上下文长度这些参数列清楚。配置模型时照着表核一遍,不要在线上临时试错。litellm能帮你统一接口,但它没法帮你消除模型本身的能力边界,这块只能靠自己的配置约束。还有一个特别容易踩的坑是max_tokens系参数的写法,不同模型对上限的默认值差异很大,有的默认只给几百token,生成长文时无声无息被截断,返回里也没有明显报错,需要自己留意返回内容是否完整。6.2 流式输出与token统计的误区流式输出是另一个容易出问题的地方。用stream=True时,litellm通过SSE协议把内容分块推给调用方。表面看没问题,但如果你在代理层做token统计,需要判断你统计的是"流式过程中产生的token"还是"完整响应后的最终token"。流式场景下,有些字段在最后一块才会返回,逐块累加容易重复计数或者漏计。我的建议是:能不用流式就不用流式,除非业务有打字机效果或长响应实时展示的需求。评测类任务通常只需要最终文本,直接用非流式接口,省心得多。如果一定要流式,务必在拿到最终结束标识后,再基于完整响应做用量统计,不要依赖中间块的增量数据。有几次我在日志里看到某个模型成本异常高,排查下来就是流式统计多算了token,修正统计逻辑后数据才恢复正常。6.3 日志排查与调试技巧litellm的日志信息量很大,关键是要学会怎么用。我调试时习惯先打开debug级别的日志:litellm --config ./config.yaml --port 4000 --detailed_debug--detailed_debug会打印每个请求的完整流转过程,包括命中了哪个模型、走了哪条密钥、是否触发了降级。排查"为什么请求走到了B模型"这类问题,打开这个日志基本一目了然。另外一个很实用的排查技巧是查看响应里的元数据字段。litellm在正常响应中会附带额外信息,比如实际使用的模型名、供应商、计费信息等。我在排查问题时,会先用一个小脚本打印完整的响应对象,不只看message.content,还要看model、usage这些字段,确认到底是谁在服务这个请求。如果发现model字段和请求时的model_name不一致,十有八九是触发了降级或别名映射。这块信息量很大,但很多人只看了content就完事,把关键线索丢掉了。关于配置热更新,我还想补充一点:litellm支持在启动后修改配置并重新加载,不用重启进程。但我在实际使用中发现,某些参数(比如模型列表结构调整)还是建议在低峰期重启,确保完全生效。热更新适合小改,大调整还是重启稳妥,这是多次测试之后我觉得比较靠谱的做法。6.4 如果只能带走一句话litellm真正帮我解决的问题,不是"用一个库调多家API",而是把"接入模型"从开发工作变成了配置工作。这带来的连锁反应是:业务代码稳定了、密钥安全了、成本可控了、团队协作也更顺了。这个项目本身迭代也很快,功能不断在加,建议在使用时锁定版本,并关注每个大版本的变更说明,不要盲目升级。我自己的做法是:核心生产环境固定版本,测试环境跑最新版验证新功能,确认稳定后再逐步升级。你如果刚开始上手,就从最小配置跑通一个代理,加一个模型,接一段真实业务,踩一轮坑之后再上生产。
最近有个多模型评测类的内部项目压到我手里,需要同时接好几家大模型的API,比较它们在中文语义理解、代码生成、长文档总结上的差异,还要给业务方一个统一入口。第一天我就被折腾得够呛:有的服务商是OpenAI风格接口,有的是自家SDK,有的要签名鉴权,有的返回结构完全不一样。把几家接完,光适配层就写了一千多行。后来,我发现了一个叫litellm的轻量方案,它把主流模型服务的接口规范全部收敛成一个OpenAI风格的端点,我原来那套适配层代码直接删掉大半,做运维的同学也只需要维护一份配置,不用碰每一家的SDK。这篇文章主要写给正在做AI应用集成、或者想在团队里搭一个统一模型网关的开发者看。你能获得三样东西:litellm能做什么、为什么能省掉大量重复适配工作,以及一条从零到生产可用的完整落地路径。我还会把实际使用中遇到的一些反直觉问题,比如不同模型的参数差异、密钥解析顺序、流式输出与计费统计的坑,原原本本摆出来。1. 为什么需要一层统一的API代理1.1 各家模型接口差异到底有多头疼先说一个很具体的场景。你手里有三家模型服务的调用权限:一家是标准的OpenAI兼容接口,传model、messages、temperature就行;一家需要自定义加密签名,请求头里要带X-Timestamp和一段动态计算的签名;还有一家走的是gRPC,连HTTP协议都不是一回事。评审的时候,要求说得很轻巧:"把这三家接入统一评测平台就行"。真正动手,你才发现每家都有自己的一套"脾气"。鉴权方式不同:有的是Bearer Token,有的是API Key拼在Header,有的要动态签名。参数名字不同:有的用max_tokens,有的用max_output_tokens,有的干脆用max_new_tokens,同一层意思,写法差出三种。返回结构不同:消息字段有的叫choices[0].message,有的叫output.text,有的叫candidates[0].content。做一层封装几乎是必然的。这种问题在只有一两个模型时还好,一旦团队要接十个、二十个模型,每个模型写一套适配代码,你会发现自己不是在写业务,而是在做"API翻译官"。更麻烦的是,任何一个服务商调整了字段,你的解析逻辑跟着改,真正要写的业务反而没时间写。1.2 litellm如何抽象这个差异litellm的思路其实非常朴素:把所有模型接口都"翻译"成业界最通用的OpenAI风格。你不管是调OpenAI系、Anthropic系、Gemini系还是国内几个主流模型,对外都暴露同一个/chat/completions端点,消息格式统一成messages结构,返回统一成choices结构。业务方完全不需要关心背后是哪一个服务商。对比一下没有litellm和有litellm的区别:维度传统方式litellm方式接入新模型写一套SDK初始化和适配代码在配置里加一条litellm_params请求协议各家SDK都有,团队要学好几套只认OpenAI格式,已有代码零改动密钥管理散落在各服务环境变量里集中在litellm配置层统一注入故障切换手动改代码切换服务商配置fallbacks自动降级当初我刚开始用的时候,心里嘀咕"这不就是一个中间层吗,能有多大差别"。真正用上之后才意识到,它最大的价值不是省那几行代码,而是把"接模型"这个动作从一次开发任务,降级成了一个配置动作。后续业务想再加一个模型,不用发版,不用改代码,运维改一改配置就能让新模型上线,这对团队协作效率的提升是非常明显的。1.3 代理模式带来的额外好处litellm除了做协议转换,天然还带了一个代理层。这意味着你可以在这个位置上做很多以前需要单独开发的横切能力:请求日志统一沉淀。谁在什么时间调用了哪个模型、消耗了多少token,全都可以从代理层拉出来。模型调用频率控制。给不同业务方设置调用速率上限,防止某个调用方把预算打爆。数据格式统一出口。业务方即使未来换了供应商,因为始终走litellm这一层,对上层没有感知。我把这层理解成"API总线的USB Hub":你的设备不需要管对面是键盘还是鼠标,只要插上同一个口就能用。litellm就是这个Hub,各种模型是外设,业务代码是电脑。这也是为什么后来团队新接手的人能很快上手,统一入口带来的心智负担降低非常多。2. litellm的核心能力拆解2.1 兼容OpenAI的入口设计litellm最核心的设计决策,就是全面拥抱OpenAI的消息格式。这意味着团队里已有的、基于OpenAI SDK写的代码,基本上可以无缝切过来。你只需要改两样东西:base_url指向litellm代理地址,api_key用litellm自己发的代理密钥。其余代码几乎不用动。以Python为例,通过代理调用和直连OpenAI在代码层面几乎一样:from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="sk-litellm-proxy-key" ) response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": "介绍一下你自己的能力边界"} ] ) print(response.choices[0].message.content)我团队里有几个同事一开始不熟悉这个设计,第一次用的时候还担心是不是要学一套新SDK。实际跑通之后才发现,原本的调用代码一行没改,只是环境变量里的base_url变了。开发体验的平滑过渡,是litellm能在工程里迅速落地的关键。2.2 密钥管理与多模型路由密钥管理是一个很容易被低估的能力。我在生产环境里见过不少团队,把各家模型的API Key直接硬编码在业务代码的配置里,甚至明文存在代码仓库里,这是非常危险的。litellm把这些密钥统一收口,支持从环境变量、配置文件、密钥管理服务里读取。config.yaml里的配置大体长这样:model_list: - model_name: gpt-3.5-turbo litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY - model_name: gemini-pro litellm_params: model: gemini/gemini-pro api_key: os.environ/GEMINI_API_KEY这里有一个很重要的解析规则:model_name是暴露给业务方的逻辑名称,litellm_params.model才是实际要请求的模型路径,前缀openai/、anthropic/、gemini/是指定provider的路由标识。业务方只知道模型叫gpt-3.5-turbo或claude-3-5-sonnet,至于你后面接的是哪一家,他们完全不用关心。反过来,你可以在不改业务代码的前提下,把model_name后面的实际模型换成别的厂商,业务方无感。2.3 重试、降级与负载均衡模型服务商偶尔抽风是常态,连个稳定的可用性保障都没有。业务方的诉求很简单:你给我一个能稳定返回结果的模型通道,至于哪个服务商不可用、要不要换一个,是平台层该关心的事。litellm在配置里就能搞定这块。举个最常用的降级场景:router_settings: fallbacks: [{"gpt-3.5-turbo": ["claude-3-5-sonnet"]}]这条配置的意思是,当gpt-3.5-turbo请求失败时,自动把请求转给claude-3-5-sonnet。从调用方的视角看,它只知道自己发出了一个请求,收到了一个正常的成功响应,中间那一次失败已经被透明处理掉了。生产环境中,这种能力比多写几行异常处理代码实在得多。负载均衡方面,同一个模型可以配置多个密钥或不同厂商的端点,litellm会自动做轮询。比如你两个服务商都有gpt-4o能力,那在model_list里写两条litellm_params,分别指向两家端点,代理层会按负载情况分发请求,这样就不容易因为单一配额打满导致整个链路不可用。2.4 预算控制与用量观测AI项目里成本失控是一个很常见的翻车点。有些评测任务在测试阶段看着没啥,一上量,大模型账单直奔天上去,老板问你钱花哪儿了,你答不上来。litellm把用量观测和预算限额做在了代理层,你可以给每个调用方或每个模型设置rpm(每分钟请求数)、tpm(每分钟token数)和max_budget(最高预算)。一旦超过预算上限,代理层会直接拒绝新请求,不会继续烧钱。同时我可以通过代理的/spend端点或对应的Dashboard,看每个模型累计算了多少成本、每次请求平均消耗多少token。做内部结算和成本分摊时,这些数据非常有用。没有这层观测之前,我都不知道一个业务方的定时任务每晚悄悄跑了几万次评测请求,多亏预算告警功能拉住了失控的调用。3. 从零跑通一个代理服务3.1 安装与环境准备litellm是Python包,建议装在一个独立的环境或者直接用官方容器。本机测试时,建一个虚拟环境是最省心的:python -m venv .venv source .venv/bin/activate pip install "litellm[proxy]"这里我特别想提醒:安装时最好带上[proxy]扩展,它会把代理服务需要的额外依赖一起装上。只装纯litellm包也能用,但缺少部分代理层功能,后面跑起来才会发现各种ModuleNotFoundError,反而不如一开始就装完整。如果你想快速用容器起,官方镜像也提供得很完整,一条命令就能把整个服务端拉起来:docker run -p 4000:4000 -v $(pwd)/config.yaml:/app/config.yaml ghcr.io/berriai/litellm:main-latest容器方式的好处是和生产环境隔离干净,本地验证完配置直接推上去就行。你在本机先跑起来,后面接代码、调降级链路都很方便。3.2 写一份可用的config.yaml配置文件是litellm的核心。我先列一个最小可用配置模板,你看着改就行:model_list: - model_name: gpt-3.5-turbo litellm_params: model: openai/gpt-3.5-turbo api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY general_settings: master_key: sk-your-proxy-master-keymaster_key是代理自己的身份验证密钥,业务方调用时需要带这个key。这样你对外只暴露一个代理入口,真实的模型服务商密钥全部藏在代理后面。不要小看这层隔离,它意味着即使某个业务方的代码或者环境变量泄露了,攻击者拿到的也只是一个代理key,而拿不到真正的模型厂商密钥。启动代理也很简单:export OPENAI_API_KEY="sk-xxxx" export ANTHROPIC_API_KEY="sk-ant-xxxx" litellm --config ./config.yaml --port 4000启动日志里如果能看到类似"Uvicorn running on http://0.0.0.0:4000"的字样,说明代理已经起来了。3.3 验证代理是否生效代理起来之后,我习惯先用一个最简单的请求做冒烟测试。不写代码,直接用curl最方便:curl --location 'http://localhost:4000/v1/chat/completions' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer sk-your-proxy-master-key' \ --data '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "你好,请回复:代理已连通"}] }'如果返回的是一个标准OpenAI风格响应,里面带choices[0].message.content,说明链路已经通了。这时候再用OpenAI SDK去连http://localhost:4000/v1,也就顺理成章了。我建议把接口验证放在配置调整之前,先确认基础链路再优化高级功能,后续排查问题会清爽很多。很多时候你觉得是降级没生效,其实只是冒烟测试就没通过,基础链路本身是断的。4. 接入自己代码的三种姿势4.1 直接把OpenAI SDK的base_url指向代理这是最推荐的方式。如果业务里已经在用OpenAI SDK,那你离接入litellm只差一个环境变量:from openai import OpenAI client = OpenAI( base_url="http://your-litellm-host:4000/v1", api_key="sk-your-proxy-master-key" ) resp = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "用一句话说明缓存的作用"}], temperature=0.7 ) print(resp.choices[0].message.content)这里要注意一点:model参数填的是model_name,不是实际的物理模型名。如果你填了anthropic/claude-3-5-sonnet这种带provider前缀的名字,代理也能识别,但那就失去了model_name做逻辑映射的意义。关键是让调用方只感知逻辑名,平台层才能自由地切换实际模型。我团队有个习惯:接口文档里只暴露model_name列表,具体物理模型由平台配置决定,业务侧完全黑盒。4.2 Python集成方式(不跑独立代理)如果你的诉求不是搭一个网关,只是在代码内部想用一个统一接口调用多个模型,可以不启动代理进程,直接用litellm的Python能力。它内置了类似OpenAI的completion接口:import asyncio from litellm import Router model_list = [ { "model_name": "gpt-3.5-turbo", "litellm_params": { "model": "openai/gpt-3.5-turbo", "api_key": "sk-xxxx" } }, { "model_name": "claude-3-5-sonnet", "litellm_params": { "model": "anthropic/claude-3-5-sonnet", "api_key": "sk-ant-xxxx" } } ] router = Router(model_list=model_list) async def main(): resp = await router.acompletion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "讲一个简短的冷笑话"}] ) print(resp.choices[0].message.content) asyncio.run(main())这种方式特别适合做评测脚本、内部工具链和比较实验。比如你想跑一个"同一道题,让三个模型分别作答然后对比"的脚本,用Router写起来非常顺手。它和代理模式的区别在于:代理模式是独立的服务,给别人提供网络入口;Router模式是进程内库,只服务当前进程,不对外暴露HTTP端口。日常脚本和测试任务,用Router就够了;要给多团队共用,必须上代理模式。4.3 多模型自动切换的工程实践实际项目里,单纯一个"点对点调用"其实不够,核心价值在于自动切换。我参与的那个评测平台,一个任务会按规则选择模型:优先用主力模型A,A不可用时降级到B,再不行就落到C。人工写这套逻辑,每次新增模型都要改代码。用litellm之后,降级策略全部落在配置里:router_settings: fallbacks: [ {"gpt-3.5-turbo": ["claude-3-5-sonnet", "gemini-pro"]} ]这样业务代码仍然是单次调用,复杂的故障转移逻辑由代理层接管。我还做过一个实验:故意把主力模型对应的服务商密钥改成错的,然后发起一次请求,代理自动把请求转到了二号模型,响应体里会有额外的字段告诉你实际走了哪个模型。这种透明降级,对在线服务尤为重要——调用方不需要感知故障,只要最终拿到结果就是成功。如果你还需要更精细的控制,比如想根据请求内容里的某个业务维度动态指定模型,litellm也可以在请求参数里按需传fallbacks,灵活度比全局配置更高。这个因人因场景而异,先掌握全局降级,再按需加精细化控制,是最稳妥的上手路径。5. 生产环境部署中的关键配置5.1 模型降级链怎么写才可靠降级链是生产环境里最值得花时间打磨的配置。我见过不少人只写了一条fallbacks,以为万事大吉,但实际跑起来发现降级触发率低得可怜。原因往往出在"什么才算失败"这个判断上。litellm默认把网络错误、服务商返回5xx、超时都算作可降级失败,但有些服务商为了表示"请求太频繁",会返回429状态码,这算限流不算宕机,需要单独对待。我在生产配置里会把超时时间调短一些,例如设置timeout: 30,这样如果上游30秒没返回,代理快速标记失败并走降级,而不是让用户长时间等待。另一个关键是降级链的顺序。把最稳、最便宜的模型放在前面,把保底模型放在最后。有的团队喜欢把最强的模型放前面,结果每次降级都落到弱模型,评测结果方差特别大,反而不利于一致性。降级链的排序,本质上是一个"稳定性和效果"的平衡决策,建议根据实际评测数据来调整。5.2 限流与成本上限的设置上线前务必配置限流和预算,特别是新开放的模型入口,不然一个内部工具大规模跑起来,月底账单能让人心梗。我配置过一套很实用的模板:router_settings: rpm: 60 tpm: 100000 max_budget: 200.0 budget_duration: 30d上面的含义是:每分钟最多60次请求,每分钟token总量上限10万,每个30天周期内最高消耗200元成本。超限后请求直接被拒。这里我还想提醒一句:预算上限要设成某个额度,而不是设成无限大。有人怕设低了影响业务,先设个"9999元",结果月底账单真就奔着9999去了。预算是用来主动约束行为的,不是用来安慰自己的。限流层面的另一个细节是并发控制。litellm的rpm和tpm是做速率限制,但真正的高并发场景还需要配合连接池和超时参数使用。我之前遇到过在线上并发上来后大量超时,一开始误以为限流没生效,后来把并发和超时参数一起调优,问题才缓解。速率限制只是保护层,真正的容量规划还是要靠自己压测。5.3 安全隔离:密钥不能进代码很多AI项目的密钥泄露事故,不是被攻击者黑进去的,而是自己从代码仓库里翻出来的。用litellm之后,你可以在代理层做一次彻底的密钥收敛:业务方接触不到真实模型密钥,代理密钥按团队分配,甚至可以定期轮换。真实模型密钥全部通过环境变量注入代理进程,这样安全等级提升一大截。生产环境推荐把配置文件和密钥分离:代码库里只放config.yaml的模板,真正的api_key用os.environ/变量名引用,并通过系统的环境变量机制注入。我见过有人图省事,直接把api_key: sk-xxxx写在提交到仓库的配置文件里,这是非常危险的习惯,换工作、离职交接、开源代码复制粘贴时,密钥都有可能随机泄露,务必从第一天就养成用环境变量引用的习惯。6. 我踩过的坑和一些建议6.1 参数兼容性比想象中更隐蔽litellm统一了消息结构,但不同模型对采样参数的容忍度还是有差别的。最典型的是temperature,有的服务商支持小数,有的只认0到1之间的档位,传个1.5过去,有的模型直接报错,有的会静默截断。还有response_format,我有一个阶段在项目里统一用了{"type": "json_object"},结果切到某些模型时无法返回JSON,排查了很久才发现是部分服务商不支持这个字段,需要改成提示词约束的方式。所以我现在的建议是:在模型列表里维护一张"能力差异表",把是否支持temperature、是否支持json_object、是否支持图像输入、最大上下文长度这些参数列清楚。配置模型时照着表核一遍,不要在线上临时试错。litellm能帮你统一接口,但它没法帮你消除模型本身的能力边界,这块只能靠自己的配置约束。还有一个特别容易踩的坑是max_tokens系参数的写法,不同模型对上限的默认值差异很大,有的默认只给几百token,生成长文时无声无息被截断,返回里也没有明显报错,需要自己留意返回内容是否完整。6.2 流式输出与token统计的误区流式输出是另一个容易出问题的地方。用stream=True时,litellm通过SSE协议把内容分块推给调用方。表面看没问题,但如果你在代理层做token统计,需要判断你统计的是"流式过程中产生的token"还是"完整响应后的最终token"。流式场景下,有些字段在最后一块才会返回,逐块累加容易重复计数或者漏计。我的建议是:能不用流式就不用流式,除非业务有打字机效果或长响应实时展示的需求。评测类任务通常只需要最终文本,直接用非流式接口,省心得多。如果一定要流式,务必在拿到最终结束标识后,再基于完整响应做用量统计,不要依赖中间块的增量数据。有几次我在日志里看到某个模型成本异常高,排查下来就是流式统计多算了token,修正统计逻辑后数据才恢复正常。6.3 日志排查与调试技巧litellm的日志信息量很大,关键是要学会怎么用。我调试时习惯先打开debug级别的日志:litellm --config ./config.yaml --port 4000 --detailed_debug--detailed_debug会打印每个请求的完整流转过程,包括命中了哪个模型、走了哪条密钥、是否触发了降级。排查"为什么请求走到了B模型"这类问题,打开这个日志基本一目了然。另外一个很实用的排查技巧是查看响应里的元数据字段。litellm在正常响应中会附带额外信息,比如实际使用的模型名、供应商、计费信息等。我在排查问题时,会先用一个小脚本打印完整的响应对象,不只看message.content,还要看model、usage这些字段,确认到底是谁在服务这个请求。如果发现model字段和请求时的model_name不一致,十有八九是触发了降级或别名映射。这块信息量很大,但很多人只看了content就完事,把关键线索丢掉了。关于配置热更新,我还想补充一点:litellm支持在启动后修改配置并重新加载,不用重启进程。但我在实际使用中发现,某些参数(比如模型列表结构调整)还是建议在低峰期重启,确保完全生效。热更新适合小改,大调整还是重启稳妥,这是多次测试之后我觉得比较靠谱的做法。6.4 如果只能带走一句话litellm真正帮我解决的问题,不是"用一个库调多家API",而是把"接入模型"从开发工作变成了配置工作。这带来的连锁反应是:业务代码稳定了、密钥安全了、成本可控了、团队协作也更顺了。这个项目本身迭代也很快,功能不断在加,建议在使用时锁定版本,并关注每个大版本的变更说明,不要盲目升级。我自己的做法是:核心生产环境固定版本,测试环境跑最新版验证新功能,确认稳定后再逐步升级。你如果刚开始上手,就从最小配置跑通一个代理,加一个模型,接一段真实业务,踩一轮坑之后再上生产。