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>
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
from typing import Final
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
import litellm
|
|
from litellm.integrations.custom_guardrail import CustomGuardrail
|
|
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import ContentFilterGuardrail
|
|
from litellm.types.guardrails import BlockedWord, ContentFilterAction, GuardrailEventHooks
|
|
from litellm.types.utils import CallTypes
|
|
from tests.test_litellm_rust.ocr.data import call_aocr
|
|
from tests.test_litellm_rust.support.callback_recorder import RecordingLogger
|
|
from tests.test_litellm_rust.support.recording_server import RecordingServer
|
|
|
|
pytestmark = pytest.mark.requires_rust_extension
|
|
|
|
|
|
class ReplaceOCRMarkdown(CustomGuardrail):
|
|
def __init__(self) -> None:
|
|
super().__init__(
|
|
guardrail_name="replace-ocr-markdown", event_hook=GuardrailEventHooks.post_call, default_on=True
|
|
)
|
|
self.call_types: list[CallTypes] = []
|
|
|
|
async def async_post_call_success_deployment_hook(self, request_data, response, call_type):
|
|
self.call_types.append(call_type)
|
|
reviewed_page: Final = response.pages[0].model_copy(update={"markdown": "Reviewed OCR"})
|
|
return response.model_copy(update={"pages": [reviewed_page]})
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_native_aocr_post_call_content_filter_blocks_matching_markdown(
|
|
server: RecordingServer,
|
|
) -> None:
|
|
guardrail: Final = ContentFilterGuardrail(
|
|
guardrail_name="block-native-ocr-markdown",
|
|
event_hook=GuardrailEventHooks.post_call,
|
|
blocked_words=[BlockedWord(keyword="native OCR response", action=ContentFilterAction.BLOCK)],
|
|
)
|
|
litellm.callbacks.append(guardrail)
|
|
|
|
with pytest.raises(HTTPException, match="Content blocked") as blocked:
|
|
await call_aocr(server, guardrails=[guardrail.guardrail_name])
|
|
|
|
assert blocked.value.status_code == 400
|
|
assert len(server.requests) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_native_aocr_post_call_replacement_reaches_caller_and_success_callback(
|
|
server: RecordingServer,
|
|
) -> None:
|
|
guardrail: Final = ReplaceOCRMarkdown()
|
|
recorder: Final = RecordingLogger()
|
|
litellm.callbacks.append(guardrail)
|
|
|
|
response: Final = await call_aocr(
|
|
server,
|
|
callbacks=[recorder],
|
|
guardrails=[guardrail.guardrail_name],
|
|
)
|
|
success_events: Final = await recorder.wait_for_async("async_log_success_event")
|
|
|
|
assert guardrail.call_types == [CallTypes.aocr]
|
|
assert response.pages[0].markdown == "Reviewed OCR"
|
|
assert len(success_events) == 1
|
|
assert success_events[0].response.pages[0].markdown == "Reviewed OCR"
|
|
assert "guardrails" not in server.requests[0].body
|