多模型API网关选型实战:从Flask自研到LiteLLM Proxy的演进
多模型 API 网关这个词最近在 AI 应用开发的圈子里越来越常被提起。说白了一个应用背后往往不只是调一个大模型日常问答用便宜快的小模型代码生成用专门的编码模型复杂推理还得上满血旗舰版再遇上模型服务商调整限流规则、key 轮换、成本分摊这些问题靠业务代码里一个个 if-else 去处理很快就乱成一锅粥。我自己手头有一个基于 Flask 的 DevOps 智能助手项目需要对接多个大模型 API。跑了一个月之后我把“自己写一套轻量网关”和“部署开源多模型网关”两条路都完整走了一遍中间踩了不少坑。这篇文章不聊概念直接讲清楚我当时的方案怎么定的、代码长什么样、上线后实际效果如何以及最后我到底选了哪条路。如果你也在纠结“多模型 API 网关值不值得自己搭”这篇文章应该能帮你省下几天的调研时间。1. 为什么要折腾多模型 API 网关被多个 API 逼出来的需求1.1 场景来源DevOps 智能助手需要多个模型协同事情的起因是我在用蓝耘元生代 MaaS 平台从零搭一个 DevOps 智能助手。这个助手本质上是一个 Flask Web 应用用户通过页面提交问题后端通过 API 调用大模型再把结果返回给前端。听起来很简单但用起来根本不是那么回事。不同场景对模型的需求完全不同一些场景需要“快速、便宜、够用就好”比如给用户生成一句简单的命令解释另一些场景需要“能力强、推理稳”比如解析系统日志并定位根因还有些场景甚至要对多模态模型做代码复现比如把一张架构设计图纸变成项目中的代码骨架。一个模型打天下是很不现实的价格、延迟、能力各有取舍所以多模型协同是天然需求。问题在于当模型超过两个API 管理的复杂度就开始指数上升。1.2 不搭网关之前代码里的一堆烂摊子在最开始的三天我是直接在 Flask 业务代码里硬写多模型调用逻辑的。每个模型一个 SDK 客户端各自配置 api_key、base_url、超时时间然后根据一个 model 参数用 if-else 分发。这样做的后果非常明显第一业务代码被 API 细节污染。核心的 DevOps 逻辑本来应该专注于“解析用户意图、组装 prompt、处理结果”结果大半代码都在处理不同 provider 的异常格式、限流策略和重试机制。第二key 管理和成本统计完全不可控。每个环境一份 key散落在环境变量和配置里出了账单都不知道是哪个业务功能花的钱。第三模型替换成本高。假如某个模型服务商调整了接口协议或者我想把某个场景从模型 A 切到模型 B改动会波及所有调用方的代码。这就是我决定搞一个统一入口的原因。1.3 对“自己搭”这件事的预期管理说句实在话市面上不是没有现成的工具但“现成”也意味着“重”。很多公司直接上 Kubernetes 那一套网关体系对个人项目和小团队来说太重了。我的目标很明确一个小型 Web 应用我需要的是一个中间层能统一接收和转发多模型 API 请求屏蔽底层 provider 差异顺带把日志、key 管理、成本统计这些基础能力做了。我一开始的预期也很朴素不追求做成通用基础设施能把眼前这个 DevOps 助手跑顺就算成功。这里我建议所有想自建网关的朋友先做一次需求盘点。你只需要回答三个问题业务里用到了几个模型调用量大概是什么级别有没有多环境、多人、多部门协作的诉求回答完之后再决定上哪种方案能少走很多弯路。2. 方案 A用 Flask 手写一个轻量转发网关2.1 整体设计只做四件事我先手写方案主要原因是当时项目本身就是一个 Flask Web 应用我不想为了加一层网关而引入一套完全陌生的技术栈。手写网关卡做好四件事就行统一入口业务端只请求一个地址带上 model 字段网关负责解析。协议转换把不同 provider 的差异协议统一成 OpenAI 兼容格式。key 路由与轮换根据 model 选择对应服务商的 key并且支持多个 key 轮换。基础可观测每次请求记录 model、tokens、耗时、状态码落库或落文件方便月底算账。再说一次别急着加什么限流、熔断、动态路由这些是后话。第一版能把请求正确转出去、正确收回来就已经赢了。2.2 核心代码与路由设计代码其实不复杂。我用 Flask 写了一个/v1/chat/completions路由内部通过一个 PROVIDER_CONFIG 表做模型名到服务商的映射。核心逻辑可以抽象成下面这样# gateway.py import os import time import hashlib import sqlite3 from flask import Flask, request, jsonify import openai app Flask(__name__) PROVIDER_CONFIG { fast-chat: { provider: openai, base_url: os.getenv(FAST_CHAT_BASE_URL), api_key: os.getenv(FAST_CHAT_API_KEY), model: fast-chat-model, timeout: 30 }, coder: { provider: openai, base_url: os.getenv(CODER_BASE_URL), api_key: os.getenv(CODER_API_KEY), model: coder-model, timeout: 60 }, }注意我这里都是用的 OpenAI 兼容格式所以后端只需要维护一份 openai SDK 的调用逻辑区别只在 base_url 和 model 上。如果是需要接入 Anthropic 这类协议不同的服务商就需要在网关层做一次 request body 的字段映射把 system prompt、messages 转成目标 API 要求的格式。转发函数的核心部分是这样的def route_chat_completion(model: str, messages: list, temperature: float 0.7): cfg PROVIDER_CONFIG.get(model) if cfg is None: raise ValueError(fmodel {model} not supported) client openai.OpenAI( api_keycfg[api_key], base_urlcfg[base_url], timeoutcfg[timeout] ) start time.time() try: resp client.chat.completions.create( modelcfg[model], messagesmessages, temperaturetemperature ) latency_ms (time.time() - start) * 1000 write_usage_log(modelmodel, latency_mslatency_ms) return { model: model, content: resp.choices[0].message.content, usage: { prompt_tokens: resp.usage.prompt_tokens, completion_tokens: resp.usage.completion_tokens } } except Exception as e: write_error_log(modelmodel, errorstr(e)) raise在这个场景里base_url 对应的是不同模型服务的接入地址。蓝耘元生代 MaaS 这类平台的核心价值就在这里多个模型通过统一的接入方式对外提供服务你不需要为每个模型单独处理签名、鉴权、协议兼容问题。所以在网关开发时我可以把大部分精力花在业务层的路由策略上而不是纠结底层协议差异。2.3 key 管理、缓存与重试的设计细节网关除了转发还要解决几个实际问题。第一个是 key 管理和多环境隔离。我在代码里没有把 key 写死在配置文件中而是统一从环境变量加载。网关部署到哪个环境就自动读取哪个环境的 key 集合。实际项目里我甚至做了一个简单的 key 池同一个模型可以配置多个 key网关每次调用时按轮询方式选择避免单个 key 的限流影响整体服务质量。第二个是缓存。DevOps 助手有不少请求是高重复度的比如“解释一下 kubectl get pods 的输出”。我为这类常见问题做了一层内存缓存命中后直接返回缓存结果不实际调用大模型 API。这个优化对降低成本和提升响应速度效果非常明显实测高峰期缓存命中率接近 30%。第三个是重试。我刚开始只在调用失败时重试一次后来发现某些模型服务商在负载高时会返回 429 限流错误。于是我把重试逻辑做成了带退避的指数重试连续失败超过三次才把错误抛给业务端import time def call_with_retry(client, messages, cfg, max_retries3): for attempt in range(max_retries): try: resp client.chat.completions.create( modelcfg[model], messagesmessages, temperature0.7 ) return resp except Exception as e: if attempt max_retries - 1: raise e wait_time 2 ** attempt time.sleep(wait_time)这段逻辑看起来简单但实话说它解决了非常多实际生产中会遇到的稳定性问题。2.4 手写方案跑了半个月的真实感受Flask 手写网关跑了大概十五天整体功能是稳定的毕竟核心逻辑不复杂。但它的问题也很明显。最让我头疼的是功能越加越多代码开始失去重点。一开始我只想做转发后来加了日志库、key 池、重试、缓存再后来项目经理又提出要按业务功能维度统计成本我发现光靠自己写下去这个网关会膨胀成一个没有文档、没有测试、只有自己敢碰的“私人项目”。半个多月之后我觉得需要及时止损于是开始认真研究第二个方案——直接部署现成的开源多模型网关。3. 方案 B直接上开源网关 LiteLLM Proxy3.1 选型理由为什么选 LiteLLM 而不是其他开源方案市面上的开源多模型网关有不少LiteLLM Proxy 是其中比较主流的一种。我当时主要对比了 openai 生态的 one-api、new-api 和 LiteLLM Proxy 这三类方案最终选 LiteLLM Proxy 有几个核心原因协议兼容性最好默认暴露 OpenAI 兼容的/chat/completions接口业务端改一个 base_url 就能切换过来Flask 端原有代码不改还有小幅优化空间。模型适配器覆盖面大主流的 API 服务商几乎都有适配器模型清单和接口差异在网关层已经被处理掉了。成本控制能力突出支持预算设置、按用户/按 key 维度做 spend 统计这是很多团队最需要的能力。社区活跃文档完善issue 响应快遇到问题基本能搜到现成答案。当然one-api 这类中文社区用得多的方案也很有特色界面友好度甚至更高。但就“多模型 API 网关”这个目标来说LiteLLM Proxy 更贴近编程接入场景也更符合我的技术栈习惯。3.2 部署与配置细节部署 LiteLLM Proxy 非常简单我用 Docker 直接拉镜像跑起来docker run -d \ --name litellm-proxy \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e OPENAI_API_KEYxxx \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml关键在config.yaml的配置。我的配置大概是这样的model_list: - model_name: fast-chat litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: coder litellm_params: model: openai/deepseek-coder api_key: os.environ/DEEPSEEK_API_KEY base_url: https://api.deepseek.com/v1 - model_name: blue-yun litellm_params: model: openai/blue-yun-model api_key: os.environ/BLUEYUN_API_KEY base_url: https://maas.blueyun.example.com/v1 litellm_settings: drop_params: true set_verbose: false general_settings: database_url: postgresql://user:passpostgres/litellm重点说几个配置细节。第一model_name是给业务端用的别名可以随意定制业务端只认这个不关心底层具体是哪个模型。第二litellm_params里的model字段规则是“服务商/模型名”不同服务商对应不同前缀。第三base_url可以覆盖默认接入地址这就是对接蓝耘元生代 MaaS 这类平台的关键点只要它暴露 OpenAI 兼容接口配置就能用。数据库我接了一个 PostgreSQL 用来记录 spend 日志。如果完全没有成本统计需求可以先用 SQLite但我个人不建议因为后面要查数据的时候会非常痛苦。3.3 接入 Flask 应用的方式接入过程比我想象得轻松很多。原来 Flask 业务代码里我是用 openai SDK 直接调用各个 base_url 的现在只需要把 base_url 改成 LiteLLM Proxy 的地址api_key 用网关自己定义的 key然后 model 参数使用 config.yaml 中的别名即可import openai client openai.OpenAI( api_keysk-litellm-proxy-key, base_urlhttp://localhost:4000/v1 ) resp client.chat.completions.create( modelcoder, messages[{role: user, content: 用 Python 写一个解析 Docker 日志的脚本}] )整个 Flask 端改动量很小原来那些 provider 协议适配、key 管理、重试逻辑的代码几乎可以全删。业务代码终于回到它该有的样子只管组织 prompt、调用统一的模型入口、处理返回结果。3.4 功能丰富度模型路由、预算控制、日志LiteLLM Proxy 在功能完整度上跟手写方案完全不在一个量级。它内置了很多我不需要自己造轮子的功能模型自动路由可以配置多个模型作为同一别名的后端网关按优先级或负载均衡策略分发请求。预算控制支持设定总预算、单 key 预算、单用户预算超过预算自动拒绝请求这对做内部工具控制成本非常有价值。日志与用量统计每次请求的 token、费用、延迟都自动记录支持按 key、按用户、按模型维度聚合查询。key 管理通过管理接口动态创建虚拟 key不用再直接暴露底层服务商的 key。我尤其喜欢它的虚拟 key 机制。以前我手上就两三个 key搞得跟商业机密一样到处藏着现在每个业务功能创建独立的 key即使某个 key 泄露也可以在管理端一键吊销完全不影响其他业务。4. 一个月实测数据与对比到底哪个更划算4.1 性能与延迟实测我实际压过两条链路的延迟从 Flask 应用发出请求到拿到完整响应的总耗时包含网络和模型生成时间。手写网关本身的转发开销可以忽略不计LiteLLM Proxy 因为是独立进程网络多一跳但在本地部署场景下多出的耗时基本在 2-5ms 以内。真正的延迟差异来自模型本身和网络链路这才是大头。同样的模型从 Flask 直连模型服务商和通过 LiteLLM Proxy 转发用户感知几乎没有差别。所以在性能这个维度上两者算是打了个平手。4.2 维护成本与稳定性对比维护成本才是这个月感受最深的差异。手写方案前半月的开发和调试大约花了一周后面每天都在修边角料这个 provider 超时了、那个 key 被限流了、日志查询太慢了。每天都有新的“小微问题”要修越修越觉得没底。LiteLLM Proxy 部署花了一个多小时之后基本没怎么管。它自己处理了重试、超时、限流适配这些问题底层模型服务的异常也能通过网关日志清楚看到。稳定性上LiteLLM Proxy 跑了半个月没有出现一次网关自身引起的故障。4.3 功能完整度对比下面这张表我整理了很久基本能代表多模型 API 网关选型的核心对比维度维度手写方案FlaskLiteLLM Proxy接入模型数量靠手工加配置配置化改动小协议兼容只支持 OpenAI 兼容为主覆盖几乎所有主流服务商key 管理环境变量自己写轮换虚拟 key、动态创建、吊销成本统计自己写 SQLite内置日/月维度统计预算控制没有内置预算限制重试与限流自己写简单重试内置指数退避、多策略部署复杂度无额外依赖一个 Docker 容器可观测性简单日志结构化日志管理端查询适合场景单一服务商、模型数少多模型、多服务商、多人协作如果你只是调两三个模型、只有自己一个人用手写方案确实可行但只要涉及多模型、多 key、多环境、多团队这些维度开源网关的功能沉淀是短时间内用代码追不上的。4.4 有没有出现事故或坑两个方案都出过幺蛾子说几个印象比较深的。手写网关最严重的一次事故是 key 池轮换逻辑出问题某个 key 被限流后轮换逻辑没有及时把它摘除导致连续几个请求都打到被限流的 key 上业务端报错率飙升。排查了一下午最后发现是轮换列表没有标记 key 的健康状态修复也不复杂但这说明手写方案里每一个“小功能”都要自己兜底。LiteLLM Proxy 这边最大的坑反而不是它本身而是我第一次配置时把base_url写错了导致请求被路由到默认服务商返回了一堆莫名其妙的错误。后来仔细看文档才发现很多 MaaS 平台的接入地址是需要显式配置 base_url 的。这个问题花了一个小时的排查时间但属于配置问题不是网关本身的问题。5. 多模型 API 网关的几个隐藏坑和建议5.1 坑点一模型名与真实模型映射混乱这个坑不管是手写方案还是用 LiteLLM Proxy 都会遇到。业务端传一个coder网关要能正确映射到服务商真实的模型名。如果服务商更新了模型版本别名映射没跟着更新业务端用的还是旧的能力表现会和你预期的不一致。我的经验是在网关层定义清晰、有业务含义的模型别名并且维护一份模型版本变更对照文档。模型升级时先改网关配置再通知业务端降低模型名混乱的风险。5.2 坑点二成本分摊统计被忽略很多自建网关的初版代码根本没有考虑到成本统计。真实场景是月底财务或项目负责人拿着账单来问你这个月费用为什么涨了哪个功能花的钱手写方案里我用 SQLite 记录请求日志按功能维度打 tag统计时手动写 SQL 汇总。LiteLLM Proxy 直接内置了 spend 统计还能按虚拟 key 维度拆分这点在项目规模变大之后会显得特别值钱。5.3 坑点三并发与超时设置API 网关层不考虑并发与超时生产环境一定会给你颜色看。有段时间我的 DevOps 助手一到下午高峰期就大面积超时原因是某个模型在高峰期响应变慢而网关和业务端都没有合理的超时和重试策略导致请求堆积。我的建议是在网关层和业务端都设置合理的超时时间并且区分快速失败的场景和可以等待重试的场景。比如简单的文本分类请求 10 秒内必须返回而代码生成场景可以放宽到 60 秒以上。用同一个超时时间处理所有请求最终要么牺牲体验要么牺牲稳定性。6. 结论与建议我的最终选择跑了一个月之后我的选择很明确主体方案用 LiteLLM Proxy手写方案只保留一个极简版本的转发层作为特殊场景兜底。如果你问我“到底值不值得自己搭”我的答案是看规模。模型数量不超过三个、调用量不大、只有你自己开发使用那么自己写一个二三十行的转发函数就够了别引入额外组件一旦模型数量变多、有多个环境多个开发者在协作、有成本分摊和预算控制需求直接上现成开源网关别浪费时间去重复造轮子。开源网关配置一个下午就能用自己写的话可能就要搭进两周的时间还未必有它功能完善。7. 最后再分享一点个人体会踩过这轮坑之后我最大的体会是网关这种基础组件最核心的价值不是“转发”这个动作而是把“模型接入”这个事变成一个可配置、可观察、可控制的标准动作。我个人在实际操作中的体会是无论你选择哪种方案都要在前期把模型清单、key 管理方式和成本统计需求理清楚。网关的上限其实取决于你对业务场景的理解深度而不是工具本身。如果你现在正在纠结要不要自己搭网关可以先从最小的可用版本开始把核心链路跑通等业务量真正上来之后再决定要不要切换到功能更全的开源方案。这样既不会因为前期过度设计而浪费时间也不会因为长期裸奔而留下隐患。