fix: Gemini provider compatibility with Gemini 3 models
- Strip additionalProperties and $schema from tool parameter schemas
(Gemini API rejects these unsupported JSON Schema fields)
- Add thoughtSignature to functionCall parts for multi-turn tool calling
(Gemini 3 models require thought signatures on function call history)
- Ensure functionResponse.response is always a JSON object, never an
array or primitive (wrap non-objects in {"result": ...})
- Resolve tool names from tool_call_id or positional index when the
tool message doesn't carry a name field
- Add functionCall parsing in streaming responses (emitGeminiChunk)
to properly relay tool calls via SSE
- Add error logging for Gemini stream failures with request body dump
- Add unit tests for schema cleaning and streaming tool call emission
This commit is contained in:
@@ -409,8 +409,12 @@ type geminiStreamChunk struct {
|
||||
Candidates []struct {
|
||||
Content struct {
|
||||
Parts []struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Thought string `json:"thought,omitempty"`
|
||||
FunctionCall *struct {
|
||||
Name string `json:"name"`
|
||||
Args json.RawMessage `json:"args"`
|
||||
} `json:"functionCall,omitempty"`
|
||||
} `json:"parts"`
|
||||
} `json:"content"`
|
||||
FinishReason string `json:"finishReason"`
|
||||
@@ -433,6 +437,7 @@ func emitGeminiChunk(ch chan<- *models.ChatCompletionStreamResponse, chunk *gemi
|
||||
content := ""
|
||||
var reasoning *string
|
||||
var finishReason *string
|
||||
var toolCalls []models.ToolCallDelta
|
||||
if len(chunk.Candidates) > 0 {
|
||||
for _, p := range chunk.Candidates[0].Content.Parts {
|
||||
if p.Text != "" {
|
||||
@@ -444,8 +449,26 @@ func emitGeminiChunk(ch chan<- *models.ChatCompletionStreamResponse, chunk *gemi
|
||||
}
|
||||
*reasoning += p.Thought
|
||||
}
|
||||
if p.FunctionCall != nil {
|
||||
name := p.FunctionCall.Name
|
||||
args := string(p.FunctionCall.Args)
|
||||
tcID := fmt.Sprintf("call_%s", name)
|
||||
tcType := "function"
|
||||
toolCalls = append(toolCalls, models.ToolCallDelta{
|
||||
Index: uint32(len(toolCalls)),
|
||||
ID: &tcID,
|
||||
Type: &tcType,
|
||||
Function: &models.FunctionCallDelta{
|
||||
Name: &name,
|
||||
Arguments: &args,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
fr := strings.ToLower(chunk.Candidates[0].FinishReason)
|
||||
if len(toolCalls) > 0 && fr == "" {
|
||||
fr = "tool_calls"
|
||||
}
|
||||
finishReason = &fr
|
||||
}
|
||||
|
||||
@@ -460,6 +483,7 @@ func emitGeminiChunk(ch chan<- *models.ChatCompletionStreamResponse, chunk *gemi
|
||||
Delta: models.ChatStreamDelta{
|
||||
Content: &content,
|
||||
ReasoningContent: reasoning,
|
||||
ToolCalls: toolCalls,
|
||||
},
|
||||
FinishReason: finishReason,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user