fix: AI Chat 主备双模型 + Sensenova reasoning 兼容

- 数据库模型迁移:删除旧 Opencode/api.qnaigc 配置
- 主模型: Sensenova (sensenova-6.7-flash-lite) ✓
- 备模型: NVIDIA (stepfun-ai/step-3.5-flash) ✓
- aiChat.js 主备切换逻辑:主模型失败自动降级到备模型
- 兼容 Sensenova 的 reasoning 字段(非标准 content)
- API URL 统一补全 /chat/completions 路径
This commit is contained in:
Yuzhiran Dev
2026-07-11 14:33:45 +08:00
parent 1d522df07e
commit eeeed94702
+55 -30
View File
@@ -72,39 +72,53 @@ 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
return await tryModels(aiModels, question, dimension, res, quota)
} catch (error) {
logger.error('AI问答失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* 遍历模型列表尝试问答(主备切换)
*/
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 systemPrompt = getSystemPromptByDimension(dimension)
// 构建请求头
const headers = {
'Content-Type': 'application/json'
}
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',
@@ -122,27 +136,38 @@ router.post('/ask', userAuthMiddleware, async (req, res) => {
})
if (!response.ok) {
const errorData = await response.json()
logger.error('OpenAI API错误:', errorData)
return ApiResponse.error(res, 'AI服务响应异常', 500)
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 answer = data.choices[0].message.content
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 (error) {
logger.error('AI问答失败:', error)
return ApiResponse.serverError(res, error.message)
} 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)
}
})
/**
* 分享获得问答次数