test(images): keep the existing test file formatting

Restore the formatting from cab48123d1 while preserving the new parametrized test and its Mapping import

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pandalaohe 2026-10-15 19:30:07 +08:00
parent 55ae32934e
commit 607d3fff6f

View file

@ -19,19 +19,7 @@ from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response impo
safe_convert_created_field,
should_convert_tool_call_to_json_mode,
)
from litellm.types.utils import (
ChatCompletionMessageCustomToolCall,
ChatCompletionMessageToolCall,
Choices,
CompletionTokensDetailsWrapper,
Function,
ImageObject,
ImageResponse,
Message,
ModelResponse,
PromptTokensDetailsWrapper,
Usage,
)
from litellm.types.utils import ChatCompletionMessageCustomToolCall, ChatCompletionMessageToolCall, Choices, CompletionTokensDetailsWrapper, Function, ImageObject, ImageResponse, Message, ModelResponse, PromptTokensDetailsWrapper, Usage
from tests._vcr_conftest_common import install_live_call_probe, record_vcr_outcome
FIXED_TIME: Final[datetime] = datetime(2025, 1, 1, 12, 0, 0)
@ -54,7 +42,7 @@ OPENAI_CUSTOM_TOOL_CALL_RESPONSE = {
"type": "custom",
"custom": {
"name": "ApplyPatch",
"input": '*** Begin Patch\n*** Update File: main.py\n@@\n+def hello():\n+ print("Hello")\n*** End Patch\n',
"input": "*** Begin Patch\n*** Update File: main.py\n@@\n+def hello():\n+ print(\"Hello\")\n*** End Patch\n",
},
}
],
@ -238,7 +226,6 @@ def _vcr_outcome_gate(request, vcr):
yield
record_vcr_outcome(request, vcr)
@pytest.fixture(scope="session")
def event_loop():
try:
@ -248,7 +235,6 @@ def event_loop():
yield loop
loop.close()
@pytest.fixture(scope="function")
def setup_and_teardown(event_loop):
import litellm
@ -282,7 +268,6 @@ def setup_and_teardown(event_loop):
if pending:
event_loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
_SCALAR_DEFAULTS = {
"num_retries": getattr(litellm, "num_retries", None),
"set_verbose": getattr(litellm, "set_verbose", False),
@ -297,7 +282,6 @@ _SCALAR_DEFAULTS = {
"cohere_key": getattr(litellm, "cohere_key", None),
}
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_basic():
# Test basic conversion with minimal input
@ -312,7 +296,6 @@ def test_convert_to_image_response_basic():
assert result.created == 1234567890
assert result.data[0].url == "http://example.com/image.jpg"
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_hidden_params():
# Test with hidden params
@ -326,7 +309,6 @@ def test_convert_to_image_response_with_hidden_params():
assert result._hidden_params == {"api_key": "test_key"}
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_multiple_images():
# Test handling multiple images in response
@ -344,7 +326,6 @@ def test_convert_to_image_response_multiple_images():
assert result.data[0].url == "http://example.com/image1.jpg"
assert result.data[1].url == "http://example.com/image2.jpg"
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_b64_json():
# Test handling b64_json in response
@ -357,7 +338,6 @@ def test_convert_to_image_response_with_b64_json():
assert result.data[0].b64_json == "base64encodedstring"
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_extra_fields():
response_dict = {
@ -379,7 +359,6 @@ def test_convert_to_image_response_with_extra_fields():
assert result.data[0].url == "http://example.com/image1.jpg"
assert result.data[1].url == "http://example.com/image2.jpg"
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_extra_fields_2():
"""
@ -557,7 +536,6 @@ def test_convert_to_image_response_with_none_usage_fields():
assert result.usage.input_tokens_details.image_tokens == 0
assert result.usage.input_tokens_details.text_tokens == 0
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_partial_none_usage_fields():
"""
@ -591,7 +569,6 @@ def test_convert_to_image_response_with_partial_none_usage_fields():
assert result.usage.input_tokens_details.image_tokens == 0
assert result.usage.input_tokens_details.text_tokens == 0
@pytest.mark.usefixtures("_vcr_outcome_gate", "setup_and_teardown")
def test_convert_to_image_response_with_valid_usage_fields():
"""
@ -626,7 +603,6 @@ def test_convert_to_image_response_with_valid_usage_fields():
assert result.usage.input_tokens_details.image_tokens == 30
assert result.usage.input_tokens_details.text_tokens == 20
def test_convert_to_model_response_object_basic():
"""Test basic conversion with all fields present."""
response_object = {
@ -689,8 +665,12 @@ def test_convert_to_model_response_object_basic():
assert result.usage.prompt_tokens == 19
assert result.usage.completion_tokens == 10
assert result.usage.total_tokens == 29
assert result.usage.prompt_tokens_details == PromptTokensDetailsWrapper(cached_tokens=0)
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(reasoning_tokens=0)
assert result.usage.prompt_tokens_details == PromptTokensDetailsWrapper(
cached_tokens=0
)
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(
reasoning_tokens=0
)
# Other fields
assert result.system_fingerprint == "fp_6b68a8204b"
@ -698,7 +678,6 @@ def test_convert_to_model_response_object_basic():
# hidden params
assert result._hidden_params is not None
def test_convert_image_input_dict_response_to_chat_completion_response():
"""Test conversion on a response with an image input."""
response_object = {
@ -749,17 +728,21 @@ def test_convert_image_input_dict_response_to_chat_completion_response():
assert choice.index == 0
assert isinstance(choice.message, Message)
assert choice.message.role == "assistant"
assert choice.message.content == "\n\nThis image shows a wooden boardwalk extending through a lush green marshland."
assert (
choice.message.content
== "\n\nThis image shows a wooden boardwalk extending through a lush green marshland."
)
assert choice.finish_reason == "stop"
assert result.usage.prompt_tokens == 9
assert result.usage.completion_tokens == 12
assert result.usage.total_tokens == 21
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(reasoning_tokens=0)
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(
reasoning_tokens=0
)
assert result._hidden_params is not None
def test_convert_to_model_response_object_tool_calls_invalid_json_arguments():
"""
Critical test - this is a basic response from OpenAI API
@ -822,8 +805,13 @@ def test_convert_to_model_response_object_tool_calls_invalid_json_arguments():
assert len(result.choices) == 1
assert result.choices[0].message.content is None
assert len(result.choices[0].message.tool_calls) == 1
assert result.choices[0].message.tool_calls[0].function.name == "get_current_weather"
assert result.choices[0].message.tool_calls[0].function.arguments == '{"location":"Boston, MA","unit":"fahren'
assert (
result.choices[0].message.tool_calls[0].function.name == "get_current_weather"
)
assert (
result.choices[0].message.tool_calls[0].function.arguments
== '{"location":"Boston, MA","unit":"fahren'
)
assert result.choices[0].finish_reason == "length"
assert result.model == "gpt-4o-2024-08-06"
assert result.created == 1729337288
@ -832,7 +820,6 @@ def test_convert_to_model_response_object_tool_calls_invalid_json_arguments():
assert result.usage.total_tokens == 102
assert result.system_fingerprint == "fp_45c6de4934"
def test_convert_to_model_response_object_tool_calls_valid_json_arguments():
"""
Critical test - this is a basic response from OpenAI API
@ -895,8 +882,13 @@ def test_convert_to_model_response_object_tool_calls_valid_json_arguments():
assert len(result.choices) == 1
assert result.choices[0].message.content is None
assert len(result.choices[0].message.tool_calls) == 1
assert result.choices[0].message.tool_calls[0].function.name == "get_current_weather"
assert result.choices[0].message.tool_calls[0].function.arguments == '{"location":"Boston, MA","unit":"fahrenheit"}'
assert (
result.choices[0].message.tool_calls[0].function.name == "get_current_weather"
)
assert (
result.choices[0].message.tool_calls[0].function.arguments
== '{"location":"Boston, MA","unit":"fahrenheit"}'
)
assert result.choices[0].finish_reason == "length"
assert result.model == "gpt-4o-2024-08-06"
assert result.created == 1729337288
@ -905,7 +897,6 @@ def test_convert_to_model_response_object_tool_calls_valid_json_arguments():
assert result.usage.total_tokens == 102
assert result.system_fingerprint == "fp_45c6de4934"
def test_convert_to_model_response_object_json_mode():
"""
This test is verifying that when convert_tool_call_to_json_mode is True, a single tool call's arguments are correctly converted into the message content of the response.
@ -956,7 +947,6 @@ def test_convert_to_model_response_object_json_mode():
assert result.usage.prompt_tokens == 5
assert result.usage.completion_tokens == 5
def test_convert_to_model_response_object_with_logprobs():
"""
@ -1192,12 +1182,13 @@ def test_convert_to_model_response_object_with_logprobs():
assert result.usage.prompt_tokens == 9
assert result.usage.completion_tokens == 9
assert result.usage.total_tokens == 18
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(reasoning_tokens=0)
assert result.usage.completion_tokens_details == CompletionTokensDetailsWrapper(
reasoning_tokens=0
)
assert result.system_fingerprint is None
assert result._hidden_params is not None
def test_convert_to_model_response_object_error():
"""Test error handling for None response object."""
with pytest.raises(Exception, match="Error in response object format"):
@ -1212,7 +1203,6 @@ def test_convert_to_model_response_object_error():
convert_tool_call_to_json_mode=False,
)
def test_image_generation_openai_with_pydantic_warning(caplog):
try:
import logging
@ -1250,7 +1240,6 @@ def test_image_generation_openai_with_pydantic_warning(caplog):
except Exception as e:
pytest.fail(f"Test failed with exception: {e}")
def test_convert_to_model_response_object_with_empty_str():
"""Test that convert_to_model_response_object handles empty strings correctly."""
@ -1354,7 +1343,6 @@ def test_convert_to_model_response_object_with_empty_str():
assert resp is not None
assert resp.choices[0].message.content is not None
def test_convert_to_model_response_object_with_thinking_content():
"""Test that convert_to_model_response_object handles thinking content correctly."""
@ -1402,7 +1390,6 @@ def test_convert_to_model_response_object_with_thinking_content():
assert resp is not None
assert resp.choices[0].message.reasoning_content is not None
def test_convert_to_model_response_object_with_empty_error_object():
"""
Test that convert_to_model_response_object handles empty error objects gracefully.
@ -1460,8 +1447,9 @@ def test_convert_to_model_response_object_with_empty_error_object():
assert isinstance(result, ModelResponse)
assert result.model == "minimax-m2.1"
assert len(result.choices) == 1
assert result.choices[0].message.content == "Hey! I'm doing well, thanks for asking!"
assert (
result.choices[0].message.content == "Hey! I'm doing well, thanks for asking!"
)
def test_convert_to_model_response_object_with_real_error():
"""
@ -1494,7 +1482,6 @@ def test_convert_to_model_response_object_with_real_error():
assert hasattr(exc_info.value, "message")
assert "Rate limit exceeded" in str(exc_info.value.message)
def test_convert_to_model_response_object_with_empty_dict_error():
"""
Test that convert_to_model_response_object handles completely empty error dict.
@ -1534,7 +1521,6 @@ def test_convert_to_model_response_object_with_empty_dict_error():
assert isinstance(result, ModelResponse)
assert result.choices[0].message.content == "Hello!"
def test_convert_to_model_response_object_preserves_provider_specific_fields_from_proxy():
"""
Test that provider_specific_fields (e.g. Anthropic citations) are preserved
@ -1612,13 +1598,18 @@ def test_convert_to_model_response_object_preserves_provider_specific_fields_fro
assert result.id == "chatcmpl-proxy-123"
choice = result.choices[0]
assert choice.message.content == "Based on current reviews, the Sony WH-1000XM5 remains one of the best headphones."
assert (
choice.message.content
== "Based on current reviews, the Sony WH-1000XM5 remains one of the best headphones."
)
assert choice.message.provider_specific_fields is not None
assert "citations" in choice.message.provider_specific_fields
assert choice.message.provider_specific_fields["citations"] == citations
assert "web_search_results" in choice.message.provider_specific_fields
assert choice.message.provider_specific_fields["web_search_results"] == web_search_results
assert (
choice.message.provider_specific_fields["web_search_results"]
== web_search_results
)
def test_convert_to_model_response_object_keeps_reasoning_items_from_a_cached_response():
reasoning_items: Final = [
@ -1706,7 +1697,6 @@ def test_convert_to_model_response_object_provider_specific_fields_merges_extra_
assert "custom_extra_field" in psf
assert psf["custom_extra_field"] == "extra_value"
def test_convert_to_model_response_object_no_provider_specific_fields_still_works():
"""
Test that responses without provider_specific_fields continue to work as before.
@ -1752,7 +1742,6 @@ def test_convert_to_model_response_object_no_provider_specific_fields_still_work
assert result.choices[0].message.refusal is None
assert result.choices[0].message.provider_specific_fields is None
def test_convert_to_model_response_object_with_error_code_only():
"""
Test that errors with only a code (no message) are still treated as real errors.
@ -1778,7 +1767,6 @@ def test_convert_to_model_response_object_with_error_code_only():
assert exc_info.value.status_code == 500
def test_model_prefix_preservation():
"""
Test that when model_response_object has a prefix like 'openai/gpt-4'
@ -1807,7 +1795,6 @@ def test_model_prefix_preservation():
assert result.model == "openai/gpt-4o"
def test_model_without_prefix():
"""
Test that when model_response_object has no prefix (e.g. 'gpt-4'),
@ -1836,7 +1823,6 @@ def test_model_without_prefix():
assert result.model == "gpt-4"
def test_extra_response_fields_preserved():
"""
Test that extra response fields (e.g. service_tier) are preserved
@ -1866,7 +1852,6 @@ def test_extra_response_fields_preserved():
assert result.service_tier == "default"
def test_hidden_params_and_response_headers_set():
"""
Test that _hidden_params and _response_headers are correctly set
@ -1901,7 +1886,6 @@ def test_hidden_params_and_response_headers_set():
assert "additional_headers" in result._hidden_params
assert result._response_headers == response_headers
def test_response_ms_computed():
"""
Test that _response_ms is computed correctly from start_time and end_time.
@ -1931,7 +1915,6 @@ def test_response_ms_computed():
assert result._response_ms == pytest.approx(250.0)
def test_error_message_includes_function_args():
"""
Test that when an exception occurs, the error message includes
@ -1944,7 +1927,7 @@ def test_error_message_includes_function_args():
"choices": [{"index": 0}],
}
with pytest.raises(Exception, match="in convert_to_model_response_object") as exc_info:
with pytest.raises(Exception, match='in convert_to_model_response_object') as exc_info:
convert_to_model_response_object(
model_response_object=ModelResponse(),
response_object=response_object,
@ -1958,7 +1941,6 @@ def test_error_message_includes_function_args():
assert "response_object" in error_msg
assert "response_type" in error_msg
@pytest.mark.parametrize("falsy_id", [None, ""])
def test_convert_to_model_response_object_falsy_id_preserves_auto_generated(falsy_id):
"""Test that a falsy id in response_object preserves the auto-generated id."""
@ -1986,7 +1968,6 @@ def test_convert_to_model_response_object_falsy_id_preserves_auto_generated(fals
assert result.id == original_id
assert result.id.startswith("chatcmpl-")
def test_convert_to_model_response_object_default_usage_overwritten():
"""
Regression test: convert_to_model_response_object must properly set Usage
@ -2030,7 +2011,6 @@ def test_convert_to_model_response_object_default_usage_overwritten():
assert result.usage.completion_tokens == 7
assert result.usage.total_tokens == 22
def test_convert_to_model_response_object_with_null_top_logprobs():
"""
Test that convert_to_model_response_object handles null top_logprobs
@ -2122,7 +2102,6 @@ def test_convert_to_model_response_object_with_null_top_logprobs():
assert token_logprob.top_logprobs == []
assert isinstance(token_logprob.top_logprobs, list)
class TestMissingChoicesGuard:
"""
Tests for the defense-in-depth guard that raises APIError when a provider
@ -2248,7 +2227,9 @@ class TestMissingChoicesGuard:
async def consume():
chunks = []
async for chunk in convert_to_streaming_response_async(response_object=response_object):
async for chunk in convert_to_streaming_response_async(
response_object=response_object
):
chunks.append(chunk)
return chunks
@ -2276,7 +2257,6 @@ class TestMissingChoicesGuard:
assert "copilot_usage" in exc_info.value.message
class TestNormalizeImagesForMessage:
def test_none_returns_none(self):
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
@ -2312,7 +2292,6 @@ class TestNormalizeImagesForMessage:
result = _normalize_images_for_message(images)
assert result[0]["index"] == 5
class TestSafeConvertCreatedField:
def test_none_returns_current_time(self):
import time
@ -2355,7 +2334,6 @@ class TestSafeConvertCreatedField:
result = safe_convert_created_field("not-a-number")
assert abs(result - int(time.time())) <= 1
class TestConvertToStreamingResponse:
def test_none_raises(self):
@ -2448,7 +2426,9 @@ class TestConvertToStreamingResponse:
async def run():
chunks = []
async for chunk in convert_to_streaming_response_async(response_object=response_object):
async for chunk in convert_to_streaming_response_async(
response_object=response_object
):
chunks.append(chunk)
return chunks
@ -2457,7 +2437,6 @@ class TestConvertToStreamingResponse:
assert chunks[0].choices[0].delta.tool_calls[0].id == "call_1"
assert chunks[0].choices[0].delta.tool_calls[0].function.name == "get_weather"
class TestConvertToStreamingResponseAsync:
def test_none_raises(self):
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
@ -2495,7 +2474,9 @@ class TestConvertToStreamingResponseAsync:
async def run():
chunks = []
async for chunk in convert_to_streaming_response_async(response_object=response_object):
async for chunk in convert_to_streaming_response_async(
response_object=response_object
):
chunks.append(chunk)
return chunks
@ -2510,7 +2491,6 @@ class TestConvertToStreamingResponseAsync:
assert chunks[-1].choices[0].finish_reason == "stop"
assert chunks[-1].usage.prompt_tokens == 3
class TestHandleInvalidParallelToolCalls:
def test_none_input(self):
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
@ -2590,7 +2570,6 @@ class TestHandleInvalidParallelToolCalls:
assert len(result) == 1
assert result[0].id == "call_1"
class TestShouldConvertToolCallToJsonMode:
def test_returns_true_when_conditions_met(self):
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
@ -2599,7 +2578,12 @@ class TestShouldConvertToolCallToJsonMode:
from litellm.constants import RESPONSE_FORMAT_TOOL_NAME
tool_calls = [{"function": {"name": RESPONSE_FORMAT_TOOL_NAME}}]
assert should_convert_tool_call_to_json_mode(tool_calls=tool_calls, convert_tool_call_to_json_mode=True) is True
assert (
should_convert_tool_call_to_json_mode(
tool_calls=tool_calls, convert_tool_call_to_json_mode=True
)
is True
)
def test_returns_false_when_flag_off(self):
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
@ -2609,7 +2593,10 @@ class TestShouldConvertToolCallToJsonMode:
tool_calls = [{"function": {"name": RESPONSE_FORMAT_TOOL_NAME}}]
assert (
should_convert_tool_call_to_json_mode(tool_calls=tool_calls, convert_tool_call_to_json_mode=False) is False
should_convert_tool_call_to_json_mode(
tool_calls=tool_calls, convert_tool_call_to_json_mode=False
)
is False
)
def test_returns_false_when_wrong_tool_name(self):
@ -2619,7 +2606,10 @@ class TestShouldConvertToolCallToJsonMode:
tool_calls = [{"function": {"name": "some_other_tool"}}]
assert (
should_convert_tool_call_to_json_mode(tool_calls=tool_calls, convert_tool_call_to_json_mode=True) is False
should_convert_tool_call_to_json_mode(
tool_calls=tool_calls, convert_tool_call_to_json_mode=True
)
is False
)
def test_returns_false_when_multiple_tool_calls(self):
@ -2633,7 +2623,10 @@ class TestShouldConvertToolCallToJsonMode:
{"function": {"name": "other"}},
]
assert (
should_convert_tool_call_to_json_mode(tool_calls=tool_calls, convert_tool_call_to_json_mode=True) is False
should_convert_tool_call_to_json_mode(
tool_calls=tool_calls, convert_tool_call_to_json_mode=True
)
is False
)
def test_returns_false_when_none(self):
@ -2641,8 +2634,12 @@ class TestShouldConvertToolCallToJsonMode:
should_convert_tool_call_to_json_mode,
)
assert should_convert_tool_call_to_json_mode(tool_calls=None, convert_tool_call_to_json_mode=True) is False
assert (
should_convert_tool_call_to_json_mode(
tool_calls=None, convert_tool_call_to_json_mode=True
)
is False
)
class TestConvertToolCallToJsonMode:
def test_converts_when_should(self):
@ -2662,7 +2659,9 @@ class TestConvertToolCallToJsonMode:
),
)
]
message, finish_reason = convert_fn(tool_calls=tool_calls, convert_tool_call_to_json_mode=True)
message, finish_reason = convert_fn(
tool_calls=tool_calls, convert_tool_call_to_json_mode=True
)
assert message is not None
assert message.content == '{"key": "value"}'
assert finish_reason == "stop"
@ -2684,11 +2683,12 @@ class TestConvertToolCallToJsonMode:
),
)
]
message, finish_reason = convert_fn(tool_calls=tool_calls, convert_tool_call_to_json_mode=False)
message, finish_reason = convert_fn(
tool_calls=tool_calls, convert_tool_call_to_json_mode=False
)
assert message is None
assert finish_reason is None
class TestConvertToModelResponseObjectEmbedding:
def test_basic_embedding_response(self):
from litellm.types.utils import EmbeddingResponse
@ -2714,7 +2714,6 @@ class TestConvertToModelResponseObjectEmbedding:
assert result.data == [{"embedding": [0.1, 0.2, 0.3], "index": 0}]
assert result.usage.prompt_tokens == 5
class TestConvertToModelResponseObjectAudioTranscription:
def test_basic_transcription(self):
from litellm.types.utils import TranscriptionResponse
@ -2774,7 +2773,6 @@ class TestConvertToModelResponseObjectAudioTranscription:
assert result.usage.output_tokens == 5
assert result.usage.input_token_details.audio_tokens == 4
class TestConvertToModelResponseObjectRerank:
def test_basic_rerank(self):
from litellm.types.utils import RerankResponse
@ -2793,7 +2791,6 @@ class TestConvertToModelResponseObjectRerank:
assert result.id == "rerank-123"
assert result.results[0]["relevance_score"] == 0.9
class TestConvertToModelResponseObjectCompletion:
def test_tool_calls_finish_reason_override(self):
response_object = {