Skip to main content
Glama
quiz-generator.js24.2 kB
/** * Intelligent Quiz Generator * @module content-generation/quiz-generator * @description Generates contextually relevant quiz questions from educational content * @version 5.0.0-alpha */ export class QuizGenerator { constructor(config = {}) { this.config = { minQuestions: 5, maxQuestions: 15, targetQuestions: 10, questionTypes: { multiple_choice: 0.5, true_false: 0.2, fill_blank: 0.15, short_answer: 0.1, matching: 0.05 }, difficultyBalance: { easy: 0.3, medium: 0.5, hard: 0.2 }, ...config }; // Question type templates and generators this.questionGenerators = { multiple_choice: this.generateMultipleChoice.bind(this), true_false: this.generateTrueFalse.bind(this), fill_blank: this.generateFillBlank.bind(this), short_answer: this.generateShortAnswer.bind(this), matching: this.generateMatching.bind(this) }; } /** * Generate comprehensive quiz from content * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @returns {Promise<Object>} Generated quiz */ async generateQuiz(content, topicAnalysis) { console.log('[QUIZ-GENERATOR] Generating quiz for:', topicAnalysis.subject); try { // Calculate question distribution const distribution = this.calculateQuestionDistribution(topicAnalysis); // Generate questions by type const questions = []; for (const [type, count] of Object.entries(distribution)) { const generator = this.questionGenerators[type]; if (generator && count > 0) { const typeQuestions = await generator(content, topicAnalysis, count); questions.push(...typeQuestions); } } // Optimize question set const optimized = this.optimizeQuestionSet(questions); // Create quiz structure const quiz = { metadata: this.generateQuizMetadata(topicAnalysis), questions: optimized, instructions: this.generateInstructions(topicAnalysis), scoring: this.generateScoringSystem(optimized), analytics: this.initializeQuizAnalytics() }; console.log(`[QUIZ-GENERATOR] Generated ${quiz.questions.length} questions`); return quiz; } catch (error) { console.error('[QUIZ-GENERATOR] Quiz generation failed:', error); throw new Error(`Quiz generation failed: ${error.message}`); } } /** * Calculate optimal question distribution * @param {Object} topicAnalysis - Topic analysis * @returns {Object} Question type distribution */ calculateQuestionDistribution(topicAnalysis) { const totalQuestions = Math.min( this.config.maxQuestions, Math.max(this.config.minQuestions, this.config.targetQuestions) ); const distribution = {}; Object.entries(this.config.questionTypes).forEach(([type, ratio]) => { const count = Math.round(totalQuestions * ratio); if (count > 0) { distribution[type] = count; } }); // Ensure minimum total const total = Object.values(distribution).reduce((sum, count) => sum + count, 0); if (total < this.config.minQuestions) { distribution.multiple_choice = (distribution.multiple_choice || 0) + (this.config.minQuestions - total); } return distribution; } /** * Generate multiple choice questions * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @param {number} count - Number of questions to generate * @returns {Promise<Array>} Multiple choice questions */ async generateMultipleChoice(content, topicAnalysis, count) { const questions = []; const { concepts, keywords, subject } = topicAnalysis; // Generate concept-based questions const conceptQuestions = Math.ceil(count * 0.6); for (let i = 0; i < Math.min(conceptQuestions, concepts.length); i++) { const concept = concepts[i]; questions.push({ id: `mc-concept-${i}-${Date.now()}`, type: 'multiple_choice', category: 'concept', question: `Qual é a definição mais adequada para ${concept}?`, options: this.generateConceptOptions(concept, subject), correct: 0, difficulty: 'easy', explanation: this.generateConceptExplanation(concept, subject), tags: [concept, 'definição', 'conceito'], source: 'concept_analysis' }); } // Generate application questions const appQuestions = Math.floor(count * 0.4); for (let i = 0; i < appQuestions; i++) { const question = this.generateApplicationQuestion(topicAnalysis, i); questions.push(question); } return questions.slice(0, count); } /** * Generate true/false questions * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @param {number} count - Number of questions to generate * @returns {Promise<Array>} True/false questions */ async generateTrueFalse(content, topicAnalysis, count) { const questions = []; const { concepts, subject } = topicAnalysis; for (let i = 0; i < count && i < concepts.length; i++) { const concept = concepts[i]; const isTrue = Math.random() > 0.5; questions.push({ id: `tf-${i}-${Date.now()}`, type: 'true_false', category: 'verification', question: this.generateTrueFalseStatement(concept, subject, isTrue), correct: isTrue, difficulty: 'easy', explanation: this.generateTrueFalseExplanation(concept, subject, isTrue), tags: [concept, 'verificação', 'verdadeiro-falso'], source: 'statement_verification' }); } return questions; } /** * Generate fill-in-the-blank questions * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @param {number} count - Number of questions to generate * @returns {Promise<Array>} Fill-in-the-blank questions */ async generateFillBlank(content, topicAnalysis, count) { const questions = []; const { keywords, subject } = topicAnalysis; for (let i = 0; i < count && i < keywords.length; i++) { const keyword = keywords[i]; const sentence = this.generateSentenceWithBlank(keyword, subject); questions.push({ id: `fb-${i}-${Date.now()}`, type: 'fill_blank', category: 'completion', question: sentence.question, answer: sentence.answer, difficulty: 'medium', hints: this.generateFillBlankHints(keyword, subject), tags: [keyword, 'completar', 'lacuna'], source: 'sentence_completion' }); } return questions; } /** * Generate short answer questions * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @param {number} count - Number of questions to generate * @returns {Promise<Array>} Short answer questions */ async generateShortAnswer(content, topicAnalysis, count) { const questions = []; const { subject } = topicAnalysis; const prompts = this.getSubjectSpecificPrompts(subject); for (let i = 0; i < count && i < prompts.length; i++) { const prompt = prompts[i]; questions.push({ id: `sa-${i}-${Date.now()}`, type: 'short_answer', category: 'explanation', question: prompt.question, expectedElements: prompt.elements, difficulty: 'hard', rubric: prompt.rubric, tags: ['explicação', 'resposta curta', subject], source: 'subject_prompts' }); } return questions; } /** * Generate matching questions * @param {Object} content - Educational content * @param {Object} topicAnalysis - Topic analysis * @param {number} count - Number of questions to generate * @returns {Promise<Array>} Matching questions */ async generateMatching(content, topicAnalysis, count) { const questions = []; const { concepts, keywords, subject } = topicAnalysis; if (concepts.length >= 4) { const pairs = this.generateMatchingPairs(concepts, subject); questions.push({ id: `match-${Date.now()}`, type: 'matching', category: 'association', question: 'Associe os conceitos com suas definições correspondentes:', leftColumn: pairs.map(pair => pair.term), rightColumn: this.shuffleArray(pairs.map(pair => pair.definition)), correctPairs: pairs.map((pair, index) => ({ left: index, right: pairs.map(p => p.definition).indexOf(pair.definition) })), difficulty: 'medium', instructions: 'Associe cada termo da coluna esquerda com sua definição na coluna direita', tags: ['associação', 'correspondência', subject], source: 'concept_matching' }); } return questions; } /** * Generate concept options for multiple choice * @param {string} concept - Target concept * @param {string} subject - Subject area * @returns {Array} Answer options */ generateConceptOptions(concept, subject) { const correctDefinition = this.generateConceptDefinition(concept, subject); const distractors = this.generateDistractors(concept, subject, 3); const options = [correctDefinition, ...distractors]; return this.shuffleArray(options); } /** * Generate concept definition * @param {string} concept - Concept to define * @param {string} subject - Subject area * @returns {string} Definition */ generateConceptDefinition(concept, subject) { const definitions = { física: `${concept} é um conceito fundamental da física que descreve propriedades ou fenômenos relacionados à matéria, energia e suas interações.`, química: `${concept} refere-se a aspectos químicos relacionados à estrutura, propriedades e transformações da matéria através de ligações e reações.`, história: `${concept} é um elemento histórico importante para compreender eventos, processos sociais e transformações ao longo do tempo.`, matemática: `${concept} é um conceito matemático que representa relações quantitativas, estruturas lógicas e padrões numéricos.`, geral: `${concept} é um conceito importante para compreender os aspectos fundamentais do tópico estudado.` }; return definitions[subject] || definitions.geral; } /** * Generate distractors for multiple choice * @param {string} concept - Target concept * @param {string} subject - Subject area * @param {number} count - Number of distractors * @returns {Array} Distractor options */ generateDistractors(concept, subject, count) { const distractors = []; // Common distractor patterns const patterns = [ `${concept} é apenas um termo técnico sem aplicação prática específica.`, `${concept} se refere exclusivamente a aspectos teóricos sem base científica.`, `${concept} é um conceito obsoleto que não se aplica aos estudos modernos.`, `${concept} representa apenas uma opinião pessoal sobre o assunto.` ]; for (let i = 0; i < count && i < patterns.length; i++) { distractors.push(patterns[i]); } return distractors; } /** * Generate application question * @param {Object} topicAnalysis - Topic analysis * @param {number} index - Question index * @returns {Object} Application question */ generateApplicationQuestion(topicAnalysis, index) { const { subject } = topicAnalysis; const applications = { física: { question: 'Em qual situação cotidiana podemos observar o conceito estudado?', options: [ 'Ao empurrar um objeto pesado', 'Ao observar uma pintura', 'Ao ler um livro', 'Ao ouvir música' ], correct: 0 }, química: { question: 'Qual processo químico melhor exemplifica o conceito abordado?', options: [ 'Digestão dos alimentos', 'Leitura de um texto', 'Escrita de uma carta', 'Contagem de objetos' ], correct: 0 }, história: { question: 'Qual evento histórico melhor demonstra a aplicação deste conceito?', options: [ 'Revolução Francesa', 'Invenção do telefone', 'Descoberta de um mineral', 'Criação de uma obra de arte' ], correct: 0 }, geral: { question: 'Como este conceito se aplica na prática?', options: [ 'Através de exemplos cotidianos relevantes', 'Apenas em situações teóricas', 'Somente em laboratórios', 'Nunca se aplica na realidade' ], correct: 0 } }; const template = applications[subject] || applications.geral; return { id: `mc-app-${index}-${Date.now()}`, type: 'multiple_choice', category: 'application', question: template.question, options: template.options, correct: template.correct, difficulty: 'medium', explanation: 'Esta aplicação demonstra como o conceito se manifesta em situações práticas.', tags: ['aplicação', 'prático', subject], source: 'application_scenarios' }; } /** * Generate true/false statement * @param {string} concept - Concept * @param {string} subject - Subject * @param {boolean} isTrue - Whether statement should be true * @returns {string} Statement */ generateTrueFalseStatement(concept, subject, isTrue) { if (isTrue) { return `${concept} é um conceito importante para compreender ${subject}.`; } else { return `${concept} não tem relação alguma com o estudo de ${subject}.`; } } /** * Generate sentence with blank for fill-in question * @param {string} keyword - Keyword to blank out * @param {string} subject - Subject area * @returns {Object} Question and answer */ generateSentenceWithBlank(keyword, subject) { const sentence = `O conceito de __________ é fundamental para compreender ${subject}.`; return { question: sentence, answer: keyword }; } /** * Generate subject-specific prompts * @param {string} subject - Subject area * @returns {Array} Prompt objects */ getSubjectSpecificPrompts(subject) { const prompts = { física: [ { question: 'Explique como as leis da física se aplicam no cotidiano.', elements: ['leis físicas', 'aplicação prática', 'exemplos'], rubric: 'Deve mencionar leis específicas e dar exemplos práticos' }, { question: 'Descreva a relação entre energia e movimento.', elements: ['energia', 'movimento', 'transformação'], rubric: 'Deve explicar tipos de energia e como se relacionam com movimento' } ], química: [ { question: 'Explique como as reações químicas afetam nossa vida diária.', elements: ['reações químicas', 'vida cotidiana', 'exemplos'], rubric: 'Deve citar reações específicas e sua importância' }, { question: 'Descreva a importância das ligações químicas.', elements: ['ligações químicas', 'propriedades', 'estrutura'], rubric: 'Deve explicar tipos de ligações e como afetam propriedades' } ], história: [ { question: 'Analise as causas e consequências do evento histórico estudado.', elements: ['causas', 'consequências', 'contexto histórico'], rubric: 'Deve identificar múltiplas causas e consequências de longo prazo' }, { question: 'Compare diferentes interpretações históricas do período estudado.', elements: ['interpretações', 'perspectivas', 'evidências'], rubric: 'Deve apresentar diferentes pontos de vista e evidências' } ], geral: [ { question: 'Explique a importância do conceito estudado.', elements: ['conceito', 'importância', 'aplicação'], rubric: 'Deve demonstrar compreensão e relevância' } ] }; return prompts[subject] || prompts.geral; } /** * Generate matching pairs * @param {Array} concepts - List of concepts * @param {string} subject - Subject area * @returns {Array} Matching pairs */ generateMatchingPairs(concepts, subject) { return concepts.slice(0, 4).map(concept => ({ term: concept, definition: this.generateConceptDefinition(concept, subject) })); } /** * Optimize question set for quality and balance * @param {Array} questions - Raw questions * @returns {Array} Optimized questions */ optimizeQuestionSet(questions) { // Remove duplicates const unique = this.removeDuplicateQuestions(questions); // Balance difficulty const balanced = this.balanceQuestionDifficulty(unique); // Ensure variety const varied = this.ensureQuestionVariety(balanced); // Sort by importance const sorted = this.sortQuestionsByImportance(varied); return sorted; } /** * Remove duplicate questions * @param {Array} questions - Questions array * @returns {Array} Unique questions */ removeDuplicateQuestions(questions) { const seen = new Set(); return questions.filter(question => { const key = question.question; if (seen.has(key)) { return false; } seen.add(key); return true; }); } /** * Balance question difficulty * @param {Array} questions - Questions array * @returns {Array} Balanced questions */ balanceQuestionDifficulty(questions) { const byDifficulty = { easy: questions.filter(q => q.difficulty === 'easy'), medium: questions.filter(q => q.difficulty === 'medium'), hard: questions.filter(q => q.difficulty === 'hard') }; const target = Math.min(this.config.maxQuestions, questions.length); const balanced = []; const easyCount = Math.floor(target * this.config.difficultyBalance.easy); const mediumCount = Math.floor(target * this.config.difficultyBalance.medium); const hardCount = Math.floor(target * this.config.difficultyBalance.hard); balanced.push(...byDifficulty.easy.slice(0, easyCount)); balanced.push(...byDifficulty.medium.slice(0, mediumCount)); balanced.push(...byDifficulty.hard.slice(0, hardCount)); return balanced; } /** * Ensure question variety * @param {Array} questions - Questions array * @returns {Array} Varied questions */ ensureQuestionVariety(questions) { const byType = {}; questions.forEach(question => { if (!byType[question.type]) { byType[question.type] = []; } byType[question.type].push(question); }); // Ensure we have at least one of each available type const varied = []; Object.values(byType).forEach(typeQuestions => { varied.push(...typeQuestions); }); return varied; } /** * Sort questions by importance * @param {Array} questions - Questions array * @returns {Array} Sorted questions */ sortQuestionsByImportance(questions) { return questions.sort((a, b) => { // Priority: concept > application > verification > completion > association const typeOrder = { concept: 0, application: 1, verification: 2, completion: 3, association: 4, explanation: 5 }; const aOrder = typeOrder[a.category] || 6; const bOrder = typeOrder[b.category] || 6; if (aOrder !== bOrder) { return aOrder - bOrder; } // Secondary sort by difficulty (easy first for learning progression) const difficultyOrder = { easy: 0, medium: 1, hard: 2 }; return (difficultyOrder[a.difficulty] || 1) - (difficultyOrder[b.difficulty] || 1); }); } /** * Generate quiz metadata * @param {Object} topicAnalysis - Topic analysis * @returns {Object} Quiz metadata */ generateQuizMetadata(topicAnalysis) { return { subject: topicAnalysis.subject, gradeLevel: topicAnalysis.gradeLevel, estimatedDuration: '10-15 minutos', totalQuestions: 0, // Will be updated after generation questionTypes: Object.keys(this.config.questionTypes), generatedAt: new Date().toISOString(), version: '5.0.0-alpha' }; } /** * Generate quiz instructions * @param {Object} topicAnalysis - Topic analysis * @returns {Array} Instructions */ generateInstructions(topicAnalysis) { return [ 'Leia cada questão cuidadosamente antes de responder.', 'Para questões de múltipla escolha, selecione apenas uma alternativa.', 'Para questões verdadeiro/falso, marque V ou F conforme apropriado.', 'Para questões de completar, escreva a palavra ou frase que melhor completa a sentença.', 'Para questões dissertativas, seja claro e objetivo em suas respostas.', 'Revise suas respostas antes de finalizar.' ]; } /** * Generate scoring system * @param {Array} questions - Quiz questions * @returns {Object} Scoring system */ generateScoringSystem(questions) { const totalPoints = questions.length * 10; // 10 points per question return { totalPoints: totalPoints, passingScore: Math.floor(totalPoints * 0.7), // 70% to pass pointsPerQuestion: 10, gradingScale: { 'Excelente': { min: 90, max: 100 }, 'Bom': { min: 80, max: 89 }, 'Satisfatório': { min: 70, max: 79 }, 'Insuficiente': { min: 0, max: 69 } } }; } /** * Initialize quiz analytics * @returns {Object} Analytics initialization */ initializeQuizAnalytics() { return { generationTime: Date.now(), questionTypeDistribution: {}, difficultyDistribution: {}, estimatedCompletionTime: null, qualityScore: null }; } /** * Utility function to shuffle array * @param {Array} array - Array to shuffle * @returns {Array} Shuffled array */ shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } /** * Generate concept explanation * @param {string} concept - Concept * @param {string} subject - Subject * @returns {string} Explanation */ generateConceptExplanation(concept, subject) { return `${concept} é fundamental para compreender ${subject} porque estabelece as bases conceituais necessárias para o aprendizado.`; } /** * Generate true/false explanation * @param {string} concept - Concept * @param {string} subject - Subject * @param {boolean} isTrue - Whether statement is true * @returns {string} Explanation */ generateTrueFalseExplanation(concept, subject, isTrue) { if (isTrue) { return `Correto. ${concept} é realmente importante para compreender ${subject}.`; } else { return `Falso. ${concept} tem relação direta com o estudo de ${subject}.`; } } /** * Generate fill blank hints * @param {string} keyword - Keyword * @param {string} subject - Subject * @returns {Array} Hints */ generateFillBlankHints(keyword, subject) { return [ `Esta palavra está relacionada a ${subject}`, `Pense nos conceitos principais do tópico`, `A resposta tem ${keyword.length} letras` ]; } } // Factory function for creating QuizGenerator instances export function createQuizGenerator(config = {}) { return new QuizGenerator(config); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server