ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Litellm实战:用开源API网关统一大模型接入与治理

Litellm实战:用开源API网关统一大模型接入与治理

1. 先说说我为什么盯上了litellm:多模型接入的混乱必须治

过去两个月我们团队手上有三条AI业务线在并行推进,每一条连接的模型来源都不一样。第一条业务线用的是某头部云厂商的闭源模型接口,第二条业务线接了一个开源模型的私有化部署,第三条业务线在试几个新出的小模型,想看看效果。麻烦很快就来了:每个接口都有自己的SDK、自己的鉴权方式、自己的返回结构,甚至同一个需求在三个接口里要用三种完全不同的代码去写。最崩溃的一次,为了给三个接口统一做一个"问答质量评估"的调试脚本,我硬是写了三套解析逻辑,因为有的返回choices[0].message.content,有的返回output.text,还有的返回结构里套了两层data。

这种状态持续了大概一周我就受不了了。代码仓库里到处是xxx_client、yyy_sdk这样的适配层,命名都还算是小事,问题是每接入一个新模型,就要重新读一遍别人的文档,重新写一遍请求和解析的胶水代码。出问题的时候还得一个接口一个接口去排查。我当时的想法很简单:能不能有一个东西,让我只用一种方式去调用所有模型?模型变更、供应商切换、密钥管理、成本统计,这些统统在这一层解决,业务代码不感知后面发生了什么。

然后我就找到了litellm。简单来说,litellm是一个开源的大模型API网关,它把一百多种模型服务的接口差异全部屏蔽掉,对外统一暴露成OpenAI格式的API。也就是说,无论背后是闭源模型、开源模型、云上托管的模型还是你自己GPU机器上跑着的模型,你都可以用同一套OpenAI SDK去调用。光这一点就已经解决了我们80%的对接痛苦。更让我觉得值得写一篇长文的是,它还顺手解决了密钥管理、预算控制、日志审计、负载均衡这些本来要自己造轮子的事情。

这篇文章我想以一个实际落地者的身份,把litellm从"知道"到"用起来"再到"用得顺手"的过程里,那些文档里写得不够直白、官网示例覆盖不到的东西都讲清楚。如果你也是那种手上一堆模型接口、天天写适配代码的人,这篇文章应该能帮你省下不少时间。

2. 为什么需要这样一个网关卡:换模型不该改业务代码

2.1 各家接口的差异到底有多大

很多人会觉得"模型API不都差不多嘛,就是发个请求拿个回复"。真不是这样。我把我们当时接入的几家接口放在一起对比过,差异大到让人头疼:

差异点模型接口A模型接口B模型接口C
鉴权方式Header里放Authorization: Bearer自定义Header塞API Key既支持Header也支持Query参数
请求体结构messages数组配model字段prompt字符串配max_tokens既有inputs又分parameters
返回文本字段choices[0].message.contentoutputs[0].textdata.text
模型名称格式模型名-版本号需要拼上instruct后缀用部署ID而不是模型名
流式返回格式SSE的data: [DONE]结尾分隔符略有不同需要额外传stream: true才给SSE流
错误码语义429限流、401鉴权429也限流但是包了一层用的HTTP状态码基本一致但body结构完全不同

这些差异单独看都不算什么,只要肯花时间,每个都能适配。但它们组合在一起就很要命了。你的业务代码里每接一个模型,就要面对一套新的字段映射规则;每次模型供应商那边调整返回结构,你的解析逻辑就要跟着改。我们有一条业务线的解析代码就因为这个改了三次,每次都是供应商版本升级导致的字段变动。

2.2 网关这层存在的意义

litellm做的事情,可以理解成在模型接口和你的业务代码之间加了一个"翻译层"。你的业务代码只需要按照OpenAI的格式去发请求,litellm内部会帮你把请求翻译成目标模型能理解的格式,再把目标模型的返回翻译回OpenAI的格式。这个思路就像是你去国外出差不需要学每一个国家的语言,只需要找一个翻译跟着你走就行。

只是这个"翻译"不只是翻译文本这么简单,litellm还帮你把周边一堆琐碎的事情也一起办了:

  • 密钥托管:真实模型供应商的API Key集中在litellm的服务端保存,业务侧拿到的是一个虚拟密钥。就算某个虚拟密钥泄露了,你也可以单独吊销它,不用动真实密钥。
  • 预算和速率控制:可以按项目、按团队、按虚拟密钥分别设置额度上限,也可以设置每分钟/每小时的请求数限制。这一点在有多条业务线共用模型额度的时候特别有用,不然一条业务线发起疯来能把整个月的预算烧光。
  • 日志和审计:所有经过网关的请求都会被记录,包括谁调的、调的哪个模型、传了多少token、花了多少钱、响应状态怎么样。出了安全事故或者成本异常的时候,有日志可以查。
  • 路由和容错:同一个模型可以配置多个供应商来源,一个供应商限流了自动切换备用。还可以做简单的负载均衡,把请求分散到价格不同的模型上,实现在效果和成本之间找一个平衡。

我见过有团队觉得这些功能自己写也不难,就打算直接在业务代码里做适配层。短期看确实能顶一阵子,但随着模型供应商变多、业务方变多,代码里就会到处是if-else,最后变成一个谁都不敢动的泥潭。网关这一层存在的意义,就是把"模型接入"这个横切关注点从业务逻辑里剥离出来,集中治理。

3. 从零跑通:安装、起一个网关、用OpenAI SDK调用起来

3.1 环境准备和安装

litellm是Python写的,安装非常简单,有Python 3.8以上环境就行:

pip install 'litellm[proxy]'

注意我这里是带[proxy]后缀的。litellm本身是个Python SDK,你可以直接把它作为一个库导入到自己的代码里用,但那样的话你只是省去了写适配代码的工作,并没有获得网关的好处。真正要获得完整的密钥管理、日志、预算这几个能力,需要把它的代理模式跑起来,也就是启动一个服务端进程,所有请求先到它这里,再由它转发到真实的模型服务。

如果你只需要在Python脚本里快速用一下,不启动服务端也是可以的:

from litellm import completion response = completion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "你好"}] )

但是这篇文章我主要讲代理模式,因为代理模式才是把它当网关用的时候。装好包之后可以先用python -m litellm --help看一眼命令行参数,确认安装成功。

3.2 最小可用配置:model_list

litellm跑起来之后需要一个配置文件(通常是config.yaml),用来告诉它:你能访问哪些模型、每个模型对应的供应商是谁、密钥是什么。下面是一个最简配置:

model_list: - model_name: my-gpt-4o litellm_params: model: openai/gpt-4o api_key: sk-xxx - model_name: my-claude litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: sk-ant-xxx - model_name: my-llama litellm_params: model: openai/llama3.1:8b-instruct api_base: http://localhost:11434

这里有个细节值得注意:model_name是你给这个模型起的别名,是业务代码里要用的名字;而litellm_params.model是litellm内部的模型标识符,格式是供应商/模型名。比如openai/gpt-4o表示通过OpenAI兼容格式访问gpt-4o,anthropic/claude-3-5-sonnet-20241022表示通过Anthropic原生的接口访问。如果你的模型跑在某个OpenAI兼容服务的本地环境上,就在litellm_params里再加一个api_base指向那个服务的地址。litellm支持的供应商前缀非常多,从常见的openai、anthropic、azure、bedrock到ollama、vllm都有,不用自己写适配代码。

然后启动服务:

python -m litellm --config config.yaml --port 4000

启动日志里如果出现Uvicorn running on http://0.0.0.0:4000,网关就算起来了。

3.3 三行代码接入

网关起来之后,你业务代码里的调用方式就变得异常简单了。因为litellm对外暴露的是OpenAI兼容接口,所以直接用官方OpenAI SDK就行,只需要把base_url指到你的网关地址:

from openai import OpenAI client = OpenAI( api_key="sk-anything", # 只要非空即可 base_url="http://localhost:4000/v1" ) resp = client.chat.completions.create( model="my-gpt-4o", messages=[{"role": "user", "content": "我用litellm跑通了"}], stream=False ) print(resp.choices[0].message.content)

你甚至不需要注册任何真实密钥,只要网关起了,配置里写了对的供应商密钥,业务代码里随便填一个非空的api_key就能通过鉴权。这个设计在本地开发阶段很舒服,你可以在不太关心密钥体系的情况下先把流程走通。

如果想要流式输出,把stream=True传进去,收到的是一个流式对象,和官方SDK的用法完全一致;如果你用的是JavaScript,同理,官方Node.js SDK里设置baseURL然后正常调用就行。当初我一个人花了一个下午折腾出来的效果,换成litellm之后,一个全新业务线接入第一个模型,从写配置到跑通,五分钟差不多了。

4. 把网关用好:虚拟密钥、预算、路由这些硬核功能逐个捋

4.1 虚拟密钥到底解决了什么问题

如果你只是给一个小工具做接入,直接用真实密钥也说得过去。但团队的场景是多人、多服务、多项目共享模型额度,这个时候真实密钥的痛点就出来了:

  • 密钥放在代码仓库里,容易泄露。
  • 一个密钥被多个服务共用,出了问题不知道是哪个服务干的。
  • 某个服务被攻击或者代码写死循环了,一个月额度一下烧完,其他服务全部停摆。

litellm针对这个问题提供了虚拟密钥机制。你先把真实供应商密钥写在config.yaml里,然后在gateway运行期间,通过管理接口创建一个虚拟密钥:

curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{"aliases": {"my-gpt-4o": "my-gpt-4o"}}'

创建成功后你会拿到一串sk-开头的新密钥。把它发给业务侧使用。每个虚拟密钥可以绑定一组模型、设置额度上限、设置过期时间。业务侧拿着虚拟密钥调用网关,网关再用真实密钥去请求上游模型供应商。如果某个虚拟密钥异常消耗额度,你可以单独限流、暂停、删除,完全不影响其他虚拟密钥的使用。

我自己的习惯是:每个服务用单独的虚拟密钥,名称和服务一一对应(比如order-service、search-service),然后用元数据字段再打上负责人和项目的标签。这样哪天某个虚拟密钥的token消耗异常,我能在日志里一眼定位到是哪个服务干的,不需要猜。

4.2 预算控制要这样配才有效

litellm的预算控制有几个层级,配置方式略有不同。最基本的层级是全局预算,写在config.yaml的general_settings下面:

general_settings: max_budget: 1000 # 美元 budget_duration: 30d

这个配置意思是在30天周期内,整个网关的总花费不能超过1000美元。超过之后,所有经过网关的请求都会被拒绝。

不过在实际落地过程中,我建议你优先配置虚拟密钥维度的预算,因为这样可以精确到服务:

model_list: - model_name: my-gpt-4o litellm_params: model: openai/gpt-4o api_key: sk-xxx max_budget: 200 budget_duration: 30d

这样my-gpt-4o这个模型在30天内最多花费200美元。还有更细的键值维度、用户维度、团队维度预算,可以根据你的组织架构来安排。我只提醒一句:预算参数要区分max_budget和rpd_limit、tpm_limit这几个字段,前者管钱,后两者管请求速率和token速率。很多人在配置的时候容易混淆,明明是速率问题却去调预算,调半天没有效果。

litellm完成一次请求之后会做异步的成本统计,回传token数和费用。这类统计数据多了之后对系统性能有一点影响,在生产环境建议把默认的日志存储换成数据库后端,而不是用内存模式,不然重启一次网关统计数据就全没了。

4.3 路由、负载均衡与自动故障转移

litellm的一个实用功能是:同一个model_name可以配置多个上游来源,网关会根据设置的策略自动分配请求。例如:

model_list: - model_name: my-gpt-4 litellm_params: model: openai/gpt-4 api_key: sk-xxx - model_name: my-gpt-4 litellm_params: model: azure/openai-gpt-4-deployment api_base: https://my-azure-resource.openai.azure.com/ api_key: azure-xxx

这样你公开的模型名只有一个my-gpt-4,上游却有两个来源。默认情况下litellm使用简单的轮询策略,也可以配置成基于rr或者自定义权重。如果某个上游供应商返回限流、超时或者5xx错误,litellm会把请求自动切换到另一个上游。对业务侧来说,它感知不到这个切换过程,看到的还是一个正常的成功响应。

我在生产环境中用这个功能做了两件事。第一件是容灾:把主用供应商和备用供应商都配置上,主用那个出现问题了,备用自动顶上。第二件是成本优化:大部分请求走价格便宜的型号,少量复杂请求走更高端的型号,通过权重调整比例,整体效果不错,账单下降也很明显。

4.4 日志、分析与可观测性

litellm在默认配置下会把每次请求的元数据打印在控制台,包括模型名、响应耗时、token用量和花费。生产环境就不建议只看控制台了,可以配置接入数据库,比如用PostgreSQL存储日志和token统计。启动的时候加--database_url参数,或者配置里写数据库连接字符串即可。

我用下来觉得最有价值的几个日志维度是:

  • 按模型统计的token消耗趋势:可以看出来哪些模型是成本黑洞,该不该换更便宜的替代品。
  • 按虚拟密钥统计的使用量:可以看出来每个服务对模型资源的消耗是否合理。
  • 失败请求的错误分布:能快速发现某个供应商在这个时间段内是不是在频繁限流或者报错。
  • 请求延迟的P50/P95:模型接口延迟波动大的时候,通过这个指标判断该不该加缓存或者引入异步处理。

有一次我们排查线上问题,发现某个模型接口的P50延迟正常,但P95异常高,导致部分用户的体验明显变差。后来通过日志定位到是供应商那边高峰期的流式输出变慢,我们给这个模型加了一个备用上游,问题就缓解了。如果没有网关这一层的日志,这种问题排查起来要麻烦得多。

5. 进阶玩法:模型映射、成本控制策略和多端集成

5.1 模型别名与统一命名规范

前面提到model_name可以自己起别名,这个能力在处理模型升级切换的时候非常有用。比如你现在对外提供的模型名是chat-assistant,业务代码全部用的这个名字;某一天你想把它从模型A换成模型B,你只需要修改config.yaml里chat-assistant指向的litellm_params.model,重启网关,业务代码一行都不用动。这是一次真正的"改配置就能切模型"。

我建议团队内部在命名上做一点规划,以用途命名模型而不是以具体型号命名。比如用chat-fast(实时场景用快速模型)、chat-powerful(复杂推理用高端模型)、embedding-default(默认向量化模型)、vision-default(图片理解模型)。这样业务侧不用关心底层是什么型号,后续供应商怎么换、型号怎么升级,对业务代码来说都是透明的。

5.2 成本控制策略:什么时候手动切换模型

litellm有一个有意思的字段叫max_input_tokens和max_output_tokens,分别限制输入和输出的token上限。配置之后,超过上限的请求会被拦截,从源头避免意外的巨额账单。之前发生过一次事故,某个prompt注入的请求把几万字的内容全都塞进了上下文,导致那一次请求的输入token费用暴涨。这个设置虽然挡不住所有费用异常,但至少能设置一个明确的顶。

如果你还想更精细一点,litellm支持配置custom_auth回调来实现自定义的鉴权逻辑。比如有一个需求是:不同用户角色只能使用不同的模型池,普通用户只能调用便宜的模型,VIP用户才能用更高阶的模型。这个在litellm里可以写一个Python文件,在请求进来的时候根据用户ID查一下权限,然后在litellm_params里动态指定可以使用的模型。文档对这个功能的介绍比较简略,但是方案确实是可行的。

有一点要提醒大家:动态指定模型不等于绕过模型权限配置。litellm的模型权限控制是通过model_group和allowed_model_group实现的,如果你要严格控制"谁只能用哪组模型",建议先把配置层的关系理清楚,再考虑自定义回调的方案。

5.3 在LangChain和LlamaIndex生态里使用litellm

如果你的RAG应用或Agent应用是基于LangChain或LlamaIndex的,litellm有两种集成方式。第一种是把litellm作为底层调用库,因为LangChain的ChatOpenAI类可以接收openai_api_base参数,直接指向litellm网关,然后传一个虚拟密钥进去,这样你的RAG链条就串到了litellm上:

from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="my-gpt-4o", openai_api_base="http://localhost:4000/v1", openai_api_key="sk-virtual-key-xxx", )

第二种方式是直接用litellm.get_llm_class()或对接到LangChain的自定义llm类。官方文档推荐方式不一定是最简单的,实际项目中我都是直接用第一种方式,效果非常稳定。LlamaIndex那边也有类似的通用方式,在OpenAI类初始化时传入api_base即可。

5.4 多实例部署时要注意的状态存储

litellm默认会把虚拟密钥、预算信息、日志等信息放在内存里或者本地SQLite里,这在单实例部署的时候没问题。但如果你把网关部署成多个副本,放在负载均衡后面,就需要注意了:虚拟密钥信息是存在某个实例内存里的,请求被调度到另一个实例的时候,密钥可能就不认了。

好在litellm支持把状态存储放到外部依赖上,比如Redis和PostgreSQL。用--redis_host参数可以接Redis,用于速率限制和预算计数器;用--database_url参数接PostgreSQL,用于密钥信息和日志持久化。生产环境如果你不是只跑单实例,这个绝对是必做的准备,不然某个实例重启、网络抖动导致请求落到别的实例上,体验就是时好时坏。

配置示例:

python -m litellm --config config.yaml --port 4000 \ --database_url postgresql://user:pass@dbhost:5432/litellm \ --redis_host redis-host --redis_port 6379 --redis_password xxx

这样你和/key/generate创建出来的虚拟密钥会写入PostgreSQL,预算计数会写入Redis,所有实例共享同一份状态。

6. 踩坑记录:用litellm过程中遇到的5个真实问题

6.1 超时设置比你想的更影响体验

litellm默认的请求超时时间有时候不太够用,尤其是一些推理很慢的开源模型。如果业务侧在发起调用时没有设置超时参数,网关转发给上游的时候也可能因为上游响应太慢而触发超时。我的经验是,在litellm_params里显式配置timeout,比如:

litellm_params: model: openai/llama3.1:70b api_base: http://192.168.1.10:8000 timeout: 300

虽然绝大多数供应商的响应速度在几十秒以内,但遇到那种复杂生成场景,你如果没配置好超时,用户侧就会看到"连接中断"一类的错误,体验非常差。

6.2 流式输出和max_tokens的交互陷阱

有一次我发现某条业务线使用流式输出时,输出被截断了一半。查了半天,发现是供应商那边有个默认的max_tokens值,而litellm的配置里没有对max_tokens做显式控制。流式输出模式下,如果不把max_tokens设置足够大,输出刚超过服务端默认值就被掐断了,客户端这边还不知道,看到的是一个正常的流结束标志。

后来我养成了一个习惯:在接入任何一个新模型前,先手动测试一次长文本的流式输出,确认输出长度确实是符合预期的。另外对所有模型统一配置max_output_tokens,并把这个值和业务侧期望的最大输出长度对齐。

6.3 上下文窗口溢出问题

context_window这个参数控制的是模型上下文窗口的大小,但它和供应商侧限定的上下文长度不完全是一回事。litellm会以这个配置去限制输入token,如果你设置得比供应商的实际值大,请求打到供应商那边依然会被拒绝;如果设置得比实际值小,那么你的合法请求也进不去。经验做法是设置成供应商官方文档标注的窗口大小,并且比那个值略小一点,给输出预留一点空间。

6.4 虚拟密钥权限最小化

有些人创建虚拟密钥时图省事,把所有模型都挂在一个密钥下。其实litellm创建密钥的时候可以指定这个密钥只能访问哪些模型,我强烈建议按照服务的最小需求来分配。比如只有客服场景才用高成本的模型,那客服服务的密钥就只给那个模型授权,其他服务就算拿到了这个密钥也用不了它不认识的模型。权限控制这种结构性的问题,越早做越好,等成本异常再回头治理就很被动了。

6.5 什么时候你其实不需要litellm

litellm确实很强大,但如果你只是一直在用某个单一固定供应商的固定模型,代码里也没有多供应商切换的需求,那引入litellm就是给自己增加不必要的运维负担。网关层的存在会带来一次额外的网络跳转,虽然延迟开销通常很小,毕竟多一跳就是多一次不确定性。没有一个工具是万能的,litellm最适合的场景是多模型、多供应商、多业务方共用一套基础设施的情况。单一模型的场景里,它反而是多余的那一层。

7. 我在实际落地中的几点体会

最后想聊几句这段时间用下来的个人感受。litellm最好的地方不仅仅是省了适配代码,而是它改变了团队协作的方式。以前接入一个新模型,要拉上开发、运维一起忙活半天,现在只需要维护配置的人把config.yaml改一下,再新建一个虚拟密钥,业务侧就能立刻用了。模型供应商的升级、替换、故障转移,这些事情从业务开发的工作清单里消失了,变成基础设施层面的日常运维。

如果你现在正处在"每个模型一套SDK、每次接入都重写适配层"的状态,我建议你先花半个下午把litellm的代理模式跑通,再把一个非核心业务线接上去试试。你会发现,原来那些最烦人的接口差异问题,真的可以在一层网关里被解决掉。

RELATED READING

延伸阅读

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