fix: 标准答案图先发+提示词优化+JSON强化+重试3次

This commit is contained in:
2026-07-16 10:41:44 +08:00
parent ba7aff17d7
commit e7a9cd23ad
+66 -48
View File
@@ -354,7 +354,7 @@ async function callAiApi(imageBase64: string, rubric: string, config: { apiKey:
deductions: string[] deductions: string[]
comment: string comment: string
}> { }> {
const systemRole = `你是高校阅卷专家,严格按照评分标准逐项评分。只输出JSON,不包含任何其他文字` const systemRole = `你是高校阅卷专家,严格按照评分标准逐项评分。你必须只输出JSON严禁包含\`\`\`markdown代码块、\`\`\`json标记、注释、说明文字或任何其他非JSON内容。输出必须以{开头,以}结尾,且必须是合法的JSON`
const hasRefImage = !!extras?.referenceAnswerImage const hasRefImage = !!extras?.referenceAnswerImage
@@ -368,10 +368,10 @@ ${extras?.referenceAnswer || '无'}
【满分】 【满分】
${extras?.maxScore ?? 100} ${extras?.maxScore ?? 100}
图片1学生答案 图片1标准答案
图片2标准答案 图片2学生答案
按照标准答案(图片2)给学生答案(图片1)评分。 根据评分标准和标准答案(图片1)对学生答案(图片2)评分。
--- ---
@@ -382,13 +382,14 @@ ${extras?.maxScore ?? 100}
4. 部分正确时按评分标准酌情给分 4. 部分正确时按评分标准酌情给分
【输出要求】 【输出要求】
只输出以下JSON,不要包含markdown代码块、\`\`\`标记、注释或任何其他文字 你必须严格输出以下格式的纯JSON,严禁包含\`\`\`\`\`\`json、markdown代码块、注释或任何说明文字。整个回复必须是合法的JSON对象,以{开始,以}结束
{ {
"score": <总分>, "score": <总分>,
"confidence": <置信度0~1>, "confidence": <置信度0~1>,
"deductions": ["扣分项说明1", "扣分项说明2"], "deductions": ["扣分项说明1", "扣分项说明2"],
"comment": "总评语及逐项得分说明" "comment": "总评语及逐项得分说明"
}` }
再次强调:只输出JSON,禁止任何其他内容。`
: `${extras?.questionTitle ? `【题目】\n${extras.questionTitle}\n` : ''}【评分标准】 : `${extras?.questionTitle ? `【题目】\n${extras.questionTitle}\n` : ''}【评分标准】
${rubric || '无'} ${rubric || '无'}
@@ -398,8 +399,8 @@ ${extras?.referenceAnswer || '无'}
【满分】 【满分】
${extras?.maxScore ?? 100} ${extras?.maxScore ?? 100}
学生答案 图片:学生答案
看图片中的内容 根据评分标准对学生答案评分
--- ---
@@ -410,65 +411,82 @@ ${extras?.maxScore ?? 100}
4. 部分正确时按评分标准酌情给分 4. 部分正确时按评分标准酌情给分
【输出要求】 【输出要求】
只输出以下JSON,不要包含markdown代码块、\`\`\`标记、注释或任何其他文字 你必须严格输出以下格式的纯JSON,严禁包含\`\`\`\`\`\`json、markdown代码块、注释或任何说明文字。整个回复必须是合法的JSON对象,以{开始,以}结束
{ {
"score": <总分>, "score": <总分>,
"confidence": <置信度0~1>, "confidence": <置信度0~1>,
"deductions": ["扣分项说明1", "扣分项说明2"], "deductions": ["扣分项说明1", "扣分项说明2"],
"comment": "总评语及逐项得分说明" "comment": "总评语及逐项得分说明"
}` }
再次强调:只输出JSON,禁止任何其他内容。`
const axios = require('axios') const axios = require('axios')
const userContent: any[] = hasRefImage const userContent: any[] = hasRefImage
? [ ? [
{ type: 'text', text: prompt }, { type: 'text', text: prompt },
{ type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } }, { type: 'image_url', image_url: { url: extras!.referenceAnswerImage } },
{ type: 'image_url', image_url: { url: extras!.referenceAnswerImage } } { type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } }
] ]
: [ : [
{ type: 'text', text: prompt }, { type: 'text', text: prompt },
{ type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } } { type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } }
] ]
const response = await axios.post(`${config.baseUrl}/chat/completions`, { let lastError: Error | null = null
model: config.model, for (let attempt = 1; attempt <= 3; attempt++) {
messages: [ try {
{ const response = await axios.post(`${config.baseUrl}/chat/completions`, {
role: 'system', model: config.model,
content: systemRole messages: [
}, {
{ role: 'system',
role: 'user', content: systemRole
content: userContent },
} {
], role: 'user',
max_tokens: 4096, content: userContent
temperature: 0.3 }
}, { ],
headers: { max_tokens: 4096,
'Content-Type': 'application/json', temperature: 0.3
'Authorization': `Bearer ${config.apiKey}` }, {
}, headers: {
timeout: ((getStore().get('timeout') as number) ?? 120) * 1000 'Content-Type': 'application/json',
}) 'Authorization': `Bearer ${config.apiKey}`
},
timeout: ((getStore().get('timeout') as number) ?? 120) * 1000
})
const content = response.data?.choices?.[0]?.message?.content const content = response.data?.choices?.[0]?.message?.content
if (!content) { if (!content) {
log.error('AI API 返回为空或格式异常:', JSON.stringify(response.data).slice(0, 1000)) lastError = new Error(`API 返回为空 (尝试 ${attempt}/3)`)
throw new Error(`API 返回为空,请检查模型名是否正确 (当前: ${config.model})`) log.warn(`AI API empty response, attempt ${attempt}/3`)
} if (attempt < 3) continue
const parsed = tryParseJson(content) throw lastError
if (!parsed) { }
log.error('AI 返回 JSON 解析失败:', content.slice(0, 500)) const parsed = tryParseJson(content)
throw new Error(`AI 返回格式异常,无法解析为 JSON。原始返回:\n${content}`) if (!parsed) {
} lastError = new Error(`AI 返回 JSON 解析失败 (尝试 ${attempt}/3)`)
return { log.warn(`AI JSON parse failed, attempt ${attempt}/3:`, content.slice(0, 200))
score: parsed.score, if (attempt < 3) continue
confidence: parsed.confidence ?? 0.5, throw lastError
deductions: parsed.deductions ?? [], }
comment: parsed.comment ?? parsed.commit ?? '' return {
score: parsed.score,
confidence: parsed.confidence ?? 0.5,
deductions: parsed.deductions ?? [],
comment: parsed.comment ?? parsed.commit ?? ''
}
} catch (err: any) {
lastError = err
if (attempt < 3) {
log.info(`AI API attempt ${attempt}/3 failed, retrying...`)
await new Promise(r => setTimeout(r, 2000))
}
}
} }
throw lastError || new Error('AI API 调用失败')
} }
function tryParseJson(text: string): any { function tryParseJson(text: string): any {