Nexari-Research commited on
Commit
88014f9
·
verified ·
1 Parent(s): fbc8175

Update context_engine.py

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