From 6a4248cb15600899ccbb6679ea7879887e5349ed Mon Sep 17 00:00:00 2001 From: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:50:19 -0700 Subject: [PATCH] fix(guardrails): skip the empty moderation call when only tool or system text is present --- .../enterprise_hooks/openai_moderation.py | 2 +- .../guardrails/test_guardrail_coverage.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/enterprise/enterprise_hooks/openai_moderation.py b/enterprise/enterprise_hooks/openai_moderation.py index fdf4d5d2043..74ceaf5d5eb 100644 --- a/enterprise/enterprise_hooks/openai_moderation.py +++ b/enterprise/enterprise_hooks/openai_moderation.py @@ -40,7 +40,7 @@ class ENTERPRISE_OpenAI_Moderation(CustomLogger): # Covers multimodal list content + Responses-API input. System, instructions # and tool text go in a second call after the message text. message_text, added_text = split_message_text(data) - texts = ("".join(message_text), "".join(added_text)) if added_text else ("".join(message_text),) + texts = tuple(text for text in ("".join(message_text), "".join(added_text)) if text) or ("",) from litellm.proxy.proxy_server import llm_router diff --git a/tests/unit/proxy/guardrails/test_guardrail_coverage.py b/tests/unit/proxy/guardrails/test_guardrail_coverage.py index fc9146d6acd..5cb46491d0e 100644 --- a/tests/unit/proxy/guardrails/test_guardrail_coverage.py +++ b/tests/unit/proxy/guardrails/test_guardrail_coverage.py @@ -907,6 +907,37 @@ async def test_openai_moderation_sends_tool_and_system_text_in_its_own_call( assert seen == ["say hi", added] +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("data", "inputs"), + [ + ({"messages": [{"role": "user", "content": [{"type": "image_url", "image_url": {"url": "x"}}]}]}, [""]), + ( + { + "system": "be brief", + "messages": [{"role": "user", "content": [{"type": "image_url", "image_url": {"url": "x"}}]}], + }, + ["be brief"], + ), + ], + ids=("no-text", "system-only"), +) +async def test_openai_moderation_makes_one_call_when_one_side_has_no_text( + monkeypatch: pytest.MonkeyPatch, user_api_key: UserAPIKeyAuth, data: dict[str, JsonValue], inputs: list[str] +) -> None: + from enterprise.enterprise_hooks.openai_moderation import ENTERPRISE_OpenAI_Moderation + + seen: Final[list[str]] = [] + + async def amoderation(model: str, input: str) -> SimpleNamespace: + seen.append(input) + return SimpleNamespace(results=[SimpleNamespace(flagged=False)]) + + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", SimpleNamespace(amoderation=amoderation)) + assert await ENTERPRISE_OpenAI_Moderation().async_moderation_hook(data, user_api_key, "acompletion") is None + assert seen == inputs + + @pytest.mark.asyncio @pytest.mark.parametrize( ("configured_after_init", "expected_model"),