Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/google/adk/tools/load_artifacts_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,23 @@
'video/',
)
_GEMINI_SUPPORTED_INLINE_MIME_TYPES = frozenset({'application/pdf'})
# MIME subtypes that match a supported prefix above but that Gemini
# rejects with 400 INVALID_ARGUMENT when sent as inline data. These
# must fall through to the text-conversion path in
# `_as_safe_part_for_llm` instead of being forwarded as inline image
# data. Verified empirically against gemini-2.5-flash via
# google-genai 1.69.0 on 2026-05-13.
_GEMINI_UNSUPPORTED_INLINE_SUBTYPES = frozenset({
'image/svg+xml',
})
_TEXT_LIKE_MIME_TYPES = frozenset({
'application/csv',
'application/json',
'application/xml',
# SVG is XML-based and Gemini rejects it as inline image data (see
# _GEMINI_UNSUPPORTED_INLINE_SUBTYPES above), so it falls through here
# and is delivered to the model as text.
'image/svg+xml',
})

if TYPE_CHECKING:
Expand All @@ -60,6 +73,8 @@ def _is_inline_mime_type_supported(mime_type: str | None) -> bool:
normalized = _normalize_mime_type(mime_type)
if not normalized:
return False
if normalized in _GEMINI_UNSUPPORTED_INLINE_SUBTYPES:
return False
return normalized.startswith(_GEMINI_SUPPORTED_INLINE_MIME_PREFIXES) or (
normalized in _GEMINI_SUPPORTED_INLINE_MIME_TYPES
)
Expand Down
43 changes: 43 additions & 0 deletions tests/unittests/tools/test_load_artifacts_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,49 @@ async def test_load_artifacts_keeps_supported_mime_types():
assert artifact_part.inline_data.mime_type == 'application/pdf'


@mark.asyncio
async def test_load_artifacts_converts_svg_to_text():
"""`image/svg+xml` matches the `image/` prefix but is rejected by Gemini
with 400 INVALID_ARGUMENT, so it must fall through to the text-conversion
path instead of being forwarded as inline image data.
"""
artifact_name = 'logo.svg'
svg_bytes = (
b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">'
b'<circle cx="5" cy="5" r="4"/></svg>'
)
artifact = types.Part(
inline_data=types.Blob(data=svg_bytes, mime_type='image/svg+xml')
)

tool_context = _StubToolContext({artifact_name: artifact})
llm_request = LlmRequest(
contents=[
types.Content(
role='user',
parts=[
types.Part(
function_response=types.FunctionResponse(
name='load_artifacts',
response={'artifact_names': [artifact_name]},
)
)
],
)
]
)

await load_artifacts_tool.process_llm_request(
tool_context=tool_context, llm_request=llm_request
)

artifact_part = llm_request.contents[-1].parts[1]
# The SVG must NOT be forwarded as inline image data — Gemini would 400.
assert artifact_part.inline_data is None
# And the original SVG markup is delivered as a text part instead.
assert artifact_part.text == svg_bytes.decode('utf-8')


def test_maybe_base64_to_bytes_decodes_standard_base64():
"""Standard base64 encoded strings are decoded correctly."""
original = b'hello world'
Expand Down
Loading