feat(*): Add AI functionality to grading endpoint

This commit is contained in:
Josh Creek
2025-04-16 23:11:48 +01:00
parent e3a58ccfb2
commit 6eef08c86b
3 changed files with 131 additions and 18 deletions
+89
View File
@@ -0,0 +1,89 @@
import type { OpenAIResponse } from './types';
const OPENAI_API_KEY = process.env['AZURE_OPENAI_API_KEY'];
const OPENAI_ENDPOINT = process.env['AZURE_OPENAI_ENDPOINT'];
const OPENAI_DEPLOYMENT = process.env['AZURE_OPENAI_DEPLOYMENT'] || 'gpt-4';
// Prompt template for grading and feedback
export function buildGradingPrompt(submission: string) {
return `You are an expert secondary school teacher and AI assessment agent. Assess the following student submission using the following steps:
1. Grade the work out of 10, using clear, objective criteria.
2. Identify specific strengths, referencing the success criteria.
3. Identify misconceptions or areas for improvement, using formative assessment language.
4. Design an individualized activity or exercise for the student to address their misconceptions or extend their learning. This activity should be:
- Appropriately scaffolded for the student's current level.
- Specific and actionable (e.g., a short written task, a practical exercise, or a quiz question).
- Aligned with the curriculum and learning objectives.
5. Write a reflection question for the student to encourage metacognition.
6. Suggest to the teacher one way to support this student in the next lesson.
7. Show your reasoning step by step (chain-of-thought).
STUDENT SUBMISSION:
${submission}
RESPONSE FORMAT:
Grade: <number>/10
Strengths: <text>
Areas for Improvement: <text>
Individualized Activity: <text>
Reflection Question: <text>
Teacher Suggestion: <text>
Reasoning: <step-by-step explanation>`;
}
export async function gradeWithOpenAI(submission: string): Promise<OpenAIResponse> {
if (!OPENAI_API_KEY || !OPENAI_ENDPOINT) {
// Fallback for local/demo mode
return {
grade: '7/10',
strengths: 'Clear argument and good evidence.',
areas_for_improvement: 'Needs deeper analysis and more examples.',
individualized_activity: 'Write a paragraph expanding on your main point and provide two additional examples to support your argument.',
reflection_question: 'What was the most challenging part of this assignment for you, and why?',
teacher_suggestion: 'In the next lesson, review how to develop arguments with supporting evidence and provide a model answer for comparison.',
reasoning: 'The submission demonstrates a good structure and evidence, but lacks depth and breadth of analysis. The activity and suggestions are designed to target this gap.'
};
}
const prompt = buildGradingPrompt(submission);
const res = await fetch(`${OPENAI_ENDPOINT}/openai/deployments/${OPENAI_DEPLOYMENT}/chat/completions?api-version=2024-02-15-preview`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': OPENAI_API_KEY
},
body: JSON.stringify({
messages: [
{ role: 'system', content: 'You are a helpful, fair, and transparent grading assistant.' },
{ role: 'user', content: prompt }
],
max_tokens: 500,
temperature: 0.2,
stream: false
})
});
if (!res.ok) throw new Error('OpenAI API error');
const data = await res.json();
const content = data.choices?.[0]?.message?.content || '';
// Parse the response using regex
const gradeMatch = content.match(/Grade:\s*(.*)/i);
const strengthsMatch = content.match(/Strengths:\s*([\s\S]*?)Areas for Improvement:/i);
const improvementMatch = content.match(/Areas for Improvement:\s*([\s\S]*?)Individualized Activity:/i);
const activityMatch = content.match(/Individualized Activity:\s*([\s\S]*?)Reflection Question:/i);
const reflectionMatch = content.match(/Reflection Question:\s*([\s\S]*?)Teacher Suggestion:/i);
const teacherSuggestionMatch = content.match(/Teacher Suggestion:\s*([\s\S]*?)Reasoning:/i);
const reasoningMatch = content.match(/Reasoning:\s*([\s\S]*)/i);
return {
grade: gradeMatch?.[1]?.trim() || '',
strengths: strengthsMatch?.[1]?.trim() || '',
areas_for_improvement: improvementMatch?.[1]?.trim() || '',
individualized_activity: activityMatch?.[1]?.trim() || '',
reflection_question: reflectionMatch?.[1]?.trim() || '',
teacher_suggestion: teacherSuggestionMatch?.[1]?.trim() || '',
reasoning: reasoningMatch?.[1]?.trim() || ''
};
}
+9
View File
@@ -0,0 +1,9 @@
export interface OpenAIResponse {
grade: string;
strengths: string;
areas_for_improvement: string;
individualized_activity: string;
reflection_question: string;
teacher_suggestion: string;
reasoning: string;
}
+33 -18
View File
@@ -1,25 +1,40 @@
import type { RequestHandler } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
import { gradeWithOpenAI } from '$lib/utils/ai';
// TODO - don't just mock the grading logic
export const POST: RequestHandler = async (event: RequestEvent) => {
const data = await event.request.formData();
const file = data.get('file');
const text = data.get('text');
try {
const data = await event.request.formData();
const file = data.get('file');
const text = data.get('text');
let submission = '';
// Simulation of grading and feedback
let feedback = '';
if (file) {
feedback = 'Received file: ' + (file as File).name + '. [AI feedback coming soon]';
} else if (typeof text === 'string' && text.trim().length > 0) {
feedback = 'Received text submission. [AI feedback coming soon]';
} else {
return new Response(JSON.stringify({ error: 'No valid input provided.' }), { status: 400 });
if (file && typeof file === 'object' && 'arrayBuffer' in file && 'name' in file) {
// Only support .txt for now for hackathon speed
const fileObj = file as File;
if (!fileObj.name.endsWith('.txt')) {
return new Response(JSON.stringify({ error: 'Only .txt files are supported at this time.' }), { status: 400 });
}
const buffer = await fileObj.arrayBuffer();
submission = new TextDecoder('utf-8').decode(buffer);
} else if (typeof text === 'string' && text.trim().length > 0) {
submission = text.trim();
} else {
return new Response(JSON.stringify({ error: 'No valid input provided.' }), { status: 400 });
}
// Call the AI grading agent
const aiResult = await gradeWithOpenAI(submission);
return new Response(JSON.stringify({
success: true,
feedback: `Grade: ${aiResult.grade}\nStrengths: ${aiResult.strengths}\nWeaknesses: ${aiResult.weaknesses}\nIndividualized Activity: ${aiResult.individualized_activity}\nReasoning: ${aiResult.reasoning}`,
...aiResult
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (err: any) {
return new Response(JSON.stringify({ error: err.message || 'Internal server error.' }), { status: 500 });
}
// Return mock feedback for now
return new Response(JSON.stringify({ success: true, feedback }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};