Skip to content

Parallel streamed function calls can lose thought signatures during replay #758

@gustafvh

Description

@gustafvh

Describe the bug

When Gemini returns multiple function calls in the same streamed model response, only the first function-call part may carry a ThoughtSignature. ADK Go's streaming response aggregator preserves a thought signature for the function call currently being assembled, but it does not propagate that signature to sibling function-call parts that are emitted in the same parallel batch without their own signature.

Gemini thinking models require replayed model-role function-call parts to carry thought signatures. If one or more parallel function-call parts are later replayed from session history without a ThoughtSignature, the next model request can fail with 400 INVALID_ARGUMENT complaining that a function call is missing a thought_signature.

There is a related but separate issue for synthetic adk_request_confirmation parts: #656. This issue is about parallel function calls emitted by the model through the streaming aggregator.

To Reproduce

  1. Use a Gemini model with thinking enabled.
  2. Trigger a streamed response where the model emits multiple function calls in parallel.
  3. Observe that the first function-call part includes a ThoughtSignature, while sibling function-call parts in the same model-role content may not.
  4. Persist and replay that model-role content in a subsequent request.
  5. The Gemini API may reject the request because one of the replayed function-call parts is missing thought_signature.

Expected behavior

When ADK Go aggregates streamed model responses into a final model-role content block, all function-call parts in the same parallel function-call batch should be replay-safe. If one function-call part has a thought signature and sibling function-call parts are missing it, the aggregator should copy the available signature onto the missing sibling parts before returning the final aggregated response.

Observed behavior

internal/llminternal/stream_aggregator.go preserves a thought signature for an individual streamed function call, but does not ensure sibling parallel function-call parts also carry a signature before the aggregated content is persisted/replayed.

Proposed fix

After flushing the final text/function-call buffers in streamingResponseAggregator.Close, scan the aggregated sequence for function-call parts:

  • remember the first non-empty ThoughtSignature
  • copy it onto subsequent function-call parts that are missing a signature

This keeps the fix local to streamed response aggregation and avoids changing unrelated tool confirmation behavior.

Testing

Add or update unit coverage for internal/llminternal/stream_aggregator.go with a streamed response containing multiple parallel function calls where only the first carries ThoughtSignature. The test should assert that all emitted function-call parts in the final aggregated response carry a non-empty signature.

Alignment with adk-python

This is specific to ADK Go's streaming aggregation path and the Go genai.Part representation. The cross-language invariant is that model-role function-call history generated from Gemini thinking models must be replayable without losing required thought signatures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions