做AI应用开发这两年我最大的痛感不是模型能力不够而是模型API的碎片化。今天用OpenAI明天想换Claude后天客户又要求接国产模型每个厂商的请求格式、返回结构、认证方式、计费口径全都不一样光是写适配层就能写吐。后来我在某个开源项目仓库里翻到了litellm花了一个晚上把它接进我们正在做的某个跨平台系统才发现这个工具基本解决了我的所有痛点。简单说litellm是一个Python库加代理服务器它把你的代码和所有主流大模型API隔离开来你想调哪个模型改一个字符串就行其他全是标准格式。这篇文章写给谁给那些正在做LLM应用开发、需要在多个模型API之间来回切换、或者在团队里搭模型网关的开发者。我会从为什么需要它、核心用法、怎么搭一个生产可用的代理服务、到常见问题排查全部用自己的实操经历来讲尽量不给官方文档复读一遍。1. 为什么需要litellm模型API碎片化的解法1.1 各家模型API的格式到底有多割裂先说个最直观的感受。OpenAI的Chat Completions接口长这样你发一个messages数组每个元素带role和content然后用max_tokens控制回复长度返回里有个choices[0].message.content。Anthropic的Messages接口则是system单独拆出来messages里只能是user和assistant长度参数叫max_tokens没错但返回结构完全换了一套里面是content数组每个元素可能是文本还可能是工具调用块。Google的Gemini接口又是另一套参数叫generationConfig里面是maxOutputTokens返回结构又是完全不同的candidates格式。这些差异看起来都不是什么高深技术但真做起来很磨人。我一开始接了两个模型在业务代码里写了一大堆if provider openai这种分支逻辑每个分支处理一套请求构造、一套响应解析、一套异常捕获。后面要接第三个模型的时候我意识到这条路走不通了——代码里全是分支测试用例翻倍出错概率指数上升而且换一个模型就要重新联调一遍一个简单的需求能拖上两三天。1.2 LiteLLM的核心设计思路litellm的解法很直接你统一用OpenAI兼容的格式发请求它在内部帮你翻译成各家API原生格式再把返回结果标准化成OpenAI的格式还给你。换句话说它就是把“多对多”的适配问题变成了“多对一对多”的网关问题。这个项目有两种形态对应不同的使用场景。第一种是Python SDK适合在你自己的代码里直接调用litellm.completion()一行搞定。第二种是代理服务器Proxy你把它跑起来以后它是一个本地或云端HTTP服务你的业务代码只需要把base_url指到它就完全不用关心背后接的是哪家模型了。我在实际项目里两种都用过。自己写脚本做测试对比实验的时候用SDK形式快团队里多个人、多个服务共用一套模型配置的时候用代理形式统一管理密钥和模型路由省心很多。说实话我更喜欢Proxy这个形态因为它不仅仅是适配层其实还带了一堆生产环境需要的东西——负载均衡、重试、fallback、限流、成本追踪、虚拟密钥管理这些后面我会详细讲。1.3 和LangChain这类框架的本质区别经常有人把litellm和LangChain放在一起比较我用了之后觉得这两者根本不是一层的东西。LangChain是一个应用编排框架它管的是链式调用、记忆、Agent、工具调用这些高层的应用逻辑它的模型调用底层其实也有自己的封装但你可以把litellm嵌进去反过来让它处理模型接入。litellm的定位很专注就管模型API请求这一层统一的请求格式、响应标准化、路由、重试、成本统计。它不是要帮你搭一个Agent应用它是让你的所有模型请求有一条统一的路。打个比方LangChain像是一个装修公司帮你把整个房子装好litellm更像水电改造、统一管线房子怎么布局是你自己的事但水电线路必须通。我实际用下来的体感是litellm把模型接入这件事彻底变成了配置项业务代码完全不需要关心某个模型是哪个厂商的、参数叫什么、响应长什么样这对团队协作和生产维护来说价值比想象的大。2. 核心细节解析与实操要点从安装到跑通第一个请求2.1 安装和最小配置安装没什么好说的pip install litellm装完之后建议看一眼版本确认装好没有python -c import litellm; print(litellm.__version__)然后就是配置密钥环境变量。litellm默认会去读环境变量里对应的key名称比如OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY、AZURE_API_KEY等等。你调用某个provider的模型时它会自动去读对应的环境变量。我第一次用的时候没注意这个细节在代码里显式传了api_key参数后来发现其实设好环境变量后完全可以不传统一走环境变量管理安全得多。建议把密钥都放在.env文件里通过load_dotenv()加载别硬编码在代码或配置里。顺手提一下托管密钥服务。我们团队后来直接用了一套内部的密钥管理系统litellm支持引用外部密钥管理器的变量名这样仓库里就永远不会出现真实密钥了安全性上会好很多。2.2 统一接口的调用方法核心方法就一个litellm.completion()。直接上示例import litellm response litellm.completion( modelanthropic/claude-3-5-sonnet-20241022, messages[ {role: system, content: 你是一个擅长写技术文档的助手。}, {role: user, content: 帮我写一段关于消息队列的简介。} ], temperature0.7, ) print(response.choices[0].message.content)注意model参数里的写法这里有关键细节模型名字前面带了一个provider前缀格式是provider/模型名。anthropic/claude-3-5-sonnet-20241022表示让litellm把请求翻译成Anthropic格式发给Claude如果写openai/gpt-4o它就按OpenAI格式发写bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0它就走AWS Bedrock的路由。前缀就是告诉litellm这个请求应该翻译成哪家API的方言。返回的响应对象也是标准化的和OpenAI的返回结构保持一致。response.choices、response.usage这些都能直接用。你换模型其实换的就是model字符串后面的代码一行都不用动。我自己曾经写了一个对比实验脚本循环调了七八个不同厂商的模型同一个messages列表同一个temperature只是model字符串不同跑出来的数据直接列表对比方便得很这用传统方式简直不敢想。2.3 成本追踪和Token统计模型API的计费口径每家不一样OpenAI按输入输出token分开计价Claude也是按token但价格不同Gemini可能按字符或者图像数量算。手动算成本是一件非常烦的事情尤其当你调了很多模型、很多轮对话之后。litellm在响应里直接带了用量和成本信息。比如调用完成后print(response.usage) # prompt_tokens, completion_tokens, total_tokens print(response._hidden_params.get(response_cost)) # 本次请求的估算成本response._hidden_params[response_cost]里面是litellm根据模型单价计算出的本次调用成本单位是美元。这个字段不是标准字段但实际很好用。它们还提供了litellm.cost_per_token()这类接口可以自己按模型名、token数估算成本适合你在请求发出前就算一下预算。我做模型选型的时候经常用这个功能。把几个候选模型各跑10次同样的任务收集每次的cost和延迟算平均值最后选的那个模型多半不是能力最强的而是“能力不错而且便宜稳定”的。没有成本数据支撑时只能拍脑袋有了数据之后拍脑袋的次数少多了。2.4 超时、重试和fallback配置模型API的稳定性各家的服务都有波动生产环境如果只连一个模型遇到服务抖动就是事故。litellm在重试和fallback上做得很完善。最简单的配置方式是在completion()里直接传参数response litellm.completion( modelopenai/gpt-4o, messagesmessages, num_retries3, timeout30, fallbacks[ {model: anthropic/claude-3-5-sonnet-20241022}, {model: openai/gpt-4o-mini} ] )num_retries表示对同一个模型最多重试几次timeout是单个请求的超时秒数fallbacks是当主模型失败之后依次尝试的备用模型列表。注意fallbacks里的模型如果配置了api_key也可以用环境变量不用显式写。我在生产里配置过一个这样的客户端主模型是Claude如果因为高峰期限流报错就自动降级到GPT-4o再不行降到GPT-4o-mini。用户那边体感就是某个时刻回复慢了一两秒但基本不会看到请求失败。如果你手动去写所有异常情况的处理这套逻辑少说几百行而且很难测全。有同学可能会问重试会不会重复扣费会但litellm默认会捕获某些特定的限流、网络类异常才重试不是无脑重试。你还可以自己定义哪些异常码需要重试。总体来说默认行为比我之前自己写的粗粒度重试要合理得多。3. 实操过程与核心环节实现搭建一个模型网关服务3.1 两步启动一个单模型网关litellm的代理服务形态我第一次用的时候也吃了一惊启动一个能跑的HTTP服务只需要一条命令litellm --model anthropic/claude-3-5-sonnet-20241022 --port 4000这样就在本地的4000端口起了一个兼容OpenAI接口的HTTP服务。你在任何语言里把请求发到http://localhost:4000/v1/chat/completions用OpenAI的请求格式传messages它就会帮你转成Claude的格式发出去再返回OpenAI格式的响应。这里有个重要细节代理本身默认要求一个自定义的Authorization: Bearer密钥。你启动时如果不设置它会打印一个临时密钥出来让你在调它的时候带上。这个设计是为了确保你的代理服务不会裸奔在网上被任何人调用。我后来实际验证过用Python的openai库指向这个本地代理完全可行from openai import OpenAI client OpenAI( api_keysk-1234, # 代理服务设置的key base_urlhttp://localhost:4000/v1 ) resp client.chat.completions.create( modelanthropic/claude-3-5-sonnet-20241022, messages[{role: user, content: 你好}] ) print(resp.choices[0].message.content)是的你没看错用OpenAI官方SDK把base_url改成litellm的地址model还是写anthropic/...它就能调Claude。这带来的一个好处是团队里如果已经有一部分服务是用OpenAI SDK写的接litellm几乎零改造成本——只改个base_url和一个key就完事了。3.2 用config.yaml配置多模型和负载均衡单模型网关只能算入门真正好用的是通过配置文件管理多模型。在项目根目录建一个config.yamlmodel_list: - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY router_settings: routing_strategy: round-robin这里model_name是你自己对外的别名你可以给上层业务暴露“claude-sonnet”这个名字实际底层接谁你自己说了算。以后你想把Claude Sonnet换成别的型号只要改config.yaml业务代码字符串完全不用变。这在实际维护里的价值太大了——你让业务团队改字符串还要重新发版但你改配置文件只需要重启服务。启动的时候加载配置litellm --config config.yaml --port 4000然后调用的时候model参数写你起的别名就行curl http://localhost:4000/v1/chat/completions \ -H Authorization: Bearer sk-1234 \ -H Content-Type: application/json \ -d {model: claude-sonnet, messages: [{role: user, content: 你好}]}如果配了router_settings.routing_strategy: round-robin多个模型之间会轮询分发请求。litellm支持的策略还有simple-shuffle、least-busy、usage-based-routing等。我把两个模型配成轮询再跑了个小压测脚本请求会在两个模型之间交替分发负载压力分散得很均匀。对着多个模型做负载均衡这对生产环境的稳定性和成本控制都是很有意义的。你可以把便宜模型和贵模型搭配起来把一部分不那么关键的流量切到便宜模型上省下来的钱相当可观。3.3 虚拟密钥和团队权限管理多个人用同一个代理服务如果都拿同一个root密钥审计就无从谈起了。litellm Proxy提供了一套密钥管理系统你可以生成多个虚拟密钥给每个团队成员或每个业务服务分配一个。启动代理后可以用管理接口生成新密钥或者用liteLLM的命令行工具操作。给A同学分发一个key给B服务分发另一个key每个key可以设置速率限制、预算上限、绑定的模型范围。这样某个key超限了你一眼就知道是谁的业务在疯狂调API而且可以对那个key单独限流不会拖垮整个网关。我们团队之前出现过某个测试服务把密钥写死在代码里循环调了一个周末花了一笔冤枉钱。上了虚拟密钥之后这种情况基本断了根——每个key都有独立的预算上限超了自动拒绝。3.4 生产部署建议容器化与依赖补全本地跑通代理只是第一步真正上线部署我强烈建议走容器化。litellm官方有Docker镜像你只需要写一个简单的docker-compose.ymlversion: 3 services: litellm: image: ghcr.io/berriai/litellm:main-latest ports: - 4000:4000 volumes: - ./config.yaml:/app/config.yaml environment: - ANTHROPIC_API_KEY${ANTHROPIC_API_KEY} - OPENAI_API_KEY${OPENAI_API_KEY} command: [--config, /app/config.yaml]注意几个生产细节。第一API密钥不要硬编码在config里用os.environ/ANTHROPIC_API_KEY这种方式引用环境变量密钥统一走部署平台的环境变量注入。第二如果你的代理要面向多个实例或做横向扩展建议把Redis接上litellm支持用Redis做集中式的速率限制和缓存。我当时没配Redis本地区别不大但一旦起了多个代理实例限流数据就不共享了每个实例各限各的效果会打折扣。第三日志要管好litellm会记录每次请求的完整元数据配合日志采集系统能做出很漂亮的API调用报表。部署后务必测一下存活接口。litellm带了一个/health/liveliness检查端点你的容器编排平台可以直接拿它做健康检查省得流量打到一台挂掉的服务上。4. 常见问题与排查技巧实录4.1 模型名前缀写错请求一直报400我刚开始用的时候把anthropic/claude-3-5-sonnet-20241022写成了claude-3-5-sonnet-20241022少写了anthropic/前缀结果请求一直报错看日志又看不出所以然因为错误信息只是说模型不存在。后来我仔细看了文档才发现如果不带前缀litellm默认按OpenAI的模型名处理直接拿claude-3-5-sonnet-20241022去OpenAI的接口找那肯定找不到。这个排查经验是先在litellm的调试日志里看它到底把请求发给了谁。设置litellm.set_verboseTrue请求的完整链路会打出来包括它实际请求的URL、请求体长什么样。看到请求体里的URL你就能一眼判断是不是前缀问题。4.2 环境变量不生效各种认证失败第二种常见问题是配了ANTHROPIC_API_KEY但代理启动后一直报401。排查思路很简单先确认你环境变量是不是真的被读到了。在代码里import litellm; print(litellm.api_key)看能不能打印出值来。如果打印出来是None大概率是环境变量没加载进去比如你改了.env文件但忘记load_dotenv()或者shell环境是旧的。还有一次我把ANTHROPIC_API_KEY误填成了OPENAI_API_KEY调Claude的时候由于litellm找不到Anthropic的key就直接用了OpenAI那个变量去请求结果那边返回401。打开verbose日志后才发现请求URL是Anthropic的但是Authorization请求头里带的密钥完全不对。所以碰到认证问题第一步是看日志里实际发出的请求头而不是凭感觉猜。4.3 有些模型参数不兼容报奇怪的参数错误各家模型支持的参数不完全一样。比如temperatureOpenAI支持0到2Claude支持0到1Gemini有些版本只支持0到1。你在litellm里统一传了temperature1.5OpenAI那边没问题转到Claude那边可能就直接报参数越界的错。对付这个litellm提供了drop_paramsTrue选项遇到目标provider不支持的参数就自动剔除而不是硬塞进去导致报错。我一般建议在自己完全掌控参数的时候不要贸然开这个因为有些参数被丢掉会影响生成质量但如果你只是快速验证一个模型开它省心很多。另外一个思路是在config.yaml里针对不同模型单独覆盖参数。比如某个模型比较脆弱你单独给它配一个更温顺的参数集其他模型走默认。4.4 Token统计和成本数字看着不对有些模型的usage信息是litellm从响应里解析出来的如果provider返回的信息不完整usage字段可能缺失成本计算自然就不准。你可以在config.yaml里为某个模型手动设置max_input_tokens和max_output_tokens但更靠谱的做法是拿一次真实请求手动记录prompt和completion的token数和litellm统计的对比一下。如果总是不准就得考虑是否需要切换到按字符计费的方案或者直接以provider官方账单为准。成本估算永远只能当作参考值尤其在prompt里塞了很多图片的时候token口径会更不统一。我自己的习惯是如果要做成本对比尽量在同一天、同一批样本下跑模型跑完立刻记录cost别隔几天再比对。4.5 排查技巧速查表现象可能原因排查思路400 Bad Request模型名前缀丢失或写错打开verbose日志确认实际请求URL401 Unauthorized环境变量没加载或配错key打印litellm.api_key检查请求头Authorization404 Model Not Foundprovider模型名不存在或已下线对照官方最新模型列表核对名字429 Rate Limit触发限流或额度不足检查代理虚拟key的限流配置看provider控制台参数报错某model不支持该参数开启drop_params或为该模型单独覆盖参数usage缺失provider响应不完整手动解析原始响应必要时写命令行工具核对我发现大多数人卡住都是在“litellm配置永远不回显错误真正原因”这个点上。所以我的建议一直是遇到任何报错第一步永远是litellm.set_verbose(True)先把内部请求链路看清楚再谈别的。5. 进阶经验从个人工具变成团队基础设施5.1 统一模型入口之后带来的协作变化我们的项目刚开始用litellm只是我一个人在脚本里调用。后来部署了网关所有服务统一走代理业务、算法、测试同学写代码都不需要再关心模型API的细节了。他们只要按OpenAI格式请求网关model参数传别名其他的全由网关处理。这个转变带来的最大好处是知识隔离。以前每个同学都要搞懂OpenAI、Anthropic的API差异出错的时候各查各的现在这些细节收敛到配置和网关层由基础设施团队统一维护。业务代码里几乎看不到直接拼请求体的逻辑了全是标准化的client调用代码可读性也突然好了很多。5.2 用回调做请求日志和审计litellm支持设置回调函数每次请求完成后你都可以拿到完整的请求和响应数据。我们把回调接进了一个内部的消息队列把每次调用的模型、耗时、token数、成本、用户标识全部记录下来沉淀成数据报表。这个报表对技术决策特别有用。比如评估“要不要把默认模型从A换成B”的时候我们直接把报表导出来看过去一周A模型的请求量、成本分布、平均耗时再换B跑一周小流量对比谁好谁坏一目了然。没有这套数据的时候我们做过不少凭感觉做的模型选型决策有些事后证明并不划算。5.3 后续还能怎么扩展litellm后续能做的事还很多。比如自定义prompt模板、批处理请求优化、根据成本预算自动路由、多级缓存等。我目前最想做的是把网关和团队的A/B测试体系接起来——同一个提示词的不同模型回答随机分发给用户然后回收用户反馈形成一个持续优化的闭环。听起来工作量不小但litellm已经把底层最麻烦的部分都处理好了剩下的是纯业务逻辑这已经是很大的优势。最后分享一个小技巧。我平时在本地写一次性脚本时喜欢用litellm写一个十几行的model_switch.py接收模型名和prompt参数直接跑出结果import sys import litellm model, prompt sys.argv[1], sys.argv[2] response litellm.completion(modelmodel, messages[{role: user, content: prompt}]) print(response.choices[0].message.content)这个脚本我用了很久换模型真的就是改一个字符串的事。如果你也在为模型API切换头疼不妨也把这条路子试起来。