Repository navigation
[integrations][java][docs] Send images, audio, video and documents in tool results to Gemini - #1370
ShawnGarello wants to merge 1 commit into
Conversation
… tool results to Gemini Generated-by: Claude Code 2.1.319 (Claude Opus 5.5)
wenjin272
left a comment
There was a problem hiding this comment.
Thanks for adding multimodal tool-result support for Gemini! I left two comments on supported media types and simplifying the user-facing documentation.
| .collect(Collectors.joining()); | ||
| List<FunctionResponsePart> media = new ArrayList<>(); | ||
| for (DataContentBlock block : result.getBlocks()) { | ||
| if (block instanceof MediaBlock) { |
There was a problem hiding this comment.
Could we restrict tool-result media to the supported MIME types (PNG/JPEG/WebP, PDF, and plain text), and explicitly reject audio/video? Gemini accepts audio/video in user messages, but rejects them in functionResponse.parts. Gemini CLI encountered the same 400 errors and added filtering for these types. The tests and documentation should reflect this distinction.
| | [Anthropic](#anthropic) | The media accepted in user messages, sent in the `tool_result` content in block order. | | ||
| | [Ollama](#ollama) | Not supported | | ||
| | [Gemini](#gemini) | Not supported | | ||
| | [Gemini](#gemini) | The media accepted in user messages, sent as the `parts` of the `functionResponse` in block order, with the tool result's text in its `response`. Google documents media in function responses for Gemini 3 models. | |
There was a problem hiding this comment.
Could we simplify the Media in tool results documentation for Gemini, Anthropic, and OpenAI Responses to focus on supported media types, similar to Media input accepted by the integration above? The provider-specific request structures and conversion details seem unnecessary for users.
Linked issue: #1216
Purpose of change
The Gemini integration now sends images, audio, video and documents returned by a tool to the model. A tool result holding media becomes one
functionResponsewhosepartsare its media blocks in order; its text stays inresponse. Before, media in a tool result failed the request.Tools can return screenshots, recordings or documents the model must read before it continues, and Google documents media in function response parts for Gemini 3 models. This is part of the plan in the issue, after #1331 (Anthropic) and #1364 (OpenAI Responses), whose tool-result media table this docs change edits. The fields below cover the adapter change; the docs part updates the Gemini row of that table and the Gemini section.
Runtime flow
GeminiChatModelConnection.chatconverts each non-system message withGeminiChatUtils.convertToContent. For a tool message, each media block of theToolResultBlockbecomes aFunctionResponsePartthroughconvertMedia, and the text blocks are joined intoresponse.result, giving onePart.fromFunctionResponse(name, {"result": text}, parts)in auserturn. User messages callconvertMediawithPart::fromUriandPart::fromBytes, tool results with theFunctionResponsePartones. Media in an assistant message still throws.Key decisions
responseand media goes inparts, without a$reffromresponseto a part. The API makes$refoptional, and using it would change theresponseof every result with media. NodisplayNameis set.base_urlproxies and new model names make one brittle. An older model's rejection comes back as the service's error.idstay unsent, as for text-only results.Behavioral Semantics
Interaction decisions
functionResponse.partsin block order; text inresponseis_error=trueis_error=false, flag not sentis_error=falseBehavioral contracts
userturn with onefunctionResponse:nameas for any tool result,response={"result": <its text blocks joined>}(""if none), andparts= one part per media block, in block order.inlineData(mimeType= the block's media type,data= the decoded bytes); a URL block becomesfileData(fileUri,mimeType). NodisplayNameis set.IllegalArgumentExceptionwith the same message as in a user message.UnsupportedContentBlockExceptionwith the reasononly user messages and tool results can carry media, not assistant messages. The message names no URL or data.response.Failure behavior
Malformed Base64 raises
IllegalArgumentException, and media in an assistant messageUnsupportedContentBlockException(a subclass), while the request is built, so nothing is sent; media is never dropped or stringified.chat()rethrows both unwrapped. A media type, URL or model the service rejects comes back as the service's error.Tests
GeminiMultimodalTesttestToolResultMediaIsSentInOrder(full structure);testMediaOnlyToolResultSendsEmptyTexttestToolResultMediaIsSentInOrdertestToolResultMediaIsSentInOrder(each part equals the same block's user-message part)testToolResultMediaIsSentInOrder(image, audio, video, document)testTextOnlyToolResultIsUnchanged(exact JSON; empty and error results too)testMalformedBase64InToolResultFails;testChatKeepsMediaErrorTypes(throughchat())testMediaInAssistantMessagesFailstestErrorToolResultWithMediaSendsNoErrorField;testTextOnlyToolResultIsUnchangedtestParallelToolResultsKeepTheirCorrelationGeminiToolMediaLiveTest: a tool returns a red PNG; the answer must say "red"Coverage by risk: every media kind and both sources appear in one tool result, with text between media blocks.
Not verified:
GEMINI_API_KEY. CI doesn't run it: no workflow sets that variable, and secrets aren't passed to fork PRs. It passed against the Gemini Developer API withgemini-3.6-flash.Test runs, test-file changes and implementation invariants
mvn -B test -pl integrations/chat-models/gemini: 84 passed (GeminiMultimodalTest12;GeminiChatModelConnectionTest65, unchanged; the live class doesn't run without a key).mvn spotless:check -pl integrations/chat-models/geminiand./tools/check-license.shpass.GeminiChatUtils.javareverted, the new media, media-only, assistant-reason, malformed-Base64, error-flag, parallel andchat()tests fail, and the text-only test passes.gemini-3.6-flash, a stable Gemini 3 model with a free tier.GEMINI_MULTIMODAL_MODELandGEMINI_BASE_URLoverride the model and endpoint. It passes the first answer back unchanged, so the tool call's thought signature goes with it.partslist, so a text-only result keeps its empty"parts":[].DataContentBlock's subtypes are text and the four media types, soGemini cannot send tool result blockstays only for a future subtype.API
No signature or dependency change. For a caller who changes nothing: text-only tool results are sent unchanged; media in a tool result is now sent instead of raising
UnsupportedContentBlockException; the reason for media in an assistant message adds "and tool results".Documentation
doc-neededdoc-not-neededdoc-includedWas this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code 2.1.319 (Claude Opus 5.5)