首页 / 资讯中心 / 文章详情

litellm:统一OpenAI接口调用多模型,告别碎片化适配

litellm:统一OpenAI接口调用多模型,告别碎片化适配 ★ FEATURED ARTICLE
1. 从一个真实困境说起多模型调用的碎片化之痛如果你正在做一个需要调用多种大语言模型的应用大概率经历过这样的场景项目初期只接了一家模型服务商的API代码写得干净利落。后来业务需要要同时接入另外两三家模型于是代码里开始出现各种if-else分支每个分支里是不同厂商的SDK调用方式、不同的参数命名、不同的返回结构。再后来要加一个失败自动切换备用模型的功能你发现光是处理各家SDK的异常类型就够写一个几百行的适配层。这不是个别现象。我接触过不少团队他们在多模型接入这件事上踩的坑几乎一模一样每接一家新模型就要重新写一遍调用逻辑、重试逻辑、日志埋点、成本统计。代码越堆越厚维护成本越来越高而真正跟业务相关的逻辑反而被淹没在适配代码里。litellm这个项目就是冲着这个痛点来的。它的核心定位可以用一句话概括用一套统一的OpenAI格式接口去调用市面上几乎所有主流的大语言模型服务。你写一次调用代码换模型只需要改一个字符串参数。它把不同厂商之间的差异——参数命名、认证方式、返回结构、流式输出格式、异常类型——全部封装在内部对外暴露一个一致的接口。这篇文章适合谁看如果你正在做AI应用开发需要接入多个模型服务商或者你已经在用某一家模型但想保留随时切换的能力又或者你只是想找一个轻量的方案来统一管理模型调用和成本统计——那litellm值得你花时间了解一下。我会从它的核心设计思路讲起然后拆解实际接入过程中的关键步骤、参数配置、踩坑经验和性能优化手段尽量把我知道的都倒出来。2. litellm到底解决了什么问题统一接口的底层逻辑2.1 多模型接入的三种典型痛苦在深入litellm的具体用法之前我想先把多模型接入这件事的痛苦来源拆清楚。只有理解了问题本质你才能判断一个工具是否真的适合你。第一种痛苦是参数命名的碎片化。同样是最大输出长度这个参数有的厂商叫max_tokens有的叫max_output_tokens有的叫max_new_tokens。同样是温度参数大部分叫temperature但取值范围和默认值可能不同。你在A模型上调好的参数换到B模型上可能直接报错或者行为完全不一样。第二种痛苦是返回结构的不一致。有的厂商返回choices[0].message.content有的返回output.text有的返回data.generations[0].text。流式输出的差异更大——有的用SSE的data:前缀有的用自定义的事件格式有的在最后一个chunk里才带上用量统计。你要写一个统一的流式处理逻辑就得为每家单独写解析代码。第三种痛苦是异常处理和重试策略的差异。限流错误的HTTP状态码可能不同超时的判定标准不同可重试的错误类型也不同。你想做一个主模型失败自动切备用模型的逻辑就得为每家SDK单独捕获异常类型然后映射到自己的重试策略上。litellm的做法是在SDK层面做一层薄薄的适配层把所有厂商的差异吸收掉对外只暴露OpenAI风格的接口。你调用completion()函数传入model某厂商/某模型剩下的参数和返回结构都跟调OpenAI一样。它内部维护了一个模型到提供商的映射表根据模型名称自动路由到对应的适配器。2.2 为什么选择兼容OpenAI格式作为统一标准你可能会问为什么是OpenAI格式而不是自己定义一套格式这个问题值得展开说说。OpenAI的Chat Completions接口事实上已经成了行业里的一种通用语言。大量开源工具、框架、教程都是围绕这个接口格式来写的。litellm选择兼容它意味着你现有的基于OpenAI接口写的代码几乎不需要改动就能切换到其他模型。这个迁移成本极低是它被广泛采用的重要原因。另外OpenAI格式本身的设计比较简洁messages数组承载对话历史model指定模型temperature、max_tokens等控制生成行为返回结构里choices数组承载结果usage字段承载用量。这套结构足够表达绝大多数场景的需求又不会过于复杂。注意虽然litellm兼容OpenAI格式但不同模型对参数的支持程度不同。比如某些模型不支持function_call某些模型不支持response_format。litellm会尽量做参数映射但遇到模型本身不支持的能力它也无能为力。这一点在选型时要心里有数。2.3 核心架构适配器模式的实际落地从代码层面看litellm的核心是一个适配器模式的实现。它内部为每个模型提供商维护了一个适配器类负责把统一的调用参数转换成该提供商SDK需要的格式再把返回结果转换回统一格式。这个架构有几个关键设计点值得注意模型名称路由通过model参数的前缀或映射表来确定使用哪个适配器。比如gpt-4走OpenAI适配器claude-3-opus走Anthropic适配器gemini-pro走Google适配器。参数透传与转换通用参数如temperature、max_tokens会被映射到各厂商对应的参数名厂商特有的参数可以通过**kwargs透传。统一异常体系把各厂商的异常类型映射到一套统一的异常类方便上层做统一的重试和降级处理。用量统计标准化把各厂商返回的token用量统一成prompt_tokens、completion_tokens、total_tokens三个字段。理解了这层架构你在使用过程中遇到某个参数不生效或者某个返回字段为空时就能快速定位是适配层的问题还是模型本身的问题。3. 从零接入litellm环境准备与第一行调用代码3.1 安装与依赖管理litellm的安装本身很简单但依赖管理有几个细节需要注意。最基础的安装方式pip install litellm这个命令会安装核心包但它不会自动安装所有模型提供商的SDK。这是有意为之的设计——如果你只用OpenAI没必要把Anthropic、Google、Cohere的SDK都装一遍。你需要按需安装对应的依赖# 如果需要用Anthropic的模型 pip install litellm[anthropic] # 如果需要用Google的模型 pip install litellm[google] # 如果需要用Cohere的模型 pip install litellm[cohere] # 如果想一次装齐常用提供商 pip install litellm[proxy]我个人的建议是在开发环境用litellm[proxy]一次装齐在生产环境按实际用到的提供商精简安装。这样既能保证开发时不会因为缺依赖而卡住又能控制生产镜像的体积。提示litellm的版本迭代比较快建议在requirements.txt或pyproject.toml里锁定具体版本号。我遇到过因为版本升级导致参数默认值变化的情况锁定版本可以避免这类意外。3.2 API Key的配置方式litellm读取API Key的方式遵循各厂商SDK的环境变量约定。比如OpenAI读OPENAI_API_KEYAnthropic读ANTHROPIC_API_KEYGoogle读GOOGLE_API_KEY。你只需要在环境变量里配好调用时不用显式传Key。export OPENAI_API_KEY你的key export ANTHROPIC_API_KEY你的key如果你不想用环境变量也可以在调用时通过api_key参数显式传入import litellm response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], api_key你的key )但我不推荐在代码里硬编码Key尤其是当你的代码会提交到版本控制系统时。环境变量或者密钥管理服务是更稳妥的选择。3.3 第一次调用最小可用示例环境准备好之后第一行调用代码长这样import litellm response litellm.completion( modelgpt-4, messages[ {role: system, content: 你是一个简洁的助手。}, {role: user, content: 用一句话解释什么是适配器模式。} ] ) print(response.choices[0].message.content)如果你之前用过OpenAI的Python SDK会发现这段代码几乎一模一样。唯一的区别是把openai.ChatCompletion.create换成了litellm.completion。这就是litellm的设计目标——让迁移成本趋近于零。换模型只需要改model参数# 换成Anthropic的模型 response litellm.completion( modelclaude-3-sonnet-20240229, messages[{role: user, content: 你好}] ) # 换成Google的模型 response litellm.completion( modelgemini-pro, messages[{role: user, content: 你好}] )3.4 模型名称的命名规则与查询方式litellm的模型名称有一套命名规则。大部分情况下直接用厂商的模型ID即可比如gpt-4、claude-3-opus-20240229、gemini-pro。但对于一些容易混淆的情况可以用提供商/模型名的格式来明确指定# 明确指定使用OpenAI提供商 modelopenai/gpt-4 # 明确指定使用Azure OpenAI modelazure/你的部署名 # 明确指定使用本地部署的模型 modelollama/llama2如果你不确定某个模型名称该怎么写litellm提供了查询接口import litellm # 列出所有支持的模型 print(litellm.model_list) # 查询某个模型支持哪些参数 print(litellm.get_supported_openai_params(modelgpt-4))这个get_supported_openai_params接口在实际开发中很有用。当你把模型从A换成B时可以先查一下B支持哪些参数避免传了不支持的参数导致报错。4. 参数映射与返回结构那些文档里不会写的细节4.1 通用参数的映射规则litellm对通用参数做了映射但映射规则有一些细节值得注意。下面这张表是我在实际使用中总结出来的统一参数OpenAIAnthropicGoogle注意事项max_tokensmax_tokensmax_tokensmax_output_tokens部分模型有上限超出会报错temperaturetemperaturetemperaturetemperature取值范围都是0-2但默认值不同top_ptop_ptop_ptop_p一般与temperature二选一streamstreamstreamstream流式格式有差异但litellm统一了stopstopstop_sequencesstop_sequences字符串或列表均可大部分情况下你不需要关心这些映射litellm会自动处理。但当你发现某个参数传了没效果时第一件事就是查一下这个模型是否支持该参数。4.2 厂商特有参数的透传方式有些厂商有自己特有的参数OpenAI格式里没有对应的字段。litellm的处理方式是通过**kwargs透传。比如Anthropic的top_k参数response litellm.completion( modelclaude-3-sonnet-20240229, messages[{role: user, content: 你好}], top_k40 # Anthropic特有参数直接透传 )但这里有个坑透传的参数不会做校验。如果你把top_k传给了一个不支持它的模型litellm可能不会报错而是静默忽略。所以透传厂商特有参数时最好确认一下目标模型是否支持。4.3 返回结构的统一与差异litellm的返回结构基本对齐OpenAI格式response.choices[0].message.content # 生成的文本 response.choices[0].finish_reason # 结束原因 response.usage.prompt_tokens # 输入token数 response.usage.completion_tokens # 输出token数 response.usage.total_tokens # 总token数但有几个细节需要注意finish_reason的取值可能不同。OpenAI用stop表示正常结束length表示达到长度上限。某些厂商可能用不同的值litellm会尽量映射但不保证完全一致。usage字段可能为空。部分厂商在流式模式下不返回用量统计或者只在最后一个chunk里返回。如果你需要精确的用量统计建议在非流式模式下获取。response.model可能与你传入的model不同。厂商可能返回实际使用的模型版本号这在做日志分析时要注意。4.4 流式输出的统一处理流式输出是多模型接入中最容易出问题的环节。不同厂商的流式格式差异很大litellm做了一层统一response litellm.completion( modelgpt-4, messages[{role: user, content: 写一首短诗}], streamTrue ) for chunk in response: delta chunk.choices[0].delta if delta.content: print(delta.content, end, flushTrue)这段代码在OpenAI、Anthropic、Google上都能跑litellm内部会把各家的流式格式转换成统一的chunk结构。但有一个坑我要特别提醒流式模式下usage字段的获取方式不同。OpenAI需要在请求时加stream_options{include_usage: True}才会在最后一个chunk里返回用量。Anthropic则是在message_delta事件里返回。litellm尽量统一了但如果你发现流式模式下拿不到用量先检查一下是否需要额外的参数。5. 重试、降级与成本控制生产环境必须考虑的三件事5.1 失败自动切换备用模型的实现生产环境里单一模型服务商出现限流或故障是常态。litellm内置了fallback机制可以在主模型失败时自动切换到备用模型response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], fallbacks[claude-3-sonnet-20240229, gemini-pro] )当gpt-4调用失败时litellm会依次尝试fallbacks列表里的模型。这个机制在应对限流时特别有用——主模型限流了自动切到备用模型用户几乎无感知。但fallback有几个细节要注意fallback只在特定异常时触发。不是所有异常都会触发fallback比如参数错误这类重试也没用的异常litellm不会触发fallback。你可以通过context_window_fallbacks、content_policy_fallbacks等参数精细控制。fallback会增加延迟。主模型失败后切换到备用模型用户感受到的延迟是主模型超时时间备用模型响应时间。如果主模型超时设置得太长用户体验会很差。建议把主模型的超时设置得短一些。fallback不保证结果一致性。不同模型的输出风格和能力不同fallback后的结果可能与主模型有差异。对于结果一致性要求高的场景要谨慎使用。5.2 重试策略的配置与调优除了fallbacklitellm还支持重试response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], num_retries3, retry_after1 # 重试间隔秒数 )重试策略的调优有几个经验值可以参考限流错误429建议重试间隔可以稍长2-5秒因为限流通常是短时间内的配额问题。超时错误建议重试但重试次数不宜过多2-3次因为超时可能是网络问题重试太多会累积延迟。服务端错误500/502/503建议重试间隔可以短一些1-2秒。参数错误400不建议重试因为重试也不会成功。提示litellm的重试是指数退避策略每次重试的间隔会逐渐增加。这个策略在应对限流时比较有效但如果你需要更精细的控制可以自己实现重试逻辑把num_retries设为0。5.3 成本追踪与预算控制多模型接入的一个隐性成本是你很难知道钱花在哪里了。不同模型的定价不同调用量大的时候成本会快速累积。litellm提供了成本追踪功能import litellm # 开启成本追踪 litellm.success_callback [cost] response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}] ) # 查看本次调用的成本 print(response._hidden_params[response_cost])litellm内部维护了一个模型定价表会根据token用量自动计算成本。这个功能在做成本分析时很有用。如果你需要更精细的预算控制可以结合litellm的callback机制在每次调用后记录成本当累计成本超过阈值时触发告警或降级。我见过一些团队用这个机制来做按部门分摊成本或者按项目控制预算。5.4 超时设置与并发控制超时设置是生产环境里容易被忽视但很重要的一个参数response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], timeout30 # 30秒超时 )超时设置的经验值短文本生成10-15秒足够长文本生成30-60秒流式输出可以设置得更长因为流式输出是逐步返回的并发控制方面litellm本身不限制并发数但你可以结合asyncio的Semaphore来控制import asyncio import litellm semaphore asyncio.Semaphore(10) # 最多10个并发 async def call_model(prompt): async with semaphore: return await litellm.acompletion( modelgpt-4, messages[{role: user, content: prompt}] )这个模式在批量处理任务时很有用可以避免因为并发过高触发限流。6. 代理服务与可观测性把litellm用出生产级效果6.1 什么时候需要启动代理服务litellm除了作为Python库使用还提供了一个代理服务模式。启动方式litellm --model gpt-4 --port 8000或者用配置文件model_list: - model_name: gpt-4 litellm_params: model: gpt-4 api_key: os.environ/OPENAI_API_KEY - model_name: claude-3 litellm_params: model: claude-3-sonnet-20240229 api_key: os.environ/ANTHROPIC_API_KEYlitellm --config config.yaml --port 8000启动后你会得到一个OpenAI兼容的HTTP接口。这意味着任何支持自定义OpenAI接口地址的工具都可以通过这个代理来调用多种模型。比如一些聊天客户端、代码助手、自动化工具只要它们支持配置API Base URL就能接入。代理模式适合这些场景多语言技术栈你的主服务不是Python但想用litellm的统一接口能力。集中管理密钥把API Key集中在代理层管理业务代码不接触密钥。统一日志和监控在代理层做统一的请求日志、成本统计、性能监控。限流和配额管理在代理层做统一的限流避免各业务线各自为政。6.2 代理层的密钥管理与访问控制代理模式下的密钥管理有两种方式方式一代理层持有真实密钥业务层用虚拟密钥。你可以在配置文件里设置master_key业务层调用时带上这个key代理层验证通过后用自己持有的真实密钥去调用模型。general_settings: master_key: sk-your-master-key方式二业务层传入真实密钥。代理层不做密钥管理业务层调用时在Header里带上真实密钥代理层透传。方式一更适合生产环境因为真实密钥不会暴露给业务层。方式二更适合开发调试。6.3 日志、监控与用量分析代理模式下litellm可以记录每次请求的详细信息。你可以配置日志输出到文件或数据库litellm_settings: success_callback: [jsonl] failure_callback: [jsonl] jsonl_log_dir: ./logs记录的字段包括请求时间、模型名称、输入输出token数、成本、延迟、是否成功等。这些数据可以用来做成本分析按模型、按业务线、按时间段统计成本。性能监控统计各模型的平均延迟、P95延迟、错误率。用量趋势观察调用量的变化趋势提前做容量规划。如果你已经有自己的监控体系比如Prometheus、Grafanalitellm也支持通过callback把指标推送到这些系统。6.4 缓存策略省钱又提速的关键手段litellm支持缓存对于重复的请求可以直接返回缓存结果既省钱又提速import litellm from litellm.caching import Cache litellm.cache Cache(typeredis, hostlocalhost, port6379) response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], cachingTrue )缓存策略的注意事项缓存key的生成规则litellm默认用modelmessages的哈希作为缓存key。如果你希望不同的参数如temperature产生不同的缓存需要自定义key生成逻辑。缓存过期时间建议设置合理的TTL避免缓存过期的内容。对于时效性要求高的场景TTL可以设短一些。缓存命中率如果你的应用有大量重复请求比如FAQ问答缓存命中率会很高成本节省效果明显。如果请求都是独一无二的缓存的价值就不大。7. 实际踩坑记录那些让我加班到深夜的问题7.1 模型名称写错导致的静默失败有一次我在配置fallback时把备用模型的名称写错了一个字母。结果主模型失败后fallback也失败了但litellm没有抛出明确的错误而是返回了一个空结果。我排查了半天才发现是模型名称的问题。教训配置fallback时一定要验证每个模型名称是否有效。可以在启动时先做一次健康检查确认所有配置的模型都能正常调用。7.2 流式模式下的用量统计缺失前面提到过流式模式下usage字段的获取方式不同。我遇到过一个场景用流式模式做实时对话但成本统计一直是0。后来发现是因为没有开启stream_options。解决方案response litellm.completion( modelgpt-4, messages[{role: user, content: 你好}], streamTrue, stream_options{include_usage: True} )但要注意不是所有模型都支持stream_options。对于不支持的模型你可能需要在非流式模式下单独获取用量或者用估算的方式。7.3 超时设置不当导致的级联故障有一次我把主模型的超时设置成了60秒fallback的超时也是60秒。结果主模型限流时用户要等60秒才能看到fallback的结果。更糟糕的是大量请求堆积导致整个服务响应变慢。教训主模型的超时应该设置得短一些比如15-20秒fallback的超时可以稍长。这样主模型失败后能快速切换用户体验更好。7.4 参数透传导致的意外行为前面提到过厂商特有参数是通过**kwargs透传的不做校验。我有一次把一个模型的参数透传给了另一个模型结果那个模型不认识这个参数但也没有报错而是用了默认值。这导致输出结果与预期不符排查了很久。教训透传厂商特有参数时一定要确认目标模型是否支持。可以在代码里加一层校验或者用get_supported_openai_params查询后再传。7.5 代理模式下的并发瓶颈代理模式虽然方便但在高并发场景下可能成为瓶颈。我见过一个团队用单进程的代理服务处理每秒几百个请求结果代理层成了性能瓶颈。解决方案代理服务可以用多worker模式启动litellm --config config.yaml --port 8000 --num_workers 4或者用Gunicorn/Uvicorn多进程部署。另外代理层的缓存和日志写入也可能成为瓶颈建议用异步写入或独立的日志收集服务。8. 选型对比与适用边界litellm不是银弹8.1 litellm与其他多模型方案的对比市面上做多模型统一接入的方案不止litellm一家。下面这张表是我根据实际使用经验整理的对比方案优势劣势适用场景litellm轻量、Python原生、模型覆盖广、社区活跃深度定制能力有限、部分厂商支持不完善Python技术栈、快速接入多模型自建适配层完全可控、可深度定制开发维护成本高、需要持续跟进各厂商API变化有特殊需求、团队有充足人力其他统一接口库各有特色模型覆盖和社区活跃度参差不齐根据具体需求评估litellm的核心优势在于轻量和覆盖面广。它不试图做一个大而全的框架而是专注于统一接口这一件事。如果你的需求就是用一套代码调用多种模型它几乎是最省事的选择。8.2 什么场景不适合用litellmlitellm不是万能的以下场景可能需要考虑其他方案需要深度定制调用逻辑比如你需要对请求做复杂的预处理、对响应做复杂的后处理litellm的抽象层可能会成为阻碍。对性能有极致要求litellm的适配层会带来一定的性能开销。如果你的场景对延迟极其敏感可能需要直接调用厂商SDK。使用的模型不在支持列表中litellm虽然覆盖了主流模型但一些小众模型或私有部署的模型可能不在支持列表中。不过它支持自定义提供商可以自己扩展。需要厂商特有的高级功能比如某些厂商的独特功能如特定的函数调用格式、特定的多模态能力litellm可能没有完全覆盖。8.3 自定义提供商的扩展方式如果你用的模型不在litellm的支持列表中可以通过自定义提供商来扩展from litellm import CustomLLM, completion, get_llm_provider class MyCustomLLM(CustomLLM): def completion(self, model, messages, **kwargs): # 在这里实现你的调用逻辑 return { choices: [{message: {content: 自定义模型的返回}}], usage: {prompt_tokens: 10, completion_tokens: 20, total_tokens: 30} } litellm.custom_provider_map [ {provider: my-custom, custom_handler: MyCustomLLM()} ] response completion( modelmy-custom/my-model, messages[{role: user, content: 你好}] )这个扩展机制让litellm的适用范围大大扩展。只要你能实现一个符合接口规范的适配器就能接入任何模型。9. 一些实战中的小技巧和后续扩展思路9.1 用Router实现更智能的负载均衡litellm的Router组件支持更复杂的路由策略比如按权重分配流量、按延迟自动选择最快的模型、按成本选择最便宜的模型from litellm import Router router Router( model_list[ {model_name: gpt-4, litellm_params: {model: gpt-4}}, {model_name: claude-3, litellm_params: {model: claude-3-sonnet-20240229}} ], routing_strategylatency-based-routing # 按延迟选择 ) response router.completion( modelgpt-4, # 这里传的是model_nameRouter会根据策略选择实际模型 messages[{role: user, content: 你好}] )这个功能在做A/B测试或者灰度发布时特别有用。你可以把一部分流量导向新模型观察效果后再决定是否全量切换。9.2 结合异步调用提升吞吐量litellm支持异步调用在批量处理场景下能显著提升吞吐量import asyncio import litellm async def batch_call(prompts): tasks [ litellm.acompletion( modelgpt-4, messages[{role: user, content: p}] ) for p in prompts ] return await asyncio.gather(*tasks) results asyncio.run(batch_call([问题1, 问题2, 问题3]))异步调用配合信号量控制并发数可以在不触发限流的前提下最大化吞吐量。9.3 用回调机制做自定义监控litellm的callback机制可以让你在每次调用前后插入自定义逻辑def my_callback(kwargs, response, start_time, end_time): # 记录日志、上报指标、计算成本等 print(f调用耗时: {end_time - start_time}秒) print(f成本: {response._hidden_params.get(response_cost, 0)}) litellm.success_callback [my_callback]这个机制可以用来做自定义的监控告警、成本分摊、审计日志等。我见过一些团队用这个机制来做按用户统计用量和异常调用告警。9.4 后续可以探索的方向如果你已经把litellm用起来了以下几个方向可以进一步探索模型评估与自动切换结合评估数据自动把流量切换到效果最好的模型。多模态统一接口litellm也在逐步支持图像、音频等多模态能力可以关注这方面的进展。与编排框架结合把litellm作为底层模型调用层上层结合LangChain、LlamaIndex等编排框架构建更复杂的应用。私有化部署结合本地部署的模型如通过Ollama、vLLM部署的模型实现完全私有化的多模型调用。我个人在实际使用中的体会是litellm最大的价值不在于它支持了多少模型而在于它把切换模型这件事的成本降到了几乎为零。这让你在做技术选型时有了更大的灵活性——你可以先用A模型快速验证发现B模型更合适时改一个字符串就能切换。这种灵活性在快速迭代的AI应用开发中价值是很大的。最后再分享一个小技巧如果你在开发环境频繁切换模型调试可以写一个简单的封装函数把常用的模型配置集中管理这样切换时只需要改一个配置项不用到处改代码。这个习惯能帮你省下不少时间。
阅读完成 · 觉得有帮助?
咨询建站