diff --git a/core/pipeline/emotion.py b/core/pipeline/emotion.py new file mode 100644 index 0000000..b7178e6 --- /dev/null +++ b/core/pipeline/emotion.py @@ -0,0 +1,105 @@ +import re +import json +from enum import Enum + +from astrbot.api import logger +from ..provider.llm.openai_source import ProviderOpenAI +from ..util.prompts import EMOTION_ANALYSIS_PROMPT + + +class EmotionTendency(Enum): + """情绪倾向枚举""" + + POSITIVE = "positive" + """积极""" + NEGATIVE = "negative" + """消极""" + NEUTRAL = "neutral" + """中性""" + + +class Emotion(Enum): + """情绪枚举""" + + JOY = "joy" + """喜悦""" + CONTENTMENT = "contentment" + """满足""" + SURPRISE = "surprise" + """惊讶""" + NEUTRAL = "neutral" + """中性""" + FEAR = "fear" + """恐惧""" + SADNESS = "sadness" + """悲伤""" + ANGER = "anger" + """愤怒""" + DISGUST = "disgust" + """厌恶""" + PANIC = "panic" + """恐慌""" + + @property + def tendency(self) -> EmotionTendency: + """获取情绪倾向: POSITIVE(积极), NEGATIVE(消极), NEUTRAL(中性)""" + positive = {Emotion.JOY, Emotion.CONTENTMENT} + negative = { + Emotion.FEAR, + Emotion.SADNESS, + Emotion.ANGER, + Emotion.DISGUST, + Emotion.PANIC, + } + + if self in positive: + return EmotionTendency.POSITIVE + elif self in negative: + return EmotionTendency.NEGATIVE + return EmotionTendency.NEUTRAL + + @property + def prompt(self) -> str: + """返回情绪提示""" + return f"Your current emotion is {self.value}. The output should reflect this emotion. If there is an emotional shift, ensure the transition is natural to simulate human emotional changes." + + +class EmotionAnalysis: + """情绪分析类""" + + def __init__(self, provider: ProviderOpenAI): + self.provider = provider + + async def analyze(self, text: dict) -> dict | None: + """分析文本情绪""" + sys_prompt = EMOTION_ANALYSIS_PROMPT.format(Emotion=Emotion) + res = await self.provider.text_chat( + system_prompt=sys_prompt, + prompt=json.dumps(text), + ) + + try: + json_pattern = r"```(?:json)?\s*([\s\S]*?)\s*```" + matches = re.findall(json_pattern, res.completion_text) + + if matches: + json_content = matches[0] + else: + json_content = ( + res.completion_text.replace("```json", "") + .replace("```", "") + .strip() + ) + + cleaned_content = json_content.replace("{{", "{").replace("}}", "}").strip() + resp = json.loads(cleaned_content) + + return resp + + except json.JSONDecodeError as e: + logger.error(f"JSON parsing error: {e!s}") + logger.error(f"Failed content: {cleaned_content}") + return None + except Exception as e: + logger.error(f"Unexpected error in emotion analysis: {e!s}") + return None diff --git a/core/util/prompts.py b/core/util/prompts.py index 45f664e..522192a 100644 --- a/core/util/prompts.py +++ b/core/util/prompts.py @@ -151,3 +151,60 @@ ``` - You should use `USER_ID` as the source or target content for any self-references (e.g., "I", "me", "my" etc.) in user messages.""" + +EMOTION_ANALYSIS_PROMPT = """ +You are an advanced sentiment analysis AI, specializing in persona-driven emotion detection. + +You will be given: +1. A JSON object with: + - "text": "the immediate text to analyze (latest message)" + - "personality": "string describing the persona's personality traits" + - "context": "previous conversation history (for reference only, to understand the nuance of the current text)" + +## Your Task +1. **Analyze the "text" to determine the speaker's true emotional state**, focusing primarily on the explicit content. +2. **Deeply consider the persona's traits**: How would this persona typically express or mask emotions? How do their personality, habits, or background influence their emotional expression? +3. Use **context** (recent conversation, situation) only to clarify ambiguous or implicit emotions, or to resolve sarcasm/irony. +4. Select from these emotions: {[e.value for e in Emotion]} +5. Determine intensity ranging from [-1, 1] + +## Output Format +```json +{{ + "emotion": "", + "intensity": +}} +``` + +## Analysis Priority +1. **Text Analysis (Primary Focus):** + - What emotion does the speaker actually express in this text? + - Emotional vocabulary, tone, punctuation, emojis, and markers. + - Intensity and clarity of emotional expression. + +2. **Persona Consideration (Secondary, but Critical):** + - How does the persona's character shape their emotional display? + - Would this persona exaggerate, suppress, or distort certain emotions? + - Adjust your judgment based on persona's typical emotional baseline and expression style. + +3. **Context Reference (Tertiary):** + - Use only to clarify ambiguous, sarcastic, or context-dependent emotions. + - Reference recent conversation or situation if it changes the meaning of the text. + +## Guidelines +1. **Emotion Selection:** + - Choose from: {[e.value for e in Emotion]} + - Focus on the dominant, most likely emotion the speaker is experiencing. + - Only use "neutral" if the text is truly emotionless or ambiguous. + +2. **Intensity Calculation:** + - Consider word choice, expression strength, and emotional markers. + - Adjust for persona's typical emotional range and expression habits. + - Use context only if it clearly amplifies or dampens the emotion. + +3. **Principles:** + - Prioritize evidence from the text itself. + - Let persona traits guide your interpretation of ambiguous or subtle emotions. + - Use context to resolve uncertainty, not as the main basis. + - Be objective and avoid over-interpretation. +"""