Spaces:
Sleeping
Sleeping
File size: 10,091 Bytes
88014f9 7bcdc81 88014f9 738863b 88014f9 fb5dc8c 88014f9 853b07e 88014f9 7bcdc81 88014f9 6a621d6 b0fce7e 88014f9 6813ba1 6a621d6 6813ba1 9843498 88014f9 9843498 6a621d6 fb5dc8c 88014f9 853b07e 88014f9 c5caf48 9513566 88014f9 c5caf48 9513566 88014f9 c5caf48 9513566 88014f9 c5caf48 88014f9 6813ba1 88014f9 6813ba1 88014f9 9843498 6813ba1 88014f9 9513566 6813ba1 88014f9 9513566 6813ba1 0ad7920 9513566 6813ba1 ee30a87 88014f9 6813ba1 88014f9 738863b c5caf48 9513566 88014f9 6813ba1 738863b 9513566 88014f9 6813ba1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# context_engine.py
"""
Model-free context & emotion heuristics.
Replaces the prior transformer-based emotion classifier with a
fast, deterministic heuristic that infers:
- primary_emotion: one of ('joy','sadness','anger','fear','surprise','neutral')
- emotion_confidence: float (0.0 - 1.0) indicating heuristic strength
- conversation_mode: Ping-Pong / Standard / Deep Dive
- emoji suggestions and min-word guidance
Design notes:
- Uses emoji presence, punctuation, uppercase emphasis, keywords, negations,
question density, message length, and repetition to infer emotion.
- Intentionally conservative: returns moderate confidences unless strong signals.
- No external libraries or model downloads required.
"""
from typing import Tuple
import re
# Keyword lists (tunable)
_JOY_KEYWORDS = {"happy", "great", "awesome", "fantastic", "nice", "love", "yay", "yay!", "cool", "amazing", "thanks", "thank you", "cheers"}
_SADNESS_KEYWORDS = {"sad", "unhappy", "depressed", "upset", "down", "sadder", "melancholy", "sorrow", "lonely"}
_ANGER_KEYWORDS = {"angry", "frustrat", "frustrated", "mad", "furious", "annoyed", "irritat", "rage", "disgusted"}
_FEAR_KEYWORDS = {"scared", "afraid", "anxious", "worried", "panic", "nervous", "fear"}
_SURPRISE_KEYWORDS = {"wow", "whoa", "surpris", "unexpected", "amazed", "shocked"}
_NEGATIONS = {"not", "don't", "didn't", "can't", "couldn't", "won't", "never", "n't"}
_EMOJI_POSITIVE = {"๐","๐","๐","๐","๐","๐","๐ค","๐","โจ","๐"}
_EMOJI_NEGATIVE = {"๐ข","๐","โน๏ธ","๐ก","๐ญ","๐ ","๐ค","๐","๐ฉ","๐"}
_EMOJI_SURPRISE = {"๐ฒ","๐ฏ","๐ฎ","๐คฏ","๐ณ"}
def _count_emojis(text: str):
# simple unicode emoji detection by ranges + common emoji symbols (lightweight)
# also check presence in our small emoji sets
pos = sum(1 for e in _EMOJI_POSITIVE if e in text)
neg = sum(1 for e in _EMOJI_NEGATIVE if e in text)
sup = sum(1 for e in _EMOJI_SURPRISE if e in text)
# rough generic emoji count (fallback)
generic = len(re.findall(r'[\U0001F300-\U0001FAFF\U00002700-\U000027BF]', text))
return {"positive": pos, "negative": neg, "surprise": sup, "generic": generic}
def _word_tokens(text: str):
return re.findall(r"\w+", text.lower())
def _keyword_score(tokens, keywords):
return sum(1 for t in tokens if any(t.startswith(k) for k in keywords))
def _has_upper_emphasis(text: str):
# Count words that are ALL CAPS and length>=2
caps = [w for w in re.findall(r"\b[A-Z]{2,}\b", text)]
return len(caps)
def _question_density(tokens, text: str):
qwords = {"what","why","how","which","when","where","who","do","does","did","can","could","would","should","is","are","was","were"}
qcount = sum(1 for t in tokens if t in qwords)
total = max(1, len(tokens))
return qcount / total
def _detect_emotion(text: str) -> Tuple[str, float]:
"""
Rule-based emotion detection.
Returns (label, confidence)
"""
if not text or not text.strip():
return ("neutral", 0.0)
t = text.strip()
tokens = _word_tokens(t)
lower = t.lower()
# simple signals
emoji_counts = _count_emojis(t)
positive_emoji = emoji_counts["positive"]
negative_emoji = emoji_counts["negative"]
surprise_emoji = emoji_counts["surprise"]
generic_emoji = emoji_counts["generic"]
upper_caps = _has_upper_emphasis(t)
exclamations = t.count("!")
question_marks = t.count("?")
repeated_punct = bool(re.search(r'([!?])\1{2,}', t)) # e.g., "!!!" or "???" or "!?!!"
# keyword matches
joy_kw = _keyword_score(tokens, _JOY_KEYWORDS)
sad_kw = _keyword_score(tokens, _SADNESS_KEYWORDS)
anger_kw = _keyword_score(tokens, _ANGER_KEYWORDS)
fear_kw = _keyword_score(tokens, _FEAR_KEYWORDS)
surprise_kw = _keyword_score(tokens, _SURPRISE_KEYWORDS)
negation_present = any(n in tokens for n in _NEGATIONS)
q_density = _question_density(tokens, t)
length = len(tokens)
# scoring heuristics (base 0)
scores = {
"joy": 0.0,
"sadness": 0.0,
"anger": 0.0,
"fear": 0.0,
"surprise": 0.0,
"neutral": 0.0
}
# Emoji-weighted signals
scores["joy"] += positive_emoji * 0.35
scores["sadness"] += negative_emoji * 0.4
scores["surprise"] += surprise_emoji * 0.4
# Keyword signals (normalized)
scores["joy"] += min(joy_kw * 0.25, 1.0)
scores["sadness"] += min(sad_kw * 0.3, 1.0)
scores["anger"] += min(anger_kw * 0.35, 1.0)
scores["fear"] += min(fear_kw * 0.3, 1.0)
scores["surprise"] += min(surprise_kw * 0.3, 1.0)
# punctuation / emphasis
if exclamations >= 2 or upper_caps >= 2 or repeated_punct:
# could be joy or anger depending on words
if joy_kw or positive_emoji:
scores["joy"] += 0.4
if anger_kw or negative_emoji:
scores["anger"] += 0.45
# otherwise, boost surprise
if not (joy_kw or anger_kw):
scores["surprise"] += 0.25
# question-dense messages -> information-seeking / surprise / neutral
if q_density > 0.2 or question_marks >= 1:
scores["surprise"] += 0.2
scores["neutral"] += 0.15
# negativity via negation nearby to positive words -> reduce joy, raise neutral/anger
if negation_present and joy_kw:
scores["joy"] = max(0.0, scores["joy"] - 0.5)
scores["neutral"] += 0.2
scores["anger"] += 0.1
# sadness signals for short emotive messages like "so sad" or "feeling down"
if sad_kw and length <= 6:
scores["sadness"] += 0.3
# length-based adjust: very short messages default to small_talk/neutral unless strong signal
if length <= 3 and sum(scores.values()) < 0.5:
scores["neutral"] += 0.5
# normalize into selection
# pick top-scoring emotion
top_em = max(scores.items(), key=lambda kv: kv[1])
label = top_em[0]
raw_score = float(top_em[1])
# compute confidence: scale raw_score to 0..1 with heuristics
# higher length + multiple signals -> higher confidence
confidence = raw_score
# boost confidence for multiple corroborating signals
corroborators = 0
if positive_emoji + negative_emoji + surprise_emoji + generic_emoji > 0:
corroborators += 1
if upper_caps > 0 or exclamations > 0 or repeated_punct:
corroborators += 1
if any([joy_kw, sad_kw, anger_kw, fear_kw, surprise_kw]):
corroborators += 1
# boost based on corroborators (0..3)
confidence = min(1.0, confidence + (0.12 * corroborators))
# fallback: if very low signal, mark neutral with low confidence
if confidence < 0.15:
label = "neutral"
confidence = round(max(confidence, 0.05), 2)
else:
confidence = round(confidence, 2)
return (label, confidence)
def get_smart_context(user_text: str):
"""
Returns a short persona instruction block including:
- Conversation Mode
- Emotional context (heuristic)
- Emoji suggestions
- Minimum verbosity hint
"""
try:
text = (user_text or "").strip()
label, confidence = _detect_emotion(text)
word_count = len(_word_tokens(text))
q_density = _question_density(_word_tokens(text), text)
# Conversation Mode determination (same as before)
if word_count < 4:
conversation_mode = "Ping-Pong Mode (Fast)"
min_words_hint = 12
elif word_count < 20:
conversation_mode = "Standard Chat Mode (Balanced)"
min_words_hint = 30
else:
conversation_mode = "Deep Dive Mode (Detailed)"
min_words_hint = 70
# Map emotion label to friendly guidance & emoji suggestions
if label == "joy":
emotional_context = "User: Positive/Energetic. Vibe: Upbeat โ be warm and slightly playful."
emoji_examples = "๐ ๐ ๐"
emoji_range = (1, 2)
elif label == "sadness":
emotional_context = "User: Low Energy. Vibe: Supportive โ be gentle and empathetic."
emoji_examples = "๐ค ๐ค๏ธ"
emoji_range = (0, 1)
elif label == "anger":
emotional_context = "User: Frustrated. Vibe: De-escalate โ calm, solution-first."
emoji_examples = "๐ ๐ ๏ธ"
emoji_range = (0, 1)
elif label == "fear":
emotional_context = "User: Anxious. Vibe: Reassure and clarify."
emoji_examples = "๐ค ๐ก๏ธ"
emoji_range = (0, 1)
elif label == "surprise":
emotional_context = "User: Curious/Alert. Vibe: Engage and explain."
emoji_examples = "๐ค โจ"
emoji_range = (0, 2)
else:
emotional_context = "User: Neutral/Professional. Vibe: Helpful and efficient."
emoji_examples = "๐ก ๐"
emoji_range = (0, 2)
# Slightly adjust min_words_hint if question density is high
if q_density > 0.25:
min_words_hint = max(min_words_hint, 30)
# Build instruction block
return (
f"\n[PSYCHOLOGICAL PROFILE]\n"
f"1. Interaction Mode: {conversation_mode}\n"
f"2. {emotional_context} (detected_emotion={label}, confidence={confidence})\n"
f"3. Emoji Suggestions: Use {emoji_range[0]}โ{emoji_range[1]} emoji(s). Examples: {emoji_examples}\n"
f"4. Minimum Word Guidance: Aim for ~{min_words_hint} words unless user explicitly requests 'short' or 'brief'.\n"
f"5. Directive: Mirror user's energy; prefer natural phrasing and avoid robotic one-line replies.\n"
)
except Exception as e:
# conservative fallback
return (
"\n[PSYCHOLOGICAL PROFILE]\n"
"1. Interaction Mode: Standard Chat Mode (Balanced)\n"
"2. User: Neutral. Vibe: Helpful and efficient.\n"
"3. Emoji Suggestions: Use 0โ1 emoji. Examples: ๐\n"
"4. Minimum Word Guidance: Aim for ~30 words.\n"
)
|