education-module-ai
Version:
AI Education Module using Agent Framework
420 lines (408 loc) • 20 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.aiService = void 0;
const generative_ai_1 = require("@google/generative-ai");
const LearningPath_1 = require("../models/LearningPath");
const dotenv_1 = __importDefault(require("dotenv"));
// Initialize environment variables
dotenv_1.default.config();
/**
* AI service for generating educational content
*/
class AIService {
constructor() {
// Initialize with environment variable or empty string if not available
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "";
this.genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
this.model = this.genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
}
/**
* Generate educational content based on subject, level, type and topic
*/
async generateEducationalContent(subject, level, type, topic) {
try {
let content;
let title;
let description;
if (this.genAI && this.model) {
// Using Google's Gemini API
const prompt = `
Generate educational content about "${topic}" in the subject of ${subject} at a ${level} level.
The content type is ${type}.
Format your response as JSON with the following structure:
{
"title": "An engaging title for this content",
"description": "A brief description of what this content covers",
"content": "The full educational content with proper formatting, examples, and explanations"
}
Make sure the content is educational, accurate, engaging, and appropriate for the specified level.
`;
const result = await this.model.generateContent(prompt);
const responseText = result.response.text();
try {
// Try to parse as JSON
const parsedResponse = JSON.parse(responseText);
title = parsedResponse.title;
description = parsedResponse.description;
content = parsedResponse.content;
}
catch (parseError) {
// If JSON parsing fails, use regex to extract parts
const titleMatch = responseText.match(/"title":\s*"([^"]+)"/);
const descriptionMatch = responseText.match(/"description":\s*"([^"]+)"/);
const contentMatch = responseText.match(/"content":\s*"([^"]+)"/);
title = titleMatch ? titleMatch[1] : `${topic} for ${level} ${subject}`;
description = descriptionMatch ? descriptionMatch[1] : `Learn about ${topic} in ${subject}`;
content = contentMatch ? contentMatch[1] : responseText;
}
}
else {
// Fallback content
title = `${topic} for ${level} ${subject}`;
description = `Learn about ${topic} in ${subject}`;
content = "To generate actual educational content, please set up the Gemini API key in your environment variables.";
}
return {
title,
content,
subject,
level,
type,
keywords: [topic, subject, level, type],
};
}
catch (error) {
console.error("Error generating content with AI:", error);
throw new Error("Failed to generate educational content. Please try again later.");
}
}
/**
* Generate a quiz based on subject, level and topic
*/
async generateQuiz(subject, level, topic, questionCount = 5) {
try {
if (this.genAI && this.model) {
// Using Google's Gemini API
const prompt = `
Generate a quiz about "${topic}" in the subject of ${subject} at a ${level} level.
The quiz should have ${questionCount} questions.
Format your response as JSON with the following structure:
{
"title": "An engaging title for this quiz",
"description": "A brief description of what this quiz covers",
"questions": [
{
"question": "The question text",
"options": ["Option A", "Option B", "Option C", "Option D"],
"correctAnswer": "The correct answer (exactly matching one of the options)",
"explanation": "Explanation of why this is the correct answer"
},
...more questions...
]
}
Make sure the quiz is educational, accurate, and appropriate for the specified level.
`;
const result = await this.model.generateContent(prompt);
let responseText = await result.response.text();
responseText = responseText.trim();
if (responseText.startsWith("```json")) {
responseText = responseText.substring(7).trim();
if (responseText.endsWith("```")) {
responseText = responseText.substring(0, responseText.length - 3).trim();
}
}
try {
// Try to parse the cleaned response as JSON
return JSON.parse(responseText);
}
catch (parseError) {
// Fallback if JSON parsing fails
console.error("Error parsing quiz JSON:", parseError);
return {
title: `${topic} Quiz`,
description: `Test your knowledge on ${topic} in ${subject} at ${level} level`,
questions: [
{
question: "Error generating quiz. Please try again.",
correctAnswer: "N/A",
explanation: "There was an error generating this quiz. Please try again later."
}
]
};
}
}
else {
// Fallback quiz
return {
title: `${topic} Quiz`,
description: `Test your knowledge on ${topic} in ${subject} at ${level} level`,
questions: [
{
question: "To generate a real quiz, what API key needs to be configured?",
options: ["OpenAI API Key", "Gemini API Key", "Quiz API Key", "No API key needed"],
correctAnswer: "Gemini API Key",
explanation: "To generate actual quizzes, please set up the Gemini API key in your environment variables."
}
]
};
}
}
catch (error) {
console.error("Error generating quiz with AI:", error);
throw new Error("Failed to generate quiz. Please try again later.");
}
}
/**
* Process a message in a learning session and generate response
*/
async processLearningSessionMessage(messages, subject, level, interactionType) {
// Format messages for the AI
const formattedMessages = messages.map(msg => ({
role: msg.role === 'user' ? 'user' : 'model',
content: msg.content
}));
try {
let response;
let relatedContentIds = [];
let followupSuggestions = [];
if (this.genAI && this.model) {
// Using Google's Gemini API
const chat = this.model.startChat({
history: formattedMessages.slice(0, -1), // Exclude the latest message
generationConfig: {
temperature: 0.7,
topP: 0.95,
topK: 64,
},
});
const lastMessage = formattedMessages[formattedMessages.length - 1];
const result = await chat.sendMessage(`
You are an educational AI tutor specialized in ${subject} at the ${level} level.
${interactionType === 'question'
? 'The user has asked a question. Provide a detailed, educational answer.'
: 'Respond to the user appropriately based on their message.'}
After your response, suggest 3 relevant follow-up questions the user might be interested in.
Format your response as:
<answer>Your detailed answer here</answer>
<follow-ups>
1. First follow-up question
2. Second follow-up question
3. Third follow-up question
</follow-ups>
`);
const responseText = result.response.text();
// Extract the answer and follow-ups
const answerMatch = responseText.match(/<answer>([\s\S]*?)<\/answer>/);
const followUpsMatch = responseText.match(/<follow-ups>([\s\S]*?)<\/follow-ups>/);
response = answerMatch ? answerMatch[1].trim() : responseText;
if (followUpsMatch) {
const followUpsText = followUpsMatch[1].trim();
followupSuggestions = followUpsText
.split('\n')
.map((line) => line.trim())
.filter((line) => line.match(/^\d+\.\s/))
.map((line) => line.replace(/^\d+\.\s/, ''));
}
}
else {
// Fallback if API keys are not available
response = "I'm an AI tutor here to help with your learning journey. To enable my full capabilities, please set up the Gemini API key in your environment variables.";
followupSuggestions = [
"How do I set up the API key?",
"What can you help me learn?",
"Can you explain a basic concept in this subject?"
];
}
return {
response,
relatedContentIds,
followupSuggestions
};
}
catch (error) {
console.error("Error processing message with AI:", error);
return {
response: "I'm sorry, I encountered an error processing your request. Please try again later.",
followupSuggestions: ["Could you rephrase your question?", "Would you like to try a different topic?"]
};
}
}
/**
* Generate a learning path for a specific topic
*/
async generateLearningPath(topic, userId) {
try {
const learningPath = new LearningPath_1.LearningPathResource();
learningPath.id = `lp-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
learningPath.userId = userId;
learningPath.topic = topic;
learningPath.difficultyLevel = 'intermediate'; // Default level
if (!this.genAI || !this.model) {
// Fallback if API keys are not available
learningPath.subject = topic.includes('data structure') || topic.includes('algorithm') ? 'computer_science' : 'general';
learningPath.overview = `This is a learning path for ${topic}. To generate actual learning paths, please set up the Gemini API key in your environment variables.`;
learningPath.estimatedCompletionTime = "Unknown";
learningPath.prerequisites = ["API key configuration knowledge"];
learningPath.steps = [
{
title: "Set up API key",
description: "Configure the Gemini API key in your environment variables to enable AI-generated learning paths.",
timeEstimate: "10 minutes",
concepts: ["API configuration", "Environment variables"],
resources: [
{
title: "Google AI Studio",
type: LearningPath_1.ResourceType.ARTICLE,
url: "https://ai.google.dev/",
description: "Get your API key from Google AI Studio"
}
]
}
];
return learningPath;
}
const prompt = `
You are an expert educational content creator. Create a detailed, structured learning path for "${topic}".
The learning path should be comprehensive and include:
1. An overview of the subject
2. Prerequisites
3. A step-by-step progression from beginner to advanced concepts
4. Estimated time commitments for each step
5. Key concepts covered in each step
6. Resources (articles, videos, books, exercises, courses) for each step
Format your response as JSON with the following structure:
{
"subject": "The broader subject area this topic belongs to",
"topic": "${topic}",
"overview": "A detailed overview of what this learning path covers",
"difficultyLevel": "beginner",
"estimatedCompletionTime": "Estimated total time to complete (e.g., '8 weeks')",
"prerequisites": ["Prerequisite 1", "Prerequisite 2", ...],
"steps": [
{
"title": "Step title",
"description": "Detailed description of what this step covers",
"timeEstimate": "Estimated time for this step (e.g., '1 week')",
"concepts": ["Concept 1", "Concept 2", ...],
"resources": [
{
"title": "Resource title",
"type": "article|video|exercise|book|course",
"url": "https://example.com/resource",
"description": "Brief description of this resource"
},
...more resources...
]
},
...more steps...
]
}
The learning path should be tailored to the topic "${topic}" and include at least 5-7 steps from absolute beginner to advanced mastery. For Data Structures and Algorithms specifically, make sure to cover the progression of fundamental concepts, different types of algorithms and their complexity analysis, and practical implementation examples.
`;
const result = await this.model.generateContent(prompt);
const responseText = result.response.text();
let parsedResponse;
try {
// Extract JSON if wrapped in code blocks
const jsonMatch = responseText.match(/```json\s*([\s\S]*?)\s*```/) ||
responseText.match(/```\s*([\s\S]*?)\s*```/) ||
[null, responseText];
const jsonText = jsonMatch[1].trim();
parsedResponse = JSON.parse(jsonText);
}
catch (parseError) {
console.error("Error parsing learning path JSON:", parseError);
throw new Error("Failed to parse learning path response");
}
// Create learning path from parsed response
learningPath.subject = parsedResponse.subject || "general";
learningPath.overview = parsedResponse.overview || `Learning path for ${topic}`;
learningPath.difficultyLevel = parsedResponse.difficultyLevel || 'beginner';
learningPath.estimatedCompletionTime = parsedResponse.estimatedCompletionTime || "Unknown";
learningPath.prerequisites = parsedResponse.prerequisites || [];
learningPath.steps = parsedResponse.steps || [];
return learningPath;
}
catch (error) {
console.error("Error generating learning path with AI:", error);
throw new Error("Failed to generate learning path. Please try again later.");
}
}
/**
* Answer a specific question on a subject
*/
async answerQuestion(subject, level, question) {
try {
if (!this.genAI || !this.model) {
console.error("AI model not initialized. Missing API key?");
// Fallback if API keys are not available
return {
answer: "To get answers to your questions, please set up the Gemini API key in your environment variables.",
relatedConcepts: ["API Configuration"],
furtherReadings: ["Google AI Studio Documentation"]
};
}
const prompt = `
You are an educational AI assistant specialized in ${subject} at the ${level} level.
Answer the following question in a clear, educational manner appropriate for a ${level} level student.
Question: "${question}"
Format your response as JSON with the following structure:
{
"answer": "Your detailed answer here",
"relatedConcepts": ["Concept 1", "Concept 2", "Concept 3"],
"furtherReadings": ["Reading 1", "Reading 2"]
}
`;
console.log("Sending prompt to AI model:", prompt);
const result = await this.model.generateContent(prompt);
console.log("Raw AI model response:", result.response);
let responseText = result.response.text();
// Clean up markdown code blocks
responseText = responseText.trim();
if (responseText.startsWith("```json")) {
responseText = responseText.substring(7).trim();
if (responseText.endsWith("```")) {
responseText = responseText.substring(0, responseText.length - 3).trim();
}
}
try {
// Try to parse as JSON
const parsedResponse = JSON.parse(responseText);
// Clean up the answer text by removing excessive markdown
let cleanedAnswer = parsedResponse.answer || "";
// Remove excessive asterisks that create bold formatting
cleanedAnswer = cleanedAnswer.replace(/\*\*/g, "");
// Remove other markdown symbols if needed
cleanedAnswer = cleanedAnswer.replace(/\*\*/g, "");
return {
answer: cleanedAnswer,
relatedConcepts: parsedResponse.relatedConcepts || [],
furtherReadings: parsedResponse.furtherReadings || []
};
}
catch (parseError) {
// Fallback if JSON parsing fails
console.error("Error parsing answer JSON:", parseError);
// Attempt to extract the answer using regex
const answerMatch = responseText.match(/"answer":\s*"([^"]+)"/);
const answer = answerMatch
? answerMatch[1]
: responseText.substring(0, Math.min(responseText.length, 500));
return {
answer,
relatedConcepts: [],
furtherReadings: []
};
}
}
catch (error) {
console.error("Error in aiService.answerQuestion:", error);
throw new Error("Failed to answer question. Please try again later.");
}
}
}
// Create and export a singleton instance
exports.aiService = new AIService();