mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-02 07:25:26 +08:00
Add default, expert, and vision DeepSeek model families
This commit is contained in:
@@ -22,6 +22,24 @@ func TestGetModelRouteDirectAndAlias(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("direct_expert", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/models/deepseek-expert-chat", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("direct_vision", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/models/deepseek-vision-chat", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("alias", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/models/gpt-4.1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -15,8 +15,17 @@ import (
|
||||
"ds2api/internal/config"
|
||||
"ds2api/internal/deepseek"
|
||||
"ds2api/internal/sse"
|
||||
"ds2api/internal/util"
|
||||
)
|
||||
|
||||
type modelAliasSnapshotReader struct {
|
||||
aliases map[string]string
|
||||
}
|
||||
|
||||
func (m modelAliasSnapshotReader) ModelAliases() map[string]string {
|
||||
return m.aliases
|
||||
}
|
||||
|
||||
func (h *Handler) testSingleAccount(w http.ResponseWriter, r *http.Request) {
|
||||
var req map[string]any
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
@@ -150,16 +159,27 @@ func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, me
|
||||
return result
|
||||
}
|
||||
thinking, search, ok := config.GetModelConfig(model)
|
||||
resolvedModel, resolved := config.ResolveModel(modelAliasSnapshotReader{
|
||||
aliases: h.Store.Snapshot().ModelAliases,
|
||||
}, model)
|
||||
if resolved {
|
||||
model = resolvedModel
|
||||
thinking, search, ok = config.GetModelConfig(model)
|
||||
}
|
||||
if !ok {
|
||||
thinking, search = false, false
|
||||
}
|
||||
_ = search
|
||||
pow, err := h.DS.GetPow(proxyCtx, authCtx, 1)
|
||||
if err != nil {
|
||||
result["message"] = "获取 PoW 失败: " + err.Error()
|
||||
return result
|
||||
}
|
||||
payload := map[string]any{"chat_session_id": sessionID, "prompt": deepseek.MessagesPrepare([]map[string]any{{"role": "user", "content": message}}), "ref_file_ids": []any{}, "thinking_enabled": thinking, "search_enabled": search}
|
||||
payload := util.StandardRequest{
|
||||
ResolvedModel: model,
|
||||
FinalPrompt: deepseek.MessagesPrepare([]map[string]any{{"role": "user", "content": message}}),
|
||||
Thinking: thinking,
|
||||
Search: search,
|
||||
}.CompletionPayload(sessionID)
|
||||
resp, err := h.DS.CallCompletion(proxyCtx, authCtx, payload, pow, 1)
|
||||
if err != nil {
|
||||
result["message"] = "请求失败: " + err.Error()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -133,3 +134,78 @@ func TestDeleteAllSessions_RetryWithReloginOnDeleteFailure(t *testing.T) {
|
||||
t.Fatalf("expected refreshed token persisted, got %q", updated.Token)
|
||||
}
|
||||
}
|
||||
|
||||
type completionPayloadDSMock struct {
|
||||
payload map[string]any
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) Login(_ context.Context, _ config.Account) (string, error) {
|
||||
return "new-token", nil
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
|
||||
return "session-id", nil
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
|
||||
return "pow-ok", nil
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) CallCompletion(_ context.Context, _ *auth.RequestAuth, payload map[string]any, _ string, _ int) (*http.Response, error) {
|
||||
m.payload = payload
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader("data: {\"v\":\"ok\"}\n\ndata: [DONE]\n\n")),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) DeleteAllSessionsForToken(_ context.Context, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *completionPayloadDSMock) GetSessionCountForToken(_ context.Context, _ string) (*deepseek.SessionStats, error) {
|
||||
return &deepseek.SessionStats{Success: true}, nil
|
||||
}
|
||||
|
||||
func TestTestAccount_MessageModeUsesExpertModelTypeForExpertModel(t *testing.T) {
|
||||
t.Setenv("DS2API_CONFIG_JSON", `{"accounts":[{"email":"batch@example.com","password":"pwd","token":"seed-token"}]}`)
|
||||
store := config.LoadStore()
|
||||
ds := &completionPayloadDSMock{}
|
||||
h := &Handler{Store: store, DS: ds}
|
||||
acc, ok := store.FindAccount("batch@example.com")
|
||||
if !ok {
|
||||
t.Fatal("expected test account")
|
||||
}
|
||||
|
||||
result := h.testAccount(context.Background(), acc, "deepseek-expert-chat", "hello")
|
||||
|
||||
if ok, _ := result["success"].(bool); !ok {
|
||||
t.Fatalf("expected success=true, got %#v", result)
|
||||
}
|
||||
if got := ds.payload["model_type"]; got != "expert" {
|
||||
t.Fatalf("expected model_type expert, got %#v", got)
|
||||
}
|
||||
if got := ds.payload["chat_session_id"]; got != "session-id" {
|
||||
t.Fatalf("unexpected chat_session_id: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestAccount_MessageModeUsesVisionModelTypeForVisionModel(t *testing.T) {
|
||||
t.Setenv("DS2API_CONFIG_JSON", `{"accounts":[{"email":"batch@example.com","password":"pwd","token":"seed-token"}]}`)
|
||||
store := config.LoadStore()
|
||||
ds := &completionPayloadDSMock{}
|
||||
h := &Handler{Store: store, DS: ds}
|
||||
acc, ok := store.FindAccount("batch@example.com")
|
||||
if !ok {
|
||||
t.Fatal("expected test account")
|
||||
}
|
||||
|
||||
result := h.testAccount(context.Background(), acc, "deepseek-vision-chat", "hello")
|
||||
|
||||
if ok, _ := result["success"].(bool); !ok {
|
||||
t.Fatalf("expected success=true, got %#v", result)
|
||||
}
|
||||
if got := ds.payload["model_type"]; got != "vision" {
|
||||
t.Fatalf("expected model_type vision, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,51 @@ func TestGetModelConfigDeepSeekReasonerSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelConfigDeepSeekExpertChat(t *testing.T) {
|
||||
thinking, search, ok := GetModelConfig("deepseek-expert-chat")
|
||||
if !ok {
|
||||
t.Fatal("expected ok for deepseek-expert-chat")
|
||||
}
|
||||
if thinking || search {
|
||||
t.Fatalf("expected no thinking/search for deepseek-expert-chat, got thinking=%v search=%v", thinking, search)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelConfigDeepSeekExpertReasonerSearch(t *testing.T) {
|
||||
thinking, search, ok := GetModelConfig("deepseek-expert-reasoner-search")
|
||||
if !ok {
|
||||
t.Fatal("expected ok for deepseek-expert-reasoner-search")
|
||||
}
|
||||
if !thinking || !search {
|
||||
t.Fatalf("expected both true, got thinking=%v search=%v", thinking, search)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelConfigDeepSeekVisionReasonerSearch(t *testing.T) {
|
||||
thinking, search, ok := GetModelConfig("deepseek-vision-reasoner-search")
|
||||
if !ok {
|
||||
t.Fatal("expected ok for deepseek-vision-reasoner-search")
|
||||
}
|
||||
if !thinking || !search {
|
||||
t.Fatalf("expected both true, got thinking=%v search=%v", thinking, search)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelTypeDefaultExpertAndVision(t *testing.T) {
|
||||
defaultType, ok := GetModelType("deepseek-chat")
|
||||
if !ok || defaultType != "default" {
|
||||
t.Fatalf("expected default model_type, got ok=%v model_type=%q", ok, defaultType)
|
||||
}
|
||||
expertType, ok := GetModelType("deepseek-expert-chat")
|
||||
if !ok || expertType != "expert" {
|
||||
t.Fatalf("expected expert model_type, got ok=%v model_type=%q", ok, expertType)
|
||||
}
|
||||
visionType, ok := GetModelType("deepseek-vision-chat")
|
||||
if !ok || visionType != "vision" {
|
||||
t.Fatalf("expected vision model_type, got ok=%v model_type=%q", ok, visionType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelConfigCaseInsensitive(t *testing.T) {
|
||||
thinking, search, ok := GetModelConfig("DeepSeek-Chat")
|
||||
if !ok {
|
||||
@@ -551,6 +596,30 @@ func TestOpenAIModelsResponse(t *testing.T) {
|
||||
if len(data) == 0 {
|
||||
t.Fatal("expected non-empty models list")
|
||||
}
|
||||
expected := map[string]bool{
|
||||
"deepseek-chat": false,
|
||||
"deepseek-reasoner": false,
|
||||
"deepseek-chat-search": false,
|
||||
"deepseek-reasoner-search": false,
|
||||
"deepseek-expert-chat": false,
|
||||
"deepseek-expert-reasoner": false,
|
||||
"deepseek-expert-chat-search": false,
|
||||
"deepseek-expert-reasoner-search": false,
|
||||
"deepseek-vision-chat": false,
|
||||
"deepseek-vision-reasoner": false,
|
||||
"deepseek-vision-chat-search": false,
|
||||
"deepseek-vision-reasoner-search": false,
|
||||
}
|
||||
for _, model := range data {
|
||||
if _, ok := expected[model.ID]; ok {
|
||||
expected[model.ID] = true
|
||||
}
|
||||
}
|
||||
for id, seen := range expected {
|
||||
if !seen {
|
||||
t.Fatalf("expected OpenAI model list to include %s", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeModelsResponse(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,10 @@ package config
|
||||
|
||||
import "testing"
|
||||
|
||||
type mockModelAliasReader map[string]string
|
||||
|
||||
func (m mockModelAliasReader) ModelAliases() map[string]string { return m }
|
||||
|
||||
func TestResolveModelDirectDeepSeek(t *testing.T) {
|
||||
got, ok := ResolveModel(nil, "deepseek-chat")
|
||||
if !ok || got != "deepseek-chat" {
|
||||
@@ -30,6 +34,31 @@ func TestResolveModelUnknown(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveModelDirectDeepSeekExpert(t *testing.T) {
|
||||
got, ok := ResolveModel(nil, "deepseek-expert-chat")
|
||||
if !ok || got != "deepseek-expert-chat" {
|
||||
t.Fatalf("expected deepseek-expert-chat, got ok=%v model=%q", ok, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveModelCustomAliasToExpert(t *testing.T) {
|
||||
got, ok := ResolveModel(mockModelAliasReader{
|
||||
"my-expert-model": "deepseek-expert-reasoner-search",
|
||||
}, "my-expert-model")
|
||||
if !ok || got != "deepseek-expert-reasoner-search" {
|
||||
t.Fatalf("expected alias -> deepseek-expert-reasoner-search, got ok=%v model=%q", ok, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveModelCustomAliasToVision(t *testing.T) {
|
||||
got, ok := ResolveModel(mockModelAliasReader{
|
||||
"my-vision-model": "deepseek-vision-chat-search",
|
||||
}, "my-vision-model")
|
||||
if !ok || got != "deepseek-vision-chat-search" {
|
||||
t.Fatalf("expected alias -> deepseek-vision-chat-search, got ok=%v model=%q", ok, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeModelsResponsePaginationFields(t *testing.T) {
|
||||
resp := ClaudeModelsResponse()
|
||||
if _, ok := resp["first_id"]; !ok {
|
||||
|
||||
@@ -19,6 +19,14 @@ var DeepSeekModels = []ModelInfo{
|
||||
{ID: "deepseek-reasoner", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-chat-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-reasoner-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-expert-chat", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-expert-reasoner", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-expert-chat-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-expert-reasoner-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-vision-chat", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-vision-reasoner", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-vision-chat-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
{ID: "deepseek-vision-reasoner-search", Object: "model", Created: 1677610602, OwnedBy: "deepseek", Permission: []any{}},
|
||||
}
|
||||
|
||||
var ClaudeModels = []ModelInfo{
|
||||
@@ -72,11 +80,40 @@ func GetModelConfig(model string) (thinking bool, search bool, ok bool) {
|
||||
return false, true, true
|
||||
case "deepseek-reasoner-search":
|
||||
return true, true, true
|
||||
case "deepseek-expert-chat":
|
||||
return false, false, true
|
||||
case "deepseek-expert-reasoner":
|
||||
return true, false, true
|
||||
case "deepseek-expert-chat-search":
|
||||
return false, true, true
|
||||
case "deepseek-expert-reasoner-search":
|
||||
return true, true, true
|
||||
case "deepseek-vision-chat":
|
||||
return false, false, true
|
||||
case "deepseek-vision-reasoner":
|
||||
return true, false, true
|
||||
case "deepseek-vision-chat-search":
|
||||
return false, true, true
|
||||
case "deepseek-vision-reasoner-search":
|
||||
return true, true, true
|
||||
default:
|
||||
return false, false, false
|
||||
}
|
||||
}
|
||||
|
||||
func GetModelType(model string) (modelType string, ok bool) {
|
||||
switch lower(model) {
|
||||
case "deepseek-chat", "deepseek-reasoner", "deepseek-chat-search", "deepseek-reasoner-search":
|
||||
return "default", true
|
||||
case "deepseek-expert-chat", "deepseek-expert-reasoner", "deepseek-expert-chat-search", "deepseek-expert-reasoner-search":
|
||||
return "expert", true
|
||||
case "deepseek-vision-chat", "deepseek-vision-reasoner", "deepseek-vision-chat-search", "deepseek-vision-reasoner-search":
|
||||
return "vision", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func IsSupportedDeepSeekModel(model string) bool {
|
||||
_, _, ok := GetModelConfig(model)
|
||||
return ok
|
||||
|
||||
@@ -66,9 +66,7 @@ func (c *Client) CreateSession(ctx context.Context, a *auth.RequestAuth, maxAtte
|
||||
}
|
||||
code, bizCode, msg, bizMsg := extractResponseStatus(resp)
|
||||
if status == http.StatusOK && code == 0 && bizCode == 0 {
|
||||
data, _ := resp["data"].(map[string]any)
|
||||
bizData, _ := data["biz_data"].(map[string]any)
|
||||
sessionID, _ := bizData["id"].(string)
|
||||
sessionID := extractCreateSessionID(resp)
|
||||
if sessionID != "" {
|
||||
return sessionID, nil
|
||||
}
|
||||
@@ -204,6 +202,22 @@ func isAuthIndicativeBizFailure(msg string, bizMsg string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DeepSeek has returned create-session ids in both biz_data.id and
|
||||
// biz_data.chat_session.id across observed response variants; accept either.
|
||||
func extractCreateSessionID(resp map[string]any) string {
|
||||
data, _ := resp["data"].(map[string]any)
|
||||
bizData, _ := data["biz_data"].(map[string]any)
|
||||
if sessionID, _ := bizData["id"].(string); strings.TrimSpace(sessionID) != "" {
|
||||
return strings.TrimSpace(sessionID)
|
||||
}
|
||||
if chatSession, ok := bizData["chat_session"].(map[string]any); ok {
|
||||
if sessionID, _ := chatSession["id"].(string); strings.TrimSpace(sessionID) != "" {
|
||||
return strings.TrimSpace(sessionID)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractResponseStatus(resp map[string]any) (code int, bizCode int, msg string, bizMsg string) {
|
||||
code = intFrom(resp["code"])
|
||||
msg, _ = resp["msg"].(string)
|
||||
|
||||
34
internal/deepseek/client_auth_test.go
Normal file
34
internal/deepseek/client_auth_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package deepseek
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExtractCreateSessionIDSupportsLegacyShape(t *testing.T) {
|
||||
resp := map[string]any{
|
||||
"data": map[string]any{
|
||||
"biz_data": map[string]any{
|
||||
"id": "legacy-session-id",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if got := extractCreateSessionID(resp); got != "legacy-session-id" {
|
||||
t.Fatalf("expected legacy session id, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractCreateSessionIDSupportsNestedChatSessionShape(t *testing.T) {
|
||||
resp := map[string]any{
|
||||
"data": map[string]any{
|
||||
"biz_data": map[string]any{
|
||||
"chat_session": map[string]any{
|
||||
"id": "nested-session-id",
|
||||
"model_type": "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if got := extractCreateSessionID(resp); got != "nested-session-id" {
|
||||
t.Fatalf("expected nested session id, got %q", got)
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,11 @@ const (
|
||||
|
||||
var defaultBaseHeaders = map[string]string{
|
||||
"Host": "chat.deepseek.com",
|
||||
"User-Agent": "DeepSeek/1.6.11 Android/35",
|
||||
"User-Agent": "DeepSeek/1.8.0 Android/35",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"x-client-platform": "android",
|
||||
"x-client-version": "1.6.11",
|
||||
"x-client-version": "1.8.0",
|
||||
"x-client-locale": "zh_CN",
|
||||
"accept-charset": "UTF-8",
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"base_headers": {
|
||||
"Host": "chat.deepseek.com",
|
||||
"User-Agent": "DeepSeek/1.6.11 Android/35",
|
||||
"User-Agent": "DeepSeek/1.8.0 Android/35",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"x-client-platform": "android",
|
||||
"x-client-version": "1.6.11",
|
||||
"x-client-version": "1.8.0",
|
||||
"x-client-locale": "zh_CN",
|
||||
"accept-charset": "UTF-8"
|
||||
},
|
||||
|
||||
@@ -5,11 +5,11 @@ const path = require('path');
|
||||
|
||||
const DEFAULT_BASE_HEADERS = Object.freeze({
|
||||
Host: 'chat.deepseek.com',
|
||||
'User-Agent': 'DeepSeek/1.6.11 Android/35',
|
||||
'User-Agent': 'DeepSeek/1.8.0 Android/35',
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'x-client-platform': 'android',
|
||||
'x-client-version': '1.6.11',
|
||||
'x-client-version': '1.8.0',
|
||||
'x-client-locale': 'zh_CN',
|
||||
'accept-charset': 'UTF-8',
|
||||
});
|
||||
|
||||
@@ -53,6 +53,10 @@ func (r *Runner) caseModelsOpenAI(ctx context.Context, cc *caseContext) error {
|
||||
ids := extractModelIDs(resp.Body)
|
||||
cc.assert("has_deepseek_chat", contains(ids, "deepseek-chat"), strings.Join(ids, ","))
|
||||
cc.assert("has_deepseek_reasoner", contains(ids, "deepseek-reasoner"), strings.Join(ids, ","))
|
||||
cc.assert("has_deepseek_expert_chat", contains(ids, "deepseek-expert-chat"), strings.Join(ids, ","))
|
||||
cc.assert("has_deepseek_expert_reasoner", contains(ids, "deepseek-expert-reasoner"), strings.Join(ids, ","))
|
||||
cc.assert("has_deepseek_vision_chat", contains(ids, "deepseek-vision-chat"), strings.Join(ids, ","))
|
||||
cc.assert("has_deepseek_vision_reasoner", contains(ids, "deepseek-vision-reasoner"), strings.Join(ids, ","))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package util
|
||||
|
||||
import "ds2api/internal/config"
|
||||
|
||||
type StandardRequest struct {
|
||||
Surface string
|
||||
RequestedModel string
|
||||
@@ -51,8 +53,17 @@ func (p ToolChoicePolicy) Allows(name string) bool {
|
||||
}
|
||||
|
||||
func (r StandardRequest) CompletionPayload(sessionID string) map[string]any {
|
||||
modelID := r.ResolvedModel
|
||||
if modelID == "" {
|
||||
modelID = r.RequestedModel
|
||||
}
|
||||
modelType := "default"
|
||||
if resolvedType, ok := config.GetModelType(modelID); ok {
|
||||
modelType = resolvedType
|
||||
}
|
||||
payload := map[string]any{
|
||||
"chat_session_id": sessionID,
|
||||
"model_type": modelType,
|
||||
"parent_message_id": nil,
|
||||
"prompt": r.FinalPrompt,
|
||||
"ref_file_ids": []any{},
|
||||
|
||||
49
internal/util/standard_request_test.go
Normal file
49
internal/util/standard_request_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStandardRequestCompletionPayloadSetsModelTypeFromResolvedModel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
thinking bool
|
||||
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"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := StandardRequest{
|
||||
ResolvedModel: tc.model,
|
||||
FinalPrompt: "hello",
|
||||
Thinking: tc.thinking,
|
||||
Search: tc.search,
|
||||
PassThrough: map[string]any{
|
||||
"temperature": 0.3,
|
||||
},
|
||||
}
|
||||
|
||||
payload := req.CompletionPayload("session-123")
|
||||
|
||||
if got := payload["model_type"]; got != tc.modelType {
|
||||
t.Fatalf("expected model_type %s, got %#v", tc.modelType, got)
|
||||
}
|
||||
if got := payload["chat_session_id"]; got != "session-123" {
|
||||
t.Fatalf("unexpected chat_session_id: %#v", got)
|
||||
}
|
||||
if got := payload["thinking_enabled"]; got != tc.thinking {
|
||||
t.Fatalf("unexpected thinking_enabled: %#v", got)
|
||||
}
|
||||
if got := payload["search_enabled"]; got != tc.search {
|
||||
t.Fatalf("unexpected search_enabled: %#v", got)
|
||||
}
|
||||
if got := payload["temperature"]; got != 0.3 {
|
||||
t.Fatalf("expected passthrough temperature, got %#v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,14 @@ export default function ApiTesterContainer({ config, onMessage, authFetch }) {
|
||||
{ id: 'deepseek-reasoner', name: 'deepseek-reasoner', icon: 'Cpu', desc: t('apiTester.models.reasoner'), color: 'text-amber-600' },
|
||||
{ id: 'deepseek-chat-search', name: 'deepseek-chat-search', icon: 'SearchIcon', desc: t('apiTester.models.chatSearch'), color: 'text-cyan-500' },
|
||||
{ id: 'deepseek-reasoner-search', name: 'deepseek-reasoner-search', icon: 'SearchIcon', desc: t('apiTester.models.reasonerSearch'), color: 'text-cyan-600' },
|
||||
{ id: 'deepseek-expert-chat', name: 'deepseek-expert-chat', icon: 'MessageSquare', desc: t('apiTester.models.expertChat'), color: 'text-emerald-500' },
|
||||
{ id: 'deepseek-expert-reasoner', name: 'deepseek-expert-reasoner', icon: 'Cpu', desc: t('apiTester.models.expertReasoner'), color: 'text-emerald-600' },
|
||||
{ id: 'deepseek-expert-chat-search', name: 'deepseek-expert-chat-search', icon: 'SearchIcon', desc: t('apiTester.models.expertChatSearch'), color: 'text-teal-500' },
|
||||
{ id: 'deepseek-expert-reasoner-search', name: 'deepseek-expert-reasoner-search', icon: 'SearchIcon', desc: t('apiTester.models.expertReasonerSearch'), color: 'text-teal-600' },
|
||||
{ id: 'deepseek-vision-chat', name: 'deepseek-vision-chat', icon: 'MessageSquare', desc: t('apiTester.models.visionChat'), color: 'text-violet-500' },
|
||||
{ id: 'deepseek-vision-reasoner', name: 'deepseek-vision-reasoner', icon: 'Cpu', desc: t('apiTester.models.visionReasoner'), color: 'text-violet-600' },
|
||||
{ id: 'deepseek-vision-chat-search', name: 'deepseek-vision-chat-search', icon: 'SearchIcon', desc: t('apiTester.models.visionChatSearch'), color: 'text-fuchsia-500' },
|
||||
{ id: 'deepseek-vision-reasoner-search', name: 'deepseek-vision-reasoner-search', icon: 'SearchIcon', desc: t('apiTester.models.visionReasonerSearch'), color: 'text-fuchsia-600' },
|
||||
]
|
||||
|
||||
const { runTest, stopGeneration } = useChatStreamClient({
|
||||
|
||||
@@ -199,7 +199,15 @@
|
||||
"chat": "Non-reasoning model",
|
||||
"reasoner": "Reasoning model",
|
||||
"chatSearch": "Non-reasoning model (with search)",
|
||||
"reasonerSearch": "Reasoning model (with search)"
|
||||
"reasonerSearch": "Reasoning model (with search)",
|
||||
"expertChat": "Non-reasoning expert mode",
|
||||
"expertReasoner": "Reasoning expert mode",
|
||||
"expertChatSearch": "Non-reasoning expert mode (with search)",
|
||||
"expertReasonerSearch": "Reasoning expert mode (with search)",
|
||||
"visionChat": "Non-reasoning vision mode",
|
||||
"visionReasoner": "Reasoning vision mode",
|
||||
"visionChatSearch": "Non-reasoning vision mode (with search)",
|
||||
"visionReasonerSearch": "Reasoning vision mode (with search)"
|
||||
},
|
||||
"missingApiKey": "Please provide an API key.",
|
||||
"requestFailed": "Request failed.",
|
||||
|
||||
@@ -199,7 +199,15 @@
|
||||
"chat": "非思考模型",
|
||||
"reasoner": "思考模型",
|
||||
"chatSearch": "非思考模型 (带搜索)",
|
||||
"reasonerSearch": "思考模型 (带搜索)"
|
||||
"reasonerSearch": "思考模型 (带搜索)",
|
||||
"expertChat": "非思考专家模式",
|
||||
"expertReasoner": "思考专家模式",
|
||||
"expertChatSearch": "非思考专家模式 (带搜索)",
|
||||
"expertReasonerSearch": "思考专家模式 (带搜索)",
|
||||
"visionChat": "非思考视觉模式",
|
||||
"visionReasoner": "思考视觉模式",
|
||||
"visionChatSearch": "非思考视觉模式 (带搜索)",
|
||||
"visionReasonerSearch": "思考视觉模式 (带搜索)"
|
||||
},
|
||||
"missingApiKey": "请提供 API 密钥",
|
||||
"requestFailed": "请求失败",
|
||||
|
||||
Reference in New Issue
Block a user