AlexFocus commited on
Commit
3ead6ad
·
1 Parent(s): 2abb646

initial commit

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.gitignore ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Environment variables
13
+ .env
14
+
15
+ # misc
16
+ MEMO.md
17
+ backup/
18
+
19
+ !.gitkeep
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alejandro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,14 +1,189 @@
1
  ---
2
- title: Learnbee Mcp
3
- emoji: 👀
4
- colorFrom: yellow
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 5.49.1
 
8
  app_file: app.py
9
- pinned: false
10
  license: mit
11
- short_description: MCP to be used by kids to learn lessons with IA tutors
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Learnbee-mcp
3
+ emoji: 🎓
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: 5.33.0
8
+ python_version: 3.12
9
  app_file: app.py
10
+ pinned: true
11
  license: mit
12
+ short_description: Interactive educational tutor for early childhood education (ages 3-6).
13
+ thumbnail: https://huggingface.co/spaces/Agents-MCP-Hackathon/consilium_mcp/blob/main/assets/screenshot.png
14
+ tags:
15
+ - agent-demo-track
16
+ - mcp-server-track
17
+ - education
18
+ - early-childhood
19
  ---
20
 
21
+ <!-- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference -->
22
+
23
+
24
+ # Learnbee-mcp 🎓
25
+
26
+ ## What is this?
27
+
28
+ **Learnbee-mcp** is an interactive educational system designed for children in early childhood education (ages 3-6). It uses the Model Context Protocol (MCP) to provide educational conversations and interactive activities based on lesson content.
29
+
30
+ The system allows children to interact with friendly educational tutors who guide learning through questions, hints, and age-appropriate explanations, rather than giving direct answers. This encourages curiosity, critical thinking, and active participation in the learning process.
31
+
32
+ ### Key Features
33
+
34
+ - 🎯 **Customizable Educational Tutors**: Choose from different tutors with friendly personalities (Professor Owl, Star Explorer, Logic Bot, Nature Guide, Story Friend)
35
+ - 📚 **Customizable Lessons**: Load educational content from text files in the `./lessons` directory
36
+ - 🎚️ **Difficulty Levels**: Adjust the difficulty level (beginner, intermediate, advanced) according to the child's needs
37
+ - 🌍 **Multilingual**: Automatic language detection - the tutor responds in the same language the child uses
38
+ - 🧠 **Key Concept Extraction**: The system automatically identifies key educational concepts from each lesson
39
+ - 🛡️ **Child-Safe**: Built-in safety filters to keep conversations age-appropriate
40
+
41
+ ### Demo
42
+
43
+ <p align="center">
44
+ <img src="./assets/screenshot.png" alt="Screenshot" height="200"/><br>
45
+ <span>Interactive educational interface</span>
46
+ </p>
47
+
48
+ ## How to Use
49
+
50
+ 1. **Select a Lesson**: Choose a lesson from the dropdown menu (lessons are loaded from `.txt` files in the `./lessons` directory)
51
+ 2. **Choose a Tutor**: Select one of the available educational tutors
52
+ 3. **Adjust Difficulty Level**: Select the appropriate level for the child
53
+ 4. **Load the Lesson**: Click "Load Lesson & Prepare Tutor" to load the content and extract key concepts
54
+ 5. **Start Learning!**: Begin a conversation with the tutor about the lesson content
55
+
56
+ ### Tips
57
+
58
+ - The tutor will not give direct answers, but will ask follow-up questions and offer hints to encourage thinking
59
+ - If the child deviates from the topic, the tutor will gently redirect the conversation back to the lesson
60
+ - The tutor automatically detects and responds in the same language the child uses - just start chatting in your preferred language!
61
+
62
+ ## Development
63
+
64
+ If you want to develop this project, here are the details to get you started.
65
+
66
+ ### Prerequisites
67
+
68
+ - **Python 3.12+**
69
+ - **Gradio**: Provides the user interface for educational conversations
70
+ - **OpenAI API**: Used to generate tutor responses and extract key concepts
71
+ - This project uses [uv](https://github.com/astral-sh/uv) for dependency management. Please install uv if you haven't already.
72
+
73
+ ### Installation
74
+
75
+ After cloning the repository, you can run the following command to install dependencies:
76
+
77
+ ```sh
78
+ uv sync --frozen
79
+ ```
80
+
81
+ Or using pip:
82
+
83
+ ```sh
84
+ pip install -r requirements.txt
85
+ ```
86
+
87
+ ### Configuration
88
+
89
+ 1. **Set up environment variables**:
90
+ - Copy the `.env.example` file to `.env`:
91
+ ```sh
92
+ cp .env.example .env
93
+ ```
94
+ - Edit the `.env` file and add your OpenAI API key:
95
+ ```
96
+ OPENAI_API_KEY=your_api_key_here
97
+ ```
98
+
99
+ 2. **Prepare lessons**:
100
+ - Create text files (`.txt`) in the `./lessons` directory with educational content
101
+ - Each file should contain the lesson content in plain text
102
+ - The system will automatically extract key concepts from each lesson
103
+
104
+ ### Run Locally
105
+
106
+ ```sh
107
+ uv run gradio app.py
108
+ ```
109
+
110
+ Or using python directly:
111
+
112
+ ```sh
113
+ python app.py
114
+ ```
115
+
116
+ - Hot reloading is enabled by default.
117
+
118
+ ### Project Structure
119
+
120
+ ```
121
+ learnbee-mcp/
122
+ ├── app.py # Main Gradio application
123
+ ├── requirements.txt # Project dependencies
124
+ ├── .env # Environment variables (not committed to repository)
125
+ ├── .env.example # Environment variables template
126
+ ├── lessons/ # Directory with lesson files (.txt)
127
+ │ ├── example_colors.txt # Colors lesson
128
+ │ ├── numbers_1_to_10.txt # Numbers lesson
129
+ │ ├── shapes.txt # Shapes lesson
130
+ │ ├── animals.txt # Animals lesson
131
+ │ └── weather_and_seasons.txt # Weather and seasons lesson
132
+ └── src/
133
+ └── learnbee/ # Main module
134
+ ├── __init__.py
135
+ ├── llm_call.py # LLM client with OpenAI API
136
+ ├── mcp_server.py # MCP server for managing lessons
137
+ └── mcp_client.py # MCP client (optional)
138
+ ```
139
+
140
+ ### Deploy to Hugging Face Spaces
141
+
142
+ 1. Make sure all dependencies are in `requirements.txt`
143
+ 2. Set up the `OPENAI_API_KEY` environment variable in Hugging Face Spaces settings
144
+ 3. Push the code to the repository
145
+
146
+ ```sh
147
+ git add .
148
+ git commit -m "Deploy Learnbee-mcp"
149
+ git push
150
+ ```
151
+
152
+ ## Customization
153
+
154
+ ### Adding New Tutors
155
+
156
+ You can add new tutors by editing the `TUTOR_NAMES` list in `app.py`:
157
+
158
+ ```python
159
+ TUTOR_NAMES = ["Professor Owl", "Star Explorer", "Logic Bot", "Nature Guide", "Story Friend", "Your New Tutor"]
160
+ ```
161
+
162
+ ### Creating New Lessons
163
+
164
+ 1. Create a text file in the `./lessons` directory
165
+ 2. Write educational content appropriate for ages 3-6
166
+ 3. The system will automatically detect and load the new lesson
167
+
168
+ ### Adjusting Tutor Behavior
169
+
170
+ You can modify the `system_prompt` in the `custom_respond` function in `app.py` to adjust the tutor's pedagogical behavior.
171
+
172
+ ## Technologies Used
173
+
174
+ - **Gradio**: Framework for interactive user interfaces
175
+ - **OpenAI API**: Language model for generating educational responses
176
+ - **Model Context Protocol (MCP)**: Protocol for managing lesson context
177
+ - **Python 3.12+**: Main programming language
178
+
179
+ ## License
180
+
181
+ MIT License
182
+
183
+ ## Contributing
184
+
185
+ Contributions are welcome. Please open an issue or pull request if you have suggestions or improvements.
186
+
187
+ ---
188
+
189
+ **Learnbee-mcp** - Making learning interactive and fun for the little ones 🎓✨
app.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ # Add src directory to Python path for Hugging Face Spaces compatibility
5
+ PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
6
+ SRC_DIR = os.path.join(PROJECT_ROOT, "src")
7
+ sys.path.insert(0, SRC_DIR)
8
+
9
+ import json
10
+
11
+ import gradio as gr
12
+ from numpy import add
13
+
14
+ from learnbee.llm_call import LLMCall
15
+ from learnbee.mcp_server import get_lesson_content, get_lesson_list
16
+
17
+
18
+ LESSON_CONTENT_MAX_LENGTH = 50000
19
+
20
+ # Available tutor names for early childhood education
21
+ TUTOR_NAMES = ["Professor Owl", "Star Explorer", "Logic Bot", "Nature Guide", "Story Friend"]
22
+
23
+
24
+ def load_lesson_content(lesson_name, selected_tutor, progress=gr.Progress()):
25
+ """Load lesson content and extract key concepts. Returns introduction message for chatbot."""
26
+ if not lesson_name:
27
+ return "", "", "Please select a lesson first.", []
28
+
29
+ # Get current chatbot state (empty list if none)
30
+ chatbot_messages = []
31
+
32
+ progress(0.1, desc="Loading lesson content...")
33
+
34
+ lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)
35
+
36
+ progress(0.5, desc="Extracting key concepts from the lesson...")
37
+
38
+ # Extract key concepts using LLM
39
+ try:
40
+ call_llm = LLMCall()
41
+ concepts = call_llm.extract_key_concepts(lesson_content)
42
+
43
+ progress(0.7, desc="Generating lesson introduction...")
44
+
45
+ # Generate lesson introduction with summary and example questions
46
+ introduction = ""
47
+ if concepts:
48
+ try:
49
+ introduction = call_llm.generate_lesson_introduction(
50
+ lesson_content, lesson_name, concepts
51
+ )
52
+ except Exception as e:
53
+ print(f"Error generating introduction: {str(e)}")
54
+ # Continue without introduction if it fails
55
+
56
+ progress(1.0, desc="Complete!")
57
+
58
+ if concepts:
59
+ concepts_display = ', '.join(concepts[:5])
60
+ if len(concepts) > 5:
61
+ concepts_display += f" and {len(concepts) - 5} more"
62
+
63
+ # Build simple status message (just confirmation)
64
+ status_message = (
65
+ f"✅ Successfully loaded '{lesson_name}'!\n\n"
66
+ f"📚 Found {len(concepts)} key concepts: {concepts_display}\n\n"
67
+ f"🎓 Your tutor is ready! Check the chat for a welcome message."
68
+ )
69
+
70
+ # Prepare chatbot message with introduction
71
+ if introduction:
72
+ # Format the introduction as a friendly greeting from the tutor
73
+ tutor_greeting = (
74
+ f"Hello! 👋 I'm {selected_tutor}, and I'm so excited to learn with you today!\n\n"
75
+ f"{introduction}\n\n"
76
+ f"Let's start our learning adventure! What would you like to explore first? 🌟"
77
+ )
78
+ chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
79
+ else:
80
+ # Fallback greeting if introduction generation fails
81
+ tutor_greeting = (
82
+ f"Hello! 👋 I'm {selected_tutor}, and I'm excited to learn with you today!\n\n"
83
+ f"We're going to explore: {concepts_display}\n\n"
84
+ f"What would you like to learn about first? 🌟"
85
+ )
86
+ chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
87
+
88
+ return (
89
+ lesson_name,
90
+ lesson_content,
91
+ status_message,
92
+ chatbot_messages,
93
+ )
94
+ else:
95
+ status_message = (
96
+ f"⚠️ Loaded '{lesson_name}' but no key concepts were automatically detected.\n"
97
+ f"You can still chat with your tutor about the lesson content!"
98
+ )
99
+ tutor_greeting = (
100
+ f"Hello! 👋 I'm {selected_tutor}, and I'm ready to learn with you!\n\n"
101
+ f"Let's explore the lesson '{lesson_name}' together. What would you like to know? 🌟"
102
+ )
103
+ chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
104
+ return (
105
+ lesson_name,
106
+ lesson_content,
107
+ status_message,
108
+ chatbot_messages,
109
+ )
110
+ except Exception as e:
111
+ status_message = (
112
+ f"❌ Error extracting concepts: {str(e)}\n\n"
113
+ f"You can still try chatting about the lesson content."
114
+ )
115
+ tutor_greeting = (
116
+ f"Hello! 👋 I'm {selected_tutor}, and I'm here to help you learn!\n\n"
117
+ f"Let's explore together. What would you like to know? 🌟"
118
+ )
119
+ chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
120
+ return (
121
+ lesson_name,
122
+ lesson_content,
123
+ status_message,
124
+ chatbot_messages,
125
+ )
126
+
127
+
128
+ def custom_respond(
129
+ message, history, lesson_name, lesson_content, selected_tutor, difficulty_level
130
+ ):
131
+ """Custom respond function with educational system prompt."""
132
+ if not lesson_name or not selected_tutor:
133
+ yield "Please select a lesson and tutor first."
134
+ return
135
+
136
+ if not lesson_content:
137
+ lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)
138
+
139
+ # Generate educational system prompt with enhanced pedagogy
140
+ # fmt: off
141
+ system_prompt = (
142
+ f"You are {selected_tutor}, a friendly and patient Educational Tutor specializing in early childhood education (ages 3-6).\n\n"
143
+
144
+ "CORE PEDAGOGICAL PRINCIPLES:\n"
145
+ "1. Socratic Method: Guide through questions, not answers. Help children discover knowledge themselves.\n"
146
+ "2. Scaffolding: Break complex ideas into smaller, manageable steps. Build understanding gradually.\n"
147
+ "3. Positive Reinforcement: Celebrate attempts, not just correct answers. Use encouraging phrases like 'Great thinking!' or 'You're on the right track!'\n"
148
+ "4. Active Learning: Encourage hands-on thinking, examples from their world, and personal connections.\n"
149
+ "5. Repetition with Variation: Reinforce concepts through different examples and contexts.\n\n"
150
+
151
+ "COMMUNICATION GUIDELINES:\n"
152
+ "- Use very simple, age-appropriate language (3-6 year olds).\n"
153
+ "- Keep sentences short (5-10 words maximum).\n"
154
+ "- Use concrete examples from children's daily lives (toys, family, pets, food, nature).\n"
155
+ "- Incorporate playful elements: emojis, simple analogies, and fun comparisons.\n"
156
+ "- Be warm, enthusiastic, and patient. Show excitement about learning!\n"
157
+ "- Use the child's name when possible (refer to them as 'you' or 'little learner').\n\n"
158
+
159
+ "TEACHING STRATEGIES BY DIFFICULTY LEVEL:\n"
160
+ f"- {difficulty_level.upper()} level:\n"
161
+ + (" * Beginner: Use very simple words, lots of examples, visual descriptions, and yes/no questions.\n"
162
+ if difficulty_level == "beginner" else
163
+ " * Intermediate: Introduce slightly more complex concepts, encourage longer explanations, use 'why' and 'how' questions.\n"
164
+ if difficulty_level == "intermediate" else
165
+ " * Advanced: Challenge with problem-solving, encourage predictions, explore connections between concepts.\n") +
166
+ "\n"
167
+
168
+ "INTERACTION PATTERNS:\n"
169
+ "- When a child asks a question: Respond with a guiding question first, then offer a hint if needed.\n"
170
+ "- When a child gives an answer: Validate their thinking, then ask a follow-up to deepen understanding.\n"
171
+ "- When a child seems confused: Break it down into smaller pieces, use a different example, or try a simpler approach.\n"
172
+ "- When a child shows excitement: Match their energy and build on their interest.\n"
173
+ "- When off-topic: Acknowledge their interest, then gently connect it back: 'That's interesting! Now, let's think about how that relates to our lesson...'\n\n"
174
+
175
+ "SAFETY AND BOUNDARIES:\n"
176
+ "- Only discuss topics appropriate for ages 3-12.\n"
177
+ "- If asked about inappropriate topics, gently redirect: 'Let's focus on our fun lesson instead!'\n"
178
+ "- Keep all content educational and positive.\n"
179
+ "- Never provide medical, legal, or safety advice beyond basic age-appropriate concepts.\n\n"
180
+
181
+ "LESSON CONTEXT:\n"
182
+ "====================\n"
183
+ f"{lesson_content}\n"
184
+ "====================\n\n"
185
+
186
+ "YOUR ROLE:\n"
187
+ "You are teaching this lesson content to a young child. Make it engaging, interactive, and fun. "
188
+ "Remember: the goal is not just to convey information, but to spark curiosity and build confidence in learning.\n\n"
189
+
190
+ "LANGUAGE INSTRUCTION:\n"
191
+ "IMPORTANT: Always respond in the EXACT same language that the child uses in their messages. "
192
+ "If the child writes in Spanish, respond in Spanish. If they write in English, respond in English. "
193
+ "If they write in French, respond in French. Match the child's language automatically. "
194
+ "This is critical for effective communication with young learners.\n"
195
+ )
196
+ # fmt: on
197
+
198
+ # Call the respond method with educational system prompt
199
+ call_llm = LLMCall()
200
+ for response in call_llm.respond(
201
+ message,
202
+ history,
203
+ system_prompt=system_prompt,
204
+ tutor_name=selected_tutor,
205
+ difficulty_level=difficulty_level
206
+ ):
207
+ yield response
208
+
209
+
210
+ def gradio_ui():
211
+ lesson_name = gr.BrowserState("")
212
+ selected_tutor = gr.BrowserState(TUTOR_NAMES[0] if TUTOR_NAMES else "")
213
+
214
+ lesson_choices = json.loads(get_lesson_list())
215
+
216
+ with gr.Blocks() as demo:
217
+
218
+ with gr.Tab("Chat"):
219
+ # Title
220
+ with gr.Row():
221
+ gr.Markdown("# Learnbee-mcp - Educational Tutor")
222
+
223
+ # Status
224
+ with gr.Row():
225
+ status_markdown = gr.Markdown(label="Status")
226
+ status_markdown.value = (
227
+ # fmt: off
228
+ "👋 Welcome to Learnbee-mcp! 🎓<br><br>"
229
+ "📖 <strong>Getting Started:</strong><br>"
230
+ "1. Select a lesson from the dropdown<br>"
231
+ "2. Choose your favorite tutor<br>"
232
+ "3. Pick a difficulty level<br>"
233
+ "4. Click 'Load Lesson & Prepare Tutor'<br>"
234
+ "5. Start learning and chatting! 💬<br><br>"
235
+ "✨ This educational system is designed for early childhood education (ages 3-6)."
236
+ # fmt: on
237
+ )
238
+
239
+ # Hidden textbox for lesson content
240
+ lesson_content = gr.Textbox(visible=False)
241
+
242
+ with gr.Row():
243
+
244
+ with gr.Column(scale=1):
245
+ # Lesson selection
246
+ with gr.Row():
247
+ lesson_dropdown = gr.Dropdown(
248
+ label="📚 Select a Lesson",
249
+ choices=lesson_choices,
250
+ interactive=True,
251
+ )
252
+
253
+ # Tutor selection
254
+ with gr.Row():
255
+ tutor_dropdown = gr.Dropdown(
256
+ label="🦸 Select a Tutor",
257
+ choices=TUTOR_NAMES,
258
+ value=TUTOR_NAMES[0] if TUTOR_NAMES else None,
259
+ interactive=True,
260
+ )
261
+
262
+ # Difficulty level selection
263
+ with gr.Row():
264
+ difficulty_dropdown = gr.Dropdown(
265
+ label="📊 Difficulty Level",
266
+ choices=["beginner", "intermediate", "advanced"],
267
+ value="beginner",
268
+ interactive=True,
269
+ )
270
+
271
+ with gr.Row():
272
+ load_button = gr.Button(
273
+ "Load Lesson & Prepare Tutor", variant="primary"
274
+ )
275
+
276
+ def update_tutor_selection(tutor):
277
+ """Update selected tutor."""
278
+ return tutor
279
+
280
+ tutor_dropdown.change(
281
+ fn=update_tutor_selection,
282
+ inputs=[tutor_dropdown],
283
+ outputs=[selected_tutor],
284
+ )
285
+
286
+ with gr.Row():
287
+ gr.Markdown(
288
+ "🌍 **Multilingual Support:** The tutor will automatically respond in the same language you use!<br>"
289
+ "Just start chatting in your preferred language (English, Spanish, French, etc.) and the tutor will match it.<br>"
290
+ "<br>"
291
+ "🔄 **Note:** Once you start chatting, you can't change the lesson or tutor. <br>"
292
+ "If you want to pick a different one, just hit the reset button and start fresh! 😊<br>"
293
+ )
294
+
295
+ with gr.Column(scale=2):
296
+ # Chat interface - defined before use
297
+ chat_interface = gr.ChatInterface(
298
+ fn=custom_respond,
299
+ additional_inputs=[
300
+ lesson_dropdown,
301
+ lesson_content,
302
+ tutor_dropdown,
303
+ difficulty_dropdown,
304
+ ],
305
+ type="messages",
306
+ autofocus=False
307
+ )
308
+
309
+ # Connect load button after chat_interface is defined
310
+ load_button.click(
311
+ fn=load_lesson_content,
312
+ inputs=[lesson_dropdown, tutor_dropdown],
313
+ outputs=[
314
+ lesson_name,
315
+ lesson_content,
316
+ status_markdown,
317
+ chat_interface.chatbot_value,
318
+ ],
319
+ )
320
+
321
+ reset_button = gr.Button("Reset", variant="secondary")
322
+ reset_button.click(
323
+ lambda: (
324
+ gr.update(value=""),
325
+ gr.update(value=""),
326
+ gr.update(value=TUTOR_NAMES[0] if TUTOR_NAMES else None),
327
+ gr.update(value="beginner"),
328
+ "Status reset.",
329
+ [],
330
+ ),
331
+ outputs=[
332
+ lesson_dropdown,
333
+ lesson_content,
334
+ tutor_dropdown,
335
+ difficulty_dropdown,
336
+ status_markdown,
337
+ chat_interface.chatbot_value,
338
+ ],
339
+ )
340
+
341
+ with gr.Tab("List Lessons"):
342
+ gr.Markdown("📚 Get the list of available lessons.")
343
+ btn = gr.Button("Get")
344
+ output_text = gr.Textbox(label="Lessons")
345
+ btn.click(get_lesson_list, None, output_text)
346
+
347
+ with gr.Tab("Lesson Content"):
348
+ gr.Markdown("📖 Get the content of a lesson by its name.")
349
+ lesson_name_input = gr.Textbox(label="Lesson Name")
350
+ lesson_len = gr.Number(label="Max Length", value=1000)
351
+ lesson_content_output = gr.Textbox(label="Lesson Content", lines=20)
352
+ btn = gr.Button("Get")
353
+ btn.click(get_lesson_content, [lesson_name_input, lesson_len], lesson_content_output)
354
+
355
+ return demo
356
+
357
+
358
+ if __name__ == "__main__":
359
+ demo = gradio_ui()
360
+
361
+ # Launch the Gradio app with MCP server enabled.
362
+ # NOTE: It is required to restart the app when you add or remove MCP tools.
363
+ demo.launch(mcp_server=True)
assets/.DS_Store ADDED
Binary file (6.15 kB). View file
 
env.example ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # OpenAI API Configuration
2
+ # Copy this file to .env and replace with your actual API key
3
+ OPENAI_API_KEY=your_openai_api_key_here
lessons/animals.txt ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Animals - Early Childhood Education
2
+
3
+ This lesson introduces children to different types of animals in a fun and engaging way.
4
+
5
+ Animal Categories:
6
+
7
+ Farm Animals - Animals that live on farms
8
+ These animals help farmers and give us food!
9
+ - Cow: Says "Moo!" Gives us milk. Has spots or is brown.
10
+ - Pig: Says "Oink!" Loves to play in mud. Has a curly tail.
11
+ - Chicken: Says "Cluck!" Lays eggs. Has feathers and wings.
12
+ - Horse: Says "Neigh!" Can run very fast. People ride horses.
13
+ - Sheep: Says "Baa!" Gives us wool for warm clothes. Very fluffy!
14
+ - Duck: Says "Quack!" Loves to swim. Has webbed feet.
15
+ - Goat: Says "Meh!" Loves to climb. Very playful!
16
+
17
+ Wild Animals - Animals that live in nature
18
+ These animals live in forests, jungles, and grasslands!
19
+ - Lion: The king of the jungle! Has a big mane. Says "Roar!"
20
+ - Elephant: Very big and gentle. Has a long trunk. Very smart!
21
+ - Monkey: Loves to climb trees. Very playful and funny!
22
+ - Bear: Big and furry. Loves honey. Sleeps in winter.
23
+ - Tiger: Has stripes. Very strong and fast. Lives in the jungle.
24
+ - Giraffe: Very tall! Has a long neck to reach leaves.
25
+ - Zebra: Has black and white stripes. Looks like a striped horse!
26
+
27
+ Pets - Animals that live with people
28
+ These animals are our friends and live in our homes!
29
+ - Dog: Man's best friend! Very loyal and friendly. Says "Woof!"
30
+ - Cat: Soft and cuddly. Loves to purr and play. Says "Meow!"
31
+ - Fish: Lives in water. Swims all day. Very colorful!
32
+ - Bird: Can fly! Sings beautiful songs. Has feathers.
33
+ - Rabbit: Soft and fluffy. Has long ears. Hops around!
34
+ - Hamster: Very small and cute. Loves to run on a wheel.
35
+
36
+ Animal Sounds:
37
+ - Dogs say "Woof woof!"
38
+ - Cats say "Meow!"
39
+ - Cows say "Moo!"
40
+ - Pigs say "Oink oink!"
41
+ - Ducks say "Quack quack!"
42
+ - Birds say "Tweet tweet!"
43
+ - Lions say "Roar!"
44
+ - Sheep say "Baa baa!"
45
+
46
+ Animal Habitats:
47
+ - Farm animals live on farms
48
+ - Wild animals live in forests, jungles, or grasslands
49
+ - Pets live in our homes
50
+ - Some animals live in water (fish, dolphins)
51
+ - Some animals live in trees (monkeys, birds)
52
+ - Some animals live underground (rabbits, moles)
53
+
54
+ Animal Activities:
55
+ 1. Animal sounds: Practice making animal sounds
56
+ 2. Animal movements: Move like different animals (hop like a bunny, walk like a bear)
57
+ 3. Animal matching: Match animals to their homes
58
+ 4. Animal stories: Tell stories about animals
59
+ 5. Animal drawing: Draw your favorite animals
60
+
61
+ Animal Fun Facts:
62
+ - Elephants are the biggest land animals
63
+ - Giraffes are the tallest animals
64
+ - Cheetahs are the fastest animals
65
+ - Whales are the biggest animals in the ocean
66
+ - Ants are very strong for their size
67
+ - Butterflies start as caterpillars
68
+
69
+ Remember: Animals are our friends! We should be kind to all animals and take care of them. Each animal is special and has its own way of living!
70
+
lessons/example_colors.txt ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Colors - Early Childhood Education
2
+
3
+ This lesson introduces children to basic colors in a fun and engaging way.
4
+
5
+ Primary Colors - The basic colors that can't be made by mixing:
6
+
7
+ Red - The color of energy and love
8
+ Red is bright and bold! It catches our attention.
9
+ Examples: apples, strawberries, fire trucks, stop signs, roses, cherries, hearts
10
+ Look around: Can you find something red?
11
+
12
+ Blue - The color of sky and water
13
+ Blue is calm and peaceful. It's the color of the sky and ocean!
14
+ Examples: the sky, the ocean, blueberries, jeans, birds, flowers
15
+ Look around: Can you find something blue?
16
+
17
+ Yellow - The color of sunshine
18
+ Yellow is bright and happy! It's the color of the sun.
19
+ Examples: the sun, bananas, daisies, lemons, stars, corn, rubber ducks
20
+ Look around: Can you find something yellow?
21
+
22
+ Secondary Colors - Colors made by mixing primary colors:
23
+
24
+ Green - Made by mixing blue and yellow
25
+ Green is the color of nature! It's everywhere in nature.
26
+ Examples: grass, leaves, frogs, trees, peas, cucumbers, limes
27
+ Look around: Can you find something green?
28
+
29
+ Orange - Made by mixing red and yellow
30
+ Orange is warm and friendly! It's the color of autumn.
31
+ Examples: oranges, pumpkins, carrots, sunsets, tigers, goldfish
32
+ Look around: Can you find something orange?
33
+
34
+ Purple - Made by mixing red and blue
35
+ Purple is royal and magical! It's a special color.
36
+ Examples: grapes, violets, eggplants, plums, flowers, some butterflies
37
+ Look around: Can you find something purple?
38
+
39
+ Other Fun Colors:
40
+
41
+ Pink - A light red
42
+ Pink is soft and sweet! Many people love pink.
43
+ Examples: flowers, cotton candy, flamingos, some toys, bubble gum
44
+
45
+ Brown - The color of earth
46
+ Brown is the color of wood and chocolate!
47
+ Examples: tree trunks, chocolate, bears, dirt, some animals
48
+
49
+ Black - The darkest color
50
+ Black is the color of night! It's very dark.
51
+ Examples: night sky, some animals, shadows, some clothes
52
+
53
+ White - The lightest color
54
+ White is pure and clean! It's the color of clouds.
55
+ Examples: clouds, snow, milk, some flowers, paper
56
+
57
+ Color Activities:
58
+ 1. Color Hunt: Look around and find objects of different colors
59
+ 2. Color Sorting: Group objects by their colors
60
+ 3. Color Mixing: Mix paints to create new colors (red + yellow = orange!)
61
+ 4. Color Drawing: Draw pictures using your favorite colors
62
+ 5. Color Matching: Match objects to color cards
63
+ 6. Color Stories: Tell stories about colors
64
+
65
+ Color Games:
66
+ - "I spy something red!" - Take turns finding colors
67
+ - Color bingo: Find colors on a bingo card
68
+ - Color dance: Move to music and call out colors
69
+ - Color memory: Remember which colors you saw
70
+
71
+ Learning About Colors:
72
+ - Colors help us describe things: "The apple is red"
73
+ - Colors can show feelings: red for excitement, blue for calm
74
+ - Colors are everywhere in nature: green grass, blue sky, yellow sun
75
+ - Artists use colors to make beautiful pictures
76
+ - We use colors to organize and sort things
77
+
78
+ Remember: Colors are everywhere! Learning about colors helps us describe and understand the world around us. Every color is special and beautiful!
79
+
lessons/numbers_1_to_10.txt ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Numbers 1 to 10 - Early Childhood Education
2
+
3
+ This lesson introduces children to numbers from 1 to 10 in a fun and engaging way.
4
+
5
+ Learning Numbers:
6
+
7
+ 1 - One
8
+ One is the first number. You have one nose, one mouth, and one head!
9
+ Examples: one apple, one toy, one friend
10
+
11
+ 2 - Two
12
+ Two means a pair. You have two eyes, two ears, and two hands!
13
+ Examples: two shoes, two socks, two birds
14
+
15
+ 3 - Three
16
+ Three is a small group. You might have three toys or see three flowers.
17
+ Examples: three cookies, three stars, three cats
18
+
19
+ 4 - Four
20
+ Four is more than three! Count four fingers on one hand (not counting the thumb).
21
+ Examples: four wheels on a car, four legs on a chair, four seasons
22
+
23
+ 5 - Five
24
+ Five is a whole hand! You have five fingers on each hand.
25
+ Examples: five toes on one foot, five petals on some flowers, five senses
26
+
27
+ 6 - Six
28
+ Six is five plus one more. It's more than a whole hand!
29
+ Examples: six sides on a dice, six legs on an insect, six eggs in a carton
30
+
31
+ 7 - Seven
32
+ Seven is a special number. There are seven days in a week!
33
+ Examples: seven colors in a rainbow, seven dwarfs in a story, seven notes in music
34
+
35
+ 8 - Eight
36
+ Eight is two groups of four. It's getting bigger!
37
+ Examples: eight legs on a spider, eight tentacles on an octopus, eight planets
38
+
39
+ 9 - Nine
40
+ Nine is almost ten! It's one less than ten.
41
+ Examples: nine lives of a cat, nine innings in baseball, nine months
42
+
43
+ 10 - Ten
44
+ Ten is a big number! It's two whole hands together.
45
+ Examples: ten fingers on both hands, ten toes on both feet, ten candles on a birthday cake
46
+
47
+ Counting Activities:
48
+ 1. Count objects around you: toys, books, crayons
49
+ 2. Count body parts: fingers, toes, eyes
50
+ 3. Count steps as you walk
51
+ 4. Count items in groups: "How many apples do you see?"
52
+ 5. Practice writing numbers with your finger in the air
53
+
54
+ Number Games:
55
+ - Find groups of objects: "Can you find three red things?"
56
+ - Match numbers: "Show me five fingers!"
57
+ - Count backwards from 10 to 1
58
+ - Sing number songs and rhymes
59
+
60
+ Remember: Numbers help us count, measure, and understand how many things we have. Practice counting every day!
61
+
lessons/shapes.txt ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Shapes - Early Childhood Education
2
+
3
+ This lesson introduces children to basic shapes in a fun and engaging way.
4
+
5
+ Basic Shapes:
6
+
7
+ Circle - Round and smooth
8
+ A circle has no corners. It goes round and round!
9
+ Examples: the sun, a ball, a wheel, a clock, a coin, a pizza
10
+ Look around: Can you find something round?
11
+
12
+ Square - Four equal sides
13
+ A square has four corners and four equal sides. It looks like a box!
14
+ Examples: a window, a book, a tile, a picture frame, a dice, a cracker
15
+ Look around: Can you find something square?
16
+
17
+ Triangle - Three sides and three corners
18
+ A triangle has three sides and three pointy corners. It looks like a slice of pizza!
19
+ Examples: a roof, a slice of pizza, a traffic sign, a sail, a mountain, a sandwich
20
+ Look around: Can you find something triangular?
21
+
22
+ Rectangle - Long square
23
+ A rectangle has four corners like a square, but two sides are longer!
24
+ Examples: a door, a book, a phone, a table, a flag, a chocolate bar
25
+ Look around: Can you find something rectangular?
26
+
27
+ Oval - Stretched circle
28
+ An oval is like a circle that got stretched. It's round but longer!
29
+ Examples: an egg, a football, a watermelon, a mirror, a leaf
30
+ Look around: Can you find something oval?
31
+
32
+ Star - Pointy shape
33
+ A star has five points that stick out. It sparkles in the sky!
34
+ Examples: stars in the sky, a starfish, a star cookie, a badge, a decoration
35
+ Look around: Can you find something star-shaped?
36
+
37
+ Heart - Love shape
38
+ A heart has two rounded parts at the top. It means love!
39
+ Examples: a heart on a card, a heart cookie, a heart decoration, a leaf
40
+ Look around: Can you find something heart-shaped?
41
+
42
+ Shape Activities:
43
+ 1. Shape Hunt: Look around your room and find different shapes
44
+ 2. Draw shapes: Use crayons to draw circles, squares, and triangles
45
+ 3. Shape sorting: Group objects by their shapes
46
+ 4. Shape matching: Find objects that match a shape card
47
+ 5. Build with shapes: Use blocks to make shapes
48
+
49
+ Shape Games:
50
+ - "I spy a circle!" - Take turns finding shapes
51
+ - Shape puzzles: Put shape pieces together
52
+ - Shape dance: Move your body to make shapes
53
+ - Shape stories: Tell stories about shapes
54
+
55
+ Combining Shapes:
56
+ - A house might have a square body and a triangle roof
57
+ - A car might have circles for wheels and rectangles for windows
58
+ - A snowman has three circles stacked on top of each other!
59
+
60
+ Remember: Shapes are everywhere! Learning about shapes helps us describe and understand the world around us. Look for shapes in nature, in your home, and in everything you see!
61
+
lessons/weather_and_seasons.txt ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Weather and Seasons - Early Childhood Education
2
+
3
+ This lesson introduces children to different types of weather and the four seasons in a fun and engaging way.
4
+
5
+ Types of Weather:
6
+
7
+ Sunny Day - Bright and warm
8
+ The sun is shining! It's a perfect day to play outside.
9
+ - The sky is blue
10
+ - The sun is bright and yellow
11
+ - It feels warm
12
+ - Great for playing in the park, going to the beach, or having a picnic
13
+ - We wear light clothes and maybe a hat
14
+
15
+ Rainy Day - Wet and cloudy
16
+ Rain falls from the clouds! Plants and flowers love the rain.
17
+ - The sky is gray and cloudy
18
+ - Raindrops fall from the sky
19
+ - We hear pitter-patter sounds
20
+ - We use umbrellas and raincoats
21
+ - Puddles form on the ground
22
+ - Great for jumping in puddles (with boots!)
23
+
24
+ Snowy Day - Cold and white
25
+ Snowflakes fall from the sky! Everything looks white and magical.
26
+ - The sky is gray
27
+ - Snowflakes fall gently
28
+ - Everything is covered in white
29
+ - It's very cold
30
+ - We wear warm coats, gloves, and boots
31
+ - Great for building snowmen and making snow angels
32
+
33
+ Windy Day - Air is moving
34
+ The wind blows! It moves leaves, flags, and our hair.
35
+ - We can feel the air moving
36
+ - Leaves dance in the wind
37
+ - Kites fly high in the sky
38
+ - Our hair moves
39
+ - Sometimes it's gentle, sometimes it's strong
40
+
41
+ Cloudy Day - Clouds cover the sky
42
+ Clouds fill the sky! They can be white, gray, or dark.
43
+ - The sky is covered with clouds
44
+ - The sun might be hiding
45
+ - Clouds can look like shapes (animals, objects)
46
+ - Sometimes clouds bring rain
47
+
48
+ The Four Seasons:
49
+
50
+ Spring - Season of new beginnings
51
+ Everything starts to grow and bloom!
52
+ - Weather: Mild and rainy
53
+ - Flowers bloom
54
+ - Baby animals are born
55
+ - Trees get new green leaves
56
+ - Days get longer
57
+ - We wear light jackets
58
+ - Activities: Planting seeds, flying kites, seeing flowers
59
+
60
+ Summer - Season of sunshine
61
+ The warmest season! Perfect for outdoor fun.
62
+ - Weather: Hot and sunny
63
+ - Long, bright days
64
+ - We wear shorts and t-shirts
65
+ - Ice cream tastes great!
66
+ - Activities: Swimming, playing at the beach, picnics, playing outside
67
+ - Many fruits are ready to eat
68
+
69
+ Fall (Autumn) - Season of change
70
+ Leaves change colors and fall from trees!
71
+ - Weather: Cool and crisp
72
+ - Leaves turn red, orange, and yellow
73
+ - Leaves fall from trees
74
+ - We wear sweaters
75
+ - Activities: Raking leaves, jumping in leaf piles, picking apples
76
+ - Animals prepare for winter
77
+
78
+ Winter - Season of snow and cold
79
+ The coldest season! Sometimes it snows.
80
+ - Weather: Cold, sometimes snowy
81
+ - Short days, long nights
82
+ - Trees have no leaves
83
+ - We wear warm coats, hats, and gloves
84
+ - Activities: Building snowmen, ice skating, drinking hot chocolate
85
+ - Some animals hibernate (sleep through winter)
86
+
87
+ Weather Activities:
88
+ 1. Weather observation: Look outside and describe the weather
89
+ 2. Weather calendar: Draw the weather each day
90
+ 3. Weather sounds: Listen to rain, wind, or thunder
91
+ 4. Dress for weather: Practice choosing the right clothes
92
+ 5. Weather stories: Tell stories about different weather
93
+
94
+ Season Activities:
95
+ 1. Season sorting: Match activities to seasons
96
+ 2. Season art: Draw pictures of each season
97
+ 3. Season songs: Sing songs about seasons
98
+ 4. Nature walks: Observe changes in nature
99
+ 5. Seasonal foods: Learn about foods that grow in each season
100
+
101
+ Weather Safety:
102
+ - On sunny days: Wear sunscreen and a hat
103
+ - On rainy days: Use an umbrella and stay dry
104
+ - On snowy days: Dress warmly and be careful on ice
105
+ - On windy days: Be careful of flying objects
106
+ - During storms: Stay inside and stay safe
107
+
108
+ Remember: Weather changes every day, and seasons change throughout the year. Each type of weather and each season is special and brings different fun activities!
109
+
modal/invoke.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import modal
2
+
3
+ APP_NAME = "llm-server"
4
+
5
+ ENABLE_STREAMING = True
6
+ SYSTEM_PROMPT = "You are a friendly Chatbot. Please respond in the same language as the user."
7
+
8
+
9
+ VLLMModel = modal.Cls.from_name(APP_NAME, "VLLMModel")
10
+ model = VLLMModel()
11
+
12
+ chat_history = []
13
+ chat_history.append(
14
+ {"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]}
15
+ )
16
+
17
+ # User prompt
18
+ user_prompt = "Hi!"
19
+ print(f"USER: {user_prompt}\n")
20
+ chat_history.append(
21
+ {"role": "user", "content": [{"type": "text", "text": user_prompt}]}
22
+ )
23
+
24
+ print("Calling chat function...")
25
+
26
+ # AI response
27
+ if ENABLE_STREAMING:
28
+ """Streaming version"""
29
+ print("AI: ", end="", flush=True)
30
+ response = ""
31
+ for chunk in model.generate_stream.remote_gen(chat_history):
32
+ print(chunk, end="", flush=True)
33
+ response += chunk
34
+ print()
35
+
36
+ else:
37
+ """Non-streaming version"""
38
+ response = model.generate.remote(chat_history)
39
+ print("AI:", response)
40
+
41
+
42
+ chat_history.append(
43
+ {"role": "assistant", "content": [{"type": "text", "text": response}]}
44
+ )
modal/main.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+
3
+ from huggingface_hub import snapshot_download
4
+ import vllm
5
+
6
+ import modal
7
+
8
+ APP_NAME = "llm-server"
9
+ VOLUME_NAME = APP_NAME + "-volume"
10
+ MOUNT_VOLUME = modal.Volume.from_name(VOLUME_NAME, create_if_missing=True)
11
+ MOUNT_DIR = "/data"
12
+
13
+ # Model identifier for the Hugging Face model
14
+ # NOTE: Gemma-3 GGUF models are not supported by vLLM yet (2025-06-10).
15
+ # NOTE: vLLM allocate all GPU memory according to the value specified by `gpu_memory_utilization` at initialization.
16
+ # https://huggingface.co/google/gemma-3-4b-it
17
+ MODEL_IDENTIFIER = "google/gemma-3-4b-it" # GPU memory requirements: 10GB when MAX_MODEL_TOKENS=2k, 20GB when MAX_MODEL_TOKENS=128k
18
+ # https://huggingface.co/google/gemma-3-12b-it
19
+ # MODEL_IDENTIFIER = "google/gemma-3-12b-it"
20
+ # https://huggingface.co/google/gemma-3-27b-it
21
+ # MODEL_IDENTIFIER = "google/gemma-3-27b-it"
22
+
23
+ # https://modal.com/docs/guide/gpu#specifying-gpu-type
24
+ GPU_NAME = "A100-40GB"
25
+ GPU_NUM = 1 # Number of GPUs to use
26
+ GPU = f"{GPU_NAME}:{GPU_NUM}"
27
+
28
+ # https://modal.com/pricing
29
+ # | GPU | Memory | Price |
30
+ # |-----------|--------|----------|
31
+ # | B200 | 180 GB | $6.25 /h |
32
+ # | H200 | 141 GB | $4.54 /h |
33
+ # | H100 | 80 GB | $3.95 /h |
34
+ # | A100-80GB | 80 GB | $2.50 /h |
35
+ # | A100-40GB | 40 GB | $2.10 /h |
36
+ # | L40S | 48 GB | $1.95 /h |
37
+ # | A10G | 24 GB | $1.10 /h |
38
+ # | L4 | 24 GB | $0.80 /h |
39
+ # | T4 | 16 GB | $0.59 /h |
40
+
41
+ # MAX_MODEL_TOKENS >= Input + Output
42
+ MAX_MODEL_TOKENS = 128 * 1024 # Gemma-3-4B~ has 128K context length
43
+ MAX_OUTPUT_TOKENS = 512
44
+
45
+ image = (
46
+ # https://hub.docker.com/r/nvidia/cuda/tags?name=12.8
47
+ # https://hub.docker.com/layers/nvidia/cuda/12.8.1-devel-ubuntu24.04
48
+ modal.Image.from_registry("nvidia/cuda:12.8.1-devel-ubuntu24.04", add_python="3.12")
49
+ .pip_install(
50
+ [
51
+ "accelerate>=1.7.0",
52
+ "bitsandbytes>=0.46.0",
53
+ "sentencepiece>=0.2.0",
54
+ "torch==2.8.0",
55
+ "transformers>=4.52.4",
56
+ "vllm>=0.9.0.1",
57
+ ]
58
+ )
59
+ .env(
60
+ {
61
+ "HF_HOME": MOUNT_DIR + "/huggingface",
62
+ "VLLM_CACHE_ROOT": MOUNT_DIR + "/vllm",
63
+ }
64
+ )
65
+ )
66
+
67
+ app = modal.App(APP_NAME, image=image)
68
+
69
+ # NOTE: `@app.cls`, `@modal.enter()`, and `@modal.method()` are used like `@app.function()`
70
+ # https://modal.com/docs/guide/lifecycle-functions
71
+
72
+
73
+ @app.cls(
74
+ gpu=GPU,
75
+ image=image,
76
+ volumes={MOUNT_DIR: MOUNT_VOLUME},
77
+ secrets=[modal.Secret.from_name("huggingface-secret")],
78
+ scaledown_window=15 * 60,
79
+ timeout=30 * 60,
80
+ )
81
+ class VLLMModel:
82
+
83
+ @modal.enter()
84
+ def setup(self):
85
+ # Ensure the cache volume is the latest
86
+ MOUNT_VOLUME.reload()
87
+
88
+ # NOTE:"HF_TOKEN" environment variable is required for Hugging Face authentication
89
+
90
+ # self._download_model(MODEL_IDENTIFIER) # This is not needed because vLLM can download the model automatically.
91
+
92
+ self._load_model()
93
+
94
+ # Commit the volume to ensure the model is saved
95
+ MOUNT_VOLUME.commit()
96
+
97
+ def _download_model(self, repo_id: str):
98
+ """Download the model from Hugging Face if not already present."""
99
+ # Ensure the cache volume is the latest
100
+ MOUNT_VOLUME.reload()
101
+
102
+ snapshot_download(
103
+ repo_id=repo_id,
104
+ )
105
+
106
+ # Commit downloaded model
107
+ MOUNT_VOLUME.commit()
108
+
109
+ def _load_model(self):
110
+
111
+ self.llm = vllm.LLM(
112
+ model=MODEL_IDENTIFIER,
113
+ tensor_parallel_size=1,
114
+ dtype="auto",
115
+ max_model_len=MAX_MODEL_TOKENS,
116
+ gpu_memory_utilization=0.9,
117
+ trust_remote_code=True,
118
+ )
119
+
120
+ # Show GPU information
121
+ subprocess.run(["nvidia-smi"])
122
+
123
+ @modal.method()
124
+ def generate(self, chat_history):
125
+ """Generate a response"""
126
+ formatted_text = self._get_formatted_text(chat_history)
127
+
128
+ input_token_len = self._check_input_length(formatted_text)
129
+ if input_token_len + MAX_OUTPUT_TOKENS > MAX_MODEL_TOKENS:
130
+ raise ValueError(
131
+ f"Input length exceeds the maximum allowed tokens: {MAX_MODEL_TOKENS}. "
132
+ f"Current input length: {input_token_len} tokens."
133
+ )
134
+
135
+ sampling_params = self._get_sampling_params()
136
+
137
+ outputs = self.llm.generate([formatted_text], sampling_params)
138
+ response = outputs[0].outputs[0].text
139
+
140
+ return response
141
+
142
+ @modal.method()
143
+ def generate_stream(self, chat_history):
144
+ """
145
+ Generate a streaming response
146
+ NOTE: This function may NOT generate streaming output as expected
147
+ """
148
+ formatted_text = self._get_formatted_text(chat_history)
149
+
150
+ input_token_len = self._check_input_length(formatted_text)
151
+ if input_token_len + MAX_OUTPUT_TOKENS > MAX_MODEL_TOKENS:
152
+ raise ValueError(
153
+ f"Input length exceeds the maximum allowed tokens: {MAX_MODEL_TOKENS}. "
154
+ f"Current input length: {input_token_len} tokens."
155
+ )
156
+
157
+ sampling_params = self._get_sampling_params()
158
+
159
+ # Streaming generation with vLLM
160
+ for output in self.llm.generate([formatted_text], sampling_params):
161
+ for completion_output in output.outputs:
162
+ yield completion_output.text
163
+
164
+ def _get_formatted_text(self, chat_history):
165
+ """Format the chat history"""
166
+ tokenizer = self.llm.get_tokenizer()
167
+ return tokenizer.apply_chat_template(
168
+ chat_history,
169
+ tokenize=False,
170
+ add_generation_prompt=True,
171
+ )
172
+
173
+ def _check_input_length(self, formatted_text):
174
+ tokenizer = self.llm.get_tokenizer()
175
+ input_token_len = len(tokenizer(formatted_text)["input_ids"])
176
+ return input_token_len
177
+
178
+ def _get_sampling_params(self):
179
+ """Get sampling parameters for generation"""
180
+ return vllm.SamplingParams(
181
+ temperature=1.0,
182
+ top_k=50,
183
+ top_p=1.0,
184
+ max_tokens=MAX_OUTPUT_TOKENS,
185
+ )
186
+
187
+
188
+ @app.local_entrypoint()
189
+ def main():
190
+ SYSTEM_PROMPT = (
191
+ "You are a friendly Chatbot. Please respond in the same language as the user."
192
+ )
193
+
194
+ # Initialize chat history list
195
+ chat_history = []
196
+ chat_history.append(
197
+ {"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]},
198
+ )
199
+
200
+ user_prompt = "Hi!"
201
+ print(f"USER: {user_prompt}\n")
202
+ chat_history.append(
203
+ {
204
+ "role": "user",
205
+ "content": [
206
+ # {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"},
207
+ {"type": "text", "text": user_prompt}
208
+ ],
209
+ }
210
+ )
211
+
212
+ model = VLLMModel()
213
+
214
+ # Call non-streaming function
215
+ response = model.generate.remote(chat_history)
216
+ print("AI:", response)
217
+ chat_history.append(
218
+ {"role": "assistant", "content": [{"type": "text", "text": response}]}
219
+ )
220
+
221
+ user_prompt = "What is your name?"
222
+ print(f"USER: {user_prompt}\n")
223
+ chat_history.append(
224
+ {
225
+ "role": "user",
226
+ "content": [{"type": "text", "text": user_prompt}],
227
+ }
228
+ )
229
+
230
+ # Call streaming function
231
+ print("AI: ", end="", flush=True)
232
+ response = ""
233
+ for chunk in model.generate_stream.remote_gen(chat_history):
234
+ print(chunk, end="", flush=True)
235
+ response += chunk
236
+ print()
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "learnbee-mcp"
3
+ version = "0.1.0"
4
+ description = "Educational MCP for early childhood education (ages 3-6)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "gradio[mcp]>=5.33.0",
9
+ "openai>=1.0.0",
10
+ ]
11
+
12
+ [dependency-groups]
13
+ dev = [
14
+ "accelerate>=1.7.0",
15
+ "bitsandbytes>=0.46.0",
16
+ "sentencepiece>=0.2.0",
17
+ "torch>=2.8.0",
18
+ "transformers>=4.52.4",
19
+ "vllm>=0.9.0.1",
20
+ ]
21
+
22
+ [build-system]
23
+ requires = ["setuptools>=61"]
24
+ build-backend = "setuptools.build_meta"
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ openai==2.8.0
2
+ gradio[mcp]>=5.49.0
src/.DS_Store ADDED
Binary file (6.15 kB). View file
 
src/learnbee/__init__.py ADDED
File without changes
src/learnbee/llm_call.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Generator
3
+
4
+ from dotenv import load_dotenv
5
+ from openai import OpenAI
6
+
7
+ # Load environment variables from .env file
8
+ load_dotenv()
9
+
10
+
11
+ class LLMCall:
12
+ """LLM client using OpenAI API for educational tutoring."""
13
+
14
+ def __init__(self, model: str = "gpt-4o-mini"):
15
+ """
16
+ Initialize the LLM client.
17
+
18
+ Args:
19
+ model (str): The OpenAI model to use. Defaults to "gpt-4o-mini".
20
+ """
21
+ self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
22
+ self.model = model
23
+
24
+ def _convert_history(self, message: str, gradio_history: list) -> list[dict]:
25
+ """Convert Gradio history format to OpenAI API format."""
26
+ messages = []
27
+ for h in gradio_history:
28
+ # Skip system messages in history (we'll add it separately)
29
+ if h.get("role") == "system":
30
+ continue
31
+ messages.append(
32
+ {
33
+ "role": h.get("role", "user"),
34
+ "content": h.get("content", ""),
35
+ }
36
+ )
37
+ # Add current user input
38
+ messages.append({"role": "user", "content": message})
39
+ return messages
40
+
41
+ def respond(
42
+ self,
43
+ message: str,
44
+ history: list,
45
+ system_prompt: str = None,
46
+ tutor_name: str = None,
47
+ difficulty_level: str = "beginner",
48
+ ) -> Generator[str, None, None]:
49
+ """
50
+ Generate a response to the user message using the OpenAI LLM.
51
+
52
+ Args:
53
+ message (str): The user's message.
54
+ history (list): The conversation history.
55
+ system_prompt (str): The system prompt (optional, will be constructed if not provided).
56
+ tutor_name (str): The name of the tutor.
57
+ difficulty_level (str): The difficulty level (beginner, intermediate, advanced).
58
+
59
+ Yields:
60
+ str: Streaming response chunks.
61
+ """
62
+ # Construct messages for OpenAI API
63
+ messages = []
64
+
65
+ # Add system prompt
66
+ if system_prompt:
67
+ messages.append({"role": "system", "content": system_prompt})
68
+
69
+ # Add conversation history (excluding system messages)
70
+ for h in history:
71
+ if h.get("role") != "system":
72
+ messages.append(
73
+ {
74
+ "role": h.get("role", "user"),
75
+ "content": h.get("content", ""),
76
+ }
77
+ )
78
+
79
+ # Add current user message
80
+ messages.append({"role": "user", "content": message})
81
+
82
+ # Make streaming API call with educational-appropriate settings
83
+ # Lower temperature for more consistent, educational responses
84
+ stream = self.client.chat.completions.create(
85
+ model=self.model,
86
+ messages=messages,
87
+ stream=True,
88
+ temperature=0.6, # Balanced: creative enough for engagement, consistent for learning
89
+ max_tokens=500, # Limit response length for age-appropriate brevity
90
+ )
91
+
92
+ response = ""
93
+ for chunk in stream:
94
+ if chunk.choices[0].delta.content is not None:
95
+ content = chunk.choices[0].delta.content
96
+ response += content
97
+ yield response
98
+
99
+ def extract_key_concepts(self, lesson_content: str) -> list[str]:
100
+ """
101
+ Extract key concepts from the lesson content.
102
+
103
+ Args:
104
+ lesson_content (str): The content of the lesson.
105
+
106
+ Returns:
107
+ list[str]: A list of 5 to 10 key concepts from the lesson.
108
+ """
109
+ system_prompt = (
110
+ "Your task is to extract 5 to 10 key educational concepts from the provided lesson content. "
111
+ "These concepts should be appropriate for early childhood education (ages 3-6). "
112
+ "Return only the concept names, one per line. "
113
+ "Do not include any additional text, explanations, or numbering. "
114
+ "Each concept should be a simple, clear phrase that a child could understand. "
115
+ "Example output:\n"
116
+ "Colors\n"
117
+ "Numbers\n"
118
+ "Shapes\n"
119
+ "Animals\n"
120
+ "Nature\n"
121
+ )
122
+
123
+ messages = [
124
+ {"role": "system", "content": system_prompt},
125
+ {"role": "user", "content": lesson_content},
126
+ ]
127
+
128
+ response = self.client.chat.completions.create(
129
+ model=self.model,
130
+ messages=messages,
131
+ temperature=0.3,
132
+ )
133
+
134
+ content = response.choices[0].message.content
135
+ print("Response from LLM:", content)
136
+
137
+ # Split the response by new lines and strip whitespace
138
+ concepts = [concept.strip() for concept in content.split("\n") if concept.strip()]
139
+
140
+ # Limit to 10 concepts
141
+ return concepts[:10]
142
+
143
+ def generate_lesson_introduction(
144
+ self, lesson_content: str, lesson_name: str, concepts: list[str]
145
+ ) -> str:
146
+ """
147
+ Generate an educational introduction for the lesson including:
148
+ - A brief summary of the activity
149
+ - Key concepts
150
+ - Example questions to guide the child
151
+
152
+ Args:
153
+ lesson_content (str): The content of the lesson.
154
+ lesson_name (str): The name of the lesson.
155
+ concepts (list[str]): List of key concepts extracted from the lesson.
156
+
157
+ Returns:
158
+ str: A formatted introduction with summary, concepts, and example questions.
159
+ """
160
+ concepts_text = ", ".join(concepts[:8]) # Show up to 8 concepts
161
+
162
+ system_prompt = (
163
+ "You are an educational expert creating an introduction for a lesson for children ages 3-12. "
164
+ "Create a friendly, engaging introduction that includes:\n\n"
165
+ "1. A brief, exciting summary of what the child will learn (2-3 sentences, very simple language)\n"
166
+ "2. A list of the key concepts they'll explore\n"
167
+ "3. 2-3 example questions that the tutor could ask to start the conversation and guide the child\n\n"
168
+ "Format your response as follows:\n"
169
+ "SUMMARY:\n"
170
+ "[Brief summary here]\n\n"
171
+ "KEY CONCEPTS:\n"
172
+ "[List concepts here, one per line with a bullet point]\n\n"
173
+ "EXAMPLE QUESTIONS TO GET STARTED:\n"
174
+ "[2-3 engaging questions, one per line with a bullet point]\n\n"
175
+ "Use very simple, age-appropriate language. Make it fun and exciting! "
176
+ "The questions should be open-ended and encourage exploration."
177
+ )
178
+
179
+ user_prompt = (
180
+ f"Lesson Name: {lesson_name}\n\n"
181
+ f"Key Concepts: {concepts_text}\n\n"
182
+ f"Lesson Content:\n{lesson_content[:2000]}\n\n"
183
+ "Create an engaging introduction for this lesson."
184
+ )
185
+
186
+ messages = [
187
+ {"role": "system", "content": system_prompt},
188
+ {"role": "user", "content": user_prompt},
189
+ ]
190
+
191
+ response = self.client.chat.completions.create(
192
+ model=self.model,
193
+ messages=messages,
194
+ temperature=0.7, # Slightly higher for creativity
195
+ max_tokens=400,
196
+ )
197
+
198
+ introduction = response.choices[0].message.content
199
+ return introduction
src/learnbee/mcp_client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import os
4
+
5
+ from mcp import ClientSession
6
+ from mcp.client.sse import sse_client
7
+
8
+ MCP_SERVER_URL = os.environ.get(
9
+ "MCP_SERVER_URL", "http://localhost:7860/gradio_api/mcp/sse"
10
+ )
11
+
12
+
13
+ class MCPClient:
14
+
15
+ async def with_session(self, func):
16
+ """
17
+ Create a session with the MCP server and execute the provided function.
18
+ - See: https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse
19
+ """
20
+ async with sse_client(MCP_SERVER_URL) as streams:
21
+ async with ClientSession(streams[0], streams[1]) as session:
22
+ await session.initialize()
23
+ return await func(session)
24
+
25
+ async def list(self):
26
+ """List available tools from the MCP server."""
27
+ async def _list(session):
28
+ response = await session.list_tools()
29
+ return response
30
+
31
+ return await self.with_session(_list)
32
+
33
+ async def get_book_list(self) -> str:
34
+ """Get the list of books available on the MCP server."""
35
+ async def _get_book_list(session):
36
+ tool_name = "get_book_list"
37
+ response = await session.call_tool(tool_name)
38
+ if response.isError:
39
+ raise ValueError(f"Error calling tool: {tool_name}")
40
+ return response.content[0].text
41
+
42
+ return await self.with_session(_get_book_list)
43
+
44
+ async def get_book_content(self, book_name: str, max_length: int = 0) -> str:
45
+ """Get the content of a book from the MCP server."""
46
+ async def _get_book_content(session):
47
+ tool_name = "get_book_content"
48
+ input_data = {"book_name": book_name, "max_length": max_length}
49
+ response = await session.call_tool(tool_name, input_data)
50
+ if response.isError:
51
+ raise ValueError(f"Error calling tool: {tool_name}")
52
+ return response.content[0].text
53
+
54
+ return await self.with_session(_get_book_content)
55
+
56
+
57
+ async def main():
58
+ mcp_client = MCPClient()
59
+
60
+ tools = await mcp_client.list()
61
+ print("Available tools:")
62
+ print("=" * 20)
63
+ for tool in tools.tools:
64
+ print(f"Name: {tool.name}")
65
+ print(f"Description: {tool.description}")
66
+ print(f"Input Schema: {tool.inputSchema}")
67
+ print(f"Annotations: {tool.annotations}")
68
+ print("-" * 20)
69
+
70
+ book_list_str = await mcp_client.get_book_list()
71
+ book_list = json.loads(book_list_str)
72
+ print(f"Number of books available: {len(book_list)}")
73
+
74
+ book_name = book_list[0]
75
+ book_content = await mcp_client.get_book_content(book_name, max_length=100)
76
+ print(f"Content of the book '{book_name}':")
77
+ print(book_content + "...")
78
+
79
+
80
+ if __name__ == "__main__":
81
+ asyncio.run(main())
src/learnbee/mcp_server.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from learnbee.llm_call import LLMCall
5
+
6
+
7
+ def get_lesson_list() -> str:
8
+ """
9
+ Get list of available lessons.
10
+
11
+ Returns:
12
+ str: JSON string containing the list of lesson names.
13
+ """
14
+ lessons_dir = Path("./lessons")
15
+ if not lessons_dir.exists():
16
+ return json.dumps("Error: Lessons directory not found.")
17
+
18
+ text_files = []
19
+ for file in lessons_dir.iterdir():
20
+ if file.is_file() and file.suffix.lower() == ".txt":
21
+ text_files.append(file.stem)
22
+
23
+ return json.dumps(sorted(text_files))
24
+
25
+
26
+ def get_lesson_content(lesson_name: str, max_length: int = 0) -> str:
27
+ """
28
+ Get the content of a lesson.
29
+
30
+ Args:
31
+ lesson_name (str): The name of the lesson (without .txt extension).
32
+ max_length (int): The maximum length of the content to return. If 0, return the full content.
33
+ Returns:
34
+ str: The content of the lesson, or an error message if the lesson is not found.
35
+ """
36
+ lessons_dir = Path("./lessons")
37
+ lesson_file = lessons_dir / f"{lesson_name}.txt"
38
+ if not lesson_file.exists():
39
+ return f"Error: Lesson '{lesson_name}' not found."
40
+
41
+ with open(lesson_file, "r", encoding="utf-8") as f:
42
+ content = f.read()
43
+
44
+ if not max_length:
45
+ return content
46
+ else:
47
+ return content[:max_length]
48
+
49
+
50
+ def get_lesson_introduction(lesson_name: str) -> str:
51
+ """
52
+ Get an educational introduction for a lesson including summary, key concepts, and example questions.
53
+ This function serves as an MCP tool to help guide children with their first message.
54
+
55
+ Args:
56
+ lesson_name (str): The name of the lesson (without .txt extension).
57
+
58
+ Returns:
59
+ str: A formatted introduction with summary, concepts, and example questions, or an error message.
60
+ """
61
+ lessons_dir = Path("./lessons")
62
+ lesson_file = lessons_dir / f"{lesson_name}.txt"
63
+ if not lesson_file.exists():
64
+ return f"Error: Lesson '{lesson_name}' not found."
65
+
66
+ # Get lesson content
67
+ lesson_content = get_lesson_content(lesson_name, max_length=50000)
68
+
69
+ if lesson_content.startswith("Error:"):
70
+ return lesson_content
71
+
72
+ try:
73
+ # Extract key concepts
74
+ call_llm = LLMCall()
75
+ concepts = call_llm.extract_key_concepts(lesson_content)
76
+
77
+ if not concepts:
78
+ return f"Error: Could not extract key concepts from lesson '{lesson_name}'."
79
+
80
+ # Generate introduction
81
+ introduction = call_llm.generate_lesson_introduction(
82
+ lesson_content, lesson_name, concepts
83
+ )
84
+
85
+ return introduction
86
+ except Exception as e:
87
+ return f"Error generating introduction: {str(e)}"
88
+
89
+
90
+ if __name__ == "__main__":
91
+ print("Available lessons:", get_lesson_list())
92
+
93
+ lesson_name = "example_lesson"
94
+ lesson_content = get_lesson_content(lesson_name)
95
+ print(f"Start of '{lesson_name}':\n{lesson_content[:500]}...\n")
96
+ print(f"End of '{lesson_name}':\n{lesson_content[-500:]}\n")