decodingdatascience commited on
Commit
d23cc78
·
verified ·
1 Parent(s): 684f608

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +211 -0
  2. 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()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ openai
3
+ python-dotenv