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>
This commit is contained in:
sinksilk 2026-09-21 23:56:12 +08:00
parent b4bb2a77a2
commit 82867e9754
3 changed files with 121 additions and 9 deletions

View file

@ -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,

View file

@ -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:

View file

@ -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