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:
+55
-30
@@ -72,39 +72,53 @@ router.post('/ask', userAuthMiddleware, async (req, res) => {
|
|||||||
return ApiResponse.error(res, '问答次数已用完,分享好友可获得更多次数', 403)
|
return ApiResponse.error(res, '问答次数已用完,分享好友可获得更多次数', 403)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前使用的AI模型配置
|
// 获取所有启用的AI模型(用于主备切换)
|
||||||
const aiModel = await AIModel.getCurrentModel()
|
const aiModels = await AIModel.getActiveModels()
|
||||||
|
|
||||||
let apiUrl, apiKey, modelId, modelConfig
|
if (!aiModels || aiModels.length === 0) {
|
||||||
|
|
||||||
if (aiModel) {
|
|
||||||
// 使用数据库配置的模型
|
|
||||||
apiUrl = aiModel.apiUrl
|
|
||||||
apiKey = aiModel.apiKey
|
|
||||||
modelId = aiModel.modelId
|
|
||||||
modelConfig = aiModel.config
|
|
||||||
} else {
|
|
||||||
// 使用环境变量配置的模型
|
// 使用环境变量配置的模型
|
||||||
apiUrl = process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions'
|
const envModels = [{
|
||||||
apiKey = process.env.OPENAI_API_KEY
|
apiUrl: process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions',
|
||||||
modelId = process.env.OPENAI_MODEL || 'gpt-3.5-turbo'
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
modelConfig = { temperature: 0.7, maxTokens: 800, topP: 1 }
|
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 needsAuth = apiKey && apiKey !== 'sk-dummy' && apiKey !== '***'
|
||||||
|
|
||||||
// 构建系统提示词
|
|
||||||
const systemPrompt = getSystemPromptByDimension(dimension)
|
|
||||||
|
|
||||||
// 构建请求头
|
// 构建请求头
|
||||||
const headers = {
|
const headers = { 'Content-Type': 'application/json' }
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
if (needsAuth) {
|
if (needsAuth) {
|
||||||
headers['Authorization'] = `Bearer ${apiKey}`
|
headers['Authorization'] = `Bearer ${apiKey}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`[AI Chat] 尝试模型: ${model.name} (${modelId})`)
|
||||||
|
|
||||||
// 调用AI API
|
// 调用AI API
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -122,27 +136,38 @@ router.post('/ask', userAuthMiddleware, async (req, res) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json()
|
const errorData = await response.json().catch(() => ({}))
|
||||||
logger.error('OpenAI API错误:', errorData)
|
logger.warn(`[AI Chat] 模型 ${model.name} 失败:`, response.status, errorData)
|
||||||
return ApiResponse.error(res, 'AI服务响应异常', 500)
|
lastError = { status: response.status, data: errorData }
|
||||||
|
continue // 尝试下一个模型
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
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()
|
await quota.useQuota()
|
||||||
|
|
||||||
|
logger.info(`[AI Chat] 模型 ${model.name} 回答成功`)
|
||||||
|
|
||||||
return ApiResponse.success(res, {
|
return ApiResponse.success(res, {
|
||||||
answer,
|
answer,
|
||||||
remainingQuota: quota.getRemainingQuota()
|
remainingQuota: quota.getRemainingQuota()
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
logger.error('AI问答失败:', error)
|
logger.warn(`[AI Chat] 模型 ${model.name} 异常:`, err.message)
|
||||||
return ApiResponse.serverError(res, error.message)
|
lastError = { message: err.message }
|
||||||
|
continue // 尝试下一个模型
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 所有模型都失败了
|
||||||
|
logger.error('所有AI模型均失败:', lastError)
|
||||||
|
return ApiResponse.error(res, 'AI服务暂时不可用,请稍后再试', 500)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分享获得问答次数
|
* 分享获得问答次数
|
||||||
|
|||||||
Reference in New Issue
Block a user