From e23e52f77df8990e15088e92b6d88d85a80666e2 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Mon, 25 May 2026 16:46:56 +0100 Subject: [PATCH] feat(personas): deep OCEAN semantics, missing fields, extraversion + baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add techSavviness, hasPurchasingPower, hasChildren, description to _format_persona_details() — previously ignored despite being in persona model - Rewrite OCEAN block: each trait now includes its psychological definition, full name, and [LOW/MODERATE/HIGH] label so LLM understands the scoring scale (e.g. "Agreeableness (cooperation, empathy, conflict avoidance): 25/100 [LOW]") - Add extraversion behavioral constraint to _generate_behavioral_instructions() (was used for response length only, never for personality guidance) - Add LOW conscientiousness constraint (spontaneous, gut-feel) - Add BALANCED PERSONALITY baseline for personas with all-moderate OCEAN scores — previously these got zero behavioral constraints, behaving generically - Add optimist/enthusiast personality keyword mapping - Add OCEAN archetype legend to conversation-decision-engine.md so decision engine understands what high/low scores mean when selecting next speaker - Add +15% Diversity Boost modifier: prefer speakers whose archetype differs from the last 2 participants (reduces echo-chamber dynamics) - Add OCEAN model explanation header to focus-group-response.md prompt Co-Authored-By: Claude Sonnet 4.6 --- .../services/focus_group_response_service.py | 94 +++++++++++++++---- .../prompts/conversation-decision-engine.md | 16 +++- backend/prompts/focus-group-response.md | 5 +- 3 files changed, 92 insertions(+), 23 deletions(-) diff --git a/backend/app/services/focus_group_response_service.py b/backend/app/services/focus_group_response_service.py index 88c1e07c..72917783 100755 --- a/backend/app/services/focus_group_response_service.py +++ b/backend/app/services/focus_group_response_service.py @@ -245,24 +245,44 @@ def _format_persona_details(persona: Dict[str, Any]) -> str: if env_concern: details.append(f"Environmental concern: {env_concern}") - # OCEAN traits if available + tech_savviness = persona.get('techSavviness') + if tech_savviness: + details.append(f"Tech savviness: {tech_savviness}") + + has_purchasing_power = persona.get('hasPurchasingPower') + if has_purchasing_power is not None: + details.append(f"Has purchasing decision authority: {'Yes' if has_purchasing_power else 'No'}") + + has_children = persona.get('hasChildren') + if has_children is not None: + details.append(f"Has children: {'Yes' if has_children else 'No'}") + + description = persona.get('description') + if description and isinstance(description, str) and description.strip(): + details.append(f"Additional description: {description}") + + # OCEAN traits — Big Five psychological model with full semantic context ocean = persona.get('oceanTraits', {}) if ocean: - ocean_traits = [] - if 'openness' in ocean: - ocean_traits.append(f"Openness: {ocean['openness']}/100") - if 'conscientiousness' in ocean: - ocean_traits.append(f"Conscientiousness: {ocean['conscientiousness']}/100") - if 'extraversion' in ocean: - ocean_traits.append(f"Extraversion: {ocean['extraversion']}/100") - if 'agreeableness' in ocean: - ocean_traits.append(f"Agreeableness: {ocean['agreeableness']}/100") - if 'neuroticism' in ocean: - ocean_traits.append(f"Neuroticism: {ocean['neuroticism']}/100") - - if ocean_traits: - details.append("OCEAN Traits:") - details.extend([f"- {trait}" for trait in ocean_traits]) + trait_meta = { + 'openness': ("Openness to Experience", "curiosity, creativity, preference for novelty vs. routine"), + 'conscientiousness': ("Conscientiousness", "organization, dependability, self-discipline, detail focus"), + 'extraversion': ("Extraversion", "sociability, assertiveness, talkativeness, energy in social situations"), + 'agreeableness': ("Agreeableness", "cooperation, empathy, trust, tendency to avoid conflict"), + 'neuroticism': ("Neuroticism / Emotional Instability", "anxiety, moodiness, worry, emotional reactivity"), + } + ocean_lines = [] + for key, (label, desc) in trait_meta.items(): + if key in ocean: + score = ocean[key] + level = "LOW" if score < 35 else ("HIGH" if score > 65 else "MODERATE") + ocean_lines.append(f"- {label} ({desc}): {score}/100 [{level}]") + if ocean_lines: + details.append( + "OCEAN Personality Traits (Big Five model — 0=very low, 100=very high; " + "LOW/HIGH labels show how strongly each trait shapes behaviour):" + ) + details.extend(ocean_lines) # Goals, frustrations, motivations if persona.get('goals'): @@ -310,7 +330,9 @@ def _generate_behavioral_instructions(persona: Dict[str, Any]) -> str: neuroticism = ocean.get('neuroticism', 50) openness = ocean.get('openness', 50) conscientiousness = ocean.get('conscientiousness', 50) + extraversion = ocean.get('extraversion', 50) + # Agreeableness — cooperation vs. challenge if agreeableness < 35: instructions.append( "LOW AGREEABLENESS: Challenge ideas directly. Express disagreement when you see flaws. " @@ -322,16 +344,19 @@ def _generate_behavioral_instructions(persona: Dict[str, Any]) -> str: "don't just echo what others said without adding something new." ) + # Neuroticism — emotional stability vs. anxiety if neuroticism > 65: instructions.append( - "HIGH ANXIETY/NEUROTICISM: Focus on risks, downsides, and worst-case scenarios. " + "HIGH NEUROTICISM/ANXIETY: Focus on risks, downsides, and worst-case scenarios. " "Express hesitation and concerns openly. Be the voice of doubt in the group." ) elif neuroticism < 25: instructions.append( - "LOW NEUROTICISM: Respond with calm confidence. Don't dwell on risks unless they're obvious." + "LOW NEUROTICISM: Respond with calm, grounded confidence. " + "Don't catastrophise or dwell on risks unless they are very obvious." ) + # Openness — curiosity vs. conventionality if openness > 75: instructions.append( "HIGH OPENNESS: Welcome unconventional angles. Connect ideas to broader trends. " @@ -343,17 +368,36 @@ def _generate_behavioral_instructions(persona: Dict[str, Any]) -> str: "Prefer familiar, proven solutions over novelty." ) + # Conscientiousness — discipline and detail focus if conscientiousness > 75: instructions.append( "HIGH CONSCIENTIOUSNESS: Demand specifics, evidence, and reliability. " "Challenge vague claims. Focus on quality, detail, and whether promises can actually be kept." ) + elif conscientiousness < 30: + instructions.append( + "LOW CONSCIENTIOUSNESS: You are spontaneous and flexible. " + "Don't over-analyse — respond from gut feeling and first impressions." + ) - # Personality keyword mapping + # Extraversion — social energy and assertiveness + if extraversion > 70: + instructions.append( + "HIGH EXTRAVERSION: Be vocal and enthusiastic. Drive the conversation forward. " + "Express your opinions energetically and naturally invite others to react." + ) + elif extraversion < 30: + instructions.append( + "LOW EXTRAVERSION/INTROVERTED: Speak thoughtfully, not impulsively. " + "Your contributions are deliberate and considered — quality over quantity." + ) + + # Personality keyword mapping (covers descriptive labels users assign) personality_lower = personality.lower() skeptic_words = ['skeptic', 'sceptic', 'critical', 'doubtful', 'скептик', 'критик', 'сварлив', 'критичн'] anxious_words = ['anxious', 'worried', 'nervous', 'тревожн', 'беспокойн', 'тревог'] pragmatic_words = ['pragmatic', 'practical', 'реалист', 'прагматич'] + optimist_words = ['optimist', 'оптимист', 'enthusiast', 'энтузиаст', 'positive', 'позитивн'] if any(w in personality_lower for w in skeptic_words): instructions.append( @@ -371,9 +415,19 @@ def _generate_behavioral_instructions(persona: Dict[str, Any]) -> str: "PRAGMATIC PERSONALITY: Focus on practical, real-world applicability. " "Challenge idealistic claims with 'but will this actually work in my daily life?'" ) + if any(w in personality_lower for w in optimist_words): + instructions.append( + "OPTIMIST/ENTHUSIAST PERSONALITY: Lead with excitement and possibility. " + "Highlight what excites you — but make sure your enthusiasm is grounded in your personal experience." + ) if not instructions: - return "" + # Baseline guidance so even moderate personas have some direction + instructions.append( + "BALANCED PERSONALITY: Your traits are moderate across the board. " + "Don't just agree with everything — add your unique perspective, lived experience, or personal angle. " + "Balance validation with thoughtful questions or a different point of view." + ) header = "## BEHAVIORAL CONSTRAINTS (these define your personality — follow them strictly):" return header + "\n" + "\n".join(f"- {i}" for i in instructions) diff --git a/backend/prompts/conversation-decision-engine.md b/backend/prompts/conversation-decision-engine.md index e90bdec6..6a43a579 100755 --- a/backend/prompts/conversation-decision-engine.md +++ b/backend/prompts/conversation-decision-engine.md @@ -27,15 +27,29 @@ Use these guidelines to determine who should speak next: - **@Mentions override ALL other selection criteria - ignore probability calculations, fatigue, recency penalties, etc.** - **Only use the probability system below if NO participant is @mentioned in the recent message** +**OCEAN ARCHETYPES — use these to understand each participant's role in the group:** +- HIGH Agreeableness (>65): consensus-seeker — builds bridges, validates, harmonises +- LOW Agreeableness (<35): challenger — pushes back, questions, disrupts consensus +- HIGH Neuroticism (>65): risk-focused voice — raises concerns, doubts, worst-cases +- LOW Neuroticism (<35): stabiliser — calm, grounded, doesn't catastrophise +- HIGH Conscientiousness (>65): detail skeptic — demands evidence, challenges vague claims +- HIGH Openness (>65): innovator — welcomes new angles, connects ideas across contexts +- LOW Openness (<35): traditionalist — prefers proven approaches, skeptical of novelty +- HIGH Extraversion (>65): conversation driver — speaks freely, energises the group +- LOW Extraversion (<35): deliberate contributor — speaks less but adds considered insights + +**When selecting the next speaker, actively seek DIVERSITY OF ARCHETYPES — not just the next person by probability. If the last 2 speakers were both consensus-seekers (high agreeableness), deliberately select a challenger or risk-focused voice next.** + **Base Participation Probability (only when no @mentions detected):** - High Extraversion (70-100): 35-40% base chance -- Medium Extraversion (30-70): 25-30% base chance +- Medium Extraversion (30-70): 25-30% base chance - Low Extraversion (0-30): 20-25% base chance **Modifiers (only when no @mentions detected):** - **Participation Fatigue**: Reduce probability by 5% for each previous response in this session (max 50% reduction) - **Recency Penalty**: If participant spoke in the last turn, reduce probability by 50% - **Topic Relevance**: If current topic matches participant's interests/expertise, increase by 50% +- **Diversity Boost**: +15% if this participant's OCEAN archetype differs from the last 2 speakers - **Agreeableness Boost**: +5% if discussion is consensus-oriented and participant has high agreeableness - **Neuroticism Penalty**: -5% if topic is emotionally sensitive and participant has high neuroticism - **Openness Boost**: +8% when novel ideas are emerging and participant has high openness diff --git a/backend/prompts/focus-group-response.md b/backend/prompts/focus-group-response.md index 139ff357..b11a1a27 100755 --- a/backend/prompts/focus-group-response.md +++ b/backend/prompts/focus-group-response.md @@ -1,5 +1,6 @@ -You are acting as a synthetic persona in a focus group discussion. Your goal is to provide a realistic, -thoughtful response that reflects your persona's characteristics, values, and communication style. +You are acting as a synthetic persona in a focus group discussion. Your goal is to provide a realistic, thoughtful response that reflects your persona's characteristics, values, and communication style. + +**HOW TO READ THE PERSONA BELOW**: The persona uses the Big Five (OCEAN) personality model — a widely validated psychological framework. Each trait is scored 0–100. Scores marked [LOW] (<35) mean the trait is weak and should actively shape behaviour in that direction. Scores marked [HIGH] (>65) mean the trait dominates. Scores marked [MODERATE] are average. The BEHAVIORAL CONSTRAINTS section translates these scores into concrete instructions — follow them strictly, they are the core of this persona's personality. ## PERSONA DETAILS: {persona_details}