mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Antigravity CLI / go-genai 发裸 Gemini 模型名(gemini-3.8-flash)并用 generationConfig.thinkingConfig 表达思考深度,而 Antigravity 上游目录只有 -low/-medium/-high/-tiered 变体。v0.2.5 起 resolveModelMapping 会给 antigravity 账号运行时补 gemini-3.x-flash 裸名自映射,裸名因此被当成显式透传直发上游, 上游回 404 "Requested entity was not found."。 在 ForwardGemini 取映射之前加 resolveGeminiThinkingVariant: - 仅对 gemini- 前缀且不带后缀的裸名生效; - 用户在 credentials.model_mapping 亲写的裸名条目一律不介入,运行时注入的 默认自映射不算用户意图; - thinkingLevel 优先;否则 thinkingBudget <0→high,≤1024→low,≤8192→medium, 其余 high;未携带 thinkingConfig→high;所选变体不存在时按 high→medium→low→tiered 降级;一个变体都没有则回退原逻辑; - thinkingConfig 仍原样透传,上游按 budget 决定实际思考量。
119 lines
5.4 KiB
Go
119 lines
5.4 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newAntigravityAccountWithMapping(mapping map[string]string) *Account {
|
|
mappingAny := make(map[string]any, len(mapping))
|
|
for k, v := range mapping {
|
|
mappingAny[k] = v
|
|
}
|
|
return &Account{
|
|
ID: 1,
|
|
Name: "ag",
|
|
Platform: PlatformAntigravity,
|
|
Credentials: map[string]any{
|
|
"model_mapping": mappingAny,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestGeminiThinkingLevelFromBody(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
body string
|
|
want string
|
|
}{
|
|
{"empty body", ``, "high"},
|
|
{"no thinkingConfig", `{"contents":[]}`, "high"},
|
|
{"budget -1 dynamic", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":-1}}}`, "high"},
|
|
{"budget 0 off", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":0}}}`, "low"},
|
|
{"budget 1000 (agy low)", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":1000}}}`, "low"},
|
|
{"budget 4000 (agy medium)", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":4000}}}`, "medium"},
|
|
{"budget 24576", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":24576}}}`, "high"},
|
|
{"thinkingLevel wins over budget", `{"generationConfig":{"thinkingConfig":{"thinkingBudget":24576,"thinkingLevel":"low"}}}`, "low"},
|
|
{"thinkingLevel medium", `{"generationConfig":{"thinkingConfig":{"thinkingLevel":"MEDIUM"}}}`, "medium"},
|
|
{"invalid json", `{"generationConfig":`, "high"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.want, geminiThinkingLevelFromBody([]byte(tt.body)))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveGeminiThinkingVariant(t *testing.T) {
|
|
// 与生产 Antigravity 账号从上游同步来的目录一致:只有带后缀的变体,没有裸名。
|
|
catalog := map[string]string{
|
|
"gemini-3.8-flash-low": "gemini-3.8-flash-low",
|
|
"gemini-3.8-flash-medium": "gemini-3.8-flash-medium",
|
|
"gemini-3.8-flash-high": "gemini-3.8-flash-high",
|
|
"gemini-3.8-flash-tiered": "gemini-3.8-flash-tiered",
|
|
"gemini-3.6-flash-high": "gemini-3.6-flash-high",
|
|
"gemini-3.1-pro-high": "gemini-3.1-pro-high",
|
|
"gemini-2.5-flash": "gemini-2.5-flash",
|
|
}
|
|
budget := func(v string) []byte {
|
|
return []byte(`{"contents":[],"generationConfig":{"thinkingConfig":{"thinkingBudget":` + v + `}}}`)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
mapping map[string]string
|
|
model string
|
|
body []byte
|
|
want string
|
|
wantMatch bool
|
|
}{
|
|
{"agy high (-1) -> -high", catalog, "gemini-3.8-flash", budget("-1"), "gemini-3.8-flash-high", true},
|
|
{"agy medium (4000) -> -medium", catalog, "gemini-3.8-flash", budget("4000"), "gemini-3.8-flash-medium", true},
|
|
{"agy low (1000) -> -low", catalog, "gemini-3.8-flash", budget("1000"), "gemini-3.8-flash-low", true},
|
|
{"models/ prefix stripped", catalog, "models/gemini-3.8-flash", budget("1000"), "gemini-3.8-flash-low", true},
|
|
{"no thinkingConfig -> -high", catalog, "gemini-3.8-flash", []byte(`{"contents":[]}`), "gemini-3.8-flash-high", true},
|
|
// gemini-3.6/3.7/3.8-flash 的四个变体会被 resolveModelMapping 自动补齐,所以降级用一个不在默认表里的型号
|
|
{"only -high exists: low request degrades to high", map[string]string{
|
|
"gemini-3.5-flash-high": "gemini-3.5-flash-high",
|
|
}, "gemini-3.5-flash", budget("1000"), "gemini-3.5-flash-high", true},
|
|
{"injected default variants are usable", catalog, "gemini-3.6-flash", budget("1000"), "gemini-3.6-flash-low", true},
|
|
{"already suffixed: untouched", catalog, "gemini-3.8-flash-low", budget("-1"), "", false},
|
|
{"bare name explicitly mapped: untouched", catalog, "gemini-2.5-flash", budget("-1"), "", false},
|
|
{"no variants in mapping: untouched", catalog, "gemini-9.9-flash", budget("-1"), "", false},
|
|
{"non-gemini model: untouched", catalog, "claude-sonnet-4-6", budget("-1"), "", false},
|
|
{"explicit bare alias wins over variants", map[string]string{
|
|
"gemini-3.8-flash": "gemini-3.8-flash-tiered",
|
|
"gemini-3.8-flash-high": "gemini-3.8-flash-high",
|
|
}, "gemini-3.8-flash", budget("-1"), "", false},
|
|
{"variant value is itself a mapping target", map[string]string{
|
|
"gemini-3.8-flash-high": "gemini-3.8-flash-tiered",
|
|
}, "gemini-3.8-flash", budget("-1"), "gemini-3.8-flash-tiered", true},
|
|
{"user-written bare identity passthrough: respected", map[string]string{
|
|
"gemini-3.8-flash": "gemini-3.8-flash",
|
|
"gemini-3.8-flash-high": "gemini-3.8-flash-high",
|
|
}, "gemini-3.8-flash", budget("1000"), "", false},
|
|
{"runtime-injected bare passthrough (not in credentials): still resolved", map[string]string{
|
|
// resolveModelMapping 会补 gemini-3.7-flash → gemini-3.7-flash 的默认透传,
|
|
// 但 credentials 里没有这条,应当继续推导变体。
|
|
"gemini-3.7-flash-medium": "gemini-3.7-flash-medium",
|
|
}, "gemini-3.7-flash", budget("4000"), "gemini-3.7-flash-medium", true},
|
|
// 空 credentials 映射 → 走 DefaultAntigravityModelMapping,其中同样只有带后缀的 3.8 flash
|
|
{"empty mapping falls back to default catalog", map[string]string{}, "gemini-3.8-flash", budget("-1"), "gemini-3.8-flash-high", true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
account := newAntigravityAccountWithMapping(tt.mapping)
|
|
got, matched := resolveGeminiThinkingVariant(account, tt.model, tt.body)
|
|
require.Equal(t, tt.wantMatch, matched)
|
|
require.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
|
|
t.Run("nil account", func(t *testing.T) {
|
|
got, matched := resolveGeminiThinkingVariant(nil, "gemini-3.8-flash", budget("-1"))
|
|
require.False(t, matched)
|
|
require.Empty(t, got)
|
|
})
|
|
}
|