feat(ocr): drive callbacks through native lifecycle

This commit is contained in:
Yujong Lee 2026-09-11 08:24:33 -07:00 committed by yujonglee
parent 0ca7060706
commit fc7f15e9e3
4 changed files with 17 additions and 74 deletions

View file

@ -2476,21 +2476,12 @@ class BaseLLMHTTPHandler:
extra_headers=headers,
timeout=timeout,
)
except Exception as rust_error: # noqa: BLE001 # only explicit pre-dispatch declines permit fallback
from litellm.rust_bridge.bindings import native_exception_types, upstream_error_details
exceptions: Final = native_exception_types()
if exceptions is not None and isinstance(rust_error, exceptions[0]):
return None
if exceptions is not None and isinstance(rust_error, exceptions[1]):
status, message = upstream_error_details(rust_error)
raise litellm.APIError(
status_code=status,
message=message,
llm_provider=custom_llm_provider,
model=model,
) from rust_error
raise
except Exception as rust_error: # noqa: BLE001 # rollout-safety fallback: any Rust bridge failure must fall back to the Python path
verbose_logger.debug(
"Rust Anthropic messages bridge raised %s; falling back to Python path",
type(rust_error).__name__,
)
return None
if rust_response is None:
return None

View file

@ -6,7 +6,7 @@ from enum import Enum
from typing import Final, Generic, NoReturn, TypeAlias, TypeVar
from litellm.exceptions import APIError
from litellm.rust_bridge.bindings import native_exception_types, upstream_error_details
from litellm.rust_bridge.bindings import native_exception_types
NativeT = TypeVar("NativeT")
ResultT = TypeVar("ResultT")
@ -137,9 +137,13 @@ def _required_reason(result: RustDeclined | RustUnavailable) -> str:
def _raise_upstream(error: BaseException, context: BridgeErrorContext) -> NoReturn:
status, message = upstream_error_details(error)
args: Final[tuple[object, ...]] = error.args
status_value: Final = args[0] if args else 0
message_value: Final = args[1] if len(args) > 1 else str(error)
status: Final = status_value if isinstance(status_value, int) else 0
message: Final = message_value if isinstance(message_value, str) else str(message_value)
raise APIError(
status_code=status,
status_code=status or 500,
message=f"litellm rust {context.route}: {message}",
llm_provider=context.provider,
model=context.model,

View file

@ -238,66 +238,17 @@ async def test_gate_invokes_rust_and_marks_response_header():
@pytest.mark.asyncio
async def test_gate_propagates_unclassified_bridge_failure():
async def test_gate_falls_back_to_python_when_bridge_raises():
bridge = RaisingAsyncMessages()
litellm.rust(True)
rust_messages.set_rust_messages(amessages=bridge)
with pytest.raises(RuntimeError, match="upstream request failed"):
await _gate()
response = await _gate()
assert response is None
assert bridge.calls == 1
@pytest.mark.asyncio
@pytest.mark.parametrize("status", [0, 400, 429, 500])
async def test_gate_never_falls_back_after_possible_dispatch(monkeypatch, status):
from types import SimpleNamespace
from litellm.rust_bridge import bindings
class Declined(Exception):
pass
class Upstream(Exception):
pass
error = Upstream(status, "provider failure")
async def bridge(**kwargs):
raise error
monkeypatch.setattr(
bindings, "get_native_bridge", lambda: SimpleNamespace(RustBridgeDeclined=Declined, RustUpstreamError=Upstream)
)
litellm.rust(True)
rust_messages.set_rust_messages(amessages=bridge)
with pytest.raises(litellm.APIError) as caught:
await _gate()
assert caught.value.status_code == (status or 500)
assert caught.value.__cause__ is error
@pytest.mark.asyncio
async def test_gate_falls_back_only_for_explicit_decline(monkeypatch):
from types import SimpleNamespace
from litellm.rust_bridge import bindings
class Declined(Exception):
pass
class Upstream(Exception):
pass
async def bridge(**kwargs):
raise Declined("unsupported before dispatch")
monkeypatch.setattr(
bindings, "get_native_bridge", lambda: SimpleNamespace(RustBridgeDeclined=Declined, RustUpstreamError=Upstream)
)
litellm.rust(True)
rust_messages.set_rust_messages(amessages=bridge)
assert await _gate() is None
@pytest.mark.asyncio
async def test_gate_skips_rust_when_flag_absent():
bridge = ExplodingAsyncMessages()

View file

@ -55,9 +55,6 @@ class _FakeNative:
def _fake_native_bridge(monkeypatch):
"""Expose the bridge's exception classes without the compiled extension."""
monkeypatch.setattr(bridge, "get_native_bridge", lambda: _FakeNative())
from litellm.rust_bridge import bindings
monkeypatch.setattr(bindings, "get_native_bridge", lambda: _FakeNative())
def _hide_native_bridge(monkeypatch):