This commit is contained in:
Justin Wilson 2026-08-26 14:22:05 -03:00 committed by GitHub
commit 2d9a6682eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 103 additions and 8 deletions

View file

@ -649,7 +649,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
if custom_llm_provider is not None and custom_llm_provider != "openai":
model_response.model = f"{custom_llm_provider}/{model}"
for _ in range(2): # if call fails due to alternating messages, retry with reformatted message
for attempt in range(2): # if call fails due to alternating messages, retry with reformatted message
try:
max_retries = inference_params.pop("max_retries", 2)
if acompletion is True:
@ -778,11 +778,10 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
return final_response_obj
except openai.UnprocessableEntityError as e:
## check if body contains unprocessable params - related issue https://github.com/BerriAI/litellm/issues/4800
if litellm.drop_params is True or drop_params is True:
if attempt == 0 and (litellm.drop_params is True or drop_params is True):
inference_params = drop_params_from_unprocessable_entity_error(e, inference_params)
else:
raise e
# e.message
except Exception as e:
if print_verbose is not None:
print_verbose(f"openai.py: Received openai error - {e}")
@ -858,7 +857,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
litellm_params=litellm_params,
headers=headers or {},
)
for _ in range(2): # if call fails due to alternating messages, retry with reformatted message
for attempt in range(2): # if call fails due to alternating messages, retry with reformatted message
try:
openai_aclient: AsyncOpenAI = self._get_openai_client(
is_async=True,
@ -930,11 +929,10 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
return final_response_obj
except openai.UnprocessableEntityError as e:
## check if body contains unprocessable params - related issue https://github.com/BerriAI/litellm/issues/4800
if litellm.drop_params is True or drop_params is True:
if attempt == 0 and (litellm.drop_params is True or drop_params is True):
data = drop_params_from_unprocessable_entity_error(e, data)
else:
raise e
# e.message
except Exception as e:
exception_response = getattr(e, "response", None)
status_code = getattr(e, "status_code", 500)
@ -1038,7 +1036,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
)
data["stream"] = True
data.update(self.get_stream_options(stream_options=stream_options, api_base=api_base))
for _ in range(2):
for attempt in range(2):
try:
openai_aclient: AsyncOpenAI = self._get_openai_client(
is_async=True,
@ -1081,7 +1079,7 @@ class OpenAIChatCompletion(BaseLLM, BaseOpenAILLM):
return streamwrapper
except openai.UnprocessableEntityError as e:
## check if body contains unprocessable params - related issue https://github.com/BerriAI/litellm/issues/4800
if litellm.drop_params is True or drop_params is True:
if attempt == 0 and (litellm.drop_params is True or drop_params is True):
data = drop_params_from_unprocessable_entity_error(e, data)
else:
raise e

View file

@ -0,0 +1,97 @@
"""
Regression tests for issue #32221: with drop_params=True, two consecutive 422s
with an unstructured body must raise instead of silently returning None.
"""
from unittest.mock import AsyncMock, MagicMock
import httpx
import openai
import pytest
from litellm.llms.openai.common_utils import OpenAIError
from litellm.llms.openai.openai import OpenAIChatCompletion, OpenAIConfig
from litellm.types.utils import ModelResponse
def _unstructured_422_error() -> openai.UnprocessableEntityError:
return openai.UnprocessableEntityError(
message="Error code: 422 - content moderation rejected the request",
response=httpx.Response(
422,
request=httpx.Request("POST", "https://api.openai.com/v1/chat/completions"),
json={"error": {"message": "content moderation rejected the request"}},
),
body={"message": "content moderation rejected the request"},
)
@pytest.mark.asyncio
async def test_acompletion_raises_after_unstructured_422_retry_exhaustion():
mock_client = MagicMock()
mock_client.chat.completions.with_raw_response.create = AsyncMock(side_effect=_unstructured_422_error())
with pytest.raises((openai.UnprocessableEntityError, OpenAIError)) as exc_info:
await OpenAIChatCompletion().acompletion(
messages=[{"role": "user", "content": "hi"}],
optional_params={},
litellm_params={},
provider_config=OpenAIConfig(),
model="gpt-4o",
model_response=ModelResponse(),
logging_obj=MagicMock(),
timeout=30.0,
api_key="sk-test",
client=mock_client,
drop_params=True,
)
assert exc_info.value.status_code == 422
assert mock_client.chat.completions.with_raw_response.create.await_count == 2
def test_completion_raises_after_unstructured_422_retry_exhaustion():
mock_client = MagicMock(spec=openai.OpenAI)
mock_client.api_key = "sk-test"
mock_client._base_url = MagicMock()
mock_client.chat.completions.with_raw_response.create.side_effect = _unstructured_422_error()
with pytest.raises((openai.UnprocessableEntityError, OpenAIError)) as exc_info:
OpenAIChatCompletion().completion(
model_response=ModelResponse(),
timeout=30.0,
optional_params={},
litellm_params={},
logging_obj=MagicMock(),
model="gpt-4o",
messages=[{"role": "user", "content": "hi"}],
api_key="sk-test",
client=mock_client,
drop_params=True,
)
assert exc_info.value.status_code == 422
assert mock_client.chat.completions.with_raw_response.create.call_count == 2
@pytest.mark.asyncio
async def test_async_streaming_raises_after_unstructured_422_retry_exhaustion():
mock_client = MagicMock()
mock_client.chat.completions.with_raw_response.create = AsyncMock(side_effect=_unstructured_422_error())
with pytest.raises((openai.UnprocessableEntityError, OpenAIError)) as exc_info:
await OpenAIChatCompletion().async_streaming(
timeout=30.0,
messages=[{"role": "user", "content": "hi"}],
optional_params={},
litellm_params={},
provider_config=OpenAIConfig(),
model="gpt-4o",
logging_obj=MagicMock(),
api_key="sk-test",
client=mock_client,
drop_params=True,
)
assert exc_info.value.status_code == 422
assert mock_client.chat.completions.with_raw_response.create.await_count == 2