上个月我在维护一个内部工具时接了三家大模型供应商的接口。每个厂商的请求体结构不一样、认证方式不一样、超时和限流表现也不一样业务代码里塞满了各种 if-else 分支。后来我把这套调用关系收敛成了统一的网关入口上层代码只认识一个地址、一套请求格式切换模型时只要改一份配置文件。这篇文章想和你聊聊我是怎么基于 LiteLLM 把这套“LLM 网关”搭起来的以及它解决了什么问题、核心原理是什么、实际部署和踩坑经验有哪些。如果你也在维护多个大模型渠道或者想在公司内部统一模型出口这篇实战记录应该能帮你少走不少弯路。1. 为什么需要 LiteLLM 这一层“LLM 网关”1.1 多供应商接入的重复劳动在哪里先说说我在没引入 LiteLLM 之前遇到的实际场景。当时项目里要用到三家模型服务代码里为每一家都写了一个独立的 client 封装。每家给的 SDK 不一样有的提供同步接口有的只提供异步接口有的消息格式里要带 system prompt有的字段名叫 system_message。这些差异在单家接入时不算什么但一旦加上模型切换、密钥轮换、灰度放量代码就彻底失控了。更麻烦的是重试逻辑。厂商 A 的限流返回值是一种结构厂商 B 是另一种厂商 C 甚至不抛异常而是直接返回一段空内容。我要在业务层处理这些差异还要为每个厂商单独统计 token 消耗和成本。有一次为了支持一个新渠道我一个人花了将近两个工作日改的却只是一层薄薄的适配代码这太不值了。后来我意识到这类问题有一个统一解法在业务代码和各个模型厂商之间插一层网关。网关对外暴露一套稳定接口对内负责翻译各家协议、管理密钥、做重试和负载均衡。业务代码完全不关心底层是哪个厂商只关心这个入口是否稳定、响应是否符合预期。这就是 LLM 网关存在的核心意义。1.2 网关层到底做了什么事用一个生活类比来说你手里有很多张银行卡每张卡的充值方式、手续费、到账时间都不一样。如果没有一个统一的“支付中台”你每次付款都要自己判断用哪张卡、怎么处理银行差异。LLM 网关就是这个支付中台它会根据你的配置决定走哪条渠道帮你自动处理限额、重试和账单汇总。具体到 LiteLLM它对外兼容 OpenAI 的消息格式也就是说你只需要按照一套非常通用的 chat/completions 风格发请求。你在配置里声明每个内部模型名对应到真实的厂商模型LiteLLM 会完成协议翻译。它还附带了一堆生产环境需要的功能多个密钥轮询、自动重试、故障转移、预算控制、虚拟 API Key、请求日志和成本统计。这些能力听起来很散但放在一起它们解决的是同一个问题让上层应用开发者不关心模型供应商的差异让基础设施负责人能在一个地方管控所有模型流量。如果你的团队超过三个人、项目里用到了两家以上的模型服务这个抽象层的价值会非常明显。1.3 LiteLLM 的三个使用形态LiteLLM 不是只有一种使用方式它至少可以以三种形态出现。第一种是独立代理服务也就是部署一个进程暴露类似“localhost:4000”的 HTTP 接口。这种方式适合多个服务共享同一个模型出口团队里所有人把请求打到同一个地址密钥和预算在网关层集中管理。第二种是作为 Python 库在程序里直接调用。你可以把 LiteLLM 当作一个路由封装直接在代码里写litellm.completion()适合脚本、数据管线和快速原型。第三种是命令行工具用来做一些一次性测试、查看配置、快速验证某个模型渠道是否可用。我个人的建议是如果只是自己写写脚本用第二种形态就够了如果是要给团队或者多个业务系统用必须上第一种形态。网关一旦独立部署你才能把权限、预算、日志、监控这些控制面能力真正用起来。2. 先把它跑起来安装、配置和第一次调用2.1 安装与基础依赖我是在一个独立虚拟环境里安装的这样做的好处是避免污染业务项目的依赖。安装命令很简单pip install litellm[proxy]这个带方括号的安装方式会同时装下代理服务所需的额外依赖。如果你只需要在 Python 脚本里调用直接pip install litellm就够。装完之后可以用litellm --help确认命令可用我通常还会跑一下python -c import litellm; print(litellm.version)看版本号方便后面排查问题。如果你不想污染本机环境也可以直接用 Docker 镜像跑代理服务。我在本地开发时习惯用裸进程部署到服务器时用容器因为容器对配置文件、端口和环境变量的管理更干净。无论哪种方式核心都是选一份配置文件作为所有模型路由的声明。2.2 用 config.yaml 声明模型路由LiteLLM 的代理服务围绕配置文件展开最常见的入口是一个 YAML 文件。下面是一份最基础的配置model_list: - model_name: my-llm litellm_params: model: openai/your-model-id api_key: sk-xxxx这里有两个容易搞混的字段。model_name是内部名字也就是你的业务代码里会调用的名字litellm_params.model是真实路由标识它由“协议前缀/厂商模型ID”组成。openai/这个前缀不是随便写的它告诉 LiteLLM 走 OpenAI 兼容协议去连接目标服务。如果底层服务是私有化部署的兼容接口比如用 vLLM 起的服务配置会写成这样model_list: - model_name: my-private-llm litellm_params: model: vllm/your-local-model-name api_base: http://127.0.0.1:8000/v1 api_key: dummy-key你可以看到协议前缀决定了用什么规则去连接后面的参数决定了连到哪、用什么身份认证。这个设计非常像数据库连接池里的 JDBC 驱动不同数据库用不同驱动名但上层得到的操作方式是一样的。2.3 启动代理并验证配置写好后启动代理只需要一行命令litellm --config config.yaml --port 4000启动成功后终端会打印一个本地地址。我一般先用 curl 做一次最简单的连通性验证curl http://localhost:4000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-any \ -d { model: my-llm, messages: [{role: user, content: 你好}] }这里的Authorization头里的 key 可以随便填因为我们还没有开启主密钥校验。如果不开启代理相当于一个完全开放的服务只建议在本地调试时这样用。真正部署时一定要设置主密钥。验证通过后你可以在任何语言里使用 OpenAI 兼容 SDK 来调用它。以 Python 为例from openai import OpenAI client OpenAI( base_urlhttp://localhost:4000, api_keysk-any, ) resp client.chat.completions.create( modelmy-llm, messages[{role: user, content: 今天天气怎么样}], ) print(resp.choices[0].message.content)注意这里的关键点业务代码里并没有出现底层厂商的 SDK也没有厂商特定的消息结构。你只是在跟一个 OpenAI 风格的服务说话至于这个服务背后实际连的是哪家、怎么鉴权业务代码一概不知。这就是网关注入之后最明显的变化。2.4 模型名映射一个非常重要的设计在动手接入第一个模型时我就深刻体会到model_name和真实模型 ID 解耦的好处了。你可以在配置里把内部名定为my-llm业务代码全部引用它。今天底层映射到厂商 A 的模型明天想换成厂商 B 的同级别模型只需要改配置文件、重载代理业务代码一行都不用动。有人可能会问那我为什么不直接在代码里维护一个映射表非要放到网关层呢区别在于网关注入后映射关系变成了运行时的路由决策而不是编译期的常量。你可以通过配置中心动态更新、做灰度、按团队区分模型版本这些能力如果散落在各个业务代码里根本没法统一管理。这个映射设计也带来一个约束内部模型名必须起得足够稳定。我见过太多人把内部名起成“gpt4-official”这种名字结果后来换了模型还得动代码。更合理的做法是按用途命名比如“general-chat”“code-review-llm”“embedding-main”把具体模型选择权留给配置。3. 生产环境的必修课重试、负载均衡、预算和密钥管理3.1 一个供应商多个密钥的负载均衡模型厂商为了限流往往会给每个 API Key 设置每分钟请求数或者 token 吞吐上限。单靠一个 key高峰时段很快就会触发限流。LiteLLM 支持一个很实用的能力对同一个内部模型名配置多条记录每条用不同的密钥网关会自动做轮询。model_list: - model_name: my-llm litellm_params: model: openai/your-model-id api_key: sk-key-one - model_name: my-llm litellm_params: model: openai/your-model-id api_key: sk-key-two当请求打到my-llm时LiteLLM 会在两个 key 之间做循环负载均衡。这样我不用在代码里维护 key 池也不用关心当前用的是哪个 key。需要增加容量时往配置里加一个条目就够了。这里有一个要注意的点同一模型不同 key 的成本统计虽然都会记录下来但如果厂商对每个 key 有独立的账单你对账时就得按时间段导出数据来核对。我在实际使用中发现网关层的负载均衡会打散 key 的调用节奏这会导致厂商控制台看到的调用曲线被“抹平”和网关日志里的按小时统计略有差异属于正常现象。3.2 自动重试与超时设置生产环境里上游模型接口偶尔抖动是常态不一定每次都是服务不可用有时只是某个节点负载过高导致响应变慢。LiteLLM 的全局设置里可以配置重试次数和超时时间litellm_settings: num_retries: 3 request_timeout: 30 retry_on_5xx: true这里我想强调重试和负载均衡是两层不同的逻辑。负载均衡是提前分散流量让每个 key、每个渠道的负载更均衡重试是某个具体请求失败后再尝试一次。两者可以同时配置但思维上要分清楚。还有一个容易被忽略的细节重试策略要与超时策略配合好。如果你把超时设成 60 秒同时又开 3 次重试那么一个慢请求在最坏情况下可能让调用方等超过 3 分钟。我通常会把超时控制在 10 到 30 秒之间重试次数控制在 2 到 3 次并且只在遇到连接超时、5xx 这类可恢复错误时才重试避免把业务层的重复提交放大到上游。3.3 预算控制与虚拟 API Key给团队共享一个网关节点的前提是你能区分出每个团队的用量和成本。LiteLLM 支持主密钥和虚拟 API Key 的概念。你可以用主密钥管理整个代理然后为每个项目或每个成员生成虚拟 key每个虚拟 key 可以单独设置预算上限。我没有把虚拟 key 的生成细节写进配置文件而是通过调用代理的管理接口来创建。大致思路是这样用主密钥访问/key/generate接口传入预算上限、模型白名单、过期时间返回一个sk-开头的虚拟 key业务方只拿到这个 key。虚拟 key 的好处在于它可以随时吊销、单独限流、独立统计。某个项目跑量失控时我只需要在网关层处理它的 key根本不用去碰底层厂商的账户。这相当于在厂商的账户体系和内部业务之间加了一层可控的缓冲。如果你只部署一个代理给两三个人测试用虚拟 key 的价值还不明显。一旦代理要服务十几个内部系统没有虚拟 key 的后果就是所有人共用一个 key出了问题根本没法定位是哪个项目在刷流量。3.4 日志与请求轨迹有一次业务方反馈某个请求特别慢如果代理没有日志我只能从上游厂商的控制台去猜。LiteLLM 默认会在终端打印请求信息包含模型名、耗时、token 消耗和状态码。生产环境里我会把日志收集到统一日志系统并按请求 ID 串联上下游。这里给大家一个排查思路上的建议先看网关的访问日志确认请求是否到达代理、代理转发后返回了什么状态、耗时主要花在了哪一段。再看厂商侧是否有对应的调用记录。如果网关日志显示转发出去后被挂起直到超时而厂商侧没有记录大概率是网络链路问题如果厂商侧有记录且耗时本来就是 20 秒那就是上游模型本身慢再调超时和重试策略才有效。4. 踩坑记录我自己碰过的五个问题与排查过程4.1 请求被路由到了不存在的模型我第一次部署代理时遇到一个诡异的现象业务代码里明明用的模型名和配置完全一致但请求总是 400提示某个根本不存在的模型 ID。后来才发现代理服务在启动时加载的配置文件和我当前编辑的不是同一份我在命令行里少传了--config参数导致它用了默认配置。这个坑看似低级但值得说是因为它反映了一个排查原则先确认代理实际加载的配置是什么。LiteLLM 提供了一个查看模型列表的接口访问/model/info就能看到当前代理认识哪些模型名。如果你发现列出的名字和自己写的配置不一致那八成是加载错了配置文件或者环境变量覆盖了配置内容。4.2 流式与非流式的兼容迷思另一个让我花了不少时间的问题是流式响应。配置和模型名都对非流式调用也很正常但一旦开启stream: true有些客户端就收不到完整结果或者直接报错。后来我梳理了一遍链路才明白不同上游对流式格式的支持细节是有差异的。LiteLLM 虽然会把响应翻译成统一的流式结构但如果你使用的客户端库版本太旧可能对某些增量字段解析不了。我的处理方式有两步一是把客户端库升级到较新版本二是在代理层不额外开启影响 SSE 的中间件避免缓冲导致流式内容迟迟不发出去。这里要说一个通用经验排查流式问题不要只盯着网关还要看客户端和网络中间件。很多流式问题的根源不在模型而在于连接被某个环节缓冲了。你可以先用 curl 直接打到代理接口看原始响应确认代理返回的文本是否即时刷新。如果 curl 没问题再去排查业务代码里的读取逻辑。4.3 套了两层代理之后超时怎么处理公司内部网络往往有统一的出口代理于是出现了一层很现实的复杂链路业务服务到 LiteLLMLiteLLM 再到上游厂商中间还可能经过公司的正向代理。这种链路一长超时问题就变得非常莫名其妙。我遇到的情况是上游模型本身只要两三秒但请求在网关处经常等满 60 秒才报错。用前面说的排查方法一层层看最终定位到正向代理上它会对长时间空闲的连接做清理而模型推理过程中的等待被它误判成了空闲连接。解决办法是设置合适的环境变量让 LiteLLM 知道经过代理同时调整上游厂商接口的超时重试逻辑或者干脆给某些直连的域名配置绕过规则。遇到这种网络层问题我的建议是不要一上来就调模型超时先把整条链路的每一跳耗时测出来。你可以在启动测试时用curl -w输出连接时间、传输时间、总时间看看具体哪一跳异常的耗时最长。4.4 环境变量不生效导致的认证错误LiteLLM 支持从环境变量读取 API Key比如配置里写api_key: os.environ/KEY_NAME。这个功能很实用但也容易踩坑。我遇到过一次启动后所有请求都报 401翻开日志发现网关确实在尝试读取某个环境变量但那个变量在当前 shell 里根本没导出。这种问题最常见的两个原因是启动代理的进程环境里没有定义对应的变量或者配置文件里引用的变量名和实际设置的名字拼写不一致。后来我习惯在启动命令前先加一句测试echo ${KEY_NAME:?环境变量未设置}如果变量不存在命令会直接提示避免等到请求阶段才发现问题。另一个更稳妥的做法是在容器编排平台里统一维护密钥这样本地开发环境不太容易因为 shell 环境不一致而出错。4.5 私有化模型偶尔 502该如何归因本地用 vLLM 起一个大模型服务时我遇到过请求偶尔返回 502 的情况而且看起来毫无规律。一开始我以为是 LiteLLM 配置问题后来越来越频繁才想到去查模型服务本身的日志。排查链路是这样的先在网关日志里找到那条 502 对应的请求确认它确实被转发到了 vLLM 服务再查 vLLM 的访问日志看看上游是否真的接到了请求如果上游日志显示连接被重置基本上是模型服务并发处理能力被打满了或者健康检查把实例摘掉了。我在配置里给这个本地模型加了一个 fallback 到其他渠道同时降低了网关侧的最大并发限制问题明显缓解。这个案例想表达的核心是502 只是网关向上游转发失败后的表现它本身不告诉你根因。你需要沿着请求链路往上层的实际服务日志去查而不是只看网关的错误码。生产环境里一定要把“网关日志”和“上游模型服务日志”放在同一个可检索的地方不然这种问题会非常难定位。5. 进阶用法把 LiteLLM 当成 Python 库而不是只当网关5.1 直接用 SDK 做程序内路由如果只是几个独立业务服务共享模型入口用代理服务很合适。但如果你写的是一个数据处理脚本、一个消息队列消费者或者不想维护一个常驻进程也可以直接把 LiteLLM 当 Python 库用from litellm import completion response completion( modelopenai/your-model-id, messages[{role: user, content: 你好}], api_keysk-xxxx, )这种模式下LiteLLM 帮你在库内部完成路由选择、重试和响应兼容。相比代理省去了一个网络跳转也省去了维护网关进程的成本。但它也有明显的短板虚拟 API Key、预算控制、多团队共享这些能力只能在代理模式下用到。如果你的目标是团队级基础设施我还是建议优先用代理模式。我在实际项目里会在同一条代码路径上同时支持两种模式本地开发时直接调用 SDK测试环境走代理。这两种模式切换的成本很低因为核心方法都是同一个风格。你只需要把 base_url 之类的参数抽成一个环境变量代码结构不用大改。5.2 统一入口之后的收益把入口统一之后我感受最深的一点是“切换模型不再是一件伤筋动骨的事”。以前换供应商要动业务代码要做联调要跑回归现在只需要改配置然后让代理重新加载业务代码根本无感知。这种安全感也鼓励团队更频繁地做模型选型测试反正切换成本很低。另一个收益是权限和成本的集中管理。所有业务系统共用同一个网关时谁在用什么模型、每个月的 token 消耗是多少在一个地方就能看全。虽然各家厂商的控制台也有统计数据但口径不一致、数据导出麻烦远不如网关层统一计算来得方便。5.3 一个小型的可复用配置模板这里给出一份我常用的配置模板它不是最优解但足够覆盖大多数中小团队的需求model_list: - model_name: general-chat litellm_params: model: openai/your-model-id api_key: os.environ/KEY_A - model_name: local-llm litellm_params: model: vllm/local-model-id api_base: http://127.0.0.1:8000/v1 api_key: dummy-key litellm_settings: num_retries: 3 request_timeout: 30 retry_on_5xx: true general_settings: master_key: os.environ/MASTER_KEY database_url: os.environ/DATABASE_URL这个模板里我把外部 API Key 都通过环境变量注入没有明文写死。database_url是用来存虚拟 key 和用量统计的如果你要用虚拟 key 功能建议配置一个数据库否则重启后 key 和预算记录容易丢失。刚开始可以先用本地数据库等规模上来再迁移到独立实例。6. 如果让我重新部署一次我会做哪些调整整个项目做下来最大的教训是一开始我没有把代理当作基础设施来对待而是当成一个临时工具在用。等到业务方陆续接入后再回头补虚拟 key、补数据库、补监控就会比较被动。如果让我重新部署一次我会优先做几件事。第一容器化部署配置文件和数据卷挂载得清清楚楚版本升级也不会影响已接入的业务。第二从第一天就启用主密钥和虚拟 key哪怕现在只有两个项目在接入也要把权限边界划清楚。第三把模型命名规范定下来内部模型名一律按业务用途命名不携带具体厂商和型号信息。第四把网关日志和上游模型服务的日志统一收集到同一个检索系统里这样排查问题的时候不用到处翻终端。在这些事情都准备好的前提下LiteLLM 就不再只是一个 API 转发工具而是一个真正属于团队的模型接入层。后续不管是引入新厂商、做模型切换还是做按部门成本分摊都会顺手很多。这套玩法我跑下来之后最大的体会是不要把技术债留给业务代码把变化收敛到配置和网关层长期来看是性价比最高的一步棋。
阅读完成 · 觉得有帮助?