1. 为什么要在 LiteLLM 里折腾扩展能力LiteLLM 是一个把上百种大模型 API 统一成 OpenAI 格式的代理层能做什么简单说你写一次client.chat.completions.create()背后可以路由到不同厂商的模型适合谁适合手里同时握着好几家模型、又不想在业务代码里写一堆 if-else 的开发者。基础用法大家都会但真正让 LiteLLM 从「能用」变成「好用」的是它的扩展能力自定义回调、路由策略、多模型代理配置。这三块决定了你的系统在真实流量下是稳如老狗还是频繁 429。我最近在做一个多模型网关的小项目需求很具体同一个gpt-4o别名背后挂三个上游按延迟和成本混合路由每次请求结束要把 token 消耗、实际命中的上游、响应耗时写进日志某个上游挂了要自动冷却不能一直往坑里撞。这套需求用 LiteLLM 的 config.yaml 加一个回调脚本就能覆盖但官方文档把功能拆得很散第一次配容易漏参数。这篇就按「本地能跑起来」的标准来写。我会先讲清楚 LiteLLM 扩展能力到底扩展在哪然后给出 TaoToken 统一 Key 的接入方式——它把多家模型的鉴权收敛成一个 API Key 和一个 Base URL省掉你在 config 里维护一堆环境变量的麻烦。接着是可复制的 config.yaml、回调脚本、curl 验证请求最后把几个高频报错逐个拆开。全程命令和配置都能直接抄改掉 Key 就能跑。需要先说明一点LiteLLM 的扩展能力不是靠改源码实现的而是通过配置项和回调钩子暴露出来的。你不需要 fork 它只要在router_settings、litellm_settings和callbacks三个地方下功夫就能把路由、重试、冷却、成本跟踪、日志全部接管。这也是它比裸写 requests 调 API 强的地方——可靠性机制是内建的你只是决定开哪些。2. TaoToken 统一 Key 与 LiteLLM 的前置准备在写 config 之前先把「上游是谁」这件事定下来。传统做法是每个厂商一个 KeyOpenAI 一个、Anthropic 一个、国内模型再一个config 里全是os.environ/XXX_API_KEY本地开发光配环境变量就够烦。TaoToken 的思路是把这些收敛成一套一个 API Key一个 Base URL模型名走它支持的命名。对 LiteLLM 来说这相当于把多个 provider 折叠成一个 OpenAI 兼容端点config 会干净很多。前置准备分三步。第一步拿到统一 Key。访问 https://taotoken.net/api-keys 创建注意这个页面是控制台里的密钥管理入口创建后只显示一次复制存好。第二步确认 Base URL 是https://taotoken.net/api注意这个地址不带任何查询参数LiteLLM 的api_base直接填它。第三步确认你要用的模型 ID可以在 https://taotoken.net/doc 的模型列表里查也可以直接在模型对话页 https://taotoken.net/chat 里试一下模型名能不能通。环境变量建议这样设避免把 Key 写进配置文件export TAOTOKEN_API_KEYsk-你的统一Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用.env文件配合 python-dotenv记得把.env加进.gitignore。我见过有人把 Key 直接写进 config.yaml 然后推到公开仓库几分钟后就被扫号脚本薅走了额度这个坑别踩。安装 LiteLLM 用 pip 就行建议建个虚拟环境python -m venv venv source venv/bin/activate pip install litellm[proxy] fastapi uvicornlitellm[proxy]会带上代理服务需要的依赖fastapi和uvicorn是启动 HTTP 服务用的。装完可以用litellm --version确认。这里有个细节LiteLLM 的 proxy 和 SDK 是两套用法proxy 读 config.yaml 启动服务SDK 是代码里import litellm直接调。本文两条路都会用到——config.yaml 配路由和回调回调脚本里用 SDK 的 CustomLogger 基类。关于统一 Key 的一个常见误解它不是「一个 Key 调所有模型所以更便宜」而是「一个 Key 管所有模型的鉴权和计费入口」。成本还是按各模型实际用量算只是你不需要在 LiteLLM 里为每个 provider 单独配 Key 和 base。对多模型代理场景来说这省下的是配置复杂度和出错概率尤其是当你有十几个模型别名要维护时。3. 可复制的 LiteLLM config.yaml 与回调脚本这一节是核心直接给能跑的配置。先看 config.yaml我把它拆成model_list、router_settings、litellm_settings三块每块都有注释说明为什么这么配。model_list: # 别名 gpt-4o 背后挂两个上游都走 TaoToken 统一通道 - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_base: os.environ/TAOTOKEN_BASE_URL api_key: os.environ/TAOTOKEN_API_KEY rpm: 500 model_info: id: taotoken-gpt4o-primary - model_name: gpt-4o litellm_params: model: openai/gpt-4o-mini api_base: os.environ/TAOTOKEN_BASE_URL api_key: os.environ/TAOTOKEN_API_KEY rpm: 1000 model_info: id: taotoken-gpt4o-fallback # 另一个别名用于验证路由切换 - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-20250514 api_base: os.environ/TAOTOKEN_BASE_URL api_key: os.environ/TAOTOKEN_API_KEY model_info: id: taotoken-claude router_settings: routing_strategy: latency-based-routing routing_strategy_args: ttl: 10 lowest_latency_buffer: 0.5 num_retries: 3 retry_after: 2 allowed_fails: 3 cooldown_time: 30 enable_pre_call_checks: true litellm_settings: callbacks: custom_callbacks.my_handler cache: true cache_params: type: local ttl: 600 set_verbose: false几个关键点解释一下。routing_strategy: latency-based-routing让路由器按最近 10 秒ttl的响应延迟选上游lowest_latency_buffer: 0.5表示延迟差在 0.5 秒以内就随机选避免所有流量都压到同一个「最快」上游。allowed_fails: 3配合cooldown_time: 30是冷却机制某个上游一分钟内失败超过 3 次就冷却 30 秒期间流量走其他上游。enable_pre_call_checks: true会在发请求前检查上下文窗口是否够用避免发出去才报错。callbacks: custom_callbacks.my_handler指向同目录下的custom_callbacks.py文件里的my_handler实例。回调脚本长这样# custom_callbacks.py import litellm from litellm.integrations.custom_logger import CustomLogger class TaoTokenHandler(CustomLogger): def log_success_event(self, kwargs, response_obj, start_time, end_time): litellm_params kwargs.get(litellm_params, {}) model_info kwargs.get(model_info, {}) usage getattr(response_obj, usage, None) cost kwargs.get(response_cost, 0) print( f[SUCCESS] model{kwargs.get(model)} fdeployment{model_info.get(id)} fprompt_tokens{getattr(usage, prompt_tokens, 0)} fcompletion_tokens{getattr(usage, completion_tokens, 0)} fcost{cost:.6f} flatency{(end_time - start_time).total_seconds():.3f}s ) def log_failure_event(self, kwargs, response_obj, start_time, end_time): litellm_params kwargs.get(litellm_params, {}) print( f[FAILURE] model{kwargs.get(model)} fapi_base{litellm_params.get(api_base)} ferror{kwargs.get(exception)} ) my_handler TaoTokenHandler()这个回调做了三件事成功时打印命中的部署 ID、token 用量、成本和延迟失败时打印 api_base 和异常。model_info.id就是 config 里给每个上游起的名字日志里能直接看出流量打到了哪个上游排查路由问题时非常有用。注意response_cost依赖 LiteLLM 内置的价格表如果模型不在表里会返回 0这时候可以在model_info里加input_cost_per_token和output_cost_per_token手动指定。启动代理服务litellm --config config.yaml --port 4000看到Uvicorn running on http://0.0.0.0:4000就说明起来了。如果你用 Claude Code 或 Cline 这类工具它们的 Base URL 填http://localhost:4000Key 随便填本地代理默认不校验Model ID 填 config 里的model_name比如gpt-4o。这三件套——Base URL、Key、Model ID——是任何客户端接入的通用公式缺一不可。4. 验证请求与成功结果确认配置写完必须验证不然你不知道路由和回调到底有没有生效。先发一个最简单的 curlcurl -s http://localhost:4000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-anything \ -d { model: gpt-4o, messages: [{role: user, content: 用一句话解释什么是路由策略}], temperature: 0.7 }正常返回是一个标准 OpenAI 格式的 JSONchoices[0].message.content里有模型回复。同时你的终端跑 litellm 的那个应该打印出[SUCCESS]那行日志里面能看到deploymenttaotoken-gpt4o-primary或taotoken-gpt4o-fallback说明路由在工作。想验证延迟路由是否真的在切换可以连续发 10 次请求观察日志里 deployment 的分布。如果两个上游延迟接近应该会看到交替出现如果某个上游明显慢流量会偏向快的那个。这里有个实测经验lowest_latency_buffer设太小比如 0.1会导致流量几乎全压到最快上游失去负载均衡意义设太大比如 2.0又退化成随机。0.5 到 1.0 之间比较平衡。验证冷却机制稍微麻烦一点需要人为制造失败。可以把 config 里某个上游的api_base改成一个不存在的地址然后连续发请求观察日志前 3 次是[FAILURE]之后该上游进入冷却请求全部走另一个上游30 秒后恢复。这个测试能帮你确认allowed_fails和cooldown_time是否按预期工作。验证回调里的成本跟踪发一个稍长的请求看日志里的cost是否非零。如果一直是 0检查模型名是否在 LiteLLM 的价格表里。对于走 TaoToken 统一通道的模型如果价格表没收录就在model_info里补上单价model_info: id: taotoken-gpt4o-primary input_cost_per_token: 0.0000025 output_cost_per_token: 0.00001验证缓存是否生效同一个请求发两次第二次的日志里延迟应该明显更低或者 LiteLLM 会打印 cache hit 相关信息。cache_params.ttl: 600表示缓存 10 分钟。注意缓存对temperature 0的请求默认也会命中如果你需要每次都不一样得在请求里加cache: {no-cache: true}。最后验证多模型别名把 curl 里的model换成claude-sonnet应该能正常返回日志里 deployment 变成taotoken-claude。这一步确认了统一 Key 通道对不同 provider 的兼容性——同一个 Base URL 和 Key既能调 OpenAI 系也能调 Anthropic 系config 里只是model字段前缀不同。5. 本篇常见报错排查配 LiteLLM 扩展能力时报错基本集中在几个地方。我按真实遇到的频率排一下。401 Authentication Error。日志里出现AuthenticationError: litellm.AuthenticationError: OpenAIException - Incorrect API key provided。原因通常是TAOTOKEN_API_KEY没导出或者导出后没重启 litellm 进程。环境变量是在进程启动时读取的你 export 完必须重启。另一个可能是 Key 复制时带了空格或换行用echo $TAOTOKEN_API_KEY | wc -c确认长度。还有一种情况是 config 里写成了api_key: os.environ/TAOTOKEN_API_KEY但实际变量名拼错LiteLLM 不会报「变量不存在」而是把字面量os.environ/TAOTOKEN_API_KEY当 Key 发出去结果就是 401。local proxy failed / Connection refused。客户端报litellm.APIConnectionError: local proxy failed或Connection refused。先确认 litellm 进程还在跑curl http://localhost:4000/health看是否返回 200。如果进程在但连不上检查端口是否被占用换--port 4001试试。如果是 Docker 里跑 litellm、宿主机访问注意--host 0.0.0.0要加上默认只监听 127.0.0.1。reading choices / KeyError choices。报错Error reading choices或KeyError: choices通常发生在回调脚本里。原因是response_obj的结构和你预期的不一样——流式响应streamTrue时response_obj是生成器没有choices属性。回调里要判断如果是流式得用log_success_event之外的方式处理或者在 config 里对特定模型关掉流式回调。另一个可能是上游返回了非标准格式比如某些兼容端点在出错时返回{error: ...}而不是标准结构这时候先看log_failure_event里的原始异常。OAuth / token 相关报错。如果你用 Claude Code 或 Codex 这类工具接入可能遇到OAuth token expired或auth.json相关错误。这类工具自己有鉴权层接入 LiteLLM 代理时要把它们的 Base URL 指向http://localhost:4000并且关掉它们自己的 OAuth 流程。以 Codex 为例~/.codex/auth.json里的OPENAI_BASE_URL要改成代理地址OPENAI_API_KEY填任意非空值。Claude Code 则在 settings 里配ANTHROPIC_BASE_URL指向代理。改完记得重启工具进程配置不会热加载。回调没触发。config 里写了callbacks: custom_callbacks.my_handler但日志里没有[SUCCESS]。三个检查点一是custom_callbacks.py是否在 litellm 启动的工作目录下Python 导入路径不对会静默失败二是my_handler是否是实例而不是类写my_handler TaoTokenHandler()而不是my_handler TaoTokenHandler三是litellm_settings的缩进是否正确YAML 对缩进敏感callbacks必须在litellm_settings下一级。路由不切换。配了latency-based-routing但流量总是打到一个上游。先确认两个上游的model_name完全一致都是gpt-4o不一致的话它们属于不同模型组不会互相路由。再确认routing_strategy_args.ttl不是太大ttl 太大意味着延迟统计窗口太长切换不灵敏。最后看日志里两个上游是否都成功过如果其中一个一直失败被冷却自然全走另一个。6. 把扩展能力用进日常开发流配好这套之后日常开发里最实用的几个场景可以串起来。本地调试时把 LiteLLM 代理挂在 4000 端口所有客户端——不管是 Python 脚本、Claude Code、还是 Cline——统一指向它日志集中在一个终端里看哪个模型被调了、花了多少 token、延迟多少一目了然。这比每个工具单独配 Key、单独看日志高效得多。长期跑编码任务或 Agent 的话可以考虑 Coding Plan 这类按周期计费的方式配合 LiteLLM 的成本跟踪回调能清楚看到每个任务的模型消耗分布。回调里把response_cost累加写进数据库跑一周就能知道钱花在哪个模型、哪个上游上优化路由策略时有数据支撑。需要提醒的是LiteLLM 的扩展能力虽然强但别一次全开。我建议的开启顺序是先只配model_list和基础路由跑通再加回调看日志确认日志正常后再开冷却和重试最后加缓存。每加一层都验证一次出问题容易定位。一次性把 config 写满某个参数写错导致服务起不来排查成本会高很多。最后留一个实用技巧config.yaml 里的model_info.id命名要有规律比如taotoken-{provider}-{model}-{序号}这样日志里一眼能看出流量走向。回调脚本里可以把日志同时写文件用logging模块按天切分方便回溯。这些细节不复杂但能让你在真正出问题时少花半小时找原因。