mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-06 17:35:30 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa29084038 | ||
|
|
21c1527c79 | ||
|
|
7ec0d99702 | ||
|
|
7e639667f8 | ||
|
|
066c48c107 | ||
|
|
d69b0658ea | ||
|
|
4315b424bf |
@@ -75,20 +75,6 @@ func TestResolveExpandedHistoricalAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveModelHeuristicReasoner(t *testing.T) {
|
|
||||||
got, ok := ResolveModel(nil, "o3-super")
|
|
||||||
if !ok || got != "deepseek-v4-pro" {
|
|
||||||
t.Fatalf("expected heuristic reasoner, got ok=%v model=%q", ok, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveModelHeuristicReasonerNoThinking(t *testing.T) {
|
|
||||||
got, ok := ResolveModel(nil, "o3-super-nothinking")
|
|
||||||
if !ok || got != "deepseek-v4-pro-nothinking" {
|
|
||||||
t.Fatalf("expected heuristic reasoner nothinking, got ok=%v model=%q", ok, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveModelUnknown(t *testing.T) {
|
func TestResolveModelUnknown(t *testing.T) {
|
||||||
_, ok := ResolveModel(nil, "totally-custom-model")
|
_, ok := ResolveModel(nil, "totally-custom-model")
|
||||||
if ok {
|
if ok {
|
||||||
@@ -96,6 +82,13 @@ func TestResolveModelUnknown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveModelUnknownKnownFamilyName(t *testing.T) {
|
||||||
|
_, ok := ResolveModel(nil, "gpt-5.5-pro-search")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("expected unknown known-family model to fail resolve without alias")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveModelRejectsLegacyDeepSeekIDs(t *testing.T) {
|
func TestResolveModelRejectsLegacyDeepSeekIDs(t *testing.T) {
|
||||||
legacyModels := []string{
|
legacyModels := []string{
|
||||||
"deepseek-chat",
|
"deepseek-chat",
|
||||||
@@ -151,13 +144,6 @@ func TestResolveModelCustomAliasToVision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveModelHeuristicVisionIgnoresSearchSuffix(t *testing.T) {
|
|
||||||
got, ok := ResolveModel(nil, "gemini-vision-search")
|
|
||||||
if !ok || got != "deepseek-v4-vision" {
|
|
||||||
t.Fatalf("expected heuristic vision alias to resolve without search variant, got ok=%v model=%q", ok, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClaudeModelsResponsePaginationFields(t *testing.T) {
|
func TestClaudeModelsResponsePaginationFields(t *testing.T) {
|
||||||
resp := ClaudeModelsResponse()
|
resp := ClaudeModelsResponse()
|
||||||
if _, ok := resp["first_id"]; !ok {
|
if _, ok := resp["first_id"]; !ok {
|
||||||
|
|||||||
@@ -214,27 +214,11 @@ func ResolveModel(store ModelAliasReader, requested string) (string, bool) {
|
|||||||
return mapped, true
|
return mapped, true
|
||||||
}
|
}
|
||||||
baseModel, noThinking := splitNoThinkingModel(model)
|
baseModel, noThinking := splitNoThinkingModel(model)
|
||||||
resolvedModel, ok := resolveCanonicalModel(aliases, baseModel)
|
if mapped, ok := aliases[baseModel]; ok && IsSupportedDeepSeekModel(mapped) {
|
||||||
if !ok {
|
return withNoThinkingVariant(mapped, noThinking), true
|
||||||
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return withNoThinkingVariant(resolvedModel, noThinking), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRetiredHistoricalModel(model string) bool {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(model, "claude-1."):
|
|
||||||
return true
|
|
||||||
case strings.HasPrefix(model, "claude-2."):
|
|
||||||
return true
|
|
||||||
case strings.HasPrefix(model, "claude-instant-"):
|
|
||||||
return true
|
|
||||||
case strings.HasPrefix(model, "gpt-3.5"):
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lower(s string) string {
|
func lower(s string) string {
|
||||||
b := []byte(s)
|
b := []byte(s)
|
||||||
@@ -315,58 +299,3 @@ func loadModelAliases(store ModelAliasReader) map[string]string {
|
|||||||
}
|
}
|
||||||
return aliases
|
return aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveCanonicalModel(aliases map[string]string, model string) (string, bool) {
|
|
||||||
model = lower(strings.TrimSpace(model))
|
|
||||||
if model == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if isRetiredHistoricalModel(model) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if IsSupportedDeepSeekModel(model) {
|
|
||||||
return model, true
|
|
||||||
}
|
|
||||||
if mapped, ok := aliases[model]; ok && IsSupportedDeepSeekModel(mapped) {
|
|
||||||
return mapped, true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(model, "deepseek-") {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
knownFamily := false
|
|
||||||
for _, prefix := range []string{
|
|
||||||
"gpt-", "o1", "o3", "claude-", "gemini-", "llama-", "qwen-", "mistral-", "command-",
|
|
||||||
} {
|
|
||||||
if strings.HasPrefix(model, prefix) {
|
|
||||||
knownFamily = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !knownFamily {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
useVision := strings.Contains(model, "vision")
|
|
||||||
useReasoner := strings.Contains(model, "reason") ||
|
|
||||||
strings.Contains(model, "reasoner") ||
|
|
||||||
strings.HasPrefix(model, "o1") ||
|
|
||||||
strings.HasPrefix(model, "o3") ||
|
|
||||||
strings.Contains(model, "opus") ||
|
|
||||||
strings.Contains(model, "slow") ||
|
|
||||||
strings.Contains(model, "r1")
|
|
||||||
useSearch := strings.Contains(model, "search")
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case useVision:
|
|
||||||
return "deepseek-v4-vision", true
|
|
||||||
case useReasoner && useSearch:
|
|
||||||
return "deepseek-v4-pro-search", true
|
|
||||||
case useReasoner:
|
|
||||||
return "deepseek-v4-pro", true
|
|
||||||
case useSearch:
|
|
||||||
return "deepseek-v4-flash-search", true
|
|
||||||
default:
|
|
||||||
return "deepseek-v4-flash", true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -127,13 +127,7 @@ func (s *chatStreamRuntime) sendKeepAlive() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = s.w.Write([]byte(": keep-alive\n\n"))
|
_, _ = s.w.Write([]byte(": keep-alive\n\n"))
|
||||||
s.sendChunk(openaifmt.BuildChatStreamChunk(
|
_ = s.rc.Flush()
|
||||||
s.completionID,
|
|
||||||
s.created,
|
|
||||||
s.model,
|
|
||||||
[]map[string]any{},
|
|
||||||
nil,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *chatStreamRuntime) sendChunk(v any) {
|
func (s *chatStreamRuntime) sendChunk(v any) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"ds2api/internal/promptcompat"
|
"ds2api/internal/promptcompat"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChatStreamKeepAliveEmitsEmptyChoiceDataFrame(t *testing.T) {
|
func TestChatStreamKeepAliveUsesCommentOnly(t *testing.T) {
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
runtime := newChatStreamRuntime(
|
runtime := newChatStreamRuntime(
|
||||||
rec,
|
rec,
|
||||||
@@ -40,18 +40,8 @@ func TestChatStreamKeepAliveEmitsEmptyChoiceDataFrame(t *testing.T) {
|
|||||||
if done {
|
if done {
|
||||||
t.Fatalf("keep-alive must not emit [DONE], body=%q", body)
|
t.Fatalf("keep-alive must not emit [DONE], body=%q", body)
|
||||||
}
|
}
|
||||||
if len(frames) != 1 {
|
if len(frames) != 0 {
|
||||||
t.Fatalf("expected one data frame, got %d body=%q", len(frames), body)
|
t.Fatalf("keep-alive must not emit JSON data frames, got %#v body=%q", frames, body)
|
||||||
}
|
|
||||||
if got := asString(frames[0]["id"]); got != "chatcmpl-test" {
|
|
||||||
t.Fatalf("expected completion id to be preserved, got %q", got)
|
|
||||||
}
|
|
||||||
if got := asString(frames[0]["object"]); got != "chat.completion.chunk" {
|
|
||||||
t.Fatalf("expected chat chunk object, got %q", got)
|
|
||||||
}
|
|
||||||
choices, _ := frames[0]["choices"].([]any)
|
|
||||||
if len(choices) != 0 {
|
|
||||||
t.Fatalf("expected empty choices heartbeat, got %#v", choices)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user