ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

LLM网关litellm实战:统一模型接口、路由与生产部署

LLM网关litellm实战:统一模型接口、路由与生产部署

1. 喂到嘴边的LLM网关:litellm到底是什么

如果你最近在折腾大模型应用,肯定绕不开一个名字:litellm。我第一次见这玩意儿的时候,还以为是个“轻量级学习模型”之类的轮子,结果点进去一看,好家伙,这哪儿是模型,这是个纯纯的“网关”。

用大白话说,litellm就是一个统一API接口层。你只需要按它规定的格式写一份请求,它能帮你转发到OpenAI、Anthropic、Google Gemini、Azure OpenAI、本地Ollama、甚至各种开源模型的推理服务上。你不需要关心后端接的是哪家的模型,也不需要为每个厂商单独写一套调用代码。上层的应用只管调litellm,litellm负责跟底下那些五花八门的模型服务打交道。

这套东西解决什么问题?最直接的痛点就俩:一个是切换模型的成本,另一个是管理多个模型的开销。你想象一下,你公司里做了一款AI客服,试了好几个模型——GPT-4o、Claude、还有国产的开源模型,结果你每换一个模型就得改一遍代码,还得重新处理token计费、限流、超时这些破事。用litellm之后,这些全被收拢到一个入口里,配置一变,模型随便换。

适合谁看这篇文?如果你是做后端开发、AI应用集成、或者在公司里搭所谓“AI中台”的人,litellm几乎是必看的。即使你只是自己写点小工具,想在各种模型之间横跳对比效果,litellm也能帮你省掉至少一半的胶水代码。我下面会从配置、部署、依赖、权限、监控、排错这几个维度,把我在实际项目里用litellm踩过的坑和摸出来的路子,一次性讲清楚。

2. 为什么需要一个统一入口:架构层面看litellm的价值

2.1 没有网关的时候,你过得有多狼狈

先说一个我真实的经历。早先我维护一个内部的知识库问答系统,一开始只接了OpenAI的接口。代码里到处都是openai.ChatCompletion.create这种调用,参数也硬编码在多个服务模块里。后来某天老板说,要对比一下另一个闭源模型在垂直领域的效果,我一看就头大:新模型的SDK、鉴权方式、消息结构全都不一样,我得把调用层全部重写一遍。

这还只是开始。对比来对比去,最后发现两个模型各有千秋,干脆都在生产环境上跑:一个处理高并发简单问答,一个处理复杂推理。这下好了,代码里得写一堆路由逻辑,还要自己统计两边各花了多少钱。后来第三个、第四个模型陆续接进来,我就彻底投降了。那个阶段我每天干的最多的事,就是在不同的API文档之间跳来跳去。

当时我就想,如果有个“模型代理层”就好了——上层的请求格式统一,至于请求发给谁、参数怎么转换、怎么计费,全在这一层解决。本质上,这就是一个分布式系统里最经典的“网关模式”。litellm不是第一个做这件事的,但在大模型这个领域,它把这件事做得特别彻底,而且面向开发者的体验做得足够好。

2.2 litellm的设计哲学:标准化写一次,路由随便切

litellm核心的抽象思路就是:把“模型名”当作路由关键字。你调litellm时传一个model参数,比如gpt-4o或者claude-3-5-sonnet,litellm内部会从配置里查这个模型名对应哪个供应商、用哪个API key、有什么特殊参数,然后帮你完成协议转换。

这个设计妙在什么地方?它把“逻辑”和“配置”彻底分离了。你在代码里只需要写一次标准请求:

from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好,介绍一下你自己"}] )

如果开发环境要用开源模型,你把model换成ollama/llama3.1,接口完全不变;生产环境要切回GPT,你改个参数就行。放在公司级别的应用里,你甚至可以让非技术人员通过配置文件来控制模型路由,开发完全不用动。

我经常拿它类比DNS——你访问一个域名,DNS帮你解析到背后的服务器IP,服务器换了IP你也不感知。litellm就是模型界的DNS,帮你把请求解析到模型服务的真实地址上。

2.3 什么时候不建议上litellm

虽然我说了这么多好处,但litellm不是银弹。如果你只是写个一次性脚本,只调一个模型,一个月跑不了几次,那直接用官方SDK反而更轻。引入litellm意味着多一层依赖、多一个进程要维护,相当于你为了一瓶水专门修了一条自来水管道。

另外,如果你的场景特别依赖某个厂商的独家特性,比如Anthropic的prompt caching特殊用法、OpenAI的某个新出的结构化输出参数,而且这些特性你在可预见的未来也不会换模型,那直接用官方SDK也许更快。litellm虽然一直在跟进各家新特性,但毕竟有一层抽象,新特性的支持总是有滞后。

我的建议是,当你的项目里出现“第二个模型请求”的一瞬间,就应该开始考虑litellm。它能让你在模型选型阶段快速试错,这在当下模型迭代速度飞快的环境里,价值怎么强调都不过分。

3. 从零部署一套可用的litellm代理服务

3.1 两种使用姿势:内置SDK还是独立代理

litellm有两种玩法,很多人一开始搞混。

第一种是在你的Python进程里直接import litellm,把它当SDK用。这种方式适合单机脚本、小型服务,你不需要额外启动任何进程,直接通过函数调用发请求。

第二种是跑一个独立的代理服务—— litellm本身是一个基于FastAPI构建的服务端,你启动它之后,它会暴露一个OpenAI兼容的HTTP接口。所有应用通过HTTP请求打到这个服务,由这个服务再转发到上游模型厂商。

我个人推荐,只要不是一次性脚本,都走第二种方式。原因很简单:独立代理可以把API key集中管理,应用层不接触任何上游密钥。你在公司里也更容易做权限控制和审计。而且litellm代理本身提供了key管理、预算控制、请求日志等一堆运维能力,这些是SDK模式给不了你的。

在正式环境里,我通常把litellm代理部署在一台单独的实例上,或者跟你的业务服务放在同一个Kubernetes集群里作为独立Deployment。资源占用不高——主要是IO密集,默认配置下几百MB内存足够跑了。

3.2 快速启动:一份配置文件拉起服务

litellm的启动入口是配置文件,默认用YAML格式。我见过很多人在这一步就卡住,其实逻辑非常简单:定义哪些模型可以被访问、各自的供应商和密钥、以及一些路由策略。

下面是我常用的一个最小化配置:

model_list: - model_name: gpt-4o-mini litellm_params: model: openai/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: llama3.1 litellm_params: model: ollama/llama3.1 api_base: http://localhost:11434

这里有几个关键点:

第一个,model_name是这个模型在litellm路由里的“别名”。你可以在业务代码里写gpt-4o-mini,但实际路由到的是openai/gpt-4o-mini。如果你想用同一个别名做灰度——比如model_name叫main-llm,后端今天指向gpt-4o,明天指向claude,通过改配置就行,业务代码一字不用动。这就是我前面说的配置驱动的核心价值。

第二个,model字段里的provider前缀,比如openai/、anthropic/、ollama/,是litellm识别用哪个适配器去转换协议的标记。这里必须写对,否则litellm不知道该怎么解析。

第三个,api_key可以直接从环境变量读取:os.environ/XXX。这个写法在生产环境非常有用,避免把密钥写死在配置文件里提交到代码仓库。

配置文件写好后,一条命令就能把代理服务拉起来:

litellm --config config.yaml --port 4000

如果要部署到生产环境,我还是建议用Docker。litellm官方镜像里预装了所有依赖,直接用就行。我自己生产环境里用的docker-compose通常是这样的:

version: "3.8" services: litellm: image: ghcr.io/berriai/litellm:main-latest ports: - "4000:4000" volumes: - ./config.yaml:/app/config.yaml environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} command: ["--config", "/app/config.yaml", "--port", "4000"]

启动之后,你可以用任意一个OpenAI SDK来请求它:

from openai import OpenAI client = OpenAI( api_key="sk-anything", # litellm代理的key,稍后讲 base_url="http://localhost:4000/v1" ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "hello"}] )

你没看错,用官方OpenAI SDK就能直接访问,因为litellm的接口完全兼容OpenAI格式。这句话潜台词极大:你项目里几万行基于OpenAI SDK的代码,换到litellm之后只需要改一个base_url,其余全不动。

3.3 上游模型的特殊参数配置

不同的模型供应商有各自的特殊参数。比如Azure OpenAI需要api_base和api_version,Bedrock需要aws的密钥对,Vertex AI需要GCP的服务账号。这些参数统一放在litellm_params里。

举一个例子,如果你接Azure版的GPT:

model_list: - model_name: azure-gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_OPENAI_API_KEY api_base: https://your-resource.openai.azure.com/ api_version: "2024-02-15-preview"

接AWS Bedrock的话,参数更特殊一些:

- model_name: bedrock-claude litellm_params: model: bedrock/anthropic.claude-3-5-sonnet-v2 aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY aws_region_name: us-east-1

这里我想强调一点,litellm对各家provider的参数命名比较统一,基本上就是“跟官方SDK保持一致”。所以当你需要接一个新供应商时,最快的办法是去看官方文档里这个供应商的SDK初始化需要什么参数,然后照搬进litellm_params即可。

4. 代理背后的关键机制:路由、密钥与预算控制

4.1 虚拟密钥:把供应商密钥藏起来

litellm代理有一个对我来说极为实用的功能:虚拟密钥管理。意思是你不用把OpenAI的sk-xxx发给业务服务,你可以通过管理接口生成一把只属于某个业务的虚拟key。这把key在litellm内部跟真实的供应商key做映射。

流程是这样的:

curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o-mini"], "max_budget": 10 }'

响应里会返回一把sk-开头的虚拟key。你把这把key给下游应用用,预算上限是10美元,只能访问gpt-4o-mini。如果某个业务方超支了或者被攻击了,直接吊销这把key就行,完全不影响其他业务,也不需要去厂商后台做任何操作。

这个能力在多人协作、多业务线共享模型资源的场景下简直是救命的。我之前在一家公司里,三个业务线共用一个OpenAI账号,每个月账单出来都不知道该让谁掏钱。上了litellm之后,每条业务线一把key,各花各的钱,报表也分得明明白白。

4.2 多模型路由策略:loadbalancing和fallback

litellm天然支持两种路由策略:负载均衡和故障转移。

负载均衡的场景是:你在OpenAI上配了多个key,想分散请求量避免触发单key的速率限制。配置里写这样一段:

model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2

两个一模一样的model_name,litellm会自动在它们之间做轮询均衡。我实测下来,高并发场景下确实能明显减少429限流错误。

故障转移则更贴近真实业务的容错需求。比如主模型挂了,自动降级到备用模型:

router_settings: fallbacks: [ {"gpt-4o": ["claude-3-5-sonnet"]} ]

当gpt-4o连续报错超过阈值,litellm会把请求自动转发给claude-3-5-sonnet。这个能力在线上服务里非常实用——AI模型服务的稳定性再怎么吹,也没人敢保证不抽风,而你的业务不能因为上游抖动就一起挂了。

4.3 预算控制:配置里的事前限制

预算控制是我觉得litellm做得最扎实的功能之一。它支持两种级别:一种是在虚拟key上做预算,一种是在global配置里做整体预算。

我常用的配置如下:

general_settings: master_key: sk-1234 database_url: postgresql://user:pass@localhost:5432/litellm max_budget: 1000

这里涉及一个需要重点解释的点:litellm要持久化记录请求日志和用量统计,必须配一个数据库。默认用SQLite就可以跑,但生产环境强烈建议上PostgreSQL。数据库里存的是每一次请求的模型、token数、耗时、延迟,它会基于这些数据实时累计预算。

我之前遇到过一个坑:刚开始没配数据库,用SQLite跑了几天,一切正常。后来请求量一上来,SQLite的写锁导致大量请求卡顿,最夸张的时候代理服务完全没响应。换到PostgreSQL之后这个问题彻底消失。所以如果你打算正经使用litellm代理,一开始就配好PostgreSQL,不要走弯路。

5. 从开发到上线的完整实操记录

5.1 本地联调阶段:一次真实接入案例

下面这段是我在一台测试机上的完整操作记录,供你对照参考。

第一步,安装litellm:

pip install 'litellm[proxy]'

注意这里的[proxy]额外依赖,它是专门给代理模式用的,包含了FastAPI相关的所有包。如果你只是用SDK模式,pip install litellm就够。

第二步,准备配置文件。因为我本地还想顺带测一下开源模型,所以我配置了Ollama作为额外的上游,让业务一个入口能同时访问闭源和开源模型。配置里加一段:

- model_name: local-llama3 litellm_params: model: ollama/llama3.1 api_base: http://localhost:11434

第三步,启动代理:

litellm --config config.yaml --port 4000

看到类似Uvicorn running on http://0.0.0.0:4000的输出就说明起来了。

第四步,用一个简单的Python脚本验证:

import openai client = openai.OpenAI( api_key="sk-1234", base_url="http://localhost:4000/v1" ) for model in ["gpt-4o-mini", "local-llama3"]: resp = client.chat.completions.create( model=model, messages=[{"role": "user", "content": "1+1=?"}] ) print(model, resp.choices[0].message.content)

模型名传的是配置里的model_name,不是upstream真实名。litellm会通过model_name找到对应的供应商并转发请求。

5.2 生产环境部署:代理与服务分离

生产环境里,我的做法是让业务服务跟litellm部署在同一个网络里,业务服务通过内网地址访问litellm代理。这样上游密钥完全不会暴露到公网。

我还会在litellm前面再挂一层网关做基础的IP白名单。虽然litellm自己有虚拟key鉴权,但多一层防护总归更稳妥。

一个比较关键的经验:litellm实例最好单独部署,不要跟高CPU消耗的业务服务混部在同一个Pod里。因为litellm转发大模型请求时会涉及大量的流式响应处理,如果业务服务本身CPU飙高,会拖累代理的响应速度,上游模型本来1秒的响应可能变成3秒。

5.3 流式输出的关键配置

大模型应用的响应普遍是流式的,litellm对stream参数的穿透做得很好。你在业务代码里这么写:

resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "写一段长文"}], stream=True ) for chunk in resp: print(chunk.choices[0].delta.content or "", end="")

litellm会把上游的流式数据实时转发给下游,用户感知不到中间隔了一个代理。这块它兼容得相当不错,我试过从OpenAI直接切换到litellm,前端代码完全不用改。

需要注意的一点是,有些上游模型供应商对流式请求的响应格式跟OpenAI不完全一致,比如Anthropic的流式返回就是SSE事件流,格式完全两样。litellm在内部把它们全部转换成OpenAI格式后输出。但有些特殊的字段,比如usage信息,某些供应商在流式结束后的最后一个chunk里才返回,这会影响你对token统计的精确性。解决方案是让litellm写回数据库的请求日志作为计费依据,而不是依赖流式响应里的usage字段。

6. 权限模型与多业务隔离的经验

6.1 团队里如何分配权限

如果你所在团队有多个人在对接模型,权限这块最好一开始就规划好。

litellm管理后台是自带的,启动代理后访问http://localhost:4000/ui,用master_key登录就能看到管理页面。在这里可以创建用户、创建虚拟key、设置每个key能访问的模型、设置预算上限。

以我实践过的最佳实践来说:

  • 每个业务线创建一个独立的“团队”,团队下再建key。
  • 每个key绑定单一的模型集合,最小权限原则。
  • 开发环境的key预算给得宽松一些,生产环境的key预算给得紧一些,并把通知告警打开。

如果业务方的需求只是“我要能调GPT”,他们不需要也不应该看到任何其他业务的key信息。通过团队隔离,litellm能让每个业务只能看到自己的用量和key。

6.2 多租户的模型路由

多租户场景下,你可能会遇到一个需求:不同客户要用不同的模型。比如某些客户对成本敏感,只能用便宜的小模型;某些客户做专业领域的问答,必须用大模型。

litellm的team配置可以绑定模型列表。你在创建team时指定这个team能访问的model_name,team下所有的key都继承这个限制。请求到达litellm时,它会先校验这把key属于哪个team,再校验请求的model是否在team允许的列表里。

这里我踩过一个坑:一开始没做team隔离,只给key绑了模型。后来发现同一个key被多个业务公用了,一个业务修改了模型的prompt格式,导致另一个业务的请求出错。后来改成team隔离,每个业务用独立team,问题迎刃而解。建议你在建key之前先建好team和模型绑定关系,不然后期调整会很痛苦。

7. 监控与可观测性:别等线上炸了才想起来

7.1 请求日志与用量报表

litellm会把所有请求的元数据记录到数据库里,包括:

  • 请求时间、模型名、token数
  • 上下游的延迟
  • 缓存命中情况
  • 请求里的用户标识

在管理后台里,你可以按维度筛选这些数据。比如查看某个team今天用了多少个token,某个key的平均响应时间是多少。对于需要给业务方出成本账单的场景,这基本是零成本的方案——数据已经在那里了。

不过日志默认是全部记录的。如果请求量大,数据库会膨胀得很快。我建议定期清理或归档历史数据,或者在配置里关闭对健康检查等非业务请求的日志记录。这一块litellm本身不做自动的滚动清理,需要你写个定期任务。

7.2 接入Prometheus与Grafana

litellm暴露了Prometheus指标端点/metrics。默认会输出以下核心指标:

  • litellm_total_requests_total:总请求数
  • litellm_total_tokens_total:总token数
  • litellm_remaining_requests_quota:剩余请求额度
  • litellm_latency_per_request:请求延迟分布

我用Prometheus配合Grafana搭过一个简单的监控面板,重点关注延迟和限流次数。有一次某个模型的上游稳定性下降,之前完全靠用户投诉才知道。接上监控之后,一个面板就能看到延迟突增,提前做好降级策略。

接入方法很简单,在docker-compose里加一个prometheus和一个grafana服务,把litellm的/metrics端点配置为采集目标就行。网上这类现成的compose模板很多,根据自己的业务稍微改一改就能用。

7.3 告警设置做兜底

监控光看不行,还得能告警。我设过两种告警:

  • 模型错误率超过5%时告警,通常意味着上游挂了或者API key被限额了。
  • 某team预算达到80%的时候提前通知,避免月底账单突然爆炸。

如果你不想折腾Prometheus告警规则,litellm本身也支持Webhook通知。在config配置里指定一个回调URL,当某个key超预算或模型持续失败时,litellm会往你的企业微信、钉钉或者自研的告警平台推消息。我建议生产环境两套都上,不嫌多。

8. 常见问题与排查技巧实录

8.1 模型名对应关系搞错

最常见的错误是配置文件里model字段的provider前缀写错,比如ollama的模型写成了openai/llama3.1,litellm会拿着OpenAI的协议去请求一个OpenAI不认的模型名,返回的直接就是404或400。

排查方法很直接:先在不经过litellm的情况下,直接用官方SDK请求上游,确认上游本身是通的。然后用litellm的SDK模式请求一次,加上debug参数:

import litellm litellm.set_verbose(True) response = litellm.completion( model="ollama/llama3.1", messages=[{"role": "user", "content": "ping"}] )

verbose会打印litellm内部拼接的完整上游请求URL和请求体,一眼就能看出问题在哪。

8.2 429限流与重试策略

litellm默认自带重试功能,遇到429、超时、5xx错误会进行指数退避重试。默认最大重试次数是2次,但我建议调大一点,尤其是面对限流频繁的小模型:

litellm.num_retries = 5 litellm.request_timeout = 30

注意,重试只对幂等请求安全。如果你的应用使用stream模式,并且用户已经看到了部分内容,重试可能导致用户看到重复的片段。更稳妥的做法是把重试集中在非流式请求上,或者在流式请求里依赖litellm的fallback机制切换到别的模型,而不是重试同一个可能已经写了半截的响应。

8.3 时延变高:检查落库的开销

有一次我发现所有请求都变慢了,不是上游的问题,而是litellm把每次请求的日志写入PostgreSQL时,数据库出现锁竞争。那时候请求量刚好到了一个阈值,单库已经扛不住频繁的insert。

后来我把数据库升级成大规格实例,同时把日志的异步写模式打开。litellm内部支持将日志异步批量写入数据库,配置里加一句:

general_settings: async_mode: true

开启之后,请求响应不再等待数据库写入完成,时延立刻回到正常水平。代价是极端情况下日志可能丢失几条,但这跟用户体验相比完全可以接受。

8.4 请求上下文长度超限

prompt太长触发上限是最常见的模型侧报错。litellm可以在中间层拦截并处理,但默认不会自动截断——因为自动截断很可能影响语义。

我的处理方式是:在业务代码里按模型预设的token上限做分段或摘要。比如统一限制每条prompt不超过6000 token。litellm也有一个max_tokens参数可以限制输出长度,但这个只是生成侧的限制,不是上下文的总限制。你要结合上游模型的context window做整体规划。

分享一个实用技巧:litellm支持按模型设置max_input_tokens参数。如果模型context window是128k,你可以在litellm_params里限制最大输入token为100k,超出直接返回错误,避免打到上游后才发现超限,白白浪费一次上游调用费用。

8.5 虚拟key鉴权失败

如果应用调用返回401,但key明明存在,大概率是key被限定了模型范围。你在生成key时如果只给了某个模型的权限,使用另一个模型访问就会401。

还有一个隐蔽的坑:如果litellm配置了数据库,而数据库表里没有初始化对应的key信息,也会导致鉴权失败。遇到这种情况,先去管理后台确认key的状态,再检查数据库连接是否正常。

9. 一点延伸:和LangChain等框架怎么配合

现在很多团队用LangChain、LlamaIndex这类编排框架来写Agent应用。这些框架默认都支持自定义base_url,因此接litellm非常自然。

比如LangChain里原来的OpenAI调用,设置如下即可切换到litellm代理:

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

框架层面的所有链式调用不受影响。Agent里的工具调用、Function Calling这些,litellm也支持传递tools参数,能和OpenAI的tool calling格式对齐。

我个人的使用习惯是:编排框架负责业务逻辑,litellm负责模型路由和成本控制。两者解耦之后,如果新的模型框架出来了,跟litellm配合往往是改配置就能接入,而不用重写任何业务代码。这是我这段时间用得最舒心的一点。

10. 个人经验:这几件事,越早知道越省心

最后聊几句实在的。

第一,litellm的升级频率挺快,新模型一出,它往往几天内就更新支持。但这也意味着API接口可能有不稳定变化。我的经验是锁定你常用的一个大版本,升级前先在测试环境跑一遍关键用例再上生产。别追最新版追得太猛,稳定大于一切。

第二,最关键密钥一定别写进配置文件仓库。使用环境变量、云厂商的密钥管理服务都行。我见过不止一个团队的API key因为写死在配置里被提交到GitHub导致被刷爆账单的事故。litellm的os.environ/XXX写法就是为这个设计的,别嫌麻烦,用起来。

第三,流式请求的故障排查比普通请求难很多。因为错误的返回可能是在流中间发出的,客户端只能拿到半个响应后断开。真遇到这种问题,先在非流式模式下复现,把错误信息看全了,再切回流式模式验证修复结果。这是我把头挠秃之后总结出来的最有效的排错思路。

litellm这个项目的确把大模型接入这件“脏活累活”做得足够优雅。它不激进,不革新,就是把网关该干的事全部干到位了。在模型还在快速迭代的今天,给自己留一条随时切换的退路,这可能是你在AI应用架构里能做的最稳健的选择之一。

RELATED READING

延伸阅读

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