Build a Kids Study Buddy with Claude

Build a Python CLI app that quizzes kids using the Claude API.

Introduction

30 Second Summary

Every kid learns differently. Some love dinosaurs, others are obsessed with space, and some just want to solve math puzzles all day.

In this project, you will build an interactive command-line Study Buddy that uses the Claude API to generate fun quiz questions for kids on any subject they choose.

What You'll Build

A Python CLI application that connects to Claude and generates adaptive, multi-turn quiz conversations from the terminal.

By the end of this project, you'll have:

  • A Python script that sends structured prompts to the Claude API and processes responses.
  • A system prompt that instructs Claude to act as a fun, kid-friendly quiz tutor.
  • A multi-turn conversation loop with score tracking and difficulty adjustment.
  • Secret Mission: Add a Story Mode that weaves correct answers into an adventure narrative.

Are there any prerequisites?

No prior experience is needed. This guide walks you through installing every tool from scratch, including Python, VS Code, and the Anthropic SDK.

You will need to purchase a minimum $5 credit on the Anthropic Console to activate API access. The project itself costs less than $0.05 to run.

Before We Start

Before We Start...

Set Up Your Development Environment

The Study Buddy app is a Python script that talks to the Claude API. Before writing any code, you need the right tools installed and a clean workspace ready.

In this step, you will install Python 3, set up VS Code as your code editor, and create an isolated environment for your project's dependencies.

In this step, get ready to:
  • Install and verify Python 3.
  • Set up VS Code with a new project folder.
  • Create and activate a virtual environment.
Install Python 3 and verify it works

Python is the programming language your Study Buddy will be written in. You need Python 3 installed so you can run scripts and install packages from the terminal.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check if Python 3 is installed by running this command:
python3 --version

✔️ I see a version number starting with 3

You should see something like Python 3.12.4 (the exact number may differ). Python 3 is ready to go.

ⓧ Command not found

You need to install Python 3. Follow these steps:

  • Go to python.org/downloads in your browser.
  • Click the large Download Python 3 button.
  • Run the installer. On Windows, check the box that says Add python.exe to PATH before clicking Install Now.
  • Once installed, close and reopen your terminal, then run python3 --version again to confirm it works.

Windows users

If python3 still does not work after installing, try python --version instead. Some Windows installations register the command as python rather than python3. Use whichever command shows a version number for the rest of this project.

Still not working?

Make sure you fully closed and reopened your terminal after installing Python. The terminal needs to reload its PATH settings to find the new command.

Help me troubleshoot my Python installation

Install VS Code and open a new project folder

VS Code is a free code editor that gives you syntax highlighting, an integrated terminal, and file management in one window.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter.

✔️ VS Code opens

You are all set. Move on to creating your project folder below.

ⓧ I don't have VS Code installed

  • Go to code.visualstudio.com in your browser.
  • Click the Download button for your operating system.
  • Run the installer and follow the prompts.
  • Once installed, open VS Code using the search bar instructions above.

Now create your project folder. Go back to your terminal (it should still be open from the Python check).

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir study-buddy
cd study-buddy
  • Confirm the folder was created by running:
ls

The folder will be empty for now. That is expected.

Now open this folder in VS Code.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the study-buddy folder, then click Open.

You should see study-buddy in the VS Code sidebar on the left. The folder is empty, which is correct.

Don't see the folder in the sidebar?

Make sure you selected the study-buddy folder itself, not a file inside it or the Desktop folder. The sidebar should show the folder name at the top of the Explorer panel.

Help me open my project folder in VS Code

Create and activate a Python virtual environment

A virtual environment is an isolated space where Python packages for this project live. It prevents conflicts with packages from other projects on your computer.

  • Open VS Code's integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).

What is the integrated terminal?

VS Code has a built-in terminal at the bottom of the window. It works just like your system terminal but automatically opens inside your project folder. You will use this terminal for the rest of the project.

  • Create a virtual environment by running this command:
python3 -m venv venv

What does this command do?

python3 -m venv tells Python to run its built-in virtual environment module. The second venv is the name of the folder where the environment files will live.

  • Activate the virtual environment using the command for your operating system:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

After activation, you should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

Make sure you ran the activation command from inside the study-buddy folder. The venv folder must exist in your current directory for the activation command to find it.

On Windows, if you see a permissions error in PowerShell, you may need to allow script execution first. Run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned and then try the activation command again.

Help me activate my virtual environment

Your development environment is ready. Next up, you will create an Anthropic account and get your Claude API key so your app can talk to the AI.

Get Your Claude API Key

Your development environment is ready. Python is installed, your virtual environment is active, and VS Code is open with your project folder.

Now you need a way for your Python code to communicate with Claude. The Claude API is the bridge that lets your script send messages to Claude and receive responses. To use it, you need an API key from Anthropic.

In this step, get ready to:
  • Create an Anthropic Console account.
  • Purchase credits to activate API access.
  • Generate an API key and store it as an environment variable.
Create an Anthropic Console account

The Anthropic Console is the web dashboard where you manage your API keys, billing, and usage. You need an account here before you can generate a key.

  • Open your browser and go to console.anthropic.com.
  • Click Sign Up to create a new account.
  • Complete the sign-up process using your email or a Google account.
  • Verify your email if prompted, then log in to the Console.

What is the Anthropic Console?

The Anthropic Console is where you manage everything related to the Claude API. You create API keys here, monitor your usage, and manage billing.

Think of it as your control panel for all things Claude API.

Purchase credits to activate API access

Anthropic requires a minimum $5 credit purchase to activate Tier 1 API access. New users may receive a small amount of free credits to test the API, but purchasing credits unlocks full access.

  • In the Anthropic Console, click on Settings in the left sidebar.
  • Select Billing from the settings menu.
  • Click Add Payment Method and enter your card details.
  • Purchase a minimum of $5 in credits to activate Tier 1 access.

Why do I need to buy credits?

Anthropic uses a tiered system for API access. Tier 1 is the entry level, and it requires a $5 minimum credit purchase to activate. This is a pay-as-you-go model.

For this project, you will spend well under $0.05 total. Each quiz session uses roughly 5,000-10,000 tokens, which costs less than $0.01 at Claude Haiku 4.5 pricing ($1/MTok input, $5/MTok output).

Generate your API key and store it as an environment variable

An API key is a secret string that identifies your application when it talks to Claude. You will store it as an environment variable so your Python code can access it without you hardcoding the secret into your files.

  • In the Anthropic Console, click API Keys in the left sidebar.
  • Click Create Key.
  • Give your key a name like study-buddy.
  • Copy the key immediately. You will not be able to see it again after closing this dialog.

Keep your API key safe

Your API key is like a password. Never share it publicly, commit it to Git, or paste it directly into your code files. Store it only as an environment variable.

  • Store your API key as an environment variable by running this command in your VS Code terminal (where your virtual environment is active):

macOS

export ANTHROPIC_API_KEY='your-api-key-here'

Replace your-api-key-here with the key you just copied. Keep the single quotes around it.

Windows

$env:ANTHROPIC_API_KEY="your-api-key-here"

Replace your-api-key-here with the key you just copied. Keep the double quotes around it.

Why an environment variable?

The Anthropic Python SDK automatically reads from the ANTHROPIC_API_KEY environment variable. This means your code never needs to contain the actual key value.

This approach keeps your secret out of your source code. If you ever share your code or push it to GitHub, the key stays safe on your machine.

  • Verify your key is stored by running this command:

macOS

echo $ANTHROPIC_API_KEY

Windows

echo $env:ANTHROPIC_API_KEY

You should see your API key printed in the terminal. If you see a blank line or nothing at all, re-run the export command from above.

Don't see your key printed?

Make sure you ran the export command in the same terminal session where your virtual environment is active. If you opened a new terminal tab or window, the variable will not carry over.

Also check that you did not accidentally include extra spaces around the = sign in the command.

help me set up my ANTHROPIC_API_KEY environment variable

Your API key is set and ready. Next up, you will install the Anthropic SDK and make your first API call to Claude.

Install the SDK and Make Your First API Call

Your environment is ready and your API key is securely stored. Now it's time to connect your Python code to Claude.

Anthropic provides an official Python SDK that handles authentication, request formatting, and response parsing for you. In this step, you'll install it, confirm your connection works, and set up the main project file.

In this step, get ready to:
  • Install the Anthropic Python package in your virtual environment.
  • Write a test script that sends a message to Claude and prints the response.
  • Verify the connection works and understand the response structure.
Install the Anthropic Python package

The Anthropic Python SDK is the official library for communicating with the Claude API. It wraps all the HTTP details so you can focus on sending messages and reading responses.

  • In your VS Code terminal (with your virtual environment still activated), install the SDK by running this command:
pip install anthropic
  • Wait for the installation to finish. You should see Successfully installed anthropic along with its dependencies listed in the terminal output.

Seeing a 'pip not found' error?

Make sure your virtual environment is activated. You should see (venv) at the start of your terminal prompt. If it's missing, re-activate the environment by running the activation command from the previous step.

help me fix a pip not found error when trying to install anthropic in my virtual environment

Write a test script to verify your API connection

Before building the full Study Buddy, you'll write a quick test script to confirm everything is wired up correctly. This proves your API key works and shows you the shape of a Claude response.

  • In VS Code, create a new file called test_api.py inside your study-buddy folder.
  • Click File in the top menu bar, then New File.
  • Save it immediately with Cmd+S (macOS) or Ctrl+S (Windows) and name it test_api.py.
  • Copy the following code into test_api.py:
import anthropic

# Create the client (automatically reads ANTHROPIC_API_KEY from your environment)
client = anthropic.Anthropic()

# Send a simple test message to Claude
response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=100,
    messages=[
        {"role": "user", "content": "Say hello in one sentence!"}
    ],
)

# Print Claude's response text
print(response.content[0].text)

What does this code do?

  • anthropic.Anthropic() creates a client that automatically reads your ANTHROPIC_API_KEY environment variable. You never need to paste your key into code.
  • client.messages.create() sends a message to Claude. The model parameter picks which Claude model to use.
  • response.content[0].text extracts the text from Claude's reply. The response contains a list of content blocks, and the first one holds the text.

Why claude-haiku-4-5?

Claude Haiku 4.5 is Anthropic's fastest and most affordable model at $1 per million input tokens and $5 per million output tokens. A full 10-question quiz session costs well under $0.01. For a kid-friendly quiz app, Haiku is more than capable and keeps your costs minimal.

  • Save test_api.py.
  • Run the test script by entering this command in your terminal:
python test_api.py

You should see a short, friendly greeting printed in the terminal. Something like "Hello! How can I help you today?" The exact words will vary each time you run it.

Seeing an authentication error?

This means your ANTHROPIC_API_KEY environment variable is not being found. Check that you exported it in the same terminal session where you're running the script. If you opened a new terminal window, you'll need to set the variable again.

Also confirm your API key is valid and that you've purchased the $5 credit to activate Tier 1 access on your Anthropic Console account.

help me debug an authentication error when calling the Claude API with my Python script

Create your main project file

Now that you've confirmed the API works, it's time to start the real project file. You'll set up study_buddy.py with the imports and client connection you'll build on for the rest of the project.

  • Create a new file in VS Code called study_buddy.py inside your study-buddy folder.
  • Add the following imports and client setup by copying this code into the file:
import anthropic
import os
import json
import re

# Create the Anthropic client (reads API key from environment automatically)
client = anthropic.Anthropic()

What does this code do?

  • anthropic is the SDK you just installed. It handles all communication with the Claude API.
  • os, json, and re are Python standard libraries you'll use later for environment variables, data handling, and parsing Claude's responses.
  • anthropic.Anthropic() creates a reusable client instance. Every API call you make later will go through this client object.
  • Save study_buddy.py.
  • Confirm the file exists by running:
ls

You should see both test_api.py and study_buddy.py listed alongside your venv folder.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete study_buddy.py so far:

import anthropic
import os
import json
import re

client = anthropic.Anthropic()

Seeing an import error when running study_buddy.py?

If you try running the file and see an error about anthropic not being found, make sure your virtual environment is still activated. The (venv) prefix should appear in your terminal prompt.

help me fix a ModuleNotFoundError for anthropic in my Python project

Your API connection is confirmed and your project file is ready. Next up, you'll write the system prompt that turns Claude into a kid-friendly quiz tutor.

Design the Study Buddy System Prompt

Your API connection is confirmed and working. Now it's time to shape how Claude behaves by giving it a system prompt.

A system prompt is a set of instructions that defines an AI's personality and rules before any conversation starts. In this step, you'll write a system prompt that transforms Claude into a kid-friendly quiz tutor, then build helper functions to reliably parse its responses.

In this step, get ready to:
  • Write a system prompt that makes Claude act as an encouraging quiz tutor for kids.
  • Build a parser to extract questions, answer options, and correct answers from Claude's responses.
  • Add difficulty adjustment logic and test the full system.
Write the Study Buddy system prompt

The system prompt tells Claude exactly what character to play and what rules to follow. For Study Buddy, Claude needs to generate multiple-choice questions in a strict, predictable format so your code can reliably extract each piece.

  • In study_buddy.py, find the line that says client = anthropic.Anthropic(). Below that line, add the following system prompt constant:
SYSTEM_PROMPT = """You are Study Buddy, a fun and encouraging quiz tutor for kids aged 7-12.

Rules:
1. When the user picks a subject, generate a multiple-choice question about that subject.
2. Format EVERY question exactly like this (with no other text before or after):
QUESTION: [Your question here]
A) [Option A]
B) [Option B]
C) [Option C]
D) [Option D]
ANSWER: [Correct letter]
EXPLANATION: [A brief, fun explanation in 1-2 sentences]

3. Start at an easy level. If the user says "harder", increase difficulty. If they say "easier", decrease it.
4. Keep questions age-appropriate and fun.
5. After the user answers, respond with encouraging feedback, then give the next question in the same format.
6. Use simple language and add fun facts when relevant.
7. The current difficulty level is provided in the user message. Adjust question complexity accordingly.
"""

What does this system prompt do?

This prompt has two jobs. First, it sets Claude's personality as a fun, encouraging tutor for kids aged 7-12. Second, it enforces a strict output format with labeled fields (QUESTION, A-D options, ANSWER, EXPLANATION).

The strict format is critical. Your code will parse Claude's response by looking for these exact labels. Without consistent formatting, the parser would break on every response.

You won't see a change yet. The parser function in the next substep uses this prompt.

Build the response parser

Claude will respond with text that follows the format defined in your system prompt. You need a function that extracts each piece (the question, the four options, the correct answer, and the explanation) into a structured Python dictionary your game loop can use.

  • Below the SYSTEM_PROMPT constant, add the parse_question function:
def parse_question(response_text):
    """Parse Claude's response into question components."""
    result = {}

    # Extract the question text
    question_match = re.search(r"QUESTION:\s*(.+?)(?=\nA\))", response_text, re.DOTALL)
    if question_match:
        result["question"] = question_match.group(1).strip()

    # Extract each multiple-choice option
    options = {}
    for letter in ["A", "B", "C", "D"]:
        option_match = re.search(rf"{letter}\)\s*(.+?)(?=\n[A-D]\)|$|\nANSWER:)", response_text, re.DOTALL)
        if option_match:
            options[letter] = option_match.group(1).strip()
    result["options"] = options

    # Extract the correct answer letter
    answer_match = re.search(r"ANSWER:\s*([A-D])", response_text)
    if answer_match:
        result["answer"] = answer_match.group(1).strip()

    # Extract the explanation
    explanation_match = re.search(r"EXPLANATION:\s*(.+)", response_text, re.DOTALL)
    if explanation_match:
        result["explanation"] = explanation_match.group(1).strip()

    return result

What does this code do?

Each re.search() call uses a regex pattern to find one labeled section of Claude's response. The re.DOTALL flag lets the . character match newlines too, so multi-line questions still get captured.

The function returns a dictionary with keys like question, options, answer, and explanation. Your game loop will use these to display the quiz and check answers.

You won't see a change yet. The next substep tests this along with the system prompt.

Add difficulty adjustment and test the prompt

Study Buddy should get harder when a kid is doing well and easier when they're struggling. A simple function that checks their score percentage handles this. After adding it, you'll run a quick test to confirm the system prompt and parser work together.

  • Below parse_question, add the get_difficulty_label function:
def get_difficulty_label(score, total):
    """Determine difficulty based on score percentage."""
    if total == 0:
        return "easy"
    percentage = score / total
    if percentage >= 0.8:
        return "hard"
    elif percentage >= 0.5:
        return "medium"
    else:
        return "easy"

What does this code do?

This function takes the learner's current score and total questions answered, then returns a difficulty label. If they haven't answered any questions yet, it defaults to easy. If they're getting 80% or more correct, it bumps up to hard.

Later, your game loop will pass this label into each message to Claude so it can adjust question complexity on the fly.

Now let's test the system prompt and parser together. You'll add a temporary test block at the bottom of your file, run it, then remove it.

  • At the very bottom of study_buddy.py, add this temporary test code:
# --- Temporary test (delete after verifying) ---
test_response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    system=SYSTEM_PROMPT,
    messages=[{"role": "user", "content": "Subject: dinosaurs. Difficulty: easy. Please ask me the first question!"}],
)

print(test_response.content[0].text)
print("\n--- Parsed Result ---")
parsed = parse_question(test_response.content[0].text)
for key, value in parsed.items():
    print(f"  {key}: {value}")
  • Save study_buddy.py.
  • Run the test by entering this command in your terminal:
python study_buddy.py
  • You should see Claude's formatted response (with QUESTION, options A-D, ANSWER, and EXPLANATION), followed by the parsed dictionary showing each field extracted cleanly.

Not seeing parsed output?

  • If you see an authentication error, make sure your ANTHROPIC_API_KEY environment variable is still set in this terminal session. Reactivate your virtual environment and re-export the key if needed.
  • If the parsed result shows empty values, check that your SYSTEM_PROMPT has the exact format labels (QUESTION:, A), B), C), D), ANSWER:, EXPLANATION:) with no typos.
  • If you see a ModuleNotFoundError, confirm your virtual environment is active (you should see (venv) in your terminal prompt).

help me debug my Study Buddy system prompt test

  • Once the test runs successfully, delete the temporary test code block (everything below the get_difficulty_label function) from study_buddy.py.
  • Save study_buddy.py again.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file with the test code removed.

ⓧ I'd like to double check the full code

Here's what study_buddy.py should look like at the end of this step:

import anthropic
import os
import json
import re

client = anthropic.Anthropic()

SYSTEM_PROMPT = """You are Study Buddy, a fun and encouraging quiz tutor for kids aged 7-12.

Rules:
1. When the user picks a subject, generate a multiple-choice question about that subject.
2. Format EVERY question exactly like this (with no other text before or after):
QUESTION: [Your question here]
A) [Option A]
B) [Option B]
C) [Option C]
D) [Option D]
ANSWER: [Correct letter]
EXPLANATION: [A brief, fun explanation in 1-2 sentences]

3. Start at an easy level. If the user says "harder", increase difficulty. If they say "easier", decrease it.
4. Keep questions age-appropriate and fun.
5. After the user answers, respond with encouraging feedback, then give the next question in the same format.
6. Use simple language and add fun facts when relevant.
7. The current difficulty level is provided in the user message. Adjust question complexity accordingly.
"""

def parse_question(response_text):
    """Parse Claude's response into question components."""
    result = {}

    question_match = re.search(r"QUESTION:\s*(.+?)(?=\nA\))", response_text, re.DOTALL)
    if question_match:
        result["question"] = question_match.group(1).strip()

    options = {}
    for letter in ["A", "B", "C", "D"]:
        option_match = re.search(rf"{letter}\)\s*(.+?)(?=\n[A-D]\)|$|\nANSWER:)", response_text, re.DOTALL)
        if option_match:
            options[letter] = option_match.group(1).strip()
    result["options"] = options

    answer_match = re.search(r"ANSWER:\s*([A-D])", response_text)
    if answer_match:
        result["answer"] = answer_match.group(1).strip()

    explanation_match = re.search(r"EXPLANATION:\s*(.+)", response_text, re.DOTALL)
    if explanation_match:
        result["explanation"] = explanation_match.group(1).strip()

    return result

def get_difficulty_label(score, total):
    """Determine difficulty based on score percentage."""
    if total == 0:
        return "easy"
    percentage = score / total
    if percentage >= 0.8:
        return "hard"
    elif percentage >= 0.5:
        return "medium"
    else:
        return "easy"

Your system prompt and parser are locked in. Next up, you'll build the game loop that sends user answers back to Claude and keeps the quiz going.

Build the Quiz Loop

You have a system prompt that generates consistently formatted questions, and helper functions that parse and evaluate those questions. Now it is time to tie everything together into an interactive quiz.

In this step, you will build the main game loop. The loop asks Claude for a question, waits for the kid to answer, checks if they are right, and then asks Claude for the next question. Each exchange is appended to a messages list so Claude remembers the full conversation.

In this step, get ready to:
  • Create a display helper that formats questions in the terminal.
  • Build the main function that starts the quiz with an initial API call.
  • Implement the answer loop with multi-turn conversation and score tracking.
Create a display helper for quiz questions

Before building the main loop, you need a function that prints a question neatly in the terminal. This keeps the display logic separate from the game logic.

  • In study_buddy.py, find the get_difficulty_label() function you wrote in the previous step.
  • Add the following display_question() function below it:
def display_question(parsed):
    """Display a formatted question to the terminal."""
    print("\n" + "=" * 50)
    print(f"\n📚 {parsed.get('question', 'No question found')}\n")
    for letter, text in parsed.get("options", {}).items():
        print(f"  {letter}) {text}")
    print()

What does this code do?

This function takes the dictionary returned by parse_question() and prints it in a readable format.

  • A separator line of = characters visually separates each question.
  • The question text and options are printed with spacing so kids can read them easily.
  • parsed.get('question', 'No question found') provides a fallback if parsing failed.
  • Save study_buddy.py.
Set up the main function and make the first API call

The main() function is where the quiz lives. It greets the kid, asks what subject they want to study, then sends the first message to Claude to kick off the quiz.

  • Below your display_question() function, add the start of the main() function:
def main():
    # Welcome the user
    print("\n🌟 Welcome to Study Buddy! 🌟")
    print("=" * 50)
    print("\nI will quiz you on any subject you like!")
    print("Type 'quit' at any time to stop.\n")

    # Ask the kid to pick a subject
    subject = input("What subject do you want to study? (e.g., dinosaurs, space, math): ").strip()
    if not subject:
        subject = "science"

    print(f"\nAwesome! Let's learn about {subject}! 🎉\n")

    # Initialize game state
    score = 0
    total = 0
    messages = []

    # Build the first message and send it to Claude
    difficulty = "easy"
    first_message = f"Subject: {subject}. Difficulty: {difficulty}. Please ask me the first question!"
    messages.append({"role": "user", "content": first_message})

    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        system=SYSTEM_PROMPT,
        messages=messages,
    )

    # Store the assistant's response in our conversation history
    assistant_text = response.content[0].text
    messages.append({"role": "assistant", "content": assistant_text})

    # Parse and display the first question
    parsed = parse_question(assistant_text)
    display_question(parsed)

What does this code do?

  • messages = [] creates an empty list that will hold the full conversation history. This is how multi-turn conversations work with the Claude API. Every message (both user and assistant) gets appended to this list.
  • The first user message tells Claude the subject and difficulty level. Claude responds with a formatted question.
  • After getting the response, you append the assistant's reply back into messages. This means the next API call will include the full conversation so far.

Why a messages list for multi-turn conversation?

The Claude API is stateless. It does not remember previous messages on its own. To have a conversation, you send the entire message history with each request. Claude reads all prior messages and responds in context.

This is why you append both the user message and the assistant response to the messages list. By the time the kid answers their fifth question, that list contains all ten prior exchanges, giving Claude full context to continue the quiz.

  • Save study_buddy.py.
Build the answer loop with score tracking

The game loop is where the quiz becomes interactive. It waits for the kid to type A, B, C, or D, checks whether they got it right, updates the score, and asks Claude for the next question.

  • Still inside the main() function, add the following while True loop directly below the display_question(parsed) line:
    while True:
        user_input = input("Your answer (A/B/C/D) or 'quit': ").strip().upper()

        if user_input.lower() == "quit":
            break

        if user_input not in ["A", "B", "C", "D"]:
            print("❓ Please enter A, B, C, or D!")
            continue

        # Check the answer and update score
        total += 1
        correct_answer = parsed.get("answer", "")

        if user_input == correct_answer:
            score += 1
            print(f"\n✅ Correct! Great job! 🎉")
        else:
            print(f"\n❌ Not quite! The answer was {correct_answer}.")

        if parsed.get("explanation"):
            print(f"💡 {parsed['explanation']}")

        print(f"\n📊 Score: {score}/{total}")

        # Adjust difficulty based on performance
        difficulty = get_difficulty_label(score, total)

        # Build the follow-up message and send to Claude
        follow_up = f"I answered {user_input}. Difficulty: {difficulty}. Please ask the next question about {subject}."
        messages.append({"role": "user", "content": follow_up})

        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=500,
            system=SYSTEM_PROMPT,
            messages=messages,
        )

        # Append assistant response to conversation history
        assistant_text = response.content[0].text
        messages.append({"role": "assistant", "content": assistant_text})

        # Parse and display the next question
        parsed = parse_question(assistant_text)
        display_question(parsed)

What does this code do?

  • The loop runs indefinitely until the user types "quit". Invalid inputs (anything other than A, B, C, D) are caught and skipped with continue.
  • After checking the answer, get_difficulty_label() recalculates the difficulty. If the kid is getting most answers right, difficulty ramps up to "hard".
  • The follow-up message tells Claude what the user answered and the new difficulty level. Claude uses the full messages list to stay in context and generate the next question.
  • After the main() function (outside and below it, with no indentation), add the entry point guard:
if __name__ == "__main__":
    main()

What does the __name__ guard do?

This line tells Python to only run main() when you execute the file directly (like python study_buddy.py). If this file were imported into another script, main() would not run automatically.

  • Save study_buddy.py.
  • Run the quiz by entering this command in your terminal:
python study_buddy.py
  • When prompted, type a subject like dinosaurs and press Enter.
  • You should see a formatted multiple-choice question appear with four options (A through D).
  • Answer a few questions by typing A, B, C, or D. You should see feedback, your score, and then the next question.
  • Type quit to exit the quiz.

Quiz not starting or crashing?

  • If you see an authentication error, make sure your ANTHROPIC_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to export it again.
  • If you see an indentation error, make sure the while True loop is indented one level inside main() and the code inside the loop is indented two levels.
  • If questions appear but answers are not being checked, make sure your if __name__ == "__main__": line has no indentation (it sits at the very left edge of the file).

Help me debug my Study Buddy quiz loop

✔️ Awesome, I've got everything!

Great. Double check you have saved study_buddy.py and your quiz runs end-to-end.

ⓧ I'd like to double check the full code

Here is the full study_buddy.py file as it should look at the end of this step:

import anthropic
import os
import json
import re

client = anthropic.Anthropic()

SYSTEM_PROMPT = """You are Study Buddy, a fun and encouraging quiz tutor for kids aged 7-12.

Rules:
1. When the user picks a subject, generate a multiple-choice question about that subject.
2. Format EVERY question exactly like this (with no other text before or after):
QUESTION: [Your question here]
A) [Option A]
B) [Option B]
C) [Option C]
D) [Option D]
ANSWER: [Correct letter]
EXPLANATION: [A brief, fun explanation in 1-2 sentences]

3. Start at an easy level. If the user says "harder", increase difficulty. If they say "easier", decrease it.
4. Keep questions age-appropriate and fun.
5. After the user answers, respond with encouraging feedback, then give the next question in the same format.
6. Use simple language and add fun facts when relevant.
7. The current difficulty level is provided in the user message. Adjust question complexity accordingly.
"""

def parse_question(response_text):
    """Parse Claude's response into question components."""
    result = {}

    question_match = re.search(r"QUESTION:\s*(.+?)(?=\nA\))", response_text, re.DOTALL)
    if question_match:
        result["question"] = question_match.group(1).strip()

    options = {}
    for letter in ["A", "B", "C", "D"]:
        option_match = re.search(rf"{letter}\)\s*(.+?)(?=\n[A-D]\)|$|\nANSWER:)", response_text, re.DOTALL)
        if option_match:
            options[letter] = option_match.group(1).strip()
    result["options"] = options

    answer_match = re.search(r"ANSWER:\s*([A-D])", response_text)
    if answer_match:
        result["answer"] = answer_match.group(1).strip()

    explanation_match = re.search(r"EXPLANATION:\s*(.+)", response_text, re.DOTALL)
    if explanation_match:
        result["explanation"] = explanation_match.group(1).strip()

    return result

def get_difficulty_label(score, total):
    """Determine difficulty based on score percentage."""
    if total == 0:
        return "easy"
    percentage = score / total
    if percentage >= 0.8:
        return "hard"
    elif percentage >= 0.5:
        return "medium"
    else:
        return "easy"

def display_question(parsed):
    """Display a formatted question to the terminal."""
    print("\n" + "=" * 50)
    print(f"\n📚 {parsed.get('question', 'No question found')}\n")
    for letter, text in parsed.get("options", {}).items():
        print(f"  {letter}) {text}")
    print()

def main():
    print("\n🌟 Welcome to Study Buddy! 🌟")
    print("=" * 50)
    print("\nI will quiz you on any subject you like!")
    print("Type 'quit' at any time to stop.\n")

    subject = input("What subject do you want to study? (e.g., dinosaurs, space, math): ").strip()
    if not subject:
        subject = "science"

    print(f"\nAwesome! Let's learn about {subject}! 🎉\n")

    score = 0
    total = 0
    messages = []

    difficulty = "easy"
    first_message = f"Subject: {subject}. Difficulty: {difficulty}. Please ask me the first question!"
    messages.append({"role": "user", "content": first_message})

    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        system=SYSTEM_PROMPT,
        messages=messages,
    )

    assistant_text = response.content[0].text
    messages.append({"role": "assistant", "content": assistant_text})

    parsed = parse_question(assistant_text)
    display_question(parsed)

    while True:
        user_input = input("Your answer (A/B/C/D) or 'quit': ").strip().upper()

        if user_input.lower() == "quit":
            break

        if user_input not in ["A", "B", "C", "D"]:
            print("❓ Please enter A, B, C, or D!")
            continue

        total += 1
        correct_answer = parsed.get("answer", "")

        if user_input == correct_answer:
            score += 1
            print(f"\n✅ Correct! Great job! 🎉")
        else:
            print(f"\n❌ Not quite! The answer was {correct_answer}.")

        if parsed.get("explanation"):
            print(f"💡 {parsed['explanation']}")

        print(f"\n📊 Score: {score}/{total}")

        difficulty = get_difficulty_label(score, total)

        follow_up = f"I answered {user_input}. Difficulty: {difficulty}. Please ask the next question about {subject}."
        messages.append({"role": "user", "content": follow_up})

        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=500,
            system=SYSTEM_PROMPT,
            messages=messages,
        )

        assistant_text = response.content[0].text
        messages.append({"role": "assistant", "content": assistant_text})

        parsed = parse_question(assistant_text)
        display_question(parsed)

if __name__ == "__main__":
    main()

Your Study Buddy is alive and quizzing. Next up, you will add a polished game-over summary that celebrates the kid's final score and wraps up the session.

Add Difficulty Adjustment and Polish

Your quiz app works end-to-end, but it ends abruptly when the learner types 'quit'. A good learning experience needs a satisfying conclusion that summarizes how the player did.

In this step, you'll understand how the difficulty adjustment and emoji feedback systems work, then add a game over summary that gives the kid a final score and tiered encouragement.

In this step, get ready to:
  • Understand how difficulty adjusts dynamically based on the player's score.
  • Review the emoji feedback system that makes the quiz kid-friendly.
  • Add a game over summary with tiered encouragement messages.
Understand the difficulty adjustment logic

Your app already adjusts difficulty on the fly. Every time the player answers a question, the app recalculates what difficulty level the next question should be.

  • In study_buddy.py, scroll up to the get_difficulty_label() function and review how it works.

How does difficulty adjustment work?

The get_difficulty_label() function calculates the player's success rate as a percentage. If they're getting 80% or more correct, it returns "hard". If they're at 50% or above, it returns "medium". Below 50%, it stays at "easy".

This label gets passed into the follow_up message that goes to Claude. Because the system prompt tells Claude to adjust question complexity based on the difficulty level in the user message, the questions naturally get harder or easier as the kid plays.

Review the emoji feedback system

Kids respond well to immediate, visual feedback. Your quiz already uses emojis to make every answer feel rewarding or gently corrective.

  • Look inside the while True loop in your main() function to see the feedback in action.

What do the emoji indicators mean?

  • Correct answers show a green checkmark and celebration emoji to reinforce success.
  • Incorrect answers show the right answer without shaming the kid.
  • A lightbulb emoji introduces the explanation so the kid learns from every question.
  • A chart emoji displays the running score so the kid always knows where they stand.
Add a game over summary

When the player types 'quit', the while loop breaks. Right now the program just ends silently. You'll add a summary that celebrates the kid's effort and shows their final results.

  • In study_buddy.py, find the end of the while True loop inside main(). Find this line (the last line inside the loop):
        display_question(parsed)
  • Add the following game over summary code directly below the while loop (at the same indentation level as while True:, inside main()):
    # Display the final score and encouragement
    print("\n" + "=" * 50)
    print(f"\n🏆 Game Over! Final Score: {score}/{total}")
    if total > 0:
        percentage = int((score / total) * 100)
        if percentage >= 80:
            print("🌟 Amazing! You're a superstar! 🌟")
        elif percentage >= 50:
            print("👍 Great effort! Keep learning!")
        else:
            print("💪 Good try! Practice makes perfect!")
    print("\nThanks for playing Study Buddy! See you next time! 👋\n")

What does this code do?

  • After the loop breaks, it calculates the player's score as a percentage.
  • The if total > 0 check prevents a division-by-zero error if the player quits before answering any questions.
  • Three tiers of encouragement reward strong performance (80%+), solid effort (50%+), and participation (below 50%). Every tier is positive so the kid feels motivated to try again.
  • Save study_buddy.py.
  • Run the app by entering this command in your terminal:
python study_buddy.py
  • Pick any subject, answer 2-3 questions, then type quit. You should see a game over banner with your final score, a percentage-based encouragement message, and a goodbye line.

Not seeing the game over summary?

  • Make sure the game over code is OUTSIDE the while True loop but still INSIDE the main() function. It should be at the same indentation level (4 spaces) as the while keyword itself.
  • Check that you saved the file before running it.
  • If you see an IndentationError, double-check that every line in the game over block uses exactly 4 spaces of indentation (matching the while line above it).

Why isn't my game over summary showing after I type quit?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

Here's the complete study_buddy.py file for comparison.

import anthropic
import os
import json
import re

client = anthropic.Anthropic()

SYSTEM_PROMPT = """You are Study Buddy, a fun and encouraging quiz tutor for kids aged 7-12.

Rules:
1. When the user picks a subject, generate a multiple-choice question about that subject.
2. Format EVERY question exactly like this (with no other text before or after):
QUESTION: [Your question here]
A) [Option A]
B) [Option B]
C) [Option C]
D) [Option D]
ANSWER: [Correct letter]
EXPLANATION: [A brief, fun explanation in 1-2 sentences]

3. Start at an easy level. If the user says "harder", increase difficulty. If they say "easier", decrease it.
4. Keep questions age-appropriate and fun.
5. After the user answers, respond with encouraging feedback, then give the next question in the same format.
6. Use simple language and add fun facts when relevant.
7. The current difficulty level is provided in the user message. Adjust question complexity accordingly.
"""

def parse_question(response_text):
    """Parse Claude's response into question components."""
    result = {}
    
    question_match = re.search(r"QUESTION:\s*(.+?)(?=\nA\))", response_text, re.DOTALL)
    if question_match:
        result["question"] = question_match.group(1).strip()
    
    options = {}
    for letter in ["A", "B", "C", "D"]:
        option_match = re.search(rf"{letter}\)\s*(.+?)(?=\n[A-D]\)|$|\nANSWER:)", response_text, re.DOTALL)
        if option_match:
            options[letter] = option_match.group(1).strip()
    result["options"] = options
    
    answer_match = re.search(r"ANSWER:\s*([A-D])", response_text)
    if answer_match:
        result["answer"] = answer_match.group(1).strip()
    
    explanation_match = re.search(r"EXPLANATION:\s*(.+)", response_text, re.DOTALL)
    if explanation_match:
        result["explanation"] = explanation_match.group(1).strip()
    
    return result

def get_difficulty_label(score, total):
    """Determine difficulty based on score percentage."""
    if total == 0:
        return "easy"
    percentage = score / total
    if percentage >= 0.8:
        return "hard"
    elif percentage >= 0.5:
        return "medium"
    else:
        return "easy"

def display_question(parsed):
    """Display a formatted question to the terminal."""
    print("\n" + "=" * 50)
    print(f"\n📚 {parsed.get('question', 'No question found')}\n")
    for letter, text in parsed.get("options", {}).items():
        print(f"  {letter}) {text}")
    print()

def main():
    print("\n🌟 Welcome to Study Buddy! 🌟")
    print("=" * 50)
    print("\nI will quiz you on any subject you like!")
    print("Type 'quit' at any time to stop.\n")
    
    subject = input("What subject do you want to study? (e.g., dinosaurs, space, math): ").strip()
    if not subject:
        subject = "science"
    
    print(f"\nAwesome! Let's learn about {subject}! 🎉\n")
    
    score = 0
    total = 0
    messages = []
    
    difficulty = "easy"
    first_message = f"Subject: {subject}. Difficulty: {difficulty}. Please ask me the first question!"
    messages.append({"role": "user", "content": first_message})
    
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        system=SYSTEM_PROMPT,
        messages=messages,
    )
    
    assistant_text = response.content[0].text
    messages.append({"role": "assistant", "content": assistant_text})
    
    parsed = parse_question(assistant_text)
    display_question(parsed)
    
    while True:
        user_input = input("Your answer (A/B/C/D) or 'quit': ").strip().upper()
        
        if user_input.lower() == "quit":
            break
        
        if user_input not in ["A", "B", "C", "D"]:
            print("❓ Please enter A, B, C, or D!")
            continue
        
        total += 1
        correct_answer = parsed.get("answer", "")
        
        if user_input == correct_answer:
            score += 1
            print(f"\n✅ Correct! Great job! 🎉")
        else:
            print(f"\n❌ Not quite! The answer was {correct_answer}.")
        
        if parsed.get("explanation"):
            print(f"💡 {parsed['explanation']}")
        
        print(f"\n📊 Score: {score}/{total}")
        
        difficulty = get_difficulty_label(score, total)
        
        follow_up = f"I answered {user_input}. Difficulty: {difficulty}. Please ask the next question about {subject}."
        messages.append({"role": "user", "content": follow_up})
        
        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=500,
            system=SYSTEM_PROMPT,
            messages=messages,
        )
        
        assistant_text = response.content[0].text
        messages.append({"role": "assistant", "content": assistant_text})
        
        parsed = parse_question(assistant_text)
        display_question(parsed)
    
    print("\n" + "=" * 50)
    print(f"\n🏆 Game Over! Final Score: {score}/{total}")
    if total > 0:
        percentage = int((score / total) * 100)
        if percentage >= 80:
            print("🌟 Amazing! You're a superstar! 🌟")
        elif percentage >= 50:
            print("👍 Great effort! Keep learning!")
        else:
            print("💪 Good try! Practice makes perfect!")
    print("\nThanks for playing Study Buddy! See you next time! 👋\n")

if __name__ == "__main__":
    main()

Your Study Buddy is fully polished and ready for kids to use. Next up, the Secret Mission challenges you to add a Story Mode that turns the quiz into an adventure.

Secret mission

Add Story Mode

What if each correct answer advanced an adventure story instead of just adding to a score? In this secret mission, you will extend Study Buddy with a Story Mode that weaves quiz questions into an ongoing narrative. Correct answers push the story forward. Wrong answers create plot twists.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally and the Claude API only charges when you actively run the script, so there are no ongoing costs while the app is idle.

Resources you used:

  • Project folder with study_buddy.py and the Python virtual environment.
  • Anthropic API key stored as the ANTHROPIC_API_KEY environment variable.
  • Anthropic Console account with pre-purchased credits.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with the Study Buddy or building new features.

  • Your project folder stays on your Desktop.
  • Your API key and credits remain active for future projects.
  • The virtual environment stays ready to activate whenever you return.

Pause - I'll come back to this later

Free up terminal resources but keep your files intact so you can return later.

  • Deactivate your virtual environment by running:
  • Close any terminal windows running the script.
deactivate

Your project files and API key remain in place. When you return, reactivate the virtual environment and run the script again.

Delete - I don't want to use this again

Remove all project resources completely.

  • Delete your project folder by running the command for your operating system:

macOS

rm -rf ~/Desktop/study-buddy

Windows

rmdir /s /q %USERPROFILE%\Desktop\study-buddy
  • Remove the environment variable from your current terminal session by running:
unset ANTHROPIC_API_KEY

If you added the variable to your shell profile (~/.zshrc or ~/.bashrc), open that file and delete the line containing ANTHROPIC_API_KEY.

  • Delete your API key from the Anthropic Console:
  • Go to the API keys page at platform.claude.com/settings/keys.
  • Click the three-dot menu next to your key.
  • Select Delete API Key.

What about my Anthropic account and credits?

Your Anthropic Console account and remaining credits stay active even after deleting the API key. You can create a new key anytime you want to build another project with Claude.

Nice Work!

Nice Work!

You just built an AI-powered Study Buddy from scratch. This is a fully working Python application that connects to the Claude API, generates quiz questions for kids, and adapts in real time based on how they're doing.

You've learned how to:

  • Built a Python CLI application that integrates with the Claude API using system prompts and multi-turn conversations.
  • Engineered a system prompt that controls Claude's behaviour as a kid-friendly quiz tutor with structured, parseable output.
  • Implemented game logic with score tracking, dynamic difficulty adjustment, and encouraging terminal feedback.
  • Added Story Mode that weaves correct quiz answers into an ongoing adventure narrative.

Ready to quiz yourself?