diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py index 722f96ef814..d04cce580a6 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py @@ -14,6 +14,7 @@ from datetime import datetime from re import Pattern from typing import TYPE_CHECKING, Any, Final, Literal, Optional, TypedDict, cast +import openai import yaml from fastapi import HTTPException @@ -2056,6 +2057,13 @@ class ContentFilterGuardrail(CustomGuardrail): except HTTPException: status = "guardrail_intervened" raise + except openai.OpenAIError as e: + # Upstream provider/stream error propagating through the iterator. + # The guardrail never evaluated content, so it must not be recorded + # as a guardrail failure. "not_run" is the canonical status here. + status = "not_run" + exception_str = str(e) + raise except Exception as e: status = "guardrail_failed_to_respond" exception_str = str(e) diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_upstream_error_not_run.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_upstream_error_not_run.py new file mode 100644 index 00000000000..348f1f50803 --- /dev/null +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_upstream_error_not_run.py @@ -0,0 +1,42 @@ +import openai +from unittest.mock import MagicMock + +import pytest + +from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import ( + ContentFilterGuardrail, +) +from litellm.types.guardrails import GuardrailEventHooks + + +@pytest.mark.asyncio +async def test_streaming_hook_upstream_error_is_not_run(): + """ + When the upstream stream raises a provider error (openai.OpenAIError + subclass), the content filter never evaluates content. The error must + propagate unchanged and be logged as "not_run", not as a guardrail + failure. Regression test for issue #31004. + """ + guardrail = ContentFilterGuardrail( + guardrail_name="test-streaming-upstream-error", + patterns=[], + event_hook=GuardrailEventHooks.during_call, + ) + + async def mock_stream(): + raise openai.APIError("Provider returned error", request=MagicMock(), body=None) + yield # pragma: no cover - makes this an async generator + + user_api_key_dict = MagicMock() + request_data: dict = {} + + with pytest.raises(openai.APIError): + async for _ in guardrail.async_post_call_streaming_iterator_hook( + user_api_key_dict=user_api_key_dict, + response=mock_stream(), + request_data=request_data, + ): + pass + + info = request_data["metadata"]["standard_logging_guardrail_information"][0] + assert info["guardrail_status"] == "not_run"