Skip to content

Commit 006b824

Browse files
authored
Merge pull request #128 from opentiny/cgm/chai-genui-optimize
feat: add new models and support multiple keys
2 parents 5d784ad + 960a35d commit 006b824

6 files changed

Lines changed: 143 additions & 4 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"modelscope": {
3+
"name": "deepseek",
4+
"baseUrl": "https://api-inference.modelscope.cn/v1",
5+
"apiKeyEnvName": "MODEL_SCOPE_API_KEY",
6+
"models": [
7+
{
8+
"name": "Qwen3.5-397B-A17B",
9+
"id": "Qwen/Qwen3.5-397B-A17B",
10+
"supportJsonFormat": false
11+
},
12+
13+
{
14+
"name": "DeepSeek-V3.2",
15+
"id": "deepseek-ai/DeepSeek-V3.2",
16+
"supportJsonFormat": false
17+
},
18+
{
19+
"name": "Qwen3-Coder-480B-A35B-Instruct",
20+
"id": "Qwen/Qwen3-Coder-480B-A35B-Instruct",
21+
"supportJsonFormat": false
22+
},
23+
{
24+
"name": "MiniMax-M2.5",
25+
"id": "MiniMax/MiniMax-M2.5",
26+
"supportJsonFormat": false
27+
},
28+
{
29+
"name": "GLM-5",
30+
"id": "ZhipuAI/GLM-5",
31+
"supportJsonFormat": false
32+
}
33+
]
34+
},
35+
"longcat": {
36+
"name": "deepseek",
37+
"baseUrl": "https://api.longcat.chat/openai/v1",
38+
"apiKeyEnvName": "LONGCAT_API_KEY",
39+
"models": [
40+
{
41+
"name": "longcat/LongCat-Flash-Lite",
42+
"id": "LongCat-Flash-Lite",
43+
"supportJsonFormat": false,
44+
"specificPrompt": "表单的提交按钮需要生成onClick事件,并在函数中使用 this.callAction 去调用 continueChat"
45+
},
46+
{
47+
"name": "longcat/LongCat-Flash-Chat",
48+
"id": "LongCat-Flash-Chat",
49+
"supportJsonFormat": false
50+
},
51+
{
52+
"name": "longcat/LongCat-Flash-Thinking-2601",
53+
"id": "LongCat-Flash-Thinking-2601",
54+
"supportJsonFormat": false
55+
}
56+
]
57+
}
58+
}

sites/playground/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"build": "vite build",
88
"dev:alpha": "tsx watch --include ./alpha-models.json --tsconfig ./tsconfig.dev.json --clear-screen=false index.ts --mode=alpha",
99
"build:alpha": "vite build --mode alpha",
10+
"build:github": "vite build --mode github",
1011
"build:agent-alpha": "vite build --mode agent-alpha",
1112
"serve": "node --import=extensionless/register dist/index.js"
1213
},
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* 每个环境变量名单独维护轮换游标(同一 env 下配置的多个 key 共享一个池)。
3+
*/
4+
const rotationIndexByEnv = new Map<string, number>();
5+
6+
/**
7+
* 从环境变量原始字符串中取出本次请求应使用的 API Key。
8+
* 支持英文逗号分隔的多个 key,按请求顺序轮询,便于聚合多账号配额。
9+
*/
10+
export function pickNextApiKeyFromEnv(apiKeyEnvName: string | undefined, envValue: string | undefined): string | undefined {
11+
if (envValue === undefined) {
12+
return undefined;
13+
}
14+
15+
if (!apiKeyEnvName) {
16+
return envValue;
17+
}
18+
const trimmed = envValue.trim();
19+
if (!trimmed) {
20+
return undefined;
21+
}
22+
23+
const keys = trimmed
24+
.split(',')
25+
.map((k) => k.trim())
26+
.filter((k) => k.length > 0);
27+
if (keys.length === 0) {
28+
return undefined;
29+
}
30+
if (keys.length === 1) {
31+
return keys[0];
32+
}
33+
34+
const poolKey = apiKeyEnvName;
35+
const prev = rotationIndexByEnv.get(poolKey) ?? 0;
36+
const picked = keys[prev % keys.length];
37+
rotationIndexByEnv.set(poolKey, (prev + 1) % keys.length);
38+
return picked;
39+
}

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,28 @@ type StreamTextOptions = Parameters<typeof streamText>[0];
2323

2424
const isDevelopment = process.env.NODE_ENV === 'development';
2525

26+
const BUSY_ERROR_MESSAGE = '算力繁忙,请切换其他模型或稍后重试';
27+
28+
function extractStatusCode(error: any): number | undefined {
29+
if (!error) {
30+
return undefined;
31+
}
32+
if (typeof error.statusCode === 'number') {
33+
return error.statusCode;
34+
}
35+
if (typeof error?.lastError?.statusCode === 'number') {
36+
return error.lastError.statusCode;
37+
}
38+
if (Array.isArray(error.errors)) {
39+
for (const item of error.errors) {
40+
if (typeof item?.statusCode === 'number') {
41+
return item.statusCode;
42+
}
43+
}
44+
}
45+
return undefined;
46+
}
47+
2648
const initClients = async (
2749
serverName: string,
2850
serverConfig: McpServer,
@@ -261,10 +283,25 @@ export function createChatGenui() {
261283

262284
console.error('Error in chat-genui onError:', error);
263285
const actualError = error?.error?.cause ?? error?.error ?? error;
264-
const statusCode = actualError?.statusCode ?? 500;
265-
const responseBody = actualError?.responseBody || null;
286+
const rawStatusCode = extractStatusCode(actualError);
287+
const statusCode =
288+
typeof rawStatusCode === 'number' &&
289+
Number.isInteger(rawStatusCode) &&
290+
rawStatusCode >= 100 &&
291+
rawStatusCode <= 599
292+
? rawStatusCode
293+
: 500;
294+
const responseBody = actualError?.responseBody ?? null;
295+
const detailsPart = responseBody
296+
? `; error details: ${typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)}`
297+
: '';
298+
const built = (actualError?.message ?? '') + detailsPart;
266299
const message =
267-
actualError?.message + (responseBody ? `; error details: ${responseBody}` : '') || 'Unknown Error Type';
300+
statusCode === 429
301+
? BUSY_ERROR_MESSAGE
302+
: built.trim() !== ''
303+
? built
304+
: 'Unknown Error Type';
268305
const type = actualError?.name || actualError?.type || 'Unknown Error Type';
269306
const param = actualError?.param || null;
270307
const code = statusCode;

sites/playground/server/src/provider-models-mapper.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { createProvider } from './ai-sdk-providers.js';
5+
import { pickNextApiKeyFromEnv } from './api-key-pool.js';
56

67
/**
78
* 模型信息接口
@@ -135,11 +136,13 @@ export class ProviderModelMapper {
135136
* 根据模型名称获取 ai-sdk 模型实例
136137
* @param modelInfo 模型信息
137138
* @returns ai-sdk 模型实例
139+
*
140+
* API Key 对应的环境变量值支持英文逗号分隔多个 key,每次请求按顺序轮换使用。
138141
*/
139142
getAiSDKModel(modelInfo: ModelInfo) {
140143
const { provider, model } = modelInfo;
141144
const { name: providerName, baseUrl, apiKeyEnvName, baseUrlEnvName } = provider;
142-
const apiKey = process.env[apiKeyEnvName];
145+
const apiKey = pickNextApiKeyFromEnv(apiKeyEnvName, apiKeyEnvName ? process.env[apiKeyEnvName] : undefined);
143146
const envUrl = process.env[baseUrlEnvName];
144147
const { id: modelId } = model;
145148
const providerInstance = createProvider(providerName, {

sites/playground/server/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const modelsFileMap = {
1212
alpha: 'alpha-models.json',
1313
'agent-alpha': 'opentiny-models.json',
1414
'production': 'opentiny-models.json',
15+
'github': 'github-models.json',
1516
'default': 'maas-models.json',
1617
};
1718

0 commit comments

Comments
 (0)