Skip to content

fix(openai): keep Gemini 3 thought signatures server-side, keyed by tool call id - #35

Merged
Mag1cFall merged 1 commit into
Mag1cFall:mainfrom
ItszFinn:fix/keep-thought-signatures
Sep 26, 2026
Merged

Mag1cFall merged 1 commit into
Mag1cFall:mainfrom
ItszFinn:fix/keep-thought-signatures

Conversation

@ItszFinn

Copy link
Copy Markdown
Contributor

Problem

Gemini 3 rejects a functionCall that comes back in history without the thoughtSignature it was issued with:

HTTP 400 INVALID_ARGUMENT: Function call is missing a thought signature.

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_validator when the signature is missing. That works, but the model gets a placeholder instead of the signature it actually produced.

Change

  • Store: internal/api/thoughtsig.go adds a small in-memory store keyed by tool call id. It's a FIFO capped at 1024 entries.
  • Fill: both response paths (streaming and non-streaming) put the signature of every emitted tool call into the store.
  • Restore: chatMessageContent puts the stored signature back when an assistant tool call comes back without extra_content.
  • Precedence:

Tests

  • internal/api/thoughtsig_test.go checks 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/ and go test ./internal/api/ ./internal/aistudio/ pass.
  • The same patch ran in my deployment (litellm in front) from 2026-09-12 on the pre-fix(aistudio): 兼容 JSON Schema const 字段并在历史函数调用缺少签名时填充 bypass 标记 #26 base. Without it, every multi-turn tool round trip failed with the error above.
  • I rebased it onto current main for 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

…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>
@ItszFinn

Copy link
Copy Markdown
Contributor Author

Live-verified on the rebased branch (current main + this patch), with litellm in front, which does not echo extra_content: our conformance suite passes 7/7 for gemini-3.8-flash (chat, system, stream, tool calls non-stream and SSE, tool-result round trip, parallel tool calls).

(Written with the help of an AI assistant.)

@Mag1cFall
Mag1cFall merged commit 5b774db into Mag1cFall:main Sep 26, 2026
@Mag1cFall

Copy link
Copy Markdown
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~

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants