LiteLLM Rust 核心 crate 开发规范:litellm-core 的职责边界、类型化契约与 Python 兼容性规则
本文围绕 litellm-rust/crates/core/CLAUDE.md 这份开发规范展开,讲解 LiteLLM Rust 实现中 litellm-core 这个 crate 的四大核心设计约束:职责边界(哪些能力属于 SDK、哪些必须留在宿主层)、强制类型化的契约规则、以 src/messages 为参照的按路由(route)组织代码的结构约定,以及保证与 Python SDK 行为兼容(parity)的测试与序列化规则。读完后你能理解 LiteLLM 为什么能把“发起一次 LLM 调用”这件事干净地封装在 Rust 里,以及新增一条路由或一个 provider 转换时应当遵循的工程标准。
一、core 是什么:每个顶层 LiteLLM 调用都有一个 Rust 入口点
规范的开头给出了 core 的一句话定位:core 是 Rust 版的 LiteLLM SDK,它负责真正发起 LLM 调用。每一个顶层 LiteLLM 调用在这里都有一个以路由名命名的公开入口点,例如 messages::messages() 就是 litellm.messages() 的 Rust 等价物,调用它返回一个类型化的非流式响应。
这一点可以直接在源码中得到印证。core/src/lib.rs 暴露了 messages、chat_completions、ocr、audio_transcription、realtime、responses、router 等模块;而 core/src/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
}
messages 是非流式入口,返回类型化的 AnthropicMessagesResponse;messages_stream 则把上游的 reqwest::Response 原样交还给宿主,由宿主把事件流“拼接”给自己的调用方——这与规范中“<route>_stream 变体”的约定逐字对应。litellm-rust/README.md 也给出了最简调用示例:
let response = litellm_core::messages::messages(MessagesRequest {
model: "claude-sonnet-4-5",
body,
api_key: Some(key),
..
})
.await?;
需要明确的适用前提:Python 端目前仍持有配置、重试、路由策略、日志、回调与支出跟踪,Rust 路径在各条路径获得 parity(一致性)覆盖和生产验证前默认关闭(见根级 litellm-rust/CLAUDE.md 的 "Production Bar" 章节)。因此 core 的定位是"调用层 SDK",而非一个独立的完整服务。
职责边界:Allowed 与 Not allowed 两张清单
规范用两张清单划定了 crate 边界。这是理解整个 Rust 重构分层的钥匙。
core 内允许的(Allowed):
- 每条路由的公开入口点,以及该路由支持流式时的
<route>_stream变体; - provider 解析、认证头构造、URL 构建,以及 provider 的 HTTP 调用(使用共享复用的 client,设置 connect 与 request 两级超时);
- 共享的请求/响应结构体;
- 消息稳定且不泄露敏感信息的类型化错误;
- 确定性的校验辅助函数;
- 有意镜像 Python 输出形状的序列化辅助;
- 与 Python 侧 base config 职责对应的路由模板,例如
messages::transformation::AnthropicMessagesProviderConfig。
core 内禁止的(Not allowed):
- 提供 HTTP 服务:axum 路由、extractors 及其他传输层关注点;
- 文件系统、数据库或缓存访问;
- 读取配置文件或 rollout 状态——这些由宿主解析后传入。环境变量读取仅限于路由
prepare.rs中的凭证兜底; - 日志回调、tracing spans、支出写入或客户回调;
- 本应放在
providers里的 provider 专属分支逻辑; - 对用户或 provider 可控输入执行 panic。
从源码结构看,这条边界是真实执行的:core/src 下没有任何 axum 依赖痕迹,服务化代码集中在 litellm-rust/crates/ai-gateway 中。workspace 共四个 crate,分工如下(引自 litellm-rust/README.md):
| Crate | 角色 |
|---|---|
litellm-core |
SDK 本体:每路由入口点、类型、provider 转换(providers/ 下各模块)、provider 解析、认证、provider HTTP 调用、router |
litellm-ai-gateway |
axum 服务器(在 server feature 之后)与 WebSocket 宿主,负责把 HTTP/WS 翻译为 core 入口点,不含 provider handler |
litellm-python-interop |
领域无关的 PyO3 基础:GIL 处理与 Python/Serde 类型转换 |
litellm-python-bridge |
暴露 LiteLLM Rust API 给 Python SDK 的 PyO3 cdylib,负责 API 注册、领域接线与 Python 异常映射 |
依赖方向是无环的:python-bridge 依赖领域层与 python-interop,而 python-interop 不依赖任何 LiteLLM 领域 crate。规范中"禁止提供 HTTP 服务"这一条,本质上就是保证 core 既能被 ai-gateway 调用,也能被 python-bridge 调用,而宿主各自处理传输层差异。
二、类型化契约(Typed Contracts):core 规则
规范中最核心的一条硬性规则是**"Typed Contracts (core rule)"**:trait 与函数边界必须是强类型化的,禁止字符串驱动的 JSON——即不允许 &str / String / Vec<String> / 裸的 serde_json::Value 作为转换的输入或输出。
规范给出了明确的落位要求:在宿主边界(host edge)把 wire bytes 解析为类型化的 struct/enum;core 与 providers 只操作这些类型,例如 RealtimeEvent、RealtimeTransformResult、OcrRequestData。所谓 type 风格的判别字段(discriminator)也必须是结构体上的类型化字段,而不是在 API 中传递的原始字符串。
为什么这样要求?因为 provider 转换是 core 里跨 crate 边界的"合同":如果转换输入输出是裸 JSON,每个 provider 实现都可以随意增删字段,Python 兼容性就无从用编译器和测试来保证;而一旦输入输出是具体类型,字段遗漏、多余、命名错误都会在编译期或单元测试中暴露。
可以在 core/src/messages/transformation.rs 中看到这条规则的落地形态——路由模板 trait AnthropicMessagesProviderConfig 的所有方法都操作类型化结构体:
pub trait AnthropicMessagesProviderConfig: Sync {
fn complete_url(
&self,
api_base: Option<&str>,
model: &str,
env_lookup: &dyn Fn(&str) -> Option<String>,
) -> Result<String, Error>;
fn resolve_api_key(
&self,
api_key: Option<&str>,
env_lookup: &dyn Fn(&str) -> Option<String>,
) -> Result<String, Error>;
fn auth_strategy(&self) -> MessagesAuthStrategy {
MessagesAuthStrategy::Header("x-api-key")
}
// ...
fn transform_request(&self, request: AnthropicMessagesRequest)
-> Result<AnthropicMessagesRequest, Error>;
fn transform_response(&self, _model: &str, response: AnthropicMessagesResponse)
-> Result<AnthropicMessagesResponse, Error>;
}
注意 env_lookup: &dyn Fn(&str) -> Option<String> 这个闭包参数——它正是规范中"环境变量读取仅限于路由 prepare.rs 的凭证兜底"的实现载体。在 core/src/messages/prepare.rs 中可以看到:
let env_lookup = |key: &str| std::env::var(key).ok();
prepare.rs 把环境变量读取封装成闭包传给 provider 配置,core 内部其他任何位置都不直接读环境,从而把"配置解析属于宿主"这条边界变成了可执行的代码模式。
三、目录结构:路由名直接放在 src/ 下,src/messages 是参照形态
规范的结构章节规定:在 src/ 下直接使用路由名建模块——messages、ocr、未来的 chat_completions、embeddings 等,对应顶层 LiteLLM 调用;禁止发明 engine 这类宽泛的名字来承载路由契约。
src/messages 被指定为路由模块的参照形态(reference shape),文档给出的标准文件构成如下:
mod.rs pub async fn messages(..) (+ messages_stream)
types.rs request/response types
transformation.rs the provider template trait
prepare.rs provider resolution, auth headers, URL
handler.rs the provider call
client.rs the shared reqwest client
对照 litellm-rust/crates/core/src/messages/ 的实际文件,该结构完整成立,且每个文件的职责与注释一一对应:mod.rs 定义两个入口点,types.rs 定义 MessagesRequest / AnthropicMessagesResponse 等共享类型,transformation.rs 是 provider 模板 trait,prepare.rs 负责 provider 解析、认证头与 URL,handler.rs 执行 provider 调用,client.rs 是共享的 reqwest client。此外还有 common_utils.rs(headers 辅助、provider 配置查找)与 tests.rs。
prepare.rs 内部的工作流很好地展示了这套结构如何协作(见 prepare.rs 的 prepare_provider_request):
- 通过
get_custom_llm_provider(request.model, request.custom_llm_provider)解析出 provider 与 model; - 用
messages_provider_config(provider)查找到该 provider 的AnthropicMessagesProviderConfig实现; validate_environment按auth_strategy构造认证头(Bearer或自定义 header,默认x-api-key),再补齐default_headers()(如anthropic-version: 2023-06-01);- 把 wire JSON 解析为类型化的
AnthropicMessagesRequest,调用config.transform_request,再序列化回 body; - 调用
config.complete_url得到最终 URL,组装出ProviderMessagesRequest交给handler.rs发起 HTTP 调用。
provider 专属的转换则放在平行的目录树里:core/src/providers/<provider>/<route>/transformation.rs。当前已实现的 provider 包括 anthropic、azure_ai、bedrock、openai、mistral、vertex_ai、reducto 等,覆盖了 messages、chat_completions、ocr、responses、realtime、audio_transcription 等路由。根级 litellm-rust/CLAUDE.md 进一步强调这条目录约定是刻意镜像 Python 的 provider 树(litellm/llms/<provider>/<route>/),并要求"新增能力时先找已有的 base 去扩展,而不是复制粘贴改"——例如 Python 侧对应的抽象是 litellm/llms/base_llm/ 下的 BaseConfig 转换类。
四、Parity Rules:用测试钉死与 Python 的输出形状
规范的最后一部分给出三条 Python 兼容性(parity)规则,这是整个"Rust 逐步接管 LiteLLM 调用"计划能安全推进的前提:
- 每个被 provider 转换使用的共享类型,都需要序列化形状(serialization shape)的单元测试。 也就是说,测试断言的不只是"字段值对不对",而是"序列化出来的 JSON 结构是否与 Python 端一致"。
- 如果 Python 兼容要求某个字段永远输出
null而不是省略,必须在代码中注释说明这一点,并用测试钉死它。 这是非常具体的一条:Rust 的 serde 天然倾向于跳过None字段,而 Python 端可能总是发出"field": null,这种差异会直接破坏下游消费方。规范要求把每一个这样的"刻意保留"变成显式注释 + 固定测试。 - 错误枚举应保留足够的细节,使 Python/HTTP 宿主能够一致地映射错误,但不暴露文档内容或上游响应 body。 这与根级规范中"OCR 会处理常含个人数据的文档,不得记录文档内容、base64 载荷、provider 响应体或密钥;错误信息必须有用但最小化数据"的要求一致。
从源码结构看,这些规则配套了真实的工程设施:core 各路由下都有 tests.rs(如 messages/tests.rs、chat_completions/tests.rs);workspace 级还有 tests/workspace_crate_allowlist.rs 这类跨 crate 的约束测试。而验证 parity 的标准动作是根级 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
cargo clippy -p litellm-ai-gateway --all-targets --all-features -- -D warnings
cargo test --workspace
cargo test -p litellm-core --features bedrock-auth
cargo test -p litellm-ai-gateway --features server
另有一条流程性规则值得注意:当 Rust 路径通过 Python 暴露时,必须补充 Python parity 测试,对比既有 Python 输出与 Rust 支撑下的输出(包括禁用、启用、bridge 不可用回退三种行为)。这与根级规范"Python 持有 rollout 状态与回退,Rust 路径在 parity 测试证明等价之前必须默认关闭"的总方针配套。
五、总结:这份规范回答了什么问题
core/CLAUDE.md 虽然篇幅不长,但用四组规则完整地定义了 litellm-core 的设计契约:
- 职责清单回答了"什么代码能进 core":只做 provider 调用全链路(解析、认证、URL、HTTP、转换、共享类型、稳定错误),不做 HTTP 服务、持久化、配置读取、日志与回调;
- 类型化契约回答了"crate 边界怎么传数据":wire bytes 在宿主边界解析为类型,core/providers 只见类型,
type判别字段必须是结构体字段; - 结构约定回答了"新路由怎么落地":路由名直接做模块名,
src/messages六文件形态(mod/types/transformation/prepare/handler/client)是模板,provider 差异放providers/<provider>/<route>/transformation.rs; - Parity 规则回答了"如何证明与 Python 等价":序列化形状测试、
null字段的显式注释 + 测试钉死、错误细节保留但数据最小化。
对于要在该仓库中新增路由或 provider 转换的开发者,这份文档与 litellm-rust/AGENTS.md、litellm-rust/ADDING_A_PROVIDER.md、根级 CLAUDE.md 的 Checks 清单共同构成了可执行的工程标准;对只是想理解 LiteLLM "Rust core + Python SDK" 混合架构的读者,src/messages 这一个模块的源码(入口 → prepare → transformation → handler → client)就是一份完整的可读范例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00