LiteLLM Rust AI Gateway routes 模块:axum 路由模板、service 分层与四条不变式
LiteLLM Rust AI Gateway routes 模块axum 路由模板、service 分层与四条不变式【免费下载链接】litellmThe fastest, litest AI Gateway. Rust core with Python SDK. Call 100 LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]项目地址: https://gitcode.com/GitHub_Trending/li/litellm在 LiteLLM 的 Rust 网关litellm-rust/crates/ai-gateway中src/routes/目录用一套统一的路由模板组织了 health、gil、messages、realtime、responses 等全部 HTTP/WS 入口。本文基于 routes/AGENTS.md 展开完整继承其中的模板约定、分层规则与不变式并结合 mod.rs、health.rs、messages/mod.rs、realtime/mod.rs 等源码帮助你在动手添加新路由前掌握这套可预测的目录布局与分层边界以及每条规则背后的工程理由。模板核心契约每个路由模块只暴露一个router()AGENTS.md 给出的总规则只有一句话每个路由模块都暴露pub fn router() - RouterAppStateroutes/mod.rs中的app函数把所有路由模块合并merge到一起并把应用状态一次性注入。新增一条路由的操作因此被压缩为两步创建模块文件然后在app里加一行.merge(name::router())。mod.rs 的app函数就是这条契约的完整体现/// Assemble the application router by merging every route modules router(). pub fn app(state: AppState) - Router { Router::new() .merge(health::router()) .merge(gil::router()) .merge(messages::router()) .merge(realtime::router()) .merge(responses::router()) .with_state(state) }注意它的职责边界mod.rs只做 merge 和.with_state(state)不在这里注册任何具体路径。文件头部的文档注释也明确复述了模板简单路由是单文件health.rs、gil.rs非平凡路由是文件夹realtime/包含handler入口service逻辑transport适配器三类角色。默认形态单文件路由文档的默认建议是一条路由就是一个文件文件内包含router()与其 handlerhandler 保持私有。这是常态——不要提前拆分除非真的难以阅读。AGENTS.md 给出的最小骨架为pub fn router() - RouterAppState { Router::new().route(PATH, get(handle)) } async fn handle(...) - impl IntoResponse { ... }仓库中有两个活例子。health.rs两个探针一个文件health.rs 是模板的最简形态pub fn router() - RouterAppState { Router::new() .route(/health/liveness, get(liveness)) .route(/health/readiness, get(readiness)) } /// The process is up. async fn liveness() - StatusCode { StatusCode::OK } /// The server is ready to accept traffic. async fn readiness() - StatusCode { StatusCode::OK }路径/health/liveness与/health/readiness完全由本模块在自己的router()内声明符合路由拥有自己的路径这一不变式。gil.rs返回 JSON 的单文件路由gil.rs 同样是单文件模板只是 handler 返回结构化 JSONpub fn router() - RouterAppState { Router::new().route(/health/gil, get(status)) } #[derive(Debug, Serialize)] struct GilStatusResponse { gil_acquired_last_30s: bool, total_acquisitions: u64, seconds_since_last: Optionu64, } async fn status() - JsonGilStatusResponse { let snapshot gil::snapshot(); // ... 组装响应 }该路由用于轮询确认 Python 只在加载阶段被触碰即 GIL 是否只在启动期获取返回最近 30 秒是否获取过 GIL、总获取次数与距上次获取的秒数。这个例子说明即使返回体稍复杂只要没有值得脱离 axum 单独测试的业务逻辑就仍应保持单文件。何时拆分出service真实逻辑进纯 Rust 层AGENTS.md 给出的第二条规则当一条路由存在值得在没有 axum 的情况下进行测试的业务逻辑时把它放进同级的service一个文件或路由继续膨胀后变成一个文件夹。分层职责被明确划分路由文件保持为axum 表面axum surfacerouter() handler 必要的 socket/SSE 适配器service是纯 Rust不含任何 axum 类型职责只有两件事——选择部署deployment并调用core的路由入口例如messages/service.rs调用litellm_core::messages::messages严禁在service中构造 provider 请求、解析密钥或直接发起 provider 调用——这些都属于core层只有当单个service文件真的难以阅读时才进一步拆出transport、repo等子层。messagesHTTP 表面 纯逻辑 servicemessages/mod.rs 是POST /v1/messagesAnthropic Messages 格式的 axum 表面。handler 的形态精确对应模板async fn handle( _auth: RequireMasterKey, State(state): StateAppState, headers: HeaderMap, Json(body): JsonValue, ) - ResultResponse, MessagesRouteError { let extra_headers forwarded_headers(headers)?; match service::run(state.router, body, extra_headers).await.map_err(...)? { service::MessagesResponse::Json(body) Ok(Json(body).into_response()), service::MessagesResponse::Stream(upstream) stream_response(upstream), } }handler 只做三件事过滤转发头、调用service::run、把Json/Stream两种结果适配成Response流式分支stream_response负责从上游reqwest::Response复制 status、Content-Type、Cache-Control并直接转发字节流。它不含任何选哪个部署、调哪个 provider的逻辑。messages/service.rs 则完全是纯 Rust整个文件只依赖litellm_core、serde_json与reqwest类型没有任何 axum 导入。其run函数的流程可以概括为从 body 取出model缺失则报InvalidRequest用router.get_available_deployment(model)选择部署失败则Error::Routing把部署里的litellm_params.model形如anthropic/claude-xxx拆出上游模型名并回写 body组装MessagesRequest含api_key、api_base、custom_llm_provider、extra_headers若stream: true走messages_stream否则走messages——两者都是litellm_core::messages暴露的 core 入口。这正是 AGENTS.md 所述service的 job is to pick the deployment and call thecoreroute entrypoint的逐字实现。请求构造、provider 调用本身位于corecrate 的 messages 模块 及litellm-rust/crates/core/src/providers/下的各 provider 实现中网关 crate 不重复这些工作。realtime文件夹形态的旧例子AGENTS.md 把realtime/称为older example即路由膨胀后的文件夹形态realtime/ mod.rs # axum surface: router() handler the WS-events adapter service.rs # pure logic: select deployment call provider (no axum) — testable对照 realtime/mod.rsrouter()只注册GET /v1/realtimehandler 在升级 WebSocket之前先完成模型校验model缺失返回 400、无部署返回 404避免socket 打开又立刻关闭的糟糕体验然后通过ws.on_upgrade(...)把 socket 交给bridge。bridge就是文档所说的 socket/SSE adapter它把 axum 的WebSocket文本帧适配成 service 所需的类型化Stream/Sink让 axum 类型不出现在service里。realtime/service.rs 的函数签名要求In: StreamItem RealtimeEvent、Out: SinkRealtimeEvent与 axum 完全解耦。其文件头注释准确概括了它在分层中的位置The seam betweencore::router只做选择和io真正的 WebSocket I/O先用router.get_available_deployment(model)选择部署剥离openai/前缀得到裸模型名先尝试从RealtimePool取预热的上游连接握手已支付、session.created已缓冲命中则走realtime_warm热路径未命中或热连接失效时退回realtime冷路径原始拨号行为。注释特别强调连接池只在延迟上帮忙不在正确性上介入池被禁用时行为退化为原冷路径。四条不变式InvariantsAGENTS.md 的 Invariants 一节列出了必须始终满足的规则以下逐条结合源码说明。1. 认证是 extractor不是手动调用handler 需要认证时只需在参数列表里加上crate::auth::RequireMasterKey检查在 axum 的提取阶段自动执行任何路由都不得自行重新实现该检查。auth/mod.rs 实现了这个 extractorFromRequestPartsAppState未配置 master key 时返回500永久性配置错误而非临时故障token 缺失或不正确时返回401且比较使用constant-timesubtle::ConstantTimeEq。messages 的 handler 第一个参数就是_auth: RequireMasterKeyrealtime 的 handler 同样如此——两条路由都没有一行手写鉴权代码。该文件还定义了hash_token对 key 做 SHA-256 hex与 Python proxy 的litellm.proxy.utils.hash_token保持同构。注释给出了严格理由原始密钥绝不能出现在任何日志载荷或回调集成中花费日志与所有 callback 接收的是user_api_key_hashhash 化同时保证该值能对上LiteLLM_SpendLogs.api_key中存储的 hash使 Rust 网关的实时花费数据能与 LiteLLM 其余部分做 join。文件内的单测hash_token_matches_python_sha256_hexdigest用固定向量验证了这一点hash_token(sk-1234)必须等于 Pythonhashlib.sha256(sk-1234).hexdigest()的值。2. handler 不含业务逻辑service 不含 axum 类型这是对前面两节的浓缩messages/mod.rs的 handler 只适配请求/响应选择部署的逻辑全部在messages/service.rsrealtime/mod.rs的bridge负责把 axum 类型挡在门口service.rs只见Stream/Sink与 core 的错误类型。这条不变式同时是测试策略——service 可以脱离 axum 直接单测。3. 本 crate 内没有 provider handler变换transform、auth headers、provider HTTP 调用都住在core/src/route/litellm-rust/crates/core/src/下按chat_completions/、messages/、realtime/、audio_transcription/、ocr/等分目录provider 实现位于litellm-rust/crates/core/src/providers/。ai-gateway crate 只做入口路由、鉴权、部署选择、上游流转发与日志接缝。这意味着阅读网关代码时不会遇到到底是谁在拼 provider 请求体的分叉——答案统一在 core。4. 路由拥有自己的路径横切关注点走 Tower 层每条路由的路径只在自己的router()里声明如health.rs声明/health/liveness、realtime/mod.rs声明/v1/realtimemod.rs只做 merge。而日志、CORS、超时这类横切关注点应作为 Tower 层应用在mod.rs不要在各个 handler 中重复实现——这与handler 保持薄是同一设计目的的两面。新增一条路由的标准操作路径综合模板与不变式往网关里加一条新路由的完整动作是在src/routes/下创建模块单文件起步pub fn router() - RouterAppState { Router::new().route(/v1/your-path, get(handle)) // 或 post(...) } async fn handle(...) - impl IntoResponse { ... }若需要鉴权在 handler 参数中加crate::auth::RequireMasterKey不要手写检查若存在值得测试的业务逻辑把它移入同级service.rs保证 service 内不出现任何 axum 类型且只选部署 调 core 入口在 mod.rs 中加pub mod your_route;和一行.merge(your_route::router())横切需求日志、CORS、超时加到mod.rs的 Tower 层而不是 handler拆分service→ 文件夹、再拆transport/repo只发生在单个文件真的难以阅读之后。测试如何验证这套模板messages/mod.rs 底部的#[cfg(test)] mod tests展示了该模板的可测试性测试通过app(state(...))组装完整路由用oneshot直接打 HTTP 请求并用本地TcpListener伪造上游从而验证上游请求体/头正确x-api-key、anthropic-beta被转发而网关的Authorization: Bearer master-key不会泄漏到上游模型别名到 provider 模型的替换production→claude-sonnet-4-5流式响应的Content-Type: text/event-stream与Cache-Control透传且事件逐字节原样转发上游流式错误在响应开始前即被映射为502与固定文案master key 缺失/错误返回401畸形 JSON 返回400且不 panic。这套测试与 AGENTS.md 的分层主张互为印证handler 薄到几乎只剩提取 转发因此路由级测试可以直接端到端地断言 HTTP 行为而 service 因为不依赖 axum可以独立地测试选部署这一步。小结这套模板解决了什么routes/AGENTS.md用不到百行的篇幅把 ai-gateway 的路由层约束成三个可预测的形态——单文件、mod.rs service.rs、文件夹handler/service/transport——并把鉴权、provider 调用、横切关注点分别固定到 extractor、core crate 与 Tower 层。它的实际收益在 mod.rs 的app函数上看得最清楚新增路由永远只改一行.merge读任何一条路由时都知道哪部分是 HTTP 胶水、哪部分是可测逻辑、哪部分在 core 里。对需要在该 crate 中增加或维护路由的开发者先读 AGENTS.md 的两条规则单文件默认、有逻辑才拆 service与四条不变式再对照health.rs与realtime/两个端点示例是上手成本最低的路径。【免费下载链接】litellmThe fastest, litest AI Gateway. Rust core with Python SDK. Call 100 LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]项目地址: https://gitcode.com/GitHub_Trending/li/litellm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →