Skip to content

refactor(litellm-rust): dissolve providers into core + ai-gateway (strict 3-crate layers) - #31218

Merged
ishaan-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_rust_dissolve_providers_3crate
Jun 24, 2026
Merged

refactor(litellm-rust): dissolve providers into core + ai-gateway (strict 3-crate layers)#31218
ishaan-berri merged 6 commits into
litellm_internal_stagingfrom
litellm_rust_dissolve_providers_3crate

Conversation

@ishaan-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Locks litellm-rust to a strict 3-crate layer structure before more providers/routes pile up. The workspace had drifted to 4 crates where providers mixed pure transforms with network I/O. This dissolves providers so each crate is a clean layer: translation / routes+I/O / Python binding. (Supersedes #31143, which predated the ai-gateway crate.)

Pre-Submission checklist

  • Added an enforcement test (crates/core/tests/workspace_crate_allowlist.rs) + existing transform/route tests still pass
  • cargo test --workspace → 39 passed, 1 ignored (the ignored one is the pre-existing live-OpenAI realtime test)
  • cargo build -p litellm-ai-gateway --features server and cargo build -p litellm-python-bridge both green; clippy -D warnings + fmt --check clean
  • No Python changes

Type

🧹 Refactoring

Changes

Dissolves the providers crate into the two layers it was straddling:

Crate Role Pure / I/O
litellm-core Translation — types, route contracts, provider transforms (core/src/providers/{mistral,openai}), router Pure
litellm-ai-gateway Routes + host — all network I/O (ai-gateway/src/io/{ocr,realtime,realtime_pool}) + the axum server (behind server feature) I/O
litellm-python-bridge PyO3 cdylib for the litellm Python SDK Binding

What moved:

  • transforms (mistral, openai) providers/core/src/providers/
  • I/O (ocr.rs, realtime.rs, realtime_pool.rs) providers/ai-gateway/src/io/
  • python-bridge now imports litellm_ai_gateway::io::ocr::run_ocr; providers crate deleted

ai-gateway gains a lib target + feature split so the cdylib stays lean: the io layer is always available, while axum + the server binary sit behind server (required-features = ["server"]), and the existing embedded-CPython config stays behind python-config. python-bridge depends on it with default-features = false → builds with no axum.

Guardrails:

  • crates/core/tests/workspace_crate_allowlist.rs fails CI if the crate set drifts from the 3 allowlisted crates (so a revived providers is caught).
  • 3-crate map table in README.md + crates/ai-gateway/README.md; rule + table in AGENTS.md (top + per-crate); CLAUDE.md boundary refreshed.

Note: kept litellm-rust/.cargo/config.toml (pyo3 -undefined dynamic_lookup, apple-darwin only) so the cdylib links under plain cargo build on macOS. No effect on Linux/CI or crate logic.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR dissolves the providers crate by redistributing its contents into the two layers it was straddling: pure transforms (mistral, openai) move into litellm-core/src/providers/, while network I/O (ocr.rs, realtime.rs, realtime_pool.rs) moves into litellm-ai-gateway/src/io/. The gateway gains a proper lib target with a feature split so the Python bridge can depend on only the I/O layer without pulling in axum.

  • Provider transforms are now pure and colocated in litellm-core, and I/O is consolidated under litellm-ai-gateway::io, eliminating the in-between providers crate.
  • A new workspace enforcement test (workspace_crate_allowlist.rs) will fail CI if a fourth crate is added without updating the allowlist, locking the 3-layer structure going forward.
  • The server feature gates axum/serde/subtle so python-bridge builds without any HTTP-server dependencies.

Confidence Score: 4/5

Clean structural refactor with no logic changes; all existing tests pass and the new enforcement test adds a meaningful guardrail. The main thing to be aware of is the blocking reqwest client in an async-capable library, which is safe today but a latent footgun for future gateway callers.

The refactor correctly redistributes code with no functional changes. Transforms stay pure, I/O stays async except OCR which is intentionally blocking for the Python bridge. Feature split is sound, test coverage is good, and the workspace allowlist test is a nice addition. Three non-blocking observations: the blocking reqwest Client in a public lib module could panic if a future route handler calls it directly; one test assertion is trivially true; and the hand-rolled TOML parser would misfire on a comment containing the word members.

litellm-rust/crates/ai-gateway/src/io/ocr.rs — the run_ocr public function uses blocking reqwest in a library that the async gateway also links; worth documenting the panic condition clearly or restricting visibility.

Important Files Changed

Filename Overview
litellm-rust/crates/ai-gateway/src/io/ocr.rs Moved from providers/src/ocr.rs; implements blocking OCR via reqwest. Two issues: blocking HTTP client can panic if called from an async context (future misuse risk), and the multibyte-char test assertion is trivially true.
litellm-rust/crates/ai-gateway/src/io/realtime.rs Moved from providers/src/realtime.rs; async WebSocket splice logic with idle timeout. Clean refactor, good test coverage including an ignored live-API integration test.
litellm-rust/crates/ai-gateway/src/io/realtime_pool.rs Pre-warmed WebSocket pool moved from providers; comprehensive unit tests with an in-process fake server, backoff logic, and liveness checks. Well-structured.
litellm-rust/crates/ai-gateway/src/lib.rs New lib target for ai-gateway; cleanly gates server modules behind the server feature and python-config modules behind python-config, enabling the python-bridge to depend on only the io layer.
litellm-rust/crates/ai-gateway/Cargo.toml Adds lib target; splits axum/serde/subtle behind server feature; reqwest and tokio-tungstenite become unconditional for the io layer. Feature split is sound.
litellm-rust/crates/core/tests/workspace_crate_allowlist.rs New enforcement test; verifies workspace has exactly 3 crates. Hand-rolled TOML parser is fragile to comments containing the word "members" but safe for the current manifest shape.
litellm-rust/crates/core/src/providers/mistral/ocr/transformation.rs Moved from providers crate; pure transform logic for Mistral OCR requests and responses. Well-tested with comprehensive unit tests.
litellm-rust/crates/core/src/providers/openai/realtime/transformation.rs Moved from providers crate; pure passthrough transforms for OpenAI realtime events + URL construction with percent-encoding. Correct handling of scheme swapping and multibyte chars.
litellm-rust/crates/python-bridge/src/lib.rs Import updated from litellm_providers to litellm_ai_gateway::io::ocr::run_ocr; otherwise unchanged.
litellm-rust/Cargo.toml Removes providers crate from workspace members and replaces the workspace dependency with litellm-ai-gateway (default-features = false).

Comments Outside Diff (2)

  1. litellm-rust/crates/ai-gateway/src/io/ocr.rs, line 35-43 (link)

    P2 reqwest::blocking in an async-capable library crate

    run_ocr uses reqwest::blocking::Client, which panics with "Cannot block the current thread from within a Tokio runtime" if called from any async context. Because litellm_ai_gateway::io::ocr is a public, always-on module in the library that the async gateway also depends on, a future route handler that calls run_ocr directly (without tokio::task::spawn_blocking) will silently panic at runtime rather than failing to compile. The doc comment says "Blocking: intended to be called with the GIL released", but this constraint is invisible to callers inside the gateway crate. Consider adding a # Panics rustdoc section that explicitly names the runtime constraint, or restricting visibility to only the Python bridge.

  2. litellm-rust/crates/ai-gateway/src/io/ocr.rs, line 127-133 (link)

    P2 Trivially-true assertion doesn't exercise the intended invariant

    truncated.is_char_boundary(truncated.len()) is always true for any valid String — the end position of a String is always a char boundary by definition. The test never actually fails for a naively byte-sliced string because the panic from invalid UTF-8 would come at .collect::<String>(), not at the assertion. To actually assert the truncation didn't split a multibyte sequence, the test should verify the content of the prefix — for example that it contains exactly ERROR_BODY_MAX_CHARS chars and that each char is the expected character.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "refactor(litellm-rust): update workspace..." | Re-trigger Greptile

Comment on lines +45 to +68
let open = after_members.find('[').expect("members should be an array");
let close = after_members[open..]
.find(']')
.map(|offset| open + offset)
.expect("members array should be closed");
let body = &after_members[open + 1..close];

let mut members = BTreeSet::new();
let mut rest = body;
while let Some(start) = rest.find('"') {
let after_quote = &rest[start + 1..];
let end = after_quote
.find('"')
.expect("opening quote should be matched");
members.insert(after_quote[..end].to_string());
rest = &after_quote[end + 1..];
}
members
}

/// The immediate subdirectory names under `crates/`.
fn crate_dirs(root: &Path) -> BTreeSet<String> {
fs::read_dir(root.join("crates"))
.expect("crates/ directory should exist")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 split_once("members") matches the first substring, including comments

parse_members calls manifest.split_once("members") which will match the very first occurrence of the substring members anywhere in the file — including a TOML comment like # workspace members. If a comment containing the word "members" is ever added before the actual members = [...] key, the parser will consume the wrong slice and the resulting BTreeSet will either be empty or contain garbage, causing the allowlist test to fire a false alarm (or silently pass with wrong data). A more robust anchor would be to split on the literal members = [ or members=[.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ishaan-berri
ishaan-berri enabled auto-merge (squash) June 24, 2026 19:00
@ishaan-berri
ishaan-berri merged commit bd75918 into litellm_internal_staging Jun 24, 2026
124 checks passed
@ishaan-berri
ishaan-berri deleted the litellm_rust_dissolve_providers_3crate branch June 24, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants