refactor(rust-bridge): move the route host modules under host/routes (#46786)

* refactor(rust-bridge): move the route host modules under host/routes

Each native route's Python half lived in its own `<route>/route_host.py`, apart
from the shared host modules Rust also imports by name. They now sit together in
`litellm/rust_bridge/host/routes/<route>.py`, with their unit tests moved to the
mirrored path. No behavior change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(rust-host): reference messages route alias

Co-Authored-By: yujonglee.dev@gmail.com <yujong@berri.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yujonglee 2026-10-15 18:27:06 -07:00 • committed by GitHub
parent 238597ed49
commit 55c8ec4647
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 20 additions and 20 deletions

View file

@ -23,7 +23,7 @@ fn run_chat_completions(
) -> PyResult<Py<PyAny>> {
let host = InferenceHost::new(
call.view()?,
"litellm.rust_bridge.chat_completions.route_host",
"litellm.rust_bridge.host.routes.chat_completions",
);
run_inference::<ChatCompletionsRoute, _>(
py,

View file

@ -26,7 +26,7 @@ use crate::{
routes::RequestView,
};
const ROUTE_HOST_MODULE: &str = "litellm.rust_bridge.messages.route_host";
const ROUTE_HOST_MODULE: &str = "litellm.rust_bridge.host.routes.messages";
const REQUEST_ERROR_MARKER: &str = "messages_request_error";
const BODY_FIELDS: [&str; 22] = [

View file

@ -77,7 +77,7 @@ impl OcrPythonHost {
_ => "",
};
let mapped = py
.import("litellm.rust_bridge.ocr.route_host")
.import("litellm.rust_bridge.host.routes.ocr")
.and_then(|module| module.getattr("map_failure"))
.and_then(|map| map.call1((error.value(py), self.view.request(py)?, provider)))
.and_then(|mapped| mapped.extract::<Py<PyBaseException>>().map_err(PyErr::from));
@ -115,7 +115,7 @@ impl PythonBinding for OcrPythonHost {
py: Python<'_>,
response: LiteLLMOcrResponse,
) -> PyResult<Py<PyAny>> {
public_response(py, "litellm.rust_bridge.ocr.route_host", &response)
public_response(py, "litellm.rust_bridge.host.routes.ocr", &response)
}
fn encode_stream_head(

View file

@ -21,7 +21,7 @@ use super::{
};
use crate::errors::RustBridgeDeclined;
const ROUTE_HOST_MODULE: &str = "litellm.rust_bridge.responses.route_host";
const ROUTE_HOST_MODULE: &str = "litellm.rust_bridge.host.routes.responses";
fn run_responses(py: Python<'_>, call: NativeCall<'_>, asynchronous: bool) -> PyResult<Py<PyAny>> {
let resolved = call.resolved()?;

View file

@ -8,4 +8,4 @@ Python state Rust needs but does not own, behind one module per question. Rust i
- The module path and function signature are a contract. The Rust side pins the path in a `const MODULE` in the crate that asks (`python-bridge/src/routes/messages/host.rs`, `python-bridge/src/secrets/`, `callbacks-legacy-python/src/python.rs`); change both sides together and keep `tests/unit/rust_bridge/host/` green
- Inputs and outputs use values and frozen snapshots. Retain caller-owned clients and caches as opaque references when identity is required; Rust never discovers them through module globals
- Moving a question into Rust means replacing the function body with a native call, not adding a second resolver. The Python signature stays
- A `*/route_host.py` module is not this: it projects one route's kwargs and maps that route's failures. Shared questions belong here
- `routes/<route>.py` holds the questions one route asks, under the same function names on every route (`response`, `map_failure`, `connection_defaults`), so Rust names only the module. A question two routes ask goes in a top-level module here instead

View file

@ -6,4 +6,4 @@ Call each path directly with an explicit decision instead. The Python path is th
Rollout policy itself, meaning which rule matches and what `LITELLM_RUST` changes, belongs in `test_catalog.py`, `test_configuration.py` and `test_dispatch.py`, tested against rules the test builds rather than the shipped `catalog.RULES`
Before adding a test here, ask whether it checks something Rust cannot. A `route_host.py` module is the Python half of a native route: it projects Python-only state (the cost map, `litellm.*` settings, request kwargs) into the plain values the Rust side consumes, and maps native failures back onto public exceptions. Those projections are what belongs here, because a wrong key or an ignored provider prefix ships the wrong value to Rust and no Rust test sees it. `messages/test_route_host.py` shows the shape. Behavior that lives in Rust (a request transform given its inputs, header assembly, stream relay) is tested in the crate, and the route end to end is tested against a recording server in `tests/test_litellm_rust/`. A test that only re-checks a Python helper the route host happens to call is a duplicate of that helper's own test and should not be added
Before adding a test here, ask whether it checks something Rust cannot. A `host/routes/<route>.py` module is the Python half of a native route: it projects Python-only state (the cost map, `litellm.*` settings, request kwargs) into the plain values the Rust side consumes, and maps native failures back onto public exceptions. Those projections are what belongs here, because a wrong key or an ignored provider prefix ships the wrong value to Rust and no Rust test sees it. `host/routes/test_messages.py` shows the shape. Behavior that lives in Rust (a request transform given its inputs, header assembly, stream relay) is tested in the crate, and the route end to end is tested against a recording server in `tests/test_litellm_rust/`. A test that only re-checks a Python helper the route host happens to call is a duplicate of that helper's own test and should not be added

View file

@ -4,7 +4,7 @@ from typing import Final
import pytest
import litellm
from litellm.rust_bridge.chat_completions.route_host import connection_defaults, response
from litellm.rust_bridge.host.routes.chat_completions import connection_defaults, response
from litellm.types.utils import ModelResponse

View file

@ -5,8 +5,8 @@ import pytest
import litellm
from litellm.constants import DEFAULT_REQUEST_TIMEOUT_SECONDS
from litellm.rust_bridge.messages import route_host
from litellm.rust_bridge.messages.route_host import response
from litellm.rust_bridge.host.routes import messages as messages_host
from litellm.rust_bridge.host.routes.messages import response
def test_response_is_a_detached_public_messages_dict() -> None:
@ -36,7 +36,7 @@ def test_settings_project_caller_configuration_without_resolving_a_model(monkeyp
monkeypatch.setattr(litellm, "request_timeout", 1800)
monkeypatch.setattr(litellm, "request_timeout_explicitly_set", True)
projected: Final = route_host.settings({"drop_params": "true", "additional_drop_params": ["metadata.user_id"]})
projected: Final = messages_host.settings({"drop_params": "true", "additional_drop_params": ["metadata.user_id"]})
assert projected == {
"drop_params": True,
@ -60,7 +60,7 @@ def test_request_timeout_carries_only_a_configured_global_timeout(
monkeypatch.setattr(litellm, "request_timeout", timeout)
monkeypatch.setattr(litellm, "request_timeout_explicitly_set", explicitly_set)
assert route_host.settings({})["request_timeout"] == expected
assert messages_host.settings({})["request_timeout"] == expected
@pytest.mark.parametrize(
@ -78,7 +78,7 @@ def test_drop_params_merges_the_global_flag_with_the_request(
) -> None:
monkeypatch.setattr(litellm, "drop_params", global_flag)
assert route_host.settings(kwargs)["drop_params"] is expected
assert messages_host.settings(kwargs)["drop_params"] is expected
@pytest.mark.parametrize(
@ -90,7 +90,7 @@ def test_drop_params_merges_the_global_flag_with_the_request(
],
)
def test_additional_drop_params_keep_only_string_paths(configured: object, expected: tuple[str, ...]) -> None:
settings: Final = route_host.settings({"additional_drop_params": configured})
settings: Final = messages_host.settings({"additional_drop_params": configured})
assert settings["additional_drop_params"] == expected
@ -117,19 +117,19 @@ def test_native_request_rejections_map_to_the_public_400() -> None:
rejected: Final = ValueError("claude-sonnet-5 does not support top_k=5")
rejected.messages_request_error = True # pyright: ignore[reportAttributeAccessIssue] # marker the native host sets
mapped: Final = route_host.map_failure(rejected, request.resolved, "anthropic")
mapped: Final = messages_host.map_failure(rejected, request.resolved, "anthropic")
assert isinstance(mapped, litellm.BadRequestError)
assert mapped.status_code == 400
assert "does not support top_k=5" in mapped.message
assert mapped.model == "claude-sonnet-5"
assert not isinstance(
route_host.map_failure(ValueError("plain"), request.resolved, "anthropic"), litellm.BadRequestError
messages_host.map_failure(ValueError("plain"), request.resolved, "anthropic"), litellm.BadRequestError
)
def test_stream_hidden_params_projects_upstream_headers_the_way_the_python_handler_does() -> None:
hidden: Final = route_host.stream_hidden_params(
hidden: Final = messages_host.stream_hidden_params(
(("request-id", "req_upstream_123"), ("x-ratelimit-remaining-requests", "41"))
)

View file

@ -3,8 +3,8 @@ from typing import Final
import pytest
import litellm
from litellm.rust_bridge.ocr.route_host import UpstreamFailure, map_failure
from litellm.rust_bridge.ocr.route_host import response as build_ocr_response
from litellm.rust_bridge.host.routes.ocr import UpstreamFailure, map_failure
from litellm.rust_bridge.host.routes.ocr import response as build_ocr_response
from litellm.rust_bridge.public_call import NativeCall
REQUEST: Final = NativeCall(

View file

@ -5,7 +5,7 @@ import pytest
from pydantic import ValidationError
import litellm
from litellm.rust_bridge.responses.route_host import connection_defaults, map_failure, response
from litellm.rust_bridge.host.routes.responses import connection_defaults, map_failure, response
from litellm.types.llms.openai import ResponsesAPIResponse