| 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 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 = (chat_history or []) + [(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): |
| logo = 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", |
| type="tuples" |
| ) |
|
|
| 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 |
| ) |
|
|