Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OPENAI_API_KEY=your_openai_api_key_here
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
__pycache__/
69 changes: 67 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,67 @@
# pythontutorbot
pythontutorbot
# Advanced Python Tutor Bot

Advanced Python Tutor Bot is a small educational AI app for beginner Python learners.

It helps with:
- Explaining Python concepts
- Debugging Python code
- Running interactive quizzes
- Improving code quality
- Beginner-friendly data structures and algorithms (DSA) guidance

## Tech Stack
- Python
- Gradio
- OpenAI Python SDK
- python-dotenv

## Features
- Professional Gradio layout with theme and clear mode descriptions
- Chat interface with persistent in-session chat history
- Four tutor modes:
- Explain Concept
- Debug Code
- Quiz Me
- Improve Code
- Reusable prompt templates
- Input validation and API key error handling

## Project Files
- `app.py` → Gradio chat UI and OpenAI integration
- `prompts.py` → reusable prompt templates and mode descriptions
- `requirements.txt` → dependencies
- `.env` → your local API key file (you create this)

## Setup
1. Create and activate a virtual environment:

```bash
python -m venv .venv
source .venv/bin/activate
```

2. Install dependencies:

```bash
pip install -r requirements.txt
```

3. Create a `.env` file in the project folder and add your API key:

```env
OPENAI_API_KEY=your_api_key_here
```

4. Run the app:

```bash
python app.py
```

5. Open the local Gradio URL shown in your terminal.

## Usage
1. Choose a tutor mode from the dropdown.
2. Chat naturally in the message box.
3. Continue the conversation to use chat history context.
4. Use **Clear Chat** to start a new session.
119 changes: 119 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
from pathlib import Path
from typing import Any

import gradio as gr
from dotenv import load_dotenv
from openai import OpenAI

from prompts import MODE_DESCRIPTIONS, PROMPT_TEMPLATES

MODEL_NAME = "gpt-4.1-mini"

# Load environment variables from a local .env file if it exists.
load_dotenv(dotenv_path=Path(__file__).parent / ".env")

SYSTEM_PROMPT = (
"You are Advanced Python Tutor Bot, an expert and patient teacher for beginners. "
"Help with Python fundamentals, debugging, quizzes, and code quality. "
"When relevant, include beginner-friendly data structures and algorithms guidance "
"(lists, dicts, sets, stacks, queues, recursion, sorting, searching, Big-O at a simple level)."
)


def _history_to_messages(history: list[Any]) -> list[dict[str, str]]:
"""Convert Gradio chat history into OpenAI message objects.

Supports both older history formats (list of [user, assistant]) and
newer message formats (list of dicts).
"""
messages: list[dict[str, str]] = []

for item in history:
if isinstance(item, dict):
role = item.get("role")
content = item.get("content", "")
if role in {"user", "assistant"} and content:
messages.append({"role": role, "content": str(content)})
continue

if isinstance(item, (list, tuple)) and len(item) == 2:
user_text, assistant_text = item
if user_text:
messages.append({"role": "user", "content": str(user_text)})
if assistant_text:
messages.append({"role": "assistant", "content": str(assistant_text)})

return messages


def build_messages(mode: str, user_input: str, history: list[Any]) -> list[dict[str, str]]:
"""Build OpenAI messages using mode template + ongoing chat history."""
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(_history_to_messages(history))

mode_instruction = PROMPT_TEMPLATES[mode].format(user_input=user_input)
messages.append({"role": "user", "content": mode_instruction})
return messages


def get_tutor_response(user_input: str, history: list[Any], mode: str) -> str:
"""Generate a response from OpenAI for the selected tutoring mode."""
if not user_input or not user_input.strip():
return "Please enter some text or code so I can help you."

api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
return (
"Missing OPENAI_API_KEY. Add it to a `.env` file in this folder like: "
"OPENAI_API_KEY=your_key_here"
)

try:
client = OpenAI(api_key=api_key)
messages = build_messages(mode, user_input.strip(), history or [])
response = client.chat.completions.create(
model=MODEL_NAME,
messages=messages,
temperature=0.4,
)
return response.choices[0].message.content or "I could not generate a response. Please try again."
except Exception as error:
return f"Something went wrong while contacting the AI service: {error}"


def build_app() -> gr.Blocks:
theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="slate")

with gr.Blocks(title="Advanced Python Tutor Bot", theme=theme) as app:
gr.Markdown(
"""
# 🧠 Advanced Python Tutor Bot
A professional learning assistant for beginners: explanations, debugging, quizzes, and code improvements.
"""
)

with gr.Row():
with gr.Column(scale=2):
mode = gr.Dropdown(
choices=["Explain Concept", "Debug Code", "Quiz Me", "Improve Code"],
value="Explain Concept",
label="Tutor Mode",
info="Pick how you want the tutor to help in this chat.",
)
with gr.Column(scale=3):
mode_note = gr.Markdown(MODE_DESCRIPTIONS["Explain Concept"])

mode.change(lambda m: MODE_DESCRIPTIONS[m], inputs=mode, outputs=mode_note)

gr.ChatInterface(
fn=get_tutor_response,
additional_inputs=[mode],
)


return app


if __name__ == "__main__":
build_app().launch()
25 changes: 25 additions & 0 deletions prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
PROMPT_TEMPLATES = {
"Explain Concept": (
"Explain this Python concept for a beginner using simple words, a short example, "
"and a quick summary at the end:\n\n{user_input}"
),
"Debug Code": (
"Help debug this Python code. Identify likely errors, explain why they happen, and "
"show a corrected version. End with a short prevention checklist:\n\n{user_input}"
),
"Quiz Me": (
"Run a short interactive Python quiz for a beginner based on this topic. "
"Ask one question at a time, wait for the learner's answer, then give feedback and continue:\n\n{user_input}"
),
"Improve Code": (
"Improve this Python code for readability, style, and beginner-friendly best practices. "
"Explain each improvement clearly and show before/after snippets when possible:\n\n{user_input}"
),
}

MODE_DESCRIPTIONS = {
"Explain Concept": "**Explain Concept**: Learn a topic with plain-English explanations and examples.",
"Debug Code": "**Debug Code**: Find errors, understand causes, and get corrected code.",
"Quiz Me": "**Quiz Me**: Practice with a guided one-question-at-a-time mini quiz.",
"Improve Code": "**Improve Code**: Refactor code for readability, structure, and best practices.",
}
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
gradio
openai
python-dotenv