1. 从一个真实困境说起:多模型调用的碎片化之痛如果你正在做AI应用开发,大概率遇到过这样的场景:项目初期用OpenAI的接口跑通了原型,效果不错,上线后老板说“试试Claude吧,听说长文本更强”,于是你翻出Anthropic的文档重新对接一遍;过两天产品经理又说“国产模型成本低,能不能加个通义千问做兜底”,你又得去研究DashScope的SDK;再后来客户要求私有化部署,得接本地跑的开源模型,又是一套完全不同的调用方式。每个模型厂商都有自己的SDK、自己的鉴权方式、自己的参数命名、自己的返回格式。OpenAI用messages数组传对话历史,Anthropic把系统提示单独拎出来叫system,Google的Gemini又是另一套contents结构。你写了一套业务逻辑,换一个模型就得改一遍代码,测试用例也得跟着重写。更别提流式输出、函数调用、多模态输入这些高级特性,每家实现细节都不一样,光是适配层就能吃掉你一半的开发时间。这就是litellm要解决的核心问题。它本质上是一个统一的LLM调用层——用一套OpenAI格式的接口,去调用市面上几乎所有主流大模型。你不需要为每个厂商单独写适配代码,不需要记十几套不同的参数名,不需要在切换模型时重写业务逻辑。改一个字符串,就能从GPT-4切到Claude,再切到本地部署的Llama,业务代码一行不动。这篇文章适合所有正在做AI应用开发的人——不管你是刚接触大模型调用的新手,还是已经被多模型适配折磨过的老手。我会从实际使用角度出发,把litellm的核心机制、实操细节、踩坑经验一次讲透。读完你至少能搞清楚三件事:litellm到底怎么用、为什么这样设计、以及在实际项目中怎么避坑。2. litellm的定位与核心机制拆解2.1 它到底解决了什么问题先明确一个概念:litellm不是模型,不是推理框架,也不是Agent框架。它是一个中间层,夹在你的业务代码和各个模型厂商的API之间。你可以把它理解成一个“翻译官”——你的业务代码只说一种语言(OpenAI格式),litellm负责把它翻译成各个厂商能听懂的话,再把厂商的回复翻译回OpenAI格式。这个定位决定了它的价值边界。它不提升模型能力,不优化推理速度,不管理GPU资源。它只做一件事:抹平不同模型API之间的差异。所以如果你的项目只用一个模型,而且不打算换,那litellm对你的价值有限。但只要你有“多模型”的需求——不管是切换、对比、兜底还是成本优化——它就能省掉大量重复劳动。2.2 统一接口的设计哲学litellm的核心设计思路可以用一句话概括:以OpenAI的API格式为基准,向上提供统一接口,向下适配各家差异。为什么选OpenAI格式作为基准?原因很实际。OpenAI的Chat Completions接口是目前事实上的行业标准,绝大多数开发者最先接触的就是它,文档最全、示例最多、社区最活跃。而且大量开源工具和框架(比如LangChain、LlamaIndex)默认就支持OpenAI格式。以它为基准,意味着你的代码不仅能调litellm,还能无缝切换到其他支持OpenAI格式的工具上,不会被锁定。具体到调用方式,litellm的completion函数签名几乎和OpenAI的官方SDK一模一样:from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] )区别只在于model参数。你在这里填不同的模型标识,litellm就会自动路由到对应的厂商。比如填claude-3-5-sonnet-20241022就走Anthropic,填gemini/gemini-1.5-pro就走Google,填ollama/llama3就走本地Ollama。业务代码完全不用改。2.3 模型路由的命名规则litellm用一套命名规则来识别模型归属。基本格式是provider/model-name,比如anthropic/claude-3-opus、gemini/gemini-1.5-flash、ollama/llama3。对于OpenAI自家的模型,provider可以省略,直接写gpt-4o就行。这套规则看起来简单,但实际用起来有几个细节值得注意。第一,模型名称必须和厂商官方文档一致,拼错一个字符就会报错。第二,有些厂商的模型有多个版本别名,litellm通常都能识别,但建议用最明确的那个。第三,本地模型的provider标识取决于你用的推理框架,Ollama是ollama/,vLLM是hosted_vllm/,各有各的写法。提示:如果你不确定某个模型的正确标识,可以查litellm的官方文档,或者在代码里打印litellm.model_list查看支持的完整列表。2.4 支持的模型范围litellm覆盖的模型范围相当广,大致可以分成几类:类别代表厂商/框架典型模型标识商业闭源APIOpenAI、Anthropic、Google、Mistralgpt-4o、claude-3-5-sonnet、gemini-1.5-pro国内商业API通义千问、智谱、月之暗面、深度求索dashscope/qwen-max、zhipu/glm-4、moonshot/kimi云平台托管Azure OpenAI、AWS Bedrock、Vertex AIazure/gpt-4、bedrock/claude-3本地推理框架Ollama、vLLM、HuggingFace TGIollama/llama3、hosted_vllm/mistral聚合平台OpenRouter、Together AIopenrouter/anthropic/claude-3这个覆盖面基本涵盖了目前主流的模型来源。对于国内开发者来说,通义千问、智谱、深度求索这些国产模型的直接支持尤其实用,不需要自己写适配层。3. 环境搭建与最小可运行示例3.1 安装与依赖管理litellm的安装非常轻量,核心包不依赖任何特定厂商的SDK:pip install litellm如果你需要用到某些特定功能,可以按需安装额外依赖。比如要用代理服务器功能,装litellm[proxy];要用某些云平台的托管服务,装对应的extra。但大多数场景下,基础安装就够了。这里有个经验:建议在虚拟环境里安装。litellm的依赖树虽然不复杂,但它会引入一些HTTP客户端和数据处理库,和项目里已有的包偶尔会有版本冲突。用venv或conda隔离一下,省得后面排查依赖问题浪费时间。3.2 API Key的配置方式litellm读取API Key的方式和OpenAI SDK一致——从环境变量里读。每个厂商对应不同的环境变量名:export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..." export GEMINI_API_KEY="..." export DASHSCOPE_API_KEY="..."这种设计的好处是密钥不硬编码在代码里,符合安全实践。但实际项目中,你可能需要从配置文件或密钥管理服务里动态加载。litellm也支持在调用时直接传api_key参数,或者通过litellm.api_key全局设置。注意:如果你在代码里同时设置了环境变量和显式传参,显式传参的优先级更高。这个规则在调试多密钥场景时很有用。3.3 第一个可运行的调用装好之后,写个最简单的调用验证一下:import os from litellm import completion os.environ["OPENAI_API_KEY"] = "你的密钥" response = completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话解释什么是API。"} ] ) print(response.choices[0].message.content)返回结构和OpenAI官方SDK完全一致,response.choices[0].message.content就是模型输出的文本。如果你之前用过OpenAI的接口,这段代码不需要任何额外学习成本。3.4 切换到其他模型的实测现在把model参数改一下,试试其他厂商:# 切换到Anthropic response = completion( model="claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": "用一句话解释什么是API。"}] ) # 切换到本地Ollama response = completion( model="ollama/llama3", messages=[{"role": "user", "content": "用一句话解释什么是API。"}] )业务代码除了model这一行,其他完全没变。这就是litellm最核心的价值——用最小的改动成本实现模型切换。实测下来,不同模型的响应格式在litellm的封装下高度一致,但有几个细节需要注意。第一,某些厂商的模型不支持system角色,litellm会自动把它合并到第一条用户消息里。第二,不同模型的max_tokens默认值不同,有些厂商不传这个参数会报错,建议显式指定。第三,流式输出的chunk结构虽然统一了,但首包延迟差异很大,做交互式应用时要考虑这一点。4. 进阶能力:流式、函数调用与多模态4.1 流式输出的统一处理流式输出是对话类应用的标配。litellm把各家的流式接口统一成了OpenAI的SSE格式:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "写一首关于编程的短诗。"}], stream=True ) for chunk in response: content = chunk.choices[0].delta.content if content: print(content, end="", flush=True)这段代码换成Claude或Gemini,结构完全一样。litellm在底层处理了各家流式协议的差异——比如Anthropic的流式事件类型和OpenAI不同,Gemini的chunk结构也不一样,但到了上层都统一成了choices[0].delta.content。这里有个实操心得:流式场景下要特别注意异常处理。网络中断、模型超时、内容过滤触发,都可能在流的中途发生。litellm会抛出异常,但异常的时机和类型各家不同。建议在循环里包一层try-except,并且记录已经输出的内容,方便断点续传或降级处理。4.2 函数调用的跨模型适配函数调用(Function Calling)是构建Agent和工具集成应用的基础。litellm同样做了统一:tools = [{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } }] response = completion( model="gpt-4o", messages=[{"role": "user", "content": "北京今天天气怎么样?"}], tools=tools, tool_choice="auto" )返回的tool_calls结构也是统一的。但要注意,不是所有模型都支持函数调用,有些开源模型需要通过特定的prompt模板来模拟,效果参差不齐。litellm会在不支持时抛出明确的错误,而不是静默失败,这一点做得比较友好。4.3 多模态输入的处理差异多模态输入是另一个差异巨大的领域。OpenAI的格式是content数组里放{"type": "text"}和{"type": "image_url"},Anthropic的格式又不一样。litellm统一成了OpenAI的写法:response = completion( model="gpt-4o", messages=[{ "role": "user", "content": [ {"type": "text", "text": "这张图里有什么?"}, {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}} ] }] )换成Claude或Gemini,代码不变。但实测发现,不同模型对图片格式和大小的限制不同。有些只支持URL不支持base64,有些对图片分辨率有上限,有些对图片数量有限制。这些限制litellm没法帮你绕过,需要在使用前查清楚目标模型的具体要求。4.4 重试、超时与降级策略生产环境中,API调用失败是常态。litellm内置了重试机制:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], num_retries=3, timeout=30 )num_retries控制重试次数,timeout控制单次请求超时。litellm的重试是带指数退避的,不会在失败后立刻重试导致雪崩。更实用的是降级策略。你可以配置一个模型列表,litellm会按顺序尝试,直到有一个成功:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], fallbacks=["claude-3-5-sonnet", "ollama/llama3"] )这个功能在多模型架构里非常关键。主模型挂了自动切备用,备用也挂了切本地兜底,保证服务可用性。配置的时候要注意,fallback列表里的模型最好在能力上接近,否则降级后的输出质量会有明显落差。5. 代理服务器模式:把litellm变成统一网关5.1 为什么需要代理模式前面讲的都是Python代码里直接调用litellm。但在实际项目中,你的技术栈可能不全是Python——前端用JavaScript,后端有Go服务,还有几个Java微服务。这时候在每个语言里都集成litellm的SDK不现实。litellm提供了一个代理服务器模式,把统一调用能力封装成一个HTTP服务。任何语言、任何框架,只要能发HTTP请求,就能用上litellm的能力。这相当于在你的基础设施里加了一个LLM网关。5.2 配置文件的结构代理模式通过一个YAML配置文件来定义模型列表和服务参数:model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-llama litellm_params: model: ollama/llama3 api_base: http://localhost:11434 general_settings: master_key: sk-your-master-key database_url: postgresql://user:pass@localhost:5432/litellmmodel_list定义对外暴露的模型名称和实际路由目标。general_settings里配置主密钥和数据库连接(用于记录用量和做预算控制)。5.3 启动与调用启动代理服务器:litellm --config config.yaml --port 4000然后用任何HTTP客户端调用:curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{ "model": "claude", "messages": [{"role": "user", "content": "你好"}] }'接口格式和OpenAI完全兼容。这意味着你现有的任何基于OpenAI SDK的代码,只需要把base_url改成代理地址,就能直接调用所有配置的模型。5.4 用量追踪与预算控制代理模式的一个隐藏价值是集中化的用量管理。所有请求都经过网关,litellm可以记录每个请求的token消耗、响应时间、模型分布,并据此做预算控制。你可以在配置里给每个模型或每个用户设置预算上限:model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o model_info: max_budget: 100 budget_duration: 30d超过预算后,litellm会拒绝请求并返回明确的错误。这个功能在多团队共用一套API密钥的场景下特别有用,能防止某个团队把预算跑爆。提示:用量数据默认存在内存里,重启就丢了。生产环境一定要配database_url,把数据持久化到PostgreSQL。6. 踩坑实录:那些文档里不会写的问题6.1 模型名称拼写与版本陷阱litellm的模型标识必须和厂商官方名称完全一致,但各家的命名风格差异很大。OpenAI用gpt-4o,Anthropic用claude-3-5-sonnet-20241022,Google用gemini/gemini-1.5-pro。有些模型有多个版本别名,比如claude-3-sonnet和claude-3-sonnet-20240229指向同一个模型,但litellm可能只认其中一个。我踩过的坑是:用了一个厂商文档里的旧版本名称,litellm报“模型不存在”。排查了半天才发现是版本号的问题。建议的做法是,在litellm的官方文档里查确认的模型标识,不要凭记忆写。6.2 环境变量读取的优先级问题litellm读取API Key的优先级是:显式传参 > 环境变量 > 配置文件。这个规则本身没问题,但在多环境部署时容易出问题。比如开发环境设了OPENAI_API_KEY,测试环境忘了设,代码里又没显式传,结果调用时用的是开发环境的密钥,账单算到了开发账号上。我的经验是:在代码里显式传api_key参数,或者用配置管理工具统一注入环境变量,不要依赖系统环境变量的隐式继承。6.3 流式输出中的异常处理流式输出最怕的是“输出到一半断了”。网络抖动、模型超时、内容过滤触发,都可能导致流中断。litellm会抛出异常,但异常发生的时机不确定——可能在第一个chunk就抛,也可能在输出了一半之后才抛。处理这种问题的关键是记录已输出的内容。在循环里维护一个缓冲区,每次收到chunk就追加进去。异常发生时,缓冲区里的内容就是已经成功输出的部分,可以据此决定是重试、降级还是返回部分结果。6.4 不同模型的Token计算差异litellm提供了token_counter函数来估算token数量,但要注意:不同模型的分词器不同,同一个文本在不同模型下的token数可能差很多。中文尤其明显,有些模型一个汉字算一个token,有些算两个。如果你用token数来做预算控制或截断,一定要用目标模型对应的分词器来算。litellm的token_counter支持传model参数,会尽量用对应的分词器,但覆盖范围有限。对于不支持的模型,它会退回到一个通用估算,误差可能比较大。6.5 代理模式下的并发与性能代理服务器模式下,litellm本身会成为性能瓶颈。默认配置下,它用异步IO处理请求,并发能力还不错。但如果你的QPS很高,或者请求的响应体很大,可能需要调整worker数量和超时参数。实测下来,单实例litellm代理在普通配置的服务器上,处理流式请求的并发能力在几百级别。再高就需要加实例做负载均衡。另外,代理模式下的日志记录会带来额外的IO开销,生产环境建议把日志级别调到WARNING以上。7. 多模型架构中的实战策略7.1 按任务类型路由模型有了litellm之后,多模型架构变得可行。一个常见的策略是按任务类型路由:简单任务用便宜的小模型,复杂任务用能力强的大模型。def route_model(task_type): if task_type == "classification": return "gpt-4o-mini" elif task_type == "reasoning": return "claude-3-5-sonnet" elif task_type == "local": return "ollama/llama3" else: return "gpt-4o"这种策略能在保证效果的前提下显著降低成本。分类、提取、格式化这类任务,小模型完全够用,没必要上大模型。7.2 成本对比与选型参考不同模型的定价差异巨大。以每百万token的输入价格为例(具体价格会变动,这里只做量级参考):模型输入价格量级适用场景GPT-4o较高复杂推理、多模态GPT-4o-mini低分类、提取、简单对话Claude 3.5 Sonnet中等长文本、代码生成Gemini 1.5 Flash低高吞吐、低成本场景国产模型较低中文场景、成本敏感本地模型仅硬件成本数据隐私、离线场景选型的时候不要只看价格,还要考虑响应速度、稳定性、中文能力、上下文长度等因素。litellm的价值在于让你能低成本地做A/B测试,用真实数据来指导选型。7.3 降级链的设计原则降级链的配置有几个原则。第一,能力要递减但不能断崖。主模型和备用模型的能力差距太大,降级后的用户体验会很差。第二,要考虑成本。降级链的末端通常是本地模型或便宜的小模型,用来兜底。第三,要监控降级频率。如果降级频繁发生,说明主模型不稳定,需要排查原因。response = completion( model="gpt-4o", messages=messages, fallbacks=["claude-3-5-sonnet", "gpt-4o-mini", "ollama/llama3"], num_retries=2 )这个配置的意思是:先试GPT-4o,重试2次;失败后试Claude;再失败试GPT-4o-mini;最后用本地Llama兜底。7.4 缓存策略减少重复调用litellm支持缓存,可以把相同请求的响应存起来,避免重复调用:import litellm from litellm.caching import Cache litellm.cache = Cache(type="redis", host="localhost", port=6379) response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], caching=True )缓存对开发调试和测试特别有用——同样的请求不用反复花钱。生产环境中,对于FAQ、模板化回复这类场景,缓存也能显著降低成本。但要注意,缓存key是基于请求参数的哈希,如果请求里有随机因素(比如temperature>0),缓存命中率会很低。8. 我个人的使用体会与几个实用建议用litellm有一段时间了,最大的感受是它确实解决了多模型调用的核心痛点,但也不是银弹。它把“调用”这一层统一了,但模型之间的能力差异、参数差异、行为差异依然存在。你不能指望换一个模型标识,输出质量就完全一样。几个实际使用中的建议。第一,在项目早期就引入litellm,哪怕你暂时只用一个模型。因为等到需要切换的时候再改造,成本会高很多。第二,把模型标识做成配置项,不要硬编码在代码里。这样切换模型只需要改配置,不用改代码、不用重新部署。第三,做好日志和监控。litellm本身提供了一些回调机制,可以接入你现有的监控体系,记录每个请求的模型、耗时、token消耗、成功与否。这些数据是后续优化选型和成本控制的基础。还有一个容易被忽略的点:litellm的版本更新比较频繁,新模型的支持、bug修复、行为变更都可能出现在小版本里。建议锁定版本号,升级前先在测试环境验证。我遇到过升级后某个模型的默认参数变了,导致输出格式不符合预期的情况。最后说一个实际场景。我们有个项目需要同时调用三个不同厂商的模型做结果对比,用litellm之后,对比逻辑的代码量减少了大概70%。原来每个模型一套调用代码、一套异常处理、一套结果解析,现在一套代码搞定。省下来的时间可以花在更有价值的事情上,比如prompt优化和效果评估。这大概就是工具的价值——不是让你做更多的事,而是让你少做重复的事。
1. 从一个真实困境说起:多模型调用的碎片化之痛如果你正在做AI应用开发,大概率遇到过这样的场景:项目初期用OpenAI的接口跑通了原型,效果不错,上线后老板说“试试Claude吧,听说长文本更强”,于是你翻出Anthropic的文档重新对接一遍;过两天产品经理又说“国产模型成本低,能不能加个通义千问做兜底”,你又得去研究DashScope的SDK;再后来客户要求私有化部署,得接本地跑的开源模型,又是一套完全不同的调用方式。每个模型厂商都有自己的SDK、自己的鉴权方式、自己的参数命名、自己的返回格式。OpenAI用messages数组传对话历史,Anthropic把系统提示单独拎出来叫system,Google的Gemini又是另一套contents结构。你写了一套业务逻辑,换一个模型就得改一遍代码,测试用例也得跟着重写。更别提流式输出、函数调用、多模态输入这些高级特性,每家实现细节都不一样,光是适配层就能吃掉你一半的开发时间。这就是litellm要解决的核心问题。它本质上是一个统一的LLM调用层——用一套OpenAI格式的接口,去调用市面上几乎所有主流大模型。你不需要为每个厂商单独写适配代码,不需要记十几套不同的参数名,不需要在切换模型时重写业务逻辑。改一个字符串,就能从GPT-4切到Claude,再切到本地部署的Llama,业务代码一行不动。这篇文章适合所有正在做AI应用开发的人——不管你是刚接触大模型调用的新手,还是已经被多模型适配折磨过的老手。我会从实际使用角度出发,把litellm的核心机制、实操细节、踩坑经验一次讲透。读完你至少能搞清楚三件事:litellm到底怎么用、为什么这样设计、以及在实际项目中怎么避坑。2. litellm的定位与核心机制拆解2.1 它到底解决了什么问题先明确一个概念:litellm不是模型,不是推理框架,也不是Agent框架。它是一个中间层,夹在你的业务代码和各个模型厂商的API之间。你可以把它理解成一个“翻译官”——你的业务代码只说一种语言(OpenAI格式),litellm负责把它翻译成各个厂商能听懂的话,再把厂商的回复翻译回OpenAI格式。这个定位决定了它的价值边界。它不提升模型能力,不优化推理速度,不管理GPU资源。它只做一件事:抹平不同模型API之间的差异。所以如果你的项目只用一个模型,而且不打算换,那litellm对你的价值有限。但只要你有“多模型”的需求——不管是切换、对比、兜底还是成本优化——它就能省掉大量重复劳动。2.2 统一接口的设计哲学litellm的核心设计思路可以用一句话概括:以OpenAI的API格式为基准,向上提供统一接口,向下适配各家差异。为什么选OpenAI格式作为基准?原因很实际。OpenAI的Chat Completions接口是目前事实上的行业标准,绝大多数开发者最先接触的就是它,文档最全、示例最多、社区最活跃。而且大量开源工具和框架(比如LangChain、LlamaIndex)默认就支持OpenAI格式。以它为基准,意味着你的代码不仅能调litellm,还能无缝切换到其他支持OpenAI格式的工具上,不会被锁定。具体到调用方式,litellm的completion函数签名几乎和OpenAI的官方SDK一模一样:from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] )区别只在于model参数。你在这里填不同的模型标识,litellm就会自动路由到对应的厂商。比如填claude-3-5-sonnet-20241022就走Anthropic,填gemini/gemini-1.5-pro就走Google,填ollama/llama3就走本地Ollama。业务代码完全不用改。2.3 模型路由的命名规则litellm用一套命名规则来识别模型归属。基本格式是provider/model-name,比如anthropic/claude-3-opus、gemini/gemini-1.5-flash、ollama/llama3。对于OpenAI自家的模型,provider可以省略,直接写gpt-4o就行。这套规则看起来简单,但实际用起来有几个细节值得注意。第一,模型名称必须和厂商官方文档一致,拼错一个字符就会报错。第二,有些厂商的模型有多个版本别名,litellm通常都能识别,但建议用最明确的那个。第三,本地模型的provider标识取决于你用的推理框架,Ollama是ollama/,vLLM是hosted_vllm/,各有各的写法。提示:如果你不确定某个模型的正确标识,可以查litellm的官方文档,或者在代码里打印litellm.model_list查看支持的完整列表。2.4 支持的模型范围litellm覆盖的模型范围相当广,大致可以分成几类:类别代表厂商/框架典型模型标识商业闭源APIOpenAI、Anthropic、Google、Mistralgpt-4o、claude-3-5-sonnet、gemini-1.5-pro国内商业API通义千问、智谱、月之暗面、深度求索dashscope/qwen-max、zhipu/glm-4、moonshot/kimi云平台托管Azure OpenAI、AWS Bedrock、Vertex AIazure/gpt-4、bedrock/claude-3本地推理框架Ollama、vLLM、HuggingFace TGIollama/llama3、hosted_vllm/mistral聚合平台OpenRouter、Together AIopenrouter/anthropic/claude-3这个覆盖面基本涵盖了目前主流的模型来源。对于国内开发者来说,通义千问、智谱、深度求索这些国产模型的直接支持尤其实用,不需要自己写适配层。3. 环境搭建与最小可运行示例3.1 安装与依赖管理litellm的安装非常轻量,核心包不依赖任何特定厂商的SDK:pip install litellm如果你需要用到某些特定功能,可以按需安装额外依赖。比如要用代理服务器功能,装litellm[proxy];要用某些云平台的托管服务,装对应的extra。但大多数场景下,基础安装就够了。这里有个经验:建议在虚拟环境里安装。litellm的依赖树虽然不复杂,但它会引入一些HTTP客户端和数据处理库,和项目里已有的包偶尔会有版本冲突。用venv或conda隔离一下,省得后面排查依赖问题浪费时间。3.2 API Key的配置方式litellm读取API Key的方式和OpenAI SDK一致——从环境变量里读。每个厂商对应不同的环境变量名:export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..." export GEMINI_API_KEY="..." export DASHSCOPE_API_KEY="..."这种设计的好处是密钥不硬编码在代码里,符合安全实践。但实际项目中,你可能需要从配置文件或密钥管理服务里动态加载。litellm也支持在调用时直接传api_key参数,或者通过litellm.api_key全局设置。注意:如果你在代码里同时设置了环境变量和显式传参,显式传参的优先级更高。这个规则在调试多密钥场景时很有用。3.3 第一个可运行的调用装好之后,写个最简单的调用验证一下:import os from litellm import completion os.environ["OPENAI_API_KEY"] = "你的密钥" response = completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话解释什么是API。"} ] ) print(response.choices[0].message.content)返回结构和OpenAI官方SDK完全一致,response.choices[0].message.content就是模型输出的文本。如果你之前用过OpenAI的接口,这段代码不需要任何额外学习成本。3.4 切换到其他模型的实测现在把model参数改一下,试试其他厂商:# 切换到Anthropic response = completion( model="claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": "用一句话解释什么是API。"}] ) # 切换到本地Ollama response = completion( model="ollama/llama3", messages=[{"role": "user", "content": "用一句话解释什么是API。"}] )业务代码除了model这一行,其他完全没变。这就是litellm最核心的价值——用最小的改动成本实现模型切换。实测下来,不同模型的响应格式在litellm的封装下高度一致,但有几个细节需要注意。第一,某些厂商的模型不支持system角色,litellm会自动把它合并到第一条用户消息里。第二,不同模型的max_tokens默认值不同,有些厂商不传这个参数会报错,建议显式指定。第三,流式输出的chunk结构虽然统一了,但首包延迟差异很大,做交互式应用时要考虑这一点。4. 进阶能力:流式、函数调用与多模态4.1 流式输出的统一处理流式输出是对话类应用的标配。litellm把各家的流式接口统一成了OpenAI的SSE格式:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "写一首关于编程的短诗。"}], stream=True ) for chunk in response: content = chunk.choices[0].delta.content if content: print(content, end="", flush=True)这段代码换成Claude或Gemini,结构完全一样。litellm在底层处理了各家流式协议的差异——比如Anthropic的流式事件类型和OpenAI不同,Gemini的chunk结构也不一样,但到了上层都统一成了choices[0].delta.content。这里有个实操心得:流式场景下要特别注意异常处理。网络中断、模型超时、内容过滤触发,都可能在流的中途发生。litellm会抛出异常,但异常的时机和类型各家不同。建议在循环里包一层try-except,并且记录已经输出的内容,方便断点续传或降级处理。4.2 函数调用的跨模型适配函数调用(Function Calling)是构建Agent和工具集成应用的基础。litellm同样做了统一:tools = [{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } }] response = completion( model="gpt-4o", messages=[{"role": "user", "content": "北京今天天气怎么样?"}], tools=tools, tool_choice="auto" )返回的tool_calls结构也是统一的。但要注意,不是所有模型都支持函数调用,有些开源模型需要通过特定的prompt模板来模拟,效果参差不齐。litellm会在不支持时抛出明确的错误,而不是静默失败,这一点做得比较友好。4.3 多模态输入的处理差异多模态输入是另一个差异巨大的领域。OpenAI的格式是content数组里放{"type": "text"}和{"type": "image_url"},Anthropic的格式又不一样。litellm统一成了OpenAI的写法:response = completion( model="gpt-4o", messages=[{ "role": "user", "content": [ {"type": "text", "text": "这张图里有什么?"}, {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}} ] }] )换成Claude或Gemini,代码不变。但实测发现,不同模型对图片格式和大小的限制不同。有些只支持URL不支持base64,有些对图片分辨率有上限,有些对图片数量有限制。这些限制litellm没法帮你绕过,需要在使用前查清楚目标模型的具体要求。4.4 重试、超时与降级策略生产环境中,API调用失败是常态。litellm内置了重试机制:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], num_retries=3, timeout=30 )num_retries控制重试次数,timeout控制单次请求超时。litellm的重试是带指数退避的,不会在失败后立刻重试导致雪崩。更实用的是降级策略。你可以配置一个模型列表,litellm会按顺序尝试,直到有一个成功:response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], fallbacks=["claude-3-5-sonnet", "ollama/llama3"] )这个功能在多模型架构里非常关键。主模型挂了自动切备用,备用也挂了切本地兜底,保证服务可用性。配置的时候要注意,fallback列表里的模型最好在能力上接近,否则降级后的输出质量会有明显落差。5. 代理服务器模式:把litellm变成统一网关5.1 为什么需要代理模式前面讲的都是Python代码里直接调用litellm。但在实际项目中,你的技术栈可能不全是Python——前端用JavaScript,后端有Go服务,还有几个Java微服务。这时候在每个语言里都集成litellm的SDK不现实。litellm提供了一个代理服务器模式,把统一调用能力封装成一个HTTP服务。任何语言、任何框架,只要能发HTTP请求,就能用上litellm的能力。这相当于在你的基础设施里加了一个LLM网关。5.2 配置文件的结构代理模式通过一个YAML配置文件来定义模型列表和服务参数:model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-llama litellm_params: model: ollama/llama3 api_base: http://localhost:11434 general_settings: master_key: sk-your-master-key database_url: postgresql://user:pass@localhost:5432/litellmmodel_list定义对外暴露的模型名称和实际路由目标。general_settings里配置主密钥和数据库连接(用于记录用量和做预算控制)。5.3 启动与调用启动代理服务器:litellm --config config.yaml --port 4000然后用任何HTTP客户端调用:curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{ "model": "claude", "messages": [{"role": "user", "content": "你好"}] }'接口格式和OpenAI完全兼容。这意味着你现有的任何基于OpenAI SDK的代码,只需要把base_url改成代理地址,就能直接调用所有配置的模型。5.4 用量追踪与预算控制代理模式的一个隐藏价值是集中化的用量管理。所有请求都经过网关,litellm可以记录每个请求的token消耗、响应时间、模型分布,并据此做预算控制。你可以在配置里给每个模型或每个用户设置预算上限:model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o model_info: max_budget: 100 budget_duration: 30d超过预算后,litellm会拒绝请求并返回明确的错误。这个功能在多团队共用一套API密钥的场景下特别有用,能防止某个团队把预算跑爆。提示:用量数据默认存在内存里,重启就丢了。生产环境一定要配database_url,把数据持久化到PostgreSQL。6. 踩坑实录:那些文档里不会写的问题6.1 模型名称拼写与版本陷阱litellm的模型标识必须和厂商官方名称完全一致,但各家的命名风格差异很大。OpenAI用gpt-4o,Anthropic用claude-3-5-sonnet-20241022,Google用gemini/gemini-1.5-pro。有些模型有多个版本别名,比如claude-3-sonnet和claude-3-sonnet-20240229指向同一个模型,但litellm可能只认其中一个。我踩过的坑是:用了一个厂商文档里的旧版本名称,litellm报“模型不存在”。排查了半天才发现是版本号的问题。建议的做法是,在litellm的官方文档里查确认的模型标识,不要凭记忆写。6.2 环境变量读取的优先级问题litellm读取API Key的优先级是:显式传参 > 环境变量 > 配置文件。这个规则本身没问题,但在多环境部署时容易出问题。比如开发环境设了OPENAI_API_KEY,测试环境忘了设,代码里又没显式传,结果调用时用的是开发环境的密钥,账单算到了开发账号上。我的经验是:在代码里显式传api_key参数,或者用配置管理工具统一注入环境变量,不要依赖系统环境变量的隐式继承。6.3 流式输出中的异常处理流式输出最怕的是“输出到一半断了”。网络抖动、模型超时、内容过滤触发,都可能导致流中断。litellm会抛出异常,但异常发生的时机不确定——可能在第一个chunk就抛,也可能在输出了一半之后才抛。处理这种问题的关键是记录已输出的内容。在循环里维护一个缓冲区,每次收到chunk就追加进去。异常发生时,缓冲区里的内容就是已经成功输出的部分,可以据此决定是重试、降级还是返回部分结果。6.4 不同模型的Token计算差异litellm提供了token_counter函数来估算token数量,但要注意:不同模型的分词器不同,同一个文本在不同模型下的token数可能差很多。中文尤其明显,有些模型一个汉字算一个token,有些算两个。如果你用token数来做预算控制或截断,一定要用目标模型对应的分词器来算。litellm的token_counter支持传model参数,会尽量用对应的分词器,但覆盖范围有限。对于不支持的模型,它会退回到一个通用估算,误差可能比较大。6.5 代理模式下的并发与性能代理服务器模式下,litellm本身会成为性能瓶颈。默认配置下,它用异步IO处理请求,并发能力还不错。但如果你的QPS很高,或者请求的响应体很大,可能需要调整worker数量和超时参数。实测下来,单实例litellm代理在普通配置的服务器上,处理流式请求的并发能力在几百级别。再高就需要加实例做负载均衡。另外,代理模式下的日志记录会带来额外的IO开销,生产环境建议把日志级别调到WARNING以上。7. 多模型架构中的实战策略7.1 按任务类型路由模型有了litellm之后,多模型架构变得可行。一个常见的策略是按任务类型路由:简单任务用便宜的小模型,复杂任务用能力强的大模型。def route_model(task_type): if task_type == "classification": return "gpt-4o-mini" elif task_type == "reasoning": return "claude-3-5-sonnet" elif task_type == "local": return "ollama/llama3" else: return "gpt-4o"这种策略能在保证效果的前提下显著降低成本。分类、提取、格式化这类任务,小模型完全够用,没必要上大模型。7.2 成本对比与选型参考不同模型的定价差异巨大。以每百万token的输入价格为例(具体价格会变动,这里只做量级参考):模型输入价格量级适用场景GPT-4o较高复杂推理、多模态GPT-4o-mini低分类、提取、简单对话Claude 3.5 Sonnet中等长文本、代码生成Gemini 1.5 Flash低高吞吐、低成本场景国产模型较低中文场景、成本敏感本地模型仅硬件成本数据隐私、离线场景选型的时候不要只看价格,还要考虑响应速度、稳定性、中文能力、上下文长度等因素。litellm的价值在于让你能低成本地做A/B测试,用真实数据来指导选型。7.3 降级链的设计原则降级链的配置有几个原则。第一,能力要递减但不能断崖。主模型和备用模型的能力差距太大,降级后的用户体验会很差。第二,要考虑成本。降级链的末端通常是本地模型或便宜的小模型,用来兜底。第三,要监控降级频率。如果降级频繁发生,说明主模型不稳定,需要排查原因。response = completion( model="gpt-4o", messages=messages, fallbacks=["claude-3-5-sonnet", "gpt-4o-mini", "ollama/llama3"], num_retries=2 )这个配置的意思是:先试GPT-4o,重试2次;失败后试Claude;再失败试GPT-4o-mini;最后用本地Llama兜底。7.4 缓存策略减少重复调用litellm支持缓存,可以把相同请求的响应存起来,避免重复调用:import litellm from litellm.caching import Cache litellm.cache = Cache(type="redis", host="localhost", port=6379) response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], caching=True )缓存对开发调试和测试特别有用——同样的请求不用反复花钱。生产环境中,对于FAQ、模板化回复这类场景,缓存也能显著降低成本。但要注意,缓存key是基于请求参数的哈希,如果请求里有随机因素(比如temperature>0),缓存命中率会很低。8. 我个人的使用体会与几个实用建议用litellm有一段时间了,最大的感受是它确实解决了多模型调用的核心痛点,但也不是银弹。它把“调用”这一层统一了,但模型之间的能力差异、参数差异、行为差异依然存在。你不能指望换一个模型标识,输出质量就完全一样。几个实际使用中的建议。第一,在项目早期就引入litellm,哪怕你暂时只用一个模型。因为等到需要切换的时候再改造,成本会高很多。第二,把模型标识做成配置项,不要硬编码在代码里。这样切换模型只需要改配置,不用改代码、不用重新部署。第三,做好日志和监控。litellm本身提供了一些回调机制,可以接入你现有的监控体系,记录每个请求的模型、耗时、token消耗、成功与否。这些数据是后续优化选型和成本控制的基础。还有一个容易被忽略的点:litellm的版本更新比较频繁,新模型的支持、bug修复、行为变更都可能出现在小版本里。建议锁定版本号,升级前先在测试环境验证。我遇到过升级后某个模型的默认参数变了,导致输出格式不符合预期的情况。最后说一个实际场景。我们有个项目需要同时调用三个不同厂商的模型做结果对比,用litellm之后,对比逻辑的代码量减少了大概70%。原来每个模型一套调用代码、一套异常处理、一套结果解析,现在一套代码搞定。省下来的时间可以花在更有价值的事情上,比如prompt优化和效果评估。这大概就是工具的价值——不是让你做更多的事,而是让你少做重复的事。