muthuk1's picture
Convert OpenMAIC from Next.js to React (Vite)
f56a29b verified
/**
* Quiz Grading API
*
* POST: Receives a text question + user answer, calls LLM for scoring and feedback.
* Used for short-answer (text) questions that cannot be graded locally.
*/
import { NextRequest } from 'next/server';
import { callLLM } from '@/lib/ai/llm';
import { createLogger } from '@/lib/logger';
import { apiError, apiSuccess } from '@/lib/server/api-response';
import { resolveModelFromRequest } from '@/lib/server/resolve-model';
const log = createLogger('Quiz Grade');
interface GradeRequest {
question: string;
userAnswer: string;
points: number;
commentPrompt?: string;
language?: string;
}
interface GradeResponse {
score: number;
comment: string;
}
export async function POST(req: NextRequest) {
let questionSnippet: string | undefined;
let resolvedPoints: number | undefined;
try {
const body = (await req.json()) as GradeRequest;
const { question, userAnswer, points, commentPrompt, language } = body;
questionSnippet = question?.substring(0, 60);
resolvedPoints = points;
if (!question || !userAnswer) {
return apiError('MISSING_REQUIRED_FIELD', 400, 'question and userAnswer are required');
}
// Validate points is a positive finite number
if (!points || !Number.isFinite(points) || points <= 0) {
return apiError('INVALID_REQUEST', 400, 'points must be a positive number');
}
// Resolve model from request headers/body
const { model: languageModel, thinkingConfig } = await resolveModelFromRequest(req, body);
const isZh = language === 'zh-CN';
const systemPrompt = isZh
? `你是一位专业的教育评估专家。请根据题目和学生答案进行评分并给出简短评语。
必须以如下 JSON 格式回复(不要包含其他内容):
{"score": <0到${points}的整数>, "comment": "<一两句评语>"}`
: `You are a professional educational assessor. Grade the student's answer and provide brief feedback.
You must reply in the following JSON format only (no other content):
{"score": <integer from 0 to ${points}>, "comment": "<one or two sentences of feedback>"}`;
const userPrompt = isZh
? `题目:${question}
满分:${points}
${commentPrompt ? `评分要点:${commentPrompt}\n` : ''}学生答案:${userAnswer}`
: `Question: ${question}
Full marks: ${points} points
${commentPrompt ? `Grading guidance: ${commentPrompt}\n` : ''}Student answer: ${userAnswer}`;
const result = await callLLM(
{
model: languageModel,
system: systemPrompt,
prompt: userPrompt,
},
'quiz-grade',
undefined,
thinkingConfig,
);
// Parse the LLM response as JSON
const text = result.text.trim();
let gradeResult: GradeResponse;
try {
// Try to extract JSON from the response
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (!jsonMatch) throw new Error('No JSON found');
const parsed = JSON.parse(jsonMatch[0]);
gradeResult = {
score: Math.max(0, Math.min(points, Math.round(Number(parsed.score)))),
comment: String(parsed.comment || ''),
};
} catch {
// Fallback: give partial credit with a generic comment
gradeResult = {
score: Math.round(points * 0.5),
comment: isZh
? '已作答,请参考标准答案。'
: 'Answer received. Please refer to the standard answer.',
};
}
return apiSuccess({ ...gradeResult });
} catch (error) {
log.error(
`Quiz grading failed [question="${questionSnippet ?? 'unknown'}...", points=${resolvedPoints ?? 'unknown'}]:`,
error,
);
return apiError('INTERNAL_ERROR', 500, 'Failed to grade answer');
}
}