Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
366 lines
12 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|