diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 42dac6167e0..3758a6fca04 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1057,7 +1057,10 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): verbose_logger.debug("Chat provider: passthrough -> %s", item) else: # Default to input_text for unknown types - converted = self._convert_content_str_to_input_text(str(item.get("text", item)), role) + converted = with_prompt_cache_breakpoint( + self._convert_content_str_to_input_text(str(item.get("text", item)), role), + item.get("prompt_cache_breakpoint"), + ) result.append(converted) verbose_logger.debug("Chat provider: unknown(%s) -> %s", original_type, converted) verbose_logger.debug("Chat provider: Final converted content: %s", result) diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index eeb951ce2da..433f45fb84d 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -4015,3 +4015,38 @@ def test_convert_chat_completion_messages_to_responses_api_omits_absent_prompt_c response, _ = handler.convert_chat_completion_messages_to_responses_api(messages) assert all("prompt_cache_breakpoint" not in block for block in response[0]["content"]) + + +def test_convert_chat_completion_messages_to_responses_api_keeps_prompt_cache_breakpoint_on_unknown_block(): + """A block type the bridge cannot map still has to keep the marker. + + The hook marks the last block of the message it targets, so a message ending in a block this + bridge does not know falls through to the stringify path. Losing the marker there is the same + failure as losing it on a text block: the request goes out in explicit mode with nothing marked. + """ + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, + ) + + handler = LiteLLMResponsesTransformationHandler() + breakpoint_marker = {"mode": "explicit"} + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "describe this"}, + { + "type": "input_audio", + "input_audio": {"data": "Zm9v", "format": "wav"}, + "prompt_cache_breakpoint": breakpoint_marker, + }, + ], + }, + ] + + response, _ = handler.convert_chat_completion_messages_to_responses_api(messages) + + content = response[0]["content"] + assert [block["type"] for block in content] == ["input_text", "input_text"] + assert content[1]["prompt_cache_breakpoint"] == breakpoint_marker diff --git a/tests/test_litellm/responses/test_responses_prompt_management.py b/tests/test_litellm/responses/test_responses_prompt_management.py index 6f8faa050dd..c44ddffe77d 100644 --- a/tests/test_litellm/responses/test_responses_prompt_management.py +++ b/tests/test_litellm/responses/test_responses_prompt_management.py @@ -428,7 +428,6 @@ class TestResponsesAPIPromptManagement: sent_input = mock_handler.call_args.kwargs["input"] assert "cache_control" not in sent_input[0] - def test_all_non_message_input_items_remain_unchanged(self): reasoning_item = { "type": "reasoning",