mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
* feat(rust): one typed decline from core up to each host Rust core says it cannot serve a request with one Decline that travels unchanged through llms and inference up to the host. The Python bridge raises RustBridgeDeclined for it in route_failure before the public exception mapping, so dispatch falls back to legacy Python instead of failing the call, and the gateway answers 501. Every route collapses errors after the send with Error::after_send, which keeps a provider answer and a response fault as they are and nests anything else as the source of an invalid response, so a billed call can never read as a decline. Response-side cases that used to decline are now ErrorDetail::Unhandled response faults. The rust-error skill documents declined() as the first question both boundaries ask, and the Python suites pin that a native decline reaches the dispatcher before any provider IO. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): keep post-provider hook failures terminal --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
71 lines
2.7 KiB
Python
71 lines
2.7 KiB
Python
from typing import Final
|
|
|
|
import pytest
|
|
from pydantic import TypeAdapter
|
|
|
|
import litellm
|
|
from litellm import RateLimitError
|
|
from tests.test_litellm_rust.responses.data import RATE_LIMITED, call
|
|
from tests.test_litellm_rust.support.callback_recorder import RecordingLogger
|
|
from tests.test_litellm_rust.support.recording_server import RecordingServer, ResponseSpec
|
|
|
|
pytestmark = pytest.mark.requires_rust_extension
|
|
|
|
_OBJECT: Final = TypeAdapter(dict[str, object])
|
|
|
|
|
|
def failure_signal(asynchronous: bool) -> str:
|
|
return "async_log_failure_event" if asynchronous else "log_failure_event"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("asynchronous", (False, True))
|
|
async def test_sdk_policy_rejection_precedes_resource_setup_and_is_logged_once(
|
|
server: RecordingServer, monkeypatch: pytest.MonkeyPatch, asynchronous: bool
|
|
) -> None:
|
|
monkeypatch.setattr(litellm, "max_budget", 1.0)
|
|
monkeypatch.setattr(litellm, "_current_cost", 2.0)
|
|
monkeypatch.setattr(litellm, "ssl_verify", object())
|
|
recorder: Final = RecordingLogger()
|
|
server.expected_requests = 0
|
|
|
|
with pytest.raises(litellm.BudgetExceededError) as caught:
|
|
await call(server, asynchronous=asynchronous, callbacks=[recorder])
|
|
|
|
failure: Final = await recorder.wait_for_async(failure_signal(asynchronous))
|
|
assert len(failure) == 1
|
|
assert _OBJECT.validate_python(failure[0].kwargs)["exception"] is caught.value
|
|
assert not server.requests
|
|
assert not any("success" in name for name in recorder.names)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("asynchronous", (False, True))
|
|
async def test_provider_failure_is_terminal_and_shared_with_callbacks(
|
|
server: RecordingServer, asynchronous: bool
|
|
) -> None:
|
|
recorder: Final = RecordingLogger()
|
|
server.enqueue(ResponseSpec(body=RATE_LIMITED, status=429))
|
|
|
|
with pytest.raises(RateLimitError) as caught:
|
|
await call(server, asynchronous=asynchronous, callbacks=[recorder])
|
|
|
|
assert caught.value.status_code == 429
|
|
assert len(server.requests) == 1
|
|
failure: Final = await recorder.wait_for_async(failure_signal(asynchronous))
|
|
assert len(failure) == 1
|
|
assert _OBJECT.validate_python(failure[0].kwargs)["exception"] is caught.value
|
|
assert not any("success" in name for name in recorder.names)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("asynchronous", (False, True))
|
|
async def test_a_native_decline_reaches_the_dispatcher_before_provider_io(
|
|
server: RecordingServer, asynchronous: bool
|
|
) -> None:
|
|
server.expected_requests = 0
|
|
|
|
with pytest.raises(RuntimeError, match="declined the request: native Python responses streaming"):
|
|
await call(server, asynchronous=asynchronous, stream=True)
|
|
|
|
assert not server.requests
|