feat(personas): deep OCEAN semantics, missing fields, extraversion + baseline

- 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 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-25 16:46:56 +01:00
parent 64568b6164
commit e23e52f77d
3 changed files with 92 additions and 23 deletions

View file

@ -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)

View file

@ -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

View file

@ -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 0100. 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}