feat: revamp DeepSeek v4 model handling

- replace legacy DeepSeek ids with the new deepseek-v4 model family\n- move thinking control to request parameters and preserve assistant reasoning content\n- switch history split to IGNORE transcript injection and map upload auth failures to 401\n- update admin defaults, API docs, samples, and tests for the new model scheme
This commit is contained in:
CJACK
2026-04-26 00:02:14 +08:00
parent ed9efc5858
commit 131ca7d398
66 changed files with 949 additions and 697 deletions

View File

@@ -10,9 +10,9 @@ func TestStandardRequestCompletionPayloadSetsModelTypeFromResolvedModel(t *testi
search bool
modelType string
}{
{name: "default", model: "deepseek-chat", thinking: false, search: false, modelType: "default"},
{name: "expert", model: "deepseek-expert-reasoner", thinking: true, search: false, modelType: "expert"},
{name: "vision", model: "deepseek-vision-chat-search", thinking: false, search: true, modelType: "vision"},
{name: "default", model: "deepseek-v4-flash", thinking: false, search: false, modelType: "default"},
{name: "expert", model: "deepseek-v4-pro", thinking: true, search: false, modelType: "expert"},
{name: "vision", model: "deepseek-v4-vision-search", thinking: false, search: true, modelType: "vision"},
}
for _, tc := range tests {

53
internal/util/thinking.go Normal file
View File

@@ -0,0 +1,53 @@
package util
import "strings"
func ResolveThinkingEnabled(req map[string]any, defaultEnabled bool) bool {
if enabled, ok := parseThinkingSetting(req["thinking"]); ok {
return enabled
}
if extraBody, ok := req["extra_body"].(map[string]any); ok {
if enabled, ok := parseThinkingSetting(extraBody["thinking"]); ok {
return enabled
}
}
if enabled, ok := parseReasoningEffort(req["reasoning_effort"]); ok {
return enabled
}
return defaultEnabled
}
func parseThinkingSetting(raw any) (bool, bool) {
switch v := raw.(type) {
case string:
switch strings.ToLower(strings.TrimSpace(v)) {
case "enabled":
return true, true
case "disabled":
return false, true
default:
return false, false
}
case map[string]any:
if typ, ok := v["type"]; ok {
return parseThinkingSetting(typ)
}
}
return false, false
}
func parseReasoningEffort(raw any) (bool, bool) {
switch strings.ToLower(strings.TrimSpace(toString(raw))) {
case "low", "medium", "high", "xhigh":
return true, true
default:
return false, false
}
}
func toString(raw any) string {
if s, ok := raw.(string); ok {
return s
}
return ""
}

View File

@@ -0,0 +1,44 @@
package util
import "testing"
func TestResolveThinkingEnabledPriority(t *testing.T) {
req := map[string]any{
"thinking": map[string]any{"type": "disabled"},
"extra_body": map[string]any{
"thinking": map[string]any{"type": "enabled"},
},
"reasoning_effort": "high",
}
if got := ResolveThinkingEnabled(req, true); got {
t.Fatalf("expected top-level thinking to win, got enabled=%v", got)
}
}
func TestResolveThinkingEnabledUsesExtraBodyFallback(t *testing.T) {
req := map[string]any{
"extra_body": map[string]any{
"thinking": map[string]any{"type": "disabled"},
},
}
if got := ResolveThinkingEnabled(req, true); got {
t.Fatalf("expected extra_body thinking to disable, got enabled=%v", got)
}
}
func TestResolveThinkingEnabledMapsReasoningEffortToEnabled(t *testing.T) {
for _, effort := range []string{"low", "medium", "high", "xhigh"} {
if got := ResolveThinkingEnabled(map[string]any{"reasoning_effort": effort}, false); !got {
t.Fatalf("expected reasoning_effort=%s to enable thinking", effort)
}
}
}
func TestResolveThinkingEnabledDefaultsWhenUnset(t *testing.T) {
if !ResolveThinkingEnabled(nil, true) {
t.Fatal("expected default thinking=true when unset")
}
if ResolveThinkingEnabled(nil, false) {
t.Fatal("expected default thinking=false when unset")
}
}

View File

@@ -349,14 +349,14 @@ func TestConvertClaudeToDeepSeekNoSystem(t *testing.T) {
}
func TestConvertClaudeToDeepSeekOpusUsesSlowMapping(t *testing.T) {
t.Setenv("DS2API_CONFIG_JSON", `{"keys":[],"accounts":[],"claude_mapping":{"fast":"deepseek-chat","slow":"deepseek-reasoner"}}`)
t.Setenv("DS2API_CONFIG_JSON", `{"keys":[],"accounts":[],"claude_mapping":{"fast":"deepseek-v4-flash","slow":"deepseek-v4-pro"}}`)
store := config.LoadStore()
req := map[string]any{
"model": "claude-opus-4-6",
"messages": []any{map[string]any{"role": "user", "content": "Hi"}},
}
out := ConvertClaudeToDeepSeek(req, store)
if out["model"] != "deepseek-reasoner" {
if out["model"] != "deepseek-v4-pro" {
t.Fatalf("expected opus to use slow mapping, got %q", out["model"])
}
}