Skip empty Gemini thought parts instead of emitting empty reasoning#543
Skip empty Gemini thought parts instead of emitting empty reasoning#543PratikDhanave wants to merge 4 commits into
Conversation
buildResponsePart appended a TextReasoningContent for every part with
Thought=true, unconditionally — unlike the text branch, which guards on
part.Text != "". A thought part carrying neither thinking text nor a
ThoughtSignature therefore produced an empty, information-free
TextReasoningContent{Text:"", ProtectedData:""} in the response.
Guard the thought branch on part.Text != "" || len(part.ThoughtSignature) > 0,
mirroring the text guard. A signature-only thought is still emitted so its
signature round-trips via ProtectedData in multi-turn requests; only the
fully-empty thought part is skipped.
There was a problem hiding this comment.
Pull request overview
This PR updates the Gemini provider’s response-part translation so that “thought” parts that carry no useful information (no thinking text and no thought signature) are omitted rather than being converted into an empty TextReasoningContent, aligning behavior with the existing “skip empty text parts” logic.
Changes:
- Skip emitting
TextReasoningContentforthought=trueparts when bothTextandThoughtSignatureare empty. - Add a black-box test covering an empty thought-only part in the response.
- Minor formatting change to a test call site for
RunText(...).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| provider/geminiprovider/agent.go | Adds a guard to avoid appending empty reasoning content for fully-empty thought parts while still preserving signature-only thoughts. |
| provider/geminiprovider/agent_test.go | Adds a regression test for omitting empty thought parts and includes a minor formatting adjustment in an existing test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for _, msg := range result.Messages { | ||
| for _, c := range msg.Contents { | ||
| if rc, ok := c.(*message.TextReasoningContent); ok { | ||
| t.Errorf("expected no reasoning content for an empty thought part, got Text=%q ProtectedData=%q", rc.Text, rc.ProtectedData) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Done — the test now also asserts the non-thought text part ("The answer is 42.") is emitted, so it fails if the provider drops all response parts, not just the reasoning content.
Address review feedback: the test asserted only the absence of reasoning content, so it would also pass if the provider dropped all response parts. Also assert the normal text part is still emitted.
Problem
In
buildResponsePart(provider/geminiprovider/agent.go), a response part withThought=trueis appended asTextReasoningContentunconditionally, while the sibling text branch guards onpart.Text != "":So a thought part carrying neither thinking text nor a
ThoughtSignatureproduces an empty, information-freeTextReasoningContent{Text:"", ProtectedData:""}in the response message.Fix
Guard the thought branch on
part.Text != "" || len(part.ThoughtSignature) > 0, mirroring the text guard. A signature-only thought (empty text, non-empty signature) is still emitted so the signature round-trips viaProtectedDatain multi-turn requests — only the fully-empty thought part is skipped.Test
TestResponseWithEmptyThoughtPartOmitted(black-box, inagent_test.go) returns a candidate with an empty{"thought": true}part alongside a normal text part, and asserts noTextReasoningContentis produced. Fails onmain(empty reasoning content emitted), passes with the fix. The existing thinking-content and thought-signature tests are unchanged.