From 59f29ca6dfb51f5e45fdd4b8a764e7f048caea2d Mon Sep 17 00:00:00 2001 From: Simon Sorg Date: Mon, 21 Sep 2026 21:51:39 +0200 Subject: [PATCH] fix(responses): preserve multimodal cache breakpoints --- .../transformation.py | 18 +++++--- ...responses_transformation_transformation.py | 46 +++++++++++-------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index e2fe45102d4..8020cf41e68 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1069,11 +1069,14 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): verbose_logger.debug("Chat provider: text -> %s", converted) elif original_type == "image_url": # Map to responses API image format - converted = cast( - dict, - self._convert_content_to_responses_format_image( - cast(ChatCompletionImageObject, item), role + converted = with_prompt_cache_breakpoint( + cast( + dict, + self._convert_content_to_responses_format_image( + cast(ChatCompletionImageObject, item), role + ), ), + item.get("prompt_cache_breakpoint"), ) result.append(converted) verbose_logger.debug("Chat provider: image_url -> %s", converted) @@ -1085,8 +1088,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): result.append(converted) verbose_logger.debug("Chat provider: image -> %s", converted) elif item_type == "file": - converted = _input_file_from_file_value( - cast("ChatCompletionFileObject", item).get("file"), # cast-ok: type tag checked + converted = with_prompt_cache_breakpoint( + _input_file_from_file_value( + cast("ChatCompletionFileObject", item).get("file"), # cast-ok: type tag checked + ), + item.get("prompt_cache_breakpoint"), ) result.append(converted) verbose_logger.debug("Chat provider: file -> %s", converted) diff --git a/tests/unit/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/unit/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index 78aac112b2a..90144e47e7f 100644 --- a/tests/unit/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/unit/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -4185,24 +4185,38 @@ def _system_input_item(text: str) -> dict[str, object]: return {"type": "message", "role": "system", "content": [{"type": "input_text", "text": text}]} -def test_prompt_cache_breakpoint_survives_chat_to_responses_conversion() -> None: +@pytest.mark.parametrize( + ("content_block", "expected_content"), + [ + ( + {"type": "text", "text": "Stable prefix"}, + {"type": "input_text", "text": "Stable prefix"}, + ), + ( + {"type": "image_url", "image_url": "https://example.com/image.png"}, + {"type": "input_image", "image_url": "https://example.com/image.png", "detail": "auto"}, + ), + ( + {"type": "file", "file": {"file_id": "file-123"}}, + {"type": "input_file", "file_id": "file-123"}, + ), + ], + ids=("text", "image_url", "file"), +) +def test_prompt_cache_breakpoint_survives_chat_to_responses_conversion( + content_block: dict[str, object], expected_content: dict[str, object] +) -> None: handler: Final = LiteLLMResponsesTransformationHandler() cache_breakpoint: Final = {"mode": "explicit"} + marked_content: Final = {**content_block, "prompt_cache_breakpoint": cache_breakpoint} request: Final = handler.transform_request( model="gpt-5.6-sol", messages=[ { - "role": "system", - "content": [ - { - "type": "text", - "text": "Stable prefix", - "prompt_cache_breakpoint": cache_breakpoint, - } - ], - }, - {"role": "user", "content": "Use a tool"}, + "role": "user", + "content": [marked_content], + } ], optional_params={"prompt_cache_options": cache_breakpoint}, litellm_params={}, @@ -4212,14 +4226,8 @@ def test_prompt_cache_breakpoint_survives_chat_to_responses_conversion() -> None assert request["input"][0] == { "type": "message", - "role": "system", - "content": [ - { - "type": "input_text", - "text": "Stable prefix", - "prompt_cache_breakpoint": cache_breakpoint, - } - ], + "role": "user", + "content": [{**expected_content, "prompt_cache_breakpoint": cache_breakpoint}], } assert request["prompt_cache_options"] == cache_breakpoint