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},
{"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.")},
{"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.")},
{"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) {
// Map deprecated or preview model names to active equivalents
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"
}
req.Model = normalizeGeminiModel(req.Model)
// Gemini mapping
var contents []GeminiContent
@@ -273,7 +265,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
}
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
} else {
// 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") ||
strings.Contains(lowerModel, "thinking") ||
strings.Contains(lowerModel, "gemini-") ||
hasMappedTools {
// Use v1beta for preview, newer models, or when using tools
hasMappedTools ||
hasHistoryToolCalls(contents) {
// Use v1beta for preview, newer models, tool use, or historical tool calls
if !strings.Contains(baseURL, "v1beta") {
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) {
// Map deprecated or preview model names to active equivalents
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"
}
req.Model = normalizeGeminiModel(req.Model)
// Simplified Gemini mapping
var contents []GeminiContent
@@ -535,7 +520,7 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
}
if foundAny {
contents = append(contents, GeminiContent{Role: "function", Parts: functionParts})
contents = append(contents, GeminiContent{Role: "user", Parts: functionParts})
i = j - 1
}
continue
@@ -617,8 +602,9 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
if strings.Contains(lowerModel, "preview") ||
strings.Contains(lowerModel, "thinking") ||
strings.Contains(lowerModel, "gemini-") ||
hasMappedTools {
// Use v1beta for preview, newer models, or when using tools
hasMappedTools ||
hasHistoryToolCalls(contents) {
// Use v1beta for preview, newer models, tool use, or historical tool calls
if !strings.Contains(baseURL, "v1beta") {
baseURL = strings.Replace(baseURL, "/v1", "/v1beta", 1)
}
@@ -748,3 +734,30 @@ func resolveToolName(toolMsg models.UnifiedMessage, toolCalls []models.ToolCall,
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
}