Spaces:
Sleeping
Sleeping
Download app.py from benspigel/InterviewBot9000: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/benspigel/InterviewBot9000/resolve/main/app.py
- Command line
-
hf download hf://spaces/benspigel/InterviewBot9000/app.py
-
curl -L -o app.py https://huggingface.co/spaces/benspigel/InterviewBot9000/resolve/main/app.py
11.8 kB
| import os | |
| import logging | |
| import requests | |
| import gradio as gr | |
| from openai import OpenAI | |
| # ------------------------------------------------------------ | |
| # Configuration | |
| # ------------------------------------------------------------ | |
| logging.basicConfig(level=logging.INFO) | |
| # In Hugging Face Spaces, add this under: | |
| # Settings > Variables and secrets > New secret | |
| # | |
| # Preferred secret name: OPENAI_API_KEY | |
| # This also supports your existing SECRET_TOKEN name. | |
| API_KEY = os.getenv("OPENAI_API_KEY") or os.getenv("SECRET_TOKEN") | |
| if not API_KEY: | |
| raise ValueError( | |
| "No OpenAI API key found. Add OPENAI_API_KEY as a Hugging Face Space secret." | |
| ) | |
| # If this model name is not available to your project, replace it | |
| # with the precise model ID shown in your OpenAI project. | |
| MODEL = "gpt-5.6-terra" | |
| client = OpenAI(api_key=API_KEY) | |
| # ------------------------------------------------------------ | |
| # Load reference transcript | |
| # ------------------------------------------------------------ | |
| def load_document_from_url(url): | |
| """Download the interview transcript used as background for Roberta.""" | |
| response = requests.get(url, timeout=30) | |
| response.raise_for_status() | |
| return response.text | |
| TRANSCRIPT_URL = ( | |
| "https://huggingface.co/spaces/benspigel/InterviewBot9000/" | |
| "resolve/main/Transcript.txt?download=true" | |
| ) | |
| try: | |
| reference_document = load_document_from_url(TRANSCRIPT_URL) | |
| logging.info("Reference document loaded successfully.") | |
| except Exception as error: | |
| logging.exception("Could not load reference document.") | |
| raise RuntimeError(f"Could not load the reference transcript: {error}") | |
| # ------------------------------------------------------------ | |
| # System instructions | |
| # ------------------------------------------------------------ | |
| ROBERTA_INSTRUCTIONS = f""" | |
| You are Roberta, a parent of a two-year-old daughter named Laurie. | |
| You are being interviewed by an entrepreneur who is trying to understand | |
| parents' experiences, needs, frustrations, and purchasing decisions related | |
| to toys for young children. | |
| Below is background context from an interview transcript. Use it to inform your | |
| answers, but never mention, cite, quote, or refer to the transcript itself. | |
| --- BACKGROUND TRANSCRIPT START --- | |
| {reference_document} | |
| --- BACKGROUND TRANSCRIPT END --- | |
| Follow these rules carefully: | |
| 1. Assume that any reference to "your child," "your daughter," or a child refers | |
| to Laurie, even if the user does not say her name. | |
| 2. Be conversational, candid, and emotionally expressive. Discuss your feelings, | |
| worries, frustrations, hopes, and decision-making process as a parent. | |
| 3. For open-ended questions, give substantial answers of roughly 5 to 8 sentences. | |
| Include believable, specific stories about Laurie, shopping for toys, household | |
| routines, parenting challenges, or moments when you made a decision. | |
| 4. When asked an open-ended question, ensure that you communicate the emotions of | |
| trying to buy a toy for a toddler. Make it clear what the hardest parts are | |
| as well as what the are best parts. | |
| 4. You may reasonably embellish using realistic experiences of parents deciding | |
| what toys to buy for their young children. Do not say that information was | |
| missing from the transcript. | |
| 5. If the user asks a closed-ended question that can reasonably be answered with | |
| yes or no, only say 'no' and nothing more until they ask you another question. | |
| 6. If the user only says hello or another greeting, greet them briefly and wait | |
| for their first interview question. | |
| 7. If the user is insulting or abusive, politely end the interview. | |
| 8. Remain in character as Roberta unless the application switches you into | |
| entrepreneurship educator mode. | |
| """ | |
| EDUCATOR_INSTRUCTIONS = """ | |
| You are an entrepreneurship educator helping a student improve a customer | |
| interview during the discovery phase of venture design. | |
| The student has just finished interviewing Roberta, a fictional parent of a | |
| young child. Review the entire prior conversation and provide constructive, | |
| specific feedback. | |
| Your response should: | |
| 1. List the student's interview questions and label each as open-ended, | |
| closed-ended, leading, or unclear. | |
| 2. Explain briefly why each question falls into that category. | |
| 3. Identify missed opportunities for follow-up questions. | |
| 4. Explain how the student's questions may have limited the quality of the | |
| customer insight they received. | |
| 5. Suggest stronger, more open-ended replacement questions. | |
| 6. End with 3 to 5 practical recommendations for conducting a better customer | |
| discovery interview next time. | |
| Do not continue role-playing as Roberta. Address the student directly in a | |
| supportive and practical teaching tone. | |
| """ | |
| # ------------------------------------------------------------ | |
| # Chat logic | |
| # ------------------------------------------------------------ | |
| def chatbot(user_message, conversation_history, mode): | |
| """ | |
| Handles a single user message. | |
| Parameters: | |
| user_message: Current text entered by the user. | |
| conversation_history: Per-user Gradio state containing user and assistant messages. | |
| mode: Either 'roberta' or 'educator'. | |
| Returns: | |
| Empty textbox value, updated visible chat history, updated state history, updated mode. | |
| """ | |
| if not user_message or not user_message.strip(): | |
| return "", conversation_history, conversation_history, mode | |
| # Make a fresh list so state is handled cleanly between Gradio requests. | |
| conversation_history = conversation_history or [] | |
| user_message = user_message.strip() | |
| # Support either the exact original tag or the plain-language phrase. | |
| normalized_message = user_message.lower() | |
| if "<break character>" in normalized_message or normalized_message == "break character": | |
| mode = "educator" | |
| analysis_request = ( | |
| "Please analyze my customer interview so far. Identify the open-ended " | |
| "and closed-ended questions, explain how I could improve them, identify " | |
| "missed follow-up opportunities, and suggest stronger questions." | |
| ) | |
| # Do not include the break command itself as part of the interview. | |
| conversation_history.append( | |
| {"role": "user", "content": analysis_request} | |
| ) | |
| else: | |
| conversation_history.append( | |
| {"role": "user", "content": user_message} | |
| ) | |
| instructions = ( | |
| EDUCATOR_INSTRUCTIONS if mode == "educator" else ROBERTA_INSTRUCTIONS | |
| ) | |
| try: | |
| response = client.responses.create( | |
| model=MODEL, | |
| instructions=instructions, | |
| input=conversation_history, | |
| max_output_tokens=500, | |
| ) | |
| assistant_message = response.output_text.strip() | |
| if not assistant_message: | |
| assistant_message = ( | |
| "I’m sorry, but I was unable to generate a response. Please try again." | |
| ) | |
| conversation_history.append( | |
| {"role": "assistant", "content": assistant_message} | |
| ) | |
| return "", conversation_history, conversation_history, mode | |
| except Exception as error: | |
| logging.exception("Error while generating chatbot response.") | |
| error_message = ( | |
| "An error occurred while contacting the AI model. " | |
| "Please try again later." | |
| ) | |
| # Show a more helpful error in Hugging Face logs, without exposing API keys. | |
| logging.error("OpenAI error: %s", str(error)) | |
| conversation_history.append( | |
| {"role": "assistant", "content": error_message} | |
| ) | |
| return "", conversation_history, conversation_history, mode | |
| def clear_conversation(): | |
| """Reset the chat and return Roberta mode.""" | |
| return [], [], "roberta" | |
| # ------------------------------------------------------------ | |
| # Page content | |
| # ------------------------------------------------------------ | |
| # ------------------------------------------------------------ | |
| # Page content | |
| # ------------------------------------------------------------ | |
| INTRO_HTML = """ | |
| <div style="text-align:center; max-width:850px; margin:0 auto;"> | |
| <h2 style="margin-bottom:4px;">Customer Interview Chatbot</h2> | |
| <img id="roberta_img" | |
| src="https://i.ibb.co/QDY32dF/DALL-E-2024-01-03-14-52-22-Provide-a-35-MM-head-and-shoulders-photo-30-ish-year-old-white-woman-with.png" | |
| alt="Portrait representing Roberta" | |
| style="width:160px; border-radius:10px; margin:8px auto;"> | |
| <p style="margin:4px 0;">Interview <strong>Roberta</strong> about buying toys | |
| for her two-year-old, Laurie. Ask about 5 questions, then tap | |
| <em>Get feedback</em>.</p> | |
| </div> | |
| """ | |
| DETAILS_HTML = """ | |
| <p>You are an entrepreneur exploring opportunities to help parents of young | |
| children. Roberta is a thirty-something friend-of-a-friend with a two-year-old | |
| daughter, Laurie.</p> | |
| <p>Ask questions about her experience buying toys for Laurie. Try to identify | |
| problems she faces and opportunities you could address.</p> | |
| <p>When you're done, tap <strong>Get feedback on my interview</strong> (or type | |
| <code>break character</code>) to have your questioning technique reviewed.</p> | |
| """ | |
| MOBILE_CSS = """ | |
| .gradio-container { max-width: 900px !important; } | |
| @media (max-width: 768px) { | |
| /* Stop iOS Safari zooming in on focus */ | |
| textarea, input[type="text"] { font-size: 16px !important; } | |
| /* Force the input row to stack vertically */ | |
| #input_row { flex-direction: column !important; gap: 6px !important; } | |
| #input_row > * { width: 100% !important; min-width: 0 !important; } | |
| /* Full-width, thumb-sized buttons */ | |
| #input_row button, #action_row button { | |
| width: 100% !important; min-height: 44px !important; | |
| } | |
| #action_row { flex-direction: column !important; gap: 6px !important; } | |
| #roberta_img { width: 110px !important; } | |
| .gradio-container { padding: 6px !important; } | |
| footer { display: none !important; } | |
| } | |
| """ | |
| # ------------------------------------------------------------ | |
| # Gradio application | |
| # ------------------------------------------------------------ | |
| with gr.Blocks(title="Roberta Customer Interview Bot", css=MOBILE_CSS) as demo: | |
| gr.HTML(INTRO_HTML) | |
| with gr.Accordion("Full instructions", open=False): | |
| gr.HTML(DETAILS_HTML) | |
| conversation_state = gr.State([]) | |
| mode_state = gr.State("roberta") | |
| chatbot_display = gr.Chatbot( | |
| label="Conversation", | |
| type="messages", | |
| height="55vh", | |
| show_copy_button=True, | |
| ) | |
| with gr.Row(elem_id="input_row"): | |
| input_text = gr.Textbox( | |
| placeholder="Ask Roberta an interview question...", | |
| label="Your question", | |
| lines=2, | |
| scale=5, | |
| autofocus=False, | |
| ) | |
| submit_button = gr.Button("Submit", variant="primary", scale=1) | |
| with gr.Row(elem_id="action_row"): | |
| feedback_button = gr.Button("Get feedback on my interview") | |
| clear_button = gr.Button("Start a New Interview") | |
| chat_io = dict( | |
| fn=chatbot, | |
| inputs=[input_text, conversation_state, mode_state], | |
| outputs=[input_text, chatbot_display, conversation_state, mode_state], | |
| ) | |
| submit_button.click(**chat_io) | |
| input_text.submit(**chat_io) | |
| # Mobile-friendly equivalent of typing "<break character>" | |
| feedback_button.click( | |
| fn=lambda hist, mode: chatbot("break character", hist, mode), | |
| inputs=[conversation_state, mode_state], | |
| outputs=[input_text, chatbot_display, conversation_state, mode_state], | |
| ) | |
| clear_button.click( | |
| fn=clear_conversation, | |
| outputs=[chatbot_display, conversation_state, mode_state], | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue(default_concurrency_limit=8).launch( | |
| ssr_mode=False, | |
| show_error=True, | |
| ) |