feat: 沙盒模型合规优化 — 移除境外模型引用,新增 GET /sandbox/models 接口

- MODEL_CATALOG 仅保留国内已备案模型(DeepSeek/SenseNova)
- chat()/chatStream() modelMap 仅路由到国内 provider
- provider 命名明确化:"OpenAI 兼容接口" → "硅基流动 / DeepSeek"
- 新增 getSandboxModels() 动态返回可用模型列表
- sandbox 控制器拆分 auth 级别,GET /sandbox/models 无需认证
- 新增 getAvailableModels() 供前端调用

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
TradeMate Dev
2026-06-23 21:23:21 +08:00
parent 0f215d2aad
commit cd0ef01c7c
3 changed files with 69 additions and 29 deletions
+30 -26
View File
@@ -24,15 +24,17 @@ interface AIProvider {
chatStream?(messages: ChatMessage[], options?: ChatOptions): AsyncIterable<string>;
}
// 仅收录沙箱实际可用的国内已备案大模型
const MODEL_CATALOG: Record<string, ModelInfo> = {
'general': { id: 'general', provider: 'OpenAI 兼容', capabilities: ['chat', 'code'], contextWindow: 8192 },
'openai': { id: 'openai', provider: 'OpenAI 兼容', capabilities: ['chat', 'code'], contextWindow: 8192 },
'gpt-3.5': { id: 'gpt-3.5', provider: 'OpenAI', capabilities: ['chat', 'code'], contextWindow: 16384 },
'gpt-4': { id: 'gpt-4', provider: 'OpenAI', capabilities: ['chat', 'code', 'vision'], contextWindow: 32768 },
'deepseek-v4-flash': { id: 'deepseek-v4-flash', provider: '商汤科技', capabilities: ['chat', 'code'], contextWindow: 32768 },
'deepseek-v4-flash': { id: 'deepseek-v4-flash', provider: '硅基流动/DeepSeek', capabilities: ['chat', 'code'], contextWindow: 32768 },
'sensenova-6.7-flash-lite': { id: 'sensenova-6.7-flash-lite', provider: '商汤科技', capabilities: ['chat', 'code'], contextWindow: 32768 },
};
}
/** 沙箱可选模型列表(前端展示用),modelId 必须与 MODEL_CATALOG 中的 id 一致 */
export const SANDBOX_MODELS = [
{ id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash', provider: '硅基流动', desc: '高速推理,代码生成能力强,价格极低', available: false },
{ id: 'sensenova-6.7-flash-lite', label: 'SenseNova 6.7 Flash Lite', provider: '商汤科技', desc: '轻量快速,日常对话与文本处理', available: false },
];
@Injectable()
export class AIGatewayService {
@@ -45,19 +47,21 @@ export class AIGatewayService {
}
private registerProviders() {
// 国内已备案大模型 — 通过硅基流动 / DeepSeek 直连等国内渠道
if (process.env.OPENAI_API_KEY) {
let apiUrl = process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions';
if (!apiUrl.endsWith('/chat/completions')) {
apiUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions';
}
const defaultModel = process.env.OPENAI_MODEL || 'gpt-3.5-turbo';
this.providers.set('openai', new OpenAICompatibleProvider(
const defaultModel = process.env.OPENAI_MODEL || 'deepseek/deepseek-v4-flash';
this.providers.set('deepseek-v4-flash', new OpenAICompatibleProvider(
process.env.OPENAI_API_KEY!,
apiUrl,
defaultModel,
'OpenAI 兼容接口',
'硅基流动 / DeepSeek',
));
this.logger.log(`OpenAI 兼容接口已注册: ${defaultModel}`);
this.logger.log(`DeepSeek(通过国内渠道)已注册: ${defaultModel}`);
this.markAvailable('deepseek-v4-flash');
}
if (process.env.SENSENOVA_API_KEY) {
@@ -73,25 +77,27 @@ export class AIGatewayService {
modelName,
'商汤科技',
));
this.markAvailable(modelName);
}
this.logger.log(`商汤科技已注册: ${models.join(', ')}`);
}
}
/** 标记 SANDBOX_MODELS 中的对应模型为可用 */
private markAvailable(modelId: string) {
for (const m of SANDBOX_MODELS) {
if (m.id === modelId) m.available = true;
}
}
async chat(model: string, messages: ChatMessage[], options?: ChatOptions): Promise<string> {
// 仅路由到国内已备案模型的 provider
const modelMap: Record<string, string> = {
'general': 'openai',
'openai': 'openai',
'gpt-3.5': 'openai',
'gpt-4': 'openai',
'longcat': 'openai',
'meituan/longcat-flash-lite': 'openai',
'deepseek-v4-flash': 'deepseek-v4-flash',
'sensenova-6.7-flash-lite': 'sensenova-6.7-flash-lite',
};
const providerKey = modelMap[model.toLowerCase()] || (model.includes('/') ? 'openai' : model);
const providerKey = modelMap[model.toLowerCase()] || model;
const provider = this.providers.get(providerKey);
if (!this.usageStats[providerKey]) this.usageStats[providerKey] = { total: 0, success: 0, failed: 0 };
@@ -117,18 +123,11 @@ export class AIGatewayService {
async *chatStream(model: string, messages: ChatMessage[], options?: ChatOptions): AsyncIterable<string> {
const modelMap: Record<string, string> = {
'general': 'openai',
'openai': 'openai',
'gpt-3.5': 'openai',
'gpt-4': 'openai',
'longcat': 'openai',
'meituan/longcat-flash-lite': 'openai',
'deepseek-v4-flash': 'deepseek-v4-flash',
'sensenova-6.7-flash-lite': 'sensenova-6.7-flash-lite',
};
const providerKey = modelMap[model.toLowerCase()] || (model.includes('/') ? 'openai' : model);
const providerKey = modelMap[model.toLowerCase()] || model;
const provider = this.providers.get(providerKey);
if (!this.usageStats[providerKey]) this.usageStats[providerKey] = { total: 0, success: 0, failed: 0 };
@@ -201,6 +200,11 @@ export class AIGatewayService {
getRegisteredProviders(): string[] {
return Array.from(this.providers.keys());
}
/** 返回沙箱前端可选的模型列表(仅返回有 provider 注册的模型) */
getSandboxModels() {
return SANDBOX_MODELS.filter(m => m.available);
}
}
class OpenAICompatibleProvider implements AIProvider {