#1609·adk-go

internal/llminternal: streamed function-call args misparse RFC 9535 array and quoted JSON paths

Author: coderdailyoneCreated Sep 16, 2026Updated Sep 18, 2026
Labelsbug

Describe the bug

streamingResponseAggregator rebuilds streamed function-call arguments by stripping a "$." prefix and splitting each PartialArg.JsonPath on ".". But partial-arg paths are RFC 9535 JSON paths — the genai field's own example is "$.foo.bar[0].data" — so $.items[0] addresses an array element and $.a["b.c"] a member whose name contains a dot. The split turns the first into a literal "items[0]" map key and the second into the bogus keys a["b / c"], so a tool whose schema declares an array or a bracket-addressed member receives malformed arguments.

adk-python's StreamingResponseAggregator parses the same paths into string/int components and builds real arrays (src/google/adk/utils/streaming_utils.py, _parse_json_path, _get_value_by_json_path, _set_value_by_json_path); the Go port predates that implementation and never gained it.

Where

  • internal/llminternal/stream_aggregator.gosetValueByJSONPath splits the path on "."; getValueFromPartialArg repeats the same split to look up an existing string to append to. Both sit under processStreamingFunctionCallPart.
  • Reachable whenever a Gemini/Vertex model streams function-call arguments (functionCallingConfig.streamFunctionCallArguments) — the aggregator backs model/gemini's streaming path.

Impact

Any function call whose arguments arrive via partialArgs and whose schema contains an array, a nested object under an array element, or a member name needing brackets is reconstructed wrong. {"items": ["a", "b"]} comes out as {"items[0]": "a", "items[1]": "b"} — the declared parameter is absent and junk keys appear in its place, so the dispatched tool fails argument validation or silently reads nothing for the real parameter.

How to reproduce

TestStreamingFunctionCallArgsWithArrayJSONPath in internal/llminternal/stream_aggregator_test.go streams PartialArgs at $.items[0] (two chunks, exercising string concatenation), $.items[2].name, $.filter["tag.list"][0], $['x.y'], and $["a\nb"], then asserts the aggregated FunctionCall.Args. On the unfixed code it fails — the args are {"items[0]": "ab", "items[2]": {"name": "x"}, "filter[\"tag": ...} — where the fix produces {"items": ["ab", nil, {"name": "x"}], "filter": {"tag.list": ["y"]}, "x.y": "n", "a\nb": true}.

Suggested fix

Parse each JsonPath into typed components — string member names, int array indices — the way _parse_json_path does in adk-python, then navigate or create map[string]any vs []any by component type, padding sparse indices up to a bound (_MAX_ARRAY_INDEX = 10000 in Python, mirrored) and rejecting indices past it and paths that conflict with an earlier scalar. In Go, append can reallocate a slice, so the setter must return the container and each level write the child's result back.