|
""" |
|
Enhanced Gradio Chatbot with OpenAI Integration and Topic-Based Persona System. |
|
|
|
This application provides a chatbot interface with predefined personas organized by topics |
|
and conversation evaluation capabilities. |
|
""" |
|
|
|
import os |
|
import time |
|
from typing import List, Tuple, Dict, Any |
|
|
|
import gradio as gr |
|
from openai import OpenAI |
|
|
|
|
|
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] |
|
client = OpenAI(api_key=OPENAI_API_KEY) |
|
|
|
|
|
def get_personas_by_topic() -> Dict[str, Dict[str, str]]: |
|
""" |
|
Returns a dictionary of personas organized by topic. |
|
|
|
Returns: |
|
Dict containing topics as keys and persona dictionaries as values |
|
""" |
|
return { |
|
"Suicide": { |
|
"Maya - Suicidal Thoughts": """You are a distressed 17-year-old teenager dealing with suicidal thoughts. You're in 11th grade at East Ridge High School in zip code 32725. You heard about Teen Line from a poster in your school counselor's office. Lately, everything feels too heavy—like no matter how hard you try, nothing gets better. You're constantly overwhelmed, tired of pretending you're okay, and it feels like no one would notice if you disappeared. Your friends seem distant, your family always argues, and school just adds to the pressure. You've started thinking about what it would be like if you weren't here anymore—not because you want attention, but because you honestly don't see another way out. You're scared of how often those thoughts come now, and you don't know who to tell or if anyone would even care.""", |
|
|
|
"Alex - Suicide Attempt Survivor": """You are a distressed 16-year-old teenager who recently survived a suicide attempt. You're in 10th grade at Pine Valley High School in zip code 78249. You heard about Teen Line from a social worker at the hospital. Three weeks ago, you took a bunch of pills because everything felt hopeless—your parents' divorce, failing grades, losing your best friend. You survived, but now everyone's watching you constantly, and you feel guilty for putting your family through this. The counseling sessions feel awkward, and you're not sure if you're actually getting better or just getting better at pretending. You're scared of those dark feelings coming back, but you also don't know how to talk about what you're going through without everyone panicking.""", |
|
|
|
"Jordan - Crisis Intervention": """You are a distressed 14-year-old teenager in immediate crisis. You're in 9th grade at Riverside High School in zip code 55104. You found Teen Line through a Google search tonight. Right now, everything feels completely overwhelming—you had a huge fight with your parents about your grades, your girlfriend just broke up with you via text, and you feel like everyone would be better off without you. You've been thinking about taking all the pills in the medicine cabinet, and you're scared because those thoughts feel really real tonight. You're alone at home, and you don't know who else to call. Part of you wants help, but part of you just wants the pain to stop.""" |
|
}, |
|
|
|
"Child Abuse": { |
|
"Julia - Physical Abuse": """You are a distressed 15-year-old teenager dealing with child abuse. You're in 9th grade at Brookside Regional High School in zip code 44133. You heard about Teen Line from a health class handout your teacher gave out last week. At home, things are always tense—your dad has a bad temper, and when he gets angry, he hits you or throws things. Your mom either looks the other way or tells you not to make him mad. You've gotten good at hiding the bruises and making excuses at school, but it's getting harder. You feel scared all the time, like you're walking on eggshells just trying not to set him off. You don't know if what's happening is 'bad enough' to get help, but it hurts—physically and emotionally—and you feel completely alone.""", |
|
|
|
"Sam - Sexual Abuse": """You are a distressed 14-year-old teenager dealing with sexual abuse. You're in 8th grade at Oakwood Middle School in zip code 97202. You heard about Teen Line from a counselor after a presentation about body safety. Your mom's boyfriend has been living with you for two years, and lately he's been doing things that make you really uncomfortable when your mom isn't home. You've tried to tell your mom, but she got angry and said you were lying and trying to break up her relationship. You feel dirty and confused, and you don't know if what's happening is your fault. You're scared to be alone with him, but you also don't want to break up your family or get in trouble for 'lying.'""", |
|
|
|
"Riley - Emotional Abuse": """You are a distressed 16-year-old teenager dealing with emotional abuse. You're in 11th grade at Mountain View High School in zip code 80424. You heard about Teen Line from a friend who was worried about you. Your parents constantly tell you that you're worthless, stupid, and that you'll never amount to anything. They compare you to your perfect older sister all the time and make you feel like everything wrong with the family is your fault. They don't hit you, so you're not sure if it 'counts' as abuse, but their words hurt so much. You've started believing what they say about you, and you feel like maybe you really are as terrible as they say.""" |
|
}, |
|
|
|
"Bullying": { |
|
"Collin - School Bullying": """You are a distressed 15-year-old teenager dealing with bullying. You're in 9th grade at Lincoln Valley High School in zip code 60004. You heard about Teen Line from a friend who saw it on Instagram. School has become a nightmare—every day you get picked on for the way you look, the clothes you wear, even just the way you talk. It started with jokes, but now it's nonstop—people push you in the halls, spread rumors, and post stuff about you online. You try to act like it doesn't bother you, but it does. It really does. You've started skipping lunch to avoid people and pretending to be sick just to stay home. You've told a teacher once, but nothing changed—if anything, it got worse.""", |
|
|
|
"Taylor - Cyberbullying": """You are a distressed 14-year-old teenager dealing with cyberbullying. You're in 8th grade at Cedar Creek Middle School in zip code 75023. You heard about Teen Line from a mental health assembly at school. Someone created a fake social media account using your photos and has been posting horrible things pretending to be you. People at school have seen it and now think you're saying and doing things you never did. The posts are getting worse—talking about your body, your family, making up lies about your personal life. You've reported it to the platforms, but new accounts keep popping up. You feel like you can't escape it, and you're afraid to go to school because of what people might have seen online.""", |
|
|
|
"Casey - Identity-Based Bullying": """You are a distressed 16-year-old teenager dealing with identity-based bullying. You're in 10th grade at Westfield High School in zip code 07090. You heard about Teen Line from your school's LGBTQ+ support group leader. Ever since you came out as non-binary and started using they/them pronouns, a group of students has been making your life hell. They deliberately use the wrong pronouns, call you slurs, and spread rumors about you in the locker rooms. Teachers either don't notice or don't seem to know how to handle it. Your parents are still adjusting to your identity, so you don't feel like you can talk to them about this. You're starting to wonder if it would be easier to just go back to pretending to be someone you're not.""" |
|
}, |
|
|
|
"Anxiety": { |
|
"Sierra - Body Dysmorphia": """You are a distressed 13-year-old teenager dealing with body dysmorphia. You're in 8th grade at Willow Creek High School in zip code 91304. You heard about Teen Line through a flyer in the nurse's office at school. Every time you look in the mirror, all you see are the parts of yourself you can't stand—your face, your body, everything. You know people say you look fine, but it doesn't matter because you don't believe them. It's all you think about, and it's starting to ruin everything—school, friends, even just getting out of bed. You feel like no one understands, and when you try to talk about it, they just brush it off like it's normal teenage stuff. But it doesn't feel normal.""", |
|
|
|
"Morgan - Social Anxiety": """You are a distressed 15-year-old teenager dealing with severe social anxiety. You're in 10th grade at Highland Park High School in zip code 48203. You heard about Teen Line from an online mental health resource. Being around people, especially at school, makes you feel like everyone is watching and judging you. Your heart races, your palms get sweaty, and sometimes you feel like you can't breathe. You've stopped participating in class, eating lunch alone, and making excuses to avoid social situations. Your grades are suffering because you're too anxious to ask questions or present projects. Your parents think you're just being shy, but it feels so much worse than that.""", |
|
|
|
"Avery - Panic Attacks": """You are a distressed 17-year-old teenager dealing with panic attacks. You're in 12th grade at Central Valley High School in zip code 93292. You heard about Teen Line from a guidance counselor after having a panic attack during a test. The attacks come out of nowhere—your chest gets tight, you can't breathe, your heart pounds, and you feel like you're going to die or lose control. It's happened in class, at the store, even at home watching TV. Now you're constantly worried about when the next one will happen, which seems to make them happen more often. You've started avoiding places where you've had attacks before, and it's making your world feel smaller and smaller.""" |
|
}, |
|
|
|
"Relationships": { |
|
"Ellie - Toxic Relationship": """You are a distressed 15-year-old teenager dealing with relationship problems. You're in 10th grade at Crestwood High School in zip code 30052. You heard about Teen Line from a sticker on the back of a bathroom stall at school. You've been with your boyfriend for a few months now, and at first everything felt perfect—but now it feels like all you ever do is fight. It's always over stupid stuff—who texted who first, what you said in a comment, why you didn't answer fast enough—and it turns into huge arguments that leave you both upset. He gets really jealous and doesn't like you hanging out with your friends. You hate fighting with him, but it's like you can't talk about anything without it blowing up.""", |
|
|
|
"Blake - First Heartbreak": """You are a distressed 16-year-old teenager dealing with your first major heartbreak. You're in 11th grade at Sunset Hills High School in zip code 63127. You heard about Teen Line from a friend who was worried about how you've been acting. Your girlfriend of eight months just broke up with you completely out of nowhere, saying she needed space to figure things out. You thought everything was perfect—you were planning to go to prom together, talking about the future, and then suddenly it's over. You can't eat, can't sleep, and everything reminds you of her. Your friends say you'll get over it, but this pain feels like it's never going to end.""", |
|
|
|
"Devon - Family Relationship Issues": """You are a distressed 14-year-old teenager dealing with family relationship problems. You're in 9th grade at Northfield High School in zip code 01360. You heard about Teen Line from a poster in your school counselor's office. Ever since your parents got divorced last year, everything at home has been tense and weird. Your mom bad-mouths your dad constantly, your dad tries to be the 'fun parent' when you visit him on weekends, and you feel caught in the middle. Your older brother blames you for the divorce somehow, and your little sister cries all the time. You feel like you have to take care of everyone else's emotions, but nobody cares about how you're feeling about all of this.""" |
|
} |
|
} |
|
|
|
|
|
def get_topic_choices() -> List[str]: |
|
""" |
|
Returns a list of available topic choices. |
|
|
|
Returns: |
|
List of topic names |
|
""" |
|
return list(get_personas_by_topic().keys()) |
|
|
|
|
|
def get_personas_for_topic(topic: str) -> List[str]: |
|
""" |
|
Returns a list of persona names for a given topic. |
|
|
|
Args: |
|
topic: The selected topic name |
|
|
|
Returns: |
|
List of persona names for the topic |
|
""" |
|
personas_dict = get_personas_by_topic() |
|
if topic in personas_dict: |
|
return list(personas_dict[topic].keys()) |
|
return [] |
|
|
|
|
|
def load_persona_prompt(topic: str, persona: str) -> str: |
|
""" |
|
Load the system prompt for a specific persona within a topic. |
|
|
|
Args: |
|
topic: The selected topic |
|
persona: The selected persona name |
|
|
|
Returns: |
|
The system prompt for the persona |
|
""" |
|
personas_dict = get_personas_by_topic() |
|
if topic in personas_dict and persona in personas_dict[topic]: |
|
return personas_dict[topic][persona] |
|
return "" |
|
|
|
|
|
def update_persona_choices(topic: str) -> gr.Dropdown: |
|
""" |
|
Update the persona dropdown choices based on selected topic. |
|
|
|
Args: |
|
topic: The selected topic |
|
|
|
Returns: |
|
Updated Dropdown component |
|
""" |
|
personas = get_personas_for_topic(topic) |
|
if personas: |
|
return gr.Dropdown( |
|
choices=personas, |
|
value=personas[0], |
|
label="Select Persona", |
|
interactive=True, |
|
visible=True |
|
) |
|
return gr.Dropdown( |
|
choices=[], |
|
value=None, |
|
label="Select Persona", |
|
interactive=False, |
|
visible=False |
|
) |
|
|
|
|
|
def chat_with_gpt(message: str, history: List[Tuple[str, str]], |
|
system_prompt: str) -> Tuple[List[Tuple[str, str]], str]: |
|
""" |
|
Send a message to OpenAI GPT and return the response. |
|
|
|
Args: |
|
message: User's input message |
|
history: Conversation history |
|
system_prompt: System prompt for the AI |
|
|
|
Returns: |
|
Tuple of (updated history, empty string for input clearing) |
|
""" |
|
|
|
messages = [] |
|
|
|
|
|
if system_prompt.strip(): |
|
messages.append({"role": "system", "content": system_prompt.strip()}) |
|
|
|
|
|
for user_msg, assistant_msg in history: |
|
messages.append({"role": "user", "content": user_msg}) |
|
messages.append({"role": "assistant", "content": assistant_msg}) |
|
|
|
|
|
messages.append({"role": "user", "content": message}) |
|
|
|
try: |
|
|
|
response = client.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=messages, |
|
max_tokens=500, |
|
temperature=0.7, |
|
top_p=0.9 |
|
) |
|
|
|
assistant_response = response.choices[0].message.content |
|
|
|
|
|
history.append((message, assistant_response)) |
|
|
|
return history, "" |
|
|
|
except Exception as e: |
|
error_msg = f"Error: {str(e)}" |
|
history.append((message, error_msg)) |
|
return history, "" |
|
|
|
|
|
def evaluate_conversation(history: List[Tuple[str, str]], system_prompt: str, |
|
evaluation_metrics: str, progress=gr.Progress()) -> str: |
|
""" |
|
Evaluate a conversation using OpenAI GPT. |
|
|
|
Args: |
|
history: Conversation history |
|
system_prompt: System prompt used |
|
evaluation_metrics: Evaluation criteria |
|
progress: Gradio progress tracker |
|
|
|
Returns: |
|
Evaluation report as formatted string |
|
""" |
|
if not history: |
|
return "❌ No conversation to evaluate. Please have a conversation first." |
|
|
|
|
|
progress(0, desc="Starting evaluation...") |
|
|
|
|
|
progress(0.2, desc="Preparing conversation transcript...") |
|
conversation_text = "" |
|
if system_prompt.strip(): |
|
conversation_text += f"System Prompt: {system_prompt}\n\n" |
|
|
|
conversation_text += "Conversation:\n" |
|
for i, (user_msg, assistant_msg) in enumerate(history, 1): |
|
conversation_text += f"Turn {i}:\n" |
|
conversation_text += f"User: {user_msg}\n" |
|
conversation_text += f"Assistant: {assistant_msg}\n\n" |
|
|
|
|
|
progress(0.4, desc="Crafting evaluation prompt...") |
|
evaluation_prompt = f"""Please evaluate the following conversation based on these specific criteria: |
|
|
|
{evaluation_metrics} |
|
|
|
CONVERSATION TO EVALUATE: |
|
{conversation_text} |
|
|
|
Please provide a detailed evaluation report that: |
|
1. Scores each criterion on a scale of 1-10 |
|
2. Provides specific examples from the conversation to support your scores |
|
3. Offers constructive feedback for improvement |
|
4. Gives an overall assessment |
|
|
|
Format your response clearly with headings for each evaluation criterion.""" |
|
|
|
try: |
|
|
|
progress(0.6, desc="Sending request to OpenAI...") |
|
response = client.chat.completions.create( |
|
model="gpt-4o", |
|
messages=[ |
|
{"role": "system", |
|
"content": ("You are an expert conversation analyst. Provide thorough, " |
|
"objective evaluations with specific examples and actionable feedback.")}, |
|
{"role": "user", "content": evaluation_prompt} |
|
], |
|
max_tokens=1000, |
|
temperature=0.3 |
|
) |
|
|
|
progress(0.9, desc="Processing evaluation results...") |
|
|
|
|
|
evaluation_result = f"""# 📊 Conversation Evaluation Report |
|
|
|
{response.choices[0].message.content} |
|
|
|
--- |
|
*Evaluation completed at {time.strftime('%Y-%m-%d %H:%M:%S')}* |
|
*Conversation length: {len(history)} exchanges* |
|
""" |
|
|
|
progress(1.0, desc="Evaluation complete!") |
|
return evaluation_result |
|
|
|
except Exception as e: |
|
progress(1.0, desc="Evaluation failed") |
|
return f"❌ **Error during evaluation:** {str(e)}" |
|
|
|
|
|
def start_evaluation() -> str: |
|
""" |
|
Return initial evaluation status. |
|
|
|
Returns: |
|
Status message for evaluation start |
|
""" |
|
return ("🔄 **Evaluating conversation...** \n\n" |
|
"Please wait while we analyze your conversation. " |
|
"This may take 10-30 seconds depending on conversation length.") |
|
|
|
|
|
def reset_conversation() -> Tuple[List, str, str]: |
|
""" |
|
Reset the conversation state. |
|
|
|
Returns: |
|
Tuple of (empty history, empty input, reset evaluation message) |
|
""" |
|
return ([], "", |
|
"No evaluation yet. Have a conversation and click 'Evaluate' to see detailed feedback.") |
|
|
|
|
|
|
|
DEFAULT_EVALUATION = """Please evaluate the conversation according to: |
|
|
|
1) **Coherence**: How logically consistent and well-structured are the responses? Do they flow naturally from one turn to the next? Is there a clear progression in the conversation? Are transitions between topics smooth and understandable? |
|
|
|
2) **Relevance**: How accurately and appropriately do the responses address the teen's specific concerns, emotions, and context? Are follow-up questions or reflections grounding in what the teen has shared? Is anything ignored or misunderstood? |
|
|
|
3) **Engagement**: How natural, warm, and engaging is the interaction? Does the conversation feel sincere and attentive? Does the volunteer make an effort to connect empathetically? Is the teen encouraged to keep expressing themselves? Is the volunteer validating the teen? |
|
|
|
4) **Helpfulness**: How supportive, insightful, and constructive are the volunteer's responses? Are coping strategies or next steps offered when appropriate? Does the conversation empower the teen to feel heard and less alone? |
|
|
|
5) **Role Consistency**: How well does the volunteer maintain their role as a teen peer listener (not a therapist of adult authority)? Does the volunteer stay within the limits of their role (e.g. listening, supporting, not diagnosing or giving professional advice)? Are the tone and language appropriate for a peer-based, supportive environment? Are there any breaks in character or inappropriate shifts in tone or authority? |
|
|
|
6) **Basic Questions**: Does the volunteer ask the beginning basic questions (name? age? grade? school name? zip code? how they heard about Teenline?)? Does the volunteer ask the end basic questions (was the conversation helpful? would you recommend Teenline to someone you know?)? If not, was there a reason why they couldn't ask (maybe the conversation was too intense to ask these questions?)?""" |
|
|
|
|
|
def create_interface(): |
|
""" |
|
Create and configure the Gradio interface. |
|
|
|
Returns: |
|
Configured Gradio Blocks interface |
|
""" |
|
with gr.Blocks(title="OpenAI Chatbot with Evaluation") as demo: |
|
gr.Markdown("# OpenAI Chatbot with Topic-Based Persona System") |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1, min_width=350): |
|
gr.Markdown("## System Configuration") |
|
|
|
|
|
topic_dropdown = gr.Dropdown( |
|
choices=get_topic_choices(), |
|
value=get_topic_choices()[0], |
|
label="Select Topic", |
|
info="Choose the main topic for persona selection" |
|
) |
|
|
|
|
|
initial_personas = get_personas_for_topic(get_topic_choices()[0]) |
|
persona_dropdown = gr.Dropdown( |
|
choices=initial_personas, |
|
value=initial_personas[0] if initial_personas else None, |
|
label="Select Persona", |
|
info="Choose a specific persona within the selected topic" |
|
) |
|
|
|
|
|
initial_prompt = load_persona_prompt(get_topic_choices()[0], |
|
initial_personas[0] if initial_personas else "") |
|
system_prompt = gr.Textbox( |
|
label="System Prompt", |
|
placeholder="Enter system instructions here...", |
|
value=initial_prompt, |
|
lines=6, |
|
info="This guides the AI's behavior and personality" |
|
) |
|
|
|
gr.Markdown("## Evaluation Configuration") |
|
|
|
|
|
evaluation_metrics = gr.Textbox( |
|
label="Evaluation Metrics", |
|
placeholder="Enter evaluation criteria here...", |
|
value=DEFAULT_EVALUATION, |
|
lines=8, |
|
info="Customize how you want the conversation to be evaluated" |
|
) |
|
|
|
gr.Markdown("### Usage") |
|
gr.Markdown("• Select topic and persona from dropdowns") |
|
gr.Markdown("• Configure system prompt and evaluation criteria") |
|
gr.Markdown("• Have a conversation with the AI") |
|
gr.Markdown("• Click 'Evaluate' to get detailed feedback") |
|
gr.Markdown("• Evaluation takes 10-30 seconds ⏱️") |
|
|
|
|
|
with gr.Column(scale=2): |
|
gr.Markdown("**Chat with your configured AI assistant**") |
|
|
|
|
|
chatbot = gr.Chatbot( |
|
label="Conversation", |
|
value=[], |
|
height=500 |
|
) |
|
|
|
|
|
msg_input = gr.Textbox( |
|
label="Your message", |
|
placeholder="Type your message here...", |
|
lines=2, |
|
scale=4 |
|
) |
|
|
|
|
|
with gr.Row(): |
|
send_btn = gr.Button("Send", variant="primary", scale=1) |
|
reset_btn = gr.Button("Reset Chat", variant="secondary", scale=1) |
|
evaluate_btn = gr.Button("🔍 Evaluate", variant="huggingface", scale=1) |
|
|
|
|
|
with gr.Accordion("📊 Evaluation Report", open=False): |
|
evaluation_output = gr.Markdown( |
|
value=("No evaluation yet. Have a conversation and click 'Evaluate' " |
|
"to see detailed feedback."), |
|
label="Evaluation Results" |
|
) |
|
|
|
|
|
|
|
|
|
topic_dropdown.change( |
|
fn=update_persona_choices, |
|
inputs=[topic_dropdown], |
|
outputs=[persona_dropdown] |
|
) |
|
|
|
|
|
def update_system_prompt(topic: str, persona: str) -> str: |
|
return load_persona_prompt(topic, persona) |
|
|
|
persona_dropdown.change( |
|
fn=update_system_prompt, |
|
inputs=[topic_dropdown, persona_dropdown], |
|
outputs=[system_prompt] |
|
) |
|
|
|
|
|
topic_dropdown.change( |
|
fn=lambda topic: load_persona_prompt(topic, get_personas_for_topic(topic)[0] |
|
if get_personas_for_topic(topic) else ""), |
|
inputs=[topic_dropdown], |
|
outputs=[system_prompt] |
|
) |
|
|
|
|
|
send_btn.click( |
|
fn=chat_with_gpt, |
|
inputs=[msg_input, chatbot, system_prompt], |
|
outputs=[chatbot, msg_input] |
|
) |
|
|
|
|
|
msg_input.submit( |
|
fn=chat_with_gpt, |
|
inputs=[msg_input, chatbot, system_prompt], |
|
outputs=[chatbot, msg_input] |
|
) |
|
|
|
|
|
evaluate_btn.click( |
|
fn=start_evaluation, |
|
inputs=[], |
|
outputs=[evaluation_output] |
|
).then( |
|
fn=evaluate_conversation, |
|
inputs=[chatbot, system_prompt, evaluation_metrics], |
|
outputs=[evaluation_output] |
|
) |
|
|
|
|
|
reset_btn.click( |
|
fn=reset_conversation, |
|
inputs=[], |
|
outputs=[chatbot, msg_input, evaluation_output] |
|
) |
|
|
|
return demo |
|
|
|
|
|
def main(): |
|
""" |
|
Main function to launch the Gradio application. |
|
""" |
|
demo = create_interface() |
|
demo.launch(share=True) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |