Skip to content

Missing ThoughtSignature on adk_request_confirmation parts breaks execution #656

Description

@gustafvh

Describe the bug

generateRequestConfirmationEvent in internal/llminternal/functions.go creates synthetic adk_request_confirmation FunctionCall parts but does not copy the ThoughtSignature from the original model-generated FunctionCall part onto them. This causes Gemini 3 models (and 2.5 with thinking enabled) to reject subsequent requests with a 400 INVALID_ARGUMENT error: "function call … is missing a thought_signature".

The stream aggregator (internal/llminternal/stream_aggregator.go) already handles this correctly — it tracks currentFunctionThoughtSignature and copies it onto emitted parts. The confirmation flow simply missed it.

To Reproduce

  1. Create an LlmAgent with a tool wrapped in tool.WithConfirmation (or using require_confirmation)
  2. Use a Gemini model that emits thought signatures (e.g. gemini-3-flash-preview)
  3. Trigger the tool so the model emits a FunctionCall with a ThoughtSignature
  4. The confirmation flow calls generateRequestConfirmationEvent, which builds a new Part{FunctionCall: ...} without setting ThoughtSignature
  5. This event is appended to session history. On the next model call, the Gemini API rejects the request with:
    400 INVALID_ARGUMENT: Unable to submit request because function call `adk_request_confirmation`
    in the N. content block is missing a `thought_signature`.
    

Expected behavior

generateRequestConfirmationEvent should copy the ThoughtSignature from the original model-generated FunctionCall part (found in functionCallEvent.Content.Parts) onto the synthetic adk_request_confirmation part it creates, so that the Gemini API thought signature validation passes.

Screenshots

N/A — API-level error, no UI involved.

Versions

  • OS: Linux (also reproducible on Mac)
  • ADK version: v0.5.0 / main at commit d8a05b5 (2026-03-17).
  • Python version: N/A (this is ADK Go). Go 1.23+.

Additional context

The fix is likely small. In generateRequestConfirmationEvent, the loop already looks up originalFunctionCall by ID from functionCallEvent.Content.Parts. It just needs to also look up the corresponding ThoughtSignature from the original Part and set it on the new part:

// Find the original Part's ThoughtSignature
var originalThoughtSig []byte
for _, p := range functionCallEvent.Content.Parts {
    if p.FunctionCall != nil && p.FunctionCall.ID == funcID {
        originalThoughtSig = p.ThoughtSignature
        break
    }
}

part := &genai.Part{
    FunctionCall: requestConfirmationFC,
}
if len(originalThoughtSig) > 0 {
    part.ThoughtSignature = originalThoughtSig
}
parts = append(parts, part)

This mirrors how stream_aggregator.go already handles ThoughtSignature propagation.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions