Skip to content

[fix] Resolve Gemini image MIME type instead of hard-coding image/jpeg - #9887

Merged
sannya-singal merged 2 commits into
agno-agi:mainfrom
BlueX888:fix/gemini-image-mime-type
Sep 2, 2026
Merged

sannya-singal merged 2 commits into
agno-agi:mainfrom
BlueX888:fix/gemini-image-mime-type

Conversation

@BlueX888

@BlueX888 BlueX888 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Gemini image inputs were always labelled image/jpeg, regardless of the actual image format. format_image_for_message() in libs/agno/agno/utils/gemini.py hard-coded "mime_type": "image/jpeg" in all three branches (URL, filepath, raw bytes), so a PNG/WebP/GIF/HEIC image was sent to the Gemini API declared as a JPEG. This can cause the API to reject or misinterpret the image, and it silently discards an explicitly supplied Image.mime_type.

This PR replaces the three hard-coded values with the existing helper agno.utils.media.resolve_image_mime_type(), which is already used elsewhere in the codebase and resolves in the documented priority order: explicit mime_type > format field > file extension > magic bytes > image/jpeg fallback. Behaviour for real JPEGs is unchanged, and the fallback keeps the previous value when nothing can be determined, so this is not a breaking change.

Issue number: #9885

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Improvement
  • Model update
  • Other:

Changes

  • libs/agno/agno/utils/gemini.py
    • URL branch: resolve from the downloaded bytes (image_bytes=content_bytes) plus any explicit mime_type/format. The URL extension is deliberately not used here — the downloaded bytes are authoritative, and a .jpg URL that actually serves WebP is precisely the case this issue is about.
    • Filepath branch: resolve from explicit mime_type/format, then the file extension, then the file's magic bytes.
    • Raw-bytes branch: resolve from explicit mime_type/format, then magic bytes.
  • libs/agno/tests/unit/utils/test_gemini.py: 7 new regression tests covering URL/bytes/filepath resolution, explicit mime_type precedence, the format field, JPEG stability, and the unknown-bytes fallback.
  • libs/agno/tests/unit/models/google/test_gemini.py: one existing expectation changed. test_format_messages_nests_tool_result_media_in_function_response builds Image(content=b"image-bytes", mime_type="image/png") but asserted "image/jpeg" — i.e. the assertion encoded the bug being fixed here. It now asserts "image/png", matching the image the fixture declares. Flagging this explicitly since changing an existing assertion deserves a maintainer's eye.

Testing

Commands actually run, in a clean uv venv with uv pip install -e libs/agno:

  • Reproduction script (no network; WebP/PNG/JPEG byte headers constructed locally) covering URL, raw bytes, filepath, explicit mime_type, and real JPEG.
    • Before: all 5 cases returned image/jpeg.
    • After: image/webp, image/webp, image/png, image/webp, image/jpeg.
  • Sabotage check: with the fix reverted and the new tests kept, 5 of the 7 new tests fail; restoring the fix makes them pass. The tests genuinely exercise the change.
  • ruff format --check on the changed files → already formatted.
  • ruff check on the changed files → all checks passed.
  • mypy --config-file libs/agno/pyproject.toml libs/agno/agno/utils/gemini.py (same config ./scripts/validate.sh uses) → 0 errors.
  • pytest libs/agno/tests/unit/utils/ → 488 passed, 1 skipped (excluding test_path_safety_consistency.py, which needs the slack_sdk extra that is not installed in my environment).
  • pytest libs/agno/tests/unit/models/google/ → 185 passed.

I did not run the entire libs/agno/tests/unit suite: it aborts during collection on modules requiring optional vectordb/workflow extras that I could not install here. Stating this rather than claiming a green full suite. CI will cover the rest.


Checklist

  • Code complies with style guidelines
  • Ran format/validation scripts (ruff format/check and mypy with the repo's pyproject.toml config, as ./scripts/validate.sh does)
  • Self-review completed
  • Documentation updated (comments, docstrings) — no public API or docstring change; behaviour is documented by the tests
  • Examples and guides: not applicable, no example changes needed
  • Tested in clean environment (fresh uv venv, editable install)
  • Tests added/updated

Duplicate and AI-Generated PR Check

  • I have searched existing open pull requests and confirmed that no other PR already addresses this issue
  • If a similar PR exists, I have explained below why this PR is a better approach
  • Check if this PR was entirely AI-generated (by Copilot, Claude Code, Cursor, etc.)

Additional Notes

Related open PR. #9827 ("fix(media): standardize MIME normalization…") touches agno/utils/media.py. It does not modify agno/utils/gemini.py, so it does not fix this issue — the Gemini helper never called into the resolver at all. This PR is the call-site fix and builds on the helper that #9827 refines, so the two are complementary rather than competing. If #9827 lands first I'm happy to rebase.

AI disclosure. This PR was generated by an AI agent (Claude), per the CONTRIBUTING requirement to disclose. Everything reported under "Testing" was actually executed and the output observed; nothing is assumed. Please review with the scrutiny that warrants, particularly the changed assertion in test_format_messages_nests_tool_result_media_in_function_response. If you'd prefer this issue be handled by a human contributor, close it and I won't reopen.

Fixes #9885

@sannya-singal sannya-singal self-assigned this Sep 1, 2026

@VANDRANKI VANDRANKI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the full diff and the shared helper it wires in.

format_image_for_message in libs/agno/agno/utils/gemini.py had three branches (URL-downloaded bytes, file path, raw bytes) that all hardcoded "mime_type": "image/jpeg". This PR replaces all three with calls to agno.utils.media.resolve_image_mime_type, which I pulled and read directly since it isn't touched by this diff:

mime_type > image_format > file extension (mimetypes.guess_type) > magic bytes (get_image_type) > "image/jpeg" fallback

I checked each call site against that priority order:

  • URL branch passes image.mime_type, image.format, and image_bytes=content_bytes (no file_path), so a downloaded WebP with no explicit mime/format falls through to magic-byte detection. Correct.
  • File-path branch additionally passes file_path=image_path, so a .png extension resolves before bytes are even inspected. Correct.
  • Raw-bytes branch passes only mime_type/format/image_bytes. Correct.

get_image_type in the same helper file checks PNG/GIF/JPEG/HEIC/WebP magic bytes and returns None for anything else, which is exactly what makes the "falls back to jpeg for unknown bytes" test pass rather than raising.

The updated test in tests/unit/models/google/test_gemini.py changes an expected mime type from image/jpeg to image/png for what looks like a real (not synthetic) test fixture, which is consistent with removing the hardcoded jpeg. The new unit tests in tests/unit/utils/test_gemini.py cover the priority order end to end (explicit mime wins over format, format wins over bytes, magic bytes for webp/jpeg, unknown bytes still default to jpeg so existing untyped-content behavior is preserved).

I did not run the suite myself, but the logic traces cleanly against the helper's own priority cascade and the tests match it. This is a low-risk, well-scoped fix: only the three previously-hardcoded lines change, and every provider using this helper already handles the returned mime type generically.

This is a community review and does not clear the merge gate.

@sannya-singal
sannya-singal merged commit d1d5a7a into agno-agi:main Sep 2, 2026
10 of 12 checks passed
@BlueX888
BlueX888 deleted the fix/gemini-image-mime-type branch September 2, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Gemini image inputs are always sent as image/jpeg

3 participants