diff --git a/server/src/routes/aiChat.js b/server/src/routes/aiChat.js index 66f5c01..814f144 100755 --- a/server/src/routes/aiChat.js +++ b/server/src/routes/aiChat.js @@ -72,71 +72,21 @@ router.post('/ask', userAuthMiddleware, async (req, res) => { return ApiResponse.error(res, '问答次数已用完,分享好友可获得更多次数', 403) } - // 获取当前使用的AI模型配置 - const aiModel = await AIModel.getCurrentModel() + // 获取所有启用的AI模型(用于主备切换) + const aiModels = await AIModel.getActiveModels() - let apiUrl, apiKey, modelId, modelConfig - - if (aiModel) { - // 使用数据库配置的模型 - apiUrl = aiModel.apiUrl - apiKey = aiModel.apiKey - modelId = aiModel.modelId - modelConfig = aiModel.config - } else { + if (!aiModels || aiModels.length === 0) { // 使用环境变量配置的模型 - apiUrl = process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions' - apiKey = process.env.OPENAI_API_KEY - modelId = process.env.OPENAI_MODEL || 'gpt-3.5-turbo' - modelConfig = { temperature: 0.7, maxTokens: 800, topP: 1 } + const envModels = [{ + apiUrl: process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions', + apiKey: process.env.OPENAI_API_KEY, + modelId: process.env.OPENAI_MODEL || 'gpt-3.5-turbo', + config: { temperature: 0.7, maxTokens: 800, topP: 1 } + }] + return await tryModels(envModels, question, dimension, res, quota) } - // 检查API配置(opencode.ai免费API无需key) - const needsAuth = apiKey && apiKey !== 'sk-dummy' && apiKey !== '***' - - // 构建系统提示词 - const systemPrompt = getSystemPromptByDimension(dimension) - - // 构建请求头 - const headers = { - 'Content-Type': 'application/json' - } - if (needsAuth) { - headers['Authorization'] = `Bearer ${apiKey}` - } - - // 调用AI API - const response = await fetch(apiUrl, { - method: 'POST', - headers, - body: JSON.stringify({ - model: modelId, - messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: question } - ], - temperature: modelConfig.temperature || 0.7, - max_tokens: modelConfig.maxTokens || 800, - top_p: modelConfig.topP || 1 - }) - }) - - if (!response.ok) { - const errorData = await response.json() - logger.error('OpenAI API错误:', errorData) - return ApiResponse.error(res, 'AI服务响应异常', 500) - } - - const data = await response.json() - const answer = data.choices[0].message.content - - // 扣除一次使用次数 - await quota.useQuota() - - return ApiResponse.success(res, { - answer, - remainingQuota: quota.getRemainingQuota() - }) + return await tryModels(aiModels, question, dimension, res, quota) } catch (error) { logger.error('AI问答失败:', error) @@ -144,6 +94,81 @@ router.post('/ask', userAuthMiddleware, async (req, res) => { } }) +/** + * 遍历模型列表尝试问答(主备切换) + */ +async function tryModels(models, question, dimension, res, quota) { + const systemPrompt = getSystemPromptByDimension(dimension) + let lastError = null + + for (const model of models) { + try { + const apiUrl = model.apiUrl + const apiKey = model.apiKey + const modelId = model.modelId + const modelConfig = model.config || {} + + // 检查是否需要 Authorization(免费/无需key的API跳过) + const needsAuth = apiKey && apiKey !== 'sk-dummy' && apiKey !== '***' + + // 构建请求头 + const headers = { 'Content-Type': 'application/json' } + if (needsAuth) { + headers['Authorization'] = `Bearer ${apiKey}` + } + + logger.info(`[AI Chat] 尝试模型: ${model.name} (${modelId})`) + + // 调用AI API + const response = await fetch(apiUrl, { + method: 'POST', + headers, + body: JSON.stringify({ + model: modelId, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: question } + ], + temperature: modelConfig.temperature || 0.7, + max_tokens: modelConfig.maxTokens || 800, + top_p: modelConfig.topP || 1 + }) + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + logger.warn(`[AI Chat] 模型 ${model.name} 失败:`, response.status, errorData) + lastError = { status: response.status, data: errorData } + continue // 尝试下一个模型 + } + + const data = await response.json() + const message = data.choices[0].message + // 兼容标准 content 和 sensenova 的 reasoning 字段 + const answer = message.content || message.reasoning || '' + + // 扣除一次使用次数 + await quota.useQuota() + + logger.info(`[AI Chat] 模型 ${model.name} 回答成功`) + + return ApiResponse.success(res, { + answer, + remainingQuota: quota.getRemainingQuota() + }) + + } catch (err) { + logger.warn(`[AI Chat] 模型 ${model.name} 异常:`, err.message) + lastError = { message: err.message } + continue // 尝试下一个模型 + } + } + + // 所有模型都失败了 + logger.error('所有AI模型均失败:', lastError) + return ApiResponse.error(res, 'AI服务暂时不可用,请稍后再试', 500) +} + /** * 分享获得问答次数 * POST /api/ai-chat/share-gain