
1. 为什么接入十几家模型前我会先搭一个统一调用层1.1 从一次“多模型竞标”需求说起去年我接手了一个多模型对比项目需求本身不复杂把几款主流大模型接入到同一个评分系统里让它们跑同一批客服问题再对比回答质量。刚开始我以为这事就是分别调几个 SDK真正做起来才发现难点根本不在模型能力而在接入层。第一个坑是每家 SDK 的返回结构都不一样。A 模型给的是data.choices[0].message.contentB 模型给的是data.content[0].textC 模型可能还要先走一层鉴权换取临时 token。业务层想写一个通用的取文本函数光是兼容这些结构就写了一堆 if-else。第二个坑是错误处理有的模型限流返回 429有的返回 400有的干脆把错误信息藏在响应体深处排查问题全靠肉眼。第三个坑是成本等我把几家模型都接完才发现想统计每个用户每次请求花了多少钱得自己拼 token 用量和价格表。后来我把这套重复劳动总结了一下任何一个多模型项目80% 的工作都花在适配上而不是调用上。于是我开始找现成的抽象层最后停在了一个叫 LiteLLM 的开源项目上。它给我的第一印象很朴素把不同模型的请求和响应都转成 OpenAI 风格。但就是这一层转换把我上面说的三个痛点一次全解决了。1.2 LiteLLM 到底做了什么LiteLLM 的核心能力说穿了不复杂你给它一个标准的 OpenAI 风格请求它会翻译成目标模型真正认识的格式发出去目标模型返回后它再翻译回 OpenAI 风格的结构还给你。模型名带上前缀比如openai/gpt-4o、anthropic/claude-3-5-sonnet、ollama/llama3它就知道该走哪条适配链路。这里有一个容易误解的点LiteLLM 不是一个模型聚合市场它不托管模型也没有自己的模型。它更像一个翻译层或网关负责把你的请求安全地转发给上游模型服务。真正发请求的仍然是各家模型的 API只是你不再需要关心它们各自的协议细节。它有两种主力形态一是直接当 Python 库用代码里调用litellm.completion()适合写脚本、做后处理、跑评测二是起一个本地代理服务对外暴露一个 OpenAI 兼容的 HTTP 接口适合团队共享一个入口统一管理密钥、成本、限流。我在项目里两种形态都用了先直接用 SDK 把链路跑通再上代理让同事一起用。下两节就按这个顺序展开。2. SDK 模式最快把 100 模型变成 OpenAI 风格接口2.1 安装与第一条 completion 请求先装依赖这一步没什么悬念。官方分发到 PyPI直接 pip 拉就行pip install litellm如果你要跑代理模式建议带上 proxy 扩展后面会用到 Flask 相关的依赖pip install litellm[proxy]装完之后配环境变量。各家模型的密钥最好都放在环境变量里而不是硬编码到代码中export OPENAI_API_KEYsk-... export ANTHROPIC_API_KEYsk-ant-... export GEMINI_API_KEY...然后写第一段调用代码你会发现和 OpenAI 官方 SDK 几乎一样from litellm import completion resp completion( modelgpt-4o-mini, messages[ {role: system, content: 你是一个乐于助人的助手。}, {role: user, content: 你好简单介绍一下你自己}, ], ) print(resp.choices[0].message.content)如果要把模型换成 Claude只需要把model参数改成claude-3-5-sonnet-20240620其他代码一行都不用动。这就是 LiteLLM 最直接的价值业务代码与具体模型解耦。后面哪怕把整个项目从某家模型切换到另一家核心逻辑也不会受影响。2.2 统一返回、流式输出和异步调用统一返回结构是 LiteLLM 最让我舒服的地方。无论上游是哪个模型返回的对象都长成 OpenAI 的样子choices[0].message.content是正文usage.prompt_tokens是输入 tokenusage.completion_tokens是输出 token。这样下游写解析逻辑时只需要认准一套字段。需要流式输出时也很直观加一个streamTrue即可resp completion( modelgpt-4o-mini, messages[{role: user, content: 写一首十五字的诗}], streamTrue, ) for chunk in resp: delta chunk.choices[0].delta.content if delta: print(delta, end, flushTrue)异步场景用acompletion签名基本一致from litellm import acompletion resp await acompletion( modelclaude-3-5-sonnet-20240620, messages[{role: user, content: 异步调用示例}], )在 FastAPI 这类异步框架里我建议优先用acompletion它能避免阻塞事件循环高并发下更稳。2.3 重试、超时和降级配置模型 API 的稳定性远不如传统数据库限流和超时是常态。LiteLLM 把重试和降级做成了配置项省掉了我原来自己写的一堆重试装饰器。import litellm # 全局重试 3 次每次间隔递增 litellm.num_retries 3 litellm.request_timeout 60 # 主模型失败时降级到备用模型 litellm.fallback_models [gpt-4o-mini, claude-3-5-haiku-20241022]这里有一条经验降级模型尽量选和主模型能力接近的否则业务会因为结果质量差异产生波动。我最早把降级模型设成gpt-4o-mini从 Claude 降级过来后部分评测场景的评分波动很大。后来改成换厂商但定位相似的模型波动明显小很多。如果你只想对单次请求重试不要设置全局参数直接传 kwargsresp completion( modelgpt-4o-mini, messages..., num_retries2, timeout30, )优先级上单次请求的 kwargs 会覆盖全局配置这个顺序在实际排查问题时很重要。3. 代理模式一个进程管好全团队的模型路由3.1 先用一行命令把代理跑起来SDK 模式解决的是我自己写得爽代理模式解决的是全团队用得爽。当项目从一个人扩展到多个人或者有多个服务都要接大模型时每个服务都配一遍密钥、都实现一遍重试逻辑就很浪费。LiteLLM 代理模式本质上是一个本地 HTTP 服务把 OpenAI 风格的请求转发到各个上游模型。启动方式很简单litellm --model gpt-4o-mini --port 4000然后任意语言都能用 OpenAI 的 SDK 访问它from openai import OpenAI client OpenAI( base_urlhttp://localhost:4000/v1, api_keysk-any-string, ) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 走代理测试}], )注意这里api_key随便填都可以绕过本地检查所以后面一定要加 master key不能让代理裸奔在局域网里。3.2 config.yaml 才是代理的灵魂单模型启动只是快速验证真正要管理多个模型必须用配置文件。LiteLLM 代理启动时支持--config config.yaml把模型列表、重试策略、密钥、预算全放在一个文件里团队可以直接维护。下面是我常用的最小配置model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY - model_name: llama3-local litellm_params: model: ollama/llama3 api_base: http://localhost:11434 litellm_settings: num_retries: 3 request_timeout: 60 fallback_models: [gpt-4o-mini] general_settings: master_key: sk-123456启动litellm --config config.yaml --port 4000这里有几个细节容易踩坑。第一model_name是你给业务方看的别名可以随便起litellm_params.model才是真正的上游模型标识必须带对前缀。第二os.environ/OPENAI_API_KEY是在配置里声明从环境变量读密钥这样密钥不会明文写入配置文件。第三fallback_models里填的是model_name别名不是上游模型名别写反了。3.3 多模型负载均衡和密钥分发同一个model_name可以配置多个 deploymentLiteLLM 会帮你在它们之间做路由。比如你有两个 OpenAI 账号都想接gpt-4o-mini配置可以写成model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY_1 - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY_2代理收到gpt-4o-mini的请求后会按负载均衡策略选一个上游转发默认偏向选择当前在途请求更少的那一个。这样做的收益很直接两个账号的限流可以互相缓解成本也能分散。密钥分发方面配置文件里的general_settings.master_key是所有管理操作的通行证。之后你可以在数据库里创建多个子 key分发给不同团队或应用。每个子 key 可以绑定不同的模型可见性、不同的预算上限。举个例子A 团队只能用gpt-4o-mini每月预算 100 美元B 团队可以用所有模型每月预算 500 美元。这个能力在多人协作时非常省心省得每个人自己拿主密钥到处用。3.4 生产部署时最容易忽略的环境配置开发环境里litellm --config config.yaml一把梭就能跑但生产环境会遇到几个隐蔽问题。第一个问题是密钥没传进去。我用 systemd 部署时明明.env里有OPENAI_API_KEY代理却一直报 401。排查半天才发现 systemd 默认不会加载 shell 里的环境变量必须在 service 文件里显式指定EnvironmentFile或者在启动命令前 source。这是生产环境最常见的低级错误。第二个问题是日志。默认日志输出到 stdout系统重启后历史日志就没了。建议至少配一个日志滚动或者让 systemd 的 journal 保留足够长时间方便事后排查。第三个问题是进程守护。直接nohup跑代理一旦服务器重启就断了。我见过同事手里的代理跑了好几天看起来正常其实内存已经涨到了好几个 GB因为没人看护也没有重启策略。正经做法是用 systemd 的Restarton-failure或者直接容器化部署。第四如果你有内网 Docker 环境把代理做成镜像也方便。启动命令还是那一条只是把配置文件和密钥用挂载卷传进去不要在镜像里烤死密钥。4. 成本统计与限流把每个请求的账单记清楚4.1 内置价格表与自定义计费参数多模型项目最容易被忽视的就是成本。刚开始大家只关心效果等模型接多了才发现每个月钱都烧在了哪里根本说不清。LiteLLM 内置了一份主流模型的 token 单价表你调完一个接口它可以按模型名和 token 用量直接估算费用。对内置表里没有的模型你可以自己在配置里补model_list: - model_name: my-local-model litellm_params: model: openai/my-local-model api_base: http://localhost:8000/v1 api_key: dummy model_info: input_cost_per_token: 0.000001 output_cost_per_token: 0.000002input_cost_per_token和output_cost_per_token是美元单价。如果内部模型不计费填 0 也行。这里建议把自定义模型的成本参数全部显式配置出来否则 LiteLLM 不知道该怎么算。4.2 从响应里把 cost 捞出来在 SDK 模式下没有比直接看响应更快的成本统计方法。LiteLLM 提供了一个简便函数from litellm import completion, completion_cost resp completion( modelgpt-4o-mini, messages[{role: user, content: 帮我总结一下这段话}], ) cost completion_cost(completion_responseresp) print(cost)这个函数会读取响应里的 token 用量结合模型单价算出一个浮点数。返回的resp对象里还有一个_hidden_params字段里面经常带着response_cost等原始信息写日志时可以直接兜底消费。代理模式下请求通过网关转发后日志会自动记录每次调用的模型、token 用量和估算成本。你不需要在业务侧写任何埋点只要保留代理日志月底拉出来就是一份粗粒度的账单。4.3 按团队、按应用做预算上限如果只是看账单那还只是事后止损。LiteLLM 代理还支持提前限流你可以给某个 API key 设置单日最大花费或最大请求数超了之后代理直接拒绝调用从源头控制成本。做法是在数据库里维护 key 的预算字段或者在管理后台给每个 key 设置 spending limit。具体字段名不同版本略有差异但核心逻辑一致每个 key 对应一个 budget每次请求成功后记账超出预算返回 403。这个能力对 SaaS 类产品很关键它可以防止某个用户刷爆整个项目的模型额度。我个人的建议是正式上生产之前先把每个 key 的预算上限配置好。哪怕只是设一个相对宽松但存在的上限也比没有强。成本失控这种事情通常不是慢慢涨上来的而是某个循环或者某个测试脚本一次性打出来的。5. 踩坑实录从 401 到流式断连我修复了什么5.1 401 与模型名不对先查配置文件我在跑代理模式第二天就遇到了 401最让人崩溃的是配置看起来都对。后来逐项排查发现litellm_params.api_key用的是os.environ/OPENAI_API_KEY但启动代理的进程里根本没有这个环境变量。也就是说配置声明从环境读但环境里压根没写进去。这个问题的排查链路其实很典型。第一步先确认环境变量是否真的存在终端里echo $OPENAI_API_KEY能打印出来才算有。第二步看代理进程是从哪个目录启动的.env文件是不是在当前工作目录下LiteLLM 是否会读取它。第三步如果上一步不稳定就在启动命令里显式 export 一遍测试环境可以用生产环境再收敛到 systemd 配置。另一个高频报错是模型名不合法。比如直接传llama3而不是ollama/llama3LiteLLM 会默认走 OpenAI 兼容协议去请求结果上游地址不对返回 404 或者模型不存在的错误。这类问题看日志最有效率日志里会显示它最终请求的 URL一眼就能看出前缀是不是配错了。5.2 流式返回拿不到 token 用量流式输出体验确实好但它会带来一个副作用默认情况下流式响应的最后没有 token 用量汇总。没有usage字段成本计算就无从谈起。我当时满心以为completion_cost能直接算出来结果传进去返回 None一度以为模型不计费。后来翻文档发现要在流式请求里显式开启用量返回OpenAI 风格是stream_options{include_usage: True}LiteLLM 对流式请求也可以透传这个参数resp completion( modelgpt-4o-mini, messages..., streamTrue, stream_options{include_usage: True}, )开启后流式最后一个 chunk 会带上 usage 信息。注意解析时要做判空不是每个 chunk 都有usage只有最后一个才有。如果一开始就打算做成本统计我建议所有流式接口都把这个参数打开不然后面补数据非常痛苦。还有一个相关坑有些模型厂商的流式协议根本不放 usageLiteLLM 也无能为力。这种情况下只能用模型输出的 token 数量自行反推或者直接按请求日志估算精度差一些但好过没有。5.3 高并发下的连接池和超时策略代理本身是 Python 进程在单机上做几十路并发没问题但我要同时给多个内部服务推量时出现了两个现象一是延迟变高二是偶尔出现Connection reset by peer。这类问题多半不是 LiteLLM 本身挂了而是上游连接没有被合理复用或者超时设置不合理。处理思路大概分两层。第一层在 LiteLLM 配置里适当调大request_timeout但不要无限大我一般设在 60 秒左右。超过 60 秒还没返回的请求继续等下去价值也不大。第二层确保代理进程有足够的资源不要和其他重型服务挤在同一个容器里。实测下来4 核 8GB 的配置扛住一个小团队的路由转发是够的前提是网络带宽正常。另外如果你的上游是自建 vLLM 或 Ollama 这类本地服务别把它们的地址配错。用ollama/llama3这种前缀时api_base要指到 Ollama 的端口一旦配错代理日志会一直出现连接失败。这里的排查方法就是看日志中的目标 URL 是否正常。5.4 排查链路从 DEBUG 日志到最小复现碰到问题不要瞎猜直接把 LiteLLM 的日志级别拉到 DEBUG重复发一次请求就能看到完整调用链请求体长什么样、转发到了哪个 URL、上游返回了什么、在哪一步报错。LITELLM_LOGDEBUG litellm --config config.yaml --port 4000日志看不到问题时我会把请求体保存下来做一次最小复现。就是绕过代理直接用同一个 messages 和 model 参数通过 SDK 模式请求上游模型。如果 SDK 模式正常而代理模式异常问题大概率在代理配置如果 SDK 模式也异常那就是上游模型或密钥的问题。这个二分法能帮我快速缩小范围。我遇到过一次奇怪的现象同一个请求用 OpenAI 官方 SDK 直接调用没问题但走 LiteLLM 代理就报参数校验错误。后来发现是业务方在请求里带了某个非标准字段官方 SDK 会悄悄忽略它而 LiteLLM 的代理会严格透传导致上游报错。解决方案是在配置里打开drop_params让代理把不认识的参数丢给上游的自己处理litellm_settings: drop_params: true这类兼容性问题没有统一答案只能靠日志和最小复现逐步排除。但万变不离其宗先分清是接入层问题还是上游问题再对症处理。6. 什么项目我推荐 LiteLLM什么项目我会劝退用了大半年我对它的边界也有了更清楚的认识。如果你的项目同时要接入两种以上不同厂商的大模型或者团队里多人、多服务都要调用模型那么 LiteLLM 几乎是为这个场景量身定做的。统一返回结构、重试降级、成本统计、密钥管理这些不是锦上添花而是绕不开的基建。反过来如果项目只使用唯一一家云厂商的模型而且短期内没有换的打算那我觉得没必要引入这一层直接用官方 SDK 更轻。LiteLLM 毕竟多了一层转发任何一个抽象层都不可能做到零损耗能少一层就少一层。还有一类场景我不太推荐你的业务对响应时间极其敏感所有请求都要在 100 毫秒内返回。代理转发会带来一点点额外网络开销在极致性能追求下这个开销可能不可接受。这种情况下直接用官方 SDK 并发调用或者干脆基于更底层的协议自建精简网关也许更合适。最后分享一个我自己的使用习惯在开始写业务代码之前先把模型的接入方式定死不要让业务代码里到处散落着具体的模型名。哪怕经过 LiteLLM 统一之后模型名仍然会出现在请求参数里。最好在业务系统中做一层模型别名映射比如业务侧只传smart、fast、cheap具体映射到哪个模型由配置决定。这样以后换模型、切换厂商就不再是一个代码改动而是一次配置变更。LiteLLM 的价值从来不是让你用某一个模型更简单而是让你在永远不确定要给哪个模型加量的时候不用重写业务代码。这一点等你的项目真的需要接第二个模型时会深有体会。