From f925c1d1e6b34da7aa36089dad232f51cc32b8a0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:41:05 +0000 Subject: [PATCH 1/4] fix(azure): strip litellm format field from file and image content parts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../prompt_templates/factory.py | 9 +++++ ...llm_core_utils_prompt_templates_factory.py | 31 ++++++++++++++++ .../test_azure_chat_gpt_transformation.py | 35 +++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 21ae8b001dd..f9b8922d019 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1067,6 +1067,13 @@ def _azure_tool_call_invoke_helper( def _azure_image_url_helper(content: ChatCompletionImageObject): if isinstance(content["image_url"], str): content["image_url"] = {"url": content["image_url"]} + elif isinstance(content["image_url"], dict): + content["image_url"].pop("format", None) + + +def _azure_file_helper(content: ChatCompletionFileObject) -> None: + if isinstance(content.get("file"), dict): + content["file"].pop("format", None) def convert_to_azure_openai_messages( @@ -1082,6 +1089,8 @@ def convert_to_azure_openai_messages( for content in m.get("content", []): if isinstance(content, dict) and content.get("type") == "image_url": _azure_image_url_helper(content) + elif isinstance(content, dict) and content.get("type") == "file": + _azure_file_helper(content) return messages diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index 66d10fd1407..bbfafb41243 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -297,6 +297,37 @@ def test_convert_to_azure_openai_messages(): assert content == expected_content +def test_convert_to_azure_openai_messages_strips_litellm_format_from_file_and_image(): + """Managed file ids write file.format = MIME type, which Azure rejects""" + + from litellm.litellm_core_utils.prompt_templates.factory import ( + convert_to_azure_openai_messages, + ) + from litellm.types.llms.openai import AllMessageValues + + input: list[AllMessageValues] = [ + { + "role": "user", + "content": [ + { + "type": "file", + "file": {"file_id": "assistant-xyz", "format": "application/pdf"}, + }, + { + "type": "image_url", + "image_url": {"url": "https://x/y.png", "format": "image/png"}, + }, + ], + } + ] + + output = convert_to_azure_openai_messages(input) + + content = output[0].get("content") + assert content[0]["file"] == {"file_id": "assistant-xyz"} + assert content[1]["image_url"] == {"url": "https://x/y.png"} + + def test_bedrock_validate_format_image_or_video(): """Test the _validate_format method for images, videos, and documents""" diff --git a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py index bc6cb0c0fed..b5c72d5bb06 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py @@ -307,3 +307,38 @@ class TestAzureToolSchemaCombinatorFlattening: ) assert "tools" not in request assert request["temperature"] == 0.2 + + +def test_transform_request_strips_litellm_format_from_managed_file_id(): + """update_messages_with_model_file_ids writes file.format = MIME type, which Azure rejects""" + import base64 + + from litellm.litellm_core_utils.prompt_templates.common_utils import ( + update_messages_with_model_file_ids, + ) + + managed_file_id: Final = base64.b64encode( + b"litellm_proxy:application/pdf;unified_id,abc123;llm_output_file_id,assistant-xyz;target_model_names,azure-gpt" + ).decode() + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Summarize this file"}, + {"type": "file", "file": {"file_id": managed_file_id}}, + ], + } + ] + messages = update_messages_with_model_file_ids(messages, None, {}) + + request = AzureOpenAIConfig().transform_request( + model="gpt-5.4", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + file_part = request["messages"][0]["content"][1]["file"] + assert "format" not in file_part + assert file_part["file_id"] == "assistant-xyz" From d993014dc6f992729587205108355040152e3d1b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:53:03 +0000 Subject: [PATCH 2/4] fix(azure): satisfy type-check gate in file and image format stripping Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/litellm_core_utils/prompt_templates/factory.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index f9b8922d019..ebfb91f2f45 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1067,13 +1067,12 @@ def _azure_tool_call_invoke_helper( def _azure_image_url_helper(content: ChatCompletionImageObject): if isinstance(content["image_url"], str): content["image_url"] = {"url": content["image_url"]} - elif isinstance(content["image_url"], dict): + else: content["image_url"].pop("format", None) def _azure_file_helper(content: ChatCompletionFileObject) -> None: - if isinstance(content.get("file"), dict): - content["file"].pop("format", None) + content.get("file", {}).pop("format", None) def convert_to_azure_openai_messages( @@ -1088,9 +1087,9 @@ def convert_to_azure_openai_messages( if m["role"] == "user" and isinstance(m.get("content"), list): for content in m.get("content", []): if isinstance(content, dict) and content.get("type") == "image_url": - _azure_image_url_helper(content) + _azure_image_url_helper(cast(ChatCompletionImageObject, content)) elif isinstance(content, dict) and content.get("type") == "file": - _azure_file_helper(content) + _azure_file_helper(cast(ChatCompletionFileObject, content)) return messages From 81524d212f8b7012639a9a6e2e1abf552cba3e45 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:54:35 +0000 Subject: [PATCH 3/4] fix(azure): rebuild content dicts instead of mutating, drop test docstrings Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/prompt_templates/factory.py | 12 ++++++++++-- ...st_litellm_core_utils_prompt_templates_factory.py | 2 -- .../azure/chat/test_azure_chat_gpt_transformation.py | 1 - 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index ebfb91f2f45..d5bf44e5e3e 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -30,8 +30,10 @@ from litellm.types.llms.openai import ( ChatCompletionAssistantMessage, ChatCompletionAssistantToolCall, ChatCompletionFileObject, + ChatCompletionFileObjectFile, ChatCompletionFunctionMessage, ChatCompletionImageObject, + ChatCompletionImageUrlObject, ChatCompletionTextObject, ChatCompletionToolCallFunctionChunk, ChatCompletionToolMessage, @@ -1068,11 +1070,17 @@ def _azure_image_url_helper(content: ChatCompletionImageObject): if isinstance(content["image_url"], str): content["image_url"] = {"url": content["image_url"]} else: - content["image_url"].pop("format", None) + content["image_url"] = cast( + ChatCompletionImageUrlObject, + {k: v for k, v in content["image_url"].items() if k != "format"}, + ) def _azure_file_helper(content: ChatCompletionFileObject) -> None: - content.get("file", {}).pop("format", None) + content["file"] = cast( + ChatCompletionFileObjectFile, + {k: v for k, v in content.get("file", {}).items() if k != "format"}, + ) def convert_to_azure_openai_messages( diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index bbfafb41243..3293048135c 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -298,8 +298,6 @@ def test_convert_to_azure_openai_messages(): def test_convert_to_azure_openai_messages_strips_litellm_format_from_file_and_image(): - """Managed file ids write file.format = MIME type, which Azure rejects""" - from litellm.litellm_core_utils.prompt_templates.factory import ( convert_to_azure_openai_messages, ) diff --git a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py index b5c72d5bb06..774b58369fb 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py @@ -310,7 +310,6 @@ class TestAzureToolSchemaCombinatorFlattening: def test_transform_request_strips_litellm_format_from_managed_file_id(): - """update_messages_with_model_file_ids writes file.format = MIME type, which Azure rejects""" import base64 from litellm.litellm_core_utils.prompt_templates.common_utils import ( From f21953571765ec04461958c9c1f2f9434cbaf4ad Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:06:31 +0000 Subject: [PATCH 4/4] test(azure): avoid rebinding messages in managed file id regression test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/azure/chat/test_azure_chat_gpt_transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py index 774b58369fb..c8451b9b48f 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_chat_gpt_transformation.py @@ -328,11 +328,11 @@ def test_transform_request_strips_litellm_format_from_managed_file_id(): ], } ] - messages = update_messages_with_model_file_ids(messages, None, {}) + updated_messages = update_messages_with_model_file_ids(messages, None, {}) request = AzureOpenAIConfig().transform_request( model="gpt-5.4", - messages=messages, + messages=updated_messages, optional_params={}, litellm_params={}, headers={},