-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix(gemini): Implement Gemini 3 thought_signature handling for stateful reasoning #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -621,13 +621,19 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M | |
| } | ||
| for _, tc := range response.ToolCalls { | ||
| argumentsJSON, _ := json.Marshal(tc.Arguments) | ||
| // Copy ExtraContent to ensure thought_signature is persisted | ||
| extraContent := tc.ExtraContent | ||
|
|
||
| assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{ | ||
| ID: tc.ID, | ||
| Type: "function", | ||
| Function: &providers.FunctionCall{ | ||
| Name: tc.Name, | ||
| Arguments: string(argumentsJSON), | ||
| Name: tc.Name, | ||
| Arguments: string(argumentsJSON), | ||
| }, | ||
| ExtraContent: extraContent, | ||
| // We also set internal ThoughtSignature, but ExtraContent is what matters for serialization | ||
| ThoughtSignature: tc.ThoughtSignature, | ||
|
Comment on lines
+634
to
+636
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ThoughtSignture should be included in ExtraContent. Why are we pulling it out to its own field? Have you tested this with non Google LLMs? |
||
| }) | ||
| } | ||
| messages = append(messages, assistantMsg) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,8 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too | |
| } | ||
| } | ||
|
|
||
| // Pre-process messages loop removed - relying on ExtraContent persistence in Agent Loop. | ||
|
|
||
| requestBody := map[string]interface{}{ | ||
| "model": model, | ||
| "messages": messages, | ||
|
|
@@ -135,6 +137,11 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { | |
| Name string `json:"name"` | ||
| Arguments string `json:"arguments"` | ||
| } `json:"function"` | ||
| ExtraContent *struct { | ||
| Google *struct { | ||
| ThoughtSignature string `json:"thought_signature"` | ||
| } `json:"google"` | ||
| } `json:"extra_content"` | ||
| } `json:"tool_calls"` | ||
| } `json:"message"` | ||
| FinishReason string `json:"finish_reason"` | ||
|
|
@@ -160,7 +167,12 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { | |
| arguments := make(map[string]interface{}) | ||
| name := "" | ||
|
|
||
| // Handle OpenAI format with nested function object | ||
| // Extract thought_signature from Gemini/Google-specific extra content | ||
| thoughtSignature := "" | ||
| if tc.ExtraContent != nil && tc.ExtraContent.Google != nil { | ||
| thoughtSignature = tc.ExtraContent.Google.ThoughtSignature | ||
| } | ||
|
Comment on lines
+170
to
+174
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like extra lines of code when tc.ExtraContent can just be passed to ToolCall struct |
||
|
|
||
| if tc.Type == "function" && tc.Function != nil { | ||
| name = tc.Function.Name | ||
| if tc.Function.Arguments != "" { | ||
|
|
@@ -178,11 +190,23 @@ func (p *HTTPProvider) parseResponse(body []byte) (*LLMResponse, error) { | |
| } | ||
| } | ||
|
|
||
| toolCalls = append(toolCalls, ToolCall{ | ||
| ID: tc.ID, | ||
| Name: name, | ||
| Arguments: arguments, | ||
| }) | ||
| // Correctly map extracted ExtraContent to ToolCall struct | ||
| toolCall := ToolCall{ | ||
| ID: tc.ID, | ||
| Name: name, | ||
| Arguments: arguments, | ||
| ThoughtSignature: thoughtSignature, // Populating internal field for convenience | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whats this for? Seems to be the wrong structure of a tool call, Thought Signature is an ExtraContent field based by google. Since this is the HTTP Provider other services other than google will use it. |
||
| } | ||
|
|
||
| if thoughtSignature != "" { | ||
| toolCall.ExtraContent = &ExtraContent{ | ||
| Google: &GoogleExtra{ | ||
| ThoughtSignature: thoughtSignature, | ||
| }, | ||
| } | ||
| } | ||
|
Comment on lines
+201
to
+207
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra assignment when line 194 set tool call fields |
||
|
|
||
| toolCalls = append(toolCalls, toolCall) | ||
| } | ||
|
|
||
| return &LLMResponse{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,16 +3,27 @@ package providers | |
| import "context" | ||
|
|
||
| type ToolCall struct { | ||
| ID string `json:"id"` | ||
| Type string `json:"type,omitempty"` | ||
| Function *FunctionCall `json:"function,omitempty"` | ||
| Name string `json:"name,omitempty"` | ||
| Arguments map[string]interface{} `json:"arguments,omitempty"` | ||
| ID string `json:"id"` | ||
| Type string `json:"type,omitempty"` | ||
| Function *FunctionCall `json:"function,omitempty"` | ||
| Name string `json:"name,omitempty"` | ||
| Arguments map[string]interface{} `json:"arguments,omitempty"` | ||
| ThoughtSignature string `json:"-"` // Internal use only | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove |
||
| ExtraContent *ExtraContent `json:"extra_content,omitempty"` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a pointer? |
||
| } | ||
|
|
||
| type ExtraContent struct { | ||
| Google *GoogleExtra `json:"google,omitempty"` | ||
| } | ||
|
|
||
| type GoogleExtra struct { | ||
| ThoughtSignature string `json:"thought_signature,omitempty"` | ||
| } | ||
|
|
||
| type FunctionCall struct { | ||
| Name string `json:"name"` | ||
| Arguments string `json:"arguments"` | ||
| Name string `json:"name"` | ||
| Arguments string `json:"arguments"` | ||
| ThoughtSignature string `json:"-"` // Internal use only | ||
| } | ||
|
|
||
| type LLMResponse struct { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AI fluff