Build a Personal AI Tutor with Python

Build a Socratic AI tutor web app with Python, Streamlit, and Google Gemini.

Introduction

30 Second Summary

Everyone has a subject they wish they understood better. Imagine having a personal tutor available anytime that asks you the right questions and helps you discover the answers yourself.

In this project, you will build a personal AI tutor web app using Python and Streamlit. You will connect it to Google's Gemini API so it can teach any subject through real conversation.

What You'll Build

You will fire up a browser-based tutor, pick a subject, and watch as it teaches you through conversation and quizzes you to make sure you actually understood.

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

  • A polished chat interface where you have back-and-forth conversations with your AI tutor in real time.
  • A Socratic teaching mode with a subject selector that lets you switch between topics like math, science, history, or coding.
  • A quiz mode with a running score tracker that tests what you have learned and gives you feedback on each answer.
  • Secret Mission: Make the tutor track your quiz score and adapt its difficulty based on how you perform.

Are there any prerequisites?

You need a Google account to create a free Gemini API key. No prior Python experience is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters to you.

Set Up Your Project

Before writing any code, you need a clean workspace for your tutor project. This means a dedicated folder and an isolated Python environment where your packages can live without interfering with anything else on your machine.

In this step, you will create your project folder, set up a Python virtual environment, and install the three libraries your tutor needs to run.

In this step, get ready to:
  • Create a project folder and set up a Python virtual environment.
  • Install Streamlit, google-genai, and python-dotenv.
  • Run the Streamlit demo app to confirm everything works.
Create your project folder and virtual environment

A virtual environment is a self-contained Python installation inside your project folder. It keeps your project's packages separate from other Python projects on your machine, preventing version conflicts.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop and create your project folder by running these commands:

macOS

cd ~/Desktop
mkdir ai-tutor
cd ai-tutor

Windows

cd ~\Desktop
mkdir ai-tutor
cd ai-tutor

Your terminal prompt now includes ai-tutor, confirming you are in the right folder.

  • Create your virtual environment and activate it by running these commands:

macOS

python3 -m venv .venv
source .venv/bin/activate

Windows

python -m venv .venv
.venv\Scripts\Activate.ps1

Your terminal prompt now starts with (.venv). This confirms the virtual environment is active and every package you install from here goes into this isolated environment.

What is a virtual environment?

Think of a virtual environment like a separate toolbox for each project. Without one, every Python project on your machine shares the same set of installed packages. That means upgrading a package for one project could break another.

The .venv folder keeps everything your tutor needs in one place. When you are done with the project, you can delete the folder and everything is cleaned up.

Don't see (.venv) in your prompt?

  • Make sure you ran the activate command, not just the python -m venv .venv creation command. The creation command sets up the environment. The activate command turns it on.
  • On Windows PowerShell, you may need to allow script execution first. Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser and then try the activate command again.
  • Both Streamlit and google-genai require Python 3.10 or higher. Run python3 --version (macOS) or python --version (Windows) to check. If your version is below 3.10, download the latest Python from python.org.

Help me activate my Python virtual environment

Install your project packages

Your tutor app needs three packages. Streamlit builds the chat interface. The google-genai SDK connects your app to Google's Gemini AI. And python-dotenv loads your API key from a file so you never hardcode secrets.

  • Install all three packages by running this command:
pip install streamlit google-genai python-dotenv

What do these packages do?

  • streamlit turns a Python script into an interactive web app. It has built-in chat components that handle the UI for you, so you write zero HTML or JavaScript.
  • google-genai is Google's official Python SDK for the Gemini API. It handles sending prompts and receiving AI-generated responses.
  • python-dotenv reads key-value pairs from a .env file and loads them as environment variables. This keeps your API key out of your code.
  • Wait for the installation to finish. You should see a line that starts with Successfully installed followed by a list of package names.

Seeing installation errors?

  • Check that your virtual environment is active. You should see (.venv) at the start of your terminal prompt. If you do not, go back and run the activate command from the previous substep.
  • On macOS, some packages may need Xcode command line tools. If prompted, follow the on-screen instructions to install them, then run the pip install command again.

Help me fix my pip install errors

Confirm Streamlit is working

Streamlit ships with a built-in demo app you can use to verify everything installed correctly.

Before you run this next command, what do you think will happen when you launch a Streamlit app for the first time?

  • Run the Streamlit demo by running this command:
streamlit hello

What does this command do?

This command starts a local web server on your machine and automatically opens a new browser tab. The Streamlit Hello app shows interactive demos like plots, maps, and animations. Your terminal will display a local URL (usually http://localhost:8501) and stay occupied running the server.

A new browser tab opens with the Streamlit Hello app. You should see a welcome page with a sidebar menu on the left where you can explore different demos.

  • Once you have confirmed the app is running, go back to your terminal and press Ctrl+C to stop the server.

Don't see the demo app in your browser?

  • If the browser did not open automatically, look in your terminal for a URL starting with http://localhost: and paste it into your browser manually.
  • If the terminal says the command was not found, make sure your virtual environment is still active. You should see (.venv) in your prompt. If it is not there, re-run the activate command and try again.
  • If Streamlit asks for your email, you can press Enter to skip it.

Help me get the Streamlit demo running

Your environment is ready to go. Next up, you will connect your app to Google's Gemini API and build a chatbot that actually talks back.

Get Your Gemini API Key and Build a Basic Chatbot

Your environment is ready. Now it's time to connect your app to a real AI model and get a chatbot running in your browser.

To talk to Google's Gemini API, you need an API key. Think of it as a password that lets your Python code send requests to Google's AI. In this step, you will grab that key, wire it up, and build a working chatbot.

In this step, get ready to:
  • Get a Gemini API key from Google AI Studio.
  • Build a basic Streamlit chatbot that connects to the Gemini API.
  • Run the app and have your first conversation with the AI.
Get your Gemini API key from Google AI Studio

Google AI Studio is a free web tool where you can create API keys for the Gemini API. The key is what your Python app uses to authenticate with Google's servers.

  • Open your browser and go to aistudio.google.com.
  • Sign in with your Google account.
  • If prompted to accept the Terms of Service, accept them to continue.
  • In the left sidebar, click Get API key.
  • Click Create API key.
  • Select Create API key in new project if prompted.
  • Copy the API key that appears. You will need it in a moment.

Keep your API key private

Your API key is like a password. Do not share it publicly, paste it into any website, or commit it to a Git repository. You will store it safely in a .env file next.

Now you need a secure place to store the key so your Python code can read it without the key being visible in your source code. A .env file is the standard way to do this.

  • Install the python-dotenv package (which loads .env files into your app) by running this command:
pip install python-dotenv
  • In VS Code, create a new file called .env in your project folder by clicking File, then New File.
  • Paste the following into .env:
GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with the API key you copied from Google AI Studio.
  • Save the file with Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm the file exists by running:
ls

You should see .env listed in the output alongside your .venv folder.

Don't see .env in the output?

Hidden files (those starting with a dot) might not show up with a basic ls command on some systems. Try running ls -a instead, which shows all files including hidden ones.

Also make sure you saved the file inside your project folder, not somewhere else.

Help me find my .env file in my project folder.

What is an API key?

An API key is a unique string that identifies your app to a service. When your code sends a request to the Gemini API, the key proves you have permission to use it.

Storing it in a .env file keeps it out of your source code. The python-dotenv package reads that file and loads the key into your app's environment variables at runtime.

Create the Streamlit chat app

With your API key safely stored, it is time to write the app. You will build tutor.py in two parts: first the setup and connection to Gemini, then the chat interface.

  • Create a new file called tutor.py in your project folder by clicking File, then New File in VS Code.
  • Set up the app and connect to the Gemini API by pasting the following code into tutor.py:
import streamlit as st
import os
from google import genai
from dotenv import load_dotenv

# Load environment variables from the .env file
load_dotenv()

# Set the page title and header
st.set_page_config(page_title="My Personal Tutor", page_icon="📚")
st.title("📚 My Personal Tutor")

# Read the API key from your environment
api_key = os.environ.get("GEMINI_API_KEY")

# Stop the app if no API key is found
if not api_key:
    st.error("Please set your GEMINI_API_KEY in the .env file.")
    st.stop()

# Create the Gemini client
client = genai.Client(api_key=api_key)

What does this code do?

  • load_dotenv() reads your .env file and makes GEMINI_API_KEY available as an environment variable.
  • st.set_page_config() and st.title() set up what you see in the browser tab and the page header.
  • os.environ.get("GEMINI_API_KEY") pulls the key from the environment. If it is missing, the app shows an error and stops.
  • genai.Client() creates a client object that handles all communication with the Gemini API.
  • Save tutor.py.
  • Run your app to verify the API key loads correctly:
streamlit run tutor.py

A browser tab opens at http://localhost:8501. You should see the heading "My Personal Tutor" on the page. No errors means your API key loaded successfully.

Seeing an error about the API key?

If the app shows a red error message about the API key, double check that your .env file is saved in the same folder as tutor.py.

Make sure the file contains GEMINI_API_KEY= followed by your key with no spaces around the = sign and no quotes around the key value.

Help me fix my Gemini API key not loading in Streamlit.

  • Stop the running app by pressing Ctrl+C in the terminal. You will restart it after adding the chat interface.

The page loads, but there is nothing to interact with yet. Time to add the chat.

  • Add the chat interface below the existing code in tutor.py by pasting the following:
# Initialize conversation history
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display all previous messages in the chat
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Handle new user input
if prompt := st.chat_input("Ask me anything..."):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    # Send the message to Gemini and display the response
    with st.chat_message("assistant"):
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=prompt,
        )
        assistant_message = response.text
        st.markdown(assistant_message)

    st.session_state.messages.append({"role": "assistant", "content": assistant_message})

What does this code do?

  • st.session_state.messages stores the full conversation history. Streamlit reruns the entire script on every interaction, so session state is the only way to remember past messages.
  • The for loop at the top replays every message each time the page reruns, so the chat history stays visible.
  • st.chat_input() places a text box at the bottom of the page. When the learner presses Enter, the input triggers the code block inside.
  • client.models.generate_content() sends the user's message to the gemini-2.5-flash model and returns the AI's response.
  • Save tutor.py.

Seeing a syntax error?

The := in if prompt := st.chat_input() is Python's walrus operator. It assigns the chat input to prompt and checks if it is not empty in one line. Make sure you are running Python 3.10 or higher.

Help me fix a syntax error in my Streamlit tutor app.

✔️ Awesome, I've got everything!

Double check you have saved tutor.py. You are ready to test your chatbot.

ⓧ I'd like to double check the full code

Your tutor.py should have two sections, in this order:

  • The setup code (imports, load_dotenv(), page config, API key loading, and genai.Client creation). Scroll up to the first code block you pasted and confirm it is at the top of your file.
  • The chat interface (session state initialization, message history display loop, and the st.chat_input handler with client.models.generate_content()). Scroll up to the second code block and confirm it follows directly after the setup.

Also confirm your .env file contains GEMINI_API_KEY= followed by your actual API key.

Run your chatbot and start a conversation

Your chatbot is complete. Before you run it, take a guess: if you ask it to teach you about photosynthesis, will it walk you through the concept step by step, or just hand you the answer?

  • Start the app by running:
streamlit run tutor.py

Your browser opens to http://localhost:8501. This time you should see a chat input field at the bottom of the page alongside the "My Personal Tutor" heading.

  • Type What is photosynthesis? into the chat input and press Enter.

The AI responds with a full answer right away. It just dumps the definition and moves on. No questions, no back-and-forth, no teaching.

  • Try a follow-up message like Can you explain that more simply? to confirm the conversation works across multiple turns.

Why does it just give answers?

Right now, your chatbot has no instructions about how to behave. It is a generic AI assistant that answers whatever you ask. A real tutor would ask questions, check your understanding, and guide you to the answer.

That gap between "answering" and "teaching" is exactly what you will fix in the next step with a technique called prompt engineering.

Not seeing a response from the AI?

If the app hangs or shows an error after you type a message, check that the API key in your .env file is correct. Go back to aistudio.google.com and compare the key.

Also make sure your virtual environment is still activated. If you see an import error, re-activate it and try again.

Help me debug why my Gemini chatbot is not responding.

Your chatbot is alive and responding. But it is not a tutor yet. Next up, you will give it a personality and a teaching method so it actually guides you instead of handing out answers.

Turn the Chatbot into a Tutor

Your chatbot is alive. In Step 2, you connected it to the Gemini API and had your first conversation.

But there is a problem hiding in plain sight. Your chatbot answers questions, sure. It does not teach. Ask it to explain something and it dumps the full answer on you. A real tutor guides you to discover the answer yourself.

Before you change any code, try it out. What do you think will happen if you ask the chatbot to teach you about photosynthesis?

  • Switch back to your browser where the Streamlit app is running.
  • Type What is photosynthesis? in the chat input and press Enter.

The chatbot gave you a full textbook definition. It explained the concept perfectly, but it did not teach you anything. It handed you the finished answer with no guiding questions and no interaction.

That is exactly the gap this step closes. You are going to turn this answer-dumping chatbot into a Socratic tutor that guides you to discover answers on your own.

In this step, get ready to:
  • Add a subject selector so the tutor can specialize in any topic.
  • Write a Socratic system prompt that teaches through questions instead of answers.
  • Refactor the app to use a persistent chat session with the Gemini API.
Add a subject selector to the sidebar

Your tutor needs to know what subject to teach. A sidebar dropdown keeps the selection visible without cluttering the main chat area.

  • In tutor.py, find the st.title("📚 My Personal Tutor") line. Copy the following code directly below it:
# --- Sidebar ---
with st.sidebar:
    st.header("Settings")
    subject = st.selectbox(
        "Choose a subject",
        ["Math", "Science", "History", "Coding", "Language", "General Knowledge"],
    )

What does this code do?

This creates a collapsible sidebar panel on the left side of the app.

  • st.sidebar groups everything inside it into the sidebar panel instead of the main page.
  • st.selectbox renders a dropdown menu. The selected value is stored in the subject variable.
  • Save tutor.py.
  • Switch to your browser. Streamlit shows a notification that the source file changed. Click Always rerun so the app auto-refreshes on every save from now on.
  • You should see a Settings header and a Choose a subject dropdown in the left sidebar.

Don't see the sidebar?

  • Make sure the sidebar code is placed below the st.title() line and above the api_key line.
  • Check that with st.sidebar: uses a colon at the end and all lines inside it are indented.
  • On narrow browser windows, the sidebar may be collapsed by default. Click the arrow icon in the top-left corner to expand it.

Still stuck? Why isn't my Streamlit sidebar showing up?

Write a Socratic system prompt

A system prompt is a set of instructions that shapes how an AI model behaves. By telling the model to follow the Socratic method, you change it from an answer machine into a tutor that asks guiding questions.

  • In tutor.py, copy the following code directly below the sidebar block you just added (above the api_key line):
# --- System Prompt ---
SYSTEM_PROMPT = f"""You are a personal tutor specializing in {subject}. Your teaching style follows the Socratic method. Here are your rules:

1. Never give the answer directly. Instead, ask guiding questions that help the student discover the answer themselves.
2. Start by asking what the student already knows about the topic.
3. Break complex topics into smaller, manageable pieces.
4. When a student is stuck, give a small hint rather than the full answer.
5. Use analogies and real-world examples to explain concepts.
6. Be encouraging and patient. Celebrate when the student makes progress.
7. If the student asks you to just tell them the answer, gently redirect them with another guiding question.
8. Keep your responses concise. Aim for 2-3 short paragraphs at most.
9. When asked to quiz the student, generate a clear question appropriate to {subject} and the conversation so far. After the student answers, tell them if they are correct or incorrect, explain why, and then continue teaching.
"""

What does this prompt do?

This is an f-string, so {subject} is replaced with whatever the learner picked in the sidebar dropdown. The nine rules shape the tutor's entire personality.

Rules 1, 4, and 7 are the core of the Socratic method. They force the model to ask questions instead of handing out answers. Rule 2 makes the tutor check your existing knowledge first, so it meets you where you are.

  • Save tutor.py. The app reruns, but you will not see any behavior change yet. The prompt exists as a variable, but nothing is using it. The next substep wires it up.
Connect the system prompt to your chat session

Right now the app calls client.models.generate_content for each message independently. It has no memory of the conversation and no system prompt. You need a persistent chat session that remembers context and uses your Socratic instructions.

  • In tutor.py, add this import at the top of the file, right below from google import genai:
from google.genai import types
  • Add a # --- Initialize Client --- comment above the api_key = os.environ.get("GEMINI_API_KEY") line.
  • Find the if "messages" not in st.session_state: block. Add a # --- Session State --- comment above it.
  • Directly below st.session_state.messages = [], copy the following code:
if "chat" not in st.session_state or st.session_state.get("current_subject") != subject:
    st.session_state.current_subject = subject
    st.session_state.messages = []
    st.session_state.chat = client.chats.create(
        model="gemini-2.5-flash",
        config=types.GenerateContentConfig(
            system_instruction=SYSTEM_PROMPT,
        ),
    )

What does this code do?

client.chats.create creates a persistent chat session that remembers all previous messages automatically. You no longer need to resend the conversation history with each request.

types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT) attaches your Socratic rules to every message in this session. The or st.session_state.get("current_subject") != subject check detects when the learner switches subjects. When that happens, the chat resets so the tutor starts fresh with the new topic.

Now you need to swap out the old single-message API call for the chat session's send_message method.

  • Scroll down to the with st.chat_message("assistant"): block inside the user input handler. Find these lines:
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=prompt,
        )
  • Replace those four lines with this single line:
        response = st.session_state.chat.send_message(prompt)

Why send_message instead of generate_content?

generate_content treats every call as a standalone request with no memory. send_message sends the message through the chat session, which automatically includes the conversation history and the system prompt. This is what makes the tutor remember what you discussed earlier in the conversation.

  • Finally, add these section comments to keep your file organized:
  • Add # --- Display Chat History --- above the for message in st.session_state.messages: line.
  • Add # --- Handle User Input --- above the if prompt := st.chat_input("Ask me anything..."): line.

Time to see if your tutor actually teaches now. Before you test, think about this: you are about to ask the same question you asked at the start of this step. Do you think the response will be different this time?

  • Save tutor.py.
  • Switch to your browser. The app reloads and your previous conversation disappears. That is expected. The app created a fresh chat session.
  • Type What is photosynthesis? in the chat input and press Enter.

This time, instead of a textbook definition, the tutor asks what you already know about photosynthesis. It guides you toward the answer with questions instead of handing it to you.

  • Try switching the subject to History in the sidebar dropdown, then ask a question. The conversation resets and the tutor now specializes in History.

Tutor still giving direct answers?

  • Make sure you replaced all four lines of the client.models.generate_content(...) call with the single st.session_state.chat.send_message(prompt) line. If the old call is still there, the system prompt is being ignored.
  • Check that SYSTEM_PROMPT is defined above the client.chats.create block. If it comes after, the variable does not exist when the chat session is created.
  • Verify the SYSTEM_PROMPT uses f""" (with the f prefix). Without it, {subject} stays as literal text instead of being replaced with the selected subject.

Still stuck? My tutor is not using the Socratic method even though I added the system prompt.

✔️ Awesome, I've got everything!

Great. Double check you have saved tutor.py and that the tutor is asking guiding questions instead of direct answers.

ⓧ I'd like to double check the full code

Scroll up through your tutor.py and confirm you have each of these sections in order:

  • Imports: streamlit, os, genai, types, and dotenv. Make sure from google.genai import types is present.
  • # --- Sidebar --- block with st.selectbox storing the result in subject.
  • # --- System Prompt --- block with the SYSTEM_PROMPT f-string containing all nine Socratic rules, using {subject} twice.
  • # --- Initialize Client --- block with api_key, the error check, and client = genai.Client(api_key=api_key).
  • # --- Session State --- block with both the messages check and the chat creation using client.chats.create.
  • # --- Display Chat History --- block with the for message in st.session_state.messages: loop.
  • # --- Handle User Input --- block using st.session_state.chat.send_message(prompt) instead of client.models.generate_content.

Your tutor now teaches through questions instead of just handing out answers. In the next step, you will add a quiz mode so the tutor can test what you have actually learned.

Add Quiz Mode

Your tutor can now teach using the Socratic method, guiding you through topics with probing questions instead of just handing out answers. But there is a gap.

You have no way to test whether you actually learned anything. Without a quiz, you are just passively reading. In this step, you will add a quiz mode that turns your tutor into a real learning tool with a score tracker.

In this step, get ready to:
  • Add a Quiz Me! button and a score display to the sidebar.
  • Wire the button to send quiz prompts and display questions in the chat.
  • Track correct answers automatically by detecting keywords in the tutor's response.
Add the Quiz Me button and score display

Before the quiz can work, the sidebar needs a button to trigger it and a place to show the learner's score. Streamlit's st.button returns True on the rerun when it is clicked, and st.metric displays a large, bold number perfect for a score counter.

  • In tutor.py, find the # --- Sidebar --- section and locate the closing parenthesis of the st.selectbox call.
  • Add the following code directly below it, still inside the with st.sidebar: block:
    st.divider()

    if "score" not in st.session_state:
        st.session_state.score = {"correct": 0, "total": 0}

    st.metric("Quiz Score", f"{st.session_state.score['correct']}/{st.session_state.score['total']}")

    quiz_clicked = st.button("🧠 Quiz Me!")

What does this code do?

  • st.divider() draws a horizontal line separating the subject picker from the quiz controls.
  • The if "score" not in st.session_state check initializes the score dictionary the first time the app runs. It tracks correct and total answers, both starting at 0.
  • st.metric displays the score as a large, prominent number like 0/0.
  • st.button creates the Quiz Me button. The return value is stored in quiz_clicked so you can check whether the button was pressed later in the script.
  • Save tutor.py.
  • Switch back to your browser where the Streamlit app is running. The app should auto-refresh.
  • In the sidebar, you should see a horizontal divider, a Quiz Score metric showing 0/0, and a Quiz Me! button.

Don't see the new sidebar elements?

  • Make sure all the new code is indented inside the with st.sidebar: block. Streamlit uses indentation to know what belongs in the sidebar.
  • Check that the new code is below the st.selectbox call, not above it or outside the with block.
  • If the app shows an error, click Always rerun in the top-right corner of the Streamlit page to see updated error messages as you fix them.

Why aren't my new sidebar elements showing up in Streamlit?

Handle the quiz button click

The button exists, but clicking it does nothing yet. You need to detect when quiz_clicked is True and send a special prompt that asks the tutor to generate a quiz question.

  • In tutor.py, find the # --- Handle User Input --- comment.
  • Add the following code directly above that comment:
# --- Handle Quiz Button ---
if quiz_clicked:
    quiz_prompt = f"Quiz me on {subject}. Ask me one clear question to test my understanding of what we have discussed. If we have not discussed anything yet, ask a foundational {subject} question."
    st.session_state.messages.append({"role": "user", "content": quiz_prompt})
    with st.chat_message("user"):
        st.markdown(quiz_prompt)

    with st.chat_message("assistant"):
        response = st.session_state.chat.send_message(quiz_prompt)
        assistant_message = response.text
        st.markdown(assistant_message)

    st.session_state.messages.append({"role": "assistant", "content": assistant_message})
    st.session_state.score["total"] += 1

What does this code do?

  • When quiz_clicked is True, the code builds a quiz_prompt string that tells the tutor to ask a question about the current subject.
  • The prompt is added to st.session_state.messages and displayed in the chat, just like a regular user message. The tutor's response is then fetched and displayed the same way.
  • score["total"] is incremented by 1 every time the button is clicked. This counts how many quiz questions have been asked.

Why place this above Handle User Input?

Streamlit runs your script from top to bottom on every interaction. The quiz handler needs to run before the chat input handler so that quiz questions appear in the chat before the user types their next answer.

  • Save tutor.py and switch back to your browser.
  • Click the Quiz Me! button in the sidebar.
  • A quiz prompt should appear in the chat as a user message, followed by the tutor's quiz question as an assistant message.
  • Check the Quiz Score in the sidebar. It should now show 0/1 because one question has been asked but none answered yet.

Button click does nothing?

  • Make sure the # --- Handle Quiz Button --- block is at the top level of the file (no indentation), not inside the with st.sidebar: block.
  • Check that the variable name quiz_clicked matches exactly between the st.button line in the sidebar and the if quiz_clicked: check. Python variable names are case-sensitive.

Why isn't my Quiz Me button triggering a quiz question?

Track correct answers automatically

The quiz asks questions and counts totals, but it never knows when you get one right. You need a way to detect a correct answer and increment the score.

A simple approach is to check the tutor's response for keywords like "correct", "right", or "well done". When the tutor confirms a correct answer, the system prompt already instructs it to use these words.

  • In tutor.py, scroll down to the # --- Handle User Input --- section.
  • Find the last line inside the if prompt block:
    st.session_state.messages.append({"role": "assistant", "content": assistant_message})
  • Add the following code directly below that line, at the same indentation level:
    lower_response = assistant_message.lower()
    if "correct" in lower_response or "right" in lower_response or "well done" in lower_response:
        st.session_state.score["correct"] += 1

What does this code do?

  • lower_response converts the tutor's entire response to lowercase so the keyword check is case-insensitive.
  • The if statement checks for three keywords the tutor uses when confirming a correct answer: "correct", "right", and "well done".
  • When a match is found, score["correct"] is incremented by 1. The updated score shows immediately in the sidebar on the next rerun.

Why keyword detection instead of structured output?

A more robust approach would use structured output from the model to explicitly flag whether the answer was correct. Keyword detection is simpler and works well here because the system prompt already instructs the tutor to say whether the answer is "correct or incorrect". For a beginner project, this pragmatic approach keeps the code short and readable.

  • Save tutor.py.

Before you test, think about what you expect to happen. If you click Quiz Me!, get a question, and answer it correctly, what should the score show?

  • Switch back to your browser.
  • Click the Quiz Me! button in the sidebar.
  • Read the tutor's question and type your best answer in the chat input.
  • If the tutor confirms you are correct, the Quiz Score in the sidebar should update to 1/1. If you got it wrong, it stays at 0/1.

Score not updating after a correct answer?

  • Make sure the keyword detection code is inside the if prompt block (indented under it), not at the top level of the file. It needs to run every time the user sends a message.
  • Check that you are using lower_response (the lowercased version) in the if statement, not assistant_message. Without lowering, a response like "Correct!" would not match "correct".
  • The tutor may not always use the exact keywords "correct", "right", or "well done". Try asking a clearly correct answer to see if the tutor uses one of these words in its response.

Why isn't my quiz score updating when I answer correctly?

✔️ Awesome, I've got everything!

Great. Double check you have saved tutor.py.

ⓧ I'd like to double check the full code

Your tutor.py file is quite long now. Scroll through it from top to bottom and confirm these sections appear in this order:

  • Imports and load_dotenv() at the top.
  • st.set_page_config and st.title.
  • # --- Sidebar --- with st.selectbox, st.divider(), score initialization, st.metric, and st.button.
  • # --- System Prompt --- with the full Socratic tutor instructions.
  • # --- Initialize Client --- with the API key check and genai.Client.
  • # --- Session State --- with messages and chat initialization.
  • # --- Display Chat History --- loop.
  • # --- Handle Quiz Button --- with the if quiz_clicked: block.
  • # --- Handle User Input --- with the keyword detection at the end.

Your tutor can now teach AND test you. Next up, you will take on the Secret Mission and make the tutor adapt its difficulty based on how well you are performing.

Secret mission

Secret Mission: Adaptive Difficulty

Your tutor treats every student the same right now. Whether you're acing every question or struggling with the basics, the difficulty never changes. A great tutor adjusts to the student. Can you make yours adapt?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine and uses the Gemini API free tier, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Your project folder on the Desktop, containing tutor.py, .env, and the .venv virtual environment.
  • A Gemini API key stored in Google AI Studio.

Keep everything running

No action needed. Choose this if you are still actively building or plan to use your tutor again soon.

To restart your tutor later, open your terminal, navigate to your project folder on the Desktop, and run these commands:

source .venv/bin/activate
streamlit run tutor.py

Your Gemini API key stays active in Google AI Studio at no cost. Your .env file keeps it ready for the next time you launch the app.

Pause - I'll come back to this later

Shut down the running Streamlit server and deactivate your virtual environment to free up system resources. Your project files stay on disk so you can pick up where you left off.

  • If the Streamlit server is still running in your terminal, press Ctrl+C to stop it.
  • Deactivate the virtual environment by running:
deactivate

When you are ready to come back, navigate to your project folder, activate the virtual environment with source .venv/bin/activate, and run streamlit run tutor.py.

Delete - I don't want to use this again

Remove all project files and start fresh.

  • If the Streamlit server is still running in your terminal, press Ctrl+C to stop it.
  • Deactivate the virtual environment by running:
deactivate
  • Delete your project folder from the Desktop by running:

macOS

cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your project folder name"]]

Windows

cd ~\Desktop
rmdir /s /q [[PROJECT_FOLDER="your project folder name"]]
  • To delete your Gemini API key, go to aistudio.google.com, open the API keys page where you created your key, and delete it.

Is the API key costing me anything?

No. The Gemini API free tier has rate limits but no charges. Keeping the key active costs nothing, so deleting it is entirely optional.

Nice Work!

Nice Work!

You just built a personal AI tutor from scratch using Python and Streamlit. You can fire it up anytime to learn any subject, powered by Google's Gemini API.

You've learned how to:

  • Build a personal AI tutor web app with Python and Streamlit that connects to Google's Gemini API using the official google-genai SDK on the free tier.
  • Craft a Socratic system prompt that guides learners with questions instead of handing out answers, with a subject selector to switch between topics like math, science, and coding.
  • Add a quiz mode with a running score tracker that tests your knowledge and gives instant feedback.
  • Secret Mission: Make the tutor adapt its difficulty based on your quiz performance, ramping up the challenge as you improve.

Ready to quiz yourself?