diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 4e27b756b68..7e126099391 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -1158,7 +1158,7 @@ class LiteLLMCompletionResponsesConfig: file_dict["file_data"] = item["file_data"] new_item: dict[str, Any] = {"type": "file", "file": file_dict} - if "cache_control" in item: + if item.get("cache_control") is not None: new_item["cache_control"] = item["cache_control"] return new_item @@ -1180,16 +1180,15 @@ class LiteLLMCompletionResponsesConfig: """ Normalize a Responses API object (Pydantic model or custom class) to a dictionary """ - if hasattr(item, "model_dump"): if hasattr(item, "model_dump"): try: return item.model_dump(exclude_none=True) - except Exception: # noqa: BLE001 # fallback if custom model_dump does not accept exclude_none + except TypeError: return item.model_dump() elif hasattr(item, "dict"): try: return item.dict(exclude_none=True) - except Exception: # noqa: BLE001 # fallback if custom dict does not accept exclude_none + except TypeError: return item.dict() target_attrs = ( @@ -1247,7 +1246,11 @@ class LiteLLMCompletionResponsesConfig: elif item.get("type") == "input_image": image_block = { **LiteLLMCompletionResponsesConfig._transform_input_image_item_to_image_item(item), - **({"cache_control": item["cache_control"]} if "cache_control" in item else {}), + **( + {"cache_control": item["cache_control"]} + if item.get("cache_control") is not None + else {} + ), } content_list.append(image_block) else: @@ -1260,7 +1263,11 @@ class LiteLLMCompletionResponsesConfig: item.get("type") or "text" ), "text": text_value, - **({"cache_control": item["cache_control"]} if "cache_control" in item else {}), + **( + {"cache_control": item["cache_control"]} + if item.get("cache_control") is not None + else {} + ), } content_list.append(content_block) return content_list diff --git a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py index 62bc61c1a63..ae2e624ef70 100644 --- a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -2635,6 +2635,33 @@ class TestCacheControlPreservation: ) assert messages == [{"role": "user", "content": "hello", "cache_control": {"type": "ephemeral"}}] + def test_none_cache_control_omitted_from_content_blocks(self): + from pydantic import BaseModel + from typing import Optional + + class MockContentItem(BaseModel): + type: str = "text" + text: str = "hello world" + cache_control: Optional[dict] = None + + item = MockContentItem() + result = LiteLLMCompletionResponsesConfig._transform_responses_api_content_to_chat_completion_content([item]) + assert isinstance(result, list) + assert len(result) == 1 + assert "cache_control" not in result[0] + + dict_item = {"type": "text", "text": "hello", "cache_control": None} + result_dict = LiteLLMCompletionResponsesConfig._transform_responses_api_content_to_chat_completion_content([dict_item]) + assert isinstance(result_dict, list) + assert len(result_dict) == 1 + assert "cache_control" not in result_dict[0] + + image_item = {"type": "input_image", "image_url": "https://example.com/a.png", "cache_control": None} + result_img = LiteLLMCompletionResponsesConfig._transform_responses_api_content_to_chat_completion_content([image_item]) + assert isinstance(result_img, list) + assert len(result_img) == 1 + assert "cache_control" not in result_img[0] + def test_tool_call_output_as_custom_object(self): """Test _transform_responses_api_tool_call_output_to_chat_completion_message with a custom object.""" class MockToolCallOutput: