From cbae69ad640f4fdf9e7c1f0f4cfa38c6c0daf497 Mon Sep 17 00:00:00 2001 From: lc6464 <64722907+lc6464@users.noreply.github.com> Date: Sat, 11 Apr 2026 01:38:13 +0800 Subject: [PATCH] fix(gemini): honor pro-model thinking constraints --- pkg/providers/gemini_provider.go | 19 ++++++++++ pkg/providers/gemini_provider_test.go | 50 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/pkg/providers/gemini_provider.go b/pkg/providers/gemini_provider.go index 5952188fd..7b913b775 100644 --- a/pkg/providers/gemini_provider.go +++ b/pkg/providers/gemini_provider.go @@ -347,12 +347,21 @@ func buildGeminiThinkingConfig(model string, options map[string]any) map[string] config["includeThoughts"] = includeThoughts if isGemini25Model(model) { + if isGemini25ProModel(model) && (rawLevel == "off" || rawLevel == "minimal") { + // Gemini 2.5 Pro cannot disable thinking; keep model-default thinking. + return config + } if budget, ok := mapGeminiThinkingBudget(rawLevel); ok { config["thinkingBudget"] = budget } return config } + if isGemini3ProModel(model) && (rawLevel == "off" || rawLevel == "minimal") { + // Gemini 3.x Pro does not support minimal thinking level. + return config + } + if thinkingLevel := mapGeminiThinkingLevel(rawLevel); thinkingLevel != "" { config["thinkingLevel"] = thinkingLevel } @@ -369,6 +378,16 @@ func isGemini25Model(model string) bool { return strings.Contains(lowerModel, "gemini-2.5") || strings.Contains(lowerModel, "gemini-25") } +func isGemini25ProModel(model string) bool { + lowerModel := strings.ToLower(strings.TrimSpace(model)) + return isGemini25Model(lowerModel) && strings.Contains(lowerModel, "pro") +} + +func isGemini3ProModel(model string) bool { + lowerModel := strings.ToLower(strings.TrimSpace(model)) + return strings.Contains(lowerModel, "gemini-3") && strings.Contains(lowerModel, "pro") +} + func mapGeminiThinkingBudget(level string) (int, bool) { level = strings.ToLower(strings.TrimSpace(level)) if level == "" { diff --git a/pkg/providers/gemini_provider_test.go b/pkg/providers/gemini_provider_test.go index 19b9fcd63..cbfb97c45 100644 --- a/pkg/providers/gemini_provider_test.go +++ b/pkg/providers/gemini_provider_test.go @@ -362,6 +362,56 @@ func TestGeminiProvider_BuildRequestBody_DefaultsThinkingOffForGemini3(t *testin } } +func TestGeminiProvider_BuildRequestBody_DefaultsThinkingOffForGemini25Pro(t *testing.T) { + provider := NewGeminiProvider("test-key", "https://example.com/v1beta", "", "", 0, nil, nil) + body := provider.buildRequestBody( + []Message{{Role: "user", Content: "hello"}}, + nil, + "gemini-2.5-pro", + nil, + ) + + generationConfig, ok := body["generationConfig"].(map[string]any) + if !ok { + t.Fatalf("generationConfig = %#v, want map", body["generationConfig"]) + } + thinkingConfig, ok := generationConfig["thinkingConfig"].(map[string]any) + if !ok { + t.Fatalf("thinkingConfig = %#v, want map", generationConfig["thinkingConfig"]) + } + if includeThoughts, ok := thinkingConfig["includeThoughts"].(bool); !ok || includeThoughts { + t.Fatalf("includeThoughts = %#v, want false for default/off", thinkingConfig["includeThoughts"]) + } + if _, hasBudget := thinkingConfig["thinkingBudget"]; hasBudget { + t.Fatalf("thinkingBudget should be omitted for Gemini 2.5 Pro default/off: %#v", thinkingConfig) + } +} + +func TestGeminiProvider_BuildRequestBody_DefaultsThinkingOffForGemini31Pro(t *testing.T) { + provider := NewGeminiProvider("test-key", "https://example.com/v1beta", "", "", 0, nil, nil) + body := provider.buildRequestBody( + []Message{{Role: "user", Content: "hello"}}, + nil, + "gemini-3.1-pro", + nil, + ) + + generationConfig, ok := body["generationConfig"].(map[string]any) + if !ok { + t.Fatalf("generationConfig = %#v, want map", body["generationConfig"]) + } + thinkingConfig, ok := generationConfig["thinkingConfig"].(map[string]any) + if !ok { + t.Fatalf("thinkingConfig = %#v, want map", generationConfig["thinkingConfig"]) + } + if includeThoughts, ok := thinkingConfig["includeThoughts"].(bool); !ok || includeThoughts { + t.Fatalf("includeThoughts = %#v, want false for default/off", thinkingConfig["includeThoughts"]) + } + if _, hasLevel := thinkingConfig["thinkingLevel"]; hasLevel { + t.Fatalf("thinkingLevel should be omitted for Gemini 3.1 Pro default/off: %#v", thinkingConfig) + } +} + func TestGeminiProvider_BuildRequestBody_PreservesMultipleSystemMessages(t *testing.T) { provider := NewGeminiProvider("test-key", "https://example.com/v1beta", "", "", 0, nil, nil) body := provider.buildRequestBody(