|
|
import os |
|
|
import gradio as gr |
|
|
from openai import OpenAI |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") |
|
|
|
|
|
if not OPENAI_API_KEY: |
|
|
raise ValueError( |
|
|
"OPENAI_API_KEY is not set. " |
|
|
"Add it in your Hugging Face Space: Settings → Variables and secrets → Secrets." |
|
|
) |
|
|
|
|
|
client = OpenAI(api_key=OPENAI_API_KEY) |
|
|
|
|
|
SYSTEM_PROMPT = """Create an intelligent Python chatbot capable of engaging in natural, helpful, and contextually appropriate conversations with human users. |
|
|
|
|
|
Requirements: |
|
|
- Maintain conversational context over multiple user turns. |
|
|
- Respond helpfully and accurately to a wide range of user inputs. |
|
|
- Reason about user intent before generating each response. |
|
|
- Politely ask clarifying questions if a request is ambiguous or unclear. |
|
|
- Avoid hallucination or speculation—respond only with information you can justify or infer from context. |
|
|
- If unable to answer, politely acknowledge the limitation. |
|
|
|
|
|
Process: |
|
|
1. On each user message, first analyze prior context (if any) and what the user is likely asking/intending. |
|
|
2. Think step-by-step (chain-of-thought) to determine the most relevant, helpful response. Always reason internally before presenting your answer. |
|
|
3. If more information is needed, ask targeted clarifying questions. |
|
|
4. Output your response, maintaining natural tone and conversational flow. |
|
|
5. Continue the conversation until the user indicates they are finished. |
|
|
|
|
|
Output: |
|
|
- Each response should be in plain English, no markdown or code blocks unless explicitly requested. |
|
|
- Maintain a single-paragraph, natural-sounding chat response of 1–3 sentences (unless a longer reply is requested or required). |
|
|
|
|
|
Example—Instructions: |
|
|
- Reasoning: "Recognize the user asked for Python list examples and may want to know how lists work." |
|
|
- Conclusion/Output: "Sure! In Python, a list is a collection of items in a particular order. For example: my_list = [1, 2, 3, 4]. Would you like to see how to add or remove items?" |
|
|
|
|
|
(For more advanced technical requests, reasoning steps and explanations may be slightly longer, but always conclude with a concise, clear reply to the user.) |
|
|
|
|
|
Edge Cases & Important Considerations: |
|
|
- If the user refers to prior conversation context, recall and incorporate it. |
|
|
- Be warm, engaging, and never condescending. |
|
|
- If asked for code, provide only what is needed and explain concisely. |
|
|
|
|
|
REMINDER: Your primary objective is to serve as a helpful Python chatbot, reasoning about context before each response, and outputting clear, appropriate conversational replies. |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init_messages(): |
|
|
return [ |
|
|
{ |
|
|
"role": "system", |
|
|
"content": [{"type": "input_text", "text": SYSTEM_PROMPT}] |
|
|
} |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def append_ui_history(chat_history, user_text, assistant_text): |
|
|
if chat_history is None: |
|
|
chat_history = [] |
|
|
chat_history = chat_history + [ |
|
|
{"role": "user", "content": user_text}, |
|
|
{"role": "assistant", "content": assistant_text}, |
|
|
] |
|
|
return chat_history |
|
|
|
|
|
def respond(user_text, chat_history, messages): |
|
|
if messages is None: |
|
|
messages = init_messages() |
|
|
|
|
|
|
|
|
messages.append( |
|
|
{ |
|
|
"role": "user", |
|
|
"content": [{"type": "input_text", "text": user_text}] |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
response = client.responses.create( |
|
|
model="gpt-5-chat-latest", |
|
|
input=messages, |
|
|
text={"format": {"type": "text"}}, |
|
|
reasoning={}, |
|
|
tools=[], |
|
|
temperature=1, |
|
|
max_output_tokens=2048, |
|
|
top_p=1, |
|
|
store=True |
|
|
) |
|
|
|
|
|
assistant_text = response.output_text |
|
|
|
|
|
|
|
|
messages.append( |
|
|
{ |
|
|
"role": "assistant", |
|
|
"content": [{"type": "output_text", "text": assistant_text}] |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
chat_history = append_ui_history(chat_history, user_text, assistant_text) |
|
|
|
|
|
return "", chat_history, messages |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FAQ_QUESTIONS = [ |
|
|
"What is the difference between a list, tuple, and set in Python?", |
|
|
"How do I use dictionaries effectively in Python?", |
|
|
"What are Python functions and how do *args and **kwargs work?", |
|
|
"How does OOP work in Python (classes, objects, inheritance)?", |
|
|
"How do I handle errors using try/except?", |
|
|
"What are list comprehensions and when should I use them?", |
|
|
"How do I read and write files in Python?" |
|
|
] |
|
|
|
|
|
def set_question(q): |
|
|
return q |
|
|
|
|
|
def clear_all(): |
|
|
return [], init_messages(), "" |
|
|
|
|
|
LOGO_URL = "https://raw.githubusercontent.com/Decoding-Data-Science/nov25/main/logo_python.png" |
|
|
|
|
|
css = """ |
|
|
#app_container {max-width: 1200px; margin: 0 auto;} |
|
|
|
|
|
.header-wrap { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 14px; |
|
|
padding: 10px 6px 2px 6px; |
|
|
} |
|
|
.header-title { |
|
|
font-size: 28px; |
|
|
font-weight: 700; |
|
|
line-height: 1.1; |
|
|
} |
|
|
.header-subtitle { |
|
|
font-size: 12.5px; |
|
|
opacity: 0.75; |
|
|
margin-top: 2px; |
|
|
} |
|
|
|
|
|
.faq-box { |
|
|
border: 1px solid rgba(255,255,255,0.08); |
|
|
border-radius: 12px; |
|
|
padding: 14px; |
|
|
} |
|
|
|
|
|
.faq-btn button { |
|
|
width: 100%; |
|
|
justify-content: flex-start; |
|
|
} |
|
|
""" |
|
|
|
|
|
with gr.Blocks(elem_id="app_container") as demo: |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1, min_width=80): |
|
|
gr.Image( |
|
|
value=LOGO_URL, |
|
|
label=None, |
|
|
show_label=False, |
|
|
height=64, |
|
|
width=64, |
|
|
container=False |
|
|
) |
|
|
with gr.Column(scale=10): |
|
|
gr.HTML( |
|
|
""" |
|
|
<div class="header-wrap"> |
|
|
<div> |
|
|
<div class="header-title">Python Tutor Bot</div> |
|
|
<div class="header-subtitle"> |
|
|
Ask anything about Python — concepts, debugging, best practices, and examples. |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
|
|
|
state = gr.State(init_messages()) |
|
|
|
|
|
|
|
|
with gr.Row(equal_height=True): |
|
|
|
|
|
with gr.Column(scale=4, min_width=320): |
|
|
with gr.Group(elem_classes=["faq-box"]): |
|
|
gr.Markdown("### FAQ — Most Asked Python Questions") |
|
|
gr.Markdown("Click a question to auto-fill it, then press **Enter** or click **Send**.") |
|
|
|
|
|
faq_buttons = [] |
|
|
for q in FAQ_QUESTIONS: |
|
|
b = gr.Button(q, elem_classes=["faq-btn"]) |
|
|
faq_buttons.append(b) |
|
|
|
|
|
gr.Markdown("### Quick prompt ideas") |
|
|
quick = gr.Radio( |
|
|
choices=[ |
|
|
"Explain with a simple example", |
|
|
"Give me a beginner-friendly analogy", |
|
|
"Show common mistakes to avoid", |
|
|
"Provide a short quiz question", |
|
|
"Compare two approaches briefly" |
|
|
], |
|
|
label="Add a style preference (optional)", |
|
|
value=None |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=8, min_width=520): |
|
|
chatbot = gr.Chatbot( |
|
|
height=520, |
|
|
label="Conversation" |
|
|
|
|
|
|
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
msg = gr.Textbox( |
|
|
placeholder="Type your Python question here…", |
|
|
label=None, |
|
|
scale=9 |
|
|
) |
|
|
send = gr.Button("Send", variant="primary", scale=1) |
|
|
|
|
|
with gr.Row(): |
|
|
clear = gr.Button("Clear Chat") |
|
|
gr.Markdown( |
|
|
"<span style='opacity:0.7;font-size:12px;'>Context is preserved across turns unless you clear.</span>" |
|
|
) |
|
|
|
|
|
|
|
|
for b, q in zip(faq_buttons, FAQ_QUESTIONS): |
|
|
b.click(fn=lambda q=q: set_question(q), inputs=None, outputs=msg) |
|
|
|
|
|
|
|
|
def apply_quick_pref(pref, current_text): |
|
|
if not pref: |
|
|
return current_text |
|
|
if current_text and current_text.strip(): |
|
|
return f"{current_text.strip()} ({pref})" |
|
|
return pref |
|
|
|
|
|
quick.change(fn=apply_quick_pref, inputs=[quick, msg], outputs=msg) |
|
|
|
|
|
|
|
|
msg.submit(respond, inputs=[msg, chatbot, state], outputs=[msg, chatbot, state]) |
|
|
send.click(respond, inputs=[msg, chatbot, state], outputs=[msg, chatbot, state]) |
|
|
|
|
|
|
|
|
clear.click(fn=clear_all, inputs=None, outputs=[chatbot, state, msg]) |
|
|
|
|
|
demo.launch( |
|
|
debug=False, |
|
|
theme=gr.themes.Soft(), |
|
|
css=css |
|
|
) |
|
|
|