decodingdatascience commited on
Commit
c64920d
·
verified ·
1 Parent(s): 5897bbf

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +211 -0
  2. prompts.py +72 -0
  3. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import gradio as gr
4
+ from dotenv import load_dotenv
5
+ from openai import OpenAI
6
+
7
+ from prompts import FOLLOW_UP_INSTRUCTION, MODE_INSTRUCTIONS, QUIZ_INSTRUCTIONS
8
+
9
+
10
+ load_dotenv()
11
+
12
+ MODEL_NAME = os.getenv("OPENAI_MODEL", "gpt-4.1-nano")
13
+ MAX_OUTPUT_TOKENS = int(os.getenv("MAX_OUTPUT_TOKENS", "450"))
14
+
15
+ SYSTEM_MESSAGE = """
16
+ You are Python Tutor Bot, a professional and patient Python tutor for beginners.
17
+ Teach through a clear learning journey:
18
+ 1. Understand what the learner wants.
19
+ 2. Explain or review in small steps.
20
+ 3. Ask one useful follow-up question.
21
+ 4. Keep answers practical, short, and encouraging.
22
+
23
+ Use simple language, small Python examples, and avoid overwhelming the learner.
24
+ """
25
+
26
+
27
+ def get_client():
28
+ """Create the OpenAI client only when the user submits a request."""
29
+ api_key = os.getenv("OPENAI_API_KEY")
30
+
31
+ if not api_key or api_key == "your_openai_api_key_here":
32
+ return None
33
+
34
+ return OpenAI(api_key=api_key)
35
+
36
+
37
+ def chat_with_openai(messages):
38
+ client = get_client()
39
+ if client is None:
40
+ return (
41
+ "OPENAI_API_KEY is missing. Add it to your local .env file, "
42
+ "or add it as a Hugging Face Space secret named OPENAI_API_KEY."
43
+ )
44
+
45
+ try:
46
+ response = client.chat.completions.create(
47
+ model=MODEL_NAME,
48
+ messages=messages,
49
+ temperature=0.3,
50
+ max_tokens=MAX_OUTPUT_TOKENS,
51
+ )
52
+ return response.choices[0].message.content
53
+ except Exception as error:
54
+ return f"Something went wrong while contacting OpenAI: {error}"
55
+
56
+
57
+ def start_state():
58
+ return {
59
+ "quiz_active": False,
60
+ "quiz_topic": "",
61
+ "quiz_number": 0,
62
+ "quiz_notes": [],
63
+ }
64
+
65
+
66
+ def build_messages(mode, user_input, message_history):
67
+ messages = [{"role": "system", "content": SYSTEM_MESSAGE}]
68
+
69
+ for chat_message in message_history[-6:]:
70
+ messages.append(
71
+ {
72
+ "role": chat_message["role"],
73
+ "content": chat_message["content"],
74
+ }
75
+ )
76
+
77
+ prompt = MODE_INSTRUCTIONS[mode].format(user_input=user_input.strip())
78
+ prompt = f"{prompt}\n\n{FOLLOW_UP_INSTRUCTION}"
79
+ messages.append({"role": "user", "content": prompt})
80
+ return messages
81
+
82
+
83
+ def build_quiz_messages(user_input, state, message_history):
84
+ messages = [{"role": "system", "content": SYSTEM_MESSAGE}]
85
+
86
+ for chat_message in message_history[-8:]:
87
+ messages.append(
88
+ {
89
+ "role": chat_message["role"],
90
+ "content": chat_message["content"],
91
+ }
92
+ )
93
+
94
+ quiz_prompt = QUIZ_INSTRUCTIONS.format(
95
+ topic=state["quiz_topic"],
96
+ quiz_number=state["quiz_number"],
97
+ user_answer=user_input.strip(),
98
+ quiz_notes="\n".join(state["quiz_notes"]) or "No previous quiz notes yet.",
99
+ )
100
+ messages.append({"role": "user", "content": quiz_prompt})
101
+ return messages
102
+
103
+
104
+ def tutor_response(mode, user_input, chat_history, message_history, state):
105
+ chat_history = chat_history or []
106
+ message_history = message_history or []
107
+ state = state or start_state()
108
+
109
+ if not user_input or not user_input.strip():
110
+ warning = "Please type a Python topic, code sample, error message, or quiz answer first."
111
+ chat_history.append({"role": "assistant", "content": warning})
112
+ return chat_history, message_history, state, ""
113
+
114
+ clean_input = user_input.strip()
115
+ chat_history.append({"role": "user", "content": clean_input})
116
+ message_history.append({"role": "user", "content": clean_input})
117
+
118
+ if mode == "Quiz Me":
119
+ current_quiz_number = None
120
+
121
+ if not state["quiz_active"]:
122
+ state = start_state()
123
+ state["quiz_active"] = True
124
+ state["quiz_topic"] = clean_input
125
+ state["quiz_number"] = 1
126
+ intro_prompt = (
127
+ f"Start a beginner Python quiz journey about: {state['quiz_topic']}.\n"
128
+ "Ask exactly one multiple-choice question. Do not give the answer yet. "
129
+ "After the options, ask the learner to reply with A, B, C, or D."
130
+ )
131
+ messages = [{"role": "system", "content": SYSTEM_MESSAGE}, {"role": "user", "content": intro_prompt}]
132
+ else:
133
+ current_quiz_number = state["quiz_number"]
134
+ state["quiz_notes"].append(f"Question {state['quiz_number']} answer: {clean_input}")
135
+ messages = build_quiz_messages(clean_input, state, message_history)
136
+
137
+ reply = chat_with_openai(messages)
138
+
139
+ if state["quiz_active"] and "Something went wrong" not in reply:
140
+ if current_quiz_number and current_quiz_number >= 3:
141
+ state = start_state()
142
+ elif current_quiz_number:
143
+ state["quiz_number"] = current_quiz_number + 1
144
+ else:
145
+ state = start_state()
146
+ messages = build_messages(mode, clean_input, message_history)
147
+ reply = chat_with_openai(messages)
148
+
149
+ message_history.append({"role": "assistant", "content": reply})
150
+ chat_history.append({"role": "assistant", "content": reply})
151
+ return chat_history, message_history, state, ""
152
+
153
+
154
+ def clear_chat():
155
+ return [], [], start_state(), ""
156
+
157
+
158
+ with gr.Blocks(title="Python Tutor Bot") as demo:
159
+ gr.Markdown(
160
+ """
161
+ # Python Tutor Bot
162
+ Learn Python step by step with explanations, debugging help, quizzes, and code reviews.
163
+ """
164
+ )
165
+
166
+ app_state = gr.State(start_state())
167
+ message_history = gr.State([])
168
+
169
+ with gr.Row():
170
+ mode_dropdown = gr.Dropdown(
171
+ choices=list(MODE_INSTRUCTIONS.keys()),
172
+ value="Explain Concept",
173
+ label="Learning mode",
174
+ )
175
+
176
+ chatbot = gr.Chatbot(
177
+ label="Python Tutor Bot",
178
+ height=430,
179
+ )
180
+
181
+ user_input_box = gr.Textbox(
182
+ label="Message",
183
+ placeholder="Example: I want to learn Python lists, or paste code with an error...",
184
+ lines=5,
185
+ )
186
+
187
+ with gr.Row():
188
+ submit_button = gr.Button("Send", variant="primary")
189
+ clear_button = gr.Button("Clear")
190
+
191
+ submit_button.click(
192
+ fn=tutor_response,
193
+ inputs=[mode_dropdown, user_input_box, chatbot, message_history, app_state],
194
+ outputs=[chatbot, message_history, app_state, user_input_box],
195
+ )
196
+
197
+ user_input_box.submit(
198
+ fn=tutor_response,
199
+ inputs=[mode_dropdown, user_input_box, chatbot, message_history, app_state],
200
+ outputs=[chatbot, message_history, app_state, user_input_box],
201
+ )
202
+
203
+ clear_button.click(
204
+ fn=clear_chat,
205
+ inputs=None,
206
+ outputs=[chatbot, message_history, app_state, user_input_box],
207
+ )
208
+
209
+
210
+ if __name__ == "__main__":
211
+ demo.launch()
prompts.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MODE_INSTRUCTIONS = {
2
+ "Explain Concept": """
3
+ Explain this Python concept for a beginner:
4
+
5
+ {user_input}
6
+
7
+ Please include:
8
+ - A short explanation
9
+ - A simple code example
10
+ - A common mistake to avoid
11
+ """,
12
+ "Debug Code": """
13
+ Help debug this Python code or error message:
14
+
15
+ {user_input}
16
+
17
+ Please include:
18
+ - What the error probably means
19
+ - The corrected code if possible
20
+ - A beginner-friendly explanation of the fix
21
+ """,
22
+ "Quiz Me": """
23
+ Create a short beginner Python quiz about this topic:
24
+
25
+ {user_input}
26
+
27
+ Please include:
28
+ - 3 multiple-choice questions
29
+ - 1 small coding question
30
+ - The answers at the end
31
+ """,
32
+ "Improve Code": """
33
+ Review and improve this Python code:
34
+
35
+ {user_input}
36
+
37
+ Please include:
38
+ - A cleaner version of the code
39
+ - What changed
40
+ - One beginner-friendly tip for writing better Python
41
+ """,
42
+ }
43
+
44
+
45
+ FOLLOW_UP_INSTRUCTION = """
46
+ End by asking exactly one short follow-up question that helps the learner continue.
47
+ Do not ask multiple questions at once.
48
+ """
49
+
50
+
51
+ QUIZ_INSTRUCTIONS = """
52
+ You are running a beginner Python quiz on this topic:
53
+
54
+ {topic}
55
+
56
+ The learner answered the previous question with:
57
+
58
+ {user_answer}
59
+
60
+ Previous quiz notes:
61
+
62
+ {quiz_notes}
63
+
64
+ This is quiz step {quiz_number}.
65
+
66
+ Please:
67
+ - Say whether the learner's answer is correct.
68
+ - Explain the answer in 2-4 short sentences.
69
+ - If this was question 3, finish with a short score-style summary and recommend the next topic.
70
+ - If this was question 1 or 2, ask exactly one new multiple-choice question with A, B, C, and D options.
71
+ - Do not reveal the new answer until the learner replies.
72
+ """
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ openai
3
+ python-dotenv