awellis commited on
Commit
65f7070
Β·
1 Parent(s): 958bbf5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +554 -554
app.py CHANGED
@@ -1,705 +1,705 @@
 
1
  """
2
- Personalized Worked Example Generator
3
- Demonstrates Cognitive Load Theory principles through AI-generated personalized examples.
4
 
5
- Based on research:
6
- - Sweller, J. (1988). Cognitive load during problem solving.
7
- - NSW CESE (2017). Cognitive load theory: Research that teachers really need to understand.
8
  """
9
 
10
  import marimo
11
 
12
- __generated_with = "0.9.0"
13
  app = marimo.App(width="medium")
14
 
15
 
16
  @app.cell
17
- def imports():
18
  import marimo as mo
19
  from openai import OpenAI
20
  from pydantic import BaseModel, Field
21
  from typing import Literal
22
  import os
23
-
24
- return mo, OpenAI, BaseModel, Field, Literal, os
25
 
26
 
27
  @app.cell
28
- def welcome_section(mo):
29
  mo.md("""
30
- # πŸŽ“ Personalized Worked Example Generator
 
31
 
32
- **Learn concepts through examples tailored to YOUR interests!**
 
33
 
34
- ## Why This Works
35
 
36
- This tool demonstrates principles from **Cognitive Load Theory**:
37
 
38
- ### The Worked Example Effect
39
- > "Novice learners who are given worked examples to study perform better
40
- > than learners who are required to solve problems themselves."
41
- >
42
- > β€” NSW Centre for Education Statistics and Evaluation (2017)
43
 
44
- **Why?** Unguided problem-solving overloads working memory. Worked examples
45
- reduce cognitive load, freeing capacity for learning.
46
 
47
- ### The Personalization Effect
 
 
 
48
 
49
- Familiar contexts (your hobbies, interests, goals) are easier to process,
50
- further reducing cognitive load and improving learning.
 
51
 
 
 
 
 
 
 
 
 
 
 
 
52
  ---
53
 
54
- ## How It Works
 
 
 
 
55
 
56
- 1. **Tell us about yourself** - interests, goals, background
57
- 2. **Choose a concept** to learn in your domain
58
- 3. **Get a custom example** woven into your context
59
 
60
- Let's get started! πŸ‘‡
61
  """)
62
  return
63
 
64
 
65
  @app.cell
66
- def define_models(BaseModel, Field, Literal):
67
- """
68
- PEDAGOGICAL DESIGN: Define data structures
69
 
70
- These Pydantic models embody our learning principles:
71
- - Clear structure reduces cognitive load
72
- - Explicit fields make the design transparent
73
- - Type hints ensure reliability
74
- """
 
75
 
76
- class LearnerProfile(BaseModel):
77
- """Collect learner information for personalization"""
78
 
79
- name: str = Field(
80
- description="Learner's first name"
81
- )
82
 
83
- domain: Literal["programming", "health_sciences", "agronomy"] = Field(
84
- description="Learning domain"
85
- )
86
 
87
- specific_interest: str = Field(
88
- description="Specific interest within domain"
89
- )
 
 
 
90
 
91
- hobby_or_passion: str = Field(
92
- description="A hobby or passion they have"
93
- )
94
 
95
- goal: str = Field(
96
- description="What they want to achieve"
97
- )
 
98
 
99
- background_level: Literal["beginner", "intermediate", "advanced"] = Field(
100
- description="Their current level in the domain"
101
- )
 
102
 
 
 
103
 
104
- class PersonalizedWorkedExample(BaseModel):
105
- """
106
- A complete worked example tailored to the learner
107
 
108
- DESIGN RATIONALE (based on CLT):
109
- - title: Engaging hook using learner's context
110
- - problem_statement: Clear framing reduces ambiguity
111
- - given_data: Familiar context reduces extraneous load
112
- - step_by_step_solution: List structure supports chunking
113
- - final_answer: Explicit conclusion aids schema formation
114
- - connection_to_goal: Motivation through relevance
115
- - practice_suggestion: Spaced practice opportunity
116
- """
117
 
118
- title: str = Field(
119
- description="Engaging title that incorporates learner's interest"
120
- )
 
121
 
122
- problem_statement: str = Field(
123
- description="Problem framed in learner's context (3-4 sentences)"
124
- )
125
 
126
- given_data: str = Field(
127
- description="Data presented in familiar context (can include code blocks or tables)"
128
- )
129
 
130
- step_by_step_solution: list[str] = Field(
131
- description="Clear steps with explanations. Include code or calculations."
132
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- final_answer: str = Field(
135
- description="The final answer with interpretation relevant to learner's context"
136
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- connection_to_goal: str = Field(
139
- description="How this example relates to their stated goal (2-3 sentences)"
140
- )
141
 
142
- practice_suggestion: str = Field(
143
- description="A similar problem they could try next, using their context"
144
- )
145
 
146
- return LearnerProfile, PersonalizedWorkedExample
 
 
 
147
 
 
148
 
149
- @app.cell
150
- def define_concepts():
151
- """Define concept library for each domain"""
152
-
153
- CONCEPTS = {
154
- "programming": [
155
- {
156
- "name": "For Loops",
157
- "abstract": "Iterate through a sequence of items",
158
- "difficulty": "beginner",
159
- "typical_use": "Processing lists, repeating actions multiple times"
160
- },
161
- {
162
- "name": "List Comprehensions",
163
- "abstract": "Create new lists using concise syntax",
164
- "difficulty": "intermediate",
165
- "typical_use": "Transform data, filter lists elegantly"
166
- },
167
- {
168
- "name": "Dictionary Methods",
169
- "abstract": "Access and manipulate key-value pairs",
170
- "difficulty": "beginner",
171
- "typical_use": "Store related data, perform fast lookups"
172
- },
173
- {
174
- "name": "Functions with Parameters",
175
- "abstract": "Create reusable code blocks that accept inputs",
176
- "difficulty": "beginner",
177
- "typical_use": "Organize code, avoid repetition"
178
- },
179
- {
180
- "name": "String Formatting",
181
- "abstract": "Create formatted text output using f-strings",
182
- "difficulty": "beginner",
183
- "typical_use": "Display data, create messages dynamically"
184
- }
185
- ],
186
- "health_sciences": [
187
- {
188
- "name": "Mean and Standard Deviation",
189
- "abstract": "Describe central tendency and variability in data",
190
- "difficulty": "beginner",
191
- "typical_use": "Summarize health measurements, describe populations"
192
- },
193
- {
194
- "name": "Correlation Analysis",
195
- "abstract": "Measure strength and direction of relationship between two variables",
196
- "difficulty": "intermediate",
197
- "typical_use": "Find relationships in health data, guide research"
198
- },
199
- {
200
- "name": "Linear Regression",
201
- "abstract": "Predict one variable from another using a straight-line relationship",
202
- "difficulty": "intermediate",
203
- "typical_use": "Predict outcomes, understand relationships, forecast trends"
204
- },
205
- {
206
- "name": "Independent T-Test",
207
- "abstract": "Compare means between two independent groups",
208
- "difficulty": "intermediate",
209
- "typical_use": "Test intervention effectiveness, compare treatments"
210
- },
211
- {
212
- "name": "Confidence Intervals",
213
- "abstract": "Estimate population parameters with uncertainty",
214
- "difficulty": "intermediate",
215
- "typical_use": "Interpret research findings, quantify precision"
216
- },
217
- {
218
- "name": "Effect Size (Cohen's d)",
219
- "abstract": "Measure practical significance of differences",
220
- "difficulty": "intermediate",
221
- "typical_use": "Interpret research impact beyond p-values"
222
- }
223
- ],
224
- "agronomy": [
225
- {
226
- "name": "Yield Prediction",
227
- "abstract": "Estimate crop output based on inputs using regression",
228
- "difficulty": "intermediate",
229
- "typical_use": "Plan harvest, allocate resources, financial projections"
230
- },
231
- {
232
- "name": "NPK Optimization",
233
- "abstract": "Calculate optimal fertilizer ratios for maximum benefit",
234
- "difficulty": "intermediate",
235
- "typical_use": "Maximize yield while minimizing cost and environmental impact"
236
- },
237
- {
238
- "name": "Growing Degree Days",
239
- "abstract": "Calculate heat accumulation for crop development",
240
- "difficulty": "beginner",
241
- "typical_use": "Predict crop stages, plan field operations"
242
- },
243
- {
244
- "name": "Water Use Efficiency",
245
- "abstract": "Calculate crop yield per unit of water used",
246
- "difficulty": "beginner",
247
- "typical_use": "Optimize irrigation, compare varieties"
248
- },
249
- {
250
- "name": "Cost-Benefit Analysis",
251
- "abstract": "Compare costs and returns of agricultural interventions",
252
- "difficulty": "intermediate",
253
- "typical_use": "Make informed decisions about inputs and practices"
254
- }
255
- ]
256
- }
257
 
258
- return CONCEPTS,
 
 
 
 
 
 
 
 
259
 
260
 
261
  @app.cell
262
- def setup_openai_client(OpenAI, os):
263
- """
264
- Initialize OpenAI client
265
 
266
- Simple and transparent - just create the client with your API key
267
- """
268
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 
 
 
 
 
 
 
 
 
 
269
 
270
- return client,
 
271
 
272
 
273
  @app.cell
274
- def define_system_prompt():
275
- """
276
- PEDAGOGICAL DESIGN: Craft the system prompt
277
-
278
- This prompt embodies our CLT principles:
279
- - Personalization reduces extraneous load
280
- - Worked examples reduce germane load
281
- - Clear structure supports schema formation
282
- """
283
-
284
- SYSTEM_PROMPT = """You are an expert educator who creates highly personalized
285
- worked examples that connect abstract concepts to learners' lived experiences.
286
-
287
- CRITICAL INSTRUCTIONS:
288
- 1. Weave the learner's interests, hobbies, and goals naturally into the example
289
- 2. Use their name throughout to increase personal connection
290
- 3. Make data and scenarios feel authentic to their context
291
- 4. Keep explanations clear but connect to what they care about
292
- 5. Match complexity to their level (beginner/intermediate/advanced)
293
- 6. Make the connection to their goal explicit and motivating
294
- 7. Use concrete numbers and realistic data
295
- 8. For programming examples, include actual runnable code with comments
296
- 9. For quantitative examples, show all calculations step by step
297
-
298
- STRUCTURE YOUR EXAMPLES:
299
- - Start with an engaging title that mentions their interest
300
- - Frame the problem in their context (use their name and interests)
301
- - Present data that feels real to their situation
302
- - Walk through steps clearly with explanations
303
- - For code: include comments explaining each part
304
- - For math: show each calculation explicitly
305
- - Connect the final answer to their goal
306
- - Suggest a related practice problem in their context
307
-
308
- AVOID:
309
- - Generic examples with personal details superficially added
310
- - Forced or artificial connections
311
- - Too much technical jargon for beginners
312
- - Abstract variable names (use meaningful names from their context)
313
- - Skipping steps in solutions
314
-
315
- Remember: This is a WORKED EXAMPLE - a complete solution for the learner
316
- to study, not a problem for them to solve.
317
- """
318
-
319
- return SYSTEM_PROMPT,
320
 
321
 
322
  @app.cell
323
- def create_generator_function(client, SYSTEM_PROMPT, PersonalizedWorkedExample):
324
- """
325
- CORE FUNCTION: Generate personalized examples
326
-
327
- Notice how simple this is:
328
- 1. Build the user prompt with learner context
329
- 2. Call OpenAI's responses API with our Pydantic model
330
- 3. Get back a validated PersonalizedWorkedExample
331
-
332
- The new responses.parse() API is cleaner than the old chat completions!
333
- """
334
-
335
- def generate_personalized_example(
336
- profile: 'LearnerProfile',
337
- concept: dict
338
- ) -> PersonalizedWorkedExample:
339
- """Generate a personalized worked example"""
340
-
341
- # Build the user prompt with all the context
342
- user_prompt = f"""
343
- Create a worked example for:
344
-
345
- LEARNER PROFILE:
346
- - Name: {profile.name}
347
- - Domain: {profile.domain}
348
- - Specific interest: {profile.specific_interest}
349
- - Hobby/passion: {profile.hobby_or_passion}
350
- - Goal: {profile.goal}
351
- - Level: {profile.background_level}
352
-
353
- CONCEPT TO TEACH:
354
- - Name: {concept['name']}
355
- - Abstract description: {concept['abstract']}
356
- - Difficulty: {concept['difficulty']}
357
- - Typical use: {concept['typical_use']}
358
-
359
- Create a worked example that teaches this concept using {profile.name}'s
360
- specific context. The example should feel personal and relevant to their
361
- goal of "{profile.goal}".
362
-
363
- Make the problem realistic and the data believable for their situation.
364
-
365
- For programming: Include complete, runnable code with explanatory comments.
366
- For quantitative problems: Show every calculation step explicitly.
367
-
368
- This is a WORKED EXAMPLE - provide the complete solution for them to study.
369
- """
370
-
371
- # Call OpenAI with structured outputs using the new responses API
372
- response = client.responses.parse(
373
- model="gpt-5.1",
374
- input=[
375
- {"role": "system", "content": SYSTEM_PROMPT},
376
- {"role": "user", "content": user_prompt}
377
- ],
378
- text_format=PersonalizedWorkedExample
379
- )
380
-
381
- # Extract the parsed example
382
- return response.output_parsed
383
-
384
- return generate_personalized_example,
 
 
 
 
 
385
 
386
 
387
  @app.cell
388
- def profile_form_header(mo):
389
- """Header for profile form"""
390
- mo.md("---\n## πŸ‘€ Step 1: Tell Us About Yourself")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  return
392
 
393
 
394
  @app.cell
395
- def profile_inputs(mo):
396
- """Individual input widgets for profile"""
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
- name_input = mo.ui.text(
399
- label="Your first name:",
400
- placeholder="e.g., Maria",
401
- full_width=True
402
  )
403
 
404
- domain_input = mo.ui.dropdown(
405
- label="Choose your learning domain:",
406
- options={
407
- "Programming (Python)": "programming",
408
- "Health Sciences (Statistics)": "health_sciences",
409
- "Agronomy (Agricultural Science)": "agronomy"
410
- },
411
- value=None,
412
- full_width=True
413
- )
414
 
415
- interest_input = mo.ui.text(
416
- label="Your specific interest in this domain:",
417
- placeholder="e.g., web development, sports nutrition, coffee farming",
418
- full_width=True
419
- )
420
 
421
- hobby_input = mo.ui.text(
422
- label="A hobby or passion you have:",
423
- placeholder="e.g., photography, cycling, cooking",
424
- full_width=True
425
- )
426
 
427
- goal_input = mo.ui.text(
428
- label="What you want to achieve:",
429
- placeholder="e.g., build a portfolio site, improve performance, increase yield",
430
- full_width=True
431
- )
432
 
433
- level_input = mo.ui.dropdown(
434
- label="Your current level:",
435
- options=["beginner", "intermediate", "advanced"],
436
- value="beginner",
437
- full_width=True
438
- )
439
 
440
- return name_input, domain_input, interest_input, hobby_input, goal_input, level_input
 
 
441
 
 
 
 
 
442
 
443
- @app.cell
444
- def display_profile_form(mo, name_input, domain_input, interest_input, hobby_input, goal_input, level_input):
445
- """Display the profile form"""
446
 
447
- mo.vstack([
448
- name_input,
449
- domain_input,
450
- interest_input,
451
- hobby_input,
452
- goal_input,
453
- level_input
454
- ])
 
 
455
 
 
 
456
  return
457
 
458
 
459
  @app.cell
460
- def check_profile_complete(name_input, domain_input, interest_input, hobby_input, goal_input, level_input):
461
- """Check if profile is complete"""
462
-
463
- profile_complete = all([
464
- name_input.value,
465
- domain_input.value,
466
- interest_input.value,
467
- hobby_input.value,
468
- goal_input.value,
469
- level_input.value
470
- ])
471
 
472
- return profile_complete,
 
 
 
 
 
 
 
 
 
473
 
474
 
475
  @app.cell
476
- def create_profile(profile_complete, LearnerProfile, name_input, domain_input, interest_input, hobby_input, goal_input, level_input):
477
- """Create profile object if form is complete"""
478
-
479
- if profile_complete:
480
- learner_profile = LearnerProfile(
481
- name=name_input.value,
482
- domain=domain_input.value,
483
- specific_interest=interest_input.value,
484
- hobby_or_passion=hobby_input.value,
485
- goal=goal_input.value,
486
- background_level=level_input.value
487
- )
488
- else:
489
- learner_profile = None
490
 
491
- return learner_profile,
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
 
494
  @app.cell
495
- def show_profile_status(mo, profile_complete, learner_profile):
496
- """Show profile status"""
497
-
498
- if profile_complete:
499
- mo.callout(
500
- f"""
501
- βœ… **Profile Complete!**
502
-
503
- Great, {learner_profile.name}! Now choose a concept below.
504
- """,
505
- kind="success"
506
- )
507
- else:
508
- mo.callout(
509
- "πŸ“ Please fill in all fields above to continue.",
510
- kind="info"
511
- )
512
 
 
 
 
 
 
 
513
  return
514
 
515
 
516
  @app.cell
517
- def concept_selection_header(mo, profile_complete):
518
- """Header for concept selection"""
 
519
 
520
- if profile_complete:
521
- mo.md("---\n## πŸ“š Step 2: Choose a Concept to Learn")
522
 
 
 
 
 
 
 
 
523
  return
524
 
525
 
526
  @app.cell
527
- def concept_selector_widget(mo, profile_complete, learner_profile, CONCEPTS):
528
- """Create concept selector based on chosen domain"""
529
-
530
- if profile_complete and learner_profile:
531
- available_concepts = CONCEPTS[learner_profile.domain]
532
-
533
- concept_selector = mo.ui.dropdown(
534
- label=f"Choose a concept in {learner_profile.domain.replace('_', ' ').title()}:",
535
- options={
536
- f"{c['name']} ({c['difficulty']})": c
537
- for c in available_concepts
538
- },
539
- value=None,
540
- full_width=True
541
- )
542
-
543
- mo.vstack([
544
- concept_selector,
545
- mo.callout(
546
- "πŸ‘† Select a concept from the dropdown above to continue.",
547
- kind="info"
548
- ) if not concept_selector.value else None
549
- ])
550
- else:
551
- concept_selector = None
552
 
553
- return concept_selector,
 
 
 
 
 
 
 
 
554
 
555
 
556
  @app.cell
557
- def generate_button_widget(mo, profile_complete, concept_selector):
558
- """Create generate button"""
559
-
560
- if profile_complete and concept_selector and concept_selector.value:
561
- generate_button = mo.ui.button(
562
- label="✨ Generate My Personalized Example",
563
- kind="success",
564
- full_width=True
565
- )
566
-
567
- mo.vstack([
568
- mo.md("---"),
569
- mo.md("## 🎯 Step 3: Generate Your Example"),
570
- generate_button
571
- ])
572
- else:
573
- generate_button = None
574
 
575
- return generate_button,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
 
578
  @app.cell
579
- def generate_and_display(mo, generate_button, learner_profile, concept_selector, generate_personalized_example):
580
- """Generate and display the personalized example"""
581
 
582
- if generate_button and generate_button.value and learner_profile and concept_selector.value:
583
 
584
- # Show loading state
585
- with mo.status.spinner(title="Creating your personalized example... This may take 30-60 seconds."):
586
- try:
587
- example = generate_personalized_example(
588
- profile=learner_profile,
589
- concept=concept_selector.value
590
- )
591
 
592
- # Display the example
593
- display_content = mo.vstack([
594
- mo.md("---"),
595
- mo.md(f"# {example.title}"),
596
- mo.md("## πŸ“‹ The Problem"),
597
- mo.md(example.problem_statement),
598
- mo.md("### Given Data"),
599
- mo.md(example.given_data),
600
- mo.md("---"),
601
- mo.md("## πŸ’‘ Step-by-Step Solution"),
602
- *[mo.md(f"**Step {i}:**\n\n{step}")
603
- for i, step in enumerate(example.step_by_step_solution, 1)],
604
- mo.md("---"),
605
- mo.md("## βœ… Final Answer"),
606
- mo.md(example.final_answer),
607
- mo.md("---"),
608
- mo.callout(
609
- f"### 🎯 Why This Matters for You\n\n{example.connection_to_goal}",
610
- kind="success"
611
- ),
612
- mo.md("---"),
613
- mo.callout(
614
- f"### πŸš€ Try This Next\n\n{example.practice_suggestion}",
615
- kind="info"
616
- ),
617
- ])
618
-
619
- display_content
620
-
621
- except Exception as e:
622
- mo.callout(
623
- f"❌ Error generating example: {str(e)}\n\nPlease check your OpenAI API key.",
624
- kind="danger"
625
- )
626
 
627
- return
 
 
628
 
 
 
 
629
 
630
- @app.cell
631
- def footer(mo):
632
- """Display footer with information"""
633
 
634
- mo.md("""
635
- ---
 
636
 
637
- ## πŸ“– About This Tool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
 
639
- ### Cognitive Load Theory Principles
640
 
641
- This tool demonstrates research-backed learning principles:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
 
643
- **The Worked Example Effect** (Sweller, 1988; Cooper & Sweller, 1987)
644
- > "Novice learners who are given worked examples to study perform better on
645
- > subsequent tests than learners who are required to solve the equivalent
646
- > problems themselves."
647
 
648
- - **Why?** Unguided problem-solving overloads working memory
649
- - **Result:** Studying worked examples frees cognitive capacity for learning
650
- - **Evidence:** Effect size of 0.52 across multiple studies (Crissman, 2006)
 
651
 
652
- **The Personalization Effect** (Cordova & Lepper, 1996)
653
- > "Familiar contexts require less cognitive effort to process, reducing
654
- > extraneous cognitive load and improving learning outcomes."
655
 
656
- - **Why?** Known contexts don't require working memory to parse
657
- - **Result:** More capacity available for schema construction
658
- - **Benefit:** Increased motivation through personal relevance
659
 
660
- ### Built With
661
 
662
- - [Marimo](https://marimo.io) - Reactive Python notebooks
663
- - [OpenAI GPT-5.1](https://platform.openai.com/docs/guides/latest-model) - Latest language model with structured outputs
664
- - [Pydantic](https://pydantic.dev) - Data validation
 
 
665
 
666
- ### πŸ”§ Extend This Tool
667
 
668
- Ideas for enhancement:
669
 
670
- - Add more domains (economics, chemistry, history, literature)
671
- - Include images and diagrams in examples
672
- - Create sequences of scaffolded examples
673
- - Track learner progress over time
674
- - Export examples to PDF or flashcards
675
- - Add multilingual support
676
- - Integrate with learning management systems
677
 
678
- ### πŸ“š Research References
 
 
679
 
680
- - Cooper, G., & Sweller, J. (1987). Effects of schema acquisition and rule
681
- automation on mathematical problem-solving transfer. *Journal of Educational
682
- Psychology*, 79(4), 347-362.
 
683
 
684
- - Cordova, D. I., & Lepper, M. R. (1996). Intrinsic motivation and the process
685
- of learning: Beneficial effects of contextualization, personalization, and
686
- choice. *Journal of Educational Psychology*, 88(4), 715.
687
 
688
- - Crissman, J. (2006). *The design and utilization of effective worked examples:
689
- A meta-analysis* (Doctoral dissertation). University of Nebraska, Lincoln.
690
 
691
- - NSW Centre for Education Statistics and Evaluation (2017). *Cognitive load
692
- theory: Research that teachers really need to understand*.
693
- [Link](https://education.nsw.gov.au/about-us/education-data-and-research/cese/publications/literature-reviews/cognitive-load-theory)
 
 
694
 
695
- - Sweller, J. (1988). Cognitive load during problem solving: Effects on learning.
696
- *Cognitive Science*, 12(2), 257-285.
697
 
698
  ---
699
 
700
- **Created for the BFH Workshop:** KI in der Lehre: Advanced
701
  """)
702
-
703
  return
704
 
705
 
 
1
+ # ruff: noqa
2
  """
3
+ Interactive Exploration: Cognitive Load Theory & AI-Generated Worked Examples
4
+ Five hands-on labs to understand how to design educational AI tools
5
 
6
+ Built for embedding in Quarto workshop materials
 
 
7
  """
8
 
9
  import marimo
10
 
11
+ __generated_with = "0.17.8"
12
  app = marimo.App(width="medium")
13
 
14
 
15
  @app.cell
16
+ def _():
17
  import marimo as mo
18
  from openai import OpenAI
19
  from pydantic import BaseModel, Field
20
  from typing import Literal
21
  import os
22
+ return BaseModel, Field, OpenAI, mo, os
 
23
 
24
 
25
  @app.cell
26
+ def _(mo):
27
  mo.md("""
28
+ # πŸ§ͺ Interactive Exploration Lab
29
+ ## Designing AI Tools Grounded in Cognitive Load Theory
30
 
31
+ Welcome to the **interactive exploration**! This isn't a complete toolβ€”it's a laboratory
32
+ where you'll experiment with the key design decisions that make AI educational tools effective.
33
 
34
+ ### What You'll Explore
35
 
36
+ Through 5 hands-on labs, you'll discover:
37
 
38
+ 1. 🎨 **Prompt Design Lab** - How prompt engineering shapes learning
39
+ 2. βš–οΈ **Personalization A/B Test** - Feel the cognitive load difference
40
+ 3. πŸ—οΈ **Data Model Designer** - What makes examples "worked"
41
+ 4. πŸŽ›οΈ **Parameter Playground** - Model settings and pedagogy
42
+ 5. πŸ” **CLT Analyzer** - Evaluate examples with a critical lens
43
 
44
+ ### Why This Matters
 
45
 
46
+ You could just use a tool. But **understanding the design principles** lets you:
47
+ - Adapt tools to your specific domain
48
+ - Critique and improve existing AI educational tools
49
+ - Design new tools grounded in learning science
50
 
51
+ **Ready to explore?** Let's start with the setup.
52
+ """)
53
+ return
54
 
55
+
56
+ @app.cell
57
+ def _(OpenAI, os):
58
+ """Setup: Initialize OpenAI client"""
59
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
60
+ return (client,)
61
+
62
+
63
+ @app.cell
64
+ def _(mo):
65
+ mo.md("""
66
  ---
67
 
68
+ ## 🎨 Lab 1: Prompt Design Laboratory
69
+
70
+ **Learning Question**: How does prompt engineering affect the quality of worked examples?
71
+
72
+ ### The Experiment
73
 
74
+ You'll see **two prompts** - a basic one and one grounded in CLT principles.
75
+ Try editing them and see how the outputs change.
 
76
 
77
+ **Key insight**: The prompt IS your pedagogical design encoded in language.
78
  """)
79
  return
80
 
81
 
82
  @app.cell
83
+ def _(BaseModel, Field):
84
+ """Simple data model for Lab 1"""
 
85
 
86
+ class SimpleExample(BaseModel):
87
+ """Minimal structure for prompt comparison"""
88
+ problem: str = Field(description="The problem to solve")
89
+ solution: str = Field(description="Step-by-step solution")
90
+ explanation: str = Field(description="Why this approach works")
91
+ return (SimpleExample,)
92
 
 
 
93
 
94
+ @app.cell
95
+ def _(mo):
96
+ """Lab 1: Prompt inputs"""
97
 
98
+ mo.md("### Try These Prompts")
 
 
99
 
100
+ basic_prompt = mo.ui.text_area(
101
+ label="Basic Prompt (no pedagogical grounding):",
102
+ value="""Create an example problem about Python for loops and solve it step by step.""",
103
+ full_width=True,
104
+ rows=3
105
+ )
106
 
107
+ clt_prompt = mo.ui.text_area(
108
+ label="CLT-Grounded Prompt (reduces cognitive load):",
109
+ value="""Create a worked example about Python for loops.
110
 
111
+ CRITICAL: This is a WORKED EXAMPLE for novice learners.
112
+ - Problem: Clear, specific, uses familiar context (counting items)
113
+ - Solution: Break into small steps, explain each step's purpose
114
+ - Explanation: Connect to WHY this pattern works (not just WHAT it does)
115
 
116
+ Keep cognitive load low: avoid technical jargon, use concrete examples.""",
117
+ full_width=True,
118
+ rows=8
119
+ )
120
 
121
+ mo.vstack([basic_prompt, clt_prompt])
122
+ return basic_prompt, clt_prompt
123
 
 
 
 
124
 
125
+ @app.cell
126
+ def _(mo):
127
+ """Lab 1: Generate button"""
 
 
 
 
 
 
128
 
129
+ lab1_button = mo.ui.button(
130
+ label="πŸ”¬ Generate Both Examples",
131
+ kind="success"
132
+ )
133
 
134
+ mo.md(f"### Compare the Results\n\n{lab1_button}")
135
+ return (lab1_button,)
 
136
 
 
 
 
137
 
138
+ @app.cell
139
+ def _(SimpleExample, basic_prompt, client, clt_prompt, lab1_button, mo):
140
+ """Lab 1: Generate and compare both examples"""
141
+
142
+ lab1_output = None
143
+
144
+ # Debug: Show button state
145
+ if lab1_button.value:
146
+ try:
147
+ with mo.status.spinner(title="Generating both examples..."):
148
+ # Generate basic example
149
+ basic_response = client.responses.parse(
150
+ model="gpt-4o-mini",
151
+ input=[{"role": "user", "content": basic_prompt.value}],
152
+ text_format=SimpleExample
153
+ )
154
+ basic_example = basic_response.output_parsed
155
 
156
+ # Generate CLT-grounded example
157
+ clt_response = client.responses.parse(
158
+ model="gpt-4o-mini",
159
+ input=[{"role": "user", "content": clt_prompt.value}],
160
+ text_format=SimpleExample
161
+ )
162
+ clt_example = clt_response.output_parsed
163
+
164
+ _comparison = mo.vstack([
165
+ mo.md("### πŸ“Š Basic Prompt Result"),
166
+ mo.md(f"**Problem:** {basic_example.problem}"),
167
+ mo.md(f"**Solution:** {basic_example.solution}"),
168
+ mo.md(f"**Explanation:** {basic_example.explanation}"),
169
+ mo.md("---"),
170
+ mo.md("### πŸŽ“ CLT-Grounded Prompt Result"),
171
+ mo.md(f"**Problem:** {clt_example.problem}"),
172
+ mo.md(f"**Solution:** {clt_example.solution}"),
173
+ mo.md(f"**Explanation:** {clt_example.explanation}"),
174
+ ])
175
+
176
+ _reflection = mo.callout(mo.md("""
177
+ ### πŸ’­ What Do You Notice?
178
+
179
+ - Which problem is clearer and more specific?
180
+ - Which solution breaks down steps better?
181
+ - Which explanation helps you understand WHY, not just WHAT?
182
+
183
+ **The prompt IS your pedagogical design!**
184
+ """), kind="info")
185
+
186
+ lab1_output = mo.vstack([_comparison, _reflection])
187
+
188
+ except Exception as e:
189
+ import traceback
190
+ lab1_output = mo.callout(
191
+ mo.md(f"""
192
+ ### ⚠️ Error Generating Examples
193
+
194
+ **Error type:** {type(e).__name__}
195
+
196
+ **Error message:** {str(e)}
197
+
198
+ **Full traceback:**
199
+ ```
200
+ {traceback.format_exc()}
201
+ ```
202
+
203
+ **Common fixes:**
204
+ - Make sure you have a `.env` file with `OPENAI_API_KEY=sk-...`
205
+ - Check that your API key is valid
206
+ - Ensure you have API credits available
207
+ """),
208
+ kind="danger"
209
+ )
210
+ else:
211
+ # Show this when button hasn't been clicked yet
212
+ lab1_output = mo.md("_Click the button above to generate examples_")
213
 
214
+ lab1_output
 
 
215
 
 
 
 
216
 
217
+ @app.cell
218
+ def _(mo):
219
+ mo.md("""
220
+ ---
221
 
222
+ ## βš–οΈ Lab 2: Personalization A/B Test
223
 
224
+ **Learning Question**: Can you FEEL the difference in cognitive load?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
+ ### The Experiment
227
+
228
+ You'll enter YOUR context (hobby, goal), then see the SAME concept taught:
229
+ - **Generic**: Standard textbook style
230
+ - **Personalized**: Using your context
231
+
232
+ **Hypothesis**: The personalized version should feel more engaging and easier to process.
233
+ """)
234
+ return
235
 
236
 
237
  @app.cell
238
+ def _(mo):
239
+ """Lab 2: Context inputs"""
 
240
 
241
+ mo.md("### Your Context")
242
+
243
+ your_hobby = mo.ui.text(
244
+ label="Your hobby or interest:",
245
+ placeholder="e.g., photography, cooking, gaming",
246
+ full_width=True
247
+ )
248
+
249
+ your_goal = mo.ui.text(
250
+ label="What you want to achieve:",
251
+ placeholder="e.g., build a recipe app, automate photo editing",
252
+ full_width=True
253
+ )
254
 
255
+ mo.vstack([your_hobby, your_goal])
256
+ return your_hobby, your_goal
257
 
258
 
259
  @app.cell
260
+ def _(mo):
261
+ """Lab 2: Generate button"""
262
+
263
+ lab2_button = mo.ui.button(
264
+ label="βš–οΈ Generate A/B Comparison",
265
+ kind="success"
266
+ )
267
+
268
+ mo.md(f"{lab2_button}")
269
+ return (lab2_button,)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
 
272
  @app.cell
273
+ def _(SimpleExample, client, lab2_button, mo, your_goal, your_hobby):
274
+ """Lab 2: Generate A/B comparison"""
275
+
276
+ lab2_output = None
277
+
278
+ if lab2_button.value and your_hobby.value and your_goal.value:
279
+ try:
280
+ with mo.status.spinner(title="Generating generic and personalized examples..."):
281
+ # Generic example
282
+ generic_prompt = "Create a worked example about Python dictionaries for beginners."
283
+ generic_response = client.responses.parse(
284
+ model="gpt-4o-mini",
285
+ input=[{"role": "user", "content": generic_prompt}],
286
+ text_format=SimpleExample
287
+ )
288
+ generic_example = generic_response.output_parsed
289
+
290
+ # Personalized example
291
+ personalized_prompt = f"""Create a worked example about Python dictionaries for beginners.
292
+
293
+ IMPORTANT: Personalize this example for someone who is interested in {your_hobby.value} and wants to {your_goal.value}.
294
+ Use familiar contexts and examples from their interest to make the concept more relatable and reduce cognitive load."""
295
+
296
+ personalized_response = client.responses.parse(
297
+ model="gpt-4o-mini",
298
+ input=[{"role": "user", "content": personalized_prompt}],
299
+ text_format=SimpleExample
300
+ )
301
+ personalized_example = personalized_response.output_parsed
302
+
303
+ _comparison = mo.vstack([
304
+ mo.md("### πŸ“– Generic Example (Standard Textbook Style)"),
305
+ mo.md(f"**Problem:** {generic_example.problem}"),
306
+ mo.md(f"**Solution:** {generic_example.solution}"),
307
+ mo.md(f"**Explanation:** {generic_example.explanation}"),
308
+ mo.md("---"),
309
+ mo.md(f"### ✨ Personalized Example (Your Context: {your_hobby.value})"),
310
+ mo.md(f"**Problem:** {personalized_example.problem}"),
311
+ mo.md(f"**Solution:** {personalized_example.solution}"),
312
+ mo.md(f"**Explanation:** {personalized_example.explanation}"),
313
+ ])
314
+
315
+ _reflection = mo.callout(mo.md("""
316
+ ### πŸ’­ How Did That Feel?
317
+
318
+ - Which example was more engaging to read?
319
+ - Which one felt easier to process mentally?
320
+ - Could you visualize the personalized example more easily?
321
+
322
+ **This is the personalization effect in action!** Familiar contexts reduce extraneous cognitive load.
323
+ """), kind="success")
324
+
325
+ lab2_output = mo.vstack([_comparison, _reflection])
326
+
327
+ except Exception as e:
328
+ lab2_output = mo.callout(
329
+ mo.md(f"""
330
+ ### ⚠️ Error Generating Examples
331
+
332
+ **Error:** {str(e)}
333
+
334
+ Check your `.env` file and API key.
335
+ """),
336
+ kind="danger"
337
+ )
338
+
339
+ lab2_output
340
 
341
 
342
  @app.cell
343
+ def _(mo):
344
+ mo.md("""
345
+ ---
346
+
347
+ ## πŸ—οΈ Lab 3: Data Model Designer
348
+
349
+ **Learning Question**: What makes a worked example "worked"?
350
+
351
+ ### The Experiment
352
+
353
+ Design the data structure for a worked example. What fields do you need?
354
+ Think about:
355
+ - What cognitive load principle does each field support?
356
+ - How does structure guide the AI's output?
357
+
358
+ **Current Model** (you can modify this in your mind):
359
+ ```python
360
+ class WorkedExample:
361
+ problem: str # What they need to solve
362
+ solution_steps: list # Broken into chunks (why a list?)
363
+ final_answer: str # Clear conclusion
364
+ key_insight: str # Schema activation
365
+ ```
366
+ """)
367
  return
368
 
369
 
370
  @app.cell
371
+ def _(mo):
372
+ """Lab 3: Interactive field selector"""
373
+
374
+ mo.md("### Which Fields Support Learning?")
375
+
376
+ field_options = {
377
+ "problem: str": "The problem statement",
378
+ "solution_steps: list[str]": "Steps as a list (chunking!)",
379
+ "solution: str": "Solution as one big block",
380
+ "final_answer: str": "Explicit conclusion",
381
+ "key_insight: str": "Why this approach works",
382
+ "code_with_comments: str": "Annotated code",
383
+ "common_mistakes: str": "What to avoid",
384
+ "connection_to_real_world: str": "Practical relevance"
385
+ }
386
 
387
+ field_selector = mo.ui.multiselect(
388
+ options=list(field_options.keys()),
389
+ label="Select fields for YOUR ideal worked example:",
390
+ value=["problem: str", "solution_steps: list[str]", "final_answer: str", "key_insight: str"]
391
  )
392
 
393
+ field_selector
394
+ return (field_selector,)
 
 
 
 
 
 
 
 
395
 
 
 
 
 
 
396
 
397
+ @app.cell
398
+ def _(field_selector, mo):
399
+ """Lab 3: Display selection count"""
400
+ mo.md(f"**You selected {len(field_selector.value)} fields**")
401
+ return
402
 
 
 
 
 
 
403
 
404
+ @app.cell
405
+ def _(field_selector, mo):
406
+ """Lab 3: Analysis"""
 
 
 
407
 
408
+ if field_selector.value:
409
+ mo.md(f"""
410
+ ### Your Selected Structure
411
 
412
+ ```python
413
+ class WorkedExample:
414
+ {chr(10).join([' ' + f for f in field_selector.value])}
415
+ ```
416
 
417
+ ### πŸ’­ Design Analysis
 
 
418
 
419
+ **Key Questions:**
420
+ - Did you choose `solution_steps: list[str]` or `solution: str`?
421
+ - **List = chunking** (reduces cognitive load)
422
+ - **String = one big block** (higher load for novices)
423
+
424
+ - Did you include `key_insight`?
425
+ - Helps with **schema activation** (connecting to prior knowledge)
426
+
427
+ - Did you include `common_mistakes`?
428
+ - **Desirable difficulty**: learning from contrasts
429
 
430
+ **The design IS the pedagogy**. Each field choice implements a CLT principle.
431
+ """)
432
  return
433
 
434
 
435
  @app.cell
436
+ def _(mo):
437
+ mo.md("""
438
+ ---
439
+
440
+ ## πŸŽ›οΈ Lab 4: Parameter Playground
 
 
 
 
 
 
441
 
442
+ **Learning Question**: How do model parameters affect pedagogical quality?
443
+
444
+ ### The Experiment
445
+
446
+ GPT-5.1 has parameters like `reasoning.effort`. Try different settings and see
447
+ how they affect example quality.
448
+
449
+ **Note**: This lab is conceptual---showing the parameters you COULD control.
450
+ """)
451
+ return
452
 
453
 
454
  @app.cell
455
+ def _(mo):
456
+ """Lab 4: Parameter sliders"""
457
+
458
+ mo.md("### Adjust Parameters")
 
 
 
 
 
 
 
 
 
 
459
 
460
+ reasoning_effort = mo.ui.dropdown(
461
+ options=["none", "low", "medium", "high"],
462
+ value="low",
463
+ label="Reasoning Effort (how much thinking?)"
464
+ )
465
+
466
+ verbosity = mo.ui.dropdown(
467
+ options=["low", "medium", "high"],
468
+ value="medium",
469
+ label="Verbosity (explanation detail)"
470
+ )
471
+
472
+ mo.vstack([reasoning_effort, verbosity])
473
+ return reasoning_effort, verbosity
474
 
475
 
476
  @app.cell
477
+ def _(mo, reasoning_effort, verbosity):
478
+ """Lab 4: Display parameter info"""
479
+ mo.callout(mo.md(f"""
480
+ **Current Settings:**
481
+
482
+ - Reasoning: {reasoning_effort.value}
483
+ - Verbosity: {verbosity.value}
 
 
 
 
 
 
 
 
 
 
484
 
485
+ **For novices**: Low reasoning (fast), medium-high verbosity (detailed explanations)
486
+
487
+ **For experts**: Higher reasoning (better solutions), lower verbosity (concise)
488
+
489
+ The "best" parameters depend on your learners!
490
+ """), kind="info")
491
  return
492
 
493
 
494
  @app.cell
495
+ def _(mo):
496
+ mo.md("""
497
+ ---
498
 
499
+ ## πŸ” Lab 5: CLT Analyzer
 
500
 
501
+ **Learning Question**: Can you evaluate examples using CLT principles?
502
+
503
+ ### The Experiment
504
+
505
+ Read an AI-generated example and evaluate it against CLT criteria.
506
+ This develops your **critical lens** for educational AI.
507
+ """)
508
  return
509
 
510
 
511
  @app.cell
512
+ def _(mo):
513
+ """Lab 5: Generate button"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
 
515
+ mo.md("### Generate an Example to Analyze")
516
+
517
+ lab5_button = mo.ui.button(
518
+ label="🎲 Generate Random Example",
519
+ kind="neutral"
520
+ )
521
+
522
+ lab5_button
523
+ return (lab5_button,)
524
 
525
 
526
  @app.cell
527
+ def _(SimpleExample, client, lab5_button, mo):
528
+ """Lab 5: Generate and display example to analyze"""
529
+
530
+ lab5_output = None
531
+
532
+ if lab5_button.value:
533
+ try:
534
+ with mo.status.spinner(title="Generating example..."):
535
+ response = client.responses.parse(
536
+ model="gpt-4o-mini",
537
+ input=[{"role": "user", "content": "Create a worked example about Python dictionaries for beginners."}],
538
+ text_format=SimpleExample
539
+ )
540
+ analyze_example = response.output_parsed
 
 
 
541
 
542
+ lab5_output = mo.vstack([
543
+ mo.md("### Example to Analyze"),
544
+ mo.md(f"**Problem:** {analyze_example.problem}"),
545
+ mo.md(f"**Solution:** {analyze_example.solution}"),
546
+ mo.md(f"**Explanation:** {analyze_example.explanation}"),
547
+ ])
548
+
549
+ except Exception as e:
550
+ lab5_output = mo.callout(
551
+ mo.md(f"""
552
+ ### ⚠️ Error Generating Example
553
+
554
+ **Error:** {str(e)}
555
+
556
+ Check your `.env` file and API key.
557
+ """),
558
+ kind="danger"
559
+ )
560
+
561
+ lab5_output
562
 
563
 
564
  @app.cell
565
+ def _(mo):
566
+ """Lab 5: CLT evaluation checklist"""
567
 
568
+ mo.md("### Evaluate Using CLT Principles")
569
 
570
+ reduces_extraneous = mo.ui.checkbox(
571
+ label="βœ… Reduces extraneous cognitive load (no unnecessary complexity)"
572
+ )
 
 
 
 
573
 
574
+ manages_intrinsic = mo.ui.checkbox(
575
+ label="βœ… Manages intrinsic load (breaks problem into chunks)"
576
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
+ optimizes_germane = mo.ui.checkbox(
579
+ label="βœ… Optimizes germane load (helps build schemas/patterns)"
580
+ )
581
 
582
+ worked_not_problem = mo.ui.checkbox(
583
+ label="βœ… Is a WORKED example (shows complete solution, not a puzzle)"
584
+ )
585
 
586
+ clear_steps = mo.ui.checkbox(
587
+ label="βœ… Has clear step-by-step progression"
588
+ )
589
 
590
+ explains_why = mo.ui.checkbox(
591
+ label="βœ… Explains WHY, not just WHAT"
592
+ )
593
 
594
+ mo.vstack([
595
+ reduces_extraneous,
596
+ manages_intrinsic,
597
+ optimizes_germane,
598
+ worked_not_problem,
599
+ clear_steps,
600
+ explains_why
601
+ ])
602
+ return (
603
+ clear_steps,
604
+ explains_why,
605
+ manages_intrinsic,
606
+ optimizes_germane,
607
+ reduces_extraneous,
608
+ worked_not_problem,
609
+ )
610
 
 
611
 
612
+ @app.cell
613
+ def _(
614
+ clear_steps,
615
+ explains_why,
616
+ manages_intrinsic,
617
+ mo,
618
+ optimizes_germane,
619
+ reduces_extraneous,
620
+ worked_not_problem,
621
+ ):
622
+ """Lab 5: Scoring"""
623
+
624
+ checklist_values = [
625
+ reduces_extraneous.value,
626
+ manages_intrinsic.value,
627
+ optimizes_germane.value,
628
+ worked_not_problem.value,
629
+ clear_steps.value,
630
+ explains_why.value
631
+ ]
632
+
633
+ score = sum(1 for v in checklist_values if v)
634
+
635
+ if score > 0:
636
+ mo.callout(f"""
637
+ ### Score: {score}/6
638
+
639
+ {"🌟" * score}
640
+
641
+ **Interpretation:**
642
+ - 5-6: Excellent pedagogical design
643
+ - 3-4: Good, but room for improvement
644
+ - 1-2: Needs significant pedagogical revision
645
+ - 0: Not yet evaluated
646
+
647
+ **Key Skill**: You're developing a CLT-grounded critical lens for evaluating AI tools!
648
+ """, kind="success" if score >= 5 else "info")
649
+ return
650
 
 
 
 
 
651
 
652
+ @app.cell
653
+ def _(mo):
654
+ mo.md("""
655
+ ---
656
 
657
+ ## 🎯 Conclusion: From Exploration to Creation
 
 
658
 
659
+ ### What You Discovered
 
 
660
 
661
+ Through these 5 labs, you explored:
662
 
663
+ 1. βœ… **Prompts encode pedagogy** - Design drives outputs
664
+ 2. βœ… **Personalization reduces load** - Context matters
665
+ 3. βœ… **Structure shapes learning** - Data models are pedagogical choices
666
+ 4. βœ… **Parameters affect quality** - Settings have learning implications
667
+ 5. βœ… **Critical evaluation is a skill** - You can assess AI tools with CLT
668
 
669
+ ### What's Next?
670
 
671
+ Now that you understand the **design principles**, you're ready to:
672
 
673
+ **Option 1: Build Your Own Tool**
674
+ - Use the simplified code from the workshop
675
+ - Apply these design principles
676
+ - Deploy to HuggingFace Spaces
 
 
 
677
 
678
+ **Option 2: Use the Complete Tool**
679
+ - [Try the full Worked Example Weaver](https://huggingface.co/spaces/virtuelleakademie/worked-example-weaver-app)
680
+ - See all 5 principles integrated
681
 
682
+ **Option 3: Adapt to Your Domain**
683
+ - Take the template
684
+ - Add your concepts
685
+ - Customize for your learners
686
 
687
+ ### The Big Idea
 
 
688
 
689
+ AI tools for education should be **grounded in learning science**, not just technically impressive.
 
690
 
691
+ You now have:
692
+ - 🧠 The theoretical foundation (CLT)
693
+ - πŸ”¬ Hands-on experience (these labs)
694
+ - πŸ› οΈ The technical skills (simple OpenAI API)
695
+ - 🎯 A critical lens (can evaluate tools)
696
 
697
+ **Go build something that helps people learn!**
 
698
 
699
  ---
700
 
701
+ *Created by the [Virtual Academy](https://virtuelleakademie.ch/), BFH*
702
  """)
 
703
  return
704
 
705