项目标题: litellm
做AI应用开发这一年多,我最大的感受就是“模型太多,切换太累”。今天想聊的这个项目,几乎解决了我在这方面九成以上的痛点——litellm。简单说,它就是一个大语言模型API的“翻译层”和“调度层”,你只需要按OpenAI的格式写一套代码,它就能帮你把请求转发到OpenAI、Anthropic、Cohere、本地部署的模型等一百多家供应商,还能顺便搞定负载均衡、成本统计和缓存。这篇博文不聊官方文档里那些漂亮话,我把自己从零上手到部署到生产环境的完整过程、踩过的坑、调优的心得都整理出来,希望对正在做多模型接入或者准备搭建内部模型网关的开发者有帮助。
1. 为什么需要litellm这层“翻译官”
1.1 多模型调用的真实痛点
先说说我最初遇到的问题。当时在做一个面向企业客户的文档处理系统,客户今天说“你们用GPT-4效果不错”,明天又说“我们内部数据必须走私有化部署的模型”,后天可能又说“某个模型太贵了,换一个便宜的吧”。最开始图省事,直接在业务代码里写不同供应商的SDK,结果很快就被逼疯了。
且不说OpenAI、Azure OpenAI、Anthropic各自有不同的鉴权方式,光是请求格式就够喝一壶的。有的模型用text/plain参数,有的用messages数组,有的要求max_tokens,有的叫max_tokens_to_sample;有的温度范围是0到1,有的可以到2;返回的字段名还不一样,有的叫choices,有的叫completion,有的叫output。处理这些差异的代码一层套一层,最后越来越难维护。
更头疼的是模型切换的场景。业务上经常要做A/B测试,比较不同模型在同一批数据上的效果。如果用原生的方式,每次切换模型都得改代码、重新部署,测试成本和风险都太高。当时我就想,如果有一个中间层,对外统一暴露OpenAI格式的接口,对内适配各种供应商,一切问题就都解决了。
1.2 litellm的核心设计思路
litellm的定位恰好就是这样一个中间层。它对外提供两个主要入口:一个是Python SDK里的completion()函数,另一个是部署为代理服务后暴露的/chat/completions接口,两者都严格兼容OpenAI的请求和响应格式。内部的逻辑则是根据你传入的model参数,把OpenAI格式的请求翻译成目标供应商需要的格式,再把响应翻译回OpenAI格式。
这个设计最聪明的地方在于,它没有自己发明一套“通用格式”,而是选择了OpenAI格式作为标准。原因其实很现实:OpenAI已经是事实上的行业标准,市面上绝大部分开源项目和工具都支持OpenAI格式,包括我那时候用的很多开发框架。这样一来,接入litellm几乎不需要改业务代码,完全可以用它直接替换原来的OpenAI调用。
打个不太恰当的比方,litellm就像一个同时精通多门外语的翻译官,你只用说中文(OpenAI格式),他帮你转成英语、日语、法语。就算你哪天想换一个交谈对象,你不需要重新学一门语言,只需要告诉翻译官换个人就行。
2. 核心功能拆解:不只是统一接口
2.1 模型路由与切换逻辑
很多人以为litellm只是“统一API格式”,实际用下来,我发现它的模型路由能力才是真正的生产力工具。在配置文件中,你可以定义一组模型,并给它们设置权重。比如你有三个模型A、B、C,想按用户请求量做分配,只需要把权重配成50、30、20,litellm会按照这个比例自动路由。这本质上是一个加权轮询的负载均衡器。
还有一个很实用的场景是“主备切换”。比如你同时接入了OpenAI和Azure OpenAI,想让Azure作为主要供应商,OpenAI作为备用。如果Azure那边出现限流(429错误)或者超时,litellm会自动把请求转发到OpenAI,整个过程对调用方完全透明。我最早在生产环境用上这个功能,是在一次Azure的故障演练中,我们的核心服务在Azure不可用的情况下自动切换到了备用供应商,业务零感知。
除了手动配权重,litellm还支持一些更智能的路由策略。比如可以根据每个模型的“预算消耗”做路由,优先使用预算剩余更多的模型;也可以设置“模型组”的概念,把多个供应商的同一个模型(比如OpenAI的gpt-4o和Azure的gpt-4o)合并成一个组,统一对外暴露。还有一点值得提的是,它支持“ttl”参数,也就是模型健康状态检查,如果一个模型持续报错,litellm会把它临时摘除,直到它恢复健康。
2.2 负载均衡、重试与超时处理
这部分我想展开聊聊生产环境中最重要的三个可靠性机制。
首先是重试机制。litellm允许针对不同类型的错误,比如限流、服务器错误、网络超时,分别配置重试次数和重试间隔。它默认对限流错误做指数退避重试,也就是每次重试的等待时间翻倍,避免在服务端还没恢复时疯狂重试压垮对方。这里有一个很实在的建议:重试配置不要一刀切。对于限流错误,重试3次是合理的;但对于参数错误(如400错误),重试再多也没用,不如直接抛给调用方。
其次是超时控制。它支持timeout参数,可以精确到秒。我在实际配置时,不同供应商给的超时时间都不一样:OpenAI的接口一般20到30秒比较合适,本地部署的模型可能更慢,我一般给到60秒。这个时间不能拍脑袋定,建议你们先压测一轮,看看自己常用模型在长上下文下的P99响应时间,再酌情设置超时。
再就是并发控制。litellm可以在代理层设置max_parallel_requests,限制同时发给某个供应商的请求数。这个非常关键,因为很多供应商的限流策略是很敏感的,你冲到它的QPS上限,轻则返回429,重则被拉黑IP。我个人的经验是,初始并发数设置得保守一点,比如5到10,后续根据监控数据慢慢往上加,别一上来就把并发拉满。
2.3 缓存、成本追踪与Key管理
缓存这个功能我一开始没怎么在意,直到有一次用真实数据测试,才发现它的价值。litellm支持在代理层做响应缓存,如果你的业务中有大量重复的请求,比如同一批文档的摘要分析、同一批客服会话的分类,缓存命中之后响应速度会快很多。它提供了两种缓存方式:一是内存缓存,适合单机部署的轻量场景;二是基于Redis的缓存,适合多实例部署,大家共享一份缓存。
成本追踪是litellm另一个非常容易被低估的功能。它会根据你每个请求实际消耗的token数量,结合模型单价,计算出这次调用的成本。我只需要在配置里把每个模型的输入单价和输出单价填好,它就能在后台生成每个项目、每个API Key、甚至每次调用的成本明细。这对做内部费用分摊和预算控制极其有用。我甚至看到有团队用这个数据来对比不同供应商的性价比,然后周期性调整路由权重来压低总成本。
Key管理则是把litellm打造成团队级模型的必备功能。你可以为每个团队成员或者每个项目生成独立的API Key,然后给每个Key设置预算上限、速率限制、可访问的模型范围。比如实习生账号只能调用轻量模型,且每月预算不超过100美元;生产环境的Key则绑定核心模型,且开了详细的审计日志。
3. 从零部署litellm的完整实操
3.1 安装与环境准备
先从最基础的安装说起。litellm是一个Python包,安装非常简单:
pip install litellm如果只是想在本地快速体验,这一步就够了。但如果是部署到生产环境,我强烈建议用Docker。官方提供了镜像,一条命令就能拉起来:
docker pull ghcr.io/berriai/litellm:main-latest docker run -d -p 4000:4000 -v ./litellm_config.yaml:/app/config.yaml ghcr.io/berriai/litellm:main-latest --config /app/config.yaml这里涉及到一个关键概念:配置文件。litellm的代理模式需要一份YAML配置文件来定义模型列表和路由规则。下面是一个最小可用的配置示例:
model_list: - model_name: gpt-4o-mini litellm_params: model: gpt-4o-mini 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是你要暴露给调用方的名字,litellm_params.model才是真正发给供应商的标识。比如你想让内部所有代码只记住gpt-4o-mini这一个名字,但实际上它可能背后映射的是某个其他供应商的模型。这种解耦设计在迁移模型时太方便了。
谈到环境变量,litellm支持从环境变量中读取API Key,这是推荐的方式,避免把密钥直接提交到代码仓库。你可以在启动代理进程前先设置好:
export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..."补充一点版本选择的经验:litellm迭代速度很快,基本一周一个版本。生产环境建议锁定一个经过验证的版本号,不要直接用latest,以免更新带来不兼容变更。我吃过这个亏,某次升级后发现一个日志字段格式变了,导致内部监控系统解析出了问题。
3.2 Python SDK:五分钟跑通首个请求
如果你的项目本身就是Python写的,可以不部署代理服务,直接在代码里用SDK。
import litellm response = litellm.completion( model="gpt-4o-mini", messages=[ {"role": "user", "content": "用一句话介绍你自己"} ], temperature=0.7 ) print(response.choices[0].message.content)这段代码和OpenAI SDK的写法几乎一样。关键在于model参数。它是litellm最核心的映射入口:
- 传
gpt-4o-mini,默认走OpenAI; - 传
anthropic/claude-3-5-sonnet,走Anthropic; - 传
azure/gpt-4o,走Azure OpenAI; - 传
ollama_chat/llama3.1,走本地Ollama。
模型名称只要加上供应商前缀,请求就会被路由到对应的服务商。这种设计带来的好处是:你的业务代码里存的是逻辑模型名,比如gpt-4o-mini,将来如果换了底层供应商,只需要改前缀和API Key配置,代码一行都不用动。
还有一个对调试特别有用的参数:
resp = litellm.completion(..., debug=True)打开debug之后,控制台会打出完整的请求头和请求体,方便排查问题。我强烈建议在开发和联调阶段开启。
异步调用是另一个高频需求,litellm的异步接口和同步接口参数保持一致:
import asyncio import litellm async def main(): response = await litellm.acompletion( model="gpt-4o-mini", messages=[{"role": "user", "content": "写一首三行诗"}] ) print(response.choices[0].message.content) asyncio.run(main())3.3 代理模式:让现有代码无缝接入
SDK接入适合Python项目,但现实情况是,很多团队的代码是Node.js写的,或者是Java写的老系统,不可能为了接litellm把整个技术栈换掉。这个时候,代理模式就是真正的救星。
启动代理服务:
litellm --config ./litellm_config.yaml --port 4000服务起来之后,它会暴露一个/chat/completions接口,完全兼容OpenAI的协议。意味着你只需要修改OpenAI SDK的base_url,就能把请求指向litellm:
from openai import OpenAI client = OpenAI( api_key="sk-随便填一个litellm的key", base_url="http://localhost:4000/v1" ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "你好"}] )注意一个细节:这里api_key的值在代理模式下其实可以是任意字符串,当然我们更推荐在litellm里配置虚拟Key来做权限管理。一行base_url的改动,就把原来的OpenAI调用无缝切到了一个支持几十家供应商、自动重试、负载均衡、缓存、审计的网关层,这是litellm在生产环境中最大的杀手锏。
代理模式下的鉴权也值得花时间配好。litellm支持Master Key机制,也就是你启动代理之前设置一个LITELLM_MASTER_KEY,调用方必须带着这个Key才能通过网关。用/key/generate接口还可以动态生成子Key,每个子Key绑定额度。
4. 进阶配置:负载均衡与多模型路由实战
4.1 多供应商配置示例
把litellm部署成生产网关之后,我做的第一件事就是把OpenAI和Azure OpenAI同时接进来,做成主备模式。下面是一份真实用过的配置:
model_list: - model_name: gpt-4o litellm_params: model: azure/gpt-4o api_base: https://your-resource.openai.azure.com api_key: os.environ/AZURE_API_KEY api_version: 2024-02-15-preview model_info: supports_function_calling: true - model_name: gpt-4o-fallback litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY router_settings: routing_strategy: simple-shuffle num_retries: 3 request_timeout: 30 fallbacks: [ {"gpt-4o": ["gpt-4o-fallback"]} ]配置项里最核心的是fallbacks,它定义了一个失败转移规则:如果gpt-4o(Azure)请求失败或者说超时,自动把请求送给gpt-4o-fallback(OpenAI)。routing_strategy我选的是simple-shuffle,它会在所有可用的目标间随机打散请求,避免总是把请求压在同一台供应商那里。
如果你需要按权重分配流量,比如不同模型按比例分摊成本,可以改用这种方式:
model_list: - model_name: my-route litellm_params: model: gpt-4o routing_strategy: weight model_weight: 30 - model_name: my-route litellm_params: model: claude-3-5-sonnet routing_strategy: weight model_weight: 70两个模型的model_name都叫my-route,调用方只用面对这个名字,litellm会自动按30%和70%的比例分配流量。
这里有一个我踩过的坑:如果要让函数调用(function calling)正常工作,配置里最好显式声明supports_function_calling: true。有几个模型本身支持但配置里没有标出来,导致网关在预处理时直接过滤掉了工具调用相关的参数,排查了好久才发现是配置文件少了一个声明。
4.2 语义缓存与预算控制
除了基础路由,我们团队最近用上了一个很香的功能——语义缓存。传统缓存要求请求完全一致才能命中,但对AI应用来说,用户提问的措辞可能千差万别,意思却一样。litellm支持基于向量的语义缓存,就是先把请求内容做embedding,再和之前存过的向量做相似度比较,相似度超过阈值就直接返回缓存结果。
开启语义缓存的配置如下:
cache_params: type: redis-semantic similarity_threshold: 0.95 redis_host: localhost redis_port: 6379这个功能特别适合知识库问答类的应用。比如内部员工天天问“年假申请流程是什么”,虽然每个人的问法不一样,但语义是一致的。开启语义缓存之后,这类请求的响应时间几乎可以降到几十毫秒,而且完全省掉了这部分token消耗。
预算控制这块,litellm做得很细。它支持四种级别:每个API Key的预算、每个项目的预算、每个用户的预算、全局预算。超出预算之后,请求会被直接拒绝,不会产生额外费用。我在配置里用的是这样一组参数:
general_settings: max_budget: 1000 budget_duration: 30d这样设定的是全局30天最多消耗1000美元。给某个Key设置独立预算则用下面的命令:
curl -X POST "http://localhost:4000/key/generate" \ -H "Authorization: Bearer sk-1234" \ -d '{"max_budget": 100, "budget_duration": "30d", "models": ["gpt-4o-mini"]}'这种做法对控制失控成本太有用了。我见过太多团队到月底一看账单傻眼的情况,自从上了litellm的Key级预算之后,再也没出现过类似惊吓。
顺带提一下,litellm还支持把每次请求的成本明细输出到Prometheus,用Grafana做可视化。生产环境强烈建议加这个功能,你可以清晰地看到每个模型、每个Key、每个时段的调用量和成本趋势,效果非常好。
5. 常见问题与排查技巧实录
5.1 鉴权与Key配置类问题
问题:请求一直返回401 Unauthorized。
这是最高频的问题了。大多数情况下是api_key没有正确读取。我排查这类问题时有一个固定的流程:
- 先在启动代理的终端里手动执行
echo $OPENAI_API_KEY,确认环境变量确实存在; - 再确认配置文件的
api_key写的是os.environ/OPENAI_API_KEY这样的格式,不是直接写的密钥本体(如果直接写密钥字面量,也要注意YAML里的特殊字符转义问题); - 最后用
debug=true跑一次请求,看打印出的请求头中Authorization字段是否携带了正确的Key。
还有一个容易忽视的点:某些供应商的Key是有地域属性的。比如Azure的Key必须匹配对应的api_base,如果你的api_base写的是区域A,而Key是在区域B生成的,鉴权一样过不去。
问题:代理起来了,但所有模型都返回“No such model available”。
这个一般是配置加载失败导致的。可能是YAML文件格式有误,特别是多行字符串的缩进问题,或者是model_list里某个模型名称写错了。我见过一个典型的错误是在模型名后面多写了一个逗号,导致整个列表解析失败。
排查方法很简单:先不带配置文件启动,用litellm --model gpt-4o-mini直接起一个临时代理,如果这样可以正常访问,那就是配置文件的问题,用python -c "import yaml; print(yaml.safe_load(open('config.yaml')))"检查一下配置文件结构。
5.2 模型参数与上下文窗口问题
问题:报错ContextWindowExceededError(上下文窗口溢出)。
不同模型的上下文长度差异很大,但OpenAI格式的调用方往往直接忽略了这一点。比如某个配置了512K上下文窗口的模型,如果业务代码无条件拼接所有召回内容,高峰期很容易把一些只有8K上下文的小模型打爆。
litellm本身不会帮你截断上下文,它只会报错。解决思路有两个:一是做请求侧的内容裁剪,比如只保留对话的最后N轮;二是利用litellm的max_tokens参数限制生成长度,避免因为响应过长而溢出。
我遇到过一种特殊情况:同一个模型的上下文窗口,在不同供应商那里居然有不同表现。有的供应商支持动态上下文压缩,有的不支持。这就需要在配置里用model_info.max_input_tokens和model_info.max_output_tokens显式声明每个模型的真实窗口大小,方便litellm做预检。
问题:temperature参数在部分模型上报错。
这事不怪litellm,是不同模型的参数范围确实不同。Claude的temperature范围是0到1,OpenAI是0到2,有些开源模型甚至不支持temperature而只有top_p。litellm的兼容策略是:如果目标模型不支持该参数,会直接忽略掉,但有些情况下会报参数错误。
我的建议是在业务代码里做参数归一化。比如把业务层的temperature统一设计成0到1的浮点数,在litellm调用前做一次映射。虽然麻烦一点,但模型切换时不会出幺蛾子。
5.3 稳定性与性能问题
问题:高并发下代理服务的延迟上升很明显。
排查后发现,大部分延迟不是litellm本身造成的,而是它内部的“重试风暴”。当上游供应商开始限流时,litellm会按指数退避重试,大量的并发请求同时进入退避等待状态,导致代理层线程池被占满,后续请求排队时间暴涨。
解决方案是两层。第一层是给每个供应商配置独立的max_parallel_requests限流,比如OpenAI模型组最多同时10个请求,Anthropic模型组最多同时5个请求,避免单个供应商限流拖垮全局。第二层是把请求超时时间从默认的60秒缩短到20到30秒,配合重试机制,让无效请求快速失败,而不是一直占着连接池。
问题:代理重启后Redis缓存还在,但监控数据丢失。
litellm在代理模式下,默认把调用日志和成本数据存在SQLite数据库里。如果你用Docker部署,没有挂载卷,重启容器后数据就没了。这个问题隐蔽但后果严重,因为你可能很久之后才发现历史数据一片空白。
生产环境一定要做持久化配置。两个地方需要持久化:一是SQLite数据库文件,挂载到宿主机;二是Redis如果也是Docker容器,同样要挂载持久化目录。建议把它们都配置好再上线,别等出了问题再补。
写在最后的一些体会
用litellm做模型网关到现在,我最大的体会是:它把AI应用开发里最脏最累的“适配层”工作直接省掉了,让团队能把精力放回业务逻辑本身。尤其是多模型自动切换和Key级成本管控这两个能力,对一个中型团队来说价值极高。
最后再分享一个小技巧。前期接入litellm的时候,在配置里同时保留一个“直连供应商”的模型别名和一个“走litellm网关”的模型别名,比如gpt-4o-direct和gpt-4o-proxy。出问题的时候,就能快速对比判断异常到底是发生在litellm层还是供应商层,排查效率会高很多。这个思路在我们内部已经保留成模板,新项目默认就这么配。如果你正准备给自己的业务引入统一模型网关,希望这篇内容能帮你少走几段弯路。