说实话最初我听到 litellm 这个名字时以为它只是又一个模型封装库。直到我把组里五六套不同供应商的模型 API 全部切到它上面之后才意识到这类工具真正解决的是工程治理问题多 Key 管理、格式差异、成本追踪、权限隔离、限流失控……听起来都是小问题攒到一起就是灾难。这篇文章与其说是教程不如说是我把一个轻量 LLM 网关从搭建到生产落地的完整笔记适合正在做多模型接入、或者被 API 供应商频繁限流和换模型名折腾过的后端开发者参考。1. 为什么我会把全团队的模型调用交给 litellm1.1 从十份 API Key 到一把钥匙的转变去年我们组做了一个聚合内容分析平台底层需要接多家模型供应商有的擅长长文本总结有的便宜适合批量分类还有的高质量但贵得离谱。早期代码很直接每个渠道单独封装一个类申请各自的 Key然后写一个本地路由函数根据任务类型分发请求。表面看没什么问题实际用了两周就开始难受。第一是 Key 散落在多个环境变量和配置文件里新同学入职配一次环境至少要问三个人第二是每家返回结构略有差异有的叫 choices有的叫 output有的还嵌套了一层 message解析层被迫写满了兼容补丁第三也是最致命的——只要有一家供应商改名、换域名或者调整限流策略我就要改代码重新上线业务方完全无法感知这个底层切换过程。litellm 最大的价值就是把这些完全不同的上游接口收敛成一个入口。它的核心形态是一个服务官方叫 Gateway Server你告诉它我有哪几个模型来源、分别用哪把 Key它就可以对外暴露一个风格统一的 HTTP 接口而且这个接口协议和市面上最常见的/v1/chat/completions协议保持一致。因此对于业务代码来说它根本不需要知道上游到底有几个供应商只需要记住一个地址、一个 Key。1.2 网关到底帮我挡掉了哪些麻烦接入 litellm 之后我这边至少省掉了四类重复劳动。第一是统一鉴权业务侧不再持有任何真实供应商 Key全部换成网关生成的虚拟 Key一旦某个实习生把 Key 提交到公开仓库我可以秒吊销那一把而不用去各供应商控制台翻箱倒柜。第二是统一重试与超时之前每个渠道自己写重试逻辑退避策略五花八门现在网关统一配置遇到网络抖动和上游 429 按同一套策略处理下游服务不会因为某个模型抖动而连环超时。第三是日志归集以前每次调用想查昨天某个请求花了多少钱、用了多少 token几乎不可能因为各家控制台的口径不统一、刷新还有延迟。litellm 可以把每次请求的 model、prompt_tokens、completion_tokens、cost、latency 全部落库管理台直接按时间线刷出来。第四是省钱逻辑可以写在一个地方比如我们内部定了规则普通分类任务一律走便宜模型只有摘要质量不达标时才允许升级到高价模型这种带预算控制的策略写进网关配置就能全局生效。1.3 和自研 adapter 的本质区别有人会说这些功能我自己用适配层也能做啊。说实话早期我也写过一千多行的 Provider 适配器真正的问题不在适配层而在状态谁来维护 Key谁来记录历史谁保证重试不再击穿下游自己写代码重复造一个适配层最终只解决接口格式统一这一件事而 Key 管理、配额控制、用量审计这些必须具备但是又性感的工程能力几乎没有精力去做。litellm 本身就是围绕这些状态问题设计的。它不是把多个 SDK 包了一层皮而是把路由、重试、日志、计费、预算都内置成了配置化能力。你可以把网关部署成一个独立服务多个后端项目共享同一个入口任何一侧升级都不需要业务代码改造。这种边界感才是它和我自己写的 adapter 最大的不同它把不稳定因素隔绝在网关之外让业务侧代码保持干净。2. 一次请求在 litellm 里的完整旅程2.1 统一入口兼容主流 Chat 协议的接口层先看一个最普通的调用。业务后端通过 litellm 发请求代码几乎不需要改原有逻辑from openai import OpenAI client OpenAI( api_keysk-litellm-virtual-key-xxxx, base_urlhttp://localhost:4000/v1 ) resp client.chat.completions.create( modelmy-fast-summary, messages[{role: user, content: ...}] )注意这里用的还是 OpenAI 官方的 Python SDK只是换掉了 base_url 和 api_key。litellm 对外暴露的接口和这套协议完全兼容这意味着业务代码里根本不用引入它自己的独立 SDK原来怎么写现在还能怎么写。而网关内部会把这个请求转换成对应供应商真正需要的格式再向上游发出调用。这一步解决了两个隐含痛点。一是团队技术栈迁移成本极低换网关几乎等于改两行配置二是方便本地调试我经常直接拿 curl 打网关接口不需要临时写脚本去调各家 SDK。说实话这种客户对格式无感知的设计让我把网关看成 LLM 世界里的 API Gateway位置非常接近微服务架构里的 BFF 层。2.2 路由决策提供商前缀、模型别名与 fallbacklitellm 的模型配置里有一个关键约定模型名必须以供应商前缀开头比如openai/gpt-4.1、anthropic/claude-sonnet-4、gemini/gemini-2.5-flash。这个前缀绝不只是装饰它决定了网关加载哪一套适配器去处理上游协议。如果你写azure/gpt-4o它就知道要走某家的 Azure 子通道如果只写gpt-4o这种独立名那你必须在 config 里额外声明它的真实 provider 是谁。更常见的是设置业务别名。我不希望业务代码里写具体模型名因为一旦上游改版、换型号每个服务都要跟着改。所以我会在 config 里把路由层抽象出来model_list: - model_name: my-fast-summary litellm_params: model: anthropic/claude-haiku-4 api_key: os.environ/ANTHROPIC_API_KEY - model_name: my-high-quality litellm_params: model: openai/gpt-4.1 api_key: os.environ/OPENAI_API_KEY请求方永远只认my-fast-summary这个名字至于它背后到底是哪家的哪个版本由配置决定。大版本升级时我只需要改 yaml 然后重新加载网关业务方无感——这一点在生产环境真的能救命我经历过某供应商把旧型号下线平台上哀鸿遍野而我们这边只要把旧模型名切到新的兼容型号然后通知下游重新测一遍即可。fallback 也是同一层配置比如判断某家超时或者连续错误时自动切到另一家router_settings: fallbacks: [ {model_name: my-fast-summary, fallback_model_name: my-fast-summary-backup}, {model_name: my-high-quality, fallback_model_name: my-fast-summary} ]注意这里的 fallback 粒度是模型不是请求级重试。它的意思是当我请求 A 达到重试上限仍然没成功才把整个请求改发给 B。所以我通常只在业务允许降级的模型上配置 fallback比如内部搜索摘要可以用更便宜的模型兜底而学外语的核心评分请求绝不能悄悄换模型。2.3 流式响应、函数调用与向量接口的透传陷阱很多人以为切换网关后会阻碍流式响应实际上 litellm 对流式支持得比较好。它会把上游的 SSE 流切成小块转发给客户端同时还能逐块统计 token。唯一的代价是如果上游不支持流式而请求里强行开了 stream网关会退化为先收完再以流式吐给下游这样对首字延迟并没有改善。我建议在配置里就固定好哪些模型支持流式哪些不支持免得业务侧拿到一个伪流式还误以为很快。function calling工具调用这里有个值得注意的点。不同供应商对工具定义和 tool_choice 的规格不一样litellm 会在网关层做格式转换。但实际项目中我遇到过一种情况上游工具参数用 JSON Schema 写法而某厂商对嵌套 ref 解析不够严格导致工具总是被模型忽略。最终排查下来问题不在网关是供应商兼容问题。所以我的建议是若你的场景强依赖 function calling至少把主要供应商限定在两三家以内不要为了统一把所有都接进去否则一旦工具解析行为不一致你等于把兼容问题从代码层搬到了配置层并没有真正消失。embeddings 接口走的也是统一入口但要注意模型前缀和维度校验。我踩过一次某家 embedding 模型升级后返回向量维度变了下游向量库直接写入失败。litellm 网关不会替你做维度强校验这里建议在接入前用一条测试脚本把所有 embedding 模型的实际维度读一遍写进文档里避免临时换模型的时候翻车。3. 我整理的部署方案从配置文件到管理台3.1 用 Docker Compose 拉起网关与配套存储生产环境不建议只跑一个裸网关进程至少需要 redis 做缓存和速率统计、PostgreSQL 存日志和预算数据。我直接用 Docker Compose 一把梭简化版结构如下services: litellm-db: image: postgres:16 environment: POSTGRES_DB: litellm POSTGRES_USER: litellm POSTGRES_PASSWORD: change-me volumes: - pgdata:/var/lib/postgresql/data litellm-redis: image: redis:7-alpine litellm: image: ghcr.io/berriai/litellm:main-latest depends_on: - litellm-db - litellm-redis ports: - 4000:4000 environment: LITELLM_MASTER_KEY: sk-master-123456 LITELLM_DATABASE_URL: postgresql://litellm:change-melitellm-db:5432/litellm REDIS_URL: redis://litellm-redis:6379 volumes: - ./config.yaml:/app/config.yaml这里有个小经验LITELLM_MASTER_KEY是管理员的钥匙首次启动时务必设置一个强随机串。它可以用来创建和管理其他所有虚拟 Key也可以访问管理台泄露它等于泄露了你所有渠道的真实 Key安全等级要按根权限对待。另外日志落库依赖 Postgres如果只是本地快速体验不配数据库也能跑但管理台里的历史记录和预算数据就是空的。我见过有人拿着裸进程试了半天说日志看不到其实不是 bug是没接 DB。3.2 config.yaml 的关键配置拆解很多人拿到 config.yaml 会盯着 model_list 填以为那是全部。实际上一个能支撑生产的配置文件至少要覆盖四块模型列表、通用调用参数、路由策略、成本控制参数。我一般会拆成几个小节来维护。模型列表上面已经写过。通用参数这里推荐关注几个键litellm_settings: drop_params: true set_verbose: false cache: true cache_params: type: redisdrop_params是我非常喜欢的一个开关。它解决的是请求带了某供应商不支持的参数的问题——比如一家模型不支持 temperature而业务侧统一传了网关会直接把参数丢弃而不是抛错。这对聚合适配场景很有用但它也会掩盖真正不该传的参数所以我只在可控内部环境开启。路由策略放在router_settings里。除了 fallback我还会设置num_retries、timeout、allowed_fails这些值。默认重试次数千万别设太大我一开始设了 5 次重试结果上游抖动时流量被放大 5 倍直接把供应商的配额打爆。现在统一设置为 1~2 次重试宁可请求失败返回给业务侧做降级也不要让网关成为流量放大器。成本控制参数写在 model 级别例如给某个模型设置max_tokens上限、rpm每分钟请求数、tpm每分钟 token 数。这些会被 Redis 统一计数超过就直接拒绝防止某个内部服务 debug 循环时把预算烧光。3.3 虚拟密钥与多团队账单隔离管理台登录后我会给每个业务线生成一把独立的虚拟 Key名字就叫team-search、team-etl、team-batch。每把 Key 可以绑定专门的模型列表也可以设置独立的 budget这样每个团队自己能看到自己的消费互不干扰管理员又能总览全部。这里有一个非常实用的机制叫teamuser双层维度。我建议至少做到 team 维度因为日志和预算统计都按 team 聚合如果所有人共享一把 Key后面做成本分摊会非常痛苦。哪怕只是个小团队也请从第一天起养成分 Key 的习惯——之后查账只知道找原因的时候你就感谢这条规则了。管理台还有一个我几乎每天会看的功能按模型名查用量按供应商查错误码按 team 查费用排序。它省去了写 SQL 的麻烦但我是那种连管理台都要看原始记录的人所以 Postgres 表结构我还是摸了一遍。常用的表就是LiteLLM_SpendLogs里面字段很清楚model、team_id、startTime、endTime、prompt_tokens、completion_tokens、total_cost、latitude查账时直接拿这些字段做聚合即可。4. 真正让老板愿意留下的日志、预算与成本治理4.1 把每次请求的 token 与费用记进 PostgreSQL接入 litellm 之前我们的成本分析基本靠猜找各家控制台手动导出表格再手动去重、换算、核对。接入网关之后计费数据随着请求实时落库我可以直接在管理台看到某个模型今天的累计花费也可以写一行 SQL 查过去 7 天每个团队的成本占比。我常用的一条审查 SQL 大概是这样的SELECT team_id, model, date_trunc(day, startTime) AS day, sum(total_cost) AS cost, sum(prompt_tokens) AS prompt_tokens, sum(completion_tokens) AS completion_tokens FROM LiteLLM_SpendLogs WHERE startTime now() - interval 7 days GROUP BY team_id, model, day ORDER BY cost DESC LIMIT 50;这个查询带给我最直观的发现是团队实际费用大头永远集中在少数几个高价模型上而这些调用里又有相当比例是重复性的简单任务根本不需要那么贵的模型。一旦数据可视化出来你就有理由去劝业务方把简单任务迁移到便宜的模型上这种说服力比拍脑袋强太多了。日志里还有一个细节completion_tokens和prompt_tokens是从上游响应里解析的多数供应商会返回个别流式场景统计可能不完全准。说实话误差通常不超过 1%但如果做预算审计请保留原始响应中的 usage不要用网关估算值替代供应商的结算值。4.2 预算上限、速率限制与告警我给网关设置了两层预算防护。第一层是团队预算每个 Key 绑定一个max_budget比如每月 500 美元到达上限后网关直接拒绝该 Key 的请求返回 429业务侧立刻感知。第二层是模型级别预算防止某个模型被一个循环任务耗尽配额。实际配置时可以写在虚拟 Key 的创建页面也可以写在 config 里model_list: - model_name: my-fast-summary litellm_params: model: anthropic/claude-haiku-4 api_key: os.environ/ANTHROPIC_API_KEY rpm: 60 tpm: 50000 max_tokens: 4096rpm 和 tpm 需要根据你的真实流量估算。我的方法是跑上一周的生产日志统计每个模型的实际峰值在这个基础上留出 30% 裕量再填上去。填得太小业务侧会经常报限流填得太大预算保护就没有意义了。告警方面litellm 支持把请求失败率和预算消耗率暴露成 metrics 接口我直接用 Prometheus 抓取再配一套简单的规则当某个指令的错误率连续 5 分钟超过 10% 时告警当今日费用超过昨日同期 1.5 倍时告警。这套规则上线后帮我抓到了至少三次问题其中一次是某个模型供应商持续返回服务不可用另一次是测试环境被人写了个死循环调用批量任务。4.3 用量驱动的模型降级策略日志数据积累一段时间后就可以做策略优化了。以我们内容平台为例大约 80% 的调用是标题抽取、情感分类、意图识别这类任务质量要求中等且重复度高完全用不到最高档模型。所以我把默认路由改成my-fast-summary指向便宜模型只有明确需要深度推理的请求才显式指定高质量模型。更细粒度一点的玩法是在网关前面再放一个小的路由规则根据消息长度或关键词判断要不要走贵模型。例如长度小于 200 字且不含代码、总结、翻译等强语义词的请求一律走便宜模型。这个判断逻辑放在 litellm 之外的业务层实现原因是它依赖语义不适合塞进网关配置。配合前面的 SQL 统计我两周内就把整体单位请求费用降了大概 40%而且业务方几乎没有任何感知。还有一招是设置模型组的动态权重。litellm 支持在同一个 model_name 下配置多个上游模型按权重分配流量。我会在新模型上线时先用 10% 的权重小流量试跑对比延迟、错误率和输出质量满意后再逐步调到 100%。这比一次性切全量安全得多尤其是面对新版本模型时我强烈建议你这么干。5. 排障实录高发的坑和我的排查套路5.1 模型名 404 与别名不匹配最让新手崩溃的问题是请求发出去网关返回类似litellm.NotFoundError: model not found。我的第一反应永远是先检查 model_name 是否出现在 config.yaml 的 model_list 里并且网关是否重新加载过配置。注意修改 yaml 后进程不会自动热更新至少需要触发管理员 API 的 reload或者干脆重启容器。很多人改了配置还在原地打转就是这个原因。第二种常见情况是配置里写了model: openai/gpt-4.1但实际上游账户根本没有这个模型的权限也会表现为 404 或 403。我建议把所有供应商账号的真实可用模型列表先拉一个清单写进备注不要凭想象写。还有一种情况是模型名带版本后缀比如某厂商的平台有claude-sonnet-4-20250514这种带日期版本而配置里漏填了日期网关确实不认识。排查这类问题我一般的顺序是先看配置文件是否加载再看上游真实模型名最后看网络层是否被某种 Windows 防火墙或公司网关拦截。这里的网关是指网络层的策略不是 litellm 本身注意区分。5.2 429、超时与重试放大429 和超时就像一个硬币的两面上游限流会表现为频繁超时和 429如果你设置了过大的重试次数网关会像一个二次放大器把本来已经拥挤的上游打到更惨。我处理过的最严重一次是上游连续抖动网关保留 5 次重试结果同一分钟内有上千次请求在打同一个模型配额瞬间耗尽。我的解法很简单重试次数设小一点超时时间设短一点然后让业务侧自己决定是否可以降级。比如统一超时 60 秒不适合所有模型便宜的模型快给它 30 秒贵的模型慢给它 90 秒。这种按模型定超时的策略在 litellm 里可以直接写在 model 配置的 timeout 字段上。而且litellm 的 429 处理要区分两种来源。一种是上游返回 429网关可以等一小会再重试但要配合cooldown_time参数让模型冷却几分钟另一种是网关自己因为预算或速率限制返回 429那重试也没用应该去检查预算设置。我踩过把这两种混为一谈的坑白白浪费了很多排查时间。5.3 参数差异max_tokens、json 输出与上下文幻觉参数兼容是另一个高发区。不同供应商对同一个参数的解释有微妙差别比如某家模型要求max_tokens必须包含思考链 token如果你只设了 200实际可用的输出可能不到 100业务侧拿到半截回答还以为网关截断了。我建议在对质量有严格要求的模型上把max_tokens设得足够大让网关侧和上游都别当瓶颈宁可在业务侧再做截断。再说 response_format 或者 JSON mode。现在各家都支持要求返回 JSON但规格不统一有的要求json_object有的要求json_schema还有的不允许与流式同时开启。litellm 会做转换但一旦你的请求里同时出现response_format、tool_choice和temperature某些供应商可能直接报参数冲突。我的规避方式是能不用 function calling 的简单结构化场景就直接用 JSON mode必须用 function calling 的场景就不要在同一请求里再叠加 JSON mode。上下文尺寸也值得关注。上游模型的 context window 不一样如果配置里没有按模型设置max_input_tokens网关会原样把长文本扔过去很可能直接触发长度错误。我在所有生产 model 配置里都写上每个模型的真实 context 上限并留 20% 裕量避免长上下文的用户请求一到网关就整个失败。5.4 热加载、时钟漂移与密钥失效的诡异问题最后说几个不是每星期遇到、但遇到一次就足以让你怀疑人生的坑。第一个是热加载问题我在管理台修改了某些配置业务侧立刻发请求结果还是旧配置生效。大概率是 Redis 缓存没有刷新。litellm 的配置缓存默认有 TTL我一般通过管理 API 触发 reload并且验证日志里的model字段是否真的切到了新值。第二个是时钟漂移。这里的时钟指网关所在服务器的系统时间。litellm 在签发和验证 Key 时凉爽会产生时间敏感逻辑如果容器宿主机时间偏差过大可能出现 Key 忽然全部失效的情况。我经历过一次排查半天最后发现是底层虚拟机的硬件时钟跳了五分钟。处理办法是给宿主机配好时间同步服务别让生产容器裸奔。第三个是虚拟 Key 的过期策略。我给测试团队生成的 Key习惯设置一个短有效期比如 7 天。结果某个自动化任务还在用同一个 Key到期后全线 401。这类问题看似低级但很容易被忽略因为非技术同事通常根本不会想起 Key 还有有效期。我建议所有虚拟 Key 的有效期在创建时就填好并建一个每周检查任务提前三天提醒相关人续期。另外升级 litellm 版本时也要小心 breaking change。有两次我在小版本升级后管理台页面正常但某些早期语法被移除直接导致模型列表加载失败。现在的习惯是升级前先看一眼官方变更日志升级后立刻跑一次全模型连通性测试再放业务流量进去。最后分享一个长期收益的操作把所有模型连通性测试写成脚本每天定时跑一遍。脚本里包含每个模型的冒烟请求、流式请求和 embeddings 请求任何一条挂了就告警。说真的这套脚本在我接入 litellm 之后帮助我避免了很多次业务方先发现问题才来通知我的被动局面。网关的价值不只在于省事更在于它把混乱的模型生态变成了一个有条理的、可观测的系统。如果你也在多模型接入的泥潭里挣扎我的建议是先不要纠结某个模型参数怎么配把网关、可观测性和预算控制跑通再逐步完善模型路由。这套底座稳定之后后续接任何新模型都只是改一段 yaml 的分钟级操作。