mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 20:23:18 +00:00
Merge pull request #22080 from BerriAI/litellm_vllm_response_fix
Fix None (TypeError: 'NoneType' object is not a mapping)
This commit is contained in:
+3
-2
@@ -4644,11 +4644,12 @@ def add_provider_specific_params_to_optional_params(
|
||||
)
|
||||
is False
|
||||
):
|
||||
extra_body = passed_params.pop("extra_body", {})
|
||||
extra_body = passed_params.pop("extra_body", None) or {}
|
||||
for k in passed_params.keys():
|
||||
if k not in openai_params and passed_params[k] is not None:
|
||||
extra_body[k] = passed_params[k]
|
||||
optional_params.setdefault("extra_body", {})
|
||||
if not isinstance(optional_params.get("extra_body"), dict):
|
||||
optional_params["extra_body"] = {}
|
||||
initial_extra_body = {
|
||||
**optional_params["extra_body"],
|
||||
**extra_body,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Tests for hosted_vllm responses API support.
|
||||
|
||||
Regression test for: https://github.com/BerriAI/litellm/issues
|
||||
Bug: client.responses.create() raised TypeError: 'NoneType' object is not a mapping
|
||||
when extra_body=None was passed through the responses→completion pipeline for
|
||||
hosted_vllm (and any OpenAI-compatible provider using add_provider_specific_params_to_optional_params).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../../../../..")
|
||||
) # Adds the parent directory to the system path
|
||||
|
||||
import litellm
|
||||
|
||||
|
||||
def _make_mock_chat_completion_response(content: str = "Hello! I'm doing well.") -> dict:
|
||||
return {
|
||||
"id": "chatcmpl-test123",
|
||||
"object": "chat.completion",
|
||||
"created": 1234567890,
|
||||
"model": "Qwen/Qwen3-8B",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": content},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
|
||||
}
|
||||
|
||||
|
||||
def _make_mock_http_client(response_body: dict) -> MagicMock:
|
||||
mock_client = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.headers = {"content-type": "application/json"}
|
||||
mock_response.json.return_value = response_body
|
||||
mock_response.text = json.dumps(response_body)
|
||||
mock_client.post.return_value = mock_response
|
||||
return mock_client
|
||||
|
||||
|
||||
def test_hosted_vllm_responses_create_with_string_input():
|
||||
"""
|
||||
Regression test: responses.create() with string input must not raise
|
||||
TypeError: 'NoneType' object is not a mapping.
|
||||
|
||||
Root cause: extra_body=None was passed explicitly through the
|
||||
responses→completion pipeline. In add_provider_specific_params_to_optional_params(),
|
||||
passed_params.pop("extra_body", {}) returned None (key existed with value None),
|
||||
and **None raised TypeError at dict unpacking.
|
||||
|
||||
Fix: normalize None to {} for both extra_body and optional_params["extra_body"].
|
||||
"""
|
||||
mock_client = _make_mock_http_client(
|
||||
_make_mock_chat_completion_response("I'm doing well, thanks!")
|
||||
)
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.llm_http_handler._get_httpx_client",
|
||||
return_value=mock_client,
|
||||
):
|
||||
response = litellm.responses(
|
||||
model="hosted_vllm/Qwen/Qwen3-8B",
|
||||
input="Hello, how are you?",
|
||||
api_base="https://test-vllm.example.com/v1",
|
||||
api_key="test-key",
|
||||
)
|
||||
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
assert response is not None
|
||||
assert isinstance(response, ResponsesAPIResponse)
|
||||
assert len(response.output) > 0
|
||||
output_message = response.output[0]
|
||||
assert output_message.role == "assistant" # type: ignore[union-attr]
|
||||
assert len(output_message.content) > 0 # type: ignore[union-attr]
|
||||
assert "well" in output_message.content[0].text # type: ignore[union-attr]
|
||||
|
||||
|
||||
def test_hosted_vllm_responses_create_with_explicit_none_extra_body():
|
||||
"""
|
||||
Directly verify the fix in add_provider_specific_params_to_optional_params:
|
||||
extra_body=None must not crash when building optional_params.
|
||||
"""
|
||||
from litellm.utils import get_optional_params
|
||||
|
||||
# This should not raise TypeError: 'NoneType' object is not a mapping
|
||||
optional_params = get_optional_params(
|
||||
model="Qwen/Qwen3-8B",
|
||||
custom_llm_provider="hosted_vllm",
|
||||
extra_body=None,
|
||||
)
|
||||
|
||||
# extra_body=None should be normalized to an empty dict (or absent)
|
||||
assert optional_params.get("extra_body") is not None or "extra_body" not in optional_params
|
||||
Reference in New Issue
Block a user