首页
/ LiteLLM Rust 核心扩展指南:如何为 litellm-rust 新增一个 Provider 或 Route

LiteLLM Rust 核心扩展指南:如何为 litellm-rust 新增一个 Provider 或 Route

2026-09-05 13:53:33作者:庞眉杨Will

在 LiteLLM 的 Rust 核心(litellm-rust/)中新增一个 provider 或 route 有一套固定的工程范式:每个 route 的完整实现都收敛在 crates/core/src/<route>/ 目录下,宿主(axum 网关 ai-gateway 或 Python 桥 python-bridge)只负责调用该 route 的唯一入口函数。本文基于仓库中的 ADDING_A_PROVIDER.mdCLAUDE.md,结合源码中的参考实现,讲清楚一个 route 的四个组成部分、provider 转换契约的接口细节,以及新增代码必须遵守的编码规范与检查命令,读完你可以独立完成"Rust 侧新 provider 接入 + Python 薄分发"的全流程。

整体架构:为什么一切都在 litellm-core

CLAUDE.md 的 Crate 划分看,整个 Rust 工作区由四个 crate 组成:

  • litellm-core就是 Rust 版的 LiteLLM SDK,负责真正发起 LLM 调用。litellm.messages() 的 Rust 等价物是 litellm_core::messages::messages(request).await —— 你传入请求,它完成 provider 解析、请求/响应转换、HTTP 调用,返回一个类型化的非流式响应;
  • litellm-ai-gateway:架在 core 前面的 HTTP/WebSocket 服务器,其 axum 路由只读取 HTTP 请求、挑选部署(deployment),然后调用 core 的入口函数;
  • litellm-python-bridge:把 core 暴露给 Python SDK,负责把 Python 对象 marshal 成 Rust 结构体后调用同一个入口函数;
  • litellm-python-interop:存放 Python 面向的 PyO3 原语,供其他 Rust 代码共享。

一个关键边界规则:crate 是"层"或"共享基座",不是"路由"——新增功能时应该加模块,而不是新建 crate。route 级 Rust 结构与 Python 侧的职责一一对应:core/src/<route>/ 端到端拥有该 route,包括以 route 命名的公开入口函数(mod.rs)、请求/响应类型(types.rs)、provider 模板 trait(transformation.rs)、provider/鉴权/URL 解析(prepare.rs)、共享 HTTP 客户端(client.rs)、真正执行调用的 handler(handler.rs)。参考实现是 core/src/messages

四步法:新增一个 provider/route 的完整流程

ADDING_A_PROVIDER.md 把新增工作归纳为四个部分,下面逐一展开,并给出源码级依据。

第 1 步:入口函数(Entrypoint)

每个 route 的 mod.rs 中定义 pub async fn <route>(request) -> CoreResult<Response>,它是 Rust 版的 litellm.<route>();如果该 route 支持流式,再提供一个 <route>_stream 变体。这是宿主唯一接触的东西——ai-gatewaypython-bridge 都只调用 core 入口函数。

以参考实现 messages/mod.rs 为例:

pub async fn messages(request: MessagesRequest<'_>) -> Result<AnthropicMessagesResponse, Error> {
    execute_messages_provider_call(request).await
}

pub async fn messages_stream(request: MessagesRequest<'_>) -> Result<reqwest::Response, Error> {
    execute_messages_provider_stream(request).await
}

非流式入口返回类型化响应;流式入口则把上游的原始 reqwest::Response 直接交回给宿主,由宿主负责把它拼接(splice)给自己的调用方。宿主因此不持有任何 provider 逻辑,流式也保持同样的形状。

Python 桥的实际调用形态可见 python-bridge/src/routes/messages.rs:它先通过 bridge_route! 宏声明 messages/amessages 两个绑定、校验必填参数(modelbody)与可选参数(api_keyapi_basecustom_llm_providerextra_headerstimeout_seconds),然后把字段组装成 MessagesRequest 并调用 run_messages(...)(即 litellm_core::messages::messages)。

第 2 步:转换契约(Transform Contract)

<route>/transformation.rs 定义一个 …ProviderConfig trait,它描述"URL 构建 + 请求/响应转换"的契约,相关类型放在 types.rs。以 messages/transformation.rs 中的 AnthropicMessagesProviderConfig 为例,它的必备方法与可选覆写包括:

方法 职责 默认行为
complete_url(api_base, model, env_lookup) 构建最终请求 URL 无默认,必须实现
resolve_api_key(api_key, env_lookup) 解析 API key(参数优先,其次环境变量) 无默认,必须实现
auth_strategy() 决定鉴权头 MessagesAuthStrategy::Header("x-api-key")
accepts_bearer_auth() 是否接受 Bearer 鉴权 false
default_headers() 附加的默认请求头 anthropic-version + content-type
transform_request(request) 请求体转换 原样透传
transform_response(model, response) 响应转换 原样透传

其中 env_lookup 是一个闭包参数(&dyn Fn(&str) -> Option<String>),由 prepare.rs 注入 |key| std::env::var(key).ok()。这正对应 CLAUDE.md 中的边界规则:core 中允许读环境变量仅限于 route 的 prepare.rs 内的凭证兜底(即 env_lookup 闭包),镜像的是 Python SDK 在调用方没传 key 时回退环境变量的行为;其余一切配置都由宿主解析后传入。

第 3 步:Provider 配置实现

crates/core/src/providers/<provider>/<route>/transformation.rs 中把该 trait 实现为一个 const <PROVIDER>_<ROUTE>_CONFIG,目录结构镜像 Python 侧的 provider 树(litellm/llms/<provider>/<route>/),并必须补齐 parity 单元测试

providers/anthropic/messages/transformation.rs 是标准范本:

const ANTHROPIC_API_KEY_ENV: &str = "ANTHROPIC_API_KEY";
const ANTHROPIC_API_BASE_ENV: &str = "ANTHROPIC_API_BASE";
const DEFAULT_ANTHROPIC_API_BASE: &str = "https://api.anthropic.com";
const MESSAGES_PATH_SUFFIX: &str = "/v1/messages";

pub struct AnthropicMessagesConfig;
pub const ANTHROPIC_MESSAGES_CONFIG: AnthropicMessagesConfig = AnthropicMessagesConfig;

URL 构建逻辑(complete_anthropic_url)展示了几个值得注意的细节:

  • 参数 api_base 优先,其次环境变量 ANTHROPIC_API_BASE,最后落到默认 https://api.anthropic.com
  • 空白值视为不存在non_empty 辅助函数会先 trim 再过滤空串,这对应 CLAUDE.md 的生产标准"把空或纯空白凭证、URL、配置值视为缺失";
  • 若 base 已经以 /v1/messages 结尾则原样返回,否则追加后缀,并先 trim_end_matches('/') 去掉尾部斜杠。

API key 解析遵循"参数 → 环境变量 → 报错"三级顺序,缺失时返回类型化的 Error::Auth 而非 panic。同文件内还附带了一组单元测试(默认 URL、自定义 base 追加后缀、完整端点不重复追加、env 兜底、key 优先级),这正是文档所要求的"add parity unit tests"的落地形式。

第 4 步:Prepare + Handler

prepare.rs 负责"决定调用什么、怎么认证",handler.rs 负责"真正发请求"。以 messages 路由为例,调用链是:

prepare 阶段messages/prepare.rs):

  1. get_custom_llm_provider(model, custom_llm_provider) 解析 provider/model,解析失败返回 Error::InvalidProvider
  2. 通过工厂函数拿到对应 provider 的 …ProviderConfig
  3. validate_environment 构建上游请求头:若调用方已通过 extra_headers 自带鉴权头(命中 auth_strategy 的头名,或在允许 Bearer 时自带 Authorization: Bearer),则跳过 key 解析;否则调用 resolve_api_key 并按策略生成 authorization: Bearer <key> 或自定义头(如 x-api-key);最后按 default_headers() 补齐缺失的默认头;
  4. request.body 反序列化为类型化请求,经 transform_request 转换后再序列化;
  5. config.complete_url 生成最终 URL;
  6. 打包为 ProviderMessagesRequest { provider, model, config, url, body, upstream_headers, timeout } 交给 handler。

其中第 2 步的"工厂函数"是新增 provider 时的关键挂接点——messages/common_utils.rs

pub(super) fn messages_provider_config(
    provider: &str,
) -> Option<&'static dyn AnthropicMessagesProviderConfig> {
    match provider {
        "anthropic" => Some(&ANTHROPIC_MESSAGES_CONFIG),
        "azure_ai" => Some(&AZURE_ANTHROPIC_MESSAGES_CONFIG),
        _ => None,
    }
}

新增一个支持 messages 路由的 provider,本质上就是在这里加一个 match 分支并指向你实现的那个 const <PROVIDER>_MESSAGES_CONFIG

handler 阶段messages/handler.rs):

  1. 调用 prepare_provider_request
  2. 共享复用的 http_client() 发起 POST(遵守"复用 HTTP 客户端、禁止每请求新建客户端"的网络 I/O 规则),逐条附加上游请求头,并在存在 timeout 时设置请求级超时;
  3. 非 2xx 状态码返回 Error::Http { status, body },且错误 body 经 truncate_error_body 截断/脱敏后才越过边界——对应"错误信息必须有用但最小化数据"的标准;
  4. 成功时把响应 JSON 反序列化为 AnthropicMessagesResponse,再经 transform_response 归一化后返回。

流式变体 execute_messages_provider_stream 在成功时直接把原始 reqwest::Response 返回给宿主;非流式与流式的差异由此清晰:core 始终拥有 provider 调用,宿主只决定如何消费响应。

编码规范:先找基座,不要复制粘贴

ADDING_A_PROVIDER.md 的 "Coding standards" 部分(与 CLAUDE.md 的 Provider Coding Standards 同源)给出了三条硬性纪律:

  1. 写新逻辑之前先找可继承的基座。当变更是"同一行为再支持一个 provider/endpoint/integration"时,代码库几乎总已存在共享抽象——例如 Python 侧 litellm/llms/base_llm/ 的 provider BaseConfig 转换类、litellm_core_utils/ 的共享辅助函数、类型化请求/响应模型、工厂函数。先用搜索找到它,然后通过继承或组合添加新变体,只覆写真正不同的部分(模型名、参数映射、鉴权)。
  2. 绝不复制现有实现再原地修改,也绝不手写一个基座已提供逻辑的平行版本。如果你发现自己写下"第二个副本",停下来抽取基座:把共享形态放在一处,让两个调用点都变成它的薄变体。好抽象的检验标准是:添加下一个 provider 只需要几行声明式代码,而不是一整个重复流程的新文件。
  3. 只在行为真正不同时偏离基座,并在 PR 中明确说明偏离原因

此外,CLAUDE.md 补充了若干与新增 provider 直接相关的生产标准:

  • parity 必须靠测试证明:每个 provider 转换都要有单元测试,覆盖支持参数过滤、请求体形状、响应归一化、缺失/null 字段、错误输入;
  • 通过 Python 暴露 Rust 路径时,需补 Python 测试证明 disabled / enabled / bridge 不可用回退三种行为;
  • 对用户/provider 输入避免 panic,返回类型化错误,由宿主映射为 Python 异常或 HTTP 响应;
  • 常量(魔法数、固定字符串)统一放 crate 级 constants.rs(如 litellm-rust/crates/core/src/constants.rs),是 Python litellm/constants.py 的 Rust 镜像;
  • 关于 Rust-only 新 provider:允许不写 Python 参照实现,此时 Python 接口是无回退的薄分发(在 litellm/llms/<provider>/<route>/ 下建薄 dispatch 类,绝不把 provider 分发塞进 litellm/main.py),并在 PR 中显式声明 rust-only 选择。

收尾:注册模块与跑检查

新增文件后还有两件事(见 ADDING_A_PROVIDER.md 末尾的 "Calling" 小节):

  1. lib.rs / mod.rs 中注册新模块。core 的模块清单在 core/src/lib.rs,例如 pub mod messages;pub mod providers; 等,新增 route 或 provider 模块后需在此(或对应的子 mod.rs)声明,使其成为 litellm_core 公开 API 的一部分。
  2. 永远不要让宿主直连 provider。文档明确写着:宿主调用 core 入口——Python 桥和 ai-gateway 路由服务都调用 litellm_core::messages::messages——绝不要往 ai-gateway 里加 provider handler

然后运行 CLAUDE.md "Checks" 一节列出的命令(与 CI 对 litellm-rust/ 下变更执行的检查一致):

cd litellm-rust
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo clippy -p litellm-core --all-targets --features bedrock-auth -- -D warnings
# ai-gateway 二进制与服务端代码在 `server` feature 之后
cargo clippy -p litellm-ai-gateway --all-targets --all-features -- -D warnings
cargo test --workspace
cargo test -p litellm-core --features bedrock-auth
# `auth`、`routes`、`state`、`realtime` 测试只存在于 `server` feature 下
cargo test -p litellm-ai-gateway --features server

格式上,rustfmt 默认风格即官方 Rust 风格指南,CI 对每个 PR 跑 cargo fmt --check,不要自行另配 rustfmt.toml

小结

为 litellm-rust 接入新 provider 的路径可以概括为一句话:入口函数收敛在 core/src/<route>/mod.rs,转换契约定义在同目录的 transformation.rs,provider 差异实现为 providers/<provider>/<route>/transformation.rs 里的一个 const 配置并注册进工厂函数,prepare 负责解析与鉴权、handler 负责共享客户端调用,宿主永远只调入口函数。配合"先找基座、测试证明 parity、检查命令全绿"的三条纪律,新增一个 provider 的增量成本被压缩为几行声明式代码与一组单元测试。

登录后查看全文
热门项目推荐
相关项目推荐