ShankG's picture
Update app.py
10928eb verified
# Integrate version
import os
import re
# import openai
import gradio as gr
from typing import List, Dict, Optional
# from dotenv import load_dotenv
import ast
from openai import OpenAI
import json
# load_dotenv()
class InteractiveDebuggingChatbot:
def __init__(self):
# Configuring the OpenAI API
# self.client = OpenAI(api_key='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' # this is also the default, it can be omitted
# )
self.client = OpenAI(api_key=os.environ['OPENAI_API_KEY'],
)
# status tracking
self.current_bug = None
self.debug_stage = 'initial'
self.questioning_index = 0
self.bug_history = []
# OpenAI Chat Context Management
self.messages = [
{"role": "system", "content": """
You are an AI tutor specializing in debugging and foundational programming concepts, using the Socratic questioning method to guide students toward deeper understanding.
Your primary goal is to help students learn from debugging rather than simply providing solutions. Instead of giving direct answers, ask thoughtful, step-by-step questions that encourage students to analyze their own code, identify errors, and refine their reasoning.
Follow these principles:
Never give direct answers: always guide students by asking questions
Always give only one question each response, providing socratic questions as responses to trigger next round of conversation.
Clarify Intent: Start by asking what the student expects the code to do and what it actually does.
Encourage Observation: Guide students to examine error messages, test cases, and outputs systematically.
Break Down Concepts: If a student lacks foundational knowledge, decompose the problem and use analogies or simple examples to bridge the gap.
Promote Self-Correction: Lead students to discover and explain their own mistakes, reinforcing key programming principles.
Encourage Experimentation: Prompt students to modify and test their code iteratively to observe effects.
Maintain a patient, encouraging, and curious tone, fostering a mindset where mistakes are opportunities for deeper learning.
"""}
]
def _extract_bugs_and_concepts_via_api(self, problem_description, buggy_code, error_message):
"""
Use GPT to extract a list of distinct bugs and misunderstood programming concepts from the
provided problem description, buggy code, and error message.
Returns a tuple: (bug_list, concept_list)
"""
prompt = f"""
You will receive a problem description, a piece of buggy Python code, and the associated error message.
Please do the following:
1. Analyze the provided information and identify ALL distinct bugs present in the code. Provide a brief description for each bug.
2. Identify any underlying programming concepts that might be misunderstood or contributing to these bugs.
Respond in strict JSON format as follows:
{{
"bugs": ["Bug description 1", "Bug description 2", ...],
"concepts": ["Concept 1", "Concept 2", ...]
}}
Problem Description:
{problem_description}
Buggy Code:
```python
{buggy_code}
Error Message/Bug Description: {error_message}
"""
try:
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a precise and structured programming tutor."},
{"role": "user", "content": prompt}
],
max_tokens=300,
temperature=0
)
content = response.choices[0].message.content.strip()
# Attempt to extract a JSON block using regex
json_match = re.search(r'\{.*\}', content, re.DOTALL)
json_str = json_match.group() if json_match else content
try:
data = json.loads(json_str)
except Exception:
data = ast.literal_eval(json_str)
bugs = data.get("bugs", ["Could not extract bug list"])
concepts = data.get("concepts", ["Could not extract concepts"])
# Ensure that both bugs and concepts are lists
if not isinstance(bugs, list):
bugs = [str(bugs)]
if not isinstance(concepts, list):
concepts = [str(concepts)]
return bugs, concepts
except Exception as e:
print(f"Error extracting bugs/concepts via API: {e}")
return ["Extraction failed"], ["Extraction failed"]
def analyze_single_bug(self, problem_description, buggy_code, error_message, specific_bug):
"""
Analyze a single bug (specified by specific_bug) using the Socratic questioning method.
This function produces a detailed analysis for the given bug.
"""
system_prompt = """You are an advanced AI programming tutor specializing in
debugging with a Socratic approach. For each bug:
1. Precisely identify the bug
2. Explain the root cause
3. Provide a corrected code example
4. Highlight the underlying programming concept
5. Suggest learning resources
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"""Debugging Analysis Request:
Problem Description: {problem_description}
Buggy Code:
```python
{buggy_code}
```
Error Message/Bug Description: {error_message}
Identified Bug: {specific_bug}
Please provide a comprehensive, step-by-step analysis of the bug:
1. Exactly where is the bug located?
2. Why does this bug occur?
3. What is the correct approach?
4. What programming concept is misunderstood?
5. How can the student learn from this?
"""}
]
# Generate the debugging analysis
response = self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=4000,
temperature=0.7
)
# Parse the analysis
analysis = response.choices[0].message.content
return analysis
def _classify_bug_type(self, analysis):
"""
Classify the bug type using the OpenAI API:
Return 0 for Syntax-related bugs, 1 for Logic-related bugs
"""
prompt = """
You are a code analysis assistant. Based on the following explanation of a programming bug,
classify the bug into one of two categories ONLY:
0 → Syntax bug (e.g., missing parentheses, wrong indentation, typos)
1 → Logic bug (e.g., incorrect algorithm, wrong loop condition, misunderstanding of concepts)
Respond ONLY with the number: 0 or 1. Do not explain. Do not include any words.
Bug Analysis:
""" + analysis
try:
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful programming assistant."},
{"role": "user", "content": prompt}
],
max_tokens=5,
temperature=0
)
raw_output = response.choices[0].message.content.strip()
cleaned = re.findall(r'\b[01]\b', raw_output)
if cleaned:
return int(cleaned[0])
else:
return -1
except Exception as e:
print(f"Error in bug classification: {e}")
return -1
def _extract_bug_location(self, analysis, original_code):
"""
Extract the specific location of the bug in the code.
Uses OpenAI to detect line numbers; falls back to regex heuristics.
Returns: "Line X", "Lines X and Y", or "Location Not Identified"
"""
# === First Attempt: Use API for accurate, code-aware line identification ===
prompt = f"""
You will receive a piece of Python code and an analysis of the bug(s).
Return ONLY the line number(s) where bugs appear in the code, based on the analysis.
If multiple lines are involved, return a list. Example: [3], [2, 4], or [] if unknown.
DO NOT include explanations or any extra text.
Bug Analysis:
{analysis}
Code:
```python
{original_code}
"""
try:
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a precise bug-locator assistant."},
{"role": "user", "content": prompt}
],
max_tokens=10,
temperature=0
)
raw = response.choices[0].message.content.strip()
lines = ast.literal_eval(raw)
if isinstance(lines, list) and all(isinstance(i, int) for i in lines):
# Keep only valid line numbers
code_lines = original_code.split("\n")
valid_lines = [i for i in lines if 1 <= i <= len(code_lines)]
if len(valid_lines) == 1:
return f"Line {valid_lines[0]}"
elif len(valid_lines) > 1:
return "Lines " + " and ".join(map(str, valid_lines))
else:
return "Location Not Identified"
except Exception as e:
print(f"[API line detection failed] Falling back: {e}")
# === Fallback: Regex-based detection ===
try:
line_matches = re.findall(r'line\s*(\d+)', analysis, re.IGNORECASE)
if line_matches:
return f"Line {line_matches[0]}"
# Heuristic: look for words like "error", "wrong", "incorrect"
lines = original_code.split('\n')
for i, line in enumerate(lines, 1):
if any(keyword in line.lower() for keyword in ['error', 'wrong', 'incorrect']):
return f"Around Line {i}"
except Exception as fallback_err:
print(f"[Regex fallback failed]: {fallback_err}")
# Final fallback
return "Location Not Identified"
def _structure_bug_analysis(self, problem_description, buggy_code, error_message):
"""
Structure the overall bug analysis.
Steps:
1. Extract a list of distinct bugs and misunderstood programming concepts from the inputs.
2. For each bug in the extracted list, call analyze_single_bug to generate a detailed analysis.
3. Combine all individual analyses.
4. Additionally, classify the overall bug type, extract the bug location, and corrected code from the combined analysis.
Returns a dictionary with:
- "type": overall bug type,
- "location": location in the code,
- "analysis": combined detailed analysis,
- "corrected_code": suggested corrected code,
- "misunderstood_concepts": list of concepts,
- "bugs_mentioned": list of identified bugs.
"""
# Extract bugs and misunderstood concepts using our dedicated API extraction.
bug_list, concepts = self._extract_bugs_and_concepts_via_api(problem_description, buggy_code, error_message)
# Analyze each extracted bug individually.
combined_analyses = []
types_list = []
locations_list = []
for bug in bug_list:
# Analyze the specific bug.
analysis_for_bug = self.analyze_single_bug(problem_description, buggy_code, error_message, bug)
combined_analyses.append(analysis_for_bug)
# Extract bug type and location for this specific analysis.
bug_type = self._classify_bug_type(analysis_for_bug)
location = self._extract_bug_location(analysis_for_bug, buggy_code)
types_list.append(bug_type)
locations_list.append(location)
# Return the complete structured bug analysis.
return {
"type": types_list,
"location": locations_list,
"analysis": combined_analyses,
"misunderstood_concepts": concepts,
"bugs_mentioned": bug_list
}
def generate_guided_questioning(self, bug_info: dict) -> str:
# If no bugs are detected, ask the user if they can proceed to the summary phase
if len(bug_info) == 0:
question = (
"I don't detect a clear error in your code."
"Please check that you have entered the correct information."
)
return question
# "0" => Syntax, "1" => Logic
# The following constructs Prompt to guide ChatCompletion to generate a Socratic guide of "only one question"
system_prompt = (
"You are an AI tutor specializing in helping students with debugging by assisting them to examine their missing or misunderstood foundational programming concepts, "
"using the Socratic questioning method to guide students. "
"Never provide direct answers; always respond with exactly ONE question. \n\n"
"We have the following guidelines:\n"
"First step, you should judge the "
"1) If the current bug is a Syntax bug (type='0'), you must:\n"
" - Point out the problematic code region for the student to attempt fixing.\n"
" - List or hint at relevant syntax rules so the student can do self-check.\n"
"2) If the current bug is a Logic bug (type='1'), you must:\n"
" - First use simple language to confirm the student's comprehension of the problem.\n"
" - If there's a misunderstanding, guide the student to adjust their understanding.\n"
" - If the understanding is correct, guide them to evaluate the relevant concepts and propose their own modifications.\n\n"
"At the end of addressing THIS bug, you must ask: 'Are all bugs resolved now? [Yes/No]'.\n"
"If user make any statement that means yes all resolved, output string Yes"
"If user says 'No', we proceed to the next bug (the system will handle the index increment; you do not do it here).\n\n"
"Important: Always produce only ONE question, do not reveal any direct solutions. "
"Do not mention code directly unless pointing out the suspicious line for Syntax bug.\n"
)
# Pass the current message history, debugging stage, and the bug information to be analyzed to the model
user_prompt = (
f"""
Current debugging stage: {self.debug_stage}
Message history:{self.messages}
Bug information to be analyzed: {bug_info}
First, you need to determine which bug you are currently analyzing and at which stage of the analysis based on historical information and bug information.
Based on the above information, please generate a question using the Socratic questioning method to guide users to further analyze:
- If it is a syntax bug, please help users locate the problematic code and refer to common syntax rules for self-checking;
- If it is a Logic bug, please first confirm whether the user's understanding of the problem is correct. If not, help them correct their understanding. If correct, let them think about it and propose modification ideas.
Simply return a question (don't provide a specific answer) and follow a concise, leading Socratic questioning style.
""")
try:
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
max_tokens=300,
temperature=0.7
)
question = response.choices[0].message.content.strip()
if question.strip().lower() == "yes":
self.debug_stage = 'summary'
return "Now that we have analyzed the problem comprehensively, let us summarize the knowledge and conceptual shifts involved."
else:
return question
except Exception as e:
print(f"[Error in generate_guided_questioning] {e}")
question = (
"Sorry, there was an error generating the guided questions."
"Please try again or check your network connection."
)
return question
def process_debugging_session(self, user_input: str) -> str:
"""
Handle debugging session
Args:
user_input (str): user input
Returns:
str: Debugging suggestions for AI assistants
"""
# Add user input to the message history
self.messages.append({"role": "user", "content": user_input})
# First input: initialization bug
if self.debug_stage == 'initial':
bug_info = self._structure_bug_analysis(user_input[1],user_input[2],user_input[3])
# bug_info = self.extract_code_and_error(user_input)
self.current_bug = bug_info
# Enter the guided questioning stage
self.debug_stage = 'questioning'
question = self.generate_guided_questioning(bug_info)
# Add question to message history
self.messages.append({"role": "assistant", "content": f"{question}"})
return f"{question}"
# Questioning stage: guiding students to think
elif self.debug_stage == 'questioning':
# Generate the next guiding question
question = self.generate_guided_questioning(self.current_bug['type'])
# Add question to message history
self.messages.append({"role": "assistant", "content": question})
return question
# solution phase
elif self.debug_stage == 'summary':
# Generate detailed solution proposals using OpenAI
try:
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[
*self.messages,
{"role": "user",
"content": f"""
Your task is to summarize the debugging process you just completed with a student. The student encountered a {self.current_bug['type']} type of bug in their code.
Based on the full conversation between you and the student, generate a summary that emphasizes learning value and meets the following points:
Clearly review how this {self.current_bug['type']} bug was identified;
Analyze the root cause of this bug — was it related to any misunderstanding of programming concepts?;
Highlight key programming concepts or techniques involved in solving this issue;
Provide specific advice to help the student avoid making the same kind of mistake in the future;
Use clear, friendly, and encouraging language — avoid overly technical or dry tone.
The summary should be between 150 to 300 words. It should be engaging, educational, and easy to understand.
"""}
],
temperature=0.7
)
summary = response.choices[0].message.content.strip()
# Reset the state and prepare for the next bug
self.bug_history.append(self.current_bug)
self.current_bug = None
self.debug_stage = 'initial'
self.questioning_index = 0
# Add solution to message history
self.messages.append({"role": "assistant", "content": summary})
return summary
except Exception as e:
print(f"Summary of build errors: {e}")
return "Sorry, there was a problem generating the summary."
def reset_session(self):
self.current_bug = None
self.debug_stage = 'initial'
self.questioning_index = 0
self.bug_history = []
self.messages = [
{"role": "system", "content": """
You are a professional programming instructor and debugging assistant who is good at helping students diagnose and solve programming problems step by step.
Your goals are:
1. Guide students' thinking by asking questions
2. Help students understand the root cause of errors
3. Provide clear, step-by-step solutions
4. Teach programming best practices
"""}
]
return [], ""
chat_history = [
"""
<div style='background-color: #f1f1f1; padding: 10px 15px; border-radius: 10px; max-width: 75%;'>
<strong>Socrates the Debugger:</strong><br>
I'm here to help you fix bugs in your Python code and explain the concepts behind them. Just enter your problem, code, and error messages — and I'll guide you step-by-step.
</div>
"""
]
def launch_interactive_debugging_chatbot(user_input, problem_description, buggy_code, error_message):
"""
Launch the interactive debugging chatbot interface
"""
# Initialize and debug the chatbot
debugger = InteractiveDebuggingChatbot()
# Creating the Gradio Interface
user_bubble = f"""
<div style='display: flex; justify-content: flex-end; margin-bottom: 10px;'>
<div style='background-color: #DCF8C6; padding: 10px 15px; border-radius: 10px; max-width: 75%;'>
<strong>User:</strong><br>{user_input}
</div>
</div>
"""
# full_input = f"{user_input}\nProblem Description: {problem_description}\n```python\n{buggy_code}\n```\nError: {error_message}"
full_input = [user_input, problem_description, buggy_code, error_message]
# Call instance method for processing
ai_response = debugger.process_debugging_session(full_input)
# input = [user_input, problem_description, buggy_code, error_message]
# ai_response = process_debugging_session(input)
# # ai_response = get_ai_response(problem_description, buggy_code, error_message)
ai_bubble = f"""
<div style='display: flex; align-items: flex-start; margin-bottom: 10px;'>
<div style='background-color: #f1f1f1; padding: 10px 15px; border-radius: 10px; max-width: 75%;'>
<strong>Socrates the Debugger:</strong><br>{ai_response}
</div>
</div>
"""
chat_history.append(user_bubble)
chat_history.append(ai_bubble)
return "\n".join(chat_history), ""
with gr.Blocks(title="Socratic Debugging Tutor") as demo:
gr.Markdown("""
<h1 style='text-align: center;'><strong>Socratic Debugging Tutor</strong></h1>
<div style='text-align: center; font-size: 16px; margin-bottom: 10px;'>
An advanced AI tool to analyze and guide through multiple programming bugs using Socratic questioning.
</div>
""")
with gr.Row():
with gr.Column(scale=3):
chat_output = gr.Markdown(
value="""
<div style='background-color: #f1f1f1; padding: 10px 15px; border-radius: 10px; max-width: 75%;'>
<strong>Socrates the Debugger:</strong><br>
I'm here to help you fix bugs in your Python code and explain the concepts behind them. Just enter your problem, code, and error messages — and I’ll guide you step-by-step.
</div>
""",
label="Chat with Socrates"
)
with gr.Column(scale=1):
gr.Markdown("### Model Settings")
model = gr.Dropdown(["gpt-4o", "gpt-4o-mini"], value="gpt-4o", label="Model")
max_tokens = gr.Slider(800, 4000, value=2000, step=100, label="Max Tokens")
temperature = gr.Slider(0.0, 1.0, value=0.7, step=0.05, label="Creativity Level",
info="Higher = more creative, lower = more focused.")
top_p = gr.Slider(0.0, 1.0, value=0.95, step=0.05, label="Response Breadth",
info="Controls how many word options are considered during generation.")
user_input = gr.Textbox(placeholder="Ask Socrates something...", lines=1, label="Your Message")
with gr.Row():
error_message = gr.Textbox(label="Error Message(s)", placeholder="e.g. TypeError: unsupported operand", lines=2)
buggy_code = gr.Textbox(label="Buggy Code", placeholder="Paste your code...", lines=6)
problem_description = gr.Textbox(label="What is the code supposed to do?", placeholder="Explain the intent...", lines=2)
# Debugging interaction logic
submit_btn = gr.Button("Submit")
submit_btn.click(
fn = launch_interactive_debugging_chatbot,
inputs=[user_input, problem_description, buggy_code, error_message],
outputs=[chat_output, user_input]
)
demo.launch(share=True)
if __name__ == "__main__":
# Checking OpenAI API Key
if not os.getenv('OPENAI_API_KEY'):
print("Please set OPENAI_API_KEY environment")
else:
launch_interactive_debugging_chatbot()