Skip to content

Commit 6bee147

Browse files
authored
fix(utils): handle image_url string shorthand in _is_image_type_with_blob_content (#6478)
## What's broken `_is_image_type_with_blob_content` crashes with `AttributeError: 'str' object has no attribute 'get'` when `image_url` is a plain string shorthand (e.g. `{"type": "image_url", "image_url": "data:image/jpeg;base64,..."}`) instead of a dict. The OpenAI API supports this form and `transform_openai_content_part` in the same file documents it. Any call to `redact_blob_message_parts` — which is invoked from `truncate_and_annotate_messages` across the OpenAI, LiteLLM, Anthropic, LangChain, pydantic-ai, and openai-agents integrations when `send_default_pii=True` — would crash on such input. ## Why it happens `item.get("image_url", {})` returns the raw string when the value is a string, and the subsequent `.get("url", "")` call fails because strings have no `.get` method. ## Fix Check `isinstance(image_url_val, dict)` before calling `.get("url", "")`, falling back to using the string directly. The same guard is applied to the redaction path at line 702 where `item["image_url"]["url"]` would also fail on a string. ## Test Added `test_redact_blob_message_parts_image_url_string_shorthand` to `TestRedactBlobMessageParts` which passes a message with the string shorthand form and asserts it is redacted to `[Blob substitute]` without raising. Fixes #6477 Co-authored-by: devteamaegis <devteamaegis@users.noreply.github.com>
1 parent 7f724ec commit 6bee147

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

sentry_sdk/ai/utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,12 @@ def _is_image_type_with_blob_content(item: "Dict[str, Any]") -> bool:
609609
if item.get("type") != "image_url":
610610
return False
611611

612-
image_url = item.get("image_url", {}).get("url", "")
612+
image_url_val = item.get("image_url")
613+
image_url = (
614+
image_url_val.get("url", "")
615+
if isinstance(image_url_val, dict)
616+
else (image_url_val or "")
617+
)
613618
data_url_match = DATA_URL_BASE64_REGEX.match(image_url)
614619

615620
return bool(data_url_match)
@@ -694,7 +699,10 @@ def redact_blob_message_parts(
694699
if item.get("type") == "blob":
695700
item["content"] = BLOB_DATA_SUBSTITUTE
696701
elif _is_image_type_with_blob_content(item):
697-
item["image_url"]["url"] = BLOB_DATA_SUBSTITUTE
702+
if isinstance(item["image_url"], dict):
703+
item["image_url"]["url"] = BLOB_DATA_SUBSTITUTE
704+
else:
705+
item["image_url"] = BLOB_DATA_SUBSTITUTE
698706

699707
return messages_copy
700708

tests/test_ai_monitoring.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,23 @@ def test_handles_non_dict_content_items(self):
923923
# Should return same list since no blobs
924924
assert result is messages
925925

926+
def test_redact_blob_message_parts_image_url_string_shorthand(self):
927+
"""image_url as a plain string (OpenAI shorthand) must not raise AttributeError"""
928+
messages = [
929+
{
930+
"role": "user",
931+
"content": [
932+
{"type": "text", "text": "What is in this image?"},
933+
{
934+
"type": "image_url",
935+
"image_url": "data:image/jpeg;base64,/9j/abc123==",
936+
},
937+
],
938+
}
939+
]
940+
result = redact_blob_message_parts(messages)
941+
assert result[0]["content"][1]["image_url"] == "[Blob substitute]"
942+
926943

927944
class TestParseDataUri:
928945
def test_parses_base64_image_data_uri(self):

0 commit comments

Comments
 (0)