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

多模型接入的统一网关实践:用litellm解决LLM混合调用与成本管控难题

多模型接入的统一网关实践:用litellm解决LLM混合调用与成本管控难题 ★ FEATURED ARTICLE
做了几年应用开发之后你会发现真正让人头疼的往往不是模型效果本身而是“接模型”这个过程。产品今天说要用模型A试试明天说要换模型B做对照后天又说两个都要跑、取结果更稳的那条链路。等你把各家的SDK装好、鉴权调通、流式输出处理好、用量账单统计好一周就过去了。我当初就是被这件事反复折腾最后才认认真真把litellm研究了一遍并且在后来的好几个项目里把它作为固定的接入层。这篇文章就聊聊我自己的理解和实测经验不保证是最佳实践但至少能让你少踩一些我踩过的坑。1. 从多模型接入的混乱现状说起litellm到底在解决什么问题1.1 没有统一接入层时的真实复杂度如果你只接一家模型服务问题还不明显。真正乱的是“同时接多家”。每家的Python SDK接口风格不同认证方式不同有的要传Header有的要传Query参数有的流式响应字段叫delta有的叫content有的工具调用格式还完全不一样。我做过一个小项目当时要同时评估三个文本生成模型和一个多模态模型。最初的做法很朴素每个服务商写一个独立的 client 封装再在上层写一个switch-case统一出口。代码结构是这样的if provider provider_a: resp await client_a.chat(messages, modelchat-model-v1) elif provider provider_b: resp await client_b.messages.create(modelmodel-b-v2, content...) elif provider provider_c: resp await client_c.completion(prompt..., modelc-v3)这样的代码刚写完是能跑的但后面每改一点都很痛苦新增一家模型就要改调用层某个模型的返回字段变了又要改解析层想统计每次请求花了多少钱、调了多少token各家报表格式还不一样。更麻烦的是重试和容错每家SDK的超时设置、重试策略、错误码含义都不同根本没法用一套逻辑统一处理。这就牵扯出一个核心问题业务代码真正关心的其实是一个很简单的接口——“给我一段消息返回模型回复”至于背后是模型A还是模型B是同步还是异步是怎么计费的业务层根本不应该关心。litellm的核心价值就是把这层复杂度收敛掉。1.2 litellm的分层思路SDK级统一与网关级统一litellm的定位是两个东西的结合体。第一层是Python SDK。它把所有模型服务商封装成同一个completion()方法不管你接哪家调用方式都长得差不多。你只需要在模型标识里带上服务商前缀比如provider_a/chat-model-v1剩下的鉴权、请求转换、响应解析都由SDK内部处理。第二层是网关服务官方文档里通常叫 LiteLLM Proxy。它把上面的SDK能力再包一层HTTP服务对外暴露一个统一的API端点让包括前端、后端、数据团队在内的所有人都可以通过一个固定的base_url去调用不同模型。网关还承担了密钥管理、Budget限额、负载均衡、多模型fallback、日志统计这些平台级职责。这两层可以分开用也可以组合用。我的习惯是个人脚本、快速验证的时候直接用SDK正式项目、多人协作的时候一定走网关。后面详细说。提示litellm本身是一个开源项目不是某一家模型服务商独有的工具它不把你绑定在某个模型上。今天接的是A家明天想切到B家只需要改配置不用改业务代码。这一点对技术选型很重要。2. litellm的两种使用路径SDK直连与网关部署的取舍2.1 第一种路径pip安装后直接用SDK安装很简单pip install litellm最基础的调用长这样import litellm resp litellm.completion( modelprovider_a/chat-model-v1, messages[ {role: user, content: 用一句话解释什么是API网关} ], api_keyyour_api_key_here ) print(resp.choices[0].message.content)如果你不想在代码里暴露api_key也可以通过环境变量设置。不同服务商的密钥环境变量名通常遵循一个约定但我建议在配置里显式看一下文档再填因为有些服务商还用api_base、api_version之类额外参数不填对就会报认证错。litellm还支持异步调用import asyncio import litellm async def main(): resp await litellm.acompletion( modelprovider_a/chat-model-v1, messages[{role: user, content: 你好}] ) print(resp.choices[0].message.content) asyncio.run(main())实测下来异步模型在并发请求场景下表现明显更好。如果业务里同时要处理一堆消息建议直接用acompletion不要用completion包线程池。SDK模式的优点是轻量、直接适合本地调试和单机脚本。缺点也很明显谁会调用、调用多少、有没有人把密钥写在代码里这些你都管不住。一旦团队超过两三个人我就强烈建议上网关。2.2 第二种路径部署统一网关网关模式的核心是一个YAML配置文件。下面是一个最简配置model_list: - model_name: llm-primary litellm_params: model: provider_a/chat-model-v1 api_key: os.environ/A_PROVIDER_API_KEY - model_name: llm-secondary litellm_params: model: provider_b/chat-model-v2 api_key: os.environ/B_PROVIDER_API_KEY启动命令litellm --config config.yaml --port 4000启动之后网关会在http://localhost:4000暴露一个兼容主流SDK调用格式的接口。测试一下curl http://localhost:4000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: llm-primary, messages: [{role: user, content: 你好}] }注意这里的model参数已经不是真正的模型标识了而是你在配置里定义的model_name。真实模型标识provider_a/chat-model-v1只会在后端生效前端和业务侧只认llm-primary。这个设计是故意的业务层不感知模型切换你只要在配置里把llm-primary指向另一家模型业务代码一行不用改。网关启动后你可以把base_url给前端、后端、数据分析团队统一使用谁也不用在自己代码里装各家SDK了。密钥也集中在服务端管理不会散落在各个业务仓库里。表格式对比一下两条路径维度SDK直连网关模式接入成本低中密钥安全容易泄露集中在服务端多团队共用不方便天然支持成本统计需自行实现内置支持预算负载均衡与重试无或需自行实现内置适合规模个人脚本、快速验证正式项目、多人团队2.3 组合使用的实际样例我自己现在最常用的方式是本地写测试脚本时直接用SDK代码里不写死真实密钥而是从.env文件读等脚本逻辑稳定了再部署成网关把同样的调用改成指向网关的base_url。这样开发和线上环境能保持一致只是调用方不同。后面要切换模型时都是只改网关配置不动业务代码。3. 网关落地的关键配置与参数解读3.1 配置文件的model_list结构不少初学者第一次看到YAML里的model_list会困惑model_name和litellm_params.model有什么区别到底哪个是给调用方用的简单说model_name是“别名”是暴露给调用方的名字litellm_params.model是“真实模型标识”是SDK用来定位服务商和模型类型的名字。举个例子model_list: - model_name: chat-default litellm_params: model: provider_a/chat-model-v1 - model_name: chat-default litellm_params: model: provider_b/chat-model-v2上面配置里两个条目的model_name都是chat-default它表示这两个模型属于同一个“逻辑模型组”。网关会按照路由策略在它们之间做负载均衡或故障转移。也就是说对调用方来说chat-default是一个稳定的入口背后是哪个模型在响应调用方不关心也不应该关心。你还可以给每个配置项补充模型元数据方便网关做成本预估和上下文控制model_list: - model_name: chat-strong litellm_params: model: provider_b/chat-model-v2 api_key: os.environ/B_PROVIDER_API_KEY model_info: cost_per_token: 0.000002 context_window: 200000上面model_info里的cost_per_token和context_window是网关做成本估算和上下文处理时的参考值。不同的模型计费方式不同有的按输入输出分别计价有的还会按图片数量计费所以实际配置时要查清楚你用的模型计费规则再填对应的字段否则统计出来的成本偏差会很大。3.2 路由策略与故障转移网关的一个重要价值是路由管理。你可以给同一个model_name掛多个真实模型并指定权重让流量按比例分摊router_settings: routing_strategy: simple-shuffle num_retries: 3 cooldown_time: 30 allowed_fails: 3这里几个参数的意思num_retries单次请求失败后最多重试几次。cooldown_time某个模型连续失败后把它临时标记为冷却的秒数。allowed_fails在进入冷却之前允许连续失败的次数。重试不是简单的“失败就再来一次”它对超时时间、错误类型都有区分。有些错误是配额不够重试也没什么用有些错误是临时网络波动重试一下可能就成功了。网关层面会把这两类情况尽量分开处理。我没记错的话部分SDK服务商自己的重试逻辑和网关重试会叠加导致实际请求次数比预期多这个后面踩坑部分再讲。如果你想实现“主模型失败自动切换到备用模型”可以配置fallbackmodel_list: - model_name: chat-main litellm_params: model: provider_a/chat-model-v1 api_key: os.environ/A_PROVIDER_API_KEY model_info: fallbacks: - provider_b/chat-model-v2调用方请求chat-main时如果provider_a侧连接失败或返回错误网关会自动尝试provider_b。这个设计对追求稳定性的生产环境特别有用。3.3 虚拟密钥与预算管理多人团队共用网关时最怕的是密钥混乱和费用失控。litellm提供了一套虚拟密钥机制你可以在网关里生成一个虚拟Key给某个团队或某个项目用同时给它设置预算上限。生成带预算的虚拟Key可以通过管理接口curl -X POST http://localhost:4000/key/generate \ -H Content-Type: application/json \ -d { models: [chat-default], max_budget: 20, budget_duration: 30d, info: {team: 某项目组} }这样可以做到某个项目组的Key最多花20美元30天自动失效。超了预算网关直接拒绝该Key的请求。财务上虽然不算绝对精确但作为第一道闸门足够了。我在某个内部模拟项目里把预算调成“按天”之后再也不担心同事半夜跑批量任务把账单跑爆了。网关还支持查看每次请求的花费记录。很多平台的成本统计是“账单出来后才看到”而网关这里每完成一次请求就能把对应的token数和估算花费记下来。这个对做模型成本对比、调用量监控非常重要。3.4 请求字段的兼容与转换使用兼容OpenAI格式的请求时很多参数会被透传到真实模型比如temperature、max_tokens、stream、tools等。但不同模型的参数名称和行为有差异比如有的模型用max_tokens有的用max_new_tokens有的系统消息能力弱有的工具调用格式特殊。litellm内部会做不少映射转换。我的经验是先用网关跑一个最小请求把模型返回原文打出来确认字段格式是否符合预期再写业务解析。不要想当然地认为“文档上说兼容就等于字段完全一致”实测过的差异比想象中多。4. 把litellm接入现有工程的几种典型方式4.1 通过切换base_url接入现有SDK如果你的项目已经用某主流SDK风格写好了代码接网关最简单的方式其实是改base_url和api_key业务代码基本不动。比如下面这段from openai import OpenAI client OpenAI( base_urlhttp://your-gateway-host:4000, api_keysk-your-virtual-key ) resp client.chat.completions.create( modelchat-default, messages[{role: user, content: 介绍一下liteLLM}] ) print(resp.choices[0].message.content)这里没有引入任何litellm依赖就是把SDK的base_url指到了网关。对于已经成熟的项目来说这种改动最平滑。只要代码里没有用到某家SDK独有的特殊参数基本上都能跑通。同样的方式也适用于数据分析和低代码脚本只要工具支持自定义base_url就能把请求发送到网关再由网关转发到背后真正的模型。4.2 在业务流程中做多模型路由有些场景不是简单的“一进一出”而是要根据业务逻辑决定用哪个模型。比如先让一个便宜模型做初筛回答不了再上贵模型或者同一个请求同时发两个模型做结果对比。这种逻辑如果写在业务代码里会非常冗长适合放在SDK或网关层。用SDK的方式可以这样组织import litellm responses await asyncio.gather( litellm.acompletion( modelprovider_a/chat-model-v1, messagesmessages ), litellm.acompletion( modelprovider_b/chat-model-v2, messagesmessages ) )如果走网关也可以通过定义多个model_name让不同请求打到不同模型业务层按需选择。4.3 与主流编排框架的对接现在很多项目会用第三方编排框架来管理LLM调用链路。litellm在这类框架里也很好对接。框架自带的多模型组件通常支持传入openai_api_base之类的参数指到网关地址即可或者直接用litellm提供的模型组件类它和框架原生组件的接口风格一致。我在某项目里的习惯是生产环境一律走网关编写测试时可以临时用一下轻量模式。集成时的关键点是确认框架组件透传参数的能力。有些框架会默认注入模型名称、重新拼base_url导致网关侧总是收到404。这时候先看一下框架的源码实现别急着改网关配置。4.4 日志、指标与可观测性多团队共用网关之后日志和监控是必不可少的一环。litellm支持把请求日志派发到很多外部存储服务。最简单的用法是在网关配置里注册自定义回调# 伪代码示例 def handle_log_payload(payload): print(payload[model], payload[spend], payload[response_cost]) litellm.callbacks [handle_log_payload]生产上更常用的方式是通过网关自带的管理接口拉取请求日志或者把日志推送到Prometheus这类指标系统用于做调用量、耗时、失败率、成本的仪表盘。具体推送方式要看实际情况但数据都是网关统一给出的比从多个服务商后台拼数据省事得多。5. 真实跑测中遇到的坑与规避方法5.1 模型标识和成本字段写错导致统计失真有一次我在配置文件里把成本字段cost_per_token填成了按“每千token”的价格网关统计出来的花费比实际高了一千倍。这种错误很隐蔽因为请求本身是成功的只是成本数据不对。后来我把每个模型的成本参数单独核对了一遍并拿一次已知token数和价格的请求做了校准才敢在报表里展示。建议配置完model_info后先发一个最小请求看网关返回的usage和费用估算字段是否合理最好做个单元测试保护防止有人后续改了配置没注意成本字段。5.2 流式请求的超时与中断流式输出能提升用户体感但也更容易出幺蛾子。某个模型在我实测时流式场景下如果有一段时间没返回内容会被网关当成空闲并触发超时但实际上模型还在思考。排查原因发现是底层某个服务商对首字返回时间要求比较严格模型推理稍慢就被掐掉了。后来我是这样处理的在请求里明确设置了更大的超时参数并把网关侧的超时设置调大另外对特别不稳定的模型先不让它走流式而是用非流式拿完整结果做完再视情况优化。别迷信“默认配置够用”超时设置一定根据模型实际响应速度调试。5.3 工具调用function calling的差异如果业务里要解析结构化输出或调用外部工具不同模型的工具调用格式差异会让解析层很痛苦。litellm虽然做了兼容但我在实测中仍然遇到过一个模型的工具参数被包装得特别深另一个模型则直接返回了简化格式的情况。这时候不要用一个解析函数打天下建议在工具调用结果出来之后对模型来源做一个分支判断用不同的清洗逻辑。至少把关键字段的取值路径打出来看一眼再决定是直接取值还是递归搜索。我还遇到过一个模型对tool_choice的传参要求跟另一个模型不同在网关层转成统一结构之后有些参数会被忽略。解决办法是尽量用业务里最常见的参数组合不要调得太花哨。5.4 并发压力下的连接池和队列特性网关在默认配置下能处理的并发是有限的。刚开始上线时某内部模拟项目只有十几个用户但有人写了个批量任务一瞬间发了两百个并发请求结果部分请求超时日志里出现大量连接被拒。后来我把网关进程的并发参数调大并在上方加了一层基础限流才把问题压下去。不要以为网关部署了就自动高可用。你需要根据最大调用量估算需要的连接池大小并通过压测确认瓶颈在哪个环节。对于异步场景调好连接池与会话复用性能差距可以达到数倍。5.5 不同模型上下文窗口不同不能一套参数走天下有的模型上下文窗口比较小把长文档粗暴塞进去会直接报长度错误有的模型支持很大上下文但实际推理速度很慢。网关虽然有context_window配置但具体如何处理截断需要自己想清楚。我的建议是在业务侧做文本切片或摘要而不是把整个文档硬塞给模型同时把context_window配置成模型真实的“安全窗口”不要拿最大值当实际可用值。很多模型的实际可用上下文会因为输出长度预留而变小这一点在配置时要留余量。6. 我现在的固定套路与建议经过几轮项目实践我现在接新项目时的套路基本固定了第一步直接装litellmSDK把主要模型用最小请求跑通确认认证、格式、耗时要符合预期第二步把配置整理成YAML本地启动网关用curl或SDK把同样的请求打到网关验证别名和路由是否正常第三步接入日志、预算和虚拟密钥再放到测试环境给其他同学试用最后才上生产并在压测后调整并发与超时参数。几个小的实操建议网关配置一定要用版本管理工具管起来改模型参数、加新模型都走review避免某个人改错了成本字段直接带崩报表。调用方永远只依赖model_name不要依赖真实模型标识。这样以后换模型时业务代码一行都不用动。密钥要区分环境。本地、测试、生产尽量用不同的虚拟Key预算也分开设。出了问题能快速定位是哪个环境、哪个项目组的调用。遇到模型返回格式奇怪时第一件事先看原始响应不要急着甩锅给网关。网关只是一个转换层它返回的错误信息里通常会有真实原因。litellm不算一个多复杂的工具但它很聪明地把“接入模型”这件事的复杂度集中到了配置层而不是代码层。模型服务商越来越多、模型切换越来越频繁的趋势下这个思路会越来越有价值。上面这些经验是自己在实际项目里一点点试出来的希望对正在接多个LLM服务的你有帮助。
阅读完成 · 觉得有帮助?
咨询建站