Files
new-api-custom/relaykit/relayconvert/reasoning/gemini_test.go
T
cneicy ceddfd3d6b feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
2026-09-26 04:49:11 +08:00

366 lines
12 KiB
Go

package reasoning
import (
"testing"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRenderGeminiMapsAndClampsInsteadOfRejecting(t *testing.T) {
t.Parallel()
budget8192 := 8192
budget100000 := 100000
budget100 := 100
tests := []struct {
name string
model string
intent Intent
wantNil bool
wantLevel string
wantBudget *int
wantEffort Effort
wantCodes []string
}{
{
name: "budget on gemini 3.1 pro becomes level",
model: "gemini-3.1-pro-preview",
intent: Intent{BudgetTokens: &budget8192},
wantLevel: "medium",
wantEffort: EffortMedium,
wantCodes: []string{"gemini_budget_to_level"},
},
{
name: "medium on gemini 3 pro moves to high",
model: "gemini-3-pro-preview",
intent: Intent{Mode: ModeEnabled, Effort: EffortMedium},
wantLevel: "high",
wantEffort: EffortHigh,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "xhigh on gemini 3 flash moves to high",
model: "gemini-3-flash-preview",
intent: Intent{Mode: ModeEnabled, Effort: EffortXHigh},
wantLevel: "high",
wantEffort: EffortHigh,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "effort on gemini 2.5 flash becomes budget",
model: "gemini-2.5-flash",
intent: Intent{Mode: ModeEnabled, Effort: EffortHigh},
wantBudget: intPtr(24576),
wantEffort: EffortHigh,
},
{
name: "oversized budget on gemini 2.5 flash is clamped",
model: "gemini-2.5-flash",
intent: Intent{BudgetTokens: &budget100000},
wantBudget: intPtr(24576),
wantEffort: EffortHigh,
wantCodes: []string{"gemini_budget_clamped"},
},
{
name: "undersized budget on gemini 2.5 flash lite is raised",
model: "gemini-2.5-flash-lite",
intent: Intent{BudgetTokens: &budget100},
wantBudget: intPtr(512),
wantEffort: EffortLow,
wantCodes: []string{"gemini_budget_clamped"},
},
{
name: "nothinking on gemini 3.1 pro uses lowest level",
model: "gemini-3.1-pro-preview",
intent: Intent{Mode: ModeDisabled, Effort: EffortNone, Source: SourceSuffix},
wantLevel: "low",
wantEffort: EffortLow,
wantCodes: []string{"gemini_thinking_disable_unsupported"},
},
{
name: "nothinking on gemini 2.5 pro uses minimum budget",
model: "gemini-2.5-pro",
intent: Intent{Mode: ModeDisabled, Effort: EffortNone, Source: SourceSuffix},
wantBudget: intPtr(128),
wantEffort: EffortLow,
wantCodes: []string{"gemini_thinking_disable_unsupported"},
},
{
name: "nothinking on gemini 2.5 flash disables",
model: "gemini-2.5-flash",
intent: Intent{Mode: ModeDisabled, Effort: EffortNone, Source: SourceSuffix},
wantBudget: intPtr(0),
wantEffort: EffortNone,
},
{
name: "unknown model gets no synthesized config",
model: "gemini-2.0-flash-thinking-exp",
intent: Intent{Mode: ModeEnabled, Source: SourceSuffix},
wantNil: true,
wantCodes: []string{"gemini_unknown_capability"},
},
{
name: "image model drops thinking controls",
model: "gemini-2.5-flash-image",
intent: Intent{Mode: ModeEnabled, Source: SourceSuffix},
wantNil: true,
wantCodes: []string{"gemini_thinking_unsupported"},
},
{
name: "gemini 3 pro image keeps include thoughts only",
model: "gemini-3-pro-image-preview",
intent: Intent{Mode: ModeEnabled, Effort: EffortLow, IncludeThoughts: boolPtr(true)},
wantEffort: EffortHigh,
wantCodes: []string{"gemini_thinking_unsupported"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := RenderGemini(tt.model, tt.intent, nil, 0)
require.NoError(t, err)
assert.Equal(t, tt.wantEffort, got.EffectiveEffort)
assert.Equal(t, tt.wantCodes, diagnosticCodes(got.Diagnostics))
if tt.wantNil {
assert.Nil(t, got.Config)
return
}
require.NotNil(t, got.Config)
assert.Equal(t, tt.wantLevel, got.Config.ThinkingLevel)
if tt.wantBudget == nil {
assert.Nil(t, got.Config.ThinkingBudget)
return
}
require.NotNil(t, got.Config.ThinkingBudget)
assert.Equal(t, *tt.wantBudget, *got.Config.ThinkingBudget)
})
}
}
func TestNormalizeGeminiThinkingConfigRewritesNativeConfig(t *testing.T) {
t.Parallel()
tests := []struct {
name string
model string
config dto.GeminiThinkingConfig
wantErr error
wantNil bool
wantLevel string
wantBudget *int
wantThoughts *bool
wantEffort Effort
wantCodes []string
}{
{
name: "budget on gemini 3.1 pro becomes level",
model: "gemini-3.1-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(8192)},
wantLevel: "medium",
wantEffort: EffortMedium,
wantCodes: []string{"gemini_budget_to_level"},
},
{
name: "dynamic budget on gemini 3 becomes high",
model: "gemini-3-flash-preview",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(-1)},
wantLevel: "high",
wantEffort: EffortHigh,
wantCodes: []string{"gemini_budget_to_level"},
},
{
name: "level on gemini 2.5 flash becomes budget",
model: "gemini-2.5-flash",
config: dto.GeminiThinkingConfig{ThinkingLevel: "high"},
wantBudget: intPtr(24576),
wantEffort: EffortHigh,
wantCodes: []string{"gemini_level_to_budget"},
},
{
name: "medium on gemini 3 pro moves to high",
model: "gemini-3-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingLevel: "medium"},
wantLevel: "high",
wantEffort: EffortHigh,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "minimal on gemini 3.1 pro moves to low",
model: "gemini-3.1-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingLevel: "minimal"},
wantLevel: "low",
wantEffort: EffortLow,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "supported level is kept without diagnostics",
model: "gemini-3.1-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingLevel: "low"},
wantLevel: "low",
wantEffort: EffortLow,
},
{
name: "uppercase enum level is canonicalized without diagnostics",
model: "gemini-3.7-flash",
config: dto.GeminiThinkingConfig{ThinkingLevel: "MEDIUM"},
wantLevel: "medium",
wantEffort: EffortMedium,
},
{
name: "mixed case level with whitespace is canonicalized without diagnostics",
model: "gemini-3.7-flash",
config: dto.GeminiThinkingConfig{ThinkingLevel: " Medium "},
wantLevel: "medium",
wantEffort: EffortMedium,
},
{
name: "uppercase minimal on gemini 3 pro moves to low",
model: "gemini-3-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingLevel: "MINIMAL"},
wantLevel: "low",
wantEffort: EffortLow,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "xhigh on gemini 3 moves to high",
model: "gemini-3.7-flash",
config: dto.GeminiThinkingConfig{ThinkingLevel: "xhigh"},
wantLevel: "high",
wantEffort: EffortHigh,
wantCodes: []string{"gemini_level_adjusted"},
},
{
name: "unknown level is rejected",
model: "gemini-3.7-flash",
config: dto.GeminiThinkingConfig{ThinkingLevel: "ULTRA"},
wantErr: ErrUnsupportedEffort,
},
{
name: "oversized budget on gemini 2.5 flash is clamped",
model: "gemini-2.5-flash",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(100000)},
wantBudget: intPtr(24576),
wantEffort: EffortHigh,
wantCodes: []string{"gemini_budget_clamped"},
},
{
name: "undersized budget on gemini 2.5 flash lite is raised",
model: "gemini-2.5-flash-lite",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(100)},
wantBudget: intPtr(512),
wantEffort: EffortLow,
wantCodes: []string{"gemini_budget_clamped"},
},
{
name: "zero budget on gemini 2.5 pro uses minimum budget",
model: "gemini-2.5-pro",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(0)},
wantBudget: intPtr(128),
wantEffort: EffortLow,
wantCodes: []string{"gemini_thinking_disable_unsupported"},
},
{
name: "zero budget on gemini 2.5 flash stays off",
model: "gemini-2.5-flash",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(0)},
wantBudget: intPtr(0),
wantEffort: EffortNone,
},
{
name: "zero budget on gemini 3.1 pro uses lowest level",
model: "gemini-3.1-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(0)},
wantLevel: "low",
wantEffort: EffortLow,
wantCodes: []string{"gemini_thinking_disable_unsupported"},
},
{
name: "dynamic budget on gemini 2.5 passes through",
model: "gemini-2.5-flash",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(-1)},
wantBudget: intPtr(-1),
wantEffort: EffortHigh,
},
{
name: "tts model drops the config",
model: "gemini-2.5-flash-preview-tts",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(1024)},
wantNil: true,
wantCodes: []string{"gemini_thinking_unsupported"},
},
{
name: "gemini 3 pro image keeps include thoughts only",
model: "gemini-3-pro-image-preview",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(1024), IncludeThoughts: boolPtr(true)},
wantThoughts: boolPtr(true),
wantEffort: EffortHigh,
wantCodes: []string{"gemini_thinking_unsupported"},
},
{
name: "unknown model passes the config through",
model: "gemini-2.0-flash-thinking-exp",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(4096)},
wantBudget: intPtr(4096),
wantEffort: EffortMedium,
},
{
name: "budget and level on gemini 3 keeps the level",
model: "gemini-3.1-pro-preview",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(8192), ThinkingLevel: "low"},
wantLevel: "low",
wantEffort: EffortLow,
wantCodes: []string{"gemini_budget_level_conflict"},
},
{
name: "budget and level on gemini 2.5 keeps the budget",
model: "gemini-2.5-flash",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(4096), ThinkingLevel: "high"},
wantBudget: intPtr(4096),
wantEffort: EffortMedium,
wantCodes: []string{"gemini_budget_level_conflict"},
},
{
name: "budget and level on an unknown model is rejected",
model: "gemini-2.0-flash-thinking-exp",
config: dto.GeminiThinkingConfig{ThinkingBudget: intPtr(1024), ThinkingLevel: "high"},
wantErr: ErrEffortConflict,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
config := tt.config
generation := &dto.GeminiChatGenerationConfig{ThinkingConfig: &config}
effort, diagnostics, err := NormalizeGeminiThinkingConfig(tt.model, generation)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantEffort, effort)
assert.Equal(t, tt.wantCodes, diagnosticCodes(diagnostics))
if tt.wantNil {
assert.Nil(t, generation.ThinkingConfig)
return
}
require.NotNil(t, generation.ThinkingConfig)
assert.Equal(t, tt.wantLevel, generation.ThinkingConfig.ThinkingLevel)
if tt.wantBudget == nil {
assert.Nil(t, generation.ThinkingConfig.ThinkingBudget)
} else {
require.NotNil(t, generation.ThinkingConfig.ThinkingBudget)
assert.Equal(t, *tt.wantBudget, *generation.ThinkingConfig.ThinkingBudget)
}
if tt.wantThoughts != nil {
require.NotNil(t, generation.ThinkingConfig.IncludeThoughts)
assert.Equal(t, *tt.wantThoughts, *generation.ThinkingConfig.IncludeThoughts)
}
})
}
}