都说自己在做 AI 应用,结果时间全耗在接模型上了。今天接 OpenAI,明天接开源模型厂商,后天又冒出来一个速度飞快的新模型,每个平台的 SDK 用法还不一样,鉴权方式更是一个比一个花。我前前后后做了几个 AI 项目以后,只要看到新的模型接口文档,第一反应不是“太好了又有新模型”,而是“又要写一遍适配代码了”。
后来在某个跨平台系统的项目中,我彻底换了一套做法,用一个叫 LiteLLM 的组件把公司内部所有模型的接入方式统一掉。这套东西直接影响了后面所有项目的架构,所以这篇想把我最近一年怎么用它、为什么选它、公司在落地时踩过的那些坑,以及最后沉淀下来的一套可复用的接入和切换方案,一次性聊清楚。
1. 为什么 AI 应用最先该解决的,是模型接口的混乱问题
先说说我最初遇到的情况。
当时团队要做一个多模型聚合问答功能。产品经理一句话倒是轻巧:“我们同时接 3 家厂商,哪家响应快、效果稳,就用哪家。”听起来很简单,但真做起来,问题接二连三地冒出来。
1.1 各家 SDK 和 API 长什么样各不相同
3 家模型服务商,3 套不同的 SDK,3 种不一样的请求结构。一家老的模型接口用的是一个多层嵌套的 messages 结构,每次请求还需要额外传一个 practices 配置;另一家新模型虽然也号称“兼容”某主流格式,但认真读完文档才发现它在工具调用(function calling)的响应体里偷偷改了两个字段的名字;还有一家干脆连语言模型的生成长度限制都和你预期的不一样。
这种差异带来的问题,不只是在“多写几行代码”上,而是团队里每个人都得熟记两三套 API 规范。换个人接手,光是搞明白“哪个模型服务商的请求体应该长什么样”就要花掉大半天。
1.2 模型切换要改业务代码,这本身就很不合理
更让人崩溃的是切换。产品经理周二说要主推模型 A,周五又改成模型 B,理由是模型 B 在某个评测集上便宜且明显快。每次切换,我都得去翻业务代码,找到写死了模型名的那几个地方,改参数、改鉴权 key、甚至改预处理逻辑。有时候漏改一处,等测试发现时,已经白烧了不少调用费用。
这也是后来我坚定认为“必须有一层统一接口”的根源。业务代码不该关心后面到底是哪家模型服务商,它只需要说“我要一句话完成什么任务,用哪个能力档位”,剩下的解析、路由、鉴权、重试,都应该被隔离在底层。
1.3 成本、延迟、限流,谁在替你做全局调度?
还有一个很现实的问题:不同模型服务商的计费方式不一样,有的是按 token 计费,有的按请求次数,有的还会区分输入和输出 token 的价格。手动记录这些成本几乎不可能,更别提在大促流量进来时做自动切换到低价模型这种事了。没有一个统一网关层,所谓“智能路由”“成本优化”都只能停留在 PPT 上。
说白了,AI 应用中真正容易被低估的风险,就是接口层的“脏活累活”。LiteLLM 之所以能进入我的视野,正是因为它把这一层标准化的活给接住了。
2. LiteLLM 解决问题的基本盘:标准化请求与响应模型
先说一个最简单的判断标准:LiteLLM 说白了就是把市面上主流模型服务商的 HTTP 接口,统一映射成一套接近 OpenAI 风格的结构。这是它所有能力的基石,也是我敢把公司业务押在上面的原因。
2.1 从"各家自定义"到"OpenAI风格兼容"
你不需要为每个模型服务商单独引入一份 SDK。LiteLLM 提供一个统一的 Python 客户端和一个 HTTP 代理服务,业务代码只需以同一套接口文本格式发送请求。
以前写调用多个模型服务的代码,大概是这种感觉(这里只用伪码表述,不同 SDK 之间肉眼可见地割裂):
# 模型服务商 A 的 SDK client_a = ModelA.Client(api_key=key_a) resp = client_a.chat.completions.create( model="model-a", messages=[{"role": "user", "content": "你好"}], temperature=0.7 ) # 模型服务商 B 的 SDK client_b = ModelB.Client(secret=key_b) resp = client_b.generate( prompt="你好", model_name="b-model", sampling_params={"temperature": 0.7} )用 LiteLLM 之后,这个差异被抹平了:
from litellm import completion resp = completion( model="openai/gpt-3.5-turbo", messages=[{"role": "user", "content": "你好"}], temperature=0.7 ) resp2 = completion( model="anthropic/claude-3-5-sonnet", messages=[{"role": "user", "content": "你好"}], temperature=0.7 )用 provider 前缀(如openai/、anthropic/、together/)把“模型服务商”和“模型名”绑在一起,由 LiteLLM 在内部完成鉴权和格式转换。从业务代码的角度看,只是换了个字符串的值,返回结构也统一了,这带来的心理安全感是很大的。
2.2 请求参数的标准字段映射
除了 messages 和 model,像 temperature、max_tokens、top_p 这类常用参数,LiteLLM 也会做统一转换。不同厂商对同一个词的理解有差别,比如有的叫 max_tokens,有的叫 max_new_tokens,还有的把最大输出 token 上限写成 max_output_tokens。LiteLLM 会在内部把它们对齐。
这里有一个使用心得:不要把各家模型的特殊高级参数硬塞进标准字段里。LiteLLM 支持通过 provider-specific 方式传递,例如:
from litellm import completion resp = completion( model="bedrock/claude-instant-1.1", messages=[{"role": "user", "content": "讲个冷笑话"}], temperature=0.8, # 厂商私有参数放到这里,很多是会被透传的,但有少数需要查文档确认 extra_body={ "max_new_tokens": 1000 } )建议凡是业务上要长期依赖的特殊参数,都要在接入之初先跑一个小实验,把 LiteLLM 文档里写明的兼容性测试一遍,不要想当然地以为所有扩展参数都能在各个环节无损穿透。
2.3 统一返回体:一眼看懂谁在响应
标准化的请求只是开始,更关键的其实是返回体。各家模型服务商的响应结构差异很大,有些把内容藏在 choices[0].text,有些放在 content[0].text,还有工具调用信息的位置更五花八门。
LiteLLM 的返回对象统一到 OpenAI 风格:
resp = completion(...) # 提取文本 text = resp.choices[0].message.content # 提取工具调用,结构相对稳定 tool_calls = resp.choices[0].message.tool_calls # 拿到 token 消耗 total_tokens = resp.usage.total_tokens这意味着我可以在上层写一套公共的解析函数,不用关心实际调用的是哪家模型服务商。异常信息也被做了统一包装,聊胜于无,起码不至于每个服务商的报错文本都不一样。
3. 公司内部落地:用 LiteLLM 网关统一管理 Key、成本与模型路由
如果你只是自己写个小工具,直接用 Python 包的 client 模式就够了。但放到了公司内部平台,涉及多团队、多业务方,每个人都有自己的密钥,每次模型调用又不能都是前端的明文请求,就必须引入 LiteLLM 的另一个模式:代理网关(Proxy Server)。
我们最终是把它部署成内部的模型网关来用的,这部分经验我拆成几个小块来讲。
3.1 为什么不用 SDK 直连,而要再套一层网关
直接调 SDK 的问题是密钥不好管。假设每个后端服务都需要调用 AI 能力,那么每个服务都得持有各家模型服务商的 Key。一旦某个 Key 要轮换或下线,你得满世界找哪些服务在用它。
上了 LiteLLM 网关之后,所有服务的实际调用都打到这一个内部 HTTP 地址,密钥只放在网关环境变量里。业务服务不用关心底层模型服务商的 Key,只需要在请求里带上自己的虚拟 API Key(LiteLLM 内部生成)。
这个“虚拟 Key + 真实 Key 分离”的设计,我认为是网关模式最大的价值点:
- 可以生成多个虚拟 Key,分配给不同团队或不同模块,后续单独吊销某一个,不影响别人。
- 每个虚拟 Key 可以设置预算上限,防止有人误写死循环导致成本爆炸。
- 每个 Key 的调用记录会留在网关日志里,出现费用异常时能够精确定位到调用方。
3.2 一个最小可用网关,我用 Docker 就拉起来了
LiteLLM 官方提供了 Docker 镜像,可作为独立服务部署。我最开始为了做技术验证,在本地直接用一个轻量命令方式起的测试网关(此处简述思路,实际路径以官方文档为准)。
关键配置有两块:一是环境变量里设置真实模型服务商的 Key,二是告诉网关“你可以路由哪些模型”。简单说,你配置好某个模型服务商的 Key,并向网关注册模型名,业务侧就能通过统一网关调用。
那时候我公司的生产环境没有用 Kubernetes 或其他重型调度,就是一台普通云主机,Docker 起服务,前面挂负载均衡,再做个简单的健康检查。初期模型调用量根本不至于需要多么复杂的架构。
3.3 模型注册表:建立公司内部的模型别名体系
模型名混乱是团队协作时很容易忽略的痛点。同一个模型,业务团队叫“大模型-1”,平台团队叫“标准-7B”,成本核算那边叫“主力模型”,各家叫法完全不同。
LiteLLM 的 model_name 其实可以自定义映射。也就是说,我可以在网关里把对外暴露的模型名定义为业务语义名,例如internal-main,然后映射到某个具体模型服务商的具体型号。业务方根本不知道也不需要知道背后的真实型号。
这个“别名”机制的价值在模型升级时体现得淋漓尽致。模型服务商发布了新版本,性能更好价格不变,我只改网关里的一条映射配置,全公司的业务代码一行不用动,调用链的下一个请求就自动用上了新模型。至于什么时候灰度、哪些业务先切,也可以通过同一模型名下挂多个真实模型加权重的策略来做。
3.4 预算控制和调用审计的实操作法
预算控制的核心不是“等超了再提醒”,而是“可能超之前就拦住”。我在网关这边设置了以下几种约束:
- 虚拟 Key 级别设置单日最大消耗 token 数
- 月度总预算设置,达到 80% 的时候先告警到群
- 某些测试环境的 Key 直接设成只读配额,跑批任务测爆了也不影响生产
调用审计方面,LiteLLM 网关默认会把每次请求的信息记录到日志中,这其中包括模型名、虚拟 Key 标识、token 消耗、耗时等。我把这些日志接到公司的日志系统里,按天做聚合分析,核算每个业务方的成本。
有一点要注意:网关的 Access Log 和错误日志尽量分开存,否则排查问题时日志量太大,很难快速定位到具体某一次模型调用的完整链路。
4. 从浅到深:我在生产环境踩过的模型路由与重试的坑
中看不中用的大框架谁都会搭,但生产环境的稳定性,才是真正决定这套方案到底能不能长期活下去的关键。下面这几个问题,都是我在实际部署、压测、灰度过程中真实踩过的。
4.1 fallback(降级)不是简单地“多试一次”
很多文档提到 LiteLLM 支持 fallback 列表,就是在主模型调用失败时自动换到备用模型。但这里有一个隐藏风险:超时时间设置不当会导致备用模型接棒时已经太晚,用户感知到的大幅延迟并不会因为 fallback 成功而消失。
我举一个调试经历。某次线上问答突然变慢,查看监控发现主模型服务商响应偶尔长达 20 秒才返回,超时时间设的是 30 秒,结果 fallback 触发时,总响应时间已经过了 30 秒多的关口,用户的体验早就变得极差。
后来我调整了策略,把主模型的超时时间缩短,比如 10 秒内没返回就算了,直接走备用模型。虽然偶尔会出现主模型其实能成功但稍微慢一点就被切走的情况,但换来的是整体响应时间可控,这个取舍我认为是值得的。
更精细的玩法是拿模型的调用成本做 fallback 决策。高成本模型失败后,自动切换低成本模型;低成本模型也失败,那就彻底放弃并返回友好报错。你的业务阈值决定了这份降级策略如何编排。
4.2 重试策略和后端限流的微妙关系
模型服务商限流(rate limit)是最常见的报错之一。面对 429 错误,程序的第一反应往往是重试,但重试不当会加剧对方服务端的压力,甚至让自己被限流更久。
LiteLLM 内置有重试机制,但默认参数未必适合你的业务。我在项目里做的是:
- 对超时类错误,最多重试 2 次,且重试间隔要带随机性
- 对 429 限流错误,读取响应里 Retry-After 头,按服务商建议的时间等待后再重试
- 对 400 一类的参数错误,直接放弃重试,因为再试也是同样的结果
重试间隔加一点随机抖动,可以避免多个业务请求同时失败时,网关在同一秒内对模型服务商发起一波整齐的重复请求,那样子直接把限流阈值打满。
4.3 token 计数不一致:成本统计里的暗坑
各家模型服务商的 tokenizer(分词器)并不完全一样,同一个中文句子,不同模型服务商算出来的 token 数可能不一样。LiteLLM 网关统计到的 usage 数据来自各模型服务商返回的 usage 字段,这是最接近实际计费的。
但你要注意:当你使用流式(stream)返回时,部分模型服务商不会在每个 chunk 里都返回 usage 信息,最后一个 chunk 才会有。网关侧如果处理不到位,日志里统计的 token 数和实际账单消耗可能出现偏差。
我的做法是在网关日志基础上,另建一个对账任务,按天拉取各模型服务商的账单明细和网关的 usage 记录做比对,偏差超过 1% 才判定为正常波动,否则就查是不是代理配置层面有请求没有正确统计到。
4.4 工具调用与流式输出的兼容问题
做 Agent 类应用时,长文本连续输出基本都会开流式。可工具调用(function calling)在流式模式下怎么解析,各家模型服务商的实现差异很大。
LiteLLM 虽然想做统一,但工具调用的参数定义里,各家对工具 schema 的校验严格程度不一样。同一个工具描述,在某家模型服务商可以正常返回结构化调用参数,在另一家可能就直接报格式错误。
解决思路是:把工具 schema 精简,避免在描述里写过长的 keyword 列表;同时做一层“工具调用结果解析失败则降级为文本回答”的保护逻辑。业务层不硬性规定模型必须走工具调用,而是给模型选择权。
5. 业务代码零改动切换模型:一份可复用的接入配置范式
这部分给想要在团队里推广这套方案的读者一个参考。我们后来沉淀了一套通用的接入流程,前后大概 4 步走完,业务方可复制这套配置范式,不用各自踩坑。
5.1 第一步:在网关侧完成模型服务商注册与模型映射
拿要新接入一家模型服务商举例。开发同学先在网关环境变量中配置对应的真实 Key,然后通过管理接口注册模型映射。这里体现了别名体系的作用:
在网关配置层面申请一个业务模型名,比如ai-multi-purpose,这个别名不区分具体厂商版本。再把它指向目标服务商的实际模型 ID。这样业务侧文档里永远只写ai-multi-purpose,不会出现“某一天模型 ID 变了导致代码里到处都是旧 ID”的窘境。
5.2 第二步:业务侧封装一个标准调用工具
我的习惯是在业务代码库里写一个非常薄的封装模块,后面所有业务方都走这个模块,不要直接散落着调 LiteLLM 的底层方法。
import litellm from typing import List, Dict def call_model( messages: List[Dict[str, str]], model_alias: str = "ai-multi-purpose", temperature: float = 0.3, stream: bool = False ): return litellm.completion( model=model_alias, messages=messages, temperature=temperature, stream=stream )封装之后要做的补充动作是:统一在这里面加业务级的日志埋点、调用失败告警、超时配置。这样后续做过载保护、收费计量,改动只需要落在这一层。
5.3 第三步:模型切换只动配置,不发布代码
当团队决定从模型 A 切到模型 B,如果 A、B 都来自同一个模型服务商或都已被映射到同一个业务别名上,那么只需要在网关更新映射关系。
如果 A 和 B 有较大的能力差异,比如原来是不支持工具调用的模型,新模型支持了,那业务侧调用逻辑可能需要调整,不过这属于功能升级,不属于日常性能切换。
为了让“只动配置”成立,我强烈建议团队给每个模型维护一个能力标签:是否支持流式、是否支持工具调用、是否支持视觉输入、上下文窗口长度是多少。切换前先看能力标签是否匹配当前业务需求,否则配置切过去会引发一堆隐性故障。
5.4 第四步:灰度切换与自动回滚
灰度切换这套,我是靠网关端的两个模型同时暴露在同一个业务别名下,然后调整路由权重来做的。例如新模型先分配 5% 的流量,观察正确率和调用延迟,稳定后逐步上调到 50%、100%。
出现异常时如何自动回滚?我在监控大盘里设置了几条规则:
- 若新模型的失败率高于 10%,自动把权重切回旧模型
- 若新模型的平均响应时长超过旧模型 p95 的 2 倍,自动回滚
- 若某虚拟 Key 的日消耗预算在半天内用掉了 80%,怀疑有流量异常,立刻降级为旧模型
这套机制上线之后,模型切换就不再是一场心惊胆战的事了,更多时候是部门内部平静调整一个配置值。
6. 对 LiteLLM 的深度评估:它适合什么团队,又掩盖了什么问题
聊完了具体操作,最后我想从更冷静的角度说说这套方案的边界。LiteLLM 并不是银弹,在我实践过程中也发现了几处必须注意的现实约束。
6.1 很轻量,但不要把它当全功能网关用
LiteLLM 在“统一接入”这件事上做得非常好,但它不是一个完整的企业级 API 网关。如果你想在它前面做鉴权、限流、审计、灰度发布,它有一些基础能力,但功能和真正的大型网关还是有差距。
我建议的架构是:外层用公司已有的网关做链路治理,内层用 LiteLLM 做模型协议转换。二者搭配起来,各司其职。如果非要 LiteLLM 承担所有网关职责,团队得额外写不少补充逻辑。
6.2 模型更新节奏快,配置维护需要专人跟进
模型服务商每月发新版、调整价格、废弃旧模型,LiteLLM 的适配列表更新通常会有一定滞后。这就意味着团队里需要有一个人专门跟进上游模型变更,定期评估是否要修改映射配置。在我们团队里,这个角色由平台组的一个同学兼职,每周花半小时看更新公告,足够缓解大部分风险。
6.3 完全替换正式开发中使用的复杂特性时,依然要认真读文档
LiteLLM 一直努力覆盖主流模型服务商的高级特性,但总有些深度定制的接口不在它的翻译范围内。比如某些模型服务商独有的高度定制化抽样参数,或者一些改造过的流式控制项,在 LiteLLM 里未必有标准映射。
我的经验是:遇到这种场景,不要为了“统一”而强行塞进 LiteLLM 模型服务商的私有参数里,而是评估一下这个高级特性是不是业务刚需。若不是,直接用标准模型能力替代;若是,可能需要本地扩展一个对应的 Provider 适配逻辑,或者退回使用原厂 SDK。
6.4 关于成本优化的清醒认知
很多做 Agent 应用的朋友对“智能路由到最便宜的模型”有幻想。LiteLLM 可以帮助你设定 fallback 或路由权重,但它不会替你判断哪个模型在具体某类任务上效果更好。成本优化永远要建立在效果评测的基础上,没有评测的盲目切模型,就是在拿线上业务试错。
我自己体会比较深的一点:统一网关层最大的价值,不是帮你选“最好的模型”,而是让你在发现更好的模型之后,有能力以最低成本把整个系统迁移过去。这种能力在模型快速迭代的当下,比单次调用省下几分钱值钱得多。
7. 如果你也想在公司推这套方案,我的建议清单
最后浓缩一份检查清单,可以直接拿去当团队落地的参考。这些东西都是我在真实项目中踩过一次又一次之后沉淀下来的,未必适合所有人,但至少能帮你少走几周弯路。
7.1 动手前提
- 至少有两家模型服务商要接入,或者预期未来一定会切换厂商
- 公司内部存在多团队调用 AI 能力,需要统一认证和计费
- 当前被“各家模型服务商 SDK 格式不统一”的问题反复折磨
- 业务对成本、限流、可观测性有明确要求
如果以上命中两条以上,从 LiteLLM 网关模式开始做是合理的。若只是自己写个 Demo 玩,那没必要上整套网关,直接用 LiteLLM 的 Python 客户端就够了。
7.2 落地时的优先级排序
我建议按这个顺序推进,不要一上来就追求大而全:
第一,先统一标准调用方式,让业务代码从厂商 SDK 中解放出来;第二,接入虚拟 Key 和基础日志,确认成本可计量;第三,配置 fallback 和超时策略,把稳定性兜住;第四,再做模型别名和灰度切换,方便后续运营;第五,最后把预算控制和调用审计做细,让团队用得放心。
这个顺序的核心思想是,每一步都能立刻产生可感知的价值,而不是憋一个大架构,两三个月后才看到效果。
7.3 千万别做的几件事
不要把所有模型服务商的 Key 写死在各个业务代码里,也不要让业务团队直接操作网关的管理接口。权限控制做不好,后面必然会出现有人悄悄加了一个高成本模型、月账单爆炸的情况。
在还没跑通“模型能力标签 + 评测集”之前,不要直接切核心链路。习惯性建议是先进 10% 流量,跑上两三天金融行业里大家也很熟悉的那套小流量验证,再放开到全量。
不要忽视对账。没有对账会出现在网关里看起来正常的请求,结果返回内容被模型服务商额外做了过滤,或者某类请求走了缓存,导致网关日志和实际计费趋势不一致的错觉。初期把对账自动化做好,后面能省很多扯皮时间。
7.4 最后一段个人体会
如果让我重新选择一次,我还是会把 LiteLLM 作为公司内部 AI 基础架构的一部分。原因不是它写得有多完美,而是它帮我解决了一个最本质的问题:把“模型调用”从一种需要频繁重写的编码工作,变成了一项可配置、可观测、可治理的日常操作。
以后模型会越来越多,模型服务商之间的能力差距也会越来越小,最后比拼的可能不是谁的算法最聪明,而是谁的团队能以最低的切换成本持续用上更好的模型。一个稳定的统一接入层,就是那件能帮你把选项永远留在手里的东西。我在实际使用中最大的感受是,它让团队的注意力终于从“适配接口”挪回到了“产品逻辑”上。就冲着这一点,我觉得值得花时间把它真正落地。