1. 项目概述:LiteLLM 是什么,它解决的到底是什么问题?
LiteLLM 这个名字第一次出现在我视野里,是在某次给某高校实验室做模型服务集成支持时。当时团队正在用 OpenAI 的 API 做一个轻量级教育问答助手,但突然收到通知:API 调用费用单月翻了三倍,而实际请求量只增加了不到 15%。一查日志才发现,大量请求被错误地发到了 gpt-4-turbo 模型上——不是因为业务逻辑写错了,而是因为不同环境(开发/测试/生产)配置了不同模型名,而 OpenAI 官方 SDK 对模型名校验极松,gpt-4、gpt-4-turbo、gpt-4-0125-preview在代码里只是字符串,拼错一个字符,就可能触发更贵的模型计费。更麻烦的是,他们还想接入国内某大厂的千问 API 和某开源模型的 Ollama 本地服务,结果发现每个平台的请求格式、参数命名、流式响应结构、错误码定义、重试逻辑全都不一样:OpenAI 用messages字段,千问用input;Ollama 返回response字段,而 Anthropic 的 Claude 则是content数组;重试时 OpenAI 推荐指数退避,千问要求固定间隔,Ollama 根本不返回 HTTP 429……光是写一个能“同时调通三家”的统一封装层,就花了两个工程师整整五天,还漏掉了超时熔断和 token 统计的精度问题。
这就是 LiteLLM 真正要解决的问题——它不是另一个大模型,也不是一个训练框架,而是一个面向生产环境的 LLM 协议抽象层。你可以把它理解成数据库领域的 JDBC:你不用为 MySQL 写一套 SQL 执行器,为 PostgreSQL 再写一套,为 TiDB 又写第三套;LiteLLM 就是 LLM 世界的“统一驱动”,它把 OpenAI、Anthropic、Google Vertex AI、Azure OpenAI、Ollama、LM Studio、Cohere、Groq、Fireworks、Together AI、DeepInfra、Perplexity、Replicate、NovelAI、以及国内多家主流大模型平台(包括适配国产芯片推理后端的定制版本)的千差万别的 API 行为,全部收敛到一套极简、稳定、可预测的接口上。核心就三个动作:completion()、chat_completion()、embedding(),输入永远是标准 OpenAI 格式,输出也永远是标准 OpenAI 格式。中间所有协议转换、字段映射、重试策略、token 计算、流式解析、错误归一化,全由 LiteLLM 在内存里完成。它不碰模型权重,不参与推理,不做任何缓存,就是一个纯粹的“翻译官”+“交通协管员”。
对一线开发者来说,它的价值不是“多了一个工具”,而是直接改写了工程实践的成本结构。以前加一个新模型支持,意味着:读文档 → 写适配器 → 测兼容性 → 改重试逻辑 → 更新监控埋点 → 同步更新所有下游服务。现在,只要一行命令pip install litellm,再在配置里加一行--model qwen/qwen2-72b-instruct,整个系统就自动识别这是千问模型,并启用对应的 endpoint、headers、payload 结构和响应解析器。我实测过,在已有基于 OpenAI SDK 的 3000 行业务代码基础上,仅替换 import 和初始化方式,15 分钟内就完成了向本地 Ollama + 远程千问双后端的平滑迁移,零修改业务逻辑。它真正释放的,是工程师在“对接模型”这件事上的认知带宽——让你能把注意力从“怎么让这个 API 不报错”转移到“怎么让这个回答更准、更安全、更符合业务规则”上。适合谁?所有正在用 API 调用大模型、正在构建 RAG 系统、正在做 Agent 编排、正在搭建企业级 AI 中台,但又被各家模型平台碎片化协议折磨得夜不能寐的开发者、架构师和 MLOps 工程师。
2. 核心设计思路与方案选型逻辑:为什么是 LiteLLM,而不是自己造轮子或选其他方案?
2.1 为什么不是手写适配器?——成本与风险的硬账
很多人第一反应是:“不就是字段映射吗?我自己写个 if-else 不就完了?” 我完全理解这种想法,三年前我也这么干过。当时给一个金融风控项目接入三家模型,写了 200 行“模型路由表”,看起来很清爽:
if model == "gpt-4": return openai_client.chat.completions.create(...) elif model == "claude-3-opus": return anthropic_client.messages.create(...) elif model == "qwen2-72b": return qwen_client.chat.completions.create(...)但上线两周后,问题开始爆发:
- OpenAI 发布了
gpt-4o-mini,我们的路由表没覆盖,所有请求 fallback 到默认模型,导致客户投诉回答质量断崖下跌; - Anthropic 更新了
messages字段的嵌套结构,旧解析器把content[0].text当成必填,而新版本允许content[0].type == "tool_use",结果整个服务 crash; - 千问平台悄悄升级了鉴权方式,从
Authorization: Bearer xxx改成X-DashScope-Signature: xxx,我们没收到通知,所有请求 401,持续 47 分钟才被监控告警拉起来。
这背后是三个无法回避的现实:
- 协议漂移不可控:大模型平台的 API 是高频迭代的,平均每月至少一次 breaking change,且文档更新往往滞后于线上变更;
- 错误语义不统一:OpenAI 的
rate_limit_exceeded、千问的Throttling.RateQuotaExceeded、Ollama 的context cancelled,本质都是限流,但你要在业务层做统一降级,就得维护一张 N×M 的错误码映射表; - 非功能需求被严重低估:重试不是简单
time.sleep(1),要考虑指数退避、jitter 防抖、熔断阈值、失败原因分类(是网络超时?还是模型内部 error?);token 统计不能只靠粗略估算,RAG 场景下 prompt + context + system message 的 token 数必须精确到个位,否则会频繁触发 truncation 导致信息丢失。
LiteLLM 的核心设计哲学,就是把上述所有“非核心但致命”的复杂性,全部下沉为基础设施能力。它不是提供一个“能用”的封装,而是提供一个“敢在生产环境长期依赖”的协议层。它的源码里有超过 80 个独立的 adapter 模块,每个都经过真实流量验证;有完整的错误码归一化表(litellm/utils.py里的get_litellm_status_code);有工业级的重试策略(支持max_retries、fallbacks、context_window_fallbacks);有基于 tiktoken 和 sentencepiece 的双引擎 token 计算器,甚至能处理gpt-4o这种混合文本/图像输入的特殊 case。你自己写,短期省事;长期看,是把技术债打包卖给了未来。
2.2 为什么不是 LangChain / LlamaIndex 的内置适配?——定位差异决定适用边界
LangChain 和 LlamaIndex 确实也提供了多模型支持,比如ChatOpenAI、ChatAnthropic、QwenChat等封装类。但它们的设计目标完全不同:LangChain 是一个应用编排框架,重心在 chain、agent、retriever 的组合逻辑;LlamaIndex 是一个RAG 构建框架,重心在数据索引、查询优化、响应合成。它们的模型封装,本质上是“为了跑通 demo 而存在的最小可行适配”,深度和健壮性远不及 LiteLLM。
举个典型例子:流式响应(streaming)。LangChain 的stream=True选项,底层其实是把 LiteLLM 的流式 generator 包了一层StreamingStdOutCallbackHandler,但它不处理流式过程中的中断恢复、partial message 拼接、event source format 解析等细节。而 LiteLLM 的stream=True是原生支持的,它会自动识别不同平台的流式格式:OpenAI 是data: {"choices": [...]},千问是{"output": {"text": "..."}, "usage": {...}},Ollama 是{"response": "...", "done": false},并统一转换成标准的ChatCompletionChunk对象,连delta.content、delta.role这些字段都严格对齐。这意味着,如果你的前端需要实时渲染思考过程(比如显示 “正在分析文档…” → “已提取关键条款…” → “生成最终结论…”),用 LangChain 你得自己 parse 原始流数据;用 LiteLLM,你直接for chunk in response:就行,字段语义完全一致。
再比如模型路由(model routing)。LangChain 没有内置的动态路由能力,你得自己写if-else或用MultiModelRouter这种实验性组件。而 LiteLLM 提供了开箱即用的ModelRouter,支持基于 latency、success rate、cost、token usage 的智能 fallback,还能设置num_retries_per_model=3这种细粒度策略。我在某电商客服项目中就用它实现了“主用千问(成本低),当千问 P95 延迟 > 1.2s 时自动切到本地 Qwen2-7B(延迟稳在 400ms),若本地也超时则 fallback 到 OpenAI(保底质量)”,整个切换对上层业务无感。这种级别的生产就绪能力,是应用框架无法也不该承担的。
2.3 为什么不是自研网关(如 Kong + 自定义插件)?——工程效率与演进速度的博弈
有些团队会倾向用 API 网关(如 Kong、Apigee)做统一入口,然后写 Lua 或 Go 插件做协议转换。这在理论上可行,但实践中会遇到三个硬伤:
- 调试成本爆炸:网关插件运行在独立进程,日志分散,链路追踪需额外埋点,一次
400 Bad Request错误,你要在网关日志、上游服务日志、模型平台日志里来回跳转,平均排查时间 > 45 分钟; - 演进速度跟不上:LiteLLM 平均每周发布 2-3 个 patch 版本,修复新模型兼容性问题(比如上周刚支持了 Groq 的 Llama3-70B,今天就发布了对 DeepSeek-V2 的 beta 支持)。而网关插件的发布流程涉及 DevOps 审批、灰度验证、回滚预案,一个 patch 从开发到上线常需 3-5 天;
- 功能边界模糊:网关擅长流量控制、认证鉴权、限流熔断,但不擅长语义解析。比如你要实现 “当 prompt 中出现‘合同’关键词时,强制添加 system message ‘请以专业律师身份回答’”,这种业务规则放在网关里,就违背了关注点分离原则,会让网关变成一个难以维护的“上帝组件”。
LiteLLM 的定位非常清晰:它是一个库(library),不是服务(service)。它运行在你的应用进程内,和业务代码共享内存、日志、trace context。你可以在completion()调用前后,无缝插入自己的 pre-hook(如敏感词过滤、prompt 注入)和 post-hook(如答案合规性校验、耗时打点)。这种紧耦合带来的,是极致的可控性和调试效率。我见过最夸张的案例:某团队用 LiteLLM + 自定义 callback,在 200 行代码内实现了“自动识别用户提问是否涉及医疗建议,若是,则拦截并返回预设免责声明”,整个链路毫秒级响应,且所有逻辑都在一个代码仓库里,新人三天就能上手维护。
3. 核心功能拆解与实操要点:从零开始构建一个高可用模型路由系统
3.1 最小可行启动:三行代码跑通 OpenAI 兼容模式
LiteLLM 的入门门槛低到令人惊讶。假设你已经有一个基于 OpenAI Python SDK 的老项目,只需三步改造:
第一步:安装与初始化
pip install litellm第二步:替换客户端初始化
# ❌ 旧代码(OpenAI SDK) from openai import OpenAI client = OpenAI(api_key="sk-xxx") # ✅ 新代码(LiteLLM) from litellm import completion import os os.environ["OPENAI_API_KEY"] = "sk-xxx" # 环境变量方式,或传入 api_key 参数第三步:替换调用方式
# ❌ 旧代码 response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "你好"}], temperature=0.7 ) # ✅ 新代码(完全兼容 OpenAI 格式!) response = completion( model="gpt-3.5-turbo", # 模型名不变 messages=[{"role": "user", "content": "你好"}], # 输入格式不变 temperature=0.7 # 参数名不变 ) print(response.choices[0].message.content) # 输出格式也完全一致提示:LiteLLM 的
completion()函数签名与 OpenAI SDK 的chat.completions.create()几乎 100% 兼容,包括所有参数(max_tokens,top_p,frequency_penalty等)和返回对象结构。这意味着你无需修改任何业务逻辑代码,就能获得协议抽象层的所有好处。这是它能快速落地的关键设计。
3.2 模型路由实战:如何让一个 API 同时调度 OpenAI、千问和本地 Ollama?
真正的价值体现在多后端场景。假设你的产品需要:
- 国际用户走 OpenAI(低延迟)
- 国内用户走千问(合规+低成本)
- 内部测试走本地 Ollama(零成本+可控)
LiteLLM 提供了两种路由模式:静态路由(Simple Router)和智能路由(Model Router)。我们从简单的开始:
静态路由(适合规则明确的场景)
from litellm import completion import os # 设置多个后端的环境变量 os.environ["OPENAI_API_KEY"] = "sk-openai-xxx" os.environ["DASHSCOPE_API_KEY"] = "dashscope-xxx" # 千问 os.environ["OLLAMA_API_BASE"] = "http://localhost:11434" # Ollama # 定义路由映射:模型别名 → 实际后端 model_alias_map = { "gpt-3.5-turbo": "openai/gpt-3.5-turbo", "qwen2-72b": "qwen/qwen2-72b-instruct", "ollama-llama3": "ollama/llama3" } # 调用时指定别名,LiteLLM 自动解析 response = completion( model="qwen2-72b", # 使用别名 messages=[{"role": "user", "content": "用中文解释量子纠缠"}], api_base=None, # 不指定,由 alias_map 决定 custom_llm_provider=None )智能路由(推荐用于生产环境)
from litellm.router import Router import litellm # 初始化 Router,传入多个模型配置 router = Router( model_list=[ { "model_name": "high-quality", # 逻辑模型名 "litellm_params": { "model": "gpt-4o", "api_key": os.getenv("OPENAI_API_KEY"), "timeout": 60, "max_retries": 2 } }, { "model_name": "cost-effective", "litellm_params": { "model": "qwen/qwen2-72b-instruct", "api_key": os.getenv("DASHSCOPE_API_KEY"), "timeout": 45, "max_retries": 3 } }, { "model_name": "fast-local", "litellm_params": { "model": "ollama/llama3", "api_base": "http://localhost:11434", "timeout": 30, "max_retries": 1 } } ], num_retries=3, # Router 层级重试 fallbacks=[{"high-quality": ["cost-effective", "fast-local"]}] # 主备策略 ) # 使用逻辑模型名调用,Router 自动选择最优后端 response = router.completion( model="high-quality", # 逻辑名,非真实模型名 messages=[{"role": "user", "content": "总结这篇论文的核心贡献"}], temperature=0.3 )注意:
Router会自动收集每个后端的 latency、success rate、error rate 等指标,并基于这些数据动态调整路由决策。你还可以通过router.get_available_deployment()手动查询当前健康节点,或用router.flush_cache()清理失效缓存。这种“自愈”能力,是手写路由无法比拟的。
3.3 生产级增强:Token 精确统计、流式响应与错误归一化
Token 精确统计(RAG 场景生死线)
在 RAG 应用中,prompt = system_message + context_chunks + user_query。如果 token 估算不准,轻则 truncation 导致关键信息丢失,重则触发模型最大上下文限制直接报错。LiteLLM 提供了token_counter工具:
from litellm import token_counter # 精确计算任意字符串的 token 数(自动匹配模型 tokenizer) prompt_tokens = token_counter( model="gpt-4o", text="你是一个法律专家,请分析以下合同条款:" ) # 计算整个 messages 的总 token(考虑 role、content、分隔符) messages = [ {"role": "system", "content": "你是一个法律专家..."}, {"role": "user", "content": "条款A:甲方应于..."} ] total_tokens = token_counter(model="gpt-4o", messages=messages) # 预留 500 token 给 response,确保不超限 if total_tokens + 500 > 128000: # gpt-4o max context # 触发 context 截断逻辑 pass流式响应(前端实时体验)
from litellm import completion # 启用流式 response = completion( model="qwen/qwen2-72b-instruct", messages=[{"role": "user", "content": "用三句话解释区块链"}], stream=True ) # 统一解析,无需关心后端格式 full_response = "" for chunk in response: if chunk.choices[0].delta.content is not None: content = chunk.choices[0].delta.content full_response += content print(content, end="", flush=True) # 实时输出到终端或前端错误归一化(统一异常处理)
from litellm import completion from litellm.exceptions import ( RateLimitError, AuthenticationError, ContextWindowExceededError, BadRequestError ) try: response = completion( model="ollama/llama3", messages=[{"role": "user", "content": "hello"}] ) except RateLimitError as e: # 所有平台的限流错误都被归一为 RateLimitError logger.warning(f"Rate limit hit: {e}") # 执行降级策略:切到备用模型 or 返回缓存答案 except ContextWindowExceededError as e: # 所有平台的上下文超限错误都被归一 logger.error(f"Context too long: {e}") # 触发 prompt 压缩逻辑4. 实操过程与核心环节实现:一个完整的企业级部署案例
4.1 场景设定:某在线教育平台的“AI 学习助手”系统
我们以一个真实复现过的项目为例:某在线教育平台需要为 50 万注册用户提供“AI 学习助手”,核心功能包括:
- 知识点答疑:学生上传题目截图,助手解析并讲解解题思路(需高准确率)
- 作文批改:学生提交作文,助手给出语法、逻辑、文采三维度评分与修改建议(需强中文能力)
- 学习计划生成:根据学生错题数据,生成个性化周学习计划(需长上下文理解)
技术约束:
- 必须支持国内合规模型(千问、讯飞星火)作为主用
- OpenAI 作为国际版备用(供海外用户)
- 本地部署 Ollama 运行 Qwen2-7B 用于 A/B 测试
- 所有模型调用需记录详细审计日志(谁、何时、调用哪个模型、耗时、token 数、是否成功)
- P95 延迟 < 2.5s,成功率 > 99.5%
4.2 架构设计:LiteLLM 作为核心协议层
整个系统采用分层架构:
┌─────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐ │ 前端 Web/App │───▶│ API Gateway (Kong) │───▶│ Business Service │ └─────────────────┘ └───────────────────────┘ └───────────────────────┘ │ ▼ ┌───────────────────────────────────┐ │ LiteLLM Protocol Layer │ │ - Model Routing & Fallback │ │ - Token Counting & Validation │ │ - Streaming & Error Handling │ │ - Audit Logging Hook │ └───────────────────────────────────┘ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ OpenAI API │ │ Qwen API │ │ Ollama Local │ │ (gpt-4o, gpt-3.5)│ │ (qwen2-72b) │ │ (qwen2-7b) │ └──────────────────┘ └──────────────────┘ └──────────────────┘LiteLLM 不作为独立服务部署,而是以内嵌库形式集成在Business Service中。这样做的好处是:
- 零网络跳转,延迟最低
- 日志、trace、metrics 与业务服务完全统一
- 可以在
completion()前后插入任意业务逻辑(如:检查用户是否 VIP,VIP 用户优先走 gpt-4o)
4.3 关键代码实现:审计日志 + 智能路由 + 安全拦截
Step 1:定义审计日志回调(Callback)
import logging import time from litellm import completion from litellm.callbacks import litellm_success_callback, litellm_failure_callback logger = logging.getLogger("litellm_audit") def success_callback(kwargs, completion_response, start_time, end_time): """成功回调:记录完整审计日志""" duration = (end_time - start_time).total_seconds() model = kwargs.get("model", "unknown") messages = kwargs.get("messages", []) input_tokens = kwargs.get("input_tokens", 0) output_tokens = getattr(completion_response, "usage", {}).get("completion_tokens", 0) logger.info( f"AUDIT_SUCCESS | " f"user_id={kwargs.get('metadata', {}).get('user_id', 'anonymous')} | " f"model={model} | " f"input_tokens={input_tokens} | " f"output_tokens={output_tokens} | " f"duration={duration:.3f}s | " f"status=success" ) def failure_callback(kwargs, exception, start_time, end_time): """失败回调:记录错误详情""" duration = (end_time - start_time).total_seconds() model = kwargs.get("model", "unknown") error_type = type(exception).__name__ logger.error( f"AUDIT_FAILURE | " f"user_id={kwargs.get('metadata', {}).get('user_id', 'anonymous')} | " f"model={model} | " f"error={error_type} | " f"message={str(exception)[:100]} | " # 截断避免日志过大 f"duration={duration:.3f}s" ) # 注册回调 litellm.success_callback = [success_callback] litellm.failure_callback = [failure_callback]Step 2:构建生产级 Router
from litellm.router import Router import os # 从环境变量加载密钥(生产环境最佳实践) openai_key = os.getenv("OPENAI_API_KEY") qwen_key = os.getenv("DASHSCOPE_API_KEY") ollama_base = os.getenv("OLLAMA_API_BASE", "http://localhost:11434") # 定义模型池,包含健康检查与负载策略 model_list = [ { "model_name": "prod-main", "litellm_params": { "model": "qwen/qwen2-72b-instruct", "api_key": qwen_key, "timeout": 45, "max_retries": 2, "num_retries": 2 }, "model_info": { "id": "qwen2-72b-prod", "region": "cn-hangzhou", "priority": 10 } }, { "model_name": "prod-backup", "litellm_params": { "model": "openai/gpt-4o", "api_key": openai_key, "timeout": 60, "max_retries": 1 }, "model_info": { "id": "gpt-4o-intl", "region": "us-east-1", "priority": 5 } }, { "model_name": "dev-test", "litellm_params": { "model": "ollama/llama3", "api_base": ollama_base, "timeout": 30, "max_retries": 0 }, "model_info": { "id": "llama3-local", "region": "on-premise", "priority": 1 } } ] # 初始化 Router,启用高级特性 router = Router( model_list=model_list, num_retries=3, fallbacks=[ {"prod-main": ["prod-backup", "dev-test"]}, {"prod-backup": ["dev-test"]} ], set_verbose=True, # 开启详细日志,便于调试 enable_pre_call_checks=True, # 自动检查模型是否支持 streaming/token_counting allowed_fails=3, # 连续失败3次后临时剔除节点 cooldown_time=60 # 剔除后60秒内不尝试 )Step 3:业务层调用封装(带安全拦截)
import re from litellm import completion from litellm.exceptions import BadRequestError def safe_ai_completion(user_id: str, messages: list, model: str = "prod-main"): """ 安全的 AI 调用封装 - 拦截敏感词(医疗、法律建议) - 强制添加 system message(根据场景) - 统一错误处理 """ # 1. 敏感词拦截(教育场景特有) last_user_msg = messages[-1]["content"] if re.search(r"(吃药|手术|诊断|起诉|律师|法院)", last_user_msg): return { "error": "内容涉及专业领域建议,暂不提供相关服务", "code": "PROHIBITED_CONTENT" } # 2. 场景化 system message 注入 system_msg = "你是一名资深教育辅导老师,用通俗易懂的语言解释知识点,避免使用专业术语。" if "作文" in last_user_msg or "批改" in last_user_msg: system_msg = "你是一名中学语文特级教师,从语法、逻辑、文采三方面批改作文,给出具体修改建议。" elif "学习计划" in last_user_msg: system_msg = "你是一名教育规划师,根据学生提供的错题数据,生成可执行的周学习计划,包含每日任务、预计耗时、重点提醒。" enhanced_messages = [{"role": "system", "content": system_msg}] + messages # 3. 调用 LiteLLM Router try: response = router.completion( model=model, messages=enhanced_messages, temperature=0.3, top_p=0.9, metadata={"user_id": user_id} # 透传给 audit log ) return { "content": response.choices[0].message.content, "input_tokens": getattr(response, "usage", {}).get("prompt_tokens", 0), "output_tokens": getattr(response, "usage", {}).get("completion_tokens", 0), "model_used": response._hidden_params.get("model", "unknown") } except BadRequestError as e: # 归一化处理:所有 bad request 都返回友好提示 return {"error": "您的输入格式有误,请重新描述问题", "code": "BAD_REQUEST"} except Exception as e: # 兜底错误 return {"error": "服务暂时不可用,请稍后再试", "code": "INTERNAL_ERROR"} # 使用示例 result = safe_ai_completion( user_id="user_123456", messages=[{"role": "user", "content": "帮我分析这道数学题:..." }] )4.4 部署与监控:如何保障 99.5% 的成功率?
部署要点:
- 进程模型:LiteLLM 运行在业务服务的同一进程中,不单独部署。避免网络延迟和故障域扩大。
- 配置管理:所有模型 endpoint、密钥、超时参数,全部通过环境变量或配置中心(如 Apollo)注入,禁止硬编码。
- 资源隔离:为 LiteLLM 调用设置独立的线程池(Python 中用
concurrent.futures.ThreadPoolExecutor),防止模型调用阻塞主线程。
监控指标(必须采集):
| 指标名 | 说明 | 告警阈值 |
|---|---|---|
litellm_request_total{model, status} | 按模型和状态(success/error)统计请求数 | 错误率 > 0.5% 持续5分钟 |
litellm_request_duration_seconds{model} | P50/P95/P99 延迟 | P95 > 2.5s 持续10分钟 |
litellm_token_usage_total{model, direction} | 输入/输出 token 总数 | 单日超预算 80% |
litellm_router_active_deployments | 当前健康节点数 | < 2 且持续1分钟 |
告警策略:
- 当
prod-main节点连续失败 3 次,自动触发router.flush_cache()并发送 Slack 告警; - 当
prod-backup的 P95 延迟 > 5s,自动降级为dev-test,并通知运维检查网络; - 每日凌晨 2 点,自动执行
router.get_all_responses()获取昨日各模型成功率报表,邮件发送给技术负责人。
5. 常见问题与排查技巧实录:那些只有踩过坑才知道的事
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令/方法 | 解决方案 |
|---|---|---|---|
调用千问返回400 Bad Request,但用 Postman 直连正常 | LiteLLM 默认发送Content-Type: application/json,而千问某些版本要求application/json; charset=utf-8 | curl -v -H "Content-Type: application/json" ...对比 | 设置litellm.drop_params = True或手动指定 headers |
| Ollama 流式响应卡住,前端收不到数据 | Ollama 的流式格式是{"response":"...", "done":false},LiteLLM 默认期望 OpenAI 格式 | litellm.set_verbose(True)查看原始响应体 | 升级 LiteLLM 到 v1.42.0+,已修复 Ollama 流式解析 bug |
token_counter计算结果比模型实际消耗少 50+ tokens | token_counter默认使用cl100k_base(GPT tokenizer),而千问用qwentokenizer | token_counter(model="qwen/qwen2-72b-instruct", ...)显式指定模型 | 务必在token_counter中传入与completion()相同的model参数 |
| Router 一直 fallback 到备用模型,不使用主模型 | 主模型节点因上次失败被加入 cooldown,但allowed_fails设置过低 | print(router.get_available_deployment(model="prod-main")) | 调整allowed_fails=5和cooldown_time=120,或手动router.upgrade_deployment(...) |
| 并发调用时 CPU 占用飙升到 100% | LiteLLM 的tiktokentokenizer 是 CPU 密集型,高并发下成为瓶颈 | top -H查看线程,perf record -g -p <pid> | 启用litellm.use_async_token_counter=True,或预计算 token 数缓存 |