Download app.py from decodingdatascience/python-chatbot-10dec: direct link, hf CLI and curl.
- Browser
- Download file 9.22 kB
-
https://huggingface.co/spaces/decodingdatascience/python-chatbot-10dec/resolve/main/app.py
- Command line
-
hf download hf://spaces/decodingdatascience/python-chatbot-10dec/app.py
-
curl -L -o app.py https://huggingface.co/spaces/decodingdatascience/python-chatbot-10dec/resolve/main/app.py
9.22 kB
| import os | |
| import gradio as gr | |
| from openai import OpenAI | |
| # ----------------------------- | |
| # Load OpenAI key from HF Secrets | |
| # ----------------------------- | |
| 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. | |
| """ | |
| # ----------------------------- | |
| # OpenAI message state (internal) | |
| # ----------------------------- | |
| def init_messages(): | |
| return [ | |
| { | |
| "role": "system", | |
| "content": [{"type": "input_text", "text": SYSTEM_PROMPT}] | |
| } | |
| ] | |
| # ----------------------------- | |
| # Gradio Chatbot UI history (messages format) | |
| # Each item must be: {"role": "...", "content": "..."} | |
| # ----------------------------- | |
| 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() | |
| # add user turn for OpenAI | |
| messages.append( | |
| { | |
| "role": "user", | |
| "content": [{"type": "input_text", "text": user_text}] | |
| } | |
| ) | |
| # call API | |
| 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 | |
| # add assistant turn for OpenAI | |
| messages.append( | |
| { | |
| "role": "assistant", | |
| "content": [{"type": "output_text", "text": assistant_text}] | |
| } | |
| ) | |
| # update UI history (messages format) | |
| chat_history = append_ui_history(chat_history, user_text, assistant_text) | |
| return "", chat_history, messages | |
| # ----------------------------- | |
| # Gradio UI | |
| # ----------------------------- | |
| 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: | |
| # Header Row | |
| 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 for OpenAI messages | |
| state = gr.State(init_messages()) | |
| # Two-column layout | |
| with gr.Row(equal_height=True): | |
| # LEFT: FAQ + Quick Ask | |
| 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 | |
| ) | |
| # RIGHT: Chat area | |
| with gr.Column(scale=8, min_width=520): | |
| chatbot = gr.Chatbot( | |
| height=520, | |
| label="Conversation" | |
| # No bubble_full_width | |
| # No type= | |
| ) | |
| 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>" | |
| ) | |
| # FAQ -> fill textbox | |
| for b, q in zip(faq_buttons, FAQ_QUESTIONS): | |
| b.click(fn=lambda q=q: set_question(q), inputs=None, outputs=msg) | |
| # Optional quick preference: append hint to textbox (UI-only) | |
| 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) | |
| # Submit logic | |
| msg.submit(respond, inputs=[msg, chatbot, state], outputs=[msg, chatbot, state]) | |
| send.click(respond, inputs=[msg, chatbot, state], outputs=[msg, chatbot, state]) | |
| # Clear | |
| clear.click(fn=clear_all, inputs=None, outputs=[chatbot, state, msg]) | |
| demo.launch( | |
| debug=False, | |
| theme=gr.themes.Soft(), | |
| css=css | |
| ) | |