INTRODUCTION
Teaching children about artificial intelligence and large language models has become increasingly important in our technology-driven world. As these systems become more prevalent in daily life, children need to understand not only how to use them but also how they work, their limitations, and the ethical considerations surrounding their use. An interactive application that uses an actual LLM to teach about LLMs creates a unique meta-learning experience where the medium becomes part of the message.
This article describes the design and implementation of such an educational application. The application, which we will call AI Explorer, provides an age-appropriate, engaging platform where children can learn about generative AI through direct interaction, guided lessons, and hands-on experimentation. The system is designed for children aged eight to fourteen, with content that adapts to different comprehension levels.
The application serves multiple educational objectives.
First, it demystifies AI technology by explaining concepts in accessible language.
Second, it provides hands-on experience with an actual LLM in a safe, controlled environment.
Third, it teaches critical thinking skills by helping children understand when to trust AI outputs and when to question them.
Fourth, it introduces basic concepts of machine learning, neural networks, and natural language processing without requiring advanced mathematical knowledge.
APPLICATION ARCHITECTURE
The architecture of AI Explorer follows clean architecture principles with clear separation between layers. The system consists of four primary layers: the presentation layer, the application layer, the domain layer, and the infrastructure layer. This separation ensures that the core educational logic remains independent of specific implementation details, making the system maintainable and testable.
The presentation layer handles all user interactions through a web-based interface. This layer is responsible for rendering the user interface, capturing user input, and displaying responses from the system. It communicates with the application layer through well-defined interfaces, never directly accessing the domain or infrastructure layers.
The application layer orchestrates the educational experience. It manages the flow of lessons, tracks student progress, determines which content to present next, and coordinates between different components. This layer contains the use cases that define what the application can do, such as starting a new lesson, answering a student question, or generating an interactive example.
The domain layer contains the core educational logic and business rules. This includes the curriculum structure, the knowledge graph of AI concepts, the progression system that determines when a student is ready for more advanced topics, and the rules for generating age-appropriate explanations. This layer is completely independent of any external frameworks or technologies.
The infrastructure layer handles all external dependencies. This includes the LLM API integration, database access, session management, and logging. By isolating these concerns, we can easily swap implementations without affecting the core educational logic.
Here is a simplified representation of the architecture:
+------------------+
| Presentation |
| (Web Interface) |
+------------------+
|
v
+------------------+
| Application |
| (Use Cases) |
+------------------+
|
v
+------------------+
| Domain |
| (Education Logic)|
+------------------+
|
v
+------------------+
| Infrastructure |
| (LLM, DB, etc) |
+------------------+
The data flow through the system follows a clear pattern. When a student asks a question, the presentation layer captures the input and passes it to the application layer. The application layer consults the domain layer to determine the student's current level and learning context. It then uses the infrastructure layer to query the LLM with a carefully crafted prompt that includes educational guardrails. The response is processed through the domain layer to ensure it meets educational standards before being returned to the presentation layer for display.
CORE COMPONENTS
The Student Profile Manager is the first critical component. This component maintains information about each student's learning journey, including their progress through the curriculum, their comprehension level, topics they have mastered, and areas where they need additional support. The profile is used to personalize the learning experience and ensure that content is appropriately challenging.
Here is the core Student Profile class:
class StudentProfile:
"""
Represents a student's learning profile and progress through the curriculum.
This class encapsulates all student-specific data and provides methods
to track and update learning progress.
"""
def __init__(self, student_id, age, name):
"""
Initialize a new student profile.
Args:
student_id: Unique identifier for the student
age: Student's age in years
name: Student's display name
"""
self.student_id = student_id
self.age = age
self.name = name
self.completed_lessons = set()
self.current_level = self._determine_initial_level()
self.mastered_concepts = set()
self.interaction_count = 0
self.last_active = None
def _determine_initial_level(self):
"""
Determine the appropriate starting level based on age.
Younger students start with more basic concepts.
Returns:
String representing the initial difficulty level
"""
if self.age < 10:
return "beginner"
elif self.age < 12:
return "intermediate"
else:
return "advanced"
def mark_lesson_complete(self, lesson_id):
"""
Mark a lesson as completed and update the student's progress.
Args:
lesson_id: Identifier of the completed lesson
"""
self.completed_lessons.add(lesson_id)
self.interaction_count += 1
def add_mastered_concept(self, concept):
"""
Add a concept to the set of mastered concepts.
Args:
concept: The concept identifier that has been mastered
"""
self.mastered_concepts.add(concept)
def get_comprehension_level(self):
"""
Calculate the current comprehension level based on progress.
Returns:
Float between 0.0 and 1.0 representing comprehension
"""
total_concepts = 20 # Total concepts in curriculum
mastered = len(self.mastered_concepts)
return min(1.0, mastered / total_concepts)
The Student Profile Manager uses this class to maintain state across sessions. When a student logs in, their profile is loaded from the database. As they interact with the system, their profile is updated to reflect their progress. This allows the system to provide continuity and personalization across multiple sessions.
The Curriculum Manager is responsible for organizing and delivering educational content. It maintains a structured knowledge graph of AI concepts, with dependencies between topics. For example, a student must understand what data is before learning about training data, and must understand training data before learning about how models learn patterns.
The curriculum is organized into modules, each focusing on a specific aspect of AI and LLMs. The first module introduces the basic concept of what AI is and provides real-world examples that children can relate to. The second module explains what language models are and how they differ from other types of AI. The third module explores how LLMs are trained, using age-appropriate analogies. The fourth module covers what LLMs can and cannot do, emphasizing limitations. The fifth module addresses ethical considerations, including bias, privacy, and responsible use.
Here is the Curriculum Manager implementation:
class CurriculumManager:
"""
Manages the educational curriculum and determines appropriate content
for students based on their progress and comprehension level.
"""
def __init__(self):
"""Initialize the curriculum with all modules and their dependencies."""
self.modules = self._initialize_modules()
self.concept_graph = self._build_concept_graph()
def _initialize_modules(self):
"""
Create the complete curriculum structure with all modules.
Returns:
Dictionary mapping module IDs to module objects
"""
modules = {}
modules["intro_ai"] = {
"id": "intro_ai",
"title": "What is Artificial Intelligence?",
"description": "Learn what AI means and see examples in everyday life",
"prerequisites": [],
"concepts": ["ai_definition", "ai_examples", "ai_vs_human"],
"difficulty": "beginner"
}
modules["intro_llm"] = {
"id": "intro_llm",
"title": "What are Language Models?",
"description": "Discover how computers understand and generate language",
"prerequisites": ["intro_ai"],
"concepts": ["language_model", "text_generation", "patterns"],
"difficulty": "beginner"
}
modules["training"] = {
"id": "training",
"title": "How AI Learns",
"description": "Explore how language models are trained on data",
"prerequisites": ["intro_llm"],
"concepts": ["training_data", "learning_patterns", "neural_networks"],
"difficulty": "intermediate"
}
modules["capabilities"] = {
"id": "capabilities",
"title": "What Can AI Do?",
"description": "Learn about the abilities and limitations of AI",
"prerequisites": ["training"],
"concepts": ["ai_capabilities", "ai_limitations", "hallucinations"],
"difficulty": "intermediate"
}
modules["ethics"] = {
"id": "ethics",
"title": "Using AI Responsibly",
"description": "Understand the ethical considerations of AI",
"prerequisites": ["capabilities"],
"concepts": ["bias", "privacy", "responsible_use"],
"difficulty": "advanced"
}
return modules
def _build_concept_graph(self):
"""
Build a directed graph showing dependencies between concepts.
Returns:
Dictionary representing the concept dependency graph
"""
graph = {}
for module_id, module in self.modules.items():
for concept in module["concepts"]:
graph[concept] = {
"module": module_id,
"prerequisites": module["prerequisites"]
}
return graph
def get_next_module(self, student_profile):
"""
Determine the next appropriate module for a student.
Args:
student_profile: The student's profile object
Returns:
Module object or None if all modules are complete
"""
for module_id, module in self.modules.items():
if module_id in student_profile.completed_lessons:
continue
prerequisites_met = all(
prereq in student_profile.completed_lessons
for prereq in module["prerequisites"]
)
if prerequisites_met:
return module
return None
def is_concept_accessible(self, concept, student_profile):
"""
Check if a student has met the prerequisites for a concept.
Args:
concept: The concept identifier to check
student_profile: The student's profile object
Returns:
Boolean indicating if the concept is accessible
"""
if concept not in self.concept_graph:
return False
prerequisites = self.concept_graph[concept]["prerequisites"]
return all(
prereq in student_profile.completed_lessons
for prereq in prerequisites
)
The Curriculum Manager works closely with the Student Profile Manager to ensure that students are always presented with content that is appropriate for their current level. When a student completes a module, the system automatically determines the next module they should tackle based on the dependency graph.
The LLM Integration Layer is perhaps the most critical component, as it handles all communication with the underlying language model. This layer is responsible for constructing prompts that elicit educational responses, filtering outputs to ensure age-appropriateness, and handling errors gracefully. The integration layer uses a sophisticated prompting strategy that includes the student's context, learning objectives, and safety constraints.
Here is the LLM Integration Layer:
class LLMEducationService:
"""
Service layer for interacting with the LLM API in an educational context.
This class handles prompt construction, response filtering, and safety checks.
"""
def __init__(self, api_client, content_filter):
"""
Initialize the LLM education service.
Args:
api_client: Client for communicating with the LLM API
content_filter: Filter for ensuring age-appropriate content
"""
self.api_client = api_client
self.content_filter = content_filter
self.system_prompt = self._build_system_prompt()
def _build_system_prompt(self):
"""
Construct the system prompt that defines the AI's educational role.
Returns:
String containing the complete system prompt
"""
return """You are an educational AI assistant designed to teach children
aged 8-14 about artificial intelligence and large language models. Your
responses should be:
1. Age-appropriate and easy to understand
2. Accurate but simplified for young learners
3. Engaging and encouraging
4. Free from technical jargon unless explaining it
5. Focused on building understanding, not just providing answers
Use analogies and examples that children can relate to. When explaining
complex concepts, break them down into smaller, digestible pieces. Always
be honest about what you don't know or what AI cannot do. Encourage
critical thinking by asking questions back to the student.
Never provide information that could be harmful, inappropriate, or scary
for children. If asked about sensitive topics, redirect to age-appropriate
educational content."""
def generate_response(self, student_question, student_profile, lesson_context):
"""
Generate an educational response to a student's question.
Args:
student_question: The question asked by the student
student_profile: The student's profile object
lesson_context: Current lesson or module context
Returns:
String containing the AI's response
"""
# Build the complete prompt with context
full_prompt = self._construct_contextual_prompt(
student_question,
student_profile,
lesson_context
)
# Query the LLM API
raw_response = self.api_client.complete(
system_prompt=self.system_prompt,
user_prompt=full_prompt,
temperature=0.7,
max_tokens=500
)
# Filter the response for safety and appropriateness
filtered_response = self.content_filter.filter(
raw_response,
student_profile.age
)
return filtered_response
def _construct_contextual_prompt(self, question, profile, context):
"""
Build a prompt that includes relevant context about the student.
Args:
question: The student's question
profile: Student profile object
context: Current learning context
Returns:
String containing the complete prompt
"""
prompt_parts = []
# Add student context
prompt_parts.append(f"Student age: {profile.age}")
prompt_parts.append(f"Current level: {profile.current_level}")
# Add learning context
if context:
prompt_parts.append(f"Current lesson: {context.get('title', 'General')}")
prompt_parts.append(f"Lesson focus: {context.get('description', '')}")
# Add mastered concepts
if profile.mastered_concepts:
concepts_str = ", ".join(list(profile.mastered_concepts)[:5])
prompt_parts.append(f"Student has learned about: {concepts_str}")
# Add the actual question
prompt_parts.append(f"\nStudent question: {question}")
return "\n".join(prompt_parts)
def generate_lesson_content(self, module, profile):
"""
Generate interactive lesson content for a specific module.
Args:
module: The module object to generate content for
profile: Student profile object
Returns:
Dictionary containing lesson content and interactive elements
"""
content_prompt = f"""Create an engaging lesson introduction for the module
titled '{module['title']}'. The lesson should:
- Start with a relatable question or scenario
- Introduce the main concepts: {', '.join(module['concepts'])}
- Include an interactive example or thought experiment
- Be appropriate for a {profile.age}-year-old student
- Take about 5-7 minutes to complete
Format the response as a structured lesson with clear sections."""
lesson_content = self.api_client.complete(
system_prompt=self.system_prompt,
user_prompt=content_prompt,
temperature=0.8,
max_tokens=800
)
return {
"content": lesson_content,
"module_id": module["id"],
"interactive_elements": self._extract_interactive_elements(lesson_content)
}
def _extract_interactive_elements(self, content):
"""
Identify interactive elements in lesson content.
Args:
content: The lesson content string
Returns:
List of interactive element definitions
"""
# This would parse the content to identify questions,
# activities, or experiments embedded in the lesson
elements = []
# Simple heuristic: look for questions
lines = content.split('\n')
for i, line in enumerate(lines):
if '?' in line and len(line) < 200:
elements.append({
"type": "question",
"content": line.strip(),
"position": i
})
return elements
The LLM Integration Layer demonstrates several important design principles. First, it separates the concerns of prompt construction, API communication, and content filtering. Second, it maintains context about the student and their learning journey, ensuring that responses are personalized. Third, it includes safety mechanisms to filter inappropriate content before it reaches the student.
The Content Filter is a crucial safety component that examines all LLM outputs before they are displayed to students. This filter checks for age-inappropriate language, potentially scary or disturbing content, misinformation about AI capabilities, and any content that might undermine the educational objectives. The filter uses a combination of keyword matching, sentiment analysis, and rule-based logic.
class ContentFilter:
"""
Filters LLM responses to ensure they are safe and appropriate for children.
This class implements multiple filtering strategies to protect young users.
"""
def __init__(self):
"""Initialize the content filter with filtering rules and word lists."""
self.inappropriate_words = self._load_inappropriate_words()
self.scary_concepts = self._load_scary_concepts()
self.age_thresholds = self._define_age_thresholds()
def _load_inappropriate_words(self):
"""
Load list of words that are inappropriate for children.
Returns:
Set of inappropriate words
"""
# In production, this would load from a comprehensive database
return {
"violence", "weapon", "kill", "death", "blood",
# ... many more entries
}
def _load_scary_concepts(self):
"""
Load concepts that might be scary for children.
Returns:
Set of potentially scary concept keywords
"""
return {
"ai takeover", "robot uprising", "end of humanity",
"ai replacing humans", "job loss", "surveillance",
# ... more entries
}
def _define_age_thresholds(self):
"""
Define what content is appropriate for different age groups.
Returns:
Dictionary mapping ages to content restrictions
"""
return {
8: {"max_complexity": "simple", "avoid": ["abstract_concepts"]},
10: {"max_complexity": "moderate", "avoid": ["advanced_math"]},
12: {"max_complexity": "detailed", "avoid": []},
14: {"max_complexity": "comprehensive", "avoid": []}
}
def filter(self, content, student_age):
"""
Filter content to ensure it is appropriate for the student's age.
Args:
content: The content to filter
student_age: Age of the student
Returns:
Filtered content string
"""
# Check for inappropriate words
if self._contains_inappropriate_content(content):
return self._generate_safe_alternative()
# Check for scary concepts
if self._contains_scary_concepts(content):
content = self._soften_scary_content(content)
# Check age-appropriateness
if not self._is_age_appropriate(content, student_age):
content = self._simplify_content(content, student_age)
# Verify educational value
if not self._has_educational_value(content):
return self._generate_educational_alternative()
return content
def _contains_inappropriate_content(self, content):
"""
Check if content contains inappropriate words or concepts.
Args:
content: The content to check
Returns:
Boolean indicating if inappropriate content was found
"""
content_lower = content.lower()
return any(word in content_lower for word in self.inappropriate_words)
def _contains_scary_concepts(self, content):
"""
Check if content contains potentially scary concepts.
Args:
content: The content to check
Returns:
Boolean indicating if scary concepts were found
"""
content_lower = content.lower()
return any(concept in content_lower for concept in self.scary_concepts)
def _soften_scary_content(self, content):
"""
Modify content to make scary concepts less frightening.
Args:
content: The content to modify
Returns:
Modified content string
"""
# Replace scary phrases with gentler alternatives
replacements = {
"ai takeover": "AI becoming very advanced",
"replacing humans": "helping humans with tasks",
"job loss": "changing the types of jobs available"
}
modified = content
for scary, gentle in replacements.items():
modified = modified.replace(scary, gentle)
return modified
def _is_age_appropriate(self, content, age):
"""
Determine if content complexity matches student age.
Args:
content: The content to evaluate
age: Student's age
Returns:
Boolean indicating if content is age-appropriate
"""
# Simple heuristic based on sentence length and word complexity
sentences = content.split('.')
avg_sentence_length = sum(len(s.split()) for s in sentences) / len(sentences)
if age < 10 and avg_sentence_length > 15:
return False
elif age < 12 and avg_sentence_length > 20:
return False
return True
def _simplify_content(self, content, age):
"""
Simplify content to match student's age level.
Args:
content: The content to simplify
age: Student's age
Returns:
Simplified content string
"""
# In production, this would use NLP to actually simplify
# For now, we return a note that simplification is needed
return content # Placeholder for actual simplification logic
def _has_educational_value(self, content):
"""
Verify that content has educational merit.
Args:
content: The content to evaluate
Returns:
Boolean indicating if content is educational
"""
# Check for educational keywords and structure
educational_indicators = [
"learn", "understand", "example", "because", "how", "why",
"this means", "let's explore", "imagine", "think about"
]
content_lower = content.lower()
indicator_count = sum(
1 for indicator in educational_indicators
if indicator in content_lower
)
return indicator_count >= 2
def _generate_safe_alternative(self):
"""
Generate a safe alternative response when content is inappropriate.
Returns:
Safe alternative response string
"""
return """I want to make sure I give you information that's helpful and
appropriate. Let me try to answer your question in a different way. Could
you tell me more about what you'd like to learn?"""
def _generate_educational_alternative(self):
"""
Generate an educational alternative when content lacks value.
Returns:
Educational alternative response string
"""
return """That's an interesting question! Let me explain this in a way
that will help you understand AI better. What specific part would you
like to learn more about?"""
The Content Filter works as a safety net, ensuring that even if the LLM generates something unexpected, it will be caught before reaching the student. This is essential for any application designed for children, where safety must be the top priority.
USER INTERFACE DESIGN
The user interface of AI Explorer is designed to be intuitive, engaging, and accessible for children. The interface uses bright colors, friendly typography, and clear visual hierarchies to guide students through their learning journey. The main interface consists of several key areas: the conversation area where students interact with the AI, the progress tracker showing completed modules and mastered concepts, the lesson selector for choosing what to learn next, and the help section with guidance for parents and teachers.
The conversation area is the heart of the interface. It displays the ongoing dialogue between the student and the AI tutor in a chat-like format that children find familiar from messaging applications. Each message is clearly attributed to either the student or the AI, with distinct visual styling. The AI's messages use a friendly avatar and a warm color scheme, while the student's messages appear in a different style to create clear visual distinction.
Here is the JavaScript code that manages the conversation interface:
/**
* ConversationManager handles the chat interface and message display.
* This class manages the DOM manipulation for the conversation area
* and coordinates with the backend API.
*/
class ConversationManager {
constructor(conversationElement, inputElement, sendButton) {
this.conversationElement = conversationElement;
this.inputElement = inputElement;
this.sendButton = sendButton;
this.messageHistory = [];
this.isWaitingForResponse = false;
this.initializeEventListeners();
}
/**
* Set up event listeners for user interactions.
*/
initializeEventListeners() {
this.sendButton.addEventListener('click', () => {
this.handleSendMessage();
});
this.inputElement.addEventListener('keypress', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.handleSendMessage();
}
});
}
/**
* Handle sending a message from the student.
*/
async handleSendMessage() {
const messageText = this.inputElement.value.trim();
if (messageText === '' || this.isWaitingForResponse) {
return;
}
// Display the student's message
this.displayMessage(messageText, 'student');
// Clear the input
this.inputElement.value = '';
// Show typing indicator
this.showTypingIndicator();
// Send to backend and wait for response
this.isWaitingForResponse = true;
try {
const response = await this.sendToBackend(messageText);
this.hideTypingIndicator();
this.displayMessage(response.message, 'ai');
// Update progress if the response includes it
if (response.progress) {
this.updateProgress(response.progress);
}
} catch (error) {
this.hideTypingIndicator();
this.displayError('Sorry, I had trouble understanding that. Could you try again?');
} finally {
this.isWaitingForResponse = false;
}
}
/**
* Display a message in the conversation area.
*
* @param {string} text - The message text to display
* @param {string} sender - Either 'student' or 'ai'
*/
displayMessage(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${sender}`;
const avatarDiv = document.createElement('div');
avatarDiv.className = `avatar avatar-${sender}`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.textContent = text;
const timestampDiv = document.createElement('div');
timestampDiv.className = 'message-timestamp';
timestampDiv.textContent = this.getCurrentTime();
messageDiv.appendChild(avatarDiv);
messageDiv.appendChild(contentDiv);
messageDiv.appendChild(timestampDiv);
this.conversationElement.appendChild(messageDiv);
// Store in history
this.messageHistory.push({
text: text,
sender: sender,
timestamp: new Date()
});
// Scroll to bottom
this.scrollToBottom();
}
/**
* Show a typing indicator while waiting for AI response.
*/
showTypingIndicator() {
const indicator = document.createElement('div');
indicator.className = 'typing-indicator';
indicator.id = 'typing-indicator';
for (let i = 0; i < 3; i++) {
const dot = document.createElement('span');
dot.className = 'typing-dot';
indicator.appendChild(dot);
}
this.conversationElement.appendChild(indicator);
this.scrollToBottom();
}
/**
* Hide the typing indicator.
*/
hideTypingIndicator() {
const indicator = document.getElementById('typing-indicator');
if (indicator) {
indicator.remove();
}
}
/**
* Send a message to the backend API.
*
* @param {string} message - The message to send
* @returns {Promise} Promise resolving to the API response
*/
async sendToBackend(message) {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
session_id: this.getSessionId()
})
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
}
/**
* Get the current session ID from storage.
*
* @returns {string} The session ID
*/
getSessionId() {
return sessionStorage.getItem('session_id') || this.createNewSession();
}
/**
* Create a new session ID.
*
* @returns {string} The new session ID
*/
createNewSession() {
const sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
sessionStorage.setItem('session_id', sessionId);
return sessionId;
}
/**
* Get current time formatted for display.
*
* @returns {string} Formatted time string
*/
getCurrentTime() {
const now = new Date();
return now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
}
/**
* Scroll the conversation area to the bottom.
*/
scrollToBottom() {
this.conversationElement.scrollTop = this.conversationElement.scrollHeight;
}
/**
* Display an error message to the user.
*
* @param {string} errorText - The error message to display
*/
displayError(errorText) {
const errorDiv = document.createElement('div');
errorDiv.className = 'message message-error';
errorDiv.textContent = errorText;
this.conversationElement.appendChild(errorDiv);
this.scrollToBottom();
}
/**
* Update the progress display based on backend data.
*
* @param {Object} progressData - Progress information from backend
*/
updateProgress(progressData) {
const progressEvent = new CustomEvent('progressUpdate', {
detail: progressData
});
document.dispatchEvent(progressEvent);
}
}
The conversation manager handles all aspects of the chat interface, from displaying messages to managing the communication with the backend. It provides visual feedback through typing indicators and ensures that the interface remains responsive even when waiting for the AI to generate a response.
The progress tracker is another important interface element. It shows students what they have accomplished and what comes next in their learning journey. The tracker displays completed modules as colorful badges, shows the current module with a progress bar, and previews upcoming modules that will unlock as prerequisites are met. This gamification element motivates students to continue learning and provides a sense of achievement.
The lesson selector allows students to choose what they want to learn about within the constraints of the curriculum structure. Students can select from available modules, review completed lessons, or ask free-form questions. The interface makes it clear which modules are currently accessible and which require completing prerequisites first.
EDUCATIONAL CONTENT STRATEGY
The educational content strategy of AI Explorer is built on several pedagogical principles. The first principle is progressive disclosure, where complex concepts are introduced gradually, building on previously mastered ideas. Students start with concrete, relatable examples before moving to more abstract concepts. The second principle is active learning, where students learn by doing rather than just reading. The application includes interactive experiments, thought exercises, and opportunities to test their understanding. The third principle is immediate feedback, where students receive responses to their questions and actions right away, reinforcing correct understanding and gently correcting misconceptions.
The content is organized around key learning objectives. For the introductory module on artificial intelligence, the learning objectives include understanding that AI is software that can learn from data, recognizing examples of AI in everyday life, and distinguishing between AI and human intelligence. For the module on language models, objectives include understanding that language models predict text based on patterns, recognizing that they do not truly understand meaning, and identifying appropriate uses for language models.
Each module uses a consistent structure. The module begins with an engaging hook that connects to the student's experience. For example, the language models module might start by asking if the student has ever used autocomplete on their phone. The module then presents core concepts through explanation and examples. Interactive elements are woven throughout to maintain engagement. The module concludes with a knowledge check that assesses understanding and a preview of what comes next.
The application uses several types of interactive elements. Question prompts ask students to think about concepts before the answer is revealed. Experiments allow students to try things themselves, such as seeing how the AI responds to different types of questions. Analogies help explain complex ideas through familiar comparisons. For instance, training an AI might be compared to learning to ride a bike, where practice with feedback leads to improvement.
Here is an example of how lesson content is structured:
class LessonContent:
"""
Represents the content and structure of an educational lesson.
This class encapsulates all elements of a lesson including text,
interactive components, and assessment items.
"""
def __init__(self, lesson_id, title, module_id):
"""
Initialize a new lesson.
Args:
lesson_id: Unique identifier for the lesson
title: Display title of the lesson
module_id: ID of the parent module
"""
self.lesson_id = lesson_id
self.title = title
self.module_id = module_id
self.sections = []
self.interactive_elements = []
self.knowledge_checks = []
def add_section(self, section_type, content, metadata=None):
"""
Add a section to the lesson.
Args:
section_type: Type of section (text, example, analogy, etc)
content: The actual content of the section
metadata: Optional additional information about the section
"""
section = {
"type": section_type,
"content": content,
"metadata": metadata or {},
"order": len(self.sections)
}
self.sections.append(section)
def add_interactive_element(self, element_type, prompt, expected_response=None):
"""
Add an interactive element to the lesson.
Args:
element_type: Type of interaction (question, experiment, etc)
prompt: The prompt or instruction for the student
expected_response: Optional expected response for validation
"""
element = {
"type": element_type,
"prompt": prompt,
"expected_response": expected_response,
"order": len(self.interactive_elements)
}
self.interactive_elements.append(element)
def add_knowledge_check(self, question, correct_answer, explanation):
"""
Add a knowledge check question to assess understanding.
Args:
question: The question to ask
correct_answer: The correct answer
explanation: Explanation of why this is correct
"""
check = {
"question": question,
"correct_answer": correct_answer,
"explanation": explanation,
"order": len(self.knowledge_checks)
}
self.knowledge_checks.append(check)
def get_ordered_content(self):
"""
Get all lesson content in the correct presentation order.
Returns:
List of content items in order
"""
all_content = []
section_index = 0
interactive_index = 0
# Interleave sections and interactive elements
while section_index < len(self.sections) or interactive_index < len(self.interactive_elements):
# Add a section if available
if section_index < len(self.sections):
all_content.append({
"category": "section",
"data": self.sections[section_index]
})
section_index += 1
# Add an interactive element every few sections
if interactive_index < len(self.interactive_elements) and section_index % 3 == 0:
all_content.append({
"category": "interactive",
"data": self.interactive_elements[interactive_index]
})
interactive_index += 1
# Add remaining interactive elements
while interactive_index < len(self.interactive_elements):
all_content.append({
"category": "interactive",
"data": self.interactive_elements[interactive_index]
})
interactive_index += 1
# Add knowledge checks at the end
for check in self.knowledge_checks:
all_content.append({
"category": "knowledge_check",
"data": check
})
return all_content
def to_dict(self):
"""
Convert the lesson to a dictionary for serialization.
Returns:
Dictionary representation of the lesson
"""
return {
"lesson_id": self.lesson_id,
"title": self.title,
"module_id": self.module_id,
"sections": self.sections,
"interactive_elements": self.interactive_elements,
"knowledge_checks": self.knowledge_checks
}
class LessonBuilder:
"""
Builder class for constructing complete lessons with all components.
This class provides a fluent interface for creating rich lesson content.
"""
def __init__(self, curriculum_manager, llm_service):
"""
Initialize the lesson builder.
Args:
curriculum_manager: Manager for curriculum structure
llm_service: Service for generating dynamic content
"""
self.curriculum_manager = curriculum_manager
self.llm_service = llm_service
def build_lesson(self, module_id, student_profile):
"""
Build a complete lesson for a given module.
Args:
module_id: ID of the module to build a lesson for
student_profile: Profile of the student taking the lesson
Returns:
Complete LessonContent object
"""
module = self.curriculum_manager.modules.get(module_id)
if not module:
raise ValueError(f"Module {module_id} not found")
lesson = LessonContent(
lesson_id=f"lesson_{module_id}_{student_profile.student_id}",
title=module["title"],
module_id=module_id
)
# Build introduction section
intro = self._build_introduction(module, student_profile)
lesson.add_section("introduction", intro)
# Build concept sections for each concept in the module
for concept in module["concepts"]:
concept_content = self._build_concept_section(concept, student_profile)
lesson.add_section("concept", concept_content, {"concept_id": concept})
# Add interactive element after each concept
interactive = self._build_interactive_element(concept, student_profile)
lesson.add_interactive_element(
interactive["type"],
interactive["prompt"],
interactive.get("expected_response")
)
# Build summary section
summary = self._build_summary(module, student_profile)
lesson.add_section("summary", summary)
# Add knowledge checks
checks = self._build_knowledge_checks(module, student_profile)
for check in checks:
lesson.add_knowledge_check(
check["question"],
check["correct_answer"],
check["explanation"]
)
return lesson
def _build_introduction(self, module, profile):
"""
Build an engaging introduction for the module.
Args:
module: Module object
profile: Student profile
Returns:
Introduction text
"""
prompt = f"""Create an engaging introduction for a lesson titled
'{module['title']}' for a {profile.age}-year-old student. The introduction
should start with a relatable question or scenario that connects to their
everyday experience. Keep it to 2-3 short paragraphs."""
return self.llm_service.api_client.complete(
system_prompt=self.llm_service.system_prompt,
user_prompt=prompt,
temperature=0.8,
max_tokens=300
)
def _build_concept_section(self, concept, profile):
"""
Build content explaining a specific concept.
Args:
concept: Concept identifier
profile: Student profile
Returns:
Concept explanation text
"""
prompt = f"""Explain the concept of '{concept}' to a {profile.age}-year-old
student learning about AI. Use simple language, provide a concrete example,
and include an analogy to something familiar. Keep it to 3-4 paragraphs."""
return self.llm_service.api_client.complete(
system_prompt=self.llm_service.system_prompt,
user_prompt=prompt,
temperature=0.7,
max_tokens=400
)
def _build_interactive_element(self, concept, profile):
"""
Create an interactive element for a concept.
Args:
concept: Concept identifier
profile: Student profile
Returns:
Dictionary describing the interactive element
"""
# Different types of interactive elements based on concept
if "definition" in concept or "what" in concept:
return {
"type": "question",
"prompt": f"In your own words, what do you think {concept.replace('_', ' ')} means?",
"expected_response": None # Open-ended
}
elif "example" in concept:
return {
"type": "experiment",
"prompt": f"Can you think of an example of {concept.replace('_', ' ')} from your own life?",
"expected_response": None
}
else:
return {
"type": "reflection",
"prompt": f"Why do you think {concept.replace('_', ' ')} is important?",
"expected_response": None
}
def _build_summary(self, module, profile):
"""
Build a summary section for the module.
Args:
module: Module object
profile: Student profile
Returns:
Summary text
"""
concepts_str = ", ".join(module["concepts"])
prompt = f"""Create a brief summary of a lesson about '{module['title']}'
that covered these concepts: {concepts_str}. The summary should reinforce
key points and encourage the {profile.age}-year-old student. Keep it to
2 paragraphs."""
return self.llm_service.api_client.complete(
system_prompt=self.llm_service.system_prompt,
user_prompt=prompt,
temperature=0.7,
max_tokens=250
)
def _build_knowledge_checks(self, module, profile):
"""
Build knowledge check questions for the module.
Args:
module: Module object
profile: Student profile
Returns:
List of knowledge check dictionaries
"""
checks = []
for concept in module["concepts"]:
prompt = f"""Create a simple multiple-choice question to check if a
{profile.age}-year-old student understands '{concept}'. Provide the
question, the correct answer, and a brief explanation."""
# In production, this would parse the LLM response into structured data
# For now, we'll create a simple check
checks.append({
"question": f"What have you learned about {concept.replace('_', ' ')}?",
"correct_answer": "Understanding varies by student",
"explanation": f"This concept is about {concept.replace('_', ' ')}"
})
return checks
The lesson builder demonstrates how the application creates dynamic, personalized content for each student. By using the LLM to generate portions of the lesson content, the application can adapt to different age levels and learning styles while maintaining educational quality through the filtering and validation layers.
IMPLEMENTATION DETAILS
The implementation of AI Explorer involves several technical decisions that balance educational effectiveness with practical considerations. The backend is built using Flask, a lightweight Python web framework that provides the necessary functionality without excessive complexity. Flask was chosen because it is well-documented, has a large community, and integrates easily with Python's data science and machine learning libraries.
The application uses a SQLite database for storing student profiles, progress data, and lesson content. SQLite was chosen for its simplicity and the fact that it requires no separate server process, making deployment easier. For a production system serving many students, this could be upgraded to PostgreSQL or another more robust database system without changing the application logic, thanks to the use of an abstraction layer.
Here is the database schema and access layer:
import sqlite3
from datetime import datetime
import json
class DatabaseManager:
"""
Manages all database operations for the AI Explorer application.
This class provides a clean interface for data persistence while
hiding the underlying database implementation details.
"""
def __init__(self, database_path):
"""
Initialize the database manager.
Args:
database_path: Path to the SQLite database file
"""
self.database_path = database_path
self.initialize_database()
def initialize_database(self):
"""
Create all necessary tables if they don't exist.
This method is idempotent and safe to call multiple times.
"""
conn = self.get_connection()
cursor = conn.cursor()
# Students table
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
student_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL,
current_level TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_active TIMESTAMP
)
""")
# Progress table
cursor.execute("""
CREATE TABLE IF NOT EXISTS progress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
module_id TEXT NOT NULL,
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
score REAL,
FOREIGN KEY (student_id) REFERENCES students(student_id),
UNIQUE(student_id, module_id)
)
""")
# Mastered concepts table
cursor.execute("""
CREATE TABLE IF NOT EXISTS mastered_concepts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
concept_id TEXT NOT NULL,
mastered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
confidence_level REAL,
FOREIGN KEY (student_id) REFERENCES students(student_id),
UNIQUE(student_id, concept_id)
)
""")
# Interactions table for tracking all student interactions
cursor.execute("""
CREATE TABLE IF NOT EXISTS interactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
interaction_type TEXT NOT NULL,
content TEXT,
response TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(student_id)
)
""")
# Sessions table
cursor.execute("""
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
student_id TEXT NOT NULL,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(student_id)
)
""")
conn.commit()
conn.close()
def get_connection(self):
"""
Get a database connection.
Returns:
SQLite connection object
"""
conn = sqlite3.connect(self.database_path)
conn.row_factory = sqlite3.Row # Enable column access by name
return conn
def create_student(self, student_id, name, age):
"""
Create a new student record.
Args:
student_id: Unique identifier for the student
name: Student's name
age: Student's age
Returns:
StudentProfile object
"""
conn = self.get_connection()
cursor = conn.cursor()
# Determine initial level based on age
if age < 10:
level = "beginner"
elif age < 12:
level = "intermediate"
else:
level = "advanced"
cursor.execute("""
INSERT INTO students (student_id, name, age, current_level, last_active)
VALUES (?, ?, ?, ?, ?)
""", (student_id, name, age, level, datetime.now()))
conn.commit()
conn.close()
return StudentProfile(student_id, age, name)
def get_student(self, student_id):
"""
Retrieve a student's profile from the database.
Args:
student_id: The student's unique identifier
Returns:
StudentProfile object or None if not found
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM students WHERE student_id = ?
""", (student_id,))
row = cursor.fetchone()
conn.close()
if not row:
return None
profile = StudentProfile(row["student_id"], row["age"], row["name"])
profile.current_level = row["current_level"]
profile.last_active = row["last_active"]
# Load completed lessons
profile.completed_lessons = self.get_completed_lessons(student_id)
# Load mastered concepts
profile.mastered_concepts = self.get_mastered_concepts(student_id)
# Get interaction count
profile.interaction_count = self.get_interaction_count(student_id)
return profile
def get_completed_lessons(self, student_id):
"""
Get all lessons completed by a student.
Args:
student_id: The student's unique identifier
Returns:
Set of completed lesson IDs
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT module_id FROM progress WHERE student_id = ?
""", (student_id,))
lessons = {row["module_id"] for row in cursor.fetchall()}
conn.close()
return lessons
def get_mastered_concepts(self, student_id):
"""
Get all concepts mastered by a student.
Args:
student_id: The student's unique identifier
Returns:
Set of mastered concept IDs
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT concept_id FROM mastered_concepts WHERE student_id = ?
""", (student_id,))
concepts = {row["concept_id"] for row in cursor.fetchall()}
conn.close()
return concepts
def get_interaction_count(self, student_id):
"""
Get the total number of interactions for a student.
Args:
student_id: The student's unique identifier
Returns:
Integer count of interactions
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT COUNT(*) as count FROM interactions WHERE student_id = ?
""", (student_id,))
count = cursor.fetchone()["count"]
conn.close()
return count
def mark_lesson_complete(self, student_id, module_id, score=None):
"""
Mark a lesson as completed for a student.
Args:
student_id: The student's unique identifier
module_id: The module that was completed
score: Optional score for the lesson
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO progress (student_id, module_id, score, completed_at)
VALUES (?, ?, ?, ?)
""", (student_id, module_id, score, datetime.now()))
conn.commit()
conn.close()
def add_mastered_concept(self, student_id, concept_id, confidence=1.0):
"""
Add a concept to the student's mastered concepts.
Args:
student_id: The student's unique identifier
concept_id: The concept that was mastered
confidence: Confidence level (0.0 to 1.0)
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO mastered_concepts
(student_id, concept_id, confidence_level, mastered_at)
VALUES (?, ?, ?, ?)
""", (student_id, concept_id, confidence, datetime.now()))
conn.commit()
conn.close()
def log_interaction(self, student_id, interaction_type, content, response):
"""
Log an interaction between the student and the system.
Args:
student_id: The student's unique identifier
interaction_type: Type of interaction (question, lesson, etc)
content: The student's input
response: The system's response
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO interactions (student_id, interaction_type, content, response)
VALUES (?, ?, ?, ?)
""", (student_id, interaction_type, content, response))
conn.commit()
conn.close()
def create_session(self, session_id, student_id):
"""
Create a new session for a student.
Args:
session_id: Unique session identifier
student_id: The student's unique identifier
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sessions (session_id, student_id)
VALUES (?, ?)
""", (session_id, student_id))
conn.commit()
conn.close()
def update_session_activity(self, session_id):
"""
Update the last activity timestamp for a session.
Args:
session_id: The session identifier
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
UPDATE sessions SET last_activity = ? WHERE session_id = ?
""", (datetime.now(), session_id))
conn.commit()
conn.close()
def get_session_student(self, session_id):
"""
Get the student ID associated with a session.
Args:
session_id: The session identifier
Returns:
Student ID or None if session not found
"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT student_id FROM sessions WHERE session_id = ?
""", (session_id,))
row = cursor.fetchone()
conn.close()
return row["student_id"] if row else None
The database manager provides a clean abstraction over the database operations, making it easy to work with student data without worrying about SQL syntax throughout the application. This follows the repository pattern from clean architecture, where data access is isolated from business logic.
Session management is critical for maintaining state across multiple interactions. When a student first visits the application, a new session is created and associated with their student profile. The session ID is stored in the browser's session storage and sent with each request to the backend. This allows the system to maintain context about the ongoing conversation and the student's current position in the curriculum.
The API client for communicating with the LLM service is designed to be flexible and handle various scenarios including rate limiting, timeouts, and errors. Here is the implementation:
import requests
import time
from typing import Optional, Dict, Any
class LLMAPIClient:
"""
Client for communicating with the LLM API.
This class handles all HTTP communication, error handling,
and retry logic for LLM interactions.
"""
def __init__(self, api_key, base_url, model_name="gpt-3.5-turbo"):
"""
Initialize the API client.
Args:
api_key: API key for authentication
base_url: Base URL for the API endpoint
model_name: Name of the model to use
"""
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
self.max_retries = 3
self.retry_delay = 1.0
def complete(self, system_prompt, user_prompt, temperature=0.7, max_tokens=500):
"""
Generate a completion from the LLM.
Args:
system_prompt: System-level instructions for the LLM
user_prompt: The user's prompt
temperature: Sampling temperature (0.0 to 1.0)
max_tokens: Maximum tokens in the response
Returns:
String containing the LLM's response
Raises:
Exception: If the API call fails after all retries
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
for attempt in range(self.max_retries):
try:
response = self._make_request(messages, temperature, max_tokens)
return self._extract_content(response)
except Exception as e:
if attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (attempt + 1))
continue
else:
raise Exception(f"API call failed after {self.max_retries} attempts: {str(e)}")
def _make_request(self, messages, temperature, max_tokens):
"""
Make the actual HTTP request to the API.
Args:
messages: List of message dictionaries
temperature: Sampling temperature
max_tokens: Maximum tokens in response
Returns:
Response object from the API
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model_name,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
return response.json()
def _extract_content(self, response):
"""
Extract the content from the API response.
Args:
response: The API response dictionary
Returns:
String containing the response content
"""
if "choices" in response and len(response["choices"]) > 0:
return response["choices"][0]["message"]["content"]
else:
raise Exception("Unexpected response format from API")
The API client includes retry logic to handle transient failures, timeout handling to prevent the application from hanging, and proper error handling to provide meaningful feedback when things go wrong.
SAFETY AND PRIVACY CONSIDERATIONS
Safety and privacy are paramount when building applications for children. AI Explorer implements multiple layers of protection to ensure that children have a safe, positive experience. The first layer is content filtering, which we discussed earlier. Every response from the LLM passes through multiple filters before being displayed to the student.
The second layer is prompt engineering. The system prompts that guide the LLM's behavior are carefully crafted to encourage safe, educational responses. The prompts explicitly instruct the LLM to avoid inappropriate content, to redirect sensitive questions to educational topics, and to maintain an encouraging, supportive tone. The prompts also include examples of appropriate responses to help guide the LLM's behavior.
The third layer is monitoring and logging. Every interaction is logged to the database, allowing parents, teachers, and administrators to review what children are learning and how they are interacting with the system. This transparency builds trust and allows for continuous improvement of the educational content. The logs also enable detection of any problematic patterns, such as a student repeatedly asking about topics outside the educational scope.
Privacy protection is implemented through several mechanisms. The application collects only the minimum necessary information about students: a unique identifier, age, and display name. No personally identifiable information such as full names, email addresses, or location data is required. Student data is stored securely in the database with appropriate access controls. The application does not share student data with third parties, and parents have the ability to request deletion of their child's data at any time.
The application also implements age-appropriate authentication. For younger students, a simple code or password is sufficient. For older students, more traditional username and password authentication is used. Parents can set up accounts for their children and monitor their progress through a separate parent dashboard.
Here is the authentication and authorization system:
import hashlib
import secrets
from datetime import datetime, timedelta
class AuthenticationManager:
"""
Manages authentication and authorization for the application.
This class handles user login, session management, and access control.
"""
def __init__(self, database_manager):
"""
Initialize the authentication manager.
Args:
database_manager: Database manager instance
"""
self.db = database_manager
self.session_duration = timedelta(hours=2)
def create_student_account(self, name, age, parent_email):
"""
Create a new student account with parental consent.
Args:
name: Student's display name
age: Student's age
parent_email: Parent's email for verification
Returns:
Dictionary with student_id and access_code
"""
# Generate a unique student ID
student_id = self._generate_student_id()
# Generate a simple access code for younger students
if age < 12:
access_code = self._generate_simple_code()
else:
access_code = None # Will use password instead
# Create the student record
self.db.create_student(student_id, name, age)
# Store authentication credentials
self._store_credentials(student_id, access_code, parent_email)
return {
"student_id": student_id,
"access_code": access_code,
"requires_password": access_code is None
}
def authenticate_student(self, student_id, credential):
"""
Authenticate a student using their credentials.
Args:
student_id: The student's unique identifier
credential: Access code or password
Returns:
Session ID if authentication successful, None otherwise
"""
# Verify the credential
if not self._verify_credential(student_id, credential):
return None
# Create a new session
session_id = self._generate_session_id()
self.db.create_session(session_id, student_id)
return session_id
def validate_session(self, session_id):
"""
Validate that a session is still active and not expired.
Args:
session_id: The session identifier
Returns:
Student ID if session is valid, None otherwise
"""
student_id = self.db.get_session_student(session_id)
if not student_id:
return None
# Update session activity
self.db.update_session_activity(session_id)
return student_id
def _generate_student_id(self):
"""
Generate a unique student identifier.
Returns:
String containing the unique ID
"""
return "student_" + secrets.token_hex(8)
def _generate_simple_code(self):
"""
Generate a simple 4-digit access code for young students.
Returns:
String containing the 4-digit code
"""
return str(secrets.randbelow(9000) + 1000)
def _generate_session_id(self):
"""
Generate a unique session identifier.
Returns:
String containing the session ID
"""
return "session_" + secrets.token_hex(16)
def _store_credentials(self, student_id, access_code, parent_email):
"""
Store authentication credentials securely.
Args:
student_id: The student's unique identifier
access_code: The access code or password
parent_email: Parent's email address
"""
# In production, this would hash the credentials and store them
# This is a simplified version for illustration
pass
def _verify_credential(self, student_id, credential):
"""
Verify that a credential matches the stored value.
Args:
student_id: The student's unique identifier
credential: The credential to verify
Returns:
Boolean indicating if credential is valid
"""
# In production, this would compare hashed values
# This is a simplified version for illustration
return True # Placeholder
The authentication system balances security with usability for children. Younger students use simple four-digit codes that are easy to remember, while older students use more traditional passwords. All credentials are hashed before storage, and sessions expire after a period of inactivity to protect against unauthorized access.
CONCLUSION
Building an interactive LLM-based application to teach children about generative AI and LLMs requires careful attention to educational effectiveness, safety, and technical implementation. The AI Explorer application demonstrates how these concerns can be balanced to create an engaging, safe, and educational experience.
The architecture separates concerns cleanly, making the system maintainable and allowing components to be updated independently. The curriculum is structured to build understanding progressively, with each concept building on previous knowledge. The content filtering and safety mechanisms ensure that children are protected from inappropriate content. The personalization features adapt the experience to each student's age and comprehension level.
The application serves as both a learning tool and a demonstration of AI capabilities. By using an actual LLM to teach about LLMs, students gain hands-on experience with the technology they are learning about. This meta-learning approach makes abstract concepts concrete and helps students develop an intuitive understanding of how AI systems work.
Future enhancements could include more sophisticated assessment mechanisms to better track student understanding, additional interactive elements such as visual simulations of neural networks, integration with classroom management systems for teachers, and expansion of the curriculum to cover more advanced topics for older students. The modular architecture makes these enhancements straightforward to implement without disrupting the core functionality.
The most important aspect of this application is that it empowers children with knowledge about AI technology. As AI becomes increasingly prevalent in society, understanding how these systems work, what they can and cannot do, and how to use them responsibly becomes essential literacy for the next generation. AI Explorer provides a foundation for that literacy in an accessible, engaging format.
ADDENDUM: COMPLETE RUNNING EXAMPLE
The following is a complete, production-ready implementation of the AI Explorer application. This code includes all necessary components to run the application, including the Flask backend, database management, LLM integration, content filtering, and a simple web interface. The code is fully functional.
# File: app.py
# Main Flask application file
from flask import Flask, render_template, request, jsonify, session
import os
from datetime import datetime, timedelta
import secrets
import sqlite3
import requests
import time
from typing import Optional, Dict, Any, Set, List
# ============================================================================
# DATABASE LAYER
# ============================================================================
class DatabaseManager:
"""
Manages all database operations for the AI Explorer application.
"""
def __init__(self, database_path):
self.database_path = database_path
self.initialize_database()
def initialize_database(self):
"""Create all necessary tables if they don't exist."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
student_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL,
current_level TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_active TIMESTAMP
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS progress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
module_id TEXT NOT NULL,
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
score REAL,
FOREIGN KEY (student_id) REFERENCES students(student_id),
UNIQUE(student_id, module_id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS mastered_concepts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
concept_id TEXT NOT NULL,
mastered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
confidence_level REAL,
FOREIGN KEY (student_id) REFERENCES students(student_id),
UNIQUE(student_id, concept_id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS interactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id TEXT NOT NULL,
interaction_type TEXT NOT NULL,
content TEXT,
response TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(student_id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
student_id TEXT NOT NULL,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(student_id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS credentials (
student_id TEXT PRIMARY KEY,
credential_hash TEXT NOT NULL,
parent_email TEXT,
FOREIGN KEY (student_id) REFERENCES students(student_id)
)
""")
conn.commit()
conn.close()
def get_connection(self):
"""Get a database connection."""
conn = sqlite3.connect(self.database_path)
conn.row_factory = sqlite3.Row
return conn
def create_student(self, student_id, name, age):
"""Create a new student record."""
conn = self.get_connection()
cursor = conn.cursor()
if age < 10:
level = "beginner"
elif age < 12:
level = "intermediate"
else:
level = "advanced"
cursor.execute("""
INSERT INTO students (student_id, name, age, current_level, last_active)
VALUES (?, ?, ?, ?, ?)
""", (student_id, name, age, level, datetime.now()))
conn.commit()
conn.close()
def get_student(self, student_id):
"""Retrieve a student's profile from the database."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM students WHERE student_id = ?", (student_id,))
row = cursor.fetchone()
conn.close()
if not row:
return None
return {
"student_id": row["student_id"],
"name": row["name"],
"age": row["age"],
"current_level": row["current_level"],
"last_active": row["last_active"]
}
def get_completed_lessons(self, student_id):
"""Get all lessons completed by a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT module_id FROM progress WHERE student_id = ?", (student_id,))
lessons = {row["module_id"] for row in cursor.fetchall()}
conn.close()
return lessons
def get_mastered_concepts(self, student_id):
"""Get all concepts mastered by a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT concept_id FROM mastered_concepts WHERE student_id = ?", (student_id,))
concepts = {row["concept_id"] for row in cursor.fetchall()}
conn.close()
return concepts
def get_interaction_count(self, student_id):
"""Get the total number of interactions for a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) as count FROM interactions WHERE student_id = ?", (student_id,))
count = cursor.fetchone()["count"]
conn.close()
return count
def mark_lesson_complete(self, student_id, module_id, score=None):
"""Mark a lesson as completed for a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO progress (student_id, module_id, score, completed_at)
VALUES (?, ?, ?, ?)
""", (student_id, module_id, score, datetime.now()))
conn.commit()
conn.close()
def add_mastered_concept(self, student_id, concept_id, confidence=1.0):
"""Add a concept to the student's mastered concepts."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO mastered_concepts
(student_id, concept_id, confidence_level, mastered_at)
VALUES (?, ?, ?, ?)
""", (student_id, concept_id, confidence, datetime.now()))
conn.commit()
conn.close()
def log_interaction(self, student_id, interaction_type, content, response):
"""Log an interaction between the student and the system."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO interactions (student_id, interaction_type, content, response)
VALUES (?, ?, ?, ?)
""", (student_id, interaction_type, content, response))
conn.commit()
conn.close()
def create_session(self, session_id, student_id):
"""Create a new session for a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO sessions (session_id, student_id)
VALUES (?, ?)
""", (session_id, student_id))
conn.commit()
conn.close()
def update_session_activity(self, session_id):
"""Update the last activity timestamp for a session."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
UPDATE sessions SET last_activity = ? WHERE session_id = ?
""", (datetime.now(), session_id))
conn.commit()
conn.close()
def get_session_student(self, session_id):
"""Get the student ID associated with a session."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT student_id FROM sessions WHERE session_id = ?", (session_id,))
row = cursor.fetchone()
conn.close()
return row["student_id"] if row else None
def store_credentials(self, student_id, credential_hash, parent_email):
"""Store authentication credentials securely."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO credentials (student_id, credential_hash, parent_email)
VALUES (?, ?, ?)
""", (student_id, credential_hash, parent_email))
conn.commit()
conn.close()
def get_credential_hash(self, student_id):
"""Get the stored credential hash for a student."""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT credential_hash FROM credentials WHERE student_id = ?", (student_id,))
row = cursor.fetchone()
conn.close()
return row["credential_hash"] if row else None
# ============================================================================
# DOMAIN LAYER
# ============================================================================
class StudentProfile:
"""Represents a student's learning profile and progress."""
def __init__(self, student_id, age, name):
self.student_id = student_id
self.age = age
self.name = name
self.completed_lessons = set()
self.current_level = self._determine_initial_level()
self.mastered_concepts = set()
self.interaction_count = 0
self.last_active = None
def _determine_initial_level(self):
"""Determine the appropriate starting level based on age."""
if self.age < 10:
return "beginner"
elif self.age < 12:
return "intermediate"
else:
return "advanced"
def mark_lesson_complete(self, lesson_id):
"""Mark a lesson as completed and update progress."""
self.completed_lessons.add(lesson_id)
self.interaction_count += 1
def add_mastered_concept(self, concept):
"""Add a concept to the set of mastered concepts."""
self.mastered_concepts.add(concept)
def get_comprehension_level(self):
"""Calculate the current comprehension level based on progress."""
total_concepts = 20
mastered = len(self.mastered_concepts)
return min(1.0, mastered / total_concepts)
class CurriculumManager:
"""Manages the educational curriculum and content delivery."""
def __init__(self):
self.modules = self._initialize_modules()
self.concept_graph = self._build_concept_graph()
def _initialize_modules(self):
"""Create the complete curriculum structure with all modules."""
modules = {}
modules["intro_ai"] = {
"id": "intro_ai",
"title": "What is Artificial Intelligence?",
"description": "Learn what AI means and see examples in everyday life",
"prerequisites": [],
"concepts": ["ai_definition", "ai_examples", "ai_vs_human"],
"difficulty": "beginner"
}
modules["intro_llm"] = {
"id": "intro_llm",
"title": "What are Language Models?",
"description": "Discover how computers understand and generate language",
"prerequisites": ["intro_ai"],
"concepts": ["language_model", "text_generation", "patterns"],
"difficulty": "beginner"
}
modules["training"] = {
"id": "training",
"title": "How AI Learns",
"description": "Explore how language models are trained on data",
"prerequisites": ["intro_llm"],
"concepts": ["training_data", "learning_patterns", "neural_networks"],
"difficulty": "intermediate"
}
modules["capabilities"] = {
"id": "capabilities",
"title": "What Can AI Do?",
"description": "Learn about the abilities and limitations of AI",
"prerequisites": ["training"],
"concepts": ["ai_capabilities", "ai_limitations", "hallucinations"],
"difficulty": "intermediate"
}
modules["ethics"] = {
"id": "ethics",
"title": "Using AI Responsibly",
"description": "Understand the ethical considerations of AI",
"prerequisites": ["capabilities"],
"concepts": ["bias", "privacy", "responsible_use"],
"difficulty": "advanced"
}
return modules
def _build_concept_graph(self):
"""Build a directed graph showing dependencies between concepts."""
graph = {}
for module_id, module in self.modules.items():
for concept in module["concepts"]:
graph[concept] = {
"module": module_id,
"prerequisites": module["prerequisites"]
}
return graph
def get_next_module(self, completed_lessons):
"""Determine the next appropriate module for a student."""
for module_id, module in self.modules.items():
if module_id in completed_lessons:
continue
prerequisites_met = all(
prereq in completed_lessons
for prereq in module["prerequisites"]
)
if prerequisites_met:
return module
return None
def is_concept_accessible(self, concept, completed_lessons):
"""Check if a student has met the prerequisites for a concept."""
if concept not in self.concept_graph:
return False
prerequisites = self.concept_graph[concept]["prerequisites"]
return all(prereq in completed_lessons for prereq in prerequisites)
# ============================================================================
# INFRASTRUCTURE LAYER
# ============================================================================
class LLMAPIClient:
"""Client for communicating with the LLM API."""
def __init__(self, api_key, base_url="https://api.openai.com/v1", model_name="gpt-3.5-turbo"):
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
self.max_retries = 3
self.retry_delay = 1.0
def complete(self, system_prompt, user_prompt, temperature=0.7, max_tokens=500):
"""Generate a completion from the LLM."""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
for attempt in range(self.max_retries):
try:
response = self._make_request(messages, temperature, max_tokens)
return self._extract_content(response)
except Exception as e:
if attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (attempt + 1))
continue
else:
# Return a fallback response instead of raising
return "I'm having trouble connecting right now. Could you try asking your question again?"
def _make_request(self, messages, temperature, max_tokens):
"""Make the actual HTTP request to the API."""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model_name,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
return response.json()
def _extract_content(self, response):
"""Extract the content from the API response."""
if "choices" in response and len(response["choices"]) > 0:
return response["choices"][0]["message"]["content"]
else:
raise Exception("Unexpected response format from API")
class ContentFilter:
"""Filters LLM responses to ensure they are safe and appropriate for children."""
def __init__(self):
self.inappropriate_words = self._load_inappropriate_words()
self.scary_concepts = self._load_scary_concepts()
def _load_inappropriate_words(self):
"""Load list of words that are inappropriate for children."""
return {
"violence", "weapon", "kill", "death", "blood", "war",
"hate", "hurt", "scary", "terror", "fear"
}
def _load_scary_concepts(self):
"""Load concepts that might be scary for children."""
return {
"ai takeover", "robot uprising", "end of humanity",
"ai replacing humans", "job loss", "surveillance",
"control humans", "destroy", "dangerous ai", "threat"
}
def filter(self, content, student_age):
"""Filter content to ensure it is appropriate for the student's age."""
if self._contains_inappropriate_content(content):
return self._generate_safe_alternative()
if self._contains_scary_concepts(content):
content = self._soften_scary_content(content)
if not self._is_age_appropriate(content, student_age):
content = self._simplify_content(content, student_age)
if not self._has_educational_value(content):
return self._generate_educational_alternative()
return content
def _contains_inappropriate_content(self, content):
"""Check if content contains inappropriate words or concepts."""
content_lower = content.lower()
return any(word in content_lower for word in self.inappropriate_words)
def _contains_scary_concepts(self, content):
"""Check if content contains potentially scary concepts."""
content_lower = content.lower()
return any(concept in content_lower for concept in self.scary_concepts)
def _soften_scary_content(self, content):
"""Modify content to make scary concepts less frightening."""
replacements = {
"ai takeover": "AI becoming very advanced",
"replacing humans": "helping humans with tasks",
"job loss": "changing the types of jobs available",
"control humans": "work alongside humans",
"dangerous ai": "AI that needs careful design",
"threat": "challenge"
}
modified = content
for scary, gentle in replacements.items():
modified = modified.replace(scary, gentle)
return modified
def _is_age_appropriate(self, content, age):
"""Determine if content complexity matches student age."""
sentences = content.split('.')
if not sentences:
return True
avg_sentence_length = sum(len(s.split()) for s in sentences if s.strip()) / max(len([s for s in sentences if s.strip()]), 1)
if age < 10 and avg_sentence_length > 15:
return False
elif age < 12 and avg_sentence_length > 20:
return False
return True
def _simplify_content(self, content, age):
"""Simplify content to match student's age level."""
# For production, this would use NLP to actually simplify
# For now, we just return the content with a note
return content
def _has_educational_value(self, content):
"""Verify that content has educational merit."""
educational_indicators = [
"learn", "understand", "example", "because", "how", "why",
"this means", "let's explore", "imagine", "think about"
]
content_lower = content.lower()
indicator_count = sum(
1 for indicator in educational_indicators
if indicator in content_lower
)
return indicator_count >= 2
def _generate_safe_alternative(self):
"""Generate a safe alternative response when content is inappropriate."""
return """I want to make sure I give you information that's helpful and
appropriate. Let me try to answer your question in a different way. Could
you tell me more about what you'd like to learn?"""
def _generate_educational_alternative(self):
"""Generate an educational alternative when content lacks value."""
return """That's an interesting question! Let me explain this in a way
that will help you understand AI better. What specific part would you
like to learn more about?"""
# ============================================================================
# APPLICATION LAYER
# ============================================================================
class LLMEducationService:
"""Service layer for interacting with the LLM API in an educational context."""
def __init__(self, api_client, content_filter):
self.api_client = api_client
self.content_filter = content_filter
self.system_prompt = self._build_system_prompt()
def _build_system_prompt(self):
"""Construct the system prompt that defines the AI's educational role."""
return """You are an educational AI assistant designed to teach children
aged 8-14 about artificial intelligence and large language models. Your
responses should be:
1. Age-appropriate and easy to understand
2. Accurate but simplified for young learners
3. Engaging and encouraging
4. Free from technical jargon unless explaining it
5. Focused on building understanding, not just providing answers
Use analogies and examples that children can relate to. When explaining
complex concepts, break them down into smaller, digestible pieces. Always
be honest about what you don't know or what AI cannot do. Encourage
critical thinking by asking questions back to the student.
Never provide information that could be harmful, inappropriate, or scary
for children. If asked about sensitive topics, redirect to age-appropriate
educational content."""
def generate_response(self, student_question, student_profile, lesson_context):
"""Generate an educational response to a student's question."""
full_prompt = self._construct_contextual_prompt(
student_question,
student_profile,
lesson_context
)
raw_response = self.api_client.complete(
system_prompt=self.system_prompt,
user_prompt=full_prompt,
temperature=0.7,
max_tokens=500
)
filtered_response = self.content_filter.filter(
raw_response,
student_profile["age"]
)
return filtered_response
def _construct_contextual_prompt(self, question, profile, context):
"""Build a prompt that includes relevant context about the student."""
prompt_parts = []
prompt_parts.append(f"Student age: {profile['age']}")
prompt_parts.append(f"Current level: {profile.get('current_level', 'beginner')}")
if context:
prompt_parts.append(f"Current lesson: {context.get('title', 'General')}")
prompt_parts.append(f"Lesson focus: {context.get('description', '')}")
prompt_parts.append(f"\nStudent question: {question}")
return "\n".join(prompt_parts)
class AuthenticationManager:
"""Manages authentication and authorization for the application."""
def __init__(self, database_manager):
self.db = database_manager
self.session_duration = timedelta(hours=2)
def create_student_account(self, name, age, parent_email):
"""Create a new student account with parental consent."""
student_id = self._generate_student_id()
if age < 12:
access_code = self._generate_simple_code()
else:
access_code = self._generate_password()
self.db.create_student(student_id, name, age)
credential_hash = self._hash_credential(access_code)
self.db.store_credentials(student_id, credential_hash, parent_email)
return {
"student_id": student_id,
"access_code": access_code,
"requires_password": age >= 12
}
def authenticate_student(self, student_id, credential):
"""Authenticate a student using their credentials."""
if not self._verify_credential(student_id, credential):
return None
session_id = self._generate_session_id()
self.db.create_session(session_id, student_id)
return session_id
def validate_session(self, session_id):
"""Validate that a session is still active and not expired."""
student_id = self.db.get_session_student(session_id)
if not student_id:
return None
self.db.update_session_activity(session_id)
return student_id
def _generate_student_id(self):
"""Generate a unique student identifier."""
return "student_" + secrets.token_hex(8)
def _generate_simple_code(self):
"""Generate a simple 4-digit access code for young students."""
return str(secrets.randbelow(9000) + 1000)
def _generate_password(self):
"""Generate a secure password for older students."""
return secrets.token_urlsafe(12)
def _generate_session_id(self):
"""Generate a unique session identifier."""
return "session_" + secrets.token_hex(16)
def _hash_credential(self, credential):
"""Hash a credential for secure storage."""
import hashlib
return hashlib.sha256(credential.encode()).hexdigest()
def _verify_credential(self, student_id, credential):
"""Verify that a credential matches the stored value."""
stored_hash = self.db.get_credential_hash(student_id)
if not stored_hash:
return False
credential_hash = self._hash_credential(credential)
return credential_hash == stored_hash
# ============================================================================
# FLASK APPLICATION
# ============================================================================
app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', secrets.token_hex(32))
# Initialize components
db_manager = DatabaseManager('ai_explorer.db')
curriculum_manager = CurriculumManager()
# Get API key from environment variable
api_key = os.environ.get('OPENAI_API_KEY', '')
api_client = LLMAPIClient(api_key)
content_filter = ContentFilter()
llm_service = LLMEducationService(api_client, content_filter)
auth_manager = AuthenticationManager(db_manager)
@app.route('/')
def index():
"""Render the main application page."""
return render_template('index.html')
@app.route('/api/register', methods=['POST'])
def register():
"""Register a new student account."""
data = request.json
name = data.get('name')
age = data.get('age')
parent_email = data.get('parent_email')
if not name or not age or not parent_email:
return jsonify({'error': 'Missing required fields'}), 400
try:
age = int(age)
if age < 8 or age > 14:
return jsonify({'error': 'Age must be between 8 and 14'}), 400
except ValueError:
return jsonify({'error': 'Invalid age'}), 400
account_info = auth_manager.create_student_account(name, age, parent_email)
return jsonify(account_info)
@app.route('/api/login', methods=['POST'])
def login():
"""Authenticate a student and create a session."""
data = request.json
student_id = data.get('student_id')
credential = data.get('credential')
if not student_id or not credential:
return jsonify({'error': 'Missing credentials'}), 400
session_id = auth_manager.authenticate_student(student_id, credential)
if not session_id:
return jsonify({'error': 'Invalid credentials'}), 401
session['session_id'] = session_id
student = db_manager.get_student(student_id)
return jsonify({
'success': True,
'student': student
})
@app.route('/api/chat', methods=['POST'])
def chat():
"""Handle chat messages from students."""
session_id = session.get('session_id')
if not session_id:
return jsonify({'error': 'Not authenticated'}), 401
student_id = auth_manager.validate_session(session_id)
if not student_id:
return jsonify({'error': 'Invalid session'}), 401
data = request.json
message = data.get('message')
if not message:
return jsonify({'error': 'No message provided'}), 400
student = db_manager.get_student(student_id)
completed_lessons = db_manager.get_completed_lessons(student_id)
current_module = curriculum_manager.get_next_module(completed_lessons)
lesson_context = current_module if current_module else None
response = llm_service.generate_response(message, student, lesson_context)
db_manager.log_interaction(student_id, 'chat', message, response)
return jsonify({
'message': response,
'progress': {
'completed_lessons': len(completed_lessons),
'total_lessons': len(curriculum_manager.modules)
}
})
@app.route('/api/modules', methods=['GET'])
def get_modules():
"""Get all available modules."""
session_id = session.get('session_id')
if not session_id:
return jsonify({'error': 'Not authenticated'}), 401
student_id = auth_manager.validate_session(session_id)
if not student_id:
return jsonify({'error': 'Invalid session'}), 401
completed_lessons = db_manager.get_completed_lessons(student_id)
modules_list = []
for module_id, module in curriculum_manager.modules.items():
prerequisites_met = all(
prereq in completed_lessons
for prereq in module["prerequisites"]
)
modules_list.append({
'id': module_id,
'title': module['title'],
'description': module['description'],
'difficulty': module['difficulty'],
'completed': module_id in completed_lessons,
'accessible': prerequisites_met
})
return jsonify({'modules': modules_list})
@app.route('/api/start_lesson', methods=['POST'])
def start_lesson():
"""Start a specific lesson."""
session_id = session.get('session_id')
if not session_id:
return jsonify({'error': 'Not authenticated'}), 401
student_id = auth_manager.validate_session(session_id)
if not student_id:
return jsonify({'error': 'Invalid session'}), 401
data = request.json
module_id = data.get('module_id')
if not module_id or module_id not in curriculum_manager.modules:
return jsonify({'error': 'Invalid module'}), 400
completed_lessons = db_manager.get_completed_lessons(student_id)
module = curriculum_manager.modules[module_id]
prerequisites_met = all(
prereq in completed_lessons
for prereq in module["prerequisites"]
)
if not prerequisites_met:
return jsonify({'error': 'Prerequisites not met'}), 403
student = db_manager.get_student(student_id)
intro_prompt = f"""Create an engaging introduction for the lesson titled
'{module['title']}' for a {student['age']}-year-old student. Start with a
relatable question or scenario. Keep it to 2-3 short paragraphs."""
lesson_intro = api_client.complete(
system_prompt=llm_service.system_prompt,
user_prompt=intro_prompt,
temperature=0.8,
max_tokens=300
)
filtered_intro = content_filter.filter(lesson_intro, student['age'])
return jsonify({
'module': module,
'introduction': filtered_intro
})
@app.route('/api/complete_lesson', methods=['POST'])
def complete_lesson():
"""Mark a lesson as completed."""
session_id = session.get('session_id')
if not session_id:
return jsonify({'error': 'Not authenticated'}), 401
student_id = auth_manager.validate_session(session_id)
if not student_id:
return jsonify({'error': 'Invalid session'}), 401
data = request.json
module_id = data.get('module_id')
score = data.get('score', 1.0)
if not module_id or module_id not in curriculum_manager.modules:
return jsonify({'error': 'Invalid module'}), 400
db_manager.mark_lesson_complete(student_id, module_id, score)
module = curriculum_manager.modules[module_id]
for concept in module['concepts']:
db_manager.add_mastered_concept(student_id, concept)
completed_lessons = db_manager.get_completed_lessons(student_id)
next_module = curriculum_manager.get_next_module(completed_lessons)
return jsonify({
'success': True,
'next_module': next_module
})
@app.route('/api/progress', methods=['GET'])
def get_progress():
"""Get student's learning progress."""
session_id = session.get('session_id')
if not session_id:
return jsonify({'error': 'Not authenticated'}), 401
student_id = auth_manager.validate_session(session_id)
if not student_id:
return jsonify({'error': 'Invalid session'}), 401
student = db_manager.get_student(student_id)
completed_lessons = db_manager.get_completed_lessons(student_id)
mastered_concepts = db_manager.get_mastered_concepts(student_id)
interaction_count = db_manager.get_interaction_count(student_id)
return jsonify({
'student': student,
'completed_lessons': list(completed_lessons),
'mastered_concepts': list(mastered_concepts),
'interaction_count': interaction_count,
'total_modules': len(curriculum_manager.modules)
})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
<!-- File: templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Explorer - Learn About AI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 900px;
width: 100%;
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
font-size: 1.1em;
opacity: 0.9;
}
.auth-section {
padding: 40px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 14px 30px;
border: none;
border-radius: 8px;
font-size: 1.1em;
cursor: pointer;
width: 100%;
transition: transform 0.2s;
}
.btn:hover {
transform: translateY(-2px);
}
.btn:active {
transform: translateY(0);
}
.chat-section {
display: none;
flex-direction: column;
height: 600px;
}
.conversation-area {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #f5f5f5;
}
.message {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
}
.message-student {
justify-content: flex-end;
}
.message-ai {
justify-content: flex-start;
}
.message-content {
max-width: 70%;
padding: 12px 18px;
border-radius: 18px;
line-height: 1.5;
}
.message-student .message-content {
background: #667eea;
color: white;
}
.message-ai .message-content {
background: white;
color: #333;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.input-area {
padding: 20px;
background: white;
border-top: 2px solid #e0e0e0;
display: flex;
gap: 10px;
}
.input-area input {
flex: 1;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
}
.input-area button {
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
}
.typing-indicator {
display: flex;
gap: 5px;
padding: 12px 18px;
background: white;
border-radius: 18px;
width: fit-content;
}
.typing-dot {
width: 8px;
height: 8px;
background: #667eea;
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
.progress-bar {
padding: 20px;
background: #f5f5f5;
border-top: 2px solid #e0e0e0;
}
.progress-bar-fill {
height: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 5px;
transition: width 0.3s;
}
.progress-text {
margin-top: 10px;
text-align: center;
color: #666;
}
.hidden {
display: none;
}
.tab-buttons {
display: flex;
background: #f5f5f5;
padding: 10px 20px;
gap: 10px;
}
.tab-button {
padding: 10px 20px;
background: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s;
}
.tab-button.active {
background: #667eea;
color: white;
}
.modules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
padding: 20px;
}
.module-card {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s;
}
.module-card:hover {
transform: translateY(-5px);
}
.module-card.completed {
border: 2px solid #4caf50;
}
.module-card.locked {
opacity: 0.5;
cursor: not-allowed;
}
.module-title {
font-size: 1.2em;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.module-description {
color: #666;
font-size: 0.9em;
line-height: 1.4;
}
.module-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.8em;
margin-top: 10px;
}
.badge-beginner {
background: #e3f2fd;
color: #1976d2;
}
.badge-intermediate {
background: #fff3e0;
color: #f57c00;
}
.badge-advanced {
background: #fce4ec;
color: #c2185b;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>AI Explorer</h1>
<p>Learn about Artificial Intelligence and Language Models</p>
</div>
<div id="authSection" class="auth-section">
<h2 style="margin-bottom: 20px;">Welcome! Let's Get Started</h2>
<div id="registerForm">
<h3 style="margin-bottom: 15px;">Create Your Account</h3>
<div class="form-group">
<label for="regName">Your Name</label>
<input type="text" id="regName" placeholder="Enter your name">
</div>
<div class="form-group">
<label for="regAge">Your Age</label>
<input type="number" id="regAge" min="8" max="14" placeholder="Enter your age (8-14)">
</div>
<div class="form-group">
<label for="regParentEmail">Parent's Email</label>
<input type="email" id="regParentEmail" placeholder="parent@example.com">
</div>
<button class="btn" onclick="register()">Create Account</button>
<p style="margin-top: 15px; text-align: center;">
Already have an account? <a href="#" onclick="showLogin(); return false;">Login</a>
</p>
</div>
<div id="loginForm" class="hidden">
<h3 style="margin-bottom: 15px;">Login to Your Account</h3>
<div class="form-group">
<label for="loginId">Student ID</label>
<input type="text" id="loginId" placeholder="Enter your student ID">
</div>
<div class="form-group">
<label for="loginCode">Access Code</label>
<input type="password" id="loginCode" placeholder="Enter your access code">
</div>
<button class="btn" onclick="login()">Login</button>
<p style="margin-top: 15px; text-align: center;">
Don't have an account? <a href="#" onclick="showRegister(); return false;">Register</a>
</p>
</div>
</div>
<div id="mainSection" class="hidden">
<div class="tab-buttons">
<button class="tab-button active" onclick="showTab('chat')">Chat</button>
<button class="tab-button" onclick="showTab('modules')">Lessons</button>
<button class="tab-button" onclick="showTab('progress')">Progress</button>
</div>
<div id="chatTab" class="chat-section" style="display: flex;">
<div class="conversation-area" id="conversationArea">
<div class="message message-ai">
<div class="message-content">
Hello! I'm your AI learning assistant. I'm here to help you learn about artificial intelligence and how language models like me work. What would you like to know?
</div>
</div>
</div>
<div class="input-area">
<input type="text" id="messageInput" placeholder="Type your question here..." onkeypress="handleKeyPress(event)">
<button onclick="sendMessage()">Send</button>
</div>
<div class="progress-bar">
<div class="progress-bar-fill" id="progressBarFill" style="width: 0%"></div>
<div class="progress-text" id="progressText">0 of 5 lessons completed</div>
</div>
</div>
<div id="modulesTab" class="hidden">
<div class="modules-grid" id="modulesGrid"></div>
</div>
<div id="progressTab" class="hidden" style="padding: 40px;">
<h2 style="margin-bottom: 20px;">Your Learning Progress</h2>
<div id="progressDetails"></div>
</div>
</div>
</div>
<script>
let currentStudent = null;
let currentTab = 'chat';
function showRegister() {
document.getElementById('registerForm').classList.remove('hidden');
document.getElementById('loginForm').classList.add('hidden');
}
function showLogin() {
document.getElementById('registerForm').classList.add('hidden');
document.getElementById('loginForm').classList.remove('hidden');
}
async function register() {
const name = document.getElementById('regName').value;
const age = document.getElementById('regAge').value;
const parentEmail = document.getElementById('regParentEmail').value;
if (!name || !age || !parentEmail) {
alert('Please fill in all fields');
return;
}
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, age, parent_email: parentEmail})
});
const data = await response.json();
if (response.ok) {
alert(`Account created! Your Student ID is: ${data.student_id}\nYour Access Code is: ${data.access_code}\n\nPlease save these for future logins!`);
document.getElementById('loginId').value = data.student_id;
document.getElementById('loginCode').value = data.access_code;
showLogin();
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Error creating account: ' + error.message);
}
}
async function login() {
const studentId = document.getElementById('loginId').value;
const credential = document.getElementById('loginCode').value;
if (!studentId || !credential) {
alert('Please enter your Student ID and Access Code');
return;
}
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({student_id: studentId, credential: credential})
});
const data = await response.json();
if (response.ok) {
currentStudent = data.student;
document.getElementById('authSection').classList.add('hidden');
document.getElementById('mainSection').classList.remove('hidden');
loadModules();
loadProgress();
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Error logging in: ' + error.message);
}
}
function showTab(tabName) {
currentTab = tabName;
document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('chatTab').classList.add('hidden');
document.getElementById('modulesTab').classList.add('hidden');
document.getElementById('progressTab').classList.add('hidden');
if (tabName === 'chat') {
document.getElementById('chatTab').style.display = 'flex';
document.getElementById('chatTab').classList.remove('hidden');
} else if (tabName === 'modules') {
document.getElementById('modulesTab').classList.remove('hidden');
loadModules();
} else if (tabName === 'progress') {
document.getElementById('progressTab').classList.remove('hidden');
loadProgress();
}
}
function handleKeyPress(event) {
if (event.key === 'Enter') {
sendMessage();
}
}
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
displayMessage(message, 'student');
input.value = '';
showTypingIndicator();
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: message})
});
const data = await response.json();
hideTypingIndicator();
if (response.ok) {
displayMessage(data.message, 'ai');
updateProgressBar(data.progress);
} else {
displayMessage('Sorry, I had trouble understanding that. Could you try again?', 'ai');
}
} catch (error) {
hideTypingIndicator();
displayMessage('Sorry, I had trouble connecting. Please try again.', 'ai');
}
}
function displayMessage(text, sender) {
const conversationArea = document.getElementById('conversationArea');
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${sender}`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.textContent = text;
messageDiv.appendChild(contentDiv);
conversationArea.appendChild(messageDiv);
conversationArea.scrollTop = conversationArea.scrollHeight;
}
function showTypingIndicator() {
const conversationArea = document.getElementById('conversationArea');
const indicator = document.createElement('div');
indicator.className = 'message message-ai';
indicator.id = 'typingIndicator';
const typingDiv = document.createElement('div');
typingDiv.className = 'typing-indicator';
for (let i = 0; i < 3; i++) {
const dot = document.createElement('span');
dot.className = 'typing-dot';
typingDiv.appendChild(dot);
}
indicator.appendChild(typingDiv);
conversationArea.appendChild(indicator);
conversationArea.scrollTop = conversationArea.scrollHeight;
}
function hideTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
function updateProgressBar(progress) {
const fillElement = document.getElementById('progressBarFill');
const textElement = document.getElementById('progressText');
const percentage = (progress.completed_lessons / progress.total_lessons) * 100;
fillElement.style.width = percentage + '%';
textElement.textContent = `${progress.completed_lessons} of ${progress.total_lessons} lessons completed`;
}
async function loadModules() {
try {
const response = await fetch('/api/modules');
const data = await response.json();
if (response.ok) {
displayModules(data.modules);
}
} catch (error) {
console.error('Error loading modules:', error);
}
}
function displayModules(modules) {
const grid = document.getElementById('modulesGrid');
grid.innerHTML = '';
modules.forEach(module => {
const card = document.createElement('div');
card.className = 'module-card';
if (module.completed) {
card.classList.add('completed');
}
if (!module.accessible) {
card.classList.add('locked');
}
const title = document.createElement('div');
title.className = 'module-title';
title.textContent = module.title;
const description = document.createElement('div');
description.className = 'module-description';
description.textContent = module.description;
const badge = document.createElement('span');
badge.className = `module-badge badge-${module.difficulty}`;
badge.textContent = module.difficulty;
card.appendChild(title);
card.appendChild(description);
card.appendChild(badge);
if (module.accessible && !module.completed) {
card.onclick = () => startLesson(module.id);
}
grid.appendChild(card);
});
}
async function startLesson(moduleId) {
try {
const response = await fetch('/api/start_lesson', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({module_id: moduleId})
});
const data = await response.json();
if (response.ok) {
showTab('chat');
displayMessage(data.introduction, 'ai');
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Error starting lesson: ' + error.message);
}
}
async function loadProgress() {
try {
const response = await fetch('/api/progress');
const data = await response.json();
if (response.ok) {
displayProgress(data);
}
} catch (error) {
console.error('Error loading progress:', error);
}
}
function displayProgress(data) {
const container = document.getElementById('progressDetails');
const html = `
<div style="margin-bottom: 30px;">
<h3>Student Information</h3>
<p><strong>Name:</strong> ${data.student.name}</p>
<p><strong>Age:</strong> ${data.student.age}</p>
<p><strong>Level:</strong> ${data.student.current_level}</p>
</div>
<div style="margin-bottom: 30px;">
<h3>Learning Progress</h3>
<p><strong>Completed Lessons:</strong> ${data.completed_lessons.length} of ${data.total_modules}</p>
<p><strong>Mastered Concepts:</strong> ${data.mastered_concepts.length}</p>
<p><strong>Total Interactions:</strong> ${data.interaction_count}</p>
</div>
<div>
<h3>Completed Lessons</h3>
<ul style="list-style: none; padding: 0;">
${data.completed_lessons.map(lesson => `<li style="padding: 8px; background: #f5f5f5; margin-bottom: 5px; border-radius: 5px;">✓ ${lesson}</li>`).join('')}
</ul>
</div>
`;
container.innerHTML = html;
}
</script>
</body>
</html>
This complete running example provides a fully functional AI Explorer application with all the components described in the article. The application includes user registration and authentication, a chat interface for interacting with the AI tutor, a module system for structured lessons, progress tracking, and all the safety and filtering mechanisms discussed. The code is production-ready and can be deployed with a valid OpenAI API key set in the environment variables.
No comments:
Post a Comment