feat(messages): route native Anthropic /messages through Rust behind LITELLM_USE_RUST_MESSAGES - #33834
Conversation
…ust:true Adds an opt-in Rust path for non-streaming Azure Anthropic Messages. A deployment sets rust: true in litellm_params to route litellm.messages() and the proxy /v1/messages endpoint through the native Rust bridge; a missing flag or rust: false keeps the existing Python path, and non-Azure providers, streaming, an unavailable bridge, or a None result all fall back to Python. Rust-backed responses carry an x-litellm-rust: true response header so callers can see which path served the request. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
…oc check Mirrors the existing LITELLM_USE_RUST_OCR entry; the flag is an internal rollout toggle that is intentionally not in the public environment settings docs yet. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
…al toggle use_litellm_rust only mutates the OCR enabled flag when configuring OCR (or called with no bridge kwargs, preserving the legacy contract), so configuring only the messages bridge no longer flips OCR state. Remove the vestigial global enabled/env state from the messages bridge. Routing is controlled per deployment by rust:true in the shared handler gate, so the messages module never consulted the global toggle; drop it rather than leave a no-op switch. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
… file and type the request/response contract Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
… via buffered fake-stream Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
… back to Python on Rust bridge errors Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
…itellm_rust_messages_azure
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
This reverts commit c86d861.
…LITELLM_USE_RUST_MESSAGES Extends the opt-in Rust Anthropic Messages path (previously azure_ai only, per-deployment rust:true) to the native anthropic provider and switches enablement to the LITELLM_USE_RUST_MESSAGES env var, mirroring the OCR bridge (LITELLM_USE_RUST_OCR). When enabled, eligible providers route through Rust; unsupported providers fall back to the Python path. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
Greptile SummaryThis PR extends the Rust-backed
Confidence Score: 4/5Safe to merge — the change is well-scoped, both the Rust and Python layers have tests, and the fallback to Python on any bridge error is preserved throughout. The core routing logic and Rust bridge extension are correct. The two minor observations are: ineligible-provider requests now pay an unnecessary bridge round-trip before falling back (where the old code short-circuited in Python), and two test cases became functionally identical after the per-deployment litellm/llms/custom_httpx/llm_http_handler.py — the removed Python-level provider guard means every non-eligible provider invokes the bridge when the env var is on; tests/test_litellm/rust_bridge/test_messages.py — the two 'flag absent / flag false' tests now exercise the same condition.
|
| Filename | Overview |
|---|---|
| litellm/llms/custom_httpx/llm_http_handler.py | Gate logic simplified: per-deployment rust: true flag and provider check removed; now only rust_messages_enabled() controls routing. Bridge is invoked for all providers when enabled, with fallback on None or exception. |
| litellm/rust_bridge/messages.py | Adds enabled field to _RustMessagesState, reads LITELLM_USE_RUST_MESSAGES env var at module load, and exposes rust_messages_enabled(). Mirrors the OCR pattern cleanly. |
| litellm/rust_bridge/ocr.py | Refactors use_litellm_rust to propagate enabled to messages state and handles all four call combinations (both, messages-only, ocr-only, bare) correctly. Logic is symmetric with OCR gating. |
| tests/test_litellm/rust_bridge/test_messages.py | Adds native Anthropic gate test and NoneAsyncMessages helper. Two tests (flag_absent, flag_false) now exercise identical conditions after the per-deployment rust flag was removed from the gate; names are misleading. |
| litellm-rust/crates/ai-gateway/src/messages/common_utils.rs | Adds "anthropic" arm to messages_provider_config, wiring native Anthropic requests to the existing ANTHROPIC_MESSAGES_CONFIG. Minimal, correct change. |
| litellm-rust/crates/ai-gateway/src/messages/tests.rs | Updates Rust tests: adds prepare_messages_call unit tests for native Anthropic (URL, auth header, scope preservation) and unknown-provider rejection; corrects messages_rejects_unsupported_provider to use "openai" now that "anthropic" is valid. |
Comments Outside Diff (2)
-
tests/test_litellm/rust_bridge/test_messages.py, line 296-315 (link)Tests now redundantly check the same condition
test_gate_skips_rust_when_flag_absentandtest_gate_skips_rust_when_flag_falsehave been updated to both calllitellm.use_litellm_rust(False, amessages=bridge). Since the gate now only checksrust_messages_enabled()and ignoreslitellm_params.rustentirely, therust=Falsekwarg in the second test'slitellm_paramsno longer has any effect. Both tests are now identical in what they actually exercise:rust_messages_enabled() == False → skip. The names imply two distinct scenarios (absent vs. explicitly-false per-deployment flag), but the code path they exercise is the same. The tests remain correct but could be consolidated or therust=Falsekwarg removed to avoid misleading future readers.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!
-
litellm/llms/custom_httpx/llm_http_handler.py, line 2264-2291 (link)Unsupported providers now always invoke the bridge when the env var is set
Previously, the gate short-circuited before calling the bridge for any provider that wasn't
azure_ai. With the new logic, whenLITELLM_USE_RUST_MESSAGES=true, every request — regardless of provider — invokes the Rust bridge, which then raisesCoreError::InvalidProviderand falls back to Python via theexcept Exceptionblock. For deployments that mix eligible providers (native Anthropic, Azure AI) with ineligible ones (OpenAI, Gemini, etc.), every ineligible request pays an extra round-trip to the bridge on the hot path before falling back. The testtest_gate_skips_rust_for_non_listed_providerexplicitly captures this:bridge.calls == 1rather than0. Adding a fast-path provider allowlist check in Python before callingamessageswould avoid this overhead.
Reviews (1): Last reviewed commit: "feat(messages): route native Anthropic /..." | Re-trigger Greptile
…essages Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
Screenshots / Proof of Fix
Captured against a live proxy on
localhost:4000hitting the real Anthropic API (no mocks), so the critical AI call runs in Rust. Proof is being captured at the head of this branch; a recorded Claude Code walkthrough (Opus 4.8, real code edits through the Rust route) plus the raw curl runs are shared with the requester rather than embedded here, matching the team preference and PR #33616The proxy is started with the rollout flag on
with a native Anthropic deployment
A real 200 comes back carrying the
x-litellm-rust: truemarker header, i.e. Rust served the upstream callWith
LITELLM_USE_RUST_MESSAGESunset the same request returns a real 200 with no marker header, i.e. it stayed on the existing Python pathType
🆕 New Feature
Changes
This builds on PR #33616 (Azure Anthropic
/messagesthrough Rust) and does two thingsFirst, it extends the Rust
/messagespath to the nativeanthropicprovider, not justazure_ai. The pure transform for native Anthropic already existed inlitellm-core(providers/anthropic/messages), it just was not reachable from the gateway; the only wiring needed was ananthropicarm inmessages_provider_configso the native config (x-api-keyauth,https://api.anthropic.com/v1/messages, defaultanthropic-version/content-typeheaders, identity request transform) resolves. Native Anthropic deliberately does not stripcache_control.scopeor foldrole: "system"chat messages, matching the Python native path; those transforms stay Azure onlySecond, it switches messages enablement to the
LITELLM_USE_RUST_MESSAGESenv var, mirroring the OCR bridge'sLITELLM_USE_RUST_OCRandrust_ocr_enabled(). The gate now shortcuts to Rust whenrust_messages_enabled()is true instead of requiring a per-deploymentrust: trueflag.use_litellm_rust(enabled, ...)toggles the messages flag under the same guard OCR uses, so programmatic control stays symmetric. When the flag is on, eligible providers (native anthropic, azure_ai) route through Rust and every other provider falls back to Python because the Rust provider lookup returns nothing and the bridge yieldsNone. All Python hooks, provider resolution, logging, and the streaming re-emit path are unchanged, and any Rust bridge exception still falls back to the Python pathStreaming stays buffered as in #33616: Python prepares the request, Rust performs the upstream call with
streamremoved, and Python re-emits the buffered result as Anthropic SSE while exposingx-litellm-rust: true. This is buffered Rust-handled streaming, not incremental Rust SSE transportTests cover both layers.
cargo test --workspaceexercises native-Anthropic provider resolution end to end throughprepare_messages_call(URL,x-api-keyauth, default headers, scope left intact) and that an unknown provider is rejected.tests/test_litellm/rust_bridge/test_messages.pycovers the env gate on and off, native-anthropic routing and argument forwarding, thex-litellm-rustheader, and fallback for an unsupported providerFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/1a5f6d0494a145e3a864c45bcc741dc8
Requested by: @ishaan-berri