Skip to content

Commit ef2089b

Browse files
authored
Merge pull request #155 from opentiny/playground-model-optimize
feat(playground): add extraBody and model split to thinking/no-thinking mode
2 parents 7590819 + d370c2f commit ef2089b

5 files changed

Lines changed: 101 additions & 14 deletions

File tree

sites/playground/server/.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PORT=3008
22
DEEPSEEK_API_KEY=<your-api-key-here>
33
DEEPSEEK_BASE_URL=https://api.modelarts-maas.com/v1/
44
# 动态模型地址:配置后从该 URL 拉取模型列表
5-
DYNAMIC_MODELS_URL=https://chat.opentiny.design/api/v1/llm/models
5+
DYNAMIC_MODELS_URL=
6+
# 动态模型基础URL
7+
DYNAMIC_BASE_URL=
8+
# 动态模型API密钥
9+
DYNAMIC_API_KEY=
610
# 本地模型文件路径:配置后会合并到动态模型列表后面
711
providerModelsPath=maas-models.json

sites/playground/server/maas-models.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,50 @@
1010
"supportJsonFormat": false,
1111
"specificPrompt": "schemaJson必须使用```schemaJson```包裹。"
1212
},
13+
{
14+
"name": "DeepSeek-V4-Flash",
15+
"id": "deepseek-v4-flash",
16+
"supportJsonFormat": false,
17+
"specificPrompt": "",
18+
"extraBody": {
19+
"thinking": {
20+
"type": "disabled"
21+
}
22+
}
23+
},
24+
{
25+
"name": "DeepSeek-V4-Flash-thinking",
26+
"id": "deepseek-v4-flash",
27+
"supportJsonFormat": false,
28+
"specificPrompt": "",
29+
"extraBody": {
30+
"thinking": {
31+
"type": "enabled"
32+
}
33+
}
34+
},
35+
{
36+
"name": "DeepSeek-V4-Pro",
37+
"id": "deepseek-v4-pro",
38+
"supportJsonFormat": false,
39+
"specificPrompt": "",
40+
"extraBody": {
41+
"thinking": {
42+
"type": "disabled"
43+
}
44+
}
45+
},
46+
{
47+
"name": "DeepSeek-V4-Pro-thinking",
48+
"id": "deepseek-v4-pro",
49+
"supportJsonFormat": false,
50+
"specificPrompt": "",
51+
"extraBody": {
52+
"thinking": {
53+
"type": "enabled"
54+
}
55+
}
56+
},
1357
{
1458
"name": "DeepSeek-V3.1",
1559
"id": "deepseek-v3.1-terminus",

sites/playground/server/src/chat-genui.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,19 @@ export async function generateLlmConfig(llmConfigParams: LLMConfigParams | undef
178178
const modelInfo = providerModelMapper.getModelInfo(model || '');
179179
const aiSDKModel = modelInfo ? providerModelMapper.getAiSDKModel(modelInfo) : undefined;
180180

181+
const rawExtraBody = modelInfo?.model?.extraBody;
182+
const extraBody =
183+
rawExtraBody && typeof rawExtraBody === 'object'
184+
? rawExtraBody
185+
: undefined;
186+
181187
return {
182188
...llmConfigParams,
183189
...modelInfo,
184190
model: aiSDKModel,
185191
supportJsonFormat: modelInfo?.model.supportJsonFormat || false,
186192
specificPrompt: modelInfo?.model.specificPrompt || '',
193+
extraBody
187194
};
188195
}
189196

@@ -258,7 +265,7 @@ export function createChatGenui() {
258265
};
259266

260267
const llmConfig = await generateLlmConfig(llmConfigParams);
261-
const { model, temperature, specificPrompt } = llmConfig;
268+
const { model, temperature, specificPrompt, provider, extraBody } = llmConfig;
262269
const { tools: mcpTools, clientsMap } = await generateAiSdkTools(
263270
mcpServers.filter((s) => s.enabled),
264271
abort.signal,
@@ -283,6 +290,12 @@ export function createChatGenui() {
283290
const renderConfigForFramework = framework === 'Angular' ? ngRendererConfig : rendererConfig;
284291
const maxSteps = 30;
285292
let hasError = false; // 标记是否已经处理了错误
293+
294+
const providerOptions =
295+
provider?.name && extraBody && Object.keys(extraBody).length > 0
296+
? { [provider.name]: extraBody } as StreamTextOptions['providerOptions']
297+
: undefined;
298+
286299
const options: StreamTextOptions = {
287300
model,
288301
temperature,
@@ -299,6 +312,7 @@ export function createChatGenui() {
299312
tools,
300313
toolChoice: 'auto',
301314
stopWhen: stepCountIs(maxSteps),
315+
...(providerOptions ? { providerOptions } : {}),
302316
onError: (error: any) => {
303317
if (hasError) {
304318
return;

sites/playground/server/src/opentiny-models.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ interface OpenTinyModel {
55
display_name: string;
66
capabilities?: {
77
vision?: boolean;
8+
reasoning?: {
9+
enable_thinking?: boolean;
10+
[key: string]: any;
11+
};
812
fileUpload?: {
913
supportDirectImage?: boolean;
1014
[key: string]: any;
@@ -13,6 +17,17 @@ interface OpenTinyModel {
1317
};
1418
}
1519

20+
const EXTRA_BODY_THINKING_DISABLED = { thinking: { type: 'disabled' as const } };
21+
const EXTRA_BODY_THINKING_ENABLED = { thinking: { type: 'enabled' as const } };
22+
23+
function shouldSplitThinkingModel(capabilities?: OpenTinyModel['capabilities']): boolean {
24+
const reasoning = capabilities?.reasoning;
25+
if (reasoning == null || typeof reasoning !== 'object' || Array.isArray(reasoning)) {
26+
return false;
27+
}
28+
return reasoning.enable_thinking === true;
29+
}
30+
1631
interface OpenTinyModelsResponse {
1732
models: OpenTinyModel[];
1833
[key: string]: any;
@@ -25,28 +40,33 @@ interface OpenTinyModelsResponse {
2540
export function convertOpenTinyToProviderModelsData(resp: OpenTinyModelsResponse): Record<string, any> {
2641
const models = Array.isArray(resp?.models) ? resp.models : [];
2742

28-
const convertedModels = models.map((m) => {
43+
const convertedModels = models.flatMap((model) => {
44+
const { display_name, id, type, capabilities, provider } = model;
2945
const supportImage =
30-
m.type === 'vision' || m.capabilities?.vision === true || m.capabilities?.fileUpload?.supportDirectImage === true;
31-
32-
const model: any = {
33-
name: m.display_name || m.id,
34-
id: m.id,
35-
};
46+
type === 'vision' || capabilities?.vision === true || capabilities?.fileUpload?.supportDirectImage === true;
3647

48+
const modelName = display_name && provider ? `${provider}_${display_name}` : id;
49+
const base: any = { id };
3750
if (supportImage) {
38-
model.features = { supportImage: true };
51+
base.features = { supportImage: true };
52+
}
53+
54+
if (!shouldSplitThinkingModel(capabilities)) {
55+
return [{ name: modelName, ...base }];
3956
}
4057

41-
return model;
58+
return [
59+
{ name: modelName, ...base, extraBody: EXTRA_BODY_THINKING_DISABLED },
60+
{ name: `${modelName}-thinking`, ...base, extraBody: EXTRA_BODY_THINKING_ENABLED },
61+
];
4262
});
4363

4464
// 为了与现有 opentiny-models.json 保持一致,统一放在 deepseek 提供商下
4565
return {
4666
dynamic: {
4767
name: 'deepseek',
48-
apiKeyEnvName: 'API_KEY',
49-
baseUrlEnvName: 'BASE_URL',
68+
apiKeyEnvName: 'DYNAMIC_API_KEY',
69+
baseUrlEnvName: 'DYNAMIC_BASE_URL',
5070
models: convertedModels,
5171
},
5272
};

sites/playground/server/src/types/llm-config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import type { LanguageModel } from 'ai';
12
import type { McpServersConfig } from './mcp-server.js';
23
import type { PlaygroundSkillConfig } from '../skills/index.js';
4+
import type { ProviderConfig } from '../ai-sdk-providers.js';
35

46
export type LLMConfigParams = {
57
model?: string;
@@ -9,7 +11,10 @@ export type LLMConfigParams = {
911
skills?: PlaygroundSkillConfig[];
1012
};
1113

12-
export type LLMConfig = LLMConfigParams & {
14+
export type LLMConfig = Omit<LLMConfigParams, 'model'> & {
15+
model?: LanguageModel;
16+
provider?: ProviderConfig;
1317
supportJsonFormat?: boolean;
1418
specificPrompt?: string;
19+
extraBody?: Record<string, unknown>;
1520
};

0 commit comments

Comments
 (0)