现在不少做AI应用的朋友应该都有同感:模型越来越多,接口格式却五花八门,今天调这家明天调那家,代码里全是if-else分支,维护起来让人头大。我之前也被这个事折腾得不轻,直到后来用上litellm这个开源工具,才算真正把这事儿捋顺了。这篇文章就结合我自己实际跑项目的经历,把litellm的用法、思路和踩过的坑从头到尾聊一遍,希望能帮到正在被多模型API集成折磨的人。
litellm本质上是一个统一大模型API调用的代理层和工具库。用大白话说,它就是把你和各家模型厂商(比如OpenAI、Anthropic、Google、Azure、国内各家厂商等)之间的通信,封装成一个统一格式。你只需要按它定义的接口去写代码,它负责在背后把请求转发到真正的模型服务商,再把结果转回来。这样做带来的直接好处是:你代码里的模型调用逻辑可以做到“改配置不改代码”,换模型、加模型就像换插座一样简单。
如果你正在做RAG应用、Agent应用、多租户SaaS服务,或者只是单纯想在不同模型之间快速做对比评测,litellm都非常合适。它既是一个命令行工具,也可以直接在Python代码里当SDK用,还能部署成一个独立的代理服务,和OpenAI的API格式完全兼容。这意味你原来用OpenAI SDK写的代码,几乎不用改就能跑到其他模型上。
下面我从整体设计、核心功能、实际部署和常见问题几个角度,把litellm讲细一点。
1. 内容整体设计与思路拆解
1.1 litellm解决的核心痛点
先说一个我实际遇到的场景。早前我做一个客服问答机器人的时候,需要同时测试好几家大模型的效果。最开始天真地以为,各家都提供了SDK,各写一套调用代码就是了。结果真做起来才发现,这根本不是一个量级的事:有的SDK是同步的,有的是异步的;有的返回带tool_calls,有的返回是raw text;有的超时设置叫timeout,有的叫request_timeout;有的错误码是401,有的返回429但含义还不太一样。
这些细节单独看都不难,但只要你要维护两套以上,代码里就会开始出现大量脏逻辑。后来我在一个开源项目里看到了litellm,发现它的核心设计思路特别好:它把所有模型提供商的API都映射成同一个格式,这个格式就是OpenAI的message格式。也就是说,你用litellm调什么模型,写起来都像在调OpenAI一样。
这套设计在工程上有个很大的优势:团队内部可以统一定义一套工具函数和错误处理逻辑,不用去分别维护多家SDK的兼容层。后续模型要升级、要替换,也只需要改配置,代码层几乎不动。
1.2 代理模式与服务化思路
litellm除了作为代码库使用,还能当作一个代理服务部署,这一点我觉得是它最值得用的地方。部署起来之后,它会暴露一个和OpenAI API完全兼容的端点,你原来代码里base_url指向OpenAI的地方,改成指向你的litellm服务就行。
这种代理模式的价值主要体现在几个方面:
- 统一鉴权与密钥管理:你不需要把各家模型的API Key散落在各个服务里,而是在litellm服务端统一管理,调用方只需要知道你分配的虚拟Key。
- 统一预算和限流:每个Key可以设置预算上限、每分钟请求数,防止某个应用把费用跑飞。
- 统一日志与监控:所有请求都经过litellm,你可以在一个地方看到调用了哪些模型、延迟多少、成功率多少、花费了多少钱。
从架构上看,这其实是一种很典型的门面模式,把背后复杂的多渠道适配全部藏起来,给上层一个干净的接口。对于前后端分离、多团队协作、或者做企业内部AI网关的场景,这个思路非常值得借鉴。
1.3 为什么选litellm而不是自研适配层
肯定有人会说,这套东西我自己写一个适配层不就行了?我最初也这么想过,但实际走一遍就会发现,维护一个适配层的工作量远超预期。
首先是模型参数差异。同样一个temperature参数,不同厂商的取值范围不一样,有的叫temperature,有的叫temp,有的甚至不支持。生成token数量的上限,各家也不一样,有的用max_tokens,有的用max_tokens_to_sample。单单把这些参数统一处理好,就需要大量测试。
其次是模型列表的更新速度。大模型行业现在是一周一个新模型,厂商API也在不断调整。litellm有专门的维护团队和社区,能很快跟上更新。我自己维护的话,根本做不到这种时效性。
再者是工具的成熟度。litellm已经支持超过100家模型提供商,而且内置了重试、超时、流式输出、函数调用、预算管理、日志记录等一大堆功能。如果自己写,这些功能没几个月根本沉淀不下来。基于成熟工具来构建自己的系统,是更务实的选择。
2. 核心细节解析与实操要点
2.1 基础调用方式与参数映射
先看最简单的用法。安装litellm后,在Python里调用模型的代码是这样的:
import litellm response = litellm.completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个有用的助手"}, {"role": "user", "content": "你好,介绍一下你自己"} ], temperature=0.7 ) print(response.choices[0].message.content)这个写法几乎和OpenAI原生SDK一模一样,因为litellm就是按照OpenAI的格式来设计的。如果我想换成Anthropic的模型,只需要把model参数改成claude-3-5-sonnet-20241022,其他代码不用动。
这里要特别说一下参数映射的问题。litellm在内部会把统一的参数转换成各个厂商的格式。比如你写max_tokens=256,对于不同厂商,它会自动映射成对应的字段。这就解决了前面提到的参数差异问题。但要注意,不是所有参数都能完全映射。有些厂商的特殊参数,litellm也提供了透传机制,用litellm.completion(..., anthropic_version="2023-06-01")这种方式,直接把额外参数传给后端厂商。
2.2 流式输出的正确姿势
流式输出做聊天机器人几乎是必选项,不然用户要等好几秒才能看到一句话的开头,体验很差。litellm支持所有接入模型流式输出,但不同厂商的流式格式不一样,litellm帮你统一成了OpenAI的流式格式。
以stream=True为例:
response = litellm.completion( model="gpt-4o", messages=messages, stream=True ) for chunk in response: delta = chunk.choices[0].delta if delta.content: print(delta.content, end="", flush=True)在实际项目中,我通常会把流式响应包一层异步生成器,方便在FastAPI里做SSE推送。有一点要提醒的是,不同模型的流式行为略有差异,比如有些模型会把内容分得特别碎,有些会一次性返回很长一段。如果需要前端打字机效果,可能要自己在服务端做一下缓冲聚合,按标点或按固定长度切分后再推送,这样体验更稳定。
2.3 函数调用与工具使用的统一格式
大模型应用中,函数调用(Function Calling)或者叫工具调用(Tool Use)越来越重要。Agent系统全靠这个能力来调用外部工具。litellm同样对此做了统一。
看一个简单的例子:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名"} }, "required": ["city"] } } } ] response = litellm.completion( model="gpt-4o", messages=messages, tools=tools ) # 模型返回的tool_call tool_calls = response.choices[0].message.tool_calls对于支持该能力的模型(比如OpenAI系列、Claude系列、Gemini系列),litellm会把tool_calls统一成这个格式。你在业务侧只需要处理这种统一的tool_calls结构,然后执行对应工具,再把结果作为message传回去即可。这一整套流程在不同模型上跑起来,表现基本一致。
2.4 异步调用与批量场景
在处理批量任务或并发请求时,异步是必须的。litellm提供了acompletion方法,用法和同步版几乎一样:
import asyncio import litellm async def ask_model(session_id, question): response = await litellm.acompletion( model="gpt-4o", messages=[ {"role": "user", "content": question} ] ) return response.choices[0].message.content async def main(): questions = ["介绍一下Python", "介绍一下Rust", "介绍一下Go"] tasks = [ask_model(i, q) for i, q in enumerate(questions)] results = await asyncio.gather(*tasks) print(results)我实际测下来,在需要同时处理几十个请求的时候,异步写法能显著降低总耗时。不过要留意的是,并发量上来了之后,厂商那边可能会给429限流。这时候litellm内置的重试机制就发挥作用了。它默认会对某些可重试的错误(比如429、5xx)进行指数退避重试。你可以通过num_retries和max_retries等参数控制重试策略。我自己踩过坑的地方是:把重试次数设得太大,导致线上请求堆积。我的建议是,总的重试等待时间不要超过30秒,超过之后最好直接返回错误给前端,让用户决定是否重试。
3. 实操过程与核心环节实现
3.1 代理服务部署与配置
litellm单独作为代理服务部署时,操作非常直接。首先安装并启动:
pip install litellm litellm --model gpt-4o --port 4000启动后,访问http://localhost:4000/v1/chat/completions,就可以用OpenAI API的格式调用gpt-4o了。但生产环境不会这么简单,你往往需要配置多个模型、多个团队、不同的预算和权限。litellm使用一个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-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: llama3-8b litellm_params: model: ollama/llama3:8b api_base: http://localhost:11434配置里model_name是你暴露给使用方的别名,litellm_params里则是真正传给litellm的参数。可以看到,本地用Ollama跑的模型也能接入同一个网关,这一点对于统一管理本地模型和云端模型特别方便。
启动代理时指定配置文件:
litellm --config config.yaml --port 4000然后你就可以这样调用了:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000/v1", api_key="sk-your-virtual-key" ) response = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "你好"}] )注意这里model参数用的是代理里的别名claude-3-5-sonnet,而不是原始模型名。这样使用方根本不用知道真正的模型是哪个,后续换模型也只改配置文件。
3.2 多租户与虚拟Key管理
如果做企业内部平台,或者服务于多个客户,litellm的预算和Key管理功能能派上大用场。启动代理后,你可以调用litellm的管理接口来创建虚拟Key:
curl -X POST http://localhost:4000/key/generate \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-admin-key" \ -d '{ "models": ["gpt-4o", "claude-3-5-sonnet"], "max_budget": 100.0, "budget_duration": "30d", "max_parallel_requests": 100 }'响应里会得到一个虚拟Key,这个Key只能调用指定的模型,且总预算不超过100美元。这在实际运营中非常有用:你给每个项目、每个租户发一个独立的虚拟Key,就算这个Key泄露了,损失也只有这个Key的限额。
我用这个功能接待过一个多项目团队的情况:每个项目组有自己的虚拟Key和预算,各自在后台看自己的调用量和花销。出现问题的时候,也能精确追溯到是哪个Key、哪个请求、调的哪个模型。这种可观测性对线上系统的帮助特别大,排查问题不再需要翻半天日志。
3.3 成本追踪与异常监控
除了管理Key,litellm还会记录每一次调用的token数、模型单价,并统计总费用。在代理模式下,你可以在控制台里看到实时的请求记录,包括状态码、响应时间、使用的模型、消耗的token等。
我自己通常还会配合一个有用的做法:把litellm的日志接进公司的监控系统。litellm支持通过litellm_settings配置回调(callback),比如:
litellm_settings: callbacks: ["prometheus"]开启之后,litellm会暴露一堆Prometheus指标,比如litellm_total_requests、litellm_total_cost、litellm_success_rate等等。你用Prometheus抓取这些指标,再配上Grafana看板,就能实时看到服务的整体健康状况。
这里补充一个我实际操作中的心得:光看成功率是不够的,一定要看分模型的延迟和错误分布。有些阶段波动特别大,如果没看分模型数据,很难定位是哪个上游出了问题。litellm在响应对象里会带上model字段,这个字段保留的是真实模型名,可以按它做维度拆解。
3.4 路由策略与多供应商容灾
litellm的另一个值得深入了解的功能是模型路由。简单来说,你可以在litellm里定义一组同类型的模型,然后让litellm根据策略自动选择用哪个模型来响应请求。
举例来说,你可以配置一个gpt-4o的模型组,里面包含openai/gpt-4o和azure/gpt-4o,再配上az作为备用。当主供应商出故障或者限流严重时,litellm会自动把流量切换到备用的azure/gpt-4o。这个策略配置在config.yaml里:
router_settings: routing_strategy: "simple-shuffle" fallbacks: [ {"gpt-4o": ["azure/gpt-4o"]} ] cooldown_time: 300这里simple-shuffle是默认的轮询策略,litellm还提供了least-busy、latency-based-routing等更高级的策略。cooldown_time表示某个模型出错后,需要冷却多久才会重新尝试。这个容灾机制在线上非常有用,因为模型厂商的稳定性和限流情况是不受你控制的,有自动切换这个兜底,至少不会因为上游波动导致服务整体不可用。
我自己建议,不要把路由策略配置得过于复杂。刚开始用轮询或者基于延迟的策略就够了。等积攒了足够的调用数据,再切换到更复杂的策略。否则一开始就上最复杂的方案,出了问题反而难排查。
4. 常见问题与排查技巧实录
4.1 模型名格式错误
这是新手最容易犯的错。很多人直接写model="gpt-4o",在某些情况下没问题,但如果你同时装了多个平台的Key,或者某些平台里存在同名模型,就会出现奇怪的报错。
litellm的模型名格式通常包含供应商前缀,比如openai/gpt-4o、anthropic/claude-3-5-sonnet-20241022、gemini/gemini-1.5-pro。如果没带前缀,litellm会尝试根据你的环境变量推断,但推断结果不一定是你想要的那个。
我遇到过一次很诡异的情况:代码里写了model="gpt-4o",但请求被转发到了Azure OpenAI,因为我环境变量里设置了AZURE_API_KEY和AZURE_API_BASE,litellm就自动优先走了Azure通道。解决方法是显式指定model="openai/gpt-4o"来锁定供应商。
排查建议:看到”model not found”或者“Unknown model”这类报错时,先检查你的模型名是否带对供应商前缀;再看环境变量里有没有设置多个平台的Key。
4.2 超时与重试设置不当
生产环境中,模型调用超时是很普遍的事。litellm默认超时时间是600秒,这个值对于很多在线应用来说太长。比如你做一个APP的聊天功能,用户不可能等10分钟才看到响应。所以一定要显式设置超时:
litellm.request_timeout = 30或者按单个请求设置:
response = litellm.completion( model="gpt-4o", messages=messages, timeout=15 )我个人的经验值是:普通对话场景15秒,Agent多轮工具调用场景20到30秒。如果超过这个时间还没返回,前端基本就该提示“稍后再试”了。
重试也要注意一个细节,litellm默认对429和5xx进行重试,这是好事。但如果你在异步场景下开了很多并发,重试会加剧上游压力,反而更容易触发限流。比较稳妥的方式是控制好总并发数,并给litellm设置num_retries为2到3次,同时开启fallbacks来做跨供应商容灾,而不是单纯依赖重试。
4.3 context_window与token溢出
不同模型的上下文窗口大小不一样,有的16K,有的32K,有的200K。当请求的总token数超过模型上限时,会直接报错ContextWindowExceededError或者类似的错误。
我在做一个长文档问答功能时,就频繁撞上这个问题。用户上传的文档太长,超出了模型窗口。解决思路有几个:
- 先用文本切分器把文档切成小块,只把相关块发给模型。
- 用带检索能力的RAG方式,而不是全量塞进上下文。
- 在litellm侧,也可以开启自动截断功能,在确保不破坏消息结构的前提下,把超出的部分丢弃。
litellm提供了一种自动处理方式,设置litellm.drop_params = True之后,它会自动丢弃一些不支持的参数,但token溢出这种问题是靠调整参数解决不了的,还是得从源头上控制输入长度。所以我对长文本场景的建议是:一定要自己做文档切分和检索,别指望模型硬扛。
4.4 代理模式下鉴权配置缺失
如果你部署了litellm代理,但没有配置master_key,那任何人只要知道你的服务器地址,就能调用你的模型,这会带来严重的安全和财务风险。
官方建议生产环境必须设置master_key:
general_settings: master_key: sk-your-master-key设置之后,所有管理接口和模型调用请求都需要在Header中带这个Key:
curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}]}'如果你是在公司内部用,建议再加上一个子Key,用/key/generate接口生成,这样可以精细控制每个内部项目的额度。不要所有地方都用同一个master_key,不然出事的时候不好定位到底是谁调用的。
4.5 不同provider返回结构差异导致的兼容问题
虽然litellm统一了大部分接口,但个别模型返回的某些字段仍然有细微差别。比如有的模型不返回usage字段,有的返回的content可能为空但tool_calls有值。如果你的代码没有对这些边界情况做保护,很容易出现NoneType相关的报错。
最典型的场景是:当你把消息传给模型,模型认为不需要调用工具,直接回了文本。这时tool_calls就是None;反过来,模型如果决定调用工具,content可能是None。我在代码里都会这样处理:
message = response.choices[0].message if message.tool_calls: # 处理工具调用 pass if message.content: # 处理文本回复 pass宁可多做一层空值判断,也不要在线上跑挂了再去补。
4.6 配置管理混乱
最后说一个比较隐蔽但很常见的问题:随着接入的模型越来越多,config.yaml可能变得很长,维护起来越来越困难。如果几个团队共用一个代理,这个配置文件就成了一个易碎品。
我的处理经验是:在litellm代理这层按团队拆分不同的配置文件,然后用脚本把公共的部分抽出来。另外,把厂商的API Key统一放在环境变量或者密钥管理服务里,别直接写在config.yaml里。毕竟配置文件是要入库的,一旦泄露,所有Key都完了。
如果你用的是Kubernetes部署,建议把配置放到ConfigMap或者K8s的Secret中,并使用环境变量引用。每次更新配置后,平稳地重启litellm服务就可以生效。
5. 从使用到落地的一些个人体会
litellm这个项目,我从单纯在脚本里调用,到部署成公司内部的统一模型网关,实际走了不少弯路。现在回头看,有几个认知层面的体会想分享出来。
第一个体会是关于标准化的价值。很多人一开始觉得,接入多家模型无非是多写几个函数,但真到了多模态、流式、函数调用、并发、预算这些都叠加在一起的时候,靠手写适配代码几乎是不可维护的。litellm的价值不在于帮你省了第一遍接入的功夫,而在于让你后续的系统演化过程中,不用反复在兼容层上打补丁。
第二个体会是配置先行。我强烈建议,刚上手litellm的时候,就算是只用一个模型,也把config.yaml建起来,走代理模式调用。因为从“直接调用SDK”切换到“代理模式”,成本很低,但收益很大。等以后要加Key、加租户、加预算的时候,你已经有一个跑通的基础设设施,不用临时抱佛脚补架构。
第三个体会是成本和延迟要一起盯。我们之前只盯着模型返回质量,忽略成本监控,结果月底账单出来吓了一跳。litellm的成本统计功能虽然能让你看到花了多少钱,但更关键的是要建立“每次请求都绑定业务场景”的意识。你可以在传入请求时带上metadata标签,比如标注是哪个功能、哪个用户群,这样在成本统计分析时,就能知道每个业务花了多少钱,这个数据对产品决策特别有帮助。
另外这个项目本身迭代速度很快,社区也很活跃。你会发现它不只是个API封装,还在往AI网关的方向走,包括模型路由、预算管理、请求日志、甚至还有多模态支持。所以如果你所在团队已经开始做多个AI应用或者多租户AI平台,花点时间研究litellm,会是性价比很高的事情。
最后抛一个我踩过坑之后养成的习惯:每当个版本升级,我都会先在测试环境把新的config.yaml和旧的config.yaml做对比,然后把关键路径的调用都回归一遍。因为litellm更新快,某些参数名或者默认行为可能有变化,直接生产环境升级,风险还是蛮大的。这个经验分享给大家参考。