Conversation
… arguments
The JSON Path that addresses a streamed argument is RFC 9535, so an array
element arrives as "$.tables[0]" or "$.data[0].label". setValueByJSONPath
split that path on "." alone and wrote the last segment as a map key, so
"$.tables[0]" became the key "tables[0]" and "$.data[0].label" became
{"data[0]": {"label": ...}}. getValueFromPartialArg walked the same split
path to find the string it was appending a chunk to, so a string streamed
in several chunks inside an array restarted at every chunk.
Any tool whose parameters include an array is therefore uncallable with
StreamFunctionCallArguments enabled: the assembled arguments fail the
tool's own schema, the model retries, and the turn ends in
MALFORMED_FUNCTION_CALL.
Parse the path into segments instead — ".name", "['name']" and "[index]",
the selectors a normalized path is built from — and use the same traversal
to write a value and to find the string a chunk continues. A path outside
that grammar is dropped rather than written to an invented key, which also
covers the value-less "$.tables[]" chunk Vertex closes an array with.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Author
|
@googlebot I signed it! @google-cla |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
cla/googlecheck. This PR is an alternative, opened only because fix(llminternal): parse RFC 9535 paths in streamed function call arguments #1610 can't merge. The approach is the same; the difference is that a path outside the supported grammar is rejected instead of being collapsed into a different key (see Solution).Problem:
With
StreamFunctionCallArguments, Vertex streams each argument leaf as aPartialArgaddressed by an RFC 9535 JSON path ($.tables[0],$.data[0].label).streamingResponseAggregatorsplit the path on.and wrote the last segment as a map key. So$.tables[0]became the literal key"tables[0]", and a string streamed over several chunks inside an array restarted from empty each time. Any tool with an array parameter gets arguments that fail its own schema. On Vertex (gemini-3.7-flash) the model retries five times and the turn ends withMALFORMED_FUNCTION_CALLand no answer.Solution:
Only
internal/llminternal/stream_aggregator.gochanges, with no new dependencies and no regexp:parseJSONPathsplits a path into segments. It accepts exactly the selectors normalized paths use (RFC 9535 §2.7):.name,['name'],["name"],[index]. Anything else is reported as unparseable and the chunk is dropped: wildcard, slice, descendant (..), negative index, Vertex's value-less$.tables[]terminator, or a path the segments don't fully cover. A path the aggregator can't read never becomes a key the tool will act on.setValueByPathcreates maps for names and grows[]anyfor indices, so an element that arrives out of order keeps its position and any gap staysnull. Indices are bounded (maxPathIndex).getValueFromPartialArgfinds the string a chunk continues through the same traversal, so string continuation works inside arrays.Behavior change
What behaves differently for someone already on the current release?
Only function calls whose arguments arrive through
PartialArgs, i.e. withStreamFunctionCallArgumentsenabled on a streaming Vertex call. Array arguments now assemble as JSON arrays and bracket-quoted member names as their real names, where before they became literal"name[0]"/"a['b"keys. A partial argument whose path is outside the supported grammar is now dropped instead of written under a mangled key. Nothing changes for non-streamed calls, for streamed calls whose arguments arrive whole inArgs, or for any path without brackets.Testing Plan
Unit Tests:
go test -race -mod=readonly -count=1 -shuffle=on ./internal/llminternal/givesok.go mod tidy -diff,go build -mod=readonly ./...andgolangci-lint run(v2.3.1) give 0 issues.With your source change reverted and your tests kept, which test fails?
All six new tests in
internal/llminternal/stream_aggregator_test.go. They drive the public aggregator with the chunk shapes Vertex sends and compare the assembled args as JSON:Manual End-to-End (E2E) Tests:
Runner with
StreamingModeSSE, Vertex global endpoint,gemini-3.7-flash, andStreamFunctionCallArguments = trueset on streamed calls only (from a production agent; tool names as declared there). Its tools take arrays:describe_tables(tables: string[]),query_gold_table(queries: object[]), andpresent_ui, whose chart presentation carriesprops.data: object[]. The prompt asked for a bar chart of revenue by month.Chunks as received:
{"id":"vMwRX2nx","name":"describe_tables","willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[0]","stringValue":"sales_vs_budget_water","willContinue":true}],"willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[0]","stringValue":"co","willContinue":true}],"willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[0]"}],"willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[1]","stringValue":"sales_vs_budget_davey","willContinue":true}],"willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[1]"}],"willContinue":true} {"partialArgs":[{"jsonPath":"$.tables[]"}]}Before (v2.2.0):
After:
For every call, the first named chunk arrived 0.5 to 5.0 s before the aggregated call. A recursive walk of every aggregated argument in the run found no key containing
[or].Checklist
Additional context
adk-python's
StreamingResponseAggregatoralready parses these paths into typed components (_parse_json_pathinsrc/google/adk/utils/streaming_utils.py); this brings the Go aggregator in line.