1. 从一个真实困境说起多模型调用的碎片化之痛如果你正在做AI应用开发大概率遇到过这样的场景项目初期用OpenAI的接口跑通了原型效果不错上线后老板说“能不能加个Claude做对比”你花了两天适配Anthropic的SDK过了一周产品经理又说“国内用户访问不稳定接一下通义千问做兜底”你又花了一天研究DashScope的调用方式再过两周成本部门要求“把简单请求路由到便宜的小模型”你发现每个厂商的计费口径、token计算方式、流式返回格式全都不一样整个人直接裂开。这不是个别现象。我在过去一年里接触过十几个AI应用团队几乎每一个都经历过“每接一个模型就重写一遍调用层”的痛苦。问题的根源在于大模型API市场没有事实标准。OpenAI的chat.completions格式虽然被很多厂商模仿但细节差异巨大——参数命名不同、返回结构不同、流式chunk的字段不同、错误码体系不同、甚至同一个概念在不同厂商那里叫法都不一样。litellm就是为了解决这个问题而生的。它的定位非常清晰用一套OpenAI格式的接口调用市面上几乎所有主流大模型。你写一次代码改一个字符串就能从GPT-4切到Claude再改一个字符串就能切到Gemini或者通义千问。对于需要做模型对比、成本优化、多模型兜底的团队来说这几乎是刚需。这篇文章我会从实际使用角度出发把litellm的核心机制、接入方式、路由策略、成本追踪、踩坑经验全部讲透。不管你是刚接触多模型调用的新手还是已经在用litellm但没发挥出全部能力的老手应该都能从中找到有用的东西。2. litellm到底做了什么核心抽象层拆解2.1 统一接口的本质是一层翻译很多人第一次看到litellm的介绍会有一个误解以为它是一个“代理服务”或者“网关”。其实litellm的核心是一个Python SDK它做的事情用一句话概括就是接收OpenAI格式的请求翻译成目标厂商的格式发出去再把返回结果翻译回OpenAI格式。这个翻译过程涉及几个关键环节。第一是参数映射比如OpenAI的max_tokens在Anthropic那里叫max_tokens_to_sample在Gemini那里叫max_output_tokenslitellm帮你自动转换。第二是消息格式转换OpenAI的messages数组里role可以是system、user、assistant但Anthropic把system单独拿出来放在顶层参数里litellm会自动拆分。第三是返回结构归一化不管你调的是哪家模型拿到的response.choices[0].message.content格式都是一样的。我实测下来这个翻译层的覆盖度相当高。文本对话、流式输出、函数调用、视觉输入、嵌入向量这些常见场景都支持。但要注意不是所有厂商的所有能力都被完整覆盖比如某些厂商特有的“联网搜索”或者“代码解释器”功能litellm可能没有对应的统一抽象这时候你还是得走原生SDK。2.2 支持的模型范围与命名规则litellm支持的模型列表非常长我粗略数了一下覆盖了OpenAI、Anthropic、Google、Meta、Mistral、Cohere、AI21、以及国内的通义千问、智谱、百川、月之暗面等几十家厂商的几百个模型。调用时的命名规则是provider/model_name的格式。比如openai/gpt-4o调用OpenAI的GPT-4oanthropic/claude-3-5-sonnet-20241022调用Claude 3.5 Sonnetgemini/gemini-1.5-pro调用Google Geminidashscope/qwen-max调用通义千问deepseek/deepseek-chat调用DeepSeek这个命名规则的好处是自描述性很强看代码就知道在调哪家模型。但有一个坑要注意同一个模型在不同渠道可能有不同的名称比如Azure OpenAI上的部署名是你自己起的这时候需要用azure/your-deployment-name的格式并且额外配置api_base和api_version。提示litellm的模型列表更新非常频繁新模型发布后通常几天内就会支持。如果你要用的模型不在列表里可以先查官方文档的provider列表确认是否支持自定义接入。2.3 两种使用模式SDK直调与代理网关litellm提供两种使用方式这是很多人一开始会混淆的地方。第一种是Python SDK直调。你在代码里import litellm然后直接调completion()函数。这种方式最简单适合快速原型和小型项目。缺点是API Key散落在代码或环境变量里多项目共享时管理麻烦。第二种是Proxy Server模式。你启动一个litellm的代理服务所有请求先打到这个代理由代理根据配置转发到不同的后端模型。这种方式适合团队协作因为可以统一管理Key、统一做限流、统一记录日志和成本。代理服务本身也提供OpenAI兼容的接口所以任何支持OpenAI格式的客户端都能直接对接。我的建议是个人项目或者快速验证用SDK直调团队项目或者生产环境用Proxy模式。后面我会分别讲两种模式的配置细节。3. 从零跑通第一个多模型调用3.1 环境准备与Key管理安装很简单pip install litellm如果你要用Proxy模式额外装pip install litellm[proxy]Key的管理是第一个容易踩坑的地方。litellm读取Key的优先级是代码里显式传入 环境变量 配置文件。环境变量的命名规则是PROVIDER_API_KEY比如export OPENAI_API_KEYsk-xxx export ANTHROPIC_API_KEYsk-ant-xxx export DASHSCOPE_API_KEYsk-xxx这里有个细节不同厂商的Key前缀不同但litellm不校验前缀所以你如果Key配错了报错信息可能来自厂商的401而不是litellm本身。我建议在项目里用一个.env文件统一管理配合python-dotenv加载避免Key泄露到代码仓库。注意如果你在CI/CD环境里跑不要把Key写进镜像或者配置文件用环境变量注入。litellm的日志默认会打印请求信息生产环境记得关掉或者脱敏。3.2 最简调用示例与参数说明先看一个最简的调用from litellm import completion response completion( modelopenai/gpt-4o, messages[ {role: system, content: 你是一个简洁的助手}, {role: user, content: 用一句话解释什么是向量数据库} ], temperature0.7, max_tokens200 ) print(response.choices[0].message.content)这段代码和直接用OpenAI SDK几乎一模一样唯一的区别是model参数带了openai/前缀。如果你想换成Claude只需要把model改成anthropic/claude-3-5-sonnet-20241022其他代码一行不用动。completion()函数的参数基本兼容OpenAI的chat.completions.create()常用的有参数说明注意事项model模型标识必须带provider前缀messages消息数组格式同OpenAItemperature随机性部分厂商范围不同litellm会做裁剪max_tokens最大输出注意不同厂商的默认值和上限不同stream流式输出返回的是生成器需要迭代tools函数调用部分厂商支持度不同response_format结构化输出JSON模式并非所有模型都支持3.3 流式输出的处理差异流式输出是多模型调用里最容易出问题的地方。虽然litellm做了归一化但不同厂商的chunk结构还是有细微差异。标准用法response completion( modelanthropic/claude-3-5-sonnet-20241022, messages[{role: user, content: 写一首关于秋天的短诗}], streamTrue ) for chunk in response: delta chunk.choices[0].delta if delta.content: print(delta.content, end, flushTrue)这里的关键是判断delta.content是否存在。有些厂商的第一个chunk只包含role信息content是空的有些厂商的最后一个chunk是结束标记。如果你不做判断直接拼接可能会遇到None导致的类型错误。我踩过的一个坑是某些厂商在流式模式下不支持response_format参数你传了JSON模式它也不报错但返回的还是普通文本。这种情况下你需要在应用层自己做JSON解析和校验。4. 路由与兜底让多模型真正发挥价值4.1 为什么需要路由策略单模型调用是最简单的场景但实际生产环境往往需要更复杂的策略。常见的需求包括成本优化简单问题走便宜模型复杂问题走贵模型高可用兜底主模型挂了自动切备用模型灰度对比同一批请求按比例分流到不同模型对比效果地域合规不同地区的用户请求路由到不同的模型服务litellm的Router组件就是为这些场景设计的。它支持多种路由策略配置方式也比较灵活。4.2 基于成本的自动路由最实用的策略之一是成本路由。你可以在配置里给每个模型设置input_cost_per_token和output_cost_per_token然后让Router自动选择最便宜的可用模型from litellm import Router router Router( model_list[ { model_name: cheap-model, litellm_params: { model: dashscope/qwen-turbo, api_key: xxx }, model_info: {input_cost_per_token: 0.0000003} }, { model_name: strong-model, litellm_params: { model: openai/gpt-4o, api_key: xxx }, model_info: {input_cost_per_token: 0.0000025} } ], routing_strategycost-based-routing )这个配置下Router会优先选择成本低的模型。但要注意成本路由不考虑模型能力差异所以更适合“同类任务、不同价位”的场景比如都是做文本分类那就选便宜的。4.3 失败重试与降级链路比成本路由更重要的是容错。生产环境里模型API超时、限流、返回错误是常态如果没有兜底机制用户体验会非常差。litellm的Router支持配置fallbacks也就是降级链路router Router( model_list[...], fallbacks[ {strong-model: [cheap-model, backup-model]}, {cheap-model: [backup-model]} ], num_retries3, timeout30 )这段配置的意思是如果strong-model调用失败自动尝试cheap-model如果还失败再尝试backup-model。num_retries控制单个模型的重试次数timeout控制超时时间。我实测下来超时设置需要根据模型特点调整。推理型模型比如o1系列响应时间可能是普通模型的5到10倍如果你统一设30秒超时推理模型几乎必然超时。建议给不同模型设置不同的超时参数。提示降级链路不是越长越好。每多一层降级用户等待时间就多一份。我的经验是最多两层降级并且要在日志里记录降级事件方便后续分析哪些模型不稳定。4.4 负载均衡与权重分配如果你有多个相同模型的API Key比如多个Azure部署可以用Router做负载均衡model_list [ { model_name: gpt-4o-pool, litellm_params: {model: azure/deploy-1, api_key: key1}, model_info: {weight: 3} }, { model_name: gpt-4o-pool, litellm_params: {model: azure/deploy-2, api_key: key2}, model_info: {weight: 1} } ]weight参数控制流量分配比例上面这个配置会让deploy-1承担75%的流量。这个功能在某个Key限流额度快用完或者某个部署性能更好的时候特别有用。5. 成本追踪与可观测性5.1 Token计数与费用计算多模型调用最头疼的问题之一是成本不透明。每个厂商的计费方式不同有的按输入输出分开计费有的有缓存折扣有的按字符数计费。litellm内置了成本计算功能调用后可以直接拿到费用response completion(modelopenai/gpt-4o, messages[...]) print(f输入token: {response.usage.prompt_tokens}) print(f输出token: {response.usage.completion_tokens}) print(f本次费用: ${response._hidden_params[response_cost]:.6f})response_cost是litellm根据内置的价格表计算的。但要注意价格表可能滞后于厂商调价如果你对成本精度要求高建议定期核对官方价格并手动覆盖。5.2 回调机制与日志记录litellm支持多种回调可以在请求前后插入自定义逻辑。最常用的是success_callback和failure_callbackimport litellm def log_success(kwargs, response, start_time, end_time): # 记录到你的日志系统 print(f模型: {kwargs[model]}, 耗时: {end_time - start_time:.2f}s) litellm.success_callback [log_success]这个机制可以用来做用量统计、异常告警、审计日志。我一般会把每次调用的模型名、token数、费用、耗时、是否降级都记录下来存到数据库里方便后续做成本分析和模型效果对比。5.3 在Proxy模式下查看用量如果你用的是Proxy模式litellm自带一个管理界面可以查看每个Key的用量、每个模型的调用次数和费用。启动时加上--detailed_debug可以看到更详细的日志。Proxy模式还支持虚拟Key也就是给每个团队成员或每个项目分配一个独立的Key设置预算上限。这个功能在团队协作时非常实用可以避免某个人不小心把额度用超。6. 那些文档里不会写的踩坑经验6.1 参数兼容性的隐形陷阱litellm虽然做了参数映射但不是所有参数都能完美转换。我遇到过几个典型问题第一个是temperature的范围。OpenAI的范围是0到2但有些厂商只支持0到1。litellm会做裁剪但裁剪后的行为可能和你预期的不一样。比如你设了1.5在只支持0到1的模型上会被裁到1.0而不是报错。第二个是stop参数。OpenAI支持最多4个停止词但有些厂商只支持1个。如果你传了多个litellm可能会只取第一个或者直接报错取决于版本。第三个是tools的格式。虽然litellm做了统一但不同模型对函数调用的支持程度差异很大。有些模型能正确返回tool_calls有些模型会把函数调用混在普通文本里返回。如果你重度依赖函数调用建议先做一轮兼容性测试。6.2 流式模式下的错误处理流式模式下错误可能发生在两个阶段建立连接时和流式传输过程中。前者会直接抛异常后者可能在迭代chunk的时候才抛出。如果你的代码只包了try/except在completion()调用处流式过程中的错误可能捕获不到。正确的做法是把迭代逻辑也包在异常处理里try: response completion(model..., messages[...], streamTrue) for chunk in response: try: content chunk.choices[0].delta.content if content: yield content except Exception as e: # 处理流式过程中的错误 break except Exception as e: # 处理连接阶段的错误 pass6.3 超时与重试的平衡重试不是越多越好。我见过有团队把num_retries设成5结果一个请求最长要等好几分钟才返回失败。用户体验极差而且可能触发厂商的限流。我的建议是重试次数不超过2次单次超时根据模型类型设置。普通对话模型15到30秒推理模型60到120秒。同时要区分可重试错误和不可重试错误——限流和超时可以重试参数错误和认证失败重试也没用。6.4 版本升级的兼容性风险litellm迭代非常快几乎每周都有新版本。这带来一个风险新版本可能改变某些行为。我就遇到过升级后某个模型的默认参数变了导致输出风格突变的情况。建议在生产环境锁定版本号升级前先在测试环境跑一轮回归测试。特别是如果你用了Router和回调这些高级功能升级前一定要看changelog。7. 把litellm用好的几个进阶思路7.1 自定义Provider接入虽然litellm支持的模型很多但总有覆盖不到的。这时候可以用custom_llm_provider参数接入自定义的OpenAI兼容接口response completion( modelopenai/your-model, api_basehttps://your-endpoint/v1, api_keyyour-key, custom_llm_provideropenai, messages[...] )只要你的接口兼容OpenAI格式就能通过这种方式接入。这个技巧在对接私有化部署的模型或者公司内部模型服务时特别有用。7.2 结合缓存降低重复成本很多应用场景下相同的请求会被重复调用。litellm支持缓存可以把结果存到内存、Redis或者磁盘import litellm litellm.cache litellm.Cache(typeredis, hostlocalhost, port6379) response completion(model..., messages[...], cachingTrue)缓存命中时不会调用模型直接返回之前的结果成本为零。但要注意缓存Key的生成逻辑如果Key只包含messages那么temperature不同的请求会命中同一个缓存这可能导致意外结果。建议根据业务场景决定是否开启缓存。7.3 多模型对比的工程化做法如果你要做模型效果对比litellm可以帮你把工程复杂度降到最低。我的做法是同一批测试用例用Router按权重分流到不同模型记录每个模型的输出和费用然后人工或自动评估。具体实现上可以用model_name相同的多个配置设置不同的weight然后在回调里记录实际使用的模型。这样你不需要改业务代码只需要调整配置就能做A/B测试。8. 一些个人体会litellm这个工具我用了大概半年多最大的感受是它把多模型调用的门槛从“每个模型读一遍文档”降到了“改一个字符串”。对于需要快速验证多个模型效果的团队来说节省的时间是实打实的。但它也不是银弹。抽象层永远会丢失一些底层细节如果你需要用到某个模型的独有功能还是得走原生SDK。我的建议是用litellm做统一调用和路由用原生SDK做深度定制两者结合而不是非此即彼。另外多模型调用带来的测试复杂度是很多人低估的。每接一个新模型你都需要重新验证一遍参数兼容性、流式行为、错误处理、成本计算。如果没有一套自动化的回归测试很容易在切换模型时出问题。这个投入是值得的但要有心理准备。最后说一个细节litellm的日志默认会打印完整的请求和响应包括你的prompt内容。如果prompt里有敏感信息记得在生产环境关掉详细日志或者做脱敏处理。这个坑我踩过一次还好发现得早。