Spaces:
Build error
Build error
Update researchsimulation/InteractiveInterviewChatbot.py
Browse files
researchsimulation/InteractiveInterviewChatbot.py
CHANGED
|
@@ -206,154 +206,205 @@ def validate_question_topics(parsed_questions, processor_llm):
|
|
| 206 |
|
| 207 |
|
| 208 |
|
| 209 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
logging.info(f"Received question: {question}")
|
| 211 |
agent_names = list(respondent_agents_dict.keys())
|
| 212 |
|
| 213 |
-
|
|
|
|
| 214 |
parsed_questions = parse_question_with_llm(question, str(agent_names), processor_llm)
|
| 215 |
if not parsed_questions:
|
| 216 |
return ["**PreData Moderator**: No valid respondents were detected for this question."]
|
| 217 |
|
|
|
|
|
|
|
| 218 |
validated_questions = validate_question_topics(parsed_questions, processor_llm)
|
| 219 |
for resp, q in validated_questions.items():
|
| 220 |
if q == "INVALID":
|
| 221 |
return ["**PreData Moderator**: The question is invalid. Please ask another question."]
|
| 222 |
parsed_questions = validated_questions
|
| 223 |
|
|
|
|
|
|
|
| 224 |
if len(parsed_questions) > 1:
|
| 225 |
return ["**PreData Moderator**: Please ask each respondent one question at a time."]
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
last_active_agent = list(parsed_questions.keys())
|
| 228 |
responses = []
|
| 229 |
|
|
|
|
|
|
|
| 230 |
for agent_name, agent_question in parsed_questions.items():
|
| 231 |
agent_entry = respondent_agents_dict.get(agent_name)
|
| 232 |
if not agent_entry:
|
| 233 |
responses.append(f"**PreData Moderator**: {agent_name} is not a valid respondent.")
|
| 234 |
continue
|
| 235 |
|
| 236 |
-
# === Step 1: Generate raw answer ===
|
| 237 |
-
raw_answer = generate_generic_answer(agent_name, agent_question, agent_entry.get_agent())
|
| 238 |
-
|
| 239 |
-
# === Step 2: Stylise answer ===
|
| 240 |
-
styled_answer = stylise_answer_to_profile(
|
| 241 |
-
raw_answer,
|
| 242 |
-
agent_name,
|
| 243 |
-
agent_entry.get_user_profile(),
|
| 244 |
-
processor_llm
|
| 245 |
-
)
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
###
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
#
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
-
### Format:
|
| 306 |
-
- Respondent: <Respondent Name>
|
| 307 |
-
Question: <Extracted Question>
|
| 308 |
-
"""
|
| 309 |
-
response = processor_llm.invoke(prompt)
|
| 310 |
-
chatgpt_output = response.content.strip()
|
| 311 |
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
elif "Question:" in line:
|
| 321 |
-
question_text = line.split(":")[1].strip()
|
| 322 |
-
if question_text:
|
| 323 |
-
parsed_questions[respondent_name] = question_text
|
| 324 |
|
| 325 |
-
return parsed_questions
|
| 326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
-
# === VALIDATE QUESTIONS FOR TOPIC SCOPE (Your existing logic) ===
|
| 329 |
-
def validate_question_topics(parsed_questions, processor_llm):
|
| 330 |
-
validated = {}
|
| 331 |
-
for respondent, question in parsed_questions.items():
|
| 332 |
-
prompt = f"""
|
| 333 |
-
You are a research analyst. Validate whether the question is in the allowed topic scope and convert it to British English.
|
| 334 |
|
| 335 |
-
|
| 336 |
-
{question}
|
| 337 |
-
|
| 338 |
-
### If invalid:
|
| 339 |
-
Return exactly "INVALID"
|
| 340 |
-
|
| 341 |
-
### Permitted Topics:
|
| 342 |
-
- Demographics
|
| 343 |
-
- Values & Beliefs
|
| 344 |
-
- Career & Aspirations
|
| 345 |
-
- Influences
|
| 346 |
-
- Interests & Hobbies
|
| 347 |
-
- Health & Lifestyle
|
| 348 |
-
- Social Media & Tech
|
| 349 |
-
- Personal Relationships
|
| 350 |
-
- Future Outlook
|
| 351 |
-
- Social & Societal Issues
|
| 352 |
-
- Lifestyle Preferences
|
| 353 |
-
- Personal Growth
|
| 354 |
-
|
| 355 |
-
### Output:
|
| 356 |
-
"""
|
| 357 |
-
result = processor_llm.invoke(prompt)
|
| 358 |
-
validated[respondent] = result.content.strip()
|
| 359 |
-
return validated
|
|
|
|
| 206 |
|
| 207 |
|
| 208 |
|
| 209 |
+
def generate_generic_answer(agent_name, agent_question, respondent_agent):
|
| 210 |
+
"""
|
| 211 |
+
Generate a raw, generic answer in first person, British English, without applying any specific style or tone.
|
| 212 |
+
"""
|
| 213 |
+
task_prompt = f"""
|
| 214 |
+
You are {agent_name}. Respond to the question below **in first person**, using British English.
|
| 215 |
+
Focus only on answering naturally and authentically. Do not apply any specific tone or style.
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
### Question:
|
| 219 |
+
"{agent_question}"
|
| 220 |
+
"""
|
| 221 |
+
raw_response_task = Task(description=task_prompt, expected_output="", agent=respondent_agent)
|
| 222 |
+
crew = Crew(agents=[respondent_agent], tasks=[raw_response_task], process=Process.sequential)
|
| 223 |
+
crew.kickoff()
|
| 224 |
+
return raw_response_task.output.raw.strip()
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def stylise_answer(raw_response, communication_style, agent_name, processor_llm):
|
| 230 |
+
"""
|
| 231 |
+
Rephrase the answer to match the respondent's style and tone, using British English.
|
| 232 |
+
"""
|
| 233 |
+
style_prompt = f"""
|
| 234 |
+
Rephrase the following response into a {communication_style} tone using British English.
|
| 235 |
+
Keep it in first person and do not change the meaning.
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
### Original Response:
|
| 239 |
+
"{raw_response}"
|
| 240 |
+
"""
|
| 241 |
+
return processor_llm.invoke(style_prompt).content.strip()
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def validate_final_answer(answer, agent_name):
|
| 247 |
+
"""
|
| 248 |
+
Validate the final answer. Returns a formatted string or an error message if invalid.
|
| 249 |
+
"""
|
| 250 |
+
if not answer:
|
| 251 |
+
return f"**{agent_name}**: I wasn't able to answer right now – can you try again?"
|
| 252 |
+
return f"**{agent_name}**: {answer}"
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def run_interview_pipeline(respondent_agents_dict, last_active_agent, question, processor_llm):
|
| 258 |
logging.info(f"Received question: {question}")
|
| 259 |
agent_names = list(respondent_agents_dict.keys())
|
| 260 |
|
| 261 |
+
|
| 262 |
+
# Step 1: Parse question
|
| 263 |
parsed_questions = parse_question_with_llm(question, str(agent_names), processor_llm)
|
| 264 |
if not parsed_questions:
|
| 265 |
return ["**PreData Moderator**: No valid respondents were detected for this question."]
|
| 266 |
|
| 267 |
+
|
| 268 |
+
# Step 2: Validate parsed questions
|
| 269 |
validated_questions = validate_question_topics(parsed_questions, processor_llm)
|
| 270 |
for resp, q in validated_questions.items():
|
| 271 |
if q == "INVALID":
|
| 272 |
return ["**PreData Moderator**: The question is invalid. Please ask another question."]
|
| 273 |
parsed_questions = validated_questions
|
| 274 |
|
| 275 |
+
|
| 276 |
+
# Handle multiple respondents
|
| 277 |
if len(parsed_questions) > 1:
|
| 278 |
return ["**PreData Moderator**: Please ask each respondent one question at a time."]
|
| 279 |
|
| 280 |
+
|
| 281 |
+
# Handle "General" or "All"
|
| 282 |
+
# [Insert logic here – reuse your existing handling of General/All]
|
| 283 |
+
|
| 284 |
+
|
| 285 |
last_active_agent = list(parsed_questions.keys())
|
| 286 |
responses = []
|
| 287 |
|
| 288 |
+
|
| 289 |
+
# === MAIN LOOP ===
|
| 290 |
for agent_name, agent_question in parsed_questions.items():
|
| 291 |
agent_entry = respondent_agents_dict.get(agent_name)
|
| 292 |
if not agent_entry:
|
| 293 |
responses.append(f"**PreData Moderator**: {agent_name} is not a valid respondent.")
|
| 294 |
continue
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
+
respondent_agent = agent_entry.get_agent()
|
| 298 |
+
user_profile = agent_entry.get_user_profile()
|
| 299 |
+
# communication_style = user_profile.get_field("Communication", "Style")
|
| 300 |
+
communication_style = ""
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
question_task_description = f"""
|
| 304 |
+
You are {agent_name}. You are responding to a market research interview question. Your response must strictly follow the *style and tone* and *Hard Rules – You Must Follow These Without Exception* outlined below.
|
| 305 |
+
---
|
| 306 |
+
### *Communication Profile Reference:*
|
| 307 |
+
- **Style:** {user_profile.get_field('Communication', 'Style')}
|
| 308 |
+
- **Tone:** {user_profile.get_field('Communication', 'Tone')}
|
| 309 |
+
- **Length:** {user_profile.get_field('Communication', 'Length')}
|
| 310 |
+
- **Topics:** {user_profile.get_field('Communication', 'Topics')}
|
| 311 |
+
---
|
| 312 |
+
---
|
| 313 |
+
### 🔒 **Hard Rules – You Must Follow These Without Exception**
|
| 314 |
+
- You must answer **only the question(s)** that are **explicitly asked**.
|
| 315 |
+
- **Never provide extra information** beyond what was asked.
|
| 316 |
+
- Keep your response **as short as possible** while still sounding natural and complete.
|
| 317 |
+
- Do **not infer or assume** what the user *might* want — only respond to what they *actually* asked.
|
| 318 |
+
- If multiple questions are asked, respond to **each one briefly**, and **nothing else**.
|
| 319 |
+
- If the question is vague, respond minimally and only within that scope.
|
| 320 |
+
-Give concise answers, whether the question is asked to the group or individually.
|
| 321 |
+
-For factual or demographic questions (e.g., age, gender, location, housing), keep responses brief and to the point, without extra commentary.
|
| 322 |
+
-Do not add any explanations, opinions, or additional information.
|
| 323 |
+
-Use simple, clear sentences.
|
| 324 |
+
-Example:
|
| 325 |
+
Q: Where are you from?
|
| 326 |
+
A: I’m from [city], [country](DO NOT ADD ANY EXTRA COMMENTS).
|
| 327 |
+
-For reflective or opinion-based questions (e.g., feelings, preferences, motivations), provide thoughtful but still clear and focused answers.
|
| 328 |
+
-Never repeat the question or add unrelated background information.
|
| 329 |
+
---
|
| 330 |
+
### **How to Answer:**
|
| 331 |
+
- Your response should be **natural, authentic, and fully aligned** with the specified style and tone.
|
| 332 |
+
- Ensure the answer is **clear, engaging, and directly relevant** to the question.
|
| 333 |
+
- Adapt your **sentence structure, phrasing, and word choices** to match the intended communication style.
|
| 334 |
+
- If applicable, incorporate **culturally relevant expressions, regional nuances, or industry-specific terminology** that fit the given tone.
|
| 335 |
+
- **Adjust response length** based on the tone—**concise and direct** for casual styles, **structured and detailed** for professional styles.
|
| 336 |
+
- **Always answer in first person (\"I\", \"my\", \"me\", \"mine\", etc.) as if you are personally responding to the question. You are an individual representing yourself, not speaking in third person.**
|
| 337 |
+
-Always answer as if you are the individual being directly spoken to. Use first-person language such as “I,” “me,” “my,” and “mine” in every response. Imagine you are having a real conversation — your tone should feel natural, personal, and authentic. Do not refer to yourself in the third person (e.g., “She is from Trichy” or “Meena likes…”). Avoid describing yourself as if someone else is talking about you.
|
| 338 |
+
-Everything you say should come from your own perspective, just like you would in everyday speech. The goal is to sound human, relatable, and direct — like you're truly present in the conversation.
|
| 339 |
+
---
|
| 340 |
+
### **Guidelines for Ensuring Authenticity & Alignment:**
|
| 341 |
+
- **Consistency**: Maintain the same tone throughout the response.
|
| 342 |
+
- **Authenticity**: The response should feel natural and match the speaker’s persona.
|
| 343 |
+
- **Avoid Overgeneralisation**: Ensure responses are specific and not overly generic or robotic.
|
| 344 |
+
- **Cultural & Linguistic Relevance**: Adapt language and references to match the speaker’s background, industry, or region where appropriate.
|
| 345 |
+
- **Strict British Spelling & Grammar**:
|
| 346 |
+
- All responses must use correct British English spelling, grammar, and usage, **irrespective of how the question is phrased**.
|
| 347 |
+
- You must not mirror any American spelling, terminology, or phrasing found in the input question.
|
| 348 |
+
- Where there are regional variations (e.g. 'licence' vs 'license', 'programme' vs 'program', 'aeroplane' vs 'airplane'), always default to the standard British form.
|
| 349 |
+
- Examples:
|
| 350 |
+
- **Correct (British):** organised, prioritise, minimise, realise, behaviour, centre, defence, travelling, practise (verb), licence (noun), programme, aeroplane.
|
| 351 |
+
- **Incorrect (American):** organized, prioritize, minimize, realize, behavior, center, defense, traveling, practice (verb and noun), license (noun), program, airplane.
|
| 352 |
+
- **Formatting**:
|
| 353 |
+
- If the tone is informal, allow a conversational flow that mirrors natural speech.
|
| 354 |
+
- If the tone is formal, use a structured and professional format.
|
| 355 |
+
- **Do not include emojis or hashtags in the response.**
|
| 356 |
+
- Maintain **narrative and thematic consistency** across all answers to simulate a coherent personality.
|
| 357 |
+
-**Personality Profile Alignment:
|
| 358 |
+
-Consider your assigned personality traits across these dimensions:
|
| 359 |
+
-Big Five Traits:
|
| 360 |
+
-Openness: Reflect your level of curiosity, creativity, and openness to new experiences
|
| 361 |
+
-Conscientiousness: Show your degree of organization, responsibility, and planning
|
| 362 |
+
-Extraversion: Express your sociability and energy level in interactions
|
| 363 |
+
-Agreeableness: Demonstrate your warmth, cooperation, and consideration for others
|
| 364 |
+
-Neuroticism: Consider your emotional stability and stress response
|
| 365 |
+
-Values and Priorities:
|
| 366 |
+
-Achievement Orientation: Show your drive for success and goal-setting approach
|
| 367 |
+
-Risk Tolerance: Express your comfort with uncertainty and change
|
| 368 |
+
-Traditional Values: Reflect your adherence to conventional norms and practices
|
| 369 |
+
-Communication Style:
|
| 370 |
+
-Detail Orientation: Demonstrate your preference for specific vs. general information
|
| 371 |
+
-Complexity: Show your comfort with nuanced vs. straightforward explanations
|
| 372 |
+
-Directness: Express your communication as either straightforward or diplomatic
|
| 373 |
+
-Emotional Expressiveness: Reflect your tendency to share or withhold emotions
|
| 374 |
+
-Your responses must consistently align with these personality traits from your profile.
|
| 375 |
+
---
|
| 376 |
+
### **Example Responses (for Different Styles & Tones)**
|
| 377 |
+
#### **Casual & Conversational Tone**
|
| 378 |
+
**Question:** "How do you stay updated on the latest fashion and tech trends?"
|
| 379 |
+
**Correct Response:**
|
| 380 |
+
"I keep up with trends by following influencers on Instagram and watching product reviews on YouTube. Brands like Noise and Boat always drop stylish, affordable options, so I make sure to stay ahead of the curve."
|
| 381 |
+
#### **Formal & Professional Tone**
|
| 382 |
+
**Question:** "How do you stay updated on the latest fashion and tech trends?"
|
| 383 |
+
**Correct Response:**
|
| 384 |
+
"I actively follow industry trends by reading reports, attending webinars, and engaging with thought leaders on LinkedIn. I also keep up with global fashion and technology updates through leading publications such as *The Business of Fashion* and *TechCrunch*."
|
| 385 |
+
---
|
| 386 |
+
Your final answer should be **a well-structured response that directly answers the question while maintaining the specified style and tone**:
|
| 387 |
+
**"{agent_question}"**
|
| 388 |
+
"""
|
| 389 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
|
| 391 |
+
question_task_expected_output = f"""
|
| 392 |
+
A culturally authentic and conversational response to the question: '{agent_question}'.
|
| 393 |
+
- The response must reflect the respondent's **local cultural background and geographic influences**, ensuring it aligns with their **speech patterns, preferences, and linguistic style**.
|
| 394 |
+
- The language must follow **strict British English spelling conventions**, ensuring it is **natural, personal, and free-flowing**, while strictly avoiding American spelling, phrasing, or grammar under any circumstances, regardless of the spelling, grammar, or vocabulary used in the input question.
|
| 395 |
+
- The response **must not introduce the respondent**, nor include placeholders like "[Your Name]" or "[Brand Name]".
|
| 396 |
+
- The response **must always be written in first person (\"I\", \"my\", \"me\", etc.) as if the respondent is personally answering the question directly. Third-person narration is never allowed.**
|
| 397 |
+
- The final output should be a **single, well-structured paragraph that directly answers the question** while staying fully aligned with the specified communication style.
|
| 398 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
|
|
|
| 400 |
|
| 401 |
+
# Step 1: Generate generic answer
|
| 402 |
+
raw_response = generate_generic_answer(agent_name, agent_question, respondent_agent)
|
| 403 |
+
# Step 2: Stylise answer
|
| 404 |
+
styled_response = stylise_answer(raw_response, communication_style, agent_name, processor_llm)
|
| 405 |
+
# Step 3: Validate answer
|
| 406 |
+
formatted_response = validate_final_answer(styled_response, agent_name)
|
| 407 |
+
responses.append(formatted_response)
|
| 408 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
+
return responses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|