litellm这个词,第一次看到的时候我以为又是一个包装精致的OpenAI封装库。真正让我决定把它留在生产环境里的,是我在一周内第三次为“切换模型渠道”这件事加班到深夜之后。场景很简单:项目里接了多家大模型API,有国内有国外,有的按token计费,有的按并发包月,Key散在各处,测试环境用一套,线上又有一套。每次要测一个新模型,都得重新写一版调用逻辑;每次账单出来,都得人工去对是哪条业务线烧的钱。我意识到缺的不是又一个SDK,而是一个能统一入口、统一鉴权、统一控制的网关层。litellm就是干这个的。
如果你手头也在维护两个以上的模型渠道,或者你想在团队里把“谁在用什么模型、花了多少钱”这件事管起来,这篇文章应该能帮你省掉不少试错时间。我会从选型逻辑、部署配置、核心API的用法讲到实际踩过的坑,尽量按照我自己跑通一个完整链路的顺序来写。
1. 为什么我最后选定litellm当AI网关
1.1 网关到底解决了什么问题
先说痛点。假设你的业务里现在接了三个模型来源:一个国内的厂商、一个国外的头部厂商、还有一个开源自托管的服务。三者API风格完全不一样:有的兼容OpenAI格式,有的要走自己的签名算法,有的只提供流式接口。你的业务代码如果直接调用这三家,那就意味着你要写三套适配层、三套鉴权逻辑、三套错误重试。更麻烦的是,当你想做模型灰度切换时,业务代码里到处都是写死的模型名,改一次要动好几个服务。
网关层的做法是:业务方只面对一个统一入口,这个入口长得跟OpenAI的接口一模一样,你传一个model参数,网关自己决定请求转发给谁。litellm就是这样一个代理服务,它把几百家模型提供商的接口差异抹平,对上游业务暴露一个标准的兼容层。
另一个容易被忽略的价值是密钥收敛。公司的API Key如果散落在各个业务代码和环境变量里,泄露面会越来越大。统一走网关之后,厂商侧的Key只存在网关的环境变量或配置中心里,业务侧拿到的都是网关签发的临时Key,泄露一个可以单独吊销,不影响其他业务。
1.2 为什么不是直接用开源SDK而是部署服务
在选定litellm之前,我其实先试用过它提供的Python SDK,也就是在业务代码里直接调用litellm.completion。这个方式对单机脚本和原型验证非常友好,几行代码就能从OpenAI切到别的provider。但它解决不了我前面说的密钥管控和按业务线计量问题,因为Key还是在业务进程里。
litellm的服务模式(也就是LiteLLM Proxy Server)给了我一个独立的网关进程,业务代码只跟这个进程对话。这个进程负责跟上游厂商通信、管理虚拟Key、记录每一次调用的token数和花费、执行速率限制。这样网关本身的部署、扩容、监控都跟业务服务解耦。如果你的团队有多条业务线共用模型资源,这种集中式管控基本是刚需。
我最终选择它的理由可以概括成几条:一,接口兼容OpenAI格式,业务代码几乎不用改;二,同一个模型名可以配置多个上游渠道,天然支持fallback;三,虚拟Key的预算和速率限制在管理层面上够用;四,部署形态灵活,本地一个进程就能跑,也可以挂到Docker里配合监控。当然它也不是没有缺点,后面我会单独讲。
1.3 部署方式选型:本地进程与容器化
litellm的部署方式有两个主流选择:直接用pip安装后启动一个Python进程,或者用Docker镜像跑容器。我的建议是,如果你只是在本机验证功能,pip就够了,一条命令的事;如果你要放到测试环境甚至生产环境,一定要走容器。
原因很简单:这个服务本质上是Python进程,依赖一堆Python包,直接装在你自己的开发机或者服务器上,很容易出现依赖冲突。我们遇到过因为本机Python版本和litellm依赖的pydantic版本不对,导致服务起不来的情况。容器化之后这些乱七八糟的依赖关系都被隔离了,日志和配置也更好管理。
我当时用的是官方Docker镜像,挂载一个config.yaml和一个存放虚拟Key的数据库文件,一条docker run命令就完成了网关搭建。数据库默认是本地SQLite,适合小规模验证;如果你有好几个网关实例需要共享Key数据,可以切换到PostgreSQL。这点往下会细说。
2. 搭建网关之前必须想清楚的几个配置
2.1 config.yaml的基本结构
litellm的网关配置核心是一个YAML文件,这个文件在启动时决定了两件事:你能用哪些模型,以及访问它们的密钥怎么拿。我见过很多人上来就填一堆模型,结果启动时报401,浪费很多时间。问题的根源是没搞清楚这个文件的字段含义。
一个最小的config.yaml长这样:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o 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这里面最关键的字段是litellm_params.model,它决定请求要转发给哪个provider。这个字段的格式是“provider/具体模型名”,比如openai/gpt-4o、anthropic/claude-3-5-sonnet、azure/xxx。路由层看到这个前缀就知道该走哪家的SDK。
model_name是暴露给业务方的名字,业务请求里传的model参数必须跟这里的model_name完全一致。你可以自己起名,比如统一叫prod-main-llm,但要注意:如果这个名字跟litellm_params.model不一样,日志里排查问题时很容易把两个名字搞混。建议从一开始就约定清楚。
2.2 环境变量与密钥管理
api_key: os.environ/OPENAI_API_KEY这种写法非常值得推荐。它表示密钥从环境变量读取,而不是直接写进yaml。有人图省事直接把Key明文写进配置文件,这在本地实验时可以接受,一旦配置文件被提交到git仓库,就是一次事故。我的习惯是:config.yaml只写环境变量的引用,实际密钥放在网关启动时的环境变量里。
如果你用的是Docker方式,可以通过-e OPENAI_API_KEY=xxx传进去,或者更规范一点,用--env-file指定一个环境变量文件。注意不是所有provider的密钥都叫api_key,比如Azure OpenAI用的是api_key加api_base,还有的provider需要传aws_access_key_id这类额外字段。这些认准litellm_params里对应字段就好。
还有一个容易忽略的是master_key。这是网关自己的管理员密钥,第一次启动时如果你没设置,litellm会让你在日志里找临时生成的Key,然后再手动更新。这个Key是给运维人员用的,不要直接发给业务同事,后面给业务方签发虚拟Key是另外一套流程。
2.3 数据库选型:SQLite还是PostgreSQL
网关启动后,虚拟Key、调用日志、预算流水都存在数据库里。默认情况下,litellm会使用本地SQLite文件,这对跑通功能完全够用。但它有个明显短板:如果你要做多实例部署,SQLite不支持多个进程同时写同一个文件,轻则锁库等待,重则数据错乱。
所以只要你有横向扩容的打算,我建议直接上PostgreSQL。在config.yaml里加一行:
general_settings: database_url: postgresql://user:password@host:5432/litellm切换数据库之后,你会发现一个好处:多台网关实例可以共享同一份虚拟Key和调用记录。你要是做过那种把网关跑在多个区域、前面挂负载均衡的架构,就知道这点有多重要。
数据库这个选择直接影响后面跟成本相关的统计能力,因为litellm的预算管理是依赖数据库里的调用记录来计算的,库要是锁了或者数据丢了,预算就成了一笔糊涂账。
3. 核心API的使用逻辑:从请求转发到虚拟密钥
3.1 为什么业务侧只需要OpenAI格式的接口
litellm对外暴露的接口风格和OpenAI一致,这是个刻意设计的决定。因为现在市面上绝大多数的LLM应用框架、LangChain之类的工具、还有各种开源项目,都优先支持OpenAI格式。如果你自己从零写业务代码,也可以直接用一个兼容OpenAI的客户端来请求网关,不用再单独引入litellm的SDK。
实际效果是:业务服务里原来是OpenAI(api_key="..."),现在改成指向网关地址,比如http://litellm-host:4000,Key换成网关签发的虚拟Key,代码其他部分几乎不用动。如果业务侧之前直接调用的是国内某个不兼容OpenAI服务的厂商,那么切换到litellm网关之后,你用OpenAI库就能请求那个厂商的模型,这个体验是很直观的。
当然,有些特殊能力是OpenAI格式表达不了的,比如某些厂商的图片生成、语音转写,这些litellm也有对应的/image、/audio端点,但我在实际项目里用得不多,本文就不展开了。文本生成这一块,OpenAI格式基本覆盖了。
3.2 请求路由与自动fallback机制
litellm的另一个核心玩法是同一个model_name下挂多个上游渠道,网关自动做故障转移。举个例子:
model_list: - model_name: my-llm litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: my-llm litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY业务侧始终请求my-llm,正常情况网关默认走第一条渠道。如果OpenAI那边超时或返回5xx,litellm会自动切到Claude。这个fallback功能我测试过很多次,触发逻辑是:第一个上游请求出错或超时,网关立刻把同一个请求转给第二个渠道。
有一点要提醒:fallback会把请求的“原始prompt”重新发一遍,如果你的业务侧对延迟敏感,切换那一刻的耗时可能会翻倍。最好在业务上设置比较长的超时时间给网关,让网关有足够时间完成fallback,而不是由业务先掐断连接。我踩过的坑就是在业务侧把超时设成了10秒,上游渠道又有个别情况要15秒才响应,导致fallback还没来得及执行,业务侧自己先超时了。这个调优我后面会专门讲。
3.3 虚拟Key的生成、限额与吊销
虚拟Key是litellm身份体系里user-facing的部分。业务侧不用知道上游厂商的真实Key,只需要拿着网关签发的虚拟Key来做请求。用管理员Key调一下接口就能生成:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-admin-key" \ -H "Content-Type: application/json" \ -d '{"models": ["gpt-4o"], "max_budget": 10, "expires": "2025-12-31T23:59:59Z"}'这个接口会返回一个sk-开头的Key。你可以给每条业务线单独生成一个Key,单独设置能访问哪些模型、预算上限是多少、什么时候过期。这样做有几个直接好处:一是某个服务出了问题疯狂调用模型时,最多只烧掉这个Key的预算,不会拖垮整个公司的额度;二是要下线一个服务时,直接删掉对应的Key就行,不用去上游厂商那边改设置。
在管理后端页面上,你还能看到每个Key最近的调用次数、token消耗和预估费用。这个功能在公司内部做成本分摊时特别好用,我后面会详细说怎么用这些数据。
3.4 通过router做更细粒度的负载均衡
除了网关Proxy,litellm的Python库里还有一个Router类,属于可以在业务代码中直接使用的路由组件。这种模式适合不想单独起网关服务、但又想做多provider调度的场景。
from litellm import Router router = Router( model_list=[ { "model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o", "api_key": "..."}, }, { "model_name": "gpt-4o", "litellm_params": {"model": "anthropic/claude-3-5-sonnet", "api_key": "..."}, }, ] ) response = router.completion( model="gpt-4o", messages=[{"role": "user", "content": "hello"}], )Router跟Proxy是同一套配置逻辑的不同形态。我个人的实践是:线上环境用Proxy,因为集中管控和可观测性更好;本地脚本和快速原型用Router,因为不需要额外维护一个服务。这两种方式没有谁绝对更优,取决于你的部署条件。
4. 跑通一个真实项目:配置、代码与链路排查
4.1 我选定的验证场景
为了验证litellm能不能承接真实业务,我搭了一个模拟项目X,大致功能是:接收用户的提问,先用一个模型做意图分类,再根据分类结果选择不同的生成模型做回答。这个场景很典型,因为它涉及多条模型链路的切换,而且不同链路的模型来源不一样,正好可以测试网关的模型路由能力。
整个链路规划如下:业务服务通过httpx请求litellm网关,网关配置了三个模型:一个用于分类的小模型、一个用于常规问答的主力模型、一个用于长文档处理的大上下文模型。三个模型分布在两个不同的上游厂商。我会故意在配置里把其中一个厂商的Key设为错误值,观察fallback是否生效。
4.2 关键配置与代码实现
网关侧的config.yaml这样写:
model_list: - model_name: classifier-small litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: main-generator litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: main-generator litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY业务侧Python代码用一个标准OpenAI客户端,只是改掉base_url和api_key:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="sk-litellm-virtual-key", ) # 第一步:意图分类 resp = client.chat.completions.create( model="classifier-small", messages=[{"role": "user", "content": user_input}], ) # 根据分类结果选择生成链路 if "long_doc" in resp.choices[0].message.content: model_name = "main-long-context" else: model_name = "main-generator" # 第二步:正式生成 answer = client.chat.completions.create( model=model_name, messages=[{"role": "user", "content": user_input}], )这段代码在没有任何上游知识的前提下完成了多厂商模型的接入,业务只认squad的模型名,底层是OpenAI还是Anthropic对它完全透明。
4.3 从401到429再到超时的完整排查链路
我把这次跑通过程中实际遇到的三个问题记下来,它们非常典型。
第一个问题是401。现象:网关能启动,但请求返回401 invalid api key。排查发现不是虚拟Key的问题,而是config.yaml里环境变量的名称写错了。os.environ/OPENAI_API_KEY引用了一个不存在的环境变量,litellm启动时不会立刻报错,直到真正转发请求时才发现没有密钥。解决方法是先确认上游Provider的Key确实能通,再确认环境变量在网关进程里可见。可以用docker exec进入容器echo $OPENAI_API_KEY验证。
第二个问题是429。现象:用官方Demo压测时,请求到某个国外厂商出现429限流。这个不全是网关的锅,是上游对单Key有并发限制。但litellm暴露了一个很关键的能力:同一个模型名配置多个上游渠道,用同一个Key的不同配额或两个不同渠道做负载均衡。我在config里为同一个model_name配了两个上游,让网关自己做轮询,瞬间把单Key的压力摊开了。
第三个问题最棘手:超时。业务端设置的是20秒超时,网关默认的上游请求超时是5分钟,按说不会出问题。但有一次某个上游渠道连续假死,TCP连接建立后迟迟不返回数据,业务侧等了20秒后主动断开,网关侧还在等上游响应,最终落了一堆pending请求。最后我在网关配置里加上了request_timeout参数,把单次上游请求的上限设为30秒。这个参数值得你一开始就配好,不要等到出事故了再想起来。
5. 稳定性治理:重试、限流与实际成本控制
5.1 重试机制的正确姿势
litellm支持在router_settings里配置重试策略。默认情况下,遇到5xx会重试一次,但这远远不够,原因有两个:第一,上游限流返回的是429,不是5xx,默认策略不会重试;第二,如果多个请求并发失败,所有请求同时重试可能再次打爆限流。
我现在的配置思路是:对429做指数退避重试,退避时间从1秒开始,最重试2次;5xx重试1次;连接类错误重试1次。重试次数不宜太多,否则一次上游故障会让网关堆积大量等待中的请求,业务端的超时反而先触发。
router_settings: retry_policy: TimeoutError: retries: 1 APIError: retries: 1 RateLimitError: retries: 2 min_retry_delay: 1.0 max_retry_delay: 60.0还有一个细节:重试是针对同一次上游请求,也就是说网关会把同一个prompt重新发给同一个provider。如果你的业务方使用流式输出,重试时可能会看到重复的token,这个需要业务侧自己做好去重,或者干脆对非幂等请求关闭重试。我见过有团队用流式接口一次真实请求被网关重试了三次,费用翻了三倍,这是重试的隐性成本。
5.2 限流在网关层的价值与代价
litellm支持在虚拟Key上设置rpm(每分钟请求数)和tpm(每分钟token数),也支持全局层面的速率限制。我之前觉得网关层限流有点多余,毕竟模型厂商自己会限制。后来发现厂商的限流是按Key维度,如果所有业务共用同一个上游Key,一个异常业务就会把其他正常业务的额度全吃光。
在网关层给每条业务线设置独立的rpm,相当于“隔离故障单元”。比如A业务配置rpm=100,B业务配置rpm=50,A业务写了个死循环疯狂调用,最多也就到100rpm,B业务完全不受影响。
不过限流也有代价:网关本身要维护令牌桶状态,如果你用多实例部署,这个状态在多个实例之间怎么同步就成了一个麻烦。官方文档建议用Redis作为分布式限流存储,我在生产环境里就是这么干的。代价是需要额外维护一个Redis,但换来的是限流数据在多实例间一致。如果只是单实例,用默认的本地内存就可以。
5.3 预算控制与成本观测
litellm的预算控制分两级:一是虚拟Key级别的max_budget,二是用户或团队级别的预算。我实际用得最多的是虚拟Key级别的预算。方法是给每条业务线一个Key,设置“单日预算上限”,比如50美元。这样即使业务方上了一个有漏洞的功能导致token消耗暴增,账单也不会失控。
预算触发的时机值得注意:litellm是在每次请求结束时计算累计消耗,如果某一次请求消耗了超预算的token,这次请求本身不会被拦截,但下一刀请求会被拒绝。所以它本质上是“事后熔断”,不是“事中拦截”。要想更精细地控制单次请求大小,需要在业务侧配合max_tokens参数,把单次请求的token上限先控制住。
成本观测方面,litellm提供了调用日志表,里面记录了每次请求的model、prompt_tokens、completion_tokens、total_cost。我后来写了个定时任务,把这张表按key和model字段汇总,每天推送到内部报表系统,团队里每个人都能看到自己负责的业务线烧了多少钱。这个机制几乎零成本,却解决了部门级成本分摊的大问题。
5.4 流式请求与日志记录的坑
流式请求是litellm的一个特殊场景。网关在透传SSE流时,日志系统要等整个流结束才能统计token数和花费,因为token数是按最终生成的增量汇总的。如果在流传输过程中网关重启,这一次调用可能只记录了一半token数。这个对成本统计影响不大,但如果你用日志数据做实时监控,要注意这个延迟。
另一个问题是:流式请求中,如果上游在某一个chunk之后断流,网关会给业务侧一个不完整的流,然后根据retry_policy决定是否重试整个请求。重试流式请求会造成业务侧出现重复段落的概率大增。我的处理方式是:关键业务流不开启自动重试,让业务侧自己判断是否重发请求,避免客户端那边出现内容拼接错误。
6. 实测检出的坑与现在的落地架构
6.1 最大的坑:model id名称混乱
第一个真正烦人的坑是:不同厂商对一个模型可能有不同叫法。比如某个开源模型在厂商A那里叫llama-3.1-8b-instruct,在厂商B那里叫llama-3.1-8b-chat。如果你在config.yaml里把两个模型都映射到同一个model_name,表面上没问题,但实际请求时,同一个提示词在两个厂商那里可能产生不一样的格式行为,尤其涉及系统提示词时。
更隐蔽的坑出现在embedding模型上。很多厂商的embedding模型名称后缀不一样,有的带-v2有的不带,如果你在业务侧写死了模型名,网关这边一换厂商就废了。建议是:在配置阶段就把每个上游渠道的“实际模型名”打印出来,人工核对一遍,别相信文档。
解决方式是维护一份内部模型对照表,统一业务侧的命名规范。我现在的config里,每个model_name都用业务语义命名,比如embedding-main、chat-default,而不是直接用厂商模型名。这样将来换厂商时,只需要改config里对应渠道的litellm_params.model,业务侧完全无感。
6.2 测试工具与压测注意点
跑通网关之后,我建议你做一轮完整的接口测试。litellm本身带了一个Swagger页面,在浏览器打开http://localhost:4000就能看到可交互的API文档,这个对快速验证特别有用。但Swagger页面上有些字段是必填的,比如messages,不填会直接报校验错误,这不是网关bug,是OpenAI接口的参数要求。
压测要注意一个问题:直连上游厂商和经由网关节点的延迟差异。网关本身只是一层转发,正常情况下增加的延迟在10毫秒以内,但如果配置了router_settings里的重试和fallback,最坏情况下的延迟可能由fallback链路的数量决定。也就是说,你压测时看到的p99延迟,很可能不是网关的转发开销,而是重试策略在极端情况下的开销。这一点在做性能评估时一定要区分开。
我还发现一个测试时容易栽的跟头:网关的虚拟Key在生成时如果设置了models白名单,而你的压测脚本用了model_list里没有的新模型名,会直接报404 model not found。这个报错在业务方看来莫名其妙,因为它觉得“明明昨天还能用”。实际上就是你改了配置但没重新生成Key,或者Key的白名单不够全。
6.3 我现在会推荐的落地架构
经过这段时间的使用,我现在给团队推荐的架构分三层:
第一层是接入层,也就是litellm网关实例,至少两个node,前面挂一个负载均衡,接受来自各业务服务的请求。第二层是数据层,用PostgreSQL统一存所有虚拟Key、调用记录和预算数据,限流状态放Redis。第三层是观测层,定时从PostgreSQL捞调用日志,汇总按Key、按模型的费用报告,再配一个简单的告警,当某个Key的消耗速度超过前一天的3倍时提醒运维。
网关实例本身不存任何业务状态,所以出故障了随时可以重启,新的实例启动后自动从PostgreSQL加载配置和Key数据。这个架构到目前为止跑得很稳,再也没有出现“换模型要改代码、对账单要翻Excel”的情况。
如果你只是单机试玩,不用上这么重的架构,一个docker run加SQLite就够了。但只要你打算引入第二条业务线、第二个上游渠道,我建议还是尽早切到PostgreSQL模式,后面迁移成本会小很多。
我在实际使用中最有感触的一点是:litellm最大的价值不是把多家API统一成一套接口,而是把“谁在用模型、用了多少、花了多少钱”这件事变成了可管理、可审计、可控制的正常运维对象。如果你也到了被模型渠道和账单反复折磨的阶段,值得花半天时间把网关搭起来试试。