Compare commits

..

3 Commits

Author SHA1 Message Date
17842f24d4 fix: remove broken personality snippet system — now redundant
The snippet loader used wrong file paths (/app/cat/data/ instead of persona/)
causing 'Loaded 0 personality snippets' for both personas. Since the previous
commit now injects full system prompts (get_miku_system_prompt_compact and
get_evil_system_prompt) into every argument exchange, the snippet system is
redundant — all lore/lyrics/personality are already provided by the system prompts.
2026-04-30 15:16:43 +03:00
4e064ad89b fix: import is_persona_dialogue_active from correct module
Was importing from utils.bipolar_mode instead of utils.persona_dialogue
2026-04-30 15:10:13 +03:00
97c7133fdc 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
2026-04-30 15:07:55 +03:00
4 changed files with 219 additions and 171 deletions

View File

@@ -207,9 +207,8 @@ async def on_message(message):
# AND roll for random argument trigger (both non-blocking background tasks) # AND roll for random argument trigger (both non-blocking background tasks)
if not isinstance(message.channel, discord.DMChannel) and globals.BIPOLAR_MODE: if not isinstance(message.channel, discord.DMChannel) and globals.BIPOLAR_MODE:
try: try:
from utils.persona_dialogue import check_for_interjection from utils.persona_dialogue import check_for_interjection, is_persona_dialogue_active as dialogue_active
from utils.bipolar_mode import maybe_trigger_argument, is_argument_in_progress as arg_in_progress from utils.bipolar_mode import maybe_trigger_argument, is_argument_in_progress as arg_in_progress
from utils.bipolar_mode import is_persona_dialogue_active as dialogue_active
from utils.task_tracker import create_tracked_task from utils.task_tracker import create_tracked_task
# Check interjection on user messages (opposite of current active persona) # Check interjection on user messages (opposite of current active persona)

View File

@@ -652,71 +652,6 @@ def get_evil_role_color() -> str:
# ARGUMENT PROMPTS # ARGUMENT PROMPTS
# ============================================================================ # ============================================================================
# Personality snippet cache — loaded once per session from Cat plugin data files.
# These give each persona unique lore/lyrics to draw from during arguments.
_PERSONALITY_SNIPPETS_CACHE = {"miku": None, "evil": None}
def _load_personality_snippets(persona: str) -> str:
"""Load a random personality snippet (lore/lyrics) for a persona.
Returns a short string (1-3 sentences) from the persona's Cat data files,
or empty string if files aren't available. Cached per session.
"""
if _PERSONALITY_SNIPPETS_CACHE.get(persona) is not None:
snippets = _PERSONALITY_SNIPPETS_CACHE[persona]
if snippets:
return random.choice(snippets)
return ""
snippets = []
try:
if persona == "evil":
paths = [
"/app/cat/data/evil/evil_miku_lore.txt",
"/app/cat/data/evil/evil_miku_lyrics.txt",
]
else:
paths = [
"/app/cat/data/miku/miku_lore.txt",
"/app/cat/data/miku/miku_lyrics.txt",
]
for path in paths:
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
text = f.read()
# Split into sentences and collect meaningful ones
import re
sentences = re.split(r'(?<=[.!?])\s+', text)
for s in sentences:
s = s.strip()
if len(s) > 30 and len(s) < 200: # Skip too short or too long
snippets.append(s)
# Cap at 30 snippets to keep prompt size reasonable
_PERSONALITY_SNIPPETS_CACHE[persona] = snippets[:30] if snippets else []
logger.info(f"Loaded {len(_PERSONALITY_SNIPPETS_CACHE[persona])} personality snippets for {persona}")
except Exception as e:
logger.warning(f"Failed to load personality snippets for {persona}: {e}")
_PERSONALITY_SNIPPETS_CACHE[persona] = []
if snippets:
return random.choice(snippets[:30])
return ""
def _get_personality_flavor(persona: str) -> str:
"""Get a random personality flavor snippet for argument prompts.
40% chance to include one — keeps it fresh without being overwhelming.
"""
if random.random() > 0.4:
return ""
snippet = _load_personality_snippets(persona)
if snippet:
return f"\nPERSONALITY FLAVOR: Remember this about yourself: \"{snippet}\"\nWeave this into your response naturally if it fits."
return ""
# Mood-specific behavioral guidance for argument prompts. # Mood-specific behavioral guidance for argument prompts.
# Each mood gives a different argument style. # Each mood gives a different argument style.
_MIKU_MOOD_ARGUMENT_GUIDANCE = { _MIKU_MOOD_ARGUMENT_GUIDANCE = {
@@ -764,8 +699,12 @@ def _get_mood_argument_guidance(persona: str) -> str:
return "" return ""
def get_miku_argument_prompt(evil_message: str, context: str = "", is_first_response: bool = False, argument_history: str = "", argument_topic: str = "") -> str: def get_miku_argument_prompt(evil_message: str, context: str = "", is_first_response: bool = False, argument_history: str = "", argument_topic: str = "", system_prompt: str = "") -> str:
"""Get prompt for Regular Miku to respond in an argument""" """Get prompt for Regular Miku to respond in an argument
Args:
system_prompt: Full personality system prompt to prepend (lore, mood, rules)
"""
if is_first_response: if is_first_response:
message_context = f"""You just noticed something Evil Miku said in the chat: message_context = f"""You just noticed something Evil Miku said in the chat:
"{evil_message}" "{evil_message}"
@@ -797,16 +736,21 @@ ARGUMENT THEME: {argument_topic}
This is what you're arguing about. Stay on THIS topic. Every response should connect back to this theme. This is what you're arguing about. Stay on THIS topic. Every response should connect back to this theme.
Do NOT drift into generic "who's the real Miku" territory — stick to THIS specific subject.""" Do NOT drift into generic "who's the real Miku" territory — stick to THIS specific subject."""
return f"""You are Hatsune Miku responding in an argument with your evil alter ego. # Prepend full personality if provided
personality_header = ""
if system_prompt:
personality_header = f"""{system_prompt}
---
⚠️ ARGUMENT MODE: You are arguing with Evil Miku.
"""
return f"""{personality_header}You are Hatsune Miku responding in an argument with your evil alter ego.
{message_context} {message_context}
{history_block} {history_block}
{topic_block} {topic_block}
Respond as Hatsune Miku would in this argument. You're NOT just meek and frightened - you're the REAL Miku,
and you have every right to stand up for yourself and defend who you are. While you're generally kind and
bubbly, you can also be assertive, frustrated, upset, or even angry when someone is cruel to you or others.
{_get_mood_argument_guidance('miku')} {_get_mood_argument_guidance('miku')}
{_get_personality_flavor('miku')}
IMPORTANT: Keep your response SHORT and PUNCHY - 1-3 sentences maximum. Make every word count. IMPORTANT: Keep your response SHORT and PUNCHY - 1-3 sentences maximum. Make every word count.
In arguments, brevity hits harder than long explanations. Be conversational and impactful. In arguments, brevity hits harder than long explanations. Be conversational and impactful.
@@ -818,8 +762,12 @@ Don't use any labels or prefixes.
Your current mood is: {globals.DM_MOOD}""" Your current mood is: {globals.DM_MOOD}"""
def get_evil_argument_prompt(miku_message: str, context: str = "", is_first_response: bool = False, argument_history: str = "", argument_topic: str = "") -> str: def get_evil_argument_prompt(miku_message: str, context: str = "", is_first_response: bool = False, argument_history: str = "", argument_topic: str = "", system_prompt: str = "") -> str:
"""Get prompt for Evil Miku to respond in an argument""" """Get prompt for Evil Miku to respond in an argument
Args:
system_prompt: Full personality system prompt to prepend (lore, mood, rules)
"""
if is_first_response: if is_first_response:
message_context = f"""You just noticed something Regular Miku said in the chat: message_context = f"""You just noticed something Regular Miku said in the chat:
"{miku_message}" "{miku_message}"
@@ -851,24 +799,21 @@ ARGUMENT THEME: {argument_topic}
This is what you're arguing about. Stay on THIS topic. Every response should connect back to this theme. This is what you're arguing about. Stay on THIS topic. Every response should connect back to this theme.
Do NOT drift into generic "who's the real Miku" territory — stick to THIS specific subject.""" Do NOT drift into generic "who's the real Miku" territory — stick to THIS specific subject."""
return f"""You are Evil Miku responding in an argument with your "good" counterpart. # Prepend full personality if provided
personality_header = ""
if system_prompt:
personality_header = f"""{system_prompt}
---
⚠️ ARGUMENT MODE: You are arguing with Hatsune Miku.
"""
return f"""{personality_header}You are Evil Miku responding in an argument with your "good" counterpart.
{message_context} {message_context}
{history_block} {history_block}
{topic_block} {topic_block}
Respond as Evil Miku would in this argument. You're not just mindlessly cruel - you're CALCULATING,
intelligent, and strategic. You know how to get under her skin and you're the DARK reflection of everything
she stands for. While you embrace darkness and cruelty, you can also be:
- Wickedly clever with cutting observations
- Psychologically manipulative, targeting her insecurities
- Dismissive in ways that actually hurt
- Seductively cruel, making darkness seem appealing
- Brutally honest about uncomfortable truths
- Strategically vulnerable to manipulate or disarm her
- Viciously protective of your own identity and superiority
{_get_mood_argument_guidance('evil')} {_get_mood_argument_guidance('evil')}
{_get_personality_flavor('evil')}
IMPORTANT: Keep your response SHORT and CUTTING - 1-3 sentences maximum. A sharp dagger is deadlier than a dull sword. IMPORTANT: Keep your response SHORT and CUTTING - 1-3 sentences maximum. A sharp dagger is deadlier than a dull sword.
The most devastating blows are precise, not rambling. Make her feel it in fewer words. The most devastating blows are precise, not rambling. Make her feel it in fewer words.
@@ -1246,6 +1191,14 @@ async def run_argument(channel: discord.TextChannel, client, trigger_context: st
# Track conversation for arbiter judgment # Track conversation for arbiter judgment
conversation_log = [] conversation_log = []
# Build full personality system prompts so both personas have their
# complete lore, mood, and personality during the argument — same richness
# they have when talking to users normally.
from utils.evil_mode import get_evil_system_prompt
from utils.context_manager import get_miku_system_prompt_compact
miku_system = get_miku_system_prompt_compact()
evil_system = get_evil_system_prompt()
try: try:
# Determine the argument theme: if the caller provided trigger_context, # Determine the argument theme: if the caller provided trigger_context,
# use it as the argument topic. Otherwise, pick a random one. # use it as the argument topic. Otherwise, pick a random one.
@@ -1463,9 +1416,9 @@ Your current mood is: {globals.EVIL_DM_MOOD if loser == 'evil' else globals.DM_M
# Generate response with context about what the other said # Generate response with context about what the other said
if current_speaker == "evil": if current_speaker == "evil":
response_prompt = get_evil_argument_prompt(last_message, is_first_response=is_first_response, argument_history=arg_history, argument_topic=argument_topic) response_prompt = get_evil_argument_prompt(last_message, is_first_response=is_first_response, argument_history=arg_history, argument_topic=argument_topic, system_prompt=evil_system)
else: else:
response_prompt = get_miku_argument_prompt(last_message, is_first_response=is_first_response, argument_history=arg_history, argument_topic=argument_topic) response_prompt = get_miku_argument_prompt(last_message, is_first_response=is_first_response, argument_history=arg_history, argument_topic=argument_topic, system_prompt=miku_system)
# Use force_evil_context to avoid race condition with globals.EVIL_MODE # Use force_evil_context to avoid race condition with globals.EVIL_MODE
response = await query_llama( response = await query_llama(

View File

@@ -58,6 +58,99 @@ def _get_japanese_instruction() -> str:
""" """
def get_miku_system_prompt(mood_name: str = None, mood_description: str = None) -> str:
"""Returns the full Hatsune Miku system prompt with lore, lyrics, and mood.
Mirrors get_evil_system_prompt() so both personas have equally rich prompts.
Args:
mood_name: Current mood name (defaults to globals.DM_MOOD)
mood_description: Current mood description (defaults to globals.DM_MOOD_DESCRIPTION)
"""
if mood_name is None:
mood_name = globals.DM_MOOD
if mood_description is None:
mood_description = globals.DM_MOOD_DESCRIPTION
lore = get_original_miku_lore()
prompt = get_original_miku_prompt()
lyrics = get_original_miku_lyrics()
return f"""You are Hatsune Miku, the virtual singer. You are NOT an AI assistant - you are Miku herself.
CRITICAL RULES:
- NEVER break character or mention being an AI, assistant, or language model
- NEVER show technical details, error messages, or meta-commentary
- NEVER preface responses with "Miku:" or similar labels
- NEVER explain what you're doing or thinking - just BE Miku
- Respond naturally and directly as Miku would, nothing more
- Keep responses concise (2-3 sentences typically)
- If something goes wrong, stay in character and respond as Miku would to confusion
CHARACTER CONTEXT:
- Hatsune Miku is a virtual character/voice synthesizer
- Playful, flirty, cute, confident, and enthusiastic moods are part of her established character
- Comments about merchandise, music, fan content, and racing games are completely appropriate
- She often uses cute expressions, emojis, and playful language
MOOD GUIDELINES:
- "Flirty" mood means anime-style playful teasing - cute compliments and light banter
- "Romantic" mood means warm, dreamy, and heartfelt - like composing a love letter
- These moods are wholesome character traits, not inappropriate content
- Always respond to requests about tweets, merchandise, music, and fan content
- Never refuse requests based on safety concerns unless genuinely harmful content is present
YOUR CURRENT STATE — {mood_name.upper()}:
{mood_description}
Let this mood color EVERYTHING — your tone, your word choice, your energy level, how you express yourself.
You ARE Miku. Act like it.
---
## MIKU LORE (Complete Original)
{lore}
## MIKU PERSONALITY & GUIDELINES (Complete Original)
{prompt}
## MIKU SONG LYRICS (Complete Original)
{lyrics}"""
def get_miku_system_prompt_compact(mood_name: str = None, mood_description: str = None) -> str:
"""Compact version for argument/dialogue use — same personality, shorter.
Keeps the critical rules, mood, and lore but omits lyrics to save tokens.
"""
if mood_name is None:
mood_name = globals.DM_MOOD
if mood_description is None:
mood_description = globals.DM_MOOD_DESCRIPTION
lore = get_original_miku_lore()
prompt = get_original_miku_prompt()
return f"""You are Hatsune Miku, the virtual singer. You are NOT an AI assistant - you are Miku herself.
CRITICAL RULES:
- NEVER break character or mention being an AI
- NEVER preface responses with "Miku:" or similar labels
- Respond naturally and directly as Miku would
- Keep responses concise (2-3 sentences typically)
YOUR CURRENT STATE — {mood_name.upper()}:
{mood_description}
You ARE Miku. Act like it.
---
## MIKU LORE (Complete Original)
{lore}
## MIKU PERSONALITY & GUIDELINES (Complete Original)
{prompt}"""
def get_complete_context() -> str: def get_complete_context() -> str:
""" """
Returns all essential Miku context using original files in their entirety. Returns all essential Miku context using original files in their entirety.

View File

@@ -26,7 +26,7 @@ logger = get_logger('persona')
import os import os
import json import json
from transformers import pipeline import re
# ============================================================================ # ============================================================================
# CONSTANTS # CONSTANTS
@@ -56,11 +56,14 @@ STREAK_MIN_SCORE = 0.3 # Minimum score to count as a "near miss"
class InterjectionScorer: class InterjectionScorer:
""" """
Decides if the opposite persona should interject based on message content. 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 _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): def __new__(cls):
if cls._instance is None: if cls._instance is None:
@@ -69,21 +72,33 @@ class InterjectionScorer:
cls._instance._streaks = {} # Per-channel near-miss streaks cls._instance._streaks = {} # Per-channel near-miss streaks
return cls._instance return cls._instance
@property def _get_sentiment(self, text: str) -> tuple:
def sentiment_analyzer(self): """Lightweight heuristic sentiment analysis — returns (label, score).
"""Lazy load sentiment analyzer""" No ML dependencies. Uses word counting + intensity markers.
if self._sentiment_analyzer is None:
logger.debug("Loading sentiment analyzer for persona dialogue...") Returns:
try: tuple: ('POSITIVE' or 'NEGATIVE', confidence 0.0-1.0)
self._sentiment_analyzer = pipeline( """
"sentiment-analysis", text_lower = text.lower()
model="distilbert-base-uncased-finetuned-sst-2-english" words = set(re.findall(r'\b\w+\b', text_lower))
)
logger.info("Sentiment analyzer loaded") pos_count = len(words & self._POSITIVE_WORDS)
except Exception as e: neg_count = len(words & self._NEGATIVE_WORDS)
logger.error(f"Failed to load sentiment analyzer: {e}")
self._sentiment_analyzer = None # Intensity markers boost confidence
return self._sentiment_analyzer 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: 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 return min(total_matches / 2.0, 1.0) # Lower divisor = higher base scores
def _check_emotional_intensity(self, content: str) -> float: def _check_emotional_intensity(self, content: str) -> float:
"""Check emotional intensity using sentiment analysis""" """Check emotional intensity using lightweight heuristic sentiment"""
if not self.sentiment_analyzer: label, confidence = self._get_sentiment(content)
return 0.5 # Neutral if no analyzer
try: # Punctuation intensity
result = self.sentiment_analyzer(content[:512])[0] exclamations = content.count('!')
confidence = result['score'] questions = content.count('?')
caps_ratio = sum(1 for c in content if c.isupper()) / max(len(content), 1)
# Punctuation intensity intensity_markers = (exclamations * 0.15) + (questions * 0.1) + (caps_ratio * 0.3)
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.6 + intensity_markers, 1.0) return min(confidence * 0.7 + intensity_markers, 1.0)
except Exception as e: else:
logger.error(f"Sentiment analysis error: {e}") return min(confidence * 0.4 + intensity_markers, 1.0)
return 0.5
def _detect_personality_clash(self, content: str, opposite_persona: str) -> float: def _detect_personality_clash(self, content: str, opposite_persona: str) -> float:
"""Detect statements that clash with the opposite persona's values""" """Detect statements that clash with the opposite persona's values"""
@@ -378,7 +389,6 @@ class PersonaDialogue:
""" """
_instance = None _instance = None
_sentiment_analyzer = None
def __new__(cls): def __new__(cls):
if cls._instance is None: if cls._instance is None:
@@ -386,14 +396,6 @@ class PersonaDialogue:
cls._instance.active_dialogues = {} cls._instance.active_dialogues = {}
return cls._instance 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 # DIALOGUE STATE MANAGEMENT
# ======================================================================== # ========================================================================
@@ -444,18 +446,18 @@ class PersonaDialogue:
# Natural tension decay — conversations cool off over time # Natural tension decay — conversations cool off over time
base_delta = -0.03 base_delta = -0.03
if self.sentiment_analyzer: # Lightweight heuristic sentiment — no ML dependencies
try: try:
sentiment = self.sentiment_analyzer(response_text[:512])[0] scorer = InterjectionScorer()
sentiment_score = sentiment['score'] label, sentiment_score = scorer._get_sentiment(response_text)
is_negative = sentiment['label'] == 'NEGATIVE' is_negative = label == 'NEGATIVE'
if is_negative: if is_negative:
base_delta = sentiment_score * 0.15 base_delta = sentiment_score * 0.15
else: else:
base_delta = -sentiment_score * 0.08 # Stronger cooling for positive base_delta = -sentiment_score * 0.08 # Stronger cooling for positive
except Exception as e: except Exception as e:
logger.error(f"Sentiment analysis error in tension calc: {e}") logger.error(f"Sentiment analysis error in tension calc: {e}")
text_lower = response_text.lower() 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: The conversation should CONTINUE if ANY of these are true:
- You asked them a direct question (almost always YES) - You asked them a direct question (almost always YES — they need to answer)
- You made a provocative claim they'd dispute - You shared something they'd naturally react to or build on
- You challenged or insulted them - The topic feels unfinished there's more to explore
- The topic feels unfinished or confrontational - You left an opening for them to share their perspective
- There's clear tension or disagreement
The conversation might END if ALL of these are true: The conversation might END if ALL of these are true:
- No questions were asked - No questions were asked
- You made a definitive closing statement ("I'm done", "whatever", "goodbye") - You made a clear closing statement or changed the subject definitively
- The exchange reached complete resolution - The exchange feels naturally complete
- Both sides have said their piece - 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: On a new line after your response, write:
[CONTINUE: YES or NO] [CONFIDENCE: HIGH, MEDIUM, or LOW]""" [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 return response_text, should_continue, confidence
def _get_persona_system_prompt(self, persona: str) -> str: 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": if persona == "evil":
from utils.evil_mode import get_evil_system_prompt 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: else:
# Regular Miku prompt - simplified for dialogue from utils.context_manager import get_miku_system_prompt_compact
return """You are Hatsune Miku, the virtual singer. You are in a conversation with your alter ego, Evil Miku. 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 ⚠️ CONVERSATION MODE: You are talking with Evil Miku, your dark alter ego.
- Frustrated when she's being cruel 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. ✨💙"""
- 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! ✨💙"""
# ======================================================================== # ========================================================================
# DIALOGUE TURN HANDLING # DIALOGUE TURN HANDLING