跳到主要内容

数据截至 (上游 commit 3f15dc32871c)

翻译层:BaseConfig 契约与参数映射

30 秒导读: LiteLLM 的卖点是"一套 OpenAI 代码调 100+ 家模型"。这一章讲这个"统一"到底是怎么做出来的: 每家供应商写一个 BaseConfig 子类,实现 6 个必答题(参数白名单、参数映射、鉴权、请求组装、响应还原、错误分类); 上层只认这个接口,永远不知道下面是 Anthropic 还是 Bedrock。

本章只讲翻译。请求实际怎么发出去、流式增量怎么拼,见 03-http-and-streaming; 一次调用的完整时间线见 01-request-lifecycle


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

一句话定义: 翻译层 = 一个抽象基类 BaseConfig(litellm/llms/base_llm/chat/transformation.py:65)+ 每家供应商一个子类, 负责把 OpenAI 格式的请求翻成供应商格式、再把供应商的响应翻回 OpenAI 格式。

它解决的问题。 你写好了一段 OpenAI 代码,想换成 Claude。麻烦不在"换个 URL":

你写的(OpenAI)Anthropic 那边叫什么 / 长什么样
max_completion_tokens=100max_tokens=100(名字不同)
stop=["\n\n"]stop_sequences=["\n\n"]
messages=[{"role":"system",...}, ...]system 必须从 messages 里抽出来放顶层 system 字段
tools=[{"type":"function","function":{...,"parameters":{...}}}]tools=[{"name":..., "input_schema":{...}}]
user="alice"metadata={"user_id":"alice"}
response_format={"json_schema":...}老模型不支持,得假装成一次工具调用去逼出 JSON

一句话直觉。 把它当成一个双向翻译官:进门时把中文翻成对方的语言,出门时再翻回中文。 调用方进出两侧看到的都是同一种语言(OpenAI 格式),中间那段外语它压根不知道。

用起来什么样。 用户视角只有一行差别:

# 同一段代码,只换 model 字符串
litellm.completion(model="gpt-4o", messages=msgs, max_tokens=100, tools=tools)
litellm.completion(model="anthropic/claude-sonnet-4-5", messages=msgs, max_tokens=100, tools=tools)
litellm.completion(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", messages=msgs, max_tokens=100, tools=tools)

三行走的是三个不同的 BaseConfig 子类,返回的都是同一个 ModelResponse


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

怎么读这张图: 从上往下是一次请求的时间顺序。灰色的第 ③ 步之前只动参数字典,之后才动 messages / URL / header。 注意最上和最下都是 OpenAI 格式——这就是"统一接口"的全部含义。

调用方 ← 永远只写 OpenAI 格式 →
completion(model="anthropic/claude-...", messages=[...], max_completion_tokens=100, tools=[...])


┌────────────────────────────────────────────────────┐
│ ① 选 config:按 provider + model + base_model 挑一个 │
│ 子类实例 ProviderConfigManager │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ ② 参数映射(只碰参数字典,不碰 messages) │
│ 能不能收? get_supported_openai_params │
│ 怎么改名? map_openai_params │
│ 产出 optional_params │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ ③ 请求组装(碰 messages / URL / header) │
│ 鉴权头 validate_environment │
│ 真实URL get_complete_url │
│ 请求体 transform_request │
│ 签名 sign_request(Bedrock 之类才用) │
└────────────────────────────────────────────────────┘
│ 供应商私有格式的 HTTP 请求 → 见 03 章
│ 供应商私有格式的 HTTP 响应 ←

┌────────────────────────────────────────────────────┐
│ ④ transform_response → ModelResponse │
│ 非 2xx → get_error_class 转成供应商专属异常 │
└────────────────────────────────────────────────────┘


调用方 ← 拿到的仍是 OpenAI 格式(ModelResponse) →

第 ② 步发生在 litellm.completion() 内部的参数预处理阶段;第 ③④ 步发生在 HTTP 编排器里, 按顺序在 litellm/llms/custom_httpx/llm_http_handler.pycompletion(:455)中依次调用: should_fake_stream(:488)→ validate_environment(:492)→ get_complete_url(:502)→ transform_request(:511)→ sign_request(:522)。

部件职责一览:

部件干什么在哪个文件
BaseConfig定义契约:6 个必答题 + 一堆可选钩子litellm/llms/base_llm/chat/transformation.py:65
get_optional_params参数过滤 + 分发到某个 config 的 map_openai_paramslitellm/utils.py:3934
ProviderConfigManager按 provider/model 选出该用哪个 configlitellm/utils.py:7780
各供应商子类真正的翻译逻辑litellm/llms/anthropic/chat/transformation.py:230
JSONProviderRegistry纯声明式接入 OpenAI 兼容供应商,零 Python 代码litellm/llms/openai_like/json_loader.py:27
CustomLLM用户自带整套 handler,完全绕开翻译层litellm/llms/custom_llm.py:41

3. 契约:BaseConfig 的必答题

BaseConfigABC。带 @abstractmethod 的一共 6 个——不实现就实例化不了,这就是"接一家新供应商的最小工作量"。

3.1 六个必答题

方法(行号)一句话职责谁在什么时候调它
get_supported_openai_params(:180)这个 model 能收哪些 OpenAI 参数(返回名字列表)参数校验前,经 litellm/litellm_core_utils/get_supported_openai_params.py:8 汇总
map_openai_params(:230)把 OpenAI 参数名/形状翻成供应商的litellm/utils.py:4059 起的分发链里
validate_environment(:240)取 API key、填鉴权 header;key 缺失就在这里报错发请求最前面(llm_http_handler.py:492)
transform_request(:298)messages + 参数 → 供应商请求体 dictURL 定好之后(llm_http_handler.py:511)
transform_response(:330)供应商响应 → ModelResponse拿到响应后(llm_http_handler.py:657:715)
get_error_class(:358)HTTP 错误 → 供应商专属异常(BaseLLMException,:40)非 2xx 时(llm_http_handler.py:324:374)

关键分工:map_openai_params 只碰参数,transform_request 才碰 messages。 Anthropic 的实现里专门写了一段注释说明为什么工具名改写要放在 transform_request 而不是 map_openai_params—— 因为前者是 Anthropic / Bedrock-Anthropic / Vertex-Anthropic 三条路径共用的唯一咽喉 (litellm/llms/anthropic/chat/transformation.py:1403-1413),而且 optional_params 会被整个展开进请求体, 放内部状态进去会被供应商判成非法字段。

3.2 可选钩子:只有需要的供应商才覆盖

钩子(行号)什么时候需要真实例子
sign_request(:252)请求体组装完还要整体签名Bedrock SigV4:litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py:144
get_complete_url(:277)URL 里要带 model 或固定路径Ollama 拼 /api/generate:litellm/llms/ollama/completion/transformation.py:408
async_transform_request(:308)异步组装时要发 HTTP(如把图片 URL 抓下来转 base64)目前只有 OpenAI 路径调用(litellm/llms/openai/openai.py:854,实现在 litellm/llms/openai/chat/gpt_transformation.py:445)
transform_parsed_response_dict(:346)走 OpenAI SDK 的供应商绕过了 transform_response,需要在这里修畸形响应github_copilot 返回空 choices 时补救:litellm/llms/github_copilot/chat/transformation.py:263
get_model_response_iterator(:361)提供流式增量解析器流式路径调用(llm_http_handler.py:730),细节见 03 章
has_custom_stream_wrapper(:404)声明"我自带整套流式包装,别走通用管线"litellm/llms/oci/chat/transformation.py:250;判定点 llm_http_handler.py:602
should_fake_stream(:119)供应商不支持真流式,先拿完整响应再切成假增量Azure o-series:litellm/llms/azure/chat/o_series_transformation.py:69;判定点 llm_http_handler.py:488
_add_response_format_to_tools(:183)供应商不支持 response_format,用 tool calling 模拟 JSON schemaFireworks AI:litellm/llms/fireworks_ai/chat/transformation.py:312
calculate_additional_costs(:428)token 之外还有基础设施费/路由费Azure AI model router:litellm/llms/azure_ai/azure_model_router/transformation.py:91;调用点 litellm/cost_calculator.py:269

_add_response_format_to_tools 值得单独看一眼——这是"用工具调用假装结构化输出"的标准做法: 把 JSON schema 塞进一个名叫 json_tool_call 的假工具(常量 RESPONSE_FORMAT_TOOL_NAME,litellm/constants.py:1313), 再用 tool_choice 逼模型必须调它,最后打上 optional_params["json_mode"] = True (base_llm/chat/transformation.py:208-224)。响应侧看到 json_mode 就把这次"工具调用的参数"当作正文内容还回去。

3.3 一个最小 config 长什么样

# 示意,非源码:接一家新供应商的最小骨架
class MyProviderConfig(BaseConfig):
def get_supported_openai_params(self, model): # 我只认这三个
return ["max_tokens", "temperature", "stream"]

def map_openai_params(self, non_default_params, optional_params, model, drop_params):
for k, v in non_default_params.items():
if k == "max_tokens":
optional_params["maxOutputTokens"] = v # 改名
elif k in ("temperature", "stream"):
optional_params[k] = v
return optional_params

def validate_environment(self, headers, model, messages, optional_params,
litellm_params, api_key=None, api_base=None):
headers["X-Api-Key"] = api_key or os.environ["MYPROVIDER_API_KEY"]
return headers
# transform_request / transform_response / get_error_class 同理

重点看:map_openai_params 是一个纯字典 → 字典的函数,没有 IO、没有 messages。


4. 参数过滤与映射:get_optional_params

入口是 litellm/utils.py:3934get_optional_params。它做四件事,顺序固定。

4.1 第一刀:passed_params 与 non_default_params

  • passed_params = 调用方传进来的全部东西,用 locals().copy() 一把抓(utils.py:3980), 包括 **kwargs 里那些非 OpenAI 的私货(top_kaws_region_name…)。
  • non_default_params = 从中筛出"值和 OpenAI 默认值不一样"的那些 (PreProcessNonDefaultParams.base_pre_process_non_default_params,utils.py:3715; 默认值表 DEFAULT_CHAT_COMPLETION_PARAM_VALUESlitellm/constants.py:669)。

为什么要分这两个? 因为"用户没传 temperature"和"用户显式传了 temperature=None"必须区分。 只有真正被改过的参数才需要翻译,也才需要被校验——用户没传的东西,不该因为供应商不支持就报错。

被排除在 non_default_params 之外的还有控制类参数本身(drop_paramsallowed_openai_paramsadditional_drop_paramsapi_version)和 messages(utils.py:3737-3753)——它们是指令,不是要转发的参数。

紧接着 pre_process_non_default_params(utils.py:3781)做两处规整: Pydantic 模型形式的 response_format 转成 JSON schema(:3805-3812); 清掉 tools 里会让 Gemini 报错的 additionalProperties: False(:3819-3826)。

另有一个 pre_process_optional_params(utils.py:3856)专管云厂商的鉴权参数 (project / region_name / token 翻成 azure / vertex / watsonx / aws 各自的写法), 以及"供应商根本不支持 function calling 时改写成 prompt"的退路(:3883-3924)。

4.2 第二刀:_check_valid_arg 与 UnsupportedParamsError

if unsupported_params:
if litellm.drop_params is True or (drop_params is not None and drop_params is True):
for k in unsupported_params.keys():
non_default_params.pop(k, None)
else:
raise UnsupportedParamsError(...)

—— litellm/utils.py:4033-4041,内嵌函数 _check_valid_arg(:4007)的收尾。默认行为是报错而不是静默丢弃: 你以为传了 logit_bias,结果供应商根本不支持,LiteLLM 宁可让你知道。异常类是 UnsupportedParamsError (litellm/exceptions.py:911),它继承 BadRequestError,所以对调用方而言就是一个普通的 400 族错误。

有三个例外会被无条件放行:user / stream_options / stream、LangChain 习惯性传的 n=1、 以及 max_retries(utils.py:4020-4026,max_retries 那处源码里明确标了 TODO: This is a patch)。

4.3 四个逃生阀,语义各不相同

逃生阀写在哪作用域语义依据
litellm.drop_params全局模块变量整个进程不支持的参数静默丢掉litellm/__init__.py:237(可由 LITELLM_DROP_PARAMS 环境变量设置);判定 utils.py:4033
drop_params=True单次 completion() 入参这一次请求同上,只影响本次utils.py:3966;判定 utils.py:4033
additional_drop_params=[...]单次 completion() 入参这一次请求按名字点名丢掉,不管供应商支不支持_should_drop_param,utils.py:2982
allowed_openai_params=[...]单次 completion() 入参这一次请求反向:强行把参数加进白名单,原样透传utils.py:4050,回填 _apply_openai_param_overrides,utils.py:4576

三个"丢"里最容易混的是前两个 vs 第三个:

  • drop_params条件性的——只丢"供应商不支持的";供应商支持的照样发出去。
  • additional_drop_params无条件的——名字对上就丢,而且丢得更早:它在 non_default_params 构造阶段 就把参数剔除了(utils.py:3753),所以 _check_valid_arg 根本看不到它,自然也不会报错。 它还支持 a.b.c 这种嵌套路径,在最后一步再做一次深层删除(utils.py:4524-4530)。
  • allowed_openai_params 是唯一往里加的阀门:把参数接到 supported_params 尾巴上让校验放行, 末了再把它塞回 optional_params。这里有个真实教训——早期实现会给"白名单里但用户没传"的参数写 None, 结果 OpenAI SDK 收到不认识的顶层 kwarg 直接炸,修法是只回填用户真传了的(utils.py:4576-4595,附 issue #25697)。

UnsupportedParamsError 的报错文案会把这四个阀门里的三个直接写给用户看(utils.py:4039)——错误信息即文档。

4.4 每种端点各有一套平行实现

聊天不是唯一入口。嵌入、图像、转写各自复制了一份同样结构的流程:

端点函数位置自带的校验函数
chatget_optional_paramslitellm/utils.py:3934_check_valid_arg,:4007
embeddingget_optional_params_embeddingslitellm/utils.py:3233_check_valid_arg,:3165
imageget_optional_params_image_genlitellm/utils.py:3108_check_valid_arg,:3037
transcriptionget_optional_params_transcriptionlitellm/utils.py:3002_check_valid_arg,:3259

四份逻辑高度重复,只有 chat 那份被抽出了 PreProcessNonDefaultParams(utils.py:3713)供 embedding 复用 (embedding_pre_process_non_default_params,:3760)。复制的代价也看得见:transcription 的报错文案写死成 "Setting user/encoding format is not supported by …"(utils.py:3048),不管实际不支持的是哪个参数。

4.5 分发:一条几百行的 if/elif,和它旁边的通用通道

utils.py:4057 开始是一条按 custom_llm_provider 逐个 elif 的长链,一直排到 :4502。 但链条末尾有两个"通用出口":

  • elif provider_config is not None:(utils.py:4495)—— 只要 ProviderConfigManager 给出了 config,就直接调它的 map_openai_params
  • else: 兜底成 OpenAILikeChatConfig(utils.py:4502)。

也就是说新接的供应商根本不需要往这条长链里加分支,只要能被 ProviderConfigManager 认出来即可。 前面那几十个显式分支是历史包袱(有些还带着 Bedrock 路由、Azure o-series 判定这类无法通用化的逻辑)。

最后一步 add_provider_specific_params_to_optional_params(utils.py:4535)处理"不在 OpenAI 规范里的私货": OpenAI 兼容供应商塞进 extra_body,其他供应商直接平铺到 optional_params


5. 用哪个 config?ProviderConfigManager 说了算

ProviderConfigManager.get_provider_chat_config(model, provider, base_model)(litellm/utils.py:8030) 是选型的唯一入口。它按三层优先级挑:

provider = "openai"? ──→ o-series 模型 → openaiOSeriesConfig
│ GPT-5 模型 → OpenAIGPT5Config

provider = "azure"? ──→ 用 base_model(而非部署名)判类型 → _get_azure_config(:7954)

查 _PROVIDER_CONFIG_MAP(懒加载,O(1) 字典)
│ 命中 → Python 类(有定制逻辑,优先级最高)
│ 未命中 ↓
查 JSONProviderRegistry → 动态生成一个 OpenAI 兼容 config
│ 未命中 ↓
返回 None(上层退回 OpenAILikeChatConfig 兜底)

三个设计点值得记:

  1. 同一个 provider 可以给出不同 config,取决于 model 字符串。OpenAI 的 o-series 和 GPT-5 参数集就和普通模型不同。
  2. base_model 是能力提示,不是路由键。 Azure 部署名可以随便起(my-gpt4-prod),从名字看不出模型能力; 传 base_model 后按它判类型。注意它是加法:最终 supported params 是 model 与 base_model 两者的并集, 只会加能力、不会减(get_supported_openai_params.py:53-58 的注释与实现)。
  3. Python 类优先于 JSON(utils.py:8057 附近注释,懒加载 _PROVIDER_CONFIG_MAP),因为 Python 类才有定制覆盖。

6. 真实样例:AnthropicConfig 全流程

AnthropicConfig(litellm/llms/anthropic/chat/transformation.py:230)是最值得读的样例—— 它同时被直连 Anthropic、Bedrock invoke、Vertex Anthropic、Azure Anthropic 四条路径复用。

6.1 白名单是动态的

get_supported_openai_params(:434)返回一个固定列表,但会按模型能力追加:

if ("claude-3-7-sonnet" in model
or AnthropicConfig._is_adaptive_thinking_model(model)
or supports_reasoning(model=model, custom_llm_provider=self.custom_llm_provider)):
params.append("thinking")
params.append("reasoning_effort")

—— :454-465。同一家供应商、不同模型,能收的参数不一样,所以这个方法的入参是 model 而不是无参。

6.2 参数映射:一个大 for 循环

map_openai_params(:1394)对 non_default_params 逐项翻译。挑三条看:

  • max_tokensmax_completion_tokens 映射到 Anthropic 的 max_tokens(:1415-1416), 并且顺手把浮点数取整(下游 API 只收整数)。
  • user="alice"metadata={"user_id": "alice"}(:1469-1475),前提是通过 _valid_user_id 检查—— Anthropic 会因为 user 是邮箱或电话而报错,所以这里用正则拦掉(:2550 起)。
  • response_format 分两路(:1445-1468):新模型(Sonnet 4.5/4.6、Opus 4.1/4.5/4.6/4.7…)走原生 output_format; 老模型退回"假装成工具调用"的老套路,并置 json_mode=True

结尾还有一处补偿:开了 thinking 却没给 max_tokens 时,自动补成 thinking budget + 默认值 (update_optional_params_with_thinking_tokens,base_llm/chat/transformation.py:103), 否则 Anthropic 会因为 max_tokens 小于思考预算而报错。

6.3 请求组装:system 抽取、tools 重塑、cache_control 透传

① system message 从数组里抽出来。 OpenAI 把 system 当成 messages 的第一条,Anthropic 要求它是顶层字段:

if len(system_prompt_indices) > 0:
for idx in reversed(system_prompt_indices):
messages.pop(idx)

—— translate_system_message(:1609 定义),:1658-1662倒序 pop 是为了下标不失效;抽出来的内容在 transform_request 里挂到 optional_params["system"](:1861-1862)。

② tools 换骨架。 OpenAI 的 function.parameters 变成 Anthropic 的 input_schema:

_tool = AnthropicMessagesTool(
name=tool["function"]["name"],
input_schema=input_anthropic_schema,
type="custom",
)

—— _map_tool_helper,:675-679。周边还有一圈防御:schema 缺省时兜底成 {"type": "object"}(:666-668), 再统一交给 sanitize_input_schema_for_anthropic(:673,在 anthropic 的 common_utils 里,$ref 展开与字段白名单都收在其中)。 入口 _map_tools(:877)负责遍历,并把 MCP server 类工具分流出去。

③ cache_control 一路透传成 cache point。 OpenAI 消息里挂的 cache_control 在翻译时被原样搬进 Anthropic 的内容块:

if "cache_control" in system_message_block:
anthropic_system_message_content["cache_control"] = system_message_block["cache_control"]

—— :1634-1635(system 块);工具上的同名字段见 :773-786;顶层 cache_control 参数则直接透传(:1561-1563)。 它还会反向影响 header:validate_environment 检测到消息里设了 cache_control,就带上对应的 beta header (litellm/llms/anthropic/common_utils.py:658,该方法一口气检测 computer use / PDF / MCP / web search 等十余种特性来拼 anthropic-beta)。

④ 最后拼成请求体:

data = {
"model": model,
"messages": anthropic_messages,
**optional_params,
}

—— :1940-1944。注意 **optional_params 是整体展开——这也解释了为什么内部协调状态(比如工具名反查表) 必须放 litellm_params 而不是 optional_params,否则会被当成非法请求字段发出去(:1403-1413 的注释与 :1855-1856 的落盘)。

6.4 响应翻回来

transform_response(:2464)做三件事:先 logging_obj.post_call 记原始响应(横切层的钩子,见 04 章), 再 raw_response.json()(解析失败就抛 AnthropicError),最后交给 transform_parsed_response(:2339) 把 content blocks 拼成 OpenAI 的 choices。JSON 模式下模型返回的其实是一次工具调用, _resolve_json_mode_non_streaming(:1982)负责把它还原成普通文本消息——这是 6.2 里那个"假装工具调用"技巧的回程


7. 零代码接入:providers.json

对纯 OpenAI 兼容的供应商(改个 base_url 就能用),连 Python 类都不用写。 litellm/llms/openai_like/providers.json 里一条 JSON 就是一个供应商——当前仓库有 26 条:

"publicai": {
"base_url": "https://api.publicai.co/v1",
"api_key_env": "PUBLICAI_API_KEY",
"base_class": "openai_gpt",
"param_mappings": { "max_completion_tokens": "max_tokens" },
"special_handling": { "convert_content_list_to_string": true }
}

JSONProviderRegistry(litellm/llms/openai_like/json_loader.py:27)在 import 时一次性读进来(:39), create_config_class(litellm/llms/openai_like/dynamic_config.py:20)在运行时动态生成一个 BaseConfig 子类:

  • param_mappings 变成 map_openai_params 里的改名表(dynamic_config.py:136-141);
  • constraints 变成 temperature 的钳位规则,包括 "n>1 时 temperature 有下限" 这种怪癖(:143-157);
  • special_handling.convert_content_list_to_string 变成消息内容降级(:46);
  • 模型不支持 function calling 时,自动从白名单里摘掉 tools/tool_choice 等五个参数(:99-114)。

这是整条翻译层设计最直接的回报:契约足够窄,以至于常见情况可以用数据描述,而不是代码。

8. 另一端:完全自定义

如果你的后端连"HTTP + JSON"都不是,BaseConfig 就不合适了。这时走 CustomLLM (litellm/llms/custom_llm.py:41):你自己实现 completion / acompletion / streaming / astreaming (还有 embedding、image 等),注册进 litellm.custom_provider_map(litellm/__init__.py:1448), 主流程按 provider 名找到你的 handler 直接调(litellm/main.py:4759), 同步/异步/流式四选一由 custom_chat_llm_router(custom_llm.py:218)决定。

代价是:你绕开了整个翻译层,参数映射、响应还原、错误分类全得自己负责。

三条接入路径的取舍:

路径写多少适合谁
providers.json 一条 JSON0 行 Python纯 OpenAI 兼容、最多改几个参数名
BaseConfig 子类6 个必答题 + 按需钩子协议不同但仍是 HTTP+JSON(Anthropic、Bedrock、Gemini…)
CustomLLM整套 handler非 HTTP、本地推理、或有特殊会话模型

9. 巧妙之处(可以偷的技术)

  1. "能不能收"和"怎么翻"拆成两个方法。 get_supported_openai_params 只回答白名单问题, 于是它能被独立复用——代理网关的 /model/infodrop_params 判定、base_model 能力并集 都只需要这一个方法,不必真的跑一遍翻译(litellm_core_utils/get_supported_openai_params.py:53-58)。

  2. "默认值 vs 显式传入"的区分是整个校验的地基。 只校验 non_default_params, 意味着"用户没传的参数永远不会引发不兼容报错"——这是"换个 model 字符串就能跑"的前提(utils.py:3737-3753)。

  3. 不支持的能力用"降级模拟"而不是直接拒绝。 response_format 没有就用工具调用假装(base_llm/chat/transformation.py:183), 流式不支持就 fake stream(:119),function calling 不支持就写进 prompt(utils.py:3915-3924)。

  4. 多路径共用的逻辑压到唯一咽喉。 Anthropic 的工具名清洗放在 transform_request 而非 map_openai_params, 一处改动同时覆盖直连 / Bedrock / Vertex / Azure 四条路径(anthropic/chat/transformation.py:1830-1849 的注释写得很清楚)。

  5. 契约窄到能用 JSON 描述。 26 个供应商零 Python 代码接入(json_loader.py:27 + dynamic_config.py:20)。

  6. 错误消息即文档。 UnsupportedParamsError 直接把 drop_params / 代理 YAML 写法 / allowed_openai_params 三种解法印在报错里(utils.py:4039)。


10. 边界与局限(诚实版)

  • get_optional_params 是个巨型函数。 utils.py:3934:4507,近 600 行 if/elif。 通用通道(:4495)已经存在,但历史分支没有清理,新读者很容易以为"加供应商必须改这里"。
  • 四套平行实现有漂移。 chat / embedding / image / transcription 各有一份 _check_valid_arg, 只有 chat 与 embedding 共享了预处理;transcription 的报错文案是写死的,和实际参数无关(utils.py:3048)。
  • BaseConfig 越来越胖。 6 个抽象方法之外还有近 20 个可选钩子,横跨鉴权、URL、流式、成本 (calculate_additional_costs,:428)。"配置类"这个名字已经名不副实,它其实是供应商适配器。
  • 翻译是有损的。 参数名能对上不代表语义一致(如 temperature 的取值范围、stop 的条数上限), 这类差异只能靠各 config 自行钳位(见 dynamic_config.py:143-157 的 temperature 钳位)。
  • transform_parsed_response_dict(:346)的存在本身说明有旁路。 走 OpenAI SDK 的供应商不经过 transform_response,统一模型在那条路径上是打了补丁的(litellm/llms/openai/openai.py:757:893)。

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

主题文件路径符号名
契约本体litellm/llms/base_llm/chat/transformation.pyBaseConfig
供应商异常基类litellm/llms/base_llm/chat/transformation.pyBaseLLMException
JSON schema → 假工具litellm/llms/base_llm/chat/transformation.py_add_response_format_to_tools
参数过滤主入口litellm/utils.pyget_optional_params
非默认参数筛选litellm/utils.pyPreProcessNonDefaultParams.base_pre_process_non_default_params
点名丢参数litellm/utils.py_should_drop_param
强制放行参数litellm/utils.py_apply_openai_param_overrides
私货参数处理litellm/utils.pyadd_provider_specific_params_to_optional_params
config 选型litellm/utils.pyProviderConfigManager.get_provider_chat_config
白名单查询litellm/litellm_core_utils/get_supported_openai_params.pyget_supported_openai_params
不支持参数异常litellm/exceptions.pyUnsupportedParamsError
OpenAI 默认参数表litellm/constants.pyDEFAULT_CHAT_COMPLETION_PARAM_VALUES
样例:Anthropic 翻译litellm/llms/anthropic/chat/transformation.pyAnthropicConfig
样例:system 抽取litellm/llms/anthropic/chat/transformation.pytranslate_system_message
样例:tools 重塑litellm/llms/anthropic/chat/transformation.py_map_tool_helper
样例:鉴权与 beta headerlitellm/llms/anthropic/common_utils.pyAnthropicModelInfo.validate_environment
声明式供应商注册表litellm/llms/openai_like/json_loader.pyJSONProviderRegistry
声明式 config 生成litellm/llms/openai_like/dynamic_config.pycreate_config_class
用户自定义后端litellm/llms/custom_llm.pyCustomLLM / custom_chat_llm_router
契约的调用现场litellm/llms/custom_httpx/llm_http_handler.pyBaseLLMHTTPHandler.completion

接着读: 03-http-and-streaming(这些翻译好的请求怎么真正发出去、流式怎么拼) · 01-request-lifecycle(整条主线) · index(全书地图)