Skip to content

fix: gemini parts in tool response - #4202

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
06-09-fix_gemini_parts_in_tool_response
Jun 9, 2026
Merged

fix: gemini parts in tool response#4202
Pratham-Mishra04 merged 1 commit into
devfrom
06-09-fix_gemini_parts_in_tool_response

Conversation

@TejasGhatte

@TejasGhatte TejasGhatte commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds support for multimodal function responses (images and files returned by tools) in the Gemini provider. Previously, when a tool returned image or file content alongside text, the media was either dropped or serialized as a raw JSON fallback. This PR preserves those media blocks by routing them through FunctionResponse.Parts (a Gemini 3+ feature), with correct provider-specific behavior for the Gemini Developer API vs. Vertex AI.

Changes

  • FunctionResponse.Parts field added to the FunctionResponse type so images/files returned by tools can be attached as sibling parts alongside the structured response object.
  • Forward conversion (convertResponsesMessagesToGeminiContents) now accepts model and provider arguments. For Gemini 3+ models, image/file content blocks from ResponsesFunctionToolCallOutputBlocks are converted to inlineData/fileData parts and attached to FunctionResponse.Parts. For older models (e.g. gemini-2.5-flash), media is silently dropped to avoid a hard upstream 400. Vertex AI emits a {"$ref": "<displayName>"} entry in the response object (as documented); the Gemini Developer API does not (the $ref form triggers an upstream bug).
  • Reverse conversion (convertGeminiContentsToResponsesMessages) reconstructs multimodal function responses back into ResponsesFunctionToolCallOutputBlocks (text + image blocks), preserving media on the Bifrost side instead of collapsing everything to a plain string.
  • Part.UnmarshalJSON now handles snake_case fallbacks (inline_data, file_data) emitted by the google-genai SDK inside functionResponse.parts.
  • Blob.UnmarshalJSON now handles snake_case fallbacks (mime_type, display_name) from FunctionResponseBlob.
  • FileData.UnmarshalJSON added with snake_case fallbacks (mime_type, file_uri, display_name) from FunctionResponseFileData.
  • Unit tests added for: image preserved on Gemini 3 (Developer API, no $ref), image dropped on older models, Vertex emitting $ref, and a full round-trip (GeminiGenerationRequestBifrostResponsesRequestGeminiGenerationRequest).
  • Integration tests added (test_30, test_30b) covering a fabricated multimodal tool history and a real two-turn workflow, parameterized across gemini-3-flash-preview (image understood) and gemini-2.5-flash (image dropped, request still succeeds).

Type of change

  • Bug fix
  • Feature

Affected areas

  • Core (Go)
  • Providers/Integrations

How to test

# Unit tests
go test ./core/providers/gemini/... -run TestResponsesAPIParallelFunctionCalling
go test ./core/providers/gemini/... -run TestMultimodalFunctionResponse_RoundTrip

# Integration tests (requires GEMINI_API_KEY)
cd tests/integrations/python
pytest tests/test_google.py::TestGoogleProvider::test_30_multimodal_function_response_image
pytest tests/test_google.py::TestGoogleProvider::test_30b_multimodal_function_response_full_workflow

Expected outcomes:

  • gemini-3-flash-preview: model identifies the tool-returned image color as "red".
  • gemini-2.5-flash: request succeeds without a 400; model produces a text reply (image was dropped by gating).

Breaking changes

  • No

Security considerations

No new auth, secrets, or PII surface. Base64 image data passes through in-memory only and is not logged or persisted.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

Summary by CodeRabbit

  • New Features

    • Improved multimodal function-response support: tools can return images alongside text for Gemini 3 models; other model types gracefully fall back to text-only or reference-style handling.
  • Compatibility

    • Better handling of provider/model variations so image-containing tool outputs are preserved where supported and safely downgraded otherwise.
  • Tests

    • Added end-to-end and regression tests validating multimodal function-response round‑trips and field preservation.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5f0d5ab4-0eac-4963-941a-256718c13a15

📥 Commits

Reviewing files that changed from the base of the PR and between c096655 and f7dd93e.

📒 Files selected for processing (4)
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • tests/integrations/python/tests/test_google.py
💤 Files with no reviewable changes (1)
  • tests/integrations/python/tests/test_google.py

📝 Walkthrough

Walkthrough

This PR adds model/provider-aware conversion and type support to preserve multimodal Gemini FunctionResponse parts through Gemini↔Bifrost conversions, and includes unit and integration tests validating image preservation, Vertex $ref behavior, and round-trip integrity.

Changes

Gemini multimodal function-response support

Layer / File(s) Summary
Type system for multimodal function responses
core/providers/gemini/types.go
FunctionResponse.Parts field added; Part, Blob, and new FileData UnmarshalJSON methods accept snake_case variants (inline_data, file_data, display_name, file_uri) alongside camelCase.
Model and provider awareness in conversion entry points
core/providers/gemini/responses.go (call site)
ToGeminiResponsesRequest now passes bifrostReq.Model and bifrostReq.Provider into convertResponsesMessagesToGeminiContents.
Inbound: Gemini→Bifrost multimodal function response conversion
core/providers/gemini/responses.go (strip & reconstruct)
convertGeminiContentsToResponsesMessages builds ResponsesToolMessageOutputStruct from FunctionResponse.Parts, converts InlineData/FileData into Responses content blocks, and uses stripFunctionResponseMediaRefs to remove {"$ref":...} placeholders while preserving textual fields.
Outbound: Bifrost→Gemini multimodal tool output conversion
core/providers/gemini/responses.go (messages→Gemini)
convertResponsesMessagesToGeminiContents now accepts model and provider; for ResponsesFunctionToolCallOutputBlocks it collects media blocks into funcMediaParts, places text into response["output"], emits Vertex-only {"$ref": "<displayName>"} entries when required, gates Parts attachment to Gemini 3+ and sets FunctionResponse.Parts when present; media-only outputs set output to empty string.
Unit test coverage for multimodal function responses
core/providers/gemini/gemini_test.go (lines 2501–2686, 4002–4143)
Adds tests asserting Gemini 3 preserves inline image Parts without $ref, older Gemini drops media while keeping text, Vertex emits $ref referencing blob displayName, and round-trip tests ensure inline base64 Parts and non-$ref fields are preserved while $ref placeholders are not re-emitted.
End-to-end integration tests for multimodal tool workflows
tests/integrations/python/tests/test_google.py (lines 1663–1867)
Adds test_30_multimodal_function_response_image and test_30b_multimodal_function_response_full_workflow to simulate and run two-turn tool-calling flows with inline image bytes and $ref references, asserting final replies and conditional image understanding based on model support.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • maximhq/bifrost#3630: Both PRs touch convertResponsesMessagesToGeminiContents in core/providers/gemini/responses.go.

Suggested reviewers

  • akshaydeo
  • danpiths
  • roroghost17

Poem

🐰 I nibble bytes of inline art,
Preserve the image, do my part,
Parts now travel, round-trip true,
No stray $ref to trouble you,
Hooray — a multimodal start! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: gemini parts in tool response' is concise and clearly refers to the main change: adding support for multimodal content (parts) in Gemini function responses.
Description check ✅ Passed The description comprehensively covers the PR objectives including Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Security considerations, and Checklist sections aligned with the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-09-fix_gemini_parts_in_tool_response

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@TejasGhatte
TejasGhatte marked this pull request as ready for review June 9, 2026 10:39
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


tejas ghatte seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Core conversion logic is correct, round-trips are well-tested at both unit and integration level, and the only open issue is a documentation inconsistency in an integration test.

The forward and reverse conversions handle all documented cases (Gemini 3 image preserved, older model drop, Vertex $ref, media-only empty-output placeholder) correctly, and the json.Valid guard ensures structured JSON payloads survive the round-trip without double-encoding. The snake_case UnmarshalJSON additions are additive and do not change existing serialisation. No regressions were found in the provider converter logic.

tests/integrations/python/tests/test_google.py — test_30's parametrize list does not include gemini-2.5-flash despite the docstring claiming the gating is validated there.

Important Files Changed

Filename Overview
core/providers/gemini/responses.go Adds forward and reverse conversion for multimodal function responses (FunctionResponse.Parts). New stripFunctionResponseMediaRefs helper, provider/model-aware gating (Gemini 3+ only), and Vertex vs. Gemini Developer API branching all look correct. The json.Valid guard correctly handles both plain-string and structured JSON text payloads in the round-trip.
core/providers/gemini/types.go Adds FunctionResponse.Parts field, snake_case UnmarshalJSON fallbacks for Part (inline_data/file_data), Blob (mime_type/display_name), and new FileData (mime_type/file_uri/display_name). camelCase-takes-priority logic and URL-safe base64 handling are preserved correctly.
core/providers/gemini/gemini_test.go Four new unit test cases cover the Gemini-3 image-preserved path, older-model drop path, Vertex $ref emission, and a full round-trip. A second round-trip test (PreservesNonRefFields) correctly relies on the json.Valid guard to embed multi-field JSON as a RawMessage, so the assertion passes. Test coverage is thorough.
tests/integrations/python/tests/test_google.py test_30 only parameterises gemini-3-flash-preview despite the docstring describing gemini-2.5-flash gating as a regression guard. test_30b's skip guard and client use the Gemini key while both generate_content calls route through Vertex (already flagged in a prior review thread).

Reviews (2): Last reviewed commit: "fix: gemini parts in tool response" | Re-trigger Greptile

Comment thread core/providers/gemini/responses.go

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/providers/gemini/responses.go`:
- Around line 2030-2064: When part.FunctionResponse.Parts is non-empty the code
currently only builds media blocks and omits the original full JSON response
(responseStr), dropping other fields; ensure you always preserve the full
serialized function response by assigning output.ResponsesToolCallOutputStr =
&responseStr even when you also set
output.ResponsesFunctionToolCallOutputBlocks, so update the branch handling
part.FunctionResponse.Parts in convertResponsesMessagesToResponsesToolMessage
(the block creating output := &schemas.ResponsesToolMessageOutputStruct{}) to
always set ResponsesToolCallOutputStr alongside the blocks generated by
convertGeminiInlineDataToContentBlock/convertGeminiFileDataToContentBlock.

In `@tests/integrations/python/tests/test_google.py`:
- Line 1771: The test function signature for
test_30b_multimodal_function_response_full_workflow includes an unused fixture
parameter test_config; remove test_config from the parameter list of the
function definition so the signature becomes (self, model,
expect_image_understood), ensuring no other references to test_config exist
inside the function or its assertions.
- Line 1767: The pytest.mark.parametrize decorator usage is incorrect: change
the first positional argument from a single string to a tuple of parameter names
so the decorator reads pytest.mark.parametrize(("model",
"expect_image_understood"), [...]) (locate the decorator invocation using
pytest.mark.parametrize in this test file and update similarly to the other
tests like test_30 that use the correct tuple form).
- Line 1667: The test function test_30_multimodal_function_response_image
currently declares an unused pytest fixture parameter test_config; remove
test_config from the function signature so the fixture is not injected (update
def test_30_multimodal_function_response_image(self, model,
expect_image_understood):) and run tests to ensure no other references rely on
that parameter.
- Line 1664: The pytest.mark.parametrize decorator is using a comma-separated
string for parameter names which is non-standard; update the decorator on the
test (pytest.mark.parametrize) to pass a tuple of parameter names (e.g.
("model", "expect_image_understood")) as the first argument instead of the
single string "model,expect_image_understood" so linters stop complaining and
tests remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 35a04376-103b-4582-b636-78491961e3de

📥 Commits

Reviewing files that changed from the base of the PR and between f2a448e and c096655.

📒 Files selected for processing (4)
  • core/providers/gemini/gemini_test.go
  • core/providers/gemini/responses.go
  • core/providers/gemini/types.go
  • tests/integrations/python/tests/test_google.py

Comment thread core/providers/gemini/responses.go
Comment thread tests/integrations/python/tests/test_google.py
Comment thread tests/integrations/python/tests/test_google.py
Comment thread tests/integrations/python/tests/test_google.py
Comment thread tests/integrations/python/tests/test_google.py
@TejasGhatte
TejasGhatte force-pushed the 06-09-fix_gemini_parts_in_tool_response branch from c096655 to f7dd93e Compare June 9, 2026 12:09

Pratham-Mishra04 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Merge activity

  • Jun 9, 1:55 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 9, 1:55 PM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 merged commit 5036ae3 into dev Jun 9, 2026
15 of 16 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 06-09-fix_gemini_parts_in_tool_response branch June 9, 2026 13:55
akshaydeo pushed a commit that referenced this pull request Jun 12, 2026
## Summary

Adds support for multimodal function responses (images and files returned by tools) in the Gemini provider. Previously, when a tool returned image or file content alongside text, the media was either dropped or serialized as a raw JSON fallback. This PR preserves those media blocks by routing them through `FunctionResponse.Parts` (a Gemini 3+ feature), with correct provider-specific behavior for the Gemini Developer API vs. Vertex AI.

## Changes

- **`FunctionResponse.Parts` field added** to the `FunctionResponse` type so images/files returned by tools can be attached as sibling parts alongside the structured response object.
- **Forward conversion (`convertResponsesMessagesToGeminiContents`)** now accepts `model` and `provider` arguments. For Gemini 3+ models, image/file content blocks from `ResponsesFunctionToolCallOutputBlocks` are converted to `inlineData`/`fileData` parts and attached to `FunctionResponse.Parts`. For older models (e.g. `gemini-2.5-flash`), media is silently dropped to avoid a hard upstream 400. Vertex AI emits a `{"$ref": "<displayName>"}` entry in the response object (as documented); the Gemini Developer API does not (the `$ref` form triggers an upstream bug).
- **Reverse conversion (`convertGeminiContentsToResponsesMessages`)** reconstructs multimodal function responses back into `ResponsesFunctionToolCallOutputBlocks` (text + image blocks), preserving media on the Bifrost side instead of collapsing everything to a plain string.
- **`Part.UnmarshalJSON`** now handles snake_case fallbacks (`inline_data`, `file_data`) emitted by the google-genai SDK inside `functionResponse.parts`.
- **`Blob.UnmarshalJSON`** now handles snake_case fallbacks (`mime_type`, `display_name`) from `FunctionResponseBlob`.
- **`FileData.UnmarshalJSON`** added with snake_case fallbacks (`mime_type`, `file_uri`, `display_name`) from `FunctionResponseFileData`.
- **Unit tests** added for: image preserved on Gemini 3 (Developer API, no `$ref`), image dropped on older models, Vertex emitting `$ref`, and a full round-trip (`GeminiGenerationRequest` → `BifrostResponsesRequest` → `GeminiGenerationRequest`).
- **Integration tests** added (`test_30`, `test_30b`) covering a fabricated multimodal tool history and a real two-turn workflow, parameterized across `gemini-3-flash-preview` (image understood) and `gemini-2.5-flash` (image dropped, request still succeeds).

## Type of change

- [x] Bug fix
- [x] Feature

## Affected areas

- [x] Core (Go)
- [x] Providers/Integrations

## How to test

```sh
# Unit tests
go test ./core/providers/gemini/... -run TestResponsesAPIParallelFunctionCalling
go test ./core/providers/gemini/... -run TestMultimodalFunctionResponse_RoundTrip

# Integration tests (requires GEMINI_API_KEY)
cd tests/integrations/python
pytest tests/test_google.py::TestGoogleProvider::test_30_multimodal_function_response_image
pytest tests/test_google.py::TestGoogleProvider::test_30b_multimodal_function_response_full_workflow
```

Expected outcomes:
- `gemini-3-flash-preview`: model identifies the tool-returned image color as "red".
- `gemini-2.5-flash`: request succeeds without a 400; model produces a text reply (image was dropped by gating).

## Breaking changes

- [x] No

## Security considerations

No new auth, secrets, or PII surface. Base64 image data passes through in-memory only and is not logged or persisted.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Improved multimodal function-response support: tools can return images alongside text for Gemini 3 models; other model types gracefully fall back to text-only or reference-style handling.

* **Compatibility**
  * Better handling of provider/model variations so image-containing tool outputs are preserved where supported and safely downgraded otherwise.

* **Tests**
  * Added end-to-end and regression tests validating multimodal function-response round‑trips and field preservation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants