Skip to content

Capture Gemini thought_signature carried on function-call response parts for multi-turn tool round-trips#693

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:gemini-thought-signature-funccall
Open

Capture Gemini thought_signature carried on function-call response parts for multi-turn tool round-trips#693
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:gemini-thought-signature-funccall

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

In provider/geminiprovider/agent.go, buildResponsePart only read part.ThoughtSignature inside the if part.Thought branch. Gemini 3 attaches the opaque thought_signature to the same part that carries the function_call (Thought is false, Text is empty, ThoughtSignature is set). For such a part the Thought branch is skipped, the else if part.Text != "" branch is skipped, and the FunctionCall branch fired without capturing the signature, so it was silently dropped. Because it was never parsed into ProtectedData, the replay path in buildRequestParts (which decodes TextReasoningContent.ProtectedData back into part.ThoughtSignature) could never send it back, breaking multi-turn tool round-trips.

The fix emits a TextReasoningContent{ProtectedData: base64(part.ThoughtSignature)} immediately before the FunctionCallContent whenever a function-call part carries a signature.

Why (parity)

This mirrors the Python reference, which emits a text-reasoning content (protected_data=base64(part.thought_signature)) immediately preceding the function call. The existing replay path already decodes ProtectedData back into part.ThoughtSignature, so the round-trip closes with no other change.

Testing

Added TestResponseWithFunctionCallThoughtSignature in the canonical agent_test.go: it feeds a response whose single part has functionCall set (no thought, no text) plus a thoughtSignature, and asserts the returned contents include a *message.TextReasoningContent whose ProtectedData equals the base64 signature, ordered before the *message.FunctionCallContent. The test fails before the fix and passes after. go build ./..., go vet ./provider/geminiprovider/..., and go test ./provider/geminiprovider/... all pass.

Copilot AI review requested due to automatic review settings July 24, 2026 01:53
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 24, 2026 01:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Gemini 3 multi-turn tool round-trips by preserving thought_signature when it’s attached to a functionCall response part (with Thought=false and empty Text), ensuring the signature is captured into framework ProtectedData and can be replayed on the next request turn.

Changes:

  • In buildResponsePart, emit a TextReasoningContent containing base64-encoded ThoughtSignature immediately before FunctionCallContent when a function-call part carries a signature.
  • Add TestResponseWithFunctionCallThoughtSignature to assert signature capture and ordering (reasoning content precedes function call).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
provider/geminiprovider/agent.go Captures ThoughtSignature on function-call parts by emitting a preceding TextReasoningContent for replay in subsequent turns.
provider/geminiprovider/agent_test.go Adds coverage for Gemini 3 behavior where thoughtSignature is attached to the function-call part and must round-trip correctly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
@PratikDhanave
PratikDhanave force-pushed the gemini-thought-signature-funccall branch from 36fd9f8 to a5f3f36 Compare July 24, 2026 02:14
@github-actions

This comment has been minimized.

Gemini 3 attaches the opaque thought_signature to the same part that
carries the function_call (Thought false, Text empty). buildResponsePart
only read the signature inside the Thought branch, so for these parts it
was silently dropped and could never be replayed on the next turn.

Emit a TextReasoningContent (ProtectedData=base64(thought_signature))
immediately before the FunctionCallContent, matching the Python
reference. The existing replay path already decodes ProtectedData back
into part.ThoughtSignature, closing the round-trip.
@PratikDhanave
PratikDhanave force-pushed the gemini-thought-signature-funccall branch from a5f3f36 to 0ff44f0 Compare July 24, 2026 09:34
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — ✅ Approved

Scope: provider/geminiprovider/agent.go (internal function buildResponsePart) + agent_test.go. No exported Go API surface changed.

What changed: When a Gemini response part carries a ThoughtSignature on a function-call part (Thought == false, Text == ""), the fix now emits a TextReasoningContent{ProtectedData: base64(thought_signature)} immediately before the FunctionCallContent. The existing buildRequestParts replay path already decodes ProtectedData back into part.ThoughtSignature, closing the round-trip.

Cross-repo parity: The PR correctly identifies this as mirroring the Python reference, which emits text-reasoning content (protected_data=base64(part.thought_signature)) immediately preceding a function call part. The Go change is semantically equivalent to the upstream Python behavior. No .NET divergence identified (Gemini SDK is Google-specific; .NET uses its own provider path).

API label: public-api-change is not warranted — only an unexported helper and a test file changed. The parity-approved label already present is correct and should remain.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 26.4 AIC · ⌖ 5.57 AIC · ⊞ 5.9K ·

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

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants