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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user