fix(openai): keep Gemini 3 thought signatures server-side, keyed by tool call id - #35
Merged
Merged
Conversation
…ool call id Gemini 3 rejects a functionCall that returns in history without the thoughtSignature it was issued with (HTTP 400, INVALID_ARGUMENT "Function call is missing a thought signature"). The proxy hands the signature to the client as extra_content.google.thought_signature, but ordinary OpenAI clients (OpenAI SDKs, litellm, most agent frameworks) do not echo unknown fields back. Mag1cFall#26 made those requests pass by sending skip_thought_signature_validator when the signature is missing. This keeps the real signature instead: store it per tool call id on both response paths (streaming and non-streaming) and restore it in chatMessageContent when the assistant message comes back without extra_content. A client that does echo extra_content still wins, and calls this process never emitted still fall back to the Mag1cFall#26 placeholder. The store is a FIFO bounded at 1024 entries. This change was written with the help of an AI assistant (Claude). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Live-verified on the rebased branch (current (Written with the help of an AI assistant.) |
Owner
|
Thanks! We reproduced it on main, tested the round trip after merging (streaming and non-streaming), and merged it. We also fixed a few more things in a follow-up commit, mainly keying the store by call ID, function name and arguments, since upstream call IDs are short enough to collide: aec8a47 Thanks again for the contribution~ |
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.
Problem
Gemini 3 rejects a
functionCallthat comes back in history without thethoughtSignatureit was issued with:The proxy already hands the signature to the client as
extra_content.google.thought_signature, but ordinary OpenAI clients (OpenAI SDKs, litellm, most agent frameworks) don't echo unknown fields back. So the second request of every tool round trip loses it.#26 fixed the 400 by sending
skip_thought_signature_validatorwhen the signature is missing. That works, but the model gets a placeholder instead of the signature it actually produced.Change
internal/api/thoughtsig.goadds a small in-memory store keyed by tool call id. It's a FIFO capped at 1024 entries.chatMessageContentputs the stored signature back when an assistant tool call comes back withoutextra_content.extra_contentstill wins.Tests
internal/api/thoughtsig_test.gochecks two things: a stored signature is restored when the client omits it and a client-supplied one takes precedence, and the store stays bounded.go vet ./internal/api/andgo test ./internal/api/ ./internal/aistudio/pass.mainfor this PR. I haven't re-run it live on the rebased base yet because my AI Studio sessions have expired.Disclosure
This change was written with the help of an AI assistant (Claude). I reviewed it and ran the tests above.
🤖 Generated with Claude Code