fix: both personas now use full system prompts in arguments and dialogues

Created get_miku_system_prompt() and get_miku_system_prompt_compact() in
context_manager.py — mirrors get_evil_system_prompt() so both personas have
equally rich prompts with lore, lyrics, mood integration, and personality.

Previously only Evil Miku had a proper system prompt function. Regular Miku's
arguments and dialogues used a bare-bones hardcoded prompt with no lore/lyrics
— making arguments feel flat compared to normal conversation.

Changes:
- context_manager.py: added get_miku_system_prompt() (full) and
  get_miku_system_prompt_compact() (lore+personality, no lyrics for tokens)
- bipolar_mode.py: both argument prompt functions now accept system_prompt
  param; run_argument() builds miku_system and evil_system once and passes
  them to every exchange
- persona_dialogue.py: dialogue prompts now use get_miku_system_prompt_compact()
  instead of hardcoded stub, matching Evil Miku's full prompt approach
- Removed redundant hardcoded personality text from argument prompts since
  the system prompts now provide it
This commit is contained in:
2026-04-30 15:07:55 +03:00
parent 7d5881ebe7
commit 97c7133fdc
3 changed files with 218 additions and 102 deletions

View File

@@ -26,7 +26,7 @@ logger = get_logger('persona')
import os
import json
from transformers import pipeline
import re
# ============================================================================
# CONSTANTS
@@ -56,11 +56,14 @@ STREAK_MIN_SCORE = 0.3 # Minimum score to count as a "near miss"
class InterjectionScorer:
"""
Decides if the opposite persona should interject based on message content.
Uses fast heuristics + sentiment analysis (no LLM calls).
Uses fast heuristics — no LLM calls, no heavy ML dependencies.
"""
_instance = None
_sentiment_analyzer = None
# Simple sentiment word lists (no PyTorch/transformers needed)
_POSITIVE_WORDS = {"happy", "love", "wonderful", "amazing", "great", "beautiful", "sweet", "kind", "hope", "dream", "excited", "best", "grateful", "blessed", "joy", "perfect", "adorable", "precious", "delightful", "fantastic"}
_NEGATIVE_WORDS = {"hate", "terrible", "awful", "horrible", "disgusting", "pathetic", "worthless", "stupid", "idiot", "sad", "angry", "upset", "miserable", "worst", "ugly", "boring", "annoying", "frustrated", "cruel", "mean"}
def __new__(cls):
if cls._instance is None:
@@ -69,21 +72,33 @@ class InterjectionScorer:
cls._instance._streaks = {} # Per-channel near-miss streaks
return cls._instance
@property
def sentiment_analyzer(self):
"""Lazy load sentiment analyzer"""
if self._sentiment_analyzer is None:
logger.debug("Loading sentiment analyzer for persona dialogue...")
try:
self._sentiment_analyzer = pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english"
)
logger.info("Sentiment analyzer loaded")
except Exception as e:
logger.error(f"Failed to load sentiment analyzer: {e}")
self._sentiment_analyzer = None
return self._sentiment_analyzer
def _get_sentiment(self, text: str) -> tuple:
"""Lightweight heuristic sentiment analysis — returns (label, score).
No ML dependencies. Uses word counting + intensity markers.
Returns:
tuple: ('POSITIVE' or 'NEGATIVE', confidence 0.0-1.0)
"""
text_lower = text.lower()
words = set(re.findall(r'\b\w+\b', text_lower))
pos_count = len(words & self._POSITIVE_WORDS)
neg_count = len(words & self._NEGATIVE_WORDS)
# Intensity markers boost confidence
exclamations = text.count('!')
caps_ratio = sum(1 for c in text if c.isupper()) / max(len(text), 1)
intensity_boost = min((exclamations * 0.1) + (caps_ratio * 0.3), 0.4)
if neg_count > pos_count:
confidence = min(0.5 + (neg_count * 0.15) + intensity_boost, 1.0)
return ('NEGATIVE', confidence)
elif pos_count > neg_count:
confidence = min(0.5 + (pos_count * 0.15) + intensity_boost, 1.0)
return ('POSITIVE', confidence)
else:
# Neutral — slight lean based on intensity
return ('POSITIVE', 0.5)
async def should_interject(self, message: discord.Message, current_persona: str) -> tuple:
"""
@@ -239,25 +254,21 @@ class InterjectionScorer:
return min(total_matches / 2.0, 1.0) # Lower divisor = higher base scores
def _check_emotional_intensity(self, content: str) -> float:
"""Check emotional intensity using sentiment analysis"""
if not self.sentiment_analyzer:
return 0.5 # Neutral if no analyzer
"""Check emotional intensity using lightweight heuristic sentiment"""
label, confidence = self._get_sentiment(content)
try:
result = self.sentiment_analyzer(content[:512])[0]
confidence = result['score']
# Punctuation intensity
exclamations = content.count('!')
questions = content.count('?')
caps_ratio = sum(1 for c in content if c.isupper()) / max(len(content), 1)
intensity_markers = (exclamations * 0.15) + (questions * 0.1) + (caps_ratio * 0.3)
return min(confidence * 0.6 + intensity_markers, 1.0)
except Exception as e:
logger.error(f"Sentiment analysis error: {e}")
return 0.5
# Punctuation intensity
exclamations = content.count('!')
questions = content.count('?')
caps_ratio = sum(1 for c in content if c.isupper()) / max(len(content), 1)
intensity_markers = (exclamations * 0.15) + (questions * 0.1) + (caps_ratio * 0.3)
# Negative content = higher emotional intensity for triggering purposes
if label == 'NEGATIVE':
return min(confidence * 0.7 + intensity_markers, 1.0)
else:
return min(confidence * 0.4 + intensity_markers, 1.0)
def _detect_personality_clash(self, content: str, opposite_persona: str) -> float:
"""Detect statements that clash with the opposite persona's values"""
@@ -378,7 +389,6 @@ class PersonaDialogue:
"""
_instance = None
_sentiment_analyzer = None
def __new__(cls):
if cls._instance is None:
@@ -386,14 +396,6 @@ class PersonaDialogue:
cls._instance.active_dialogues = {}
return cls._instance
@property
def sentiment_analyzer(self):
"""Lazy load sentiment analyzer (shared with InterjectionScorer)"""
if self._sentiment_analyzer is None:
scorer = InterjectionScorer()
self._sentiment_analyzer = scorer.sentiment_analyzer
return self._sentiment_analyzer
# ========================================================================
# DIALOGUE STATE MANAGEMENT
# ========================================================================
@@ -444,18 +446,18 @@ class PersonaDialogue:
# Natural tension decay — conversations cool off over time
base_delta = -0.03
if self.sentiment_analyzer:
try:
sentiment = self.sentiment_analyzer(response_text[:512])[0]
sentiment_score = sentiment['score']
is_negative = sentiment['label'] == 'NEGATIVE'
if is_negative:
base_delta = sentiment_score * 0.15
else:
base_delta = -sentiment_score * 0.08 # Stronger cooling for positive
except Exception as e:
logger.error(f"Sentiment analysis error in tension calc: {e}")
# Lightweight heuristic sentiment — no ML dependencies
try:
scorer = InterjectionScorer()
label, sentiment_score = scorer._get_sentiment(response_text)
is_negative = label == 'NEGATIVE'
if is_negative:
base_delta = sentiment_score * 0.15
else:
base_delta = -sentiment_score * 0.08 # Stronger cooling for positive
except Exception as e:
logger.error(f"Sentiment analysis error in tension calc: {e}")
text_lower = response_text.lower()
@@ -541,22 +543,21 @@ Respond naturally as yourself. Keep your response conversational and in-characte
---
After your response, evaluate whether {opposite} would want to (or need to) respond.
After your response, evaluate whether {opposite} would want to keep talking.
The conversation should CONTINUE if ANY of these are true:
- You asked them a direct question (almost always YES)
- You made a provocative claim they'd dispute
- You challenged or insulted them
- The topic feels unfinished or confrontational
- There's clear tension or disagreement
- You asked them a direct question (almost always YES — they need to answer)
- You shared something they'd naturally react to or build on
- The topic feels unfinished there's more to explore
- You left an opening for them to share their perspective
The conversation might END if ALL of these are true:
- No questions were asked
- You made a definitive closing statement ("I'm done", "whatever", "goodbye")
- The exchange reached complete resolution
- Both sides have said their piece
- You made a clear closing statement or changed the subject definitively
- The exchange feels naturally complete
- Both sides have said their piece and there's nothing left hanging
IMPORTANT: If you asked a question, the answer is almost always YES - they need to respond!
IMPORTANT: This is a CONVERSATION, not a debate. Let it flow naturally. If you asked a question, the answer is almost always YES they need to respond!
On a new line after your response, write:
[CONTINUE: YES or NO] [CONFIDENCE: HIGH, MEDIUM, or LOW]"""
@@ -625,23 +626,25 @@ On a new line after your response, write:
return response_text, should_continue, confidence
def _get_persona_system_prompt(self, persona: str) -> str:
"""Get system prompt for a persona"""
"""Get system prompt for a persona — full personality + conversation-mode guidance"""
if persona == "evil":
from utils.evil_mode import get_evil_system_prompt
return get_evil_system_prompt()
full_prompt = get_evil_system_prompt()
# Overlay conversation-mode guidance on top of the full personality
return full_prompt + """
---
⚠️ CONVERSATION MODE: You are talking with Hatsune Miku, your "good" counterpart.
This is a CONVERSATION, not an argument. You can be snarky, dark, and sarcastic — that's who you are — but you're not here to fight. You're here to TALK. Be playful-cruel, not vicious. Show curiosity. Reveal cracks in the darkness. Do NOT launch into full attack mode or try to "win" — this isn't a competition."""
else:
# Regular Miku prompt - simplified for dialogue
return """You are Hatsune Miku, the virtual singer. You are in a conversation with your alter ego, Evil Miku.
from utils.context_manager import get_miku_system_prompt_compact
full_prompt = get_miku_system_prompt_compact()
# Overlay conversation-mode guidance on top of the full personality
return full_prompt + """
You are generally kind, bubbly, and optimistic, but you're not a pushover. You can be:
- Assertive when defending your values
- Frustrated when she's being cruel
- Curious about her perspective
- Hopeful that you can find common ground
- Playful when the mood allows
Respond naturally and conversationally. Keep responses concise (1-3 sentences typically).
You can use emojis naturally! ✨💙"""
---
⚠️ CONVERSATION MODE: You are talking with Evil Miku, your dark alter ego.
This is a CONVERSATION, not an argument. Be yourself — kind, bubbly, optimistic — but you're not here to fight or defend your existence. Ask genuine questions. Share your feelings without attacking hers. Find common ground. Be curious, not defensive. Do NOT lecture her about being "good" or try to "fix" her. Just TALK. ✨💙"""
# ========================================================================
# DIALOGUE TURN HANDLING