fix(gemini): resolve latency and error issues with model normalization and v1beta tool routing
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
2026-07-28 17:58:21 +00:00
parent c96e1d0350
commit c6329d5612
2 changed files with 39 additions and 26 deletions
+2 -2
View File
@@ -209,9 +209,9 @@ func (db *DB) RunMigrations() error {
}{ }{
{"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, "", nil, nil, nil}, {"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`, "", nil, nil, nil},
{"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, "", nil, nil, nil}, {"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`, "", nil, nil, nil},
{"gemini-auto", "heuristic", `["gemini-2.5-flash","gemini-2.5-pro"]`, "", nil, nil, nil}, {"gemini-auto", "heuristic", `["gemini-3.5-flash-lite","gemini-3.1-flash-lite","gemini-2.5-flash"]`, "", nil, nil, nil},
{"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.6","deepseek-v4-pro"]`, "", nil, intPtr(9), strPtr("Complex Coding, Logic, Agents.")}, {"heavy-logic", "heuristic", `["grok-4.3","kimi-k2.6","deepseek-v4-pro"]`, "", nil, intPtr(9), strPtr("Complex Coding, Logic, Agents.")},
{"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3-flash-preview"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")}, {"standard-pro", "heuristic", `["gpt-5.4-mini","gemini-3.5-flash-lite"]`, "", nil, intPtr(5), strPtr("General Assistant, Long Docs.")},
{"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, "", nil, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")}, {"fast-flow", "heuristic", `["deepseek-v4-flash","gpt-5.4-nano"]`, "", nil, intPtr(2), strPtr("Classification, JSON, Basic Q&A.")},
{"dispatcher", "classifier", `["fast-flow","standard-pro","heavy-logic"]`, "gpt-5.4-nano", intPtr(10), nil, strPtr("Auto-dispatches to tier groups by complexity.")}, {"dispatcher", "classifier", `["fast-flow","standard-pro","heavy-logic"]`, "gpt-5.4-nano", intPtr(10), nil, strPtr("Auto-dispatches to tier groups by complexity.")},
} }
+37 -24
View File
@@ -202,15 +202,7 @@ func (p *GeminiProvider) ResponsesStream(ctx context.Context, req *models.Respon
} }
func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) { func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.UnifiedRequest) (*models.ChatCompletionResponse, error) {
// Map deprecated or preview model names to active equivalents req.Model = normalizeGeminiModel(req.Model)
switch req.Model {
case "gemini-2.0-flash":
req.Model = "gemini-2.5-flash"
case "gemini-3-flash":
req.Model = "gemini-3-flash-preview"
case "gemini-3-pro":
req.Model = "gemini-3-pro-preview"
}
// Gemini mapping // Gemini mapping
var contents []GeminiContent var contents []GeminiContent
@@ -273,7 +265,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
} }
if foundAny { if foundAny {
contents = append(contents, GeminiContent{Role: "function", Parts: functionParts}) contents = append(contents, GeminiContent{Role: "user", Parts: functionParts})
i = j - 1 // Advance outer loop past the tool messages we consumed i = j - 1 // Advance outer loop past the tool messages we consumed
} else { } else {
// If no tool results found but assistant made calls, Gemini WILL error. // If no tool results found but assistant made calls, Gemini WILL error.
@@ -364,8 +356,9 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
if strings.Contains(lowerModel, "preview") || if strings.Contains(lowerModel, "preview") ||
strings.Contains(lowerModel, "thinking") || strings.Contains(lowerModel, "thinking") ||
strings.Contains(lowerModel, "gemini-") || strings.Contains(lowerModel, "gemini-") ||
hasMappedTools { hasMappedTools ||
// Use v1beta for preview, newer models, or when using tools hasHistoryToolCalls(contents) {
// Use v1beta for preview, newer models, tool use, or historical tool calls
if !strings.Contains(baseURL, "v1beta") { if !strings.Contains(baseURL, "v1beta") {
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1) baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
} }
@@ -478,15 +471,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
} }
func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) { func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.UnifiedRequest) (<-chan *models.ChatCompletionStreamResponse, error) {
// Map deprecated or preview model names to active equivalents req.Model = normalizeGeminiModel(req.Model)
switch req.Model {
case "gemini-2.0-flash":
req.Model = "gemini-2.5-flash"
case "gemini-3-flash":
req.Model = "gemini-3-flash-preview"
case "gemini-3-pro":
req.Model = "gemini-3-pro-preview"
}
// Simplified Gemini mapping // Simplified Gemini mapping
var contents []GeminiContent var contents []GeminiContent
@@ -535,7 +520,7 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
} }
if foundAny { if foundAny {
contents = append(contents, GeminiContent{Role: "function", Parts: functionParts}) contents = append(contents, GeminiContent{Role: "user", Parts: functionParts})
i = j - 1 i = j - 1
} }
continue continue
@@ -617,8 +602,9 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
if strings.Contains(lowerModel, "preview") || if strings.Contains(lowerModel, "preview") ||
strings.Contains(lowerModel, "thinking") || strings.Contains(lowerModel, "thinking") ||
strings.Contains(lowerModel, "gemini-") || strings.Contains(lowerModel, "gemini-") ||
hasMappedTools { hasMappedTools ||
// Use v1beta for preview, newer models, or when using tools hasHistoryToolCalls(contents) {
// Use v1beta for preview, newer models, tool use, or historical tool calls
if !strings.Contains(baseURL, "v1beta") { if !strings.Contains(baseURL, "v1beta") {
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1) baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
} }
@@ -748,3 +734,30 @@ func resolveToolName(toolMsg models.UnifiedMessage, toolCalls []models.ToolCall,
return "unknown_function" return "unknown_function"
} }
func normalizeGeminiModel(model string) string {
switch model {
case "gemini-2.0-flash", "gemini-1.5-flash":
return "gemini-3.5-flash-lite"
case "gemini-3-flash", "gemini-3-flash-preview":
return "gemini-3.5-flash-lite"
case "gemini-3-pro", "gemini-3-pro-preview", "gemini-1.5-pro":
return "gemini-2.5-pro"
default:
return model
}
}
func hasHistoryToolCalls(contents []GeminiContent) bool {
for _, c := range contents {
if c.Role == "function" {
return true
}
for _, p := range c.Parts {
if p.FunctionCall != nil || p.FunctionResponse != nil {
return true
}
}
}
return false
}