mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-08 08:08:37 +00:00
* test(rust-bridge): pin Messages and Responses callback contracts on the native path Each test runs the native route and asserts what the Python path does today, so a known gap is a strict xfail that names its reason and the exception it fails with Messages and Responses share one Surface and hook recorder in support/callbacks.py, and each surface folder lists its own expected counts and gaps. Body-level hooks from the function body (pre-request hook, websearch short-circuit, Messages interceptors, planned and legacy agentic loops, streaming deployment hook) are xfail until native runs them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(rust-bridge): give every route folder the same one-concern-per-file layout Route data, the route Surface and the server fixture move into each route folder, and support/ keeps only route-neutral helpers. test_inference.py splits into chat_completions/ and responses/, ocr/test_requests.py splits into requests, failures and settings, and Messages and Responses get streaming and body hook files. Messages request tests that duplicate inference-messages request.rs are removed, and drop_params read from litellm.drop_params is now covered Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(rust-bridge): expect the required-route decline error for unsupported Responses requests Co-Authored-By: Yujong Lee <yujong@berri.ai> * test(rust-bridge): import route data from the route folders in the secrets unit tests Co-Authored-By: Yujong Lee <yujong@berri.ai> * test(rust-bridge): import NoReturn in the OCR dispatch tests Co-Authored-By: Yujong Lee <yujong@berri.ai> * test(rust-bridge): move route-neutral helpers out of support/callbacks.py Co-Authored-By: Yujong Lee <yujong@berri.ai> * test(rust-bridge): cover deployment rejection and concurrent callbacks * test(rust-bridge): assert native serving via the x-litellm-rust marker in the route folder tests Co-Authored-By: Yujong Lee <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>
73 lines
2.8 KiB
Python
73 lines
2.8 KiB
Python
from collections.abc import AsyncIterator, Iterator
|
|
from typing import Final
|
|
|
|
import pytest
|
|
|
|
from tests.test_litellm_rust.messages.data import MESSAGES_EVENTS, MESSAGES_RESPONSE, call
|
|
from tests.test_litellm_rust.support.callback_recorder import RecordingLogger, drain_logging
|
|
from tests.test_litellm_rust.support.recording_server import RecordingServer, ResponseSpec, relaxed
|
|
from tests.test_litellm_rust.support.results import consume
|
|
from tests.test_litellm_rust.support.routing import assert_served_natively
|
|
|
|
pytestmark = pytest.mark.requires_rust_extension
|
|
|
|
STREAM: Final = ResponseSpec(body=None, events=MESSAGES_EVENTS)
|
|
|
|
|
|
@pytest.fixture
|
|
def server(server: RecordingServer) -> Iterator[RecordingServer]:
|
|
yield from relaxed(server)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_relays_provider_events_and_logs_success_once_after_the_last_chunk(
|
|
server: RecordingServer,
|
|
) -> None:
|
|
server.enqueue(STREAM)
|
|
recorder: Final = RecordingLogger()
|
|
|
|
stream: Final = await call(server, stream=True, callbacks=[recorder])
|
|
assert isinstance(stream, AsyncIterator)
|
|
first: Final = await anext(stream)
|
|
await drain_logging()
|
|
assert "async_log_success_event" not in recorder.names
|
|
rest: Final = [chunk async for chunk in stream]
|
|
|
|
assert_served_natively(server)
|
|
assert first + b"".join(rest) == b"".join(STREAM.payloads())
|
|
success: Final = await recorder.wait_for_async("async_log_success_event")
|
|
assert len(success) == 1
|
|
assert isinstance(success[0].kwargs, dict)
|
|
assert success[0].kwargs["completion_start_time"] is not None
|
|
usage: Final = success[0].response.usage
|
|
assert usage.prompt_tokens == MESSAGES_RESPONSE["usage"]["input_tokens"]
|
|
assert usage.completion_tokens == MESSAGES_RESPONSE["usage"]["output_tokens"]
|
|
assert "log_failure_event" not in recorder.names
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sync_stream_relays_provider_events_and_logs_success_once(server: RecordingServer) -> None:
|
|
server.enqueue(STREAM)
|
|
recorder: Final = RecordingLogger()
|
|
|
|
stream: Final = await call(server, asynchronous=False, stream=True, callbacks=[recorder])
|
|
chunks: Final = await consume(stream)
|
|
|
|
assert_served_natively(server)
|
|
assert b"".join(chunks) == b"".join(STREAM.payloads())
|
|
assert len(await recorder.wait_for_async("async_log_success_event")) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_closed_early_logs_success_once(server: RecordingServer) -> None:
|
|
server.enqueue(STREAM)
|
|
recorder: Final = RecordingLogger()
|
|
|
|
stream: Final = await call(server, stream=True, callbacks=[recorder])
|
|
assert isinstance(stream, AsyncIterator)
|
|
await anext(stream)
|
|
await stream.aclose()
|
|
|
|
assert len(await recorder.wait_for_async("async_log_success_event")) == 1
|
|
with pytest.raises(StopAsyncIteration):
|
|
await anext(stream)
|