Skip to content

Commit 4a17806

Browse files
committed
fix(integrations): ensure _convert_message_parts does not mutate original messages and handle data URLs correctly
1 parent d9d1264 commit 4a17806

File tree

2 files changed

+90
-7
lines changed

2 files changed

+90
-7
lines changed

sentry_sdk/integrations/litellm.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
from typing import TYPE_CHECKING
23

34
import sentry_sdk
@@ -72,21 +73,27 @@ def _convert_message_parts(messages: "List[Dict[str, Any]]") -> "List[Dict[str,
7273
]
7374
}
7475
"""
76+
# Deep copy to avoid mutating original messages from kwargs
77+
messages = copy.deepcopy(messages)
7578

7679
def _map_item(item: "Dict[str, Any]") -> "Dict[str, Any]":
7780
if item.get("type") == "image_url":
7881
image_url = item.get("image_url") or {}
79-
if image_url.get("url", "").startswith("data:"):
82+
url = image_url.get("url", "")
83+
if url.startswith("data:") and ";base64," in url:
84+
parts = url.split(";base64,", 1)
85+
# Remove "data:" prefix (5 chars) to get proper MIME type
86+
mime_type = parts[0][5:]
8087
return {
8188
"type": "blob",
8289
"modality": "image",
83-
"mime_type": item["image_url"]["url"].split(";base64,")[0],
84-
"content": item["image_url"]["url"].split(";base64,")[1],
90+
"mime_type": mime_type,
91+
"content": parts[1],
8592
}
86-
else:
93+
elif url:
8794
return {
8895
"type": "uri",
89-
"uri": item["image_url"]["url"],
96+
"uri": url,
9097
}
9198
return item
9299

tests/integrations/litellm/test_litellm.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ def test_binary_content_encoding_image_url(sentry_init, capture_events):
805805
)
806806
assert blob_item is not None
807807
assert blob_item["modality"] == "image"
808-
assert blob_item["mime_type"] == "data:image/png"
808+
assert blob_item["mime_type"] == "image/png"
809809
assert IMAGE_B64 in blob_item["content"] or "[Filtered]" in str(
810810
blob_item["content"]
811811
)
@@ -912,5 +912,81 @@ def test_convert_message_parts_direct():
912912
item for item in converted[0]["content"] if item.get("type") == "blob"
913913
)
914914
assert blob_item["modality"] == "image"
915-
assert blob_item["mime_type"] == "data:image/png"
915+
assert blob_item["mime_type"] == "image/png"
916916
assert IMAGE_B64 in blob_item["content"]
917+
918+
919+
def test_convert_message_parts_does_not_mutate_original():
920+
"""Ensure _convert_message_parts does not mutate the original messages."""
921+
original_url = IMAGE_DATA_URI
922+
messages = [
923+
{
924+
"role": "user",
925+
"content": [
926+
{
927+
"type": "image_url",
928+
"image_url": {"url": original_url},
929+
},
930+
],
931+
}
932+
]
933+
_convert_message_parts(messages)
934+
# Original should be unchanged
935+
assert messages[0]["content"][0]["type"] == "image_url"
936+
assert messages[0]["content"][0]["image_url"]["url"] == original_url
937+
938+
939+
def test_convert_message_parts_data_url_without_base64():
940+
"""Data URLs without ;base64, marker should be treated as regular URIs."""
941+
messages = [
942+
{
943+
"role": "user",
944+
"content": [
945+
{
946+
"type": "image_url",
947+
"image_url": {"url": "data:image/png,rawdata"},
948+
},
949+
],
950+
}
951+
]
952+
converted = _convert_message_parts(messages)
953+
uri_item = converted[0]["content"][0]
954+
# Should be converted to uri type, not blob (since no base64 encoding)
955+
assert uri_item["type"] == "uri"
956+
assert uri_item["uri"] == "data:image/png,rawdata"
957+
958+
959+
def test_convert_message_parts_image_url_none():
960+
"""image_url being None should not crash."""
961+
messages = [
962+
{
963+
"role": "user",
964+
"content": [
965+
{
966+
"type": "image_url",
967+
"image_url": None,
968+
},
969+
],
970+
}
971+
]
972+
converted = _convert_message_parts(messages)
973+
# Should return item unchanged
974+
assert converted[0]["content"][0]["type"] == "image_url"
975+
976+
977+
def test_convert_message_parts_image_url_missing_url():
978+
"""image_url missing the url key should not crash."""
979+
messages = [
980+
{
981+
"role": "user",
982+
"content": [
983+
{
984+
"type": "image_url",
985+
"image_url": {"detail": "high"},
986+
},
987+
],
988+
}
989+
]
990+
converted = _convert_message_parts(messages)
991+
# Should return item unchanged
992+
assert converted[0]["content"][0]["type"] == "image_url"

0 commit comments

Comments
 (0)