Skip to content

[Platform][VertexAi] Wrap list-array tool responses for Struct-proto compatibility#2102

Open
peter-si wants to merge 1 commit into
symfony:mainfrom
peter-si:fix/vertex-gemini-list-array-tool-response
Open

[Platform][VertexAi] Wrap list-array tool responses for Struct-proto compatibility#2102
peter-si wants to merge 1 commit into
symfony:mainfrom
peter-si:fix/vertex-gemini-list-array-tool-response

Conversation

@peter-si

@peter-si peter-si commented May 19, 2026

Copy link
Copy Markdown
Q A
Branch? main
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT

Summary

Wraps list-array tool responses in a {items: [...]} object so they pass Gemini's Struct-proto validation on functionResponse.response. Without this, any tool that returns a JSON list 500s the whole turn with Proto field is not repeating, cannot start list.

Why

google.protobuf.Struct is a JSON-object shape — it cannot represent a sequential JSON array. The current normalizer wraps scalars as {rawResponse: <value>} (good) but passes lists through unchanged (broken). Every tool that returns rows / records / promos / search results / etc. fails.

Concrete reproducer: a tool method returning [{name: 'A'}, {name: 'B'}] produces functionResponse.response = [{name: 'A'}, {name: 'B'}], which Vertex rejects.

What changes

In Symfony\AI\Platform\Bridge\VertexAi\Contract\ToolCallMessageNormalizer:

  • Associative arrays → pass through (unchanged)
  • List arrays → wrapped as {items: [...]} (new)
  • Scalars / null → wrapped as {rawResponse: <value>} (unchanged)

Drive-bys while in the file: extracted decodeContent() and asStruct() for readability, added a class-level docblock describing the Struct quirk, and dropped a no-op array_filter() on the outer payload (it never had anything to filter).

Backwards compatibility

The output shape changes for tools that returned a JSON list, but those tools currently 500 with the Struct-proto error against Gemini, so there are no working consumers depending on the broken shape. A [BC BREAK] entry has been added to the ai-vertex-ai-platform CHANGELOG under 0.10.

Tests

New src/platform/tests/Bridge/VertexAi/Contract/ToolCallMessageNormalizerTest covers associative passthrough, list-wrap, empty-list-wrap, scalar wrap, non-JSON content, null content, and tool-name preservation. 9 tests, 9 assertions, all green.

Note: PHPStan / Component / Agent is currently red on this branch due to a pre-existing Variable $sourceCollection might not be defined. issue in Agent\Toolbox\Toolbox::call() — verified independently against upstream/main. Fix submitted as #2103; once that merges, a rebase here will clear the failure.

🤖 Generated with Claude Code

@peter-si peter-si changed the title [VertexAi] Wrap list-array tool responses for Struct-proto compatibility [Platform][VertexAi] Wrap list-array tool responses for Struct-proto compatibility May 19, 2026
@peter-si peter-si force-pushed the fix/vertex-gemini-list-array-tool-response branch from 2549c02 to 1976b00 Compare May 19, 2026 09:22
@peter-si

Copy link
Copy Markdown
Author

Heads up on red CI here:

I've opened #2103 with the two-line fix. Once that merges, the failure here will clear after a rebase. Let me know if you'd prefer this PR to depend on / include that fix instead.

Gemini declares `functionResponse.response` as a `google.protobuf.Struct`
which represents a JSON object. Sequential JSON arrays from tools that
return lists are rejected with "Proto field is not repeating, cannot
start list", failing the whole turn.

Wrap list-arrays as `{items: [...]}` so list-returning tools work end
to end. Existing handling is preserved: associative arrays pass through,
scalars / null wrap as `{rawResponse: <value>}`.

Also extracts the JSON-decode and shape-coercion into named helpers,
adds a class-level docblock describing the Struct quirk, and drops a
no-op `array_filter()` on the outer payload.
@peter-si peter-si force-pushed the fix/vertex-gemini-list-array-tool-response branch from 1976b00 to 1adabea Compare May 19, 2026 09:39
@peter-si peter-si marked this pull request as ready for review May 19, 2026 10:01
@carsonbot carsonbot added Bug Something isn't working Platform Issues & PRs about the AI Platform component Status: Needs Review labels May 19, 2026
@chr-hertel

Copy link
Copy Markdown
Member

Hi @peter-si,
thanks for raising this, how did you end up with a tool result like [{name: 'A'}, {name: 'B'}]? so like, what is the return value of the tool?

I would expect a tool result to be more structured, and that's why we have the Symfony\AI\Agent\Toolbox\ToolResultConverter dealing with this here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working Platform Issues & PRs about the AI Platform component Status: Needs Review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants