跳到主要内容

数据截至 (上游 commit 3f15dc32871c)

LiteLLM — 架构与原理

30 秒导读: LiteLLM 让你用一套 OpenAI 格式的代码去调 OpenAI、Anthropic、Gemini、Bedrock、Azure 等上百家 LLM——换供应商只改一个 model 字符串。同一份代码还能包成一台 FastAPI 网关,给团队发虚拟 key、记账、限流、多实例负载均衡。


1. 这是什么(零基础也能懂)

一句话定义: LiteLLM 是一个统一 LLM 调用层——对上暴露 OpenAI 的接口形状,对下把请求翻译成各家供应商自己的格式,再把各家的返回翻译回 OpenAI 格式。

它解决谁的什么问题

假设你写了个 agent,用的是 OpenAI 的 chat.completions.create()。现在老板说:成本太高,换成 Claude;某些请求走公司自建的 vLLM;欧洲用户必须走 Azure。

按原样做,你要:

  • 装三个不同的 SDK,学三套 API;
  • 处理三套鉴权(API key / AWS SigV4 / Azure AD token);
  • 三套参数名(OpenAI 的 max_tokens、Anthropic 的 max_tokens 但语义不同、Gemini 的 maxOutputTokens);
  • 三套流式协议;
  • 三套异常类型;
  • 三套计费口径。

LiteLLM 把这六件事全部收进库里。你的业务代码只改 model="anthropic/claude-sonnet-4-20250514"

它有两种用法

形态是什么适合谁
Python SDKpip install litellm,调 completion()单个应用、单个开发者
AI Gateway(Proxy)一台 FastAPI 服务,监听 /v1/chat/completions团队/公司,要发 key、记账、限流

关键点:网关不是另一套实现。网关内部就是拿 HTTP 请求体去调同一个 SDK,外面套了认证、预算、路由。

用起来什么样

SDK 形态,换供应商只动 model 这一行:

# 示意,非源码
from litellm import completion

# 环境变量里放好 ANTHROPIC_API_KEY / OPENAI_API_KEY
r = completion(
model="anthropic/claude-sonnet-4-20250514", # 换成 "openai/gpt-4o" 即切供应商
messages=[{"role": "user", "content": "Hello!"}],
)
print(r.choices[0].message.content) # 返回对象永远是 OpenAI 形状

网关形态,连业务代码都不用改——直接把 OpenAI SDK 的 base_url 指过来:

# 示意,非源码
import openai
client = openai.OpenAI(api_key="sk-1234", base_url="http://0.0.0.0:4000")
client.chat.completions.create(model="gpt-4o", messages=[...])

网关侧只需要一份 YAML 声明"对外叫什么名字、实际打到哪":

# 示意,非源码;真实样例见克隆根的 proxy_server_config.yaml
model_list:
- model_name: gpt-3.5-turbo # 对外的模型组名
litellm_params:
model: openai/gpt-4.1-mini # 实际打到哪
api_key: os.environ/OPENAI_API_KEY

一句话直觉

把 LiteLLM 当成 LLM 世界的 ODBC / JDBC。 数据库驱动层做的事是:定义一套标准 SQL 接口,每种数据库写一个驱动做方言翻译。LiteLLM 做的是同一件事——标准接口选的是 OpenAI 格式,每家供应商写一个「驱动」(一个 BaseConfig 子类)。


2. 顶层全景(它大概怎么转)

2.1 三层结构:每层都可单独使用

这张图从上到下是请求下行的方向。中间两层都是可选的:纯 SDK 用法直接从最下面那层开始。

调用方(OpenAI 格式的请求)

┌──────────┴──────────┐
│ ③ 网关层(可选) │ FastAPI:虚拟 key 认证 / 预算 / 限流 / 记账
└──────────┬──────────┘ litellm/proxy/

┌──────────┴──────────┐
│ ② 路由层(可选) │ Router:一个模型名 → 一组部署
└──────────┬──────────┘ 挑一个、失败换一个、坏的关小黑屋
│ litellm/router.py
┌──────────┴──────────┐
│ ① SDK 层(必经) │ completion():横切壳 + 翻译 + HTTP
└──────────┬──────────┘ litellm/main.py

各家供应商的 HTTP API

三层的依赖是单向的:网关依赖 Router,Router 依赖 SDK,SDK 谁也不依赖。所以你可以只用 SDK,也可以只用 SDK+Router(在自己进程里做负载均衡),也可以全套。

2.2 部件一句话职责

部件干什么锚点(path:line)
completion()SDK 的总入口,一路把请求准备好交给 HTTP 编排器litellm/main.py:4901
@client 装饰器套在入口外面,管日志、缓存、重试、成本、异常映射litellm/utils.py:1342
get_llm_provider()"anthropic/claude-x" 拆成 provider + modellitellm/litellm_core_utils/get_llm_provider_logic.py:130
get_optional_params()OpenAI 参数 → 这家支持的参数;不支持就报错或丢弃litellm/utils.py:3934
BaseConfig每家供应商的翻译契约(6 个抽象方法)litellm/llms/base_llm/chat/transformation.py:65
ProviderConfigManager按 provider 枚举查出对应的 BaseConfig 子类litellm/utils.py:8030
BaseLLMHTTPHandler统一的 HTTP 编排:验环境→拼 URL→翻译→发→翻译回来litellm/llms/custom_httpx/llm_http_handler.py:455
CustomStreamWrapper把各家的流式碎片统一成 OpenAI 的 chunk 迭代器litellm/litellm_core_utils/streaming_handler.py:185
cost_per_token()查 JSON 价格表,按 token 数算钱litellm/cost_calculator.py:300
Router模型组 → 部署列表,负载均衡 / 冷却 / 回退litellm/router.py:373
user_api_key_auth()网关的认证依赖:验虚拟 key、查权限与预算litellm/proxy/auth/user_api_key_auth.py:2628
ProxyBaseLLMRequestProcessing网关的请求编排:hook + 路由 + 回写用量头litellm/proxy/common_request_processing.py:1372

2.3 主线走一遍(SDK 层,不进代码)

这是最该先看懂的一条链。从左到右是执行顺序,每一步只做一件事。

completion(model="anthropic/claude-...", messages=[...])

├─① @client 外壳 先查缓存 / 起日志对象 / 记开始时间

├─② 识别供应商 "anthropic/claude-x" → provider=anthropic, model=claude-x

├─③ 参数收敛 OpenAI 参数表 ∩ 这家支持的参数表
│ 差集:报错(默认)或丢掉(drop_params=True)

├─④ 取翻译器 ProviderConfigManager → AnthropicConfig 实例

├─⑤ HTTP 编排 validate_environment(填鉴权头)
│ → get_complete_url(拼完整地址)
│ → transform_request(OpenAI body → Anthropic body)
│ → sign_request(需要签名的家,如 Bedrock)
│ → 真正发 httpx 请求

└─⑥ 翻译回来 transform_response:Anthropic JSON → ModelResponse
回到 @client 外壳:算成本、写缓存、发日志

要记住的一件事: 第 ⑤ 步那串调用顺序是写死在编排器里的,所有供应商共用;每家的差异全部塞进 BaseConfig 子类的方法实现里。这就是 LiteLLM 能横向铺开上百家的根本原因(依据:litellm/llms/custom_httpx/llm_http_handler.py:492-646)。

2.4 规模感

指标数字来源
LlmProviders 枚举成员150litellm/types/utils.py:3618
litellm/llms/ 下的子目录137克隆目录统计
litellm/llms/ 下的 class *Config(390克隆源码统计
chat 翻译文件(*/chat/transformation.py)84克隆目录统计
JSON 价格表条目3041model_prices_and_context_window.json
纯 JSON 注册的 OpenAI 兼容供应商26litellm/llms/openai_like/providers.json
版本1.98.0pyproject.toml:3

3. 阅读地图

建议按编号顺序读。01 是主干,02–04 是主干上挂的三块肉,05–06 是主干之上叠的两层。只想搞懂"它怎么统一上百家"的话,读 01 + 02 就够了。

章节讲什么什么时候读
LiteLLM — 架构与原理本页:是什么、全景、地图现在
主线:一次 completion() 从调用到返回端到端追一次调用,把 §2.3 那张图落到真实函数上必读,第一篇
翻译层:BaseConfig 契约与参数映射6 个抽象方法各管什么;map_openai_params 怎么处理"这家不支持这个参数"必读,第二篇
传输与流式:统一的 HTTP 编排和增量拼装BaseLLMHTTPHandler 的固定调用序列;SSE 碎片怎么拼成 OpenAI chunk;fake stream想改传输/流式时
横切层:@client 装饰器里的日志、缓存、成本与异常同步/异步两个 wrapper;缓存命中路径;成本计算;异常统一映射到 OpenAI 异常类想搞懂"钱和日志从哪来"
Router:模型组、负载均衡、冷却与回退模型组语义;5 种路由策略;失败率冷却;fallback 链要做多实例/高可用
AI Gateway:把 SDK 包成带认证与计费的多租户网关虚拟 key 认证、预算/限流 hook、spend log 落库、端点分发要自建网关

4. 巧妙之处(可借鉴的技术)

4.1 用「6 个抽象方法」把供应商差异关进笼子

妙在哪: 主干流程一行都不为新供应商改动。加一家 = 写一个类、实现 6 个方法、在枚举里加一项。

BaseConfig 强制实现的只有这六个(依据:litellm/llms/base_llm/chat/transformation.py):

抽象方法回答的问题锚点
get_supported_openai_params这家认哪些 OpenAI 参数?:180
map_openai_paramsOpenAI 参数名/值怎么改写成这家的?:230
validate_environment鉴权头怎么填、缺什么 key 就报错?:240
transform_requestOpenAI 请求体 → 这家的请求体:298
transform_response这家的响应 → ModelResponse:330
get_error_class这家的错误码 → 哪个异常类:358

其余的都给了默认实现,只有"特殊的家"才覆写——比如 get_complete_url(默认拼 /chat/completions)、sign_request(默认不签名,Bedrock 才覆写做 SigV4)、should_fake_stream(默认 False)(依据::277:252:119)。

4.2 只有真正特殊的家才配写代码,其余的一段 JSON 就够

妙在哪: 长尾供应商大多是"OpenAI 兼容 + 换个 base_url",给它们各写一个类是纯浪费。LiteLLM 把这批人搬到了一个 JSON 里。

providers.json 里一家就长这样——两个字段:

{ "base_url": "https://ai-gateway.helicone.ai/", "api_key_env": "HELICONE_API_KEY" }

查配置时先查 Python 类映射表(有自定义逻辑的优先),查不到才回落到 JSON 注册表,并在运行时动态造一个类出来(依据:litellm/utils.py:8030-8057 get_provider_chat_config 与懒加载 _PROVIDER_CONFIG_MAP;litellm/llms/openai_like/json_loader.py:27 JSONProviderRegistry;litellm/llms/openai_like/dynamic_config.py:20 create_config_class)。

当前 26 家走这条零代码路径。

4.3 参数不兼容时,把选择权交回给用户

妙在哪: "这家不支持 logit_bias" 有两种处理——静默丢弃(结果和预期不符,难查)或直接报错(代码跑不动)。LiteLLM 默认选报错,但错误信息里直接写出修复方法。

判断逻辑很直白:非默认参数逐个比对 get_supported_openai_params() 的白名单,不在名单里就进 unsupported_params;开了 drop_params 就 pop 掉,没开就抛 UnsupportedParamsError,消息里同时给出 SDK 写法和 proxy YAML 写法(依据:litellm/utils.py:4007-4045 _check_valid_arg)。

名单里还硬编码了几个豁免:userstreamstream_options 永不报错,n=1 不报错(因为 LangChain 默认就传 n=1)——这是被真实生态逼出来的补丁,注释里写得很老实。

4.4 假流式:模型不支持流式,就装作支持

妙在哪: 调用方的 for chunk in resp: 代码不用为"这个模型不支持流式"分叉。

BaseConfig.should_fake_stream() 默认返回 False,需要的家覆写它返回 True。编排器一旦拿到 fake_stream=True,就走"一次性拿完整响应 → 包成单个 chunk 的迭代器"这条路,外面看起来和真流式一模一样(依据:litellm/llms/base_llm/chat/transformation.py:119 should_fake_stream;litellm/llms/custom_httpx/llm_http_handler.py:486-488;litellm/llms/base_llm/base_model_iterator.py:234 FakeStreamResponseIterator)。

4.5 价格是数据,不是代码

妙在哪: 供应商天天调价、天天出新模型。如果价格写在 Python 里,每次调价都要发版。

LiteLLM 把它做成一张 3041 条的 JSON 表,每条记录带 input_cost_per_token / output_cost_per_token / cache_read_input_token_cost / max_input_tokens / mode / supports_* 等字段;仓库里同时留了一份 backup JSON 供离线使用(依据:克隆根 model_prices_and_context_window.json;litellm/model_prices_and_context_window_backup.json)。

算钱的入口是 cost_per_token(),缓存读/缓存写/音频/图片这些"非纯 token"的计价分支收在 generic_cost_per_token() 里(依据:litellm/cost_calculator.py:300;litellm/litellm_core_utils/llm_cost_calc/utils.py:794)。

自定义模型也能进表:调用时传 input_cost_per_token / output_cost_per_token,completion() 会当场 register_model() 把这条价格注册进去(依据:litellm/main.py:5282-5290)。

4.6 网关的端点分发:方法名即路由

妙在哪: 网关支持 chat / embedding / responses / rerank / image / audio ……十几种端点。如果每种都写一个 if 分支去调对应的 Router 方法,新增端点就要改分发代码。

LiteLLM 让 FastAPI 侧传下来的 route_type 字符串,直接等于 Router 上的方法名,分发就一行反射:

# 真实源码片段,litellm/proxy/route_llm_request.py:448
return getattr(llm_router, f"{route_type}")(**data)

/chat/completions 传的是 route_type="acompletion",对应 Router.acompletion(依据:litellm/proxy/proxy_server.py:9888 chat_completionroute_type="acompletion";litellm/router.py:2131 Router.acompletion)。

4.7 冷却看失败率,不看失败次数;而且单实例模型组默认不冷却

妙在哪: 两个真实教训被直接写进了判据。

  • 看比率不看次数:偶发的 3 次失败不该拉黑一个部署。判据是"这一分钟内失败占比 > 阈值 请求数达到最小样本量",样本不足时不做判断(依据:litellm/router_utils/cooldown_handlers.py:317 _should_cooldown_deployment)。
  • 单部署模型组默认豁免:如果这个模型组只有一个部署,把它冷却掉等于服务直接不可用——所以 429 和高失败率这两条都显式跳过 is_single_deployment_model_group(同上,注释写着 "by default we should avoid cooldowns on single deployment model groups")。

5. 代码地图(导航索引)

按主题跳源码。行号会随上游漂移,符号名相对稳定——优先用符号名 grep。

5.1 SDK 主线

主题文件路径符号名
同步入口(全流程起点)litellm/main.py:4901completion
异步入口litellm/main.py:388acompletion
供应商识别("a/b" → provider+model)litellm/litellm_core_utils/get_llm_provider_logic.py:130get_llm_provider
参数收敛 + 不支持参数处理litellm/utils.py:3934 / :4007get_optional_params / _check_valid_arg
供应商分发上下文(打包 30 个变量)litellm/types/completion.py:207_CompletionDispatchContext
供应商分发链起点litellm/main.py:5575completionif custom_llm_provider == "azure"
单个供应商的分发函数(样板)litellm/main.py:1469_complete_deepseek

5.2 翻译层

主题文件路径符号名
翻译契约(抽象基类)litellm/llms/base_llm/chat/transformation.py:65BaseConfig
供应商异常基类litellm/llms/base_llm/chat/transformation.py:40BaseLLMException
按 provider 查翻译器litellm/utils.py:8030ProviderConfigManager.get_provider_chat_config
供应商枚举(150 项)litellm/types/utils.py:3618LlmProviders
具体样板:Anthropic 参数映射litellm/llms/anthropic/chat/transformation.py:1394AnthropicConfig.map_openai_params
具体样板:Anthropic 请求/响应翻译litellm/llms/anthropic/chat/transformation.py:1781 / :2464transform_request / transform_response
JSON 注册的 OpenAI 兼容供应商litellm/llms/openai_like/json_loader.py:27JSONProviderRegistry
运行时动态造配置类litellm/llms/openai_like/dynamic_config.py:20create_config_class

5.3 传输与流式

主题文件路径符号名
HTTP 编排器(所有供应商共用)litellm/llms/custom_httpx/llm_http_handler.py:455BaseLLMHTTPHandler.completion
同步/异步底层发送litellm/llms/custom_httpx/llm_http_handler.py:332 / :282_make_common_sync_call / _make_common_async_call
同步流式调用litellm/llms/custom_httpx/llm_http_handler.py:671make_sync_call
异步流式调用litellm/llms/custom_httpx/llm_http_handler.py:746acompletion_stream_function
统一 chunk 迭代器(SSE 解析)litellm/llms/base_llm/base_model_iterator.py:66 / :111BaseModelResponseIterator / _handle_string_chunk
假流式迭代器litellm/llms/base_llm/base_model_iterator.py:234FakeStreamResponseIterator
对外的流式包装器litellm/litellm_core_utils/streaming_handler.py:185 / :1504CustomStreamWrapper / chunk_creator

5.4 横切层(日志 / 缓存 / 成本 / 异常)

主题文件路径符号名
装饰器本体(同步 + 异步两个 wrapper)litellm/utils.py:1342 / :1280 / :1580client / wrapper / wrapper_async
成功回调投递到线程池litellm/utils.py:1554executor.submit(ctx.run, logging_obj.success_handler, ...)
日志对象(所有 callback 的汇聚点)litellm/litellm_core_utils/litellm_logging.pyLogging
缓存读写litellm/caching/caching_handler.pyLLMCachingHandler
成本计算入口litellm/cost_calculator.py:300 / :1112 / :1715cost_per_token / completion_cost / response_cost_calculator
通用计价(含缓存/多模态分支)litellm/litellm_core_utils/llm_cost_calc/utils.py:794generic_cost_per_token
价格表克隆根 model_prices_and_context_window.json3041 条,按模型名做 key
异常统一映射litellm/litellm_core_utils/exception_mapping_utils.py:2164exception_type
对外异常类(继承 openai 的)litellm/exceptions.py:129AuthenticationError / RateLimitError / ContextWindowExceededError

5.5 Router

主题文件路径符号名
Router 本体litellm/router.py:373 / :382Router / Router.__init__
对外异步入口litellm/router.py:2131Router.acompletion
挑部署 + 调 SDKlitellm/router.py:2923Router._acompletion
回退链litellm/router.py:6540async_function_with_fallbacks
重试litellm/router.py:6635async_function_with_retries
失败时的冷却回调litellm/router.py:7177deployment_callback_on_failure
模型组装载litellm/router.py:8174set_model_list
选部署主流程litellm/router.py:11129async_get_available_deployment
默认策略:加权随机litellm/router_strategy/simple_shuffle.py:21simple_shuffle
其余策略litellm/router_strategy/lowest_latency.py / lowest_cost.py / lowest_tpm_rpm_v2.py / least_busy.py
冷却判据与写入litellm/router_utils/cooldown_handlers.py:317 / :413_should_cooldown_deployment / _set_cooldown_deployments

5.6 AI Gateway(Proxy)

主题文件路径符号名
FastAPI 应用litellm/proxy/proxy_server.py:1379app = FastAPI(...)
chat 端点(4 条路径共用一个函数)litellm/proxy/proxy_server.py:9888chat_completion
认证依赖(挂在每个端点上)litellm/proxy/auth/user_api_key_auth.py:2628 / :1088user_api_key_auth / _user_api_key_auth_builder
请求编排(hook + 路由 + 回写头)litellm/proxy/common_request_processing.py:1372 / :2002ProxyBaseLLMRequestProcessing / base_process_llm_request
端点 → Router 方法的反射分发litellm/proxy/route_llm_request.py:323 / :448route_request
Hook 总线(前置/中置/后置)litellm/proxy/utils.py:430 / :1386 / :1870 / :2377ProxyLogging / pre_call_hook / during_call_hook / post_call_success_hook
预算拦截litellm/proxy/hooks/max_budget_limiter.py:15_PROXY_MaxBudgetLimiter
并发/速率限制litellm/proxy/hooks/parallel_request_limiter_v3.py并发与 TPM/RPM 限流 hook
花费落库回调litellm/proxy/hooks/proxy_track_cost_callback.py:211_PROXY_track_cost_callback
虚拟 key 表(注意 token 存的是哈希)schema.prisma:416LiteLLM_VerificationToken
花费流水表schema.prisma:611LiteLLM_SpendLogs

本文所有引用 as-of commit 3f15dc32871c8026295e0fdbac4be7710263861c(litellm 1.98.0)。行号可能随上游漂移,符号名是更稳定的锚。