1. 从一个真实困境说起多模型调用的碎片化之痛如果你做过任何跟大语言模型相关的应用开发大概率经历过这样一个阶段项目初期只接了一家模型服务商的接口代码写得干净利落几十行就能跑通。但很快产品经理或者业务方就会提出新需求——“能不能同时支持另外几家的模型”“用户想自己选模型怎么办”“某家的接口今天不太稳定能不能自动切到备用的”这时候你打开代码一看每个服务商的SDK不一样、认证方式不一样、请求参数命名不一样、返回结构不一样、流式输出的格式不一样、错误码的定义也不一样。想把它们统一起来要么写一大堆适配层代码要么在每个业务逻辑里塞满if-else。更麻烦的是当你辛辛苦苦适配了三五家之后发现又出了新的模型服务又得从头来一遍。这就是litellm要解决的核心问题。它是一个用Python写的开源库目标非常明确用一套OpenAI风格的统一接口去调用市面上几乎所有主流的大语言模型服务。你只需要把原来调用OpenAI的那几行代码里的模型名称改一下就能切换到别的服务商请求格式、返回格式、流式处理方式基本保持一致。这篇文章适合哪些人看如果你正在做AI应用开发需要接入多个模型服务如果你在搭建内部工具平台希望让团队成员不用关心底层是哪家模型如果你在做模型评测或对比实验需要频繁切换不同模型——那litellm这个工具值得你花时间了解一下。我会从它的设计思路、核心用法、实际踩坑经验、进阶配置几个维度展开尽量把我在实际项目中积累的经验都倒出来。2. litellm到底统一了什么核心抽象层拆解2.1 它不是在造新轮子而是在做翻译层很多人第一次听说litellm会误以为它是一个新的模型推理框架或者服务部署工具。其实不是。litellm的定位非常清晰它是一个客户端侧的适配层本身不做模型推理也不部署模型。它做的事情是接收你用OpenAI格式写的请求把它翻译成目标服务商能理解的格式发出去拿到响应后再翻译回OpenAI格式返回给你。这个设计思路的好处在于你不需要改变自己的编程习惯。如果你之前用过OpenAI的Python SDK那用litellm几乎零学习成本。原来怎么写现在还怎么写只是import的包和模型名称变了。举个最直观的例子。原来你调用某家模型的代码可能是这样的from openai import OpenAI client OpenAI(api_keyyour-key, base_urlhttps://api.openai.com/v1) response client.chat.completions.create( modelgpt-4, messages[{role: user, content: 你好}] ) print(response.choices[0].message.content)换成litellm之后from litellm import completion response completion( modelgpt-4, messages[{role: user, content: 你好}] ) print(response.choices[0].message.content)看起来差别不大对吧但关键在于当你想换成另一家模型时只需要把model参数改成对应的标识符比如claude-3-sonnet或者gemini-pro其他代码一行都不用动。litellm会自动处理认证、请求格式转换、响应解析这些脏活累活。2.2 模型命名规则前缀决定路由litellm识别不同服务商的方式是通过模型名称的前缀。这个设计很巧妙既保持了简洁性又避免了歧义。比如前缀对应服务商类型示例模型名无前缀或openai/OpenAI兼容接口gpt-4、gpt-3.5-turboanthropic/Anthropic系列anthropic/claude-3-sonnetgemini/Google系列gemini/gemini-proazure/Azure部署的模型azure/my-deploymentollama/本地Ollama服务ollama/llama3huggingface/HuggingFace推理端点huggingface/meta-llama/Llama-3-8Bbedrock/云服务商托管模型bedrock/anthropic.claude-v2这个前缀机制的好处是你可以在同一个项目里混用多家模型而且代码结构完全一致。比如你可以在一个对话流程里先用一个模型做意图识别再用另一个模型生成回复代码写起来非常自然。2.3 统一的不只是接口还有异常和流式处理实际项目中最让人头疼的往往不是正常调用而是异常处理。不同服务商的错误码体系完全不同有的返回HTTP状态码有的在响应体里塞错误信息有的用自定义异常类。litellm在这方面做了大量工作它把各种服务商的异常统一映射成了OpenAI风格的异常类型。比如你捕获litellm.exceptions.RateLimitError不管底层是哪家服务商触发了限流你都能用同一套逻辑处理。这对于需要做重试、降级、熔断的系统来说价值非常大。流式输出也是类似。不同服务商的流式返回格式差异很大有的用SSE有的用WebSocket有的在chunk结构上完全不同。litellm把这些都统一成了OpenAI的流式格式你只需要用同样的for chunk in response:循环就能处理所有服务商的流式输出。3. 从零跑通第一个多模型调用环境与实操3.1 安装与密钥管理安装litellm非常简单一条命令搞定pip install litellm但这里有个实际项目中容易忽略的点密钥管理。litellm支持从环境变量读取各服务商的API Key这是推荐的做法。你需要设置的环境变量名称遵循一定规律export OPENAI_API_KEYsk-... export ANTHROPIC_API_KEYsk-ant-... export GEMINI_API_KEY... export AZURE_API_KEY... export AZURE_API_BASEhttps://your-resource.openai.azure.com/ export AZURE_API_VERSION2024-02-01注意不要把密钥硬编码在代码里也不要把包含密钥的文件提交到版本控制系统。我在实际项目中见过因为把密钥写在测试脚本里然后不小心push到公共仓库导致密钥泄露的案例处理起来非常麻烦。如果你不想用环境变量也可以在调用时直接传参response completion( modelanthropic/claude-3-sonnet, messages[{role: user, content: 你好}], api_keyyour-anthropic-key )但这种方式不利于密钥轮换和多环境管理只建议在快速验证时使用。3.2 第一次跨模型调用对比测试装好之后我建议你先做一个简单的对比测试感受一下litellm的统一性。写一个脚本用同一个问题分别调用两到三家不同的模型from litellm import completion models [ gpt-3.5-turbo, anthropic/claude-3-haiku, gemini/gemini-pro ] question 用一句话解释什么是递归 for model in models: try: response completion( modelmodel, messages[{role: user, content: question}], max_tokens100 ) print(f模型: {model}) print(f回复: {response.choices[0].message.content}) print(f消耗token: {response.usage.total_tokens}) print(- * 50) except Exception as e: print(f模型 {model} 调用失败: {e})这段代码跑通之后你会直观地感受到litellm的价值同一套代码结构只需要改一个字符串就能切换模型。而且返回的response对象结构完全一致你可以用同样的方式提取内容、统计token消耗。3.3 流式输出统一格式下的细节差异流式输出是实际产品中非常常用的功能litellm也做了统一。用法如下from litellm import completion response completion( modelanthropic/claude-3-sonnet, messages[{role: user, content: 写一首关于春天的短诗}], streamTrue ) for chunk in response: content chunk.choices[0].delta.content if content: print(content, end, flushTrue)看起来和OpenAI的流式用法一模一样。但实际使用中有一个细节需要注意不同服务商的流式chunk中content字段可能为None。比如在某些服务商的实现中第一个chunk可能只包含角色信息content是空的最后一个chunk可能只包含结束标记。所以你在处理时一定要做空值判断否则会报错。另外流式模式下token统计的获取方式也略有不同。有些服务商会在最后一个chunk中返回usage信息有些则不会。如果你需要精确统计token消耗建议在流式场景下额外做一次非流式的调用或者查阅对应服务商的文档确认其流式响应中是否包含usage字段。4. 实际项目中绕不开的坑与应对策略4.1 参数映射的隐性差异litellm虽然做了大量统一工作但不同服务商支持的参数并不完全一致。比如OpenAI支持temperature、top_p、frequency_penalty、presence_penalty等参数但有些服务商可能不支持其中某几个或者取值范围不同。我遇到过的一个典型问题是某家服务商的temperature取值范围是0到1而OpenAI是0到2。如果你在代码里统一传了1.5在OpenAI上没问题但到那家服务商就会报错。litellm在这种情况下通常会做参数裁剪或者抛出明确的异常但你需要知道这个差异的存在。应对策略是在代码中维护一个各服务商参数支持情况的配置表在调用前做参数校验和适配。litellm本身也提供了一些参数映射的配置能力可以在初始化时指定。4.2 超时与重试默认值不一定适合生产litellm的默认超时时间对于快速测试是够用的但在生产环境中特别是调用一些响应较慢的模型时可能需要调整。你可以这样设置response completion( modelanthropic/claude-3-opus, messages[{role: user, content: 分析这段文本}], timeout60, num_retries3 )num_retries参数会在遇到可重试的错误如限流、超时时自动重试。但这里有个坑重试会消耗额外的token和时间。如果你的应用对成本敏感需要谨慎设置重试次数。另外对于流式请求重试的逻辑会更复杂因为流已经开始输出后很难干净地重试。我的经验是对于非流式请求设置2到3次重试比较合理对于流式请求建议在应用层做重试而不是依赖litellm的内置重试。4.3 成本追踪多模型混用时的账本问题当你同时使用多家模型时成本追踪会变得复杂。不同服务商的计费方式不同有的按输入输出token分别计价有的按调用次数有的有阶梯定价。litellm提供了一个成本追踪的功能可以在响应中返回估算的成本from litellm import completion, completion_cost response completion( modelgpt-4, messages[{role: user, content: 你好}] ) cost completion_cost(completion_responseresponse) print(f本次调用预估成本: ${cost:.6f})但要注意这个成本估算是基于litellm内置的价格表可能不是实时最新的。如果你的项目对成本核算要求很高建议定期更新价格表或者直接对接各服务商的账单接口。另外在多租户场景下你可能需要按用户或按项目维度追踪成本。litellm支持在调用时传入metadata你可以利用这个机制做成本归属response completion( modelgpt-4, messages[{role: user, content: 你好}], metadata{user_id: user_123, project: chatbot} )4.4 本地模型接入Ollama与HuggingFace的注意事项litellm也支持接入本地运行的模型比如通过Ollama或HuggingFace推理端点。这对于需要数据隐私或者想节省成本的场景很有用。以Ollama为例你需要先确保Ollama服务在本地运行然后这样调用response completion( modelollama/llama3, messages[{role: user, content: 你好}], api_basehttp://localhost:11434 )这里有个实际经验本地模型的响应速度高度依赖于你的硬件配置。如果你在开发机上跑一个7B参数的模型响应时间可能在几秒到几十秒不等。在生产环境中使用本地模型时一定要做好超时设置和并发控制否则容易把本地服务打满。另外本地模型对prompt格式的敏感度往往比云端大模型更高。同样的prompt在GPT-4上表现很好在本地小模型上可能完全跑偏。建议针对每个本地模型单独调优prompt模板。5. 进阶用法让litellm在复杂系统中发挥更大价值5.1 用Router做负载均衡与降级litellm提供了一个Router组件可以在多个模型部署之间做负载均衡和自动降级。这在生产环境中非常实用。比如你同时部署了两个GPT-4的接入点可以这样配置from litellm import Router router Router( model_list[ { model_name: gpt-4, litellm_params: { model: gpt-4, api_key: key1 } }, { model_name: gpt-4, litellm_params: { model: gpt-4, api_key: key2 } } ], fallbacks[{gpt-4: [anthropic/claude-3-sonnet]}] ) response router.completion( modelgpt-4, messages[{role: user, content: 你好}] )这个配置的意思是两个接入点都叫gpt-4Router会自动在它们之间做负载均衡如果两个都失败了自动降级到Claude 3 Sonnet。这种配置在需要高可用性的场景下非常有用。但要注意降级到不同模型时输出质量可能会有差异。如果你的应用对输出格式有严格要求降级后可能需要额外的后处理。我在实际项目中就遇到过降级后模型不遵循JSON格式输出的情况后来在prompt里加了更强的格式约束才解决。5.2 缓存机制省钱又提速litellm支持多种缓存后端包括内存缓存、Redis缓存等。对于重复性较高的查询开启缓存可以显著降低成本并提升响应速度。import litellm from litellm.caching import Cache litellm.cache Cache(typeredis, hostlocalhost, port6379) response completion( modelgpt-4, messages[{role: user, content: 什么是机器学习}], cachingTrue )缓存的工作原理是对请求内容做哈希如果相同的请求之前已经调用过直接返回缓存结果。这里有个需要注意的点缓存键的生成方式。默认情况下litellm会根据模型名和messages内容生成缓存键。如果你的应用中有动态内容比如时间戳、用户ID需要确保这些不影响缓存键的生成否则缓存命中率会很低。另外对于流式请求缓存的行为可能和你预期的不太一样。建议在流式场景下先测试缓存是否按预期工作。5.3 自定义Provider接入非标准接口虽然litellm已经支持了很多服务商但总有一些特殊情况比如公司内部自建的模型服务、或者某个小众的服务商。litellm提供了自定义Provider的机制你可以通过继承基类来实现自己的适配逻辑。这个功能对于企业内部的模型网关建设特别有用。你可以把公司内部所有模型服务都封装成litellm的Provider然后业务团队就可以用统一的接口来调用不需要关心底层细节。实现自定义Provider需要你了解litellm的Provider基类接口主要包括completion、streaming、embedding等方法的实现。官方文档里有详细的说明我这里就不展开代码了。核心思路就是把你的请求转换成litellm的内部格式调用你的服务再把响应转换回litellm的标准格式。6. 一些零散但重要的经验之谈6.1 版本升级要谨慎litellm的迭代速度比较快新版本可能会引入一些不兼容的变更。我在项目中遇到过升级后某些模型的参数映射逻辑变了导致原本正常的调用开始报错。建议在升级前先看changelog在测试环境验证后再上生产。如果项目对稳定性要求很高可以考虑锁定版本号。6.2 日志与可观测性多模型调用的场景下日志和监控尤为重要。你需要知道每次调用走了哪个模型、耗时多少、消耗了多少token、是否触发了降级。litellm支持传入自定义的logger你可以把调用信息输出到你的日志系统或监控平台。我通常会在调用时记录以下信息请求ID、模型名称、输入token数、输出token数、耗时、是否命中缓存、是否触发降级。这些数据对于后续的成本分析和性能优化非常有价值。6.3 测试策略多模型应用的测试比单模型复杂得多。你不仅要测试每个模型单独的表现还要测试切换逻辑、降级逻辑、缓存逻辑。我的建议是对于核心业务逻辑用mock的方式测试不依赖真实模型调用对于集成测试选择一到两个稳定的模型做端到端验证对于模型输出质量的评估单独建立评测流程不要混在功能测试里。6.4 关于Embedding和图像生成litellm不仅支持文本对话还支持Embedding和图像生成。用法同样统一from litellm import embedding, image_generation # Embedding embedding_response embedding( modeltext-embedding-ada-002, input[文本1, 文本2] ) # 图像生成 image_response image_generation( modeldall-e-3, prompt一只在草地上奔跑的狗 )但要注意不同服务商的Embedding维度不同图像生成的尺寸和格式支持也不同。在做向量检索或图像处理时需要针对具体模型做适配。6.5 社区生态与扩展litellm有一个比较活跃的社区很多常见的需求已经有现成的解决方案。比如与LangChain、LlamaIndex等框架的集成与各种可观测性平台的对接都有社区贡献的适配代码。在动手造轮子之前建议先搜一下社区有没有现成的方案。另外litellm的代理模式Proxy也值得关注。它可以把litellm包装成一个OpenAI兼容的API服务这样任何支持OpenAI接口的客户端都可以通过它来调用多家模型。这对于需要统一模型网关的团队来说非常方便。我在实际使用中的体会是litellm最大的价值不在于它支持了多少家模型而在于它把多模型调用的复杂度从业务代码中抽离了出来。你不需要在每个业务模块里处理不同服务商的差异只需要在配置层面做好管理。这种关注点分离的设计让代码的可维护性提升了一个档次。当然它也不是银弹参数差异、成本追踪、降级策略这些还是需要你自己根据业务需求来设计和调优。但至少它帮你解决了最繁琐的那部分工作。
阅读完成 · 觉得有帮助?