From 82867e9754dcbb11acb15e8e957c8e2ee5653396 Mon Sep 17 00:00:00 2001 From: sinksilk <785976238@qq.com> Date: Mon, 21 Sep 2026 23:56:12 +0800 Subject: [PATCH 1/2] fix(azure): map content_filter to ContentPolicyViolationError Azure prompt filters send code=content_filter, innererror.ContentFiltered, and a curly apostrophe in the message. Those missed the existing matcher and became BadRequestError, so content_policy_fallbacks never fired Signed-off-by: sinksilk <785976238@qq.com> --- .../exception_mapping_utils.py | 20 ++-- .../test_exception_mapping_utils.py | 4 + .../azure/test_azure_exception_mapping.py | 106 ++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 0fdfb301291..5720ec9e625 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -122,7 +122,8 @@ class ExceptionCheckers: known_exception_substrings: Final = [ "content_policy_violation", "responsibleaipolicyviolation", - "the response was filtered due to the prompt triggering azure openai's content management", + "contentfiltered", + "the response was filtered due to the prompt triggering azure openai", "your task failed as a result of our safety system", "the model produced invalid content", "content_filter_policy", @@ -1923,6 +1924,10 @@ def _map_vllm_exception( ) +_AZURE_CONTENT_POLICY_ERROR_CODES: Final = frozenset(("content_policy_violation", "content_filter")) +_AZURE_CONTENT_POLICY_INNER_CODES: Final = frozenset(("ResponsibleAIPolicyViolation", "ContentFiltered")) + + def _map_azure_exception( *, model: str, @@ -1949,13 +1954,9 @@ def _map_azure_exception( if isinstance(body_dict, dict): if isinstance(body_dict.get("error"), dict): azure_error_code = body_dict["error"].get("code") - # Also check inner_error for - # ResponsibleAIPolicyViolation which indicates a - # content policy violation even when the top-level - # code is generic (e.g. "invalid_request_error"). - if azure_error_code != "content_policy_violation": + if azure_error_code not in _AZURE_CONTENT_POLICY_ERROR_CODES: _inner: Final = body_dict["error"].get("inner_error") or body_dict["error"].get("innererror") - if isinstance(_inner, dict) and _inner.get("code") == "ResponsibleAIPolicyViolation": + if isinstance(_inner, dict) and _inner.get("code") in _AZURE_CONTENT_POLICY_INNER_CODES: azure_error_code = "content_policy_violation" else: azure_error_code = body_dict.get("code") @@ -1986,8 +1987,9 @@ def _map_azure_exception( litellm_debug_info=extra_information, response=getattr(original_exception, "response", None), ) - elif azure_error_code == "content_policy_violation" or ExceptionCheckers.is_azure_content_policy_violation_error( - error_str + elif ( + azure_error_code in _AZURE_CONTENT_POLICY_ERROR_CODES + or ExceptionCheckers.is_azure_content_policy_violation_error(error_str) ): from litellm.llms.azure.exception_mapping import ( AzureOpenAIExceptionMapping, diff --git a/tests/unit/litellm_core_utils/test_exception_mapping_utils.py b/tests/unit/litellm_core_utils/test_exception_mapping_utils.py index 9de768ea47b..bbdc066c242 100644 --- a/tests/unit/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/unit/litellm_core_utils/test_exception_mapping_utils.py @@ -171,9 +171,11 @@ class TestExceptionCheckers: error_strings = [ "invalid_request_error content_policy_violation occurred", "The response was filtered due to the prompt triggering Azure OpenAI's content management policy", + "The response was filtered due to the prompt triggering Azure OpenAI’s content management policy", "Your task failed as a result of our safety system detecting harmful content", "The model produced invalid content that violates our policy", "Request blocked due to content_filter_policy restrictions", + "innererror code ContentFiltered", ] for error_str in error_strings: @@ -186,8 +188,10 @@ class TestExceptionCheckers: error_strings = [ "INVALID_REQUEST_ERROR CONTENT_POLICY_VIOLATION", "The Response Was Filtered Due To The Prompt Triggering Azure OpenAI's Content Management", + "The Response Was Filtered Due To The Prompt Triggering Azure OpenAI’s Content Management", "YOUR TASK FAILED AS A RESULT OF OUR SAFETY SYSTEM", "Content_Filter_Policy restriction detected", + "CONTENTFILTERED", ] for error_str in error_strings: diff --git a/tests/unit/llms/azure/test_azure_exception_mapping.py b/tests/unit/llms/azure/test_azure_exception_mapping.py index 16560c7a1fa..63f76786869 100644 --- a/tests/unit/llms/azure/test_azure_exception_mapping.py +++ b/tests/unit/llms/azure/test_azure_exception_mapping.py @@ -403,6 +403,112 @@ class TestAzureExceptionMapping: assert "optional_pre_call_checks" in error.message assert "docs.litellm.ai" in error.message + def test_azure_prompt_content_filter_maps_to_content_policy_violation(self): + """Prompt-filter 400s use code=content_filter and innererror.ContentFiltered (#42247).""" + + mock_exception = Exception("Bad request") + mock_exception.body = { + "error": { + "message": ( + "The response was filtered due to the prompt triggering " + "Azure OpenAI’s content management policy. Please modify your prompt and retry." + ), + "type": "invalid_request_error", + "param": "prompt", + "code": "content_filter", + "content_filters": [ + { + "blocked": True, + "source_type": "prompt", + "content_filter_results": { + "hate": {"filtered": False, "severity": "safe"}, + "sexual": {"filtered": False, "severity": "safe"}, + "violence": {"filtered": False, "severity": "safe"}, + "self_harm": {"filtered": False, "severity": "safe"}, + "jailbreak": {"detected": True, "filtered": True}, + }, + } + ], + "innererror": {"code": "ContentFiltered"}, + } + } + mock_response = MagicMock() + mock_response.status_code = 400 + mock_exception.response = mock_response + + with pytest.raises(ContentPolicyViolationError) as exc_info: + exception_type( + model="azure/gpt-4", + original_exception=mock_exception, + custom_llm_provider="azure", + ) + + e = exc_info.value + assert e.provider_specific_fields is not None + assert e.provider_specific_fields["innererror"]["code"] == "ContentFiltered" + assert e.provider_specific_fields["inner_error"]["code"] == "ContentFiltered" + + def test_azure_content_filtered_innererror_without_top_code(self): + """innererror.ContentFiltered is a prompt-filter alias even when the top-level code is generic.""" + + mock_exception = Exception("Bad request") + mock_exception.body = { + "error": { + "code": "invalid_request_error", + "innererror": {"code": "ContentFiltered"}, + "message": "The request was rejected.", + "type": "invalid_request_error", + } + } + mock_response = MagicMock() + mock_response.status_code = 400 + mock_exception.response = mock_response + + with pytest.raises(ContentPolicyViolationError) as exc_info: + exception_type( + model="azure/gpt-4", + original_exception=mock_exception, + custom_llm_provider="azure", + ) + + e = exc_info.value + assert e.provider_specific_fields is not None + assert e.provider_specific_fields["innererror"]["code"] == "ContentFiltered" + + def test_azure_ordinary_invalid_request_stays_bad_request(self): + """Ordinary Azure 400s must stay BadRequestError so content_policy_fallbacks do not fire.""" + from litellm.exceptions import BadRequestError + + mock_exception = Exception( + "Error code: 400 - {'error': {'message': \"Invalid 'temperature': decimal above maximum value. " + "Expected a value <= 2, but got 200 instead.\", 'type': 'invalid_request_error', " + "'param': 'temperature', 'code': 'decimal_above_max_value'}}" + ) + mock_exception.body = { + "error": { + "message": ( + "Invalid 'temperature': decimal above maximum value. " + "Expected a value <= 2, but got 200 instead." + ), + "type": "invalid_request_error", + "param": "temperature", + "code": "decimal_above_max_value", + } + } + mock_response = MagicMock() + mock_response.status_code = 400 + mock_exception.response = mock_response + mock_exception.status_code = 400 + + with pytest.raises(BadRequestError) as exc_info: + exception_type( + model="azure/gpt-4", + original_exception=mock_exception, + custom_llm_provider="azure", + ) + + assert not isinstance(exc_info.value, ContentPolicyViolationError) + def test_openai_invalid_encrypted_content_error(self): """Test that OpenAI invalid_encrypted_content errors also get helpful guidance.""" from litellm.exceptions import BadRequestError From 00beee9d4ea70d8a13238873986577d8a844ed5c Mon Sep 17 00:00:00 2001 From: sinksilk <785976238@qq.com> Date: Fri, 25 Sep 2026 01:09:56 +0800 Subject: [PATCH 2/2] test(azure): drop redundant content-filter mapping docstrings Greptile flagged the new test docstrings as repeating clear method names Signed-off-by: sinksilk <785976238@qq.com> --- tests/unit/llms/azure/test_azure_exception_mapping.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/unit/llms/azure/test_azure_exception_mapping.py b/tests/unit/llms/azure/test_azure_exception_mapping.py index 63f76786869..b4dafbe7a6e 100644 --- a/tests/unit/llms/azure/test_azure_exception_mapping.py +++ b/tests/unit/llms/azure/test_azure_exception_mapping.py @@ -404,8 +404,6 @@ class TestAzureExceptionMapping: assert "docs.litellm.ai" in error.message def test_azure_prompt_content_filter_maps_to_content_policy_violation(self): - """Prompt-filter 400s use code=content_filter and innererror.ContentFiltered (#42247).""" - mock_exception = Exception("Bad request") mock_exception.body = { "error": { @@ -449,8 +447,6 @@ class TestAzureExceptionMapping: assert e.provider_specific_fields["inner_error"]["code"] == "ContentFiltered" def test_azure_content_filtered_innererror_without_top_code(self): - """innererror.ContentFiltered is a prompt-filter alias even when the top-level code is generic.""" - mock_exception = Exception("Bad request") mock_exception.body = { "error": { @@ -476,7 +472,6 @@ class TestAzureExceptionMapping: assert e.provider_specific_fields["innererror"]["code"] == "ContentFiltered" def test_azure_ordinary_invalid_request_stays_bad_request(self): - """Ordinary Azure 400s must stay BadRequestError so content_policy_fallbacks do not fire.""" from litellm.exceptions import BadRequestError mock_exception = Exception(