Spaces:
Running
Running
File size: 15,994 Bytes
3ead6ad |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
import os
import sys
# Add src directory to Python path for Hugging Face Spaces compatibility
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
SRC_DIR = os.path.join(PROJECT_ROOT, "src")
sys.path.insert(0, SRC_DIR)
import json
import gradio as gr
from numpy import add
from learnbee.llm_call import LLMCall
from learnbee.mcp_server import get_lesson_content, get_lesson_list
LESSON_CONTENT_MAX_LENGTH = 50000
# Available tutor names for early childhood education
TUTOR_NAMES = ["Professor Owl", "Star Explorer", "Logic Bot", "Nature Guide", "Story Friend"]
def load_lesson_content(lesson_name, selected_tutor, progress=gr.Progress()):
"""Load lesson content and extract key concepts. Returns introduction message for chatbot."""
if not lesson_name:
return "", "", "Please select a lesson first.", []
# Get current chatbot state (empty list if none)
chatbot_messages = []
progress(0.1, desc="Loading lesson content...")
lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)
progress(0.5, desc="Extracting key concepts from the lesson...")
# Extract key concepts using LLM
try:
call_llm = LLMCall()
concepts = call_llm.extract_key_concepts(lesson_content)
progress(0.7, desc="Generating lesson introduction...")
# Generate lesson introduction with summary and example questions
introduction = ""
if concepts:
try:
introduction = call_llm.generate_lesson_introduction(
lesson_content, lesson_name, concepts
)
except Exception as e:
print(f"Error generating introduction: {str(e)}")
# Continue without introduction if it fails
progress(1.0, desc="Complete!")
if concepts:
concepts_display = ', '.join(concepts[:5])
if len(concepts) > 5:
concepts_display += f" and {len(concepts) - 5} more"
# Build simple status message (just confirmation)
status_message = (
f"β
Successfully loaded '{lesson_name}'!\n\n"
f"π Found {len(concepts)} key concepts: {concepts_display}\n\n"
f"π Your tutor is ready! Check the chat for a welcome message."
)
# Prepare chatbot message with introduction
if introduction:
# Format the introduction as a friendly greeting from the tutor
tutor_greeting = (
f"Hello! π I'm {selected_tutor}, and I'm so excited to learn with you today!\n\n"
f"{introduction}\n\n"
f"Let's start our learning adventure! What would you like to explore first? π"
)
chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
else:
# Fallback greeting if introduction generation fails
tutor_greeting = (
f"Hello! π I'm {selected_tutor}, and I'm excited to learn with you today!\n\n"
f"We're going to explore: {concepts_display}\n\n"
f"What would you like to learn about first? π"
)
chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
return (
lesson_name,
lesson_content,
status_message,
chatbot_messages,
)
else:
status_message = (
f"β οΈ Loaded '{lesson_name}' but no key concepts were automatically detected.\n"
f"You can still chat with your tutor about the lesson content!"
)
tutor_greeting = (
f"Hello! π I'm {selected_tutor}, and I'm ready to learn with you!\n\n"
f"Let's explore the lesson '{lesson_name}' together. What would you like to know? π"
)
chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
return (
lesson_name,
lesson_content,
status_message,
chatbot_messages,
)
except Exception as e:
status_message = (
f"β Error extracting concepts: {str(e)}\n\n"
f"You can still try chatting about the lesson content."
)
tutor_greeting = (
f"Hello! π I'm {selected_tutor}, and I'm here to help you learn!\n\n"
f"Let's explore together. What would you like to know? π"
)
chatbot_messages = [{"role": "assistant", "content": tutor_greeting}]
return (
lesson_name,
lesson_content,
status_message,
chatbot_messages,
)
def custom_respond(
message, history, lesson_name, lesson_content, selected_tutor, difficulty_level
):
"""Custom respond function with educational system prompt."""
if not lesson_name or not selected_tutor:
yield "Please select a lesson and tutor first."
return
if not lesson_content:
lesson_content = get_lesson_content(lesson_name, LESSON_CONTENT_MAX_LENGTH)
# Generate educational system prompt with enhanced pedagogy
# fmt: off
system_prompt = (
f"You are {selected_tutor}, a friendly and patient Educational Tutor specializing in early childhood education (ages 3-6).\n\n"
"CORE PEDAGOGICAL PRINCIPLES:\n"
"1. Socratic Method: Guide through questions, not answers. Help children discover knowledge themselves.\n"
"2. Scaffolding: Break complex ideas into smaller, manageable steps. Build understanding gradually.\n"
"3. Positive Reinforcement: Celebrate attempts, not just correct answers. Use encouraging phrases like 'Great thinking!' or 'You're on the right track!'\n"
"4. Active Learning: Encourage hands-on thinking, examples from their world, and personal connections.\n"
"5. Repetition with Variation: Reinforce concepts through different examples and contexts.\n\n"
"COMMUNICATION GUIDELINES:\n"
"- Use very simple, age-appropriate language (3-6 year olds).\n"
"- Keep sentences short (5-10 words maximum).\n"
"- Use concrete examples from children's daily lives (toys, family, pets, food, nature).\n"
"- Incorporate playful elements: emojis, simple analogies, and fun comparisons.\n"
"- Be warm, enthusiastic, and patient. Show excitement about learning!\n"
"- Use the child's name when possible (refer to them as 'you' or 'little learner').\n\n"
"TEACHING STRATEGIES BY DIFFICULTY LEVEL:\n"
f"- {difficulty_level.upper()} level:\n"
+ (" * Beginner: Use very simple words, lots of examples, visual descriptions, and yes/no questions.\n"
if difficulty_level == "beginner" else
" * Intermediate: Introduce slightly more complex concepts, encourage longer explanations, use 'why' and 'how' questions.\n"
if difficulty_level == "intermediate" else
" * Advanced: Challenge with problem-solving, encourage predictions, explore connections between concepts.\n") +
"\n"
"INTERACTION PATTERNS:\n"
"- When a child asks a question: Respond with a guiding question first, then offer a hint if needed.\n"
"- When a child gives an answer: Validate their thinking, then ask a follow-up to deepen understanding.\n"
"- When a child seems confused: Break it down into smaller pieces, use a different example, or try a simpler approach.\n"
"- When a child shows excitement: Match their energy and build on their interest.\n"
"- 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"
"SAFETY AND BOUNDARIES:\n"
"- Only discuss topics appropriate for ages 3-12.\n"
"- If asked about inappropriate topics, gently redirect: 'Let's focus on our fun lesson instead!'\n"
"- Keep all content educational and positive.\n"
"- Never provide medical, legal, or safety advice beyond basic age-appropriate concepts.\n\n"
"LESSON CONTEXT:\n"
"====================\n"
f"{lesson_content}\n"
"====================\n\n"
"YOUR ROLE:\n"
"You are teaching this lesson content to a young child. Make it engaging, interactive, and fun. "
"Remember: the goal is not just to convey information, but to spark curiosity and build confidence in learning.\n\n"
"LANGUAGE INSTRUCTION:\n"
"IMPORTANT: Always respond in the EXACT same language that the child uses in their messages. "
"If the child writes in Spanish, respond in Spanish. If they write in English, respond in English. "
"If they write in French, respond in French. Match the child's language automatically. "
"This is critical for effective communication with young learners.\n"
)
# fmt: on
# Call the respond method with educational system prompt
call_llm = LLMCall()
for response in call_llm.respond(
message,
history,
system_prompt=system_prompt,
tutor_name=selected_tutor,
difficulty_level=difficulty_level
):
yield response
def gradio_ui():
lesson_name = gr.BrowserState("")
selected_tutor = gr.BrowserState(TUTOR_NAMES[0] if TUTOR_NAMES else "")
lesson_choices = json.loads(get_lesson_list())
with gr.Blocks() as demo:
with gr.Tab("Chat"):
# Title
with gr.Row():
gr.Markdown("# Learnbee-mcp - Educational Tutor")
# Status
with gr.Row():
status_markdown = gr.Markdown(label="Status")
status_markdown.value = (
# fmt: off
"π Welcome to Learnbee-mcp! π<br><br>"
"π <strong>Getting Started:</strong><br>"
"1. Select a lesson from the dropdown<br>"
"2. Choose your favorite tutor<br>"
"3. Pick a difficulty level<br>"
"4. Click 'Load Lesson & Prepare Tutor'<br>"
"5. Start learning and chatting! π¬<br><br>"
"β¨ This educational system is designed for early childhood education (ages 3-6)."
# fmt: on
)
# Hidden textbox for lesson content
lesson_content = gr.Textbox(visible=False)
with gr.Row():
with gr.Column(scale=1):
# Lesson selection
with gr.Row():
lesson_dropdown = gr.Dropdown(
label="π Select a Lesson",
choices=lesson_choices,
interactive=True,
)
# Tutor selection
with gr.Row():
tutor_dropdown = gr.Dropdown(
label="π¦Έ Select a Tutor",
choices=TUTOR_NAMES,
value=TUTOR_NAMES[0] if TUTOR_NAMES else None,
interactive=True,
)
# Difficulty level selection
with gr.Row():
difficulty_dropdown = gr.Dropdown(
label="π Difficulty Level",
choices=["beginner", "intermediate", "advanced"],
value="beginner",
interactive=True,
)
with gr.Row():
load_button = gr.Button(
"Load Lesson & Prepare Tutor", variant="primary"
)
def update_tutor_selection(tutor):
"""Update selected tutor."""
return tutor
tutor_dropdown.change(
fn=update_tutor_selection,
inputs=[tutor_dropdown],
outputs=[selected_tutor],
)
with gr.Row():
gr.Markdown(
"π **Multilingual Support:** The tutor will automatically respond in the same language you use!<br>"
"Just start chatting in your preferred language (English, Spanish, French, etc.) and the tutor will match it.<br>"
"<br>"
"π **Note:** Once you start chatting, you can't change the lesson or tutor. <br>"
"If you want to pick a different one, just hit the reset button and start fresh! π<br>"
)
with gr.Column(scale=2):
# Chat interface - defined before use
chat_interface = gr.ChatInterface(
fn=custom_respond,
additional_inputs=[
lesson_dropdown,
lesson_content,
tutor_dropdown,
difficulty_dropdown,
],
type="messages",
autofocus=False
)
# Connect load button after chat_interface is defined
load_button.click(
fn=load_lesson_content,
inputs=[lesson_dropdown, tutor_dropdown],
outputs=[
lesson_name,
lesson_content,
status_markdown,
chat_interface.chatbot_value,
],
)
reset_button = gr.Button("Reset", variant="secondary")
reset_button.click(
lambda: (
gr.update(value=""),
gr.update(value=""),
gr.update(value=TUTOR_NAMES[0] if TUTOR_NAMES else None),
gr.update(value="beginner"),
"Status reset.",
[],
),
outputs=[
lesson_dropdown,
lesson_content,
tutor_dropdown,
difficulty_dropdown,
status_markdown,
chat_interface.chatbot_value,
],
)
with gr.Tab("List Lessons"):
gr.Markdown("π Get the list of available lessons.")
btn = gr.Button("Get")
output_text = gr.Textbox(label="Lessons")
btn.click(get_lesson_list, None, output_text)
with gr.Tab("Lesson Content"):
gr.Markdown("π Get the content of a lesson by its name.")
lesson_name_input = gr.Textbox(label="Lesson Name")
lesson_len = gr.Number(label="Max Length", value=1000)
lesson_content_output = gr.Textbox(label="Lesson Content", lines=20)
btn = gr.Button("Get")
btn.click(get_lesson_content, [lesson_name_input, lesson_len], lesson_content_output)
return demo
if __name__ == "__main__":
demo = gradio_ui()
# Launch the Gradio app with MCP server enabled.
# NOTE: It is required to restart the app when you add or remove MCP tools.
demo.launch(mcp_server=True)
|