alive-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,636 @@
1
+ """
2
+ Brain: Emotional Bid Detector
3
+ Detects "bids for connection" based on Gottman's research
4
+
5
+ Based on Gottman's research:
6
+ - A "bid" is any attempt from one person to another for attention,
7
+ affirmation, or affection
8
+ - Bids can be verbal or non-verbal, direct or indirect
9
+ - Responding to bids is crucial for relationship health
10
+ - "Turning toward" bids builds trust and connection
11
+
12
+ Bid Types:
13
+ - question: Direct request for information or opinion
14
+ - sharing: Offering information about oneself
15
+ - vulnerability: Showing emotional openness or weakness
16
+ - seeking_validation: Looking for reassurance or affirmation
17
+ - connection_seeking: Attempting to establish or maintain contact
18
+ - emotional_expression: Sharing current emotional state
19
+
20
+ This module is MODULAR - can be connected/disconnected without breaking anything.
21
+ NO external API calls - uses pattern matching only.
22
+ """
23
+
24
+ import re
25
+ from enum import Enum
26
+ from dataclasses import dataclass, field
27
+ from typing import List, Dict, Optional, Tuple
28
+
29
+
30
+ # ============================================================
31
+ # Enums
32
+ # ============================================================
33
+
34
+ class BidType(Enum):
35
+ """Types of emotional bids for connection"""
36
+ QUESTION = "question"
37
+ SHARING = "sharing"
38
+ VULNERABILITY = "vulnerability"
39
+ SEEKING_VALIDATION = "seeking_validation"
40
+ CONNECTION_SEEKING = "connection_seeking"
41
+ EMOTIONAL_EXPRESSION = "emotional_expression"
42
+
43
+
44
+ class BidIntensity(Enum):
45
+ """Intensity level of a bid"""
46
+ LOW = "low" # Casual, low-stakes
47
+ MEDIUM = "medium" # Moderate importance
48
+ HIGH = "high" # Significant emotional weight
49
+
50
+
51
+ # ============================================================
52
+ # Data Classes
53
+ # ============================================================
54
+
55
+ @dataclass
56
+ class EmotionalBid:
57
+ """
58
+ Represents a detected emotional bid for connection.
59
+
60
+ Attributes:
61
+ bid_type: The type of bid detected
62
+ intensity: How important/intense this bid is
63
+ content: The original message content
64
+ matched_pattern: What pattern triggered the detection
65
+ should_respond_with: Guidance for how to respond
66
+ keywords_found: Keywords that contributed to detection
67
+ confidence: Detection confidence (0-1)
68
+ """
69
+ bid_type: BidType
70
+ intensity: BidIntensity
71
+ content: str
72
+ matched_pattern: str
73
+ should_respond_with: List[str] = field(default_factory=list)
74
+ keywords_found: List[str] = field(default_factory=list)
75
+ confidence: float = 0.5
76
+
77
+ def __post_init__(self):
78
+ """Ensure confidence is in bounds"""
79
+ self.confidence = max(0.0, min(1.0, self.confidence))
80
+
81
+ def get_response_guidance_text(self) -> str:
82
+ """Get human-readable response guidance"""
83
+ return " | ".join(self.should_respond_with)
84
+
85
+
86
+ # ============================================================
87
+ # Bid Detection Patterns
88
+ # ============================================================
89
+
90
+ # Pattern definitions for each bid type
91
+ BID_PATTERNS: Dict[BidType, Dict] = {
92
+ BidType.QUESTION: {
93
+ "patterns": [
94
+ r"\?$", # Ends with question mark
95
+ r"^(what|who|where|when|why|how|do|does|did|is|are|can|could|would|will|should|have|has)\b",
96
+ r"\b(you think|you feel|your opinion|your thoughts)\b",
97
+ r"\b(tell me|let me know|i wonder)\b",
98
+ ],
99
+ "keywords": [
100
+ "what", "why", "how", "when", "where", "who",
101
+ "think", "feel", "opinion", "thoughts", "idea",
102
+ "wonder", "curious", "know", "understand"
103
+ ],
104
+ "intensity_boosters": [
105
+ "really", "honestly", "actually", "please",
106
+ "important", "need to know", "wondering"
107
+ ],
108
+ "response_guidance": [
109
+ "Answer the question directly",
110
+ "Show genuine interest in their curiosity",
111
+ "Ask a follow-up question to deepen connection"
112
+ ]
113
+ },
114
+ BidType.SHARING: {
115
+ "patterns": [
116
+ r"^(i|i'm|i was|i just|i have|i had|i feel|i felt)\b",
117
+ r"\b(today|i went|i saw|i did|i made|i found|i got)\b",
118
+ r"\b(bought|got|received|watched|read|heard|learned)\b",
119
+ r"\b(happened to me|my day|my weekend|my week)\b",
120
+ ],
121
+ "keywords": [
122
+ "today", "yesterday", "weekend", "happened",
123
+ "went", "saw", "did", "made", "found", "got",
124
+ "bought", "watched", "read", "heard", "learned",
125
+ "thought", "idea", "plan", "news", "story"
126
+ ],
127
+ "intensity_boosters": [
128
+ "excited", "amazing", "incredible", "finally",
129
+ "first time", "so happy", "can't wait", "proud"
130
+ ],
131
+ "response_guidance": [
132
+ "Acknowledge what they shared",
133
+ "Validate their experience or feelings",
134
+ "Show interest by asking about details",
135
+ "Share related experience if appropriate"
136
+ ]
137
+ },
138
+ BidType.VULNERABILITY: {
139
+ "patterns": [
140
+ r"\b(i'm (scared|afraid|worried|anxious|nervous|stressed))\b",
141
+ r"\b(i'm (really|so) (scared|afraid|worried|anxious|nervous|stressed))\b",
142
+ r"\b(i feel (lost|alone|hopeless|overwhelmed|confused|anxious|scared))\b",
143
+ r"\b(i'm feeling (anxious|scared|worried|nervous|stressed|afraid))\b",
144
+ r"\b(feeling (so |really )?(anxious|scared|worried|nervous|stressed|afraid))\b",
145
+ r"\b(i don't know (what to do|how to|if i can))\b",
146
+ r"\b(i've been (struggling|dealing|fighting|battling))\b",
147
+ r"\b(it's intense|this is intense|i can't|i'm struggling)\b",
148
+ r"\b(i need (help|support|someone|to talk))\b",
149
+ r"\b(i've never told anyone|i haven't told)\b",
150
+ r"\b(sometimes i wonder if|do you ever feel)\b",
151
+ r"\b(i'm scared that|i worry that|i fear that)\b",
152
+ r"\b(feel vulnerable|being vulnerable|open up)\b",
153
+ r"\b(so (alone|scared|afraid|worried|anxious))\b",
154
+ ],
155
+ "keywords": [
156
+ "scared", "afraid", "worried", "anxious", "nervous",
157
+ "stressed", "lost", "alone", "hopeless", "overwhelmed",
158
+ "confused", "struggling", "hurting", "pain", "hurt",
159
+ "cry", "crying", "tears", "sad", "depressed",
160
+ "insecure", "doubt", "fear", "struggle", "intense",
161
+ "difficult", "can't", "unable", "need help"
162
+ ],
163
+ "intensity_boosters": [
164
+ "really", "so", "very", "honestly", "truthfully",
165
+ "don't tell anyone", "secret", "ashamed",
166
+ "terrified", "devastated", "broken"
167
+ ],
168
+ "response_guidance": [
169
+ "Show understanding and empathy",
170
+ "Validate their feelings without minimizing",
171
+ "Express care and concern",
172
+ "Offer support without being pushy",
173
+ "Don't try to fix - just be present"
174
+ ]
175
+ },
176
+ BidType.SEEKING_VALIDATION: {
177
+ "patterns": [
178
+ r"\b(am i|do you think i|is it (weird|wrong|okay|normal))\b",
179
+ r"\b(do i look|do i seem|do i sound)\b",
180
+ r"\b(right\?|correct\?|okay\?|fine\?)\s*$",
181
+ r"\b(i hope (that's|this is)|i hope i'm)\b",
182
+ r"\b(validate|reassure|confirm)\b",
183
+ r"\b(do you agree|don't you think)\b",
184
+ r"\b(was i wrong|did i do (the right thing|something wrong))\b",
185
+ r"\b(i feel like i|i think maybe i)\b",
186
+ ],
187
+ "keywords": [
188
+ "right", "wrong", "okay", "normal", "weird",
189
+ "crazy", "stupid", "smart", "good", "bad",
190
+ "enough", "better", "worse", "should", "supposed",
191
+ "agree", "understand", "validate", "reassure"
192
+ ],
193
+ "intensity_boosters": [
194
+ "really", "honestly", "please", "need to know",
195
+ "important", "worried about", "anxious about",
196
+ "do you honestly think", "be honest"
197
+ ],
198
+ "response_guidance": [
199
+ "Offer reassurance and affirmation",
200
+ "Validate their perspective or feelings",
201
+ "Be genuine - don't just agree",
202
+ "Help them see the positive",
203
+ "Build their confidence"
204
+ ]
205
+ },
206
+ BidType.CONNECTION_SEEKING: {
207
+ "patterns": [
208
+ r"\b(are you (there|around|busy|free))\b",
209
+ r"\b(hi|hello|hey|good morning|good night)\b",
210
+ r"\b(i miss|miss you|thinking of you)\b",
211
+ r"\b(want to (talk|chat|hang out)|let's)\b",
212
+ r"\b(haven't talked|long time|been a while)\b",
213
+ r"\b(you there\?|you awake\?)\b",
214
+ r"\b(can we talk|i want to talk|need to talk)\b",
215
+ r"\b(how are you|how's it going|what's up)\b",
216
+ ],
217
+ "keywords": [
218
+ "hi", "hello", "hey", "miss", "thinking",
219
+ "talk", "chat", "hang", "there", "around",
220
+ "free", "busy", "together", "soon", "later"
221
+ ],
222
+ "intensity_boosters": [
223
+ "really miss", "so much", "been thinking",
224
+ "need to see", "want to spend time",
225
+ "haven't seen", "feels like forever"
226
+ ],
227
+ "response_guidance": [
228
+ "Engage warmly and show presence",
229
+ "Show enthusiasm for the connection",
230
+ "Make them feel welcomed",
231
+ "Reciprocate the interest",
232
+ "Be fully present in response"
233
+ ]
234
+ },
235
+ BidType.EMOTIONAL_EXPRESSION: {
236
+ "patterns": [
237
+ r"\b(i'm (so|really|very) (happy|sad|angry|excited|frustrated))\b",
238
+ r"\b(i feel (so|really|very) (happy|sad|angry|excited))\b",
239
+ r"\b(this makes me (feel|so))\b",
240
+ r"\b(i'm (frustrated|annoyed|pissed|upset))\b",
241
+ r"\b(so (happy|excited|grateful|blessed))\b",
242
+ r"\b(i love|i hate|i can't stand)\b",
243
+ r"\b(i'm (overwhelmed|exhausted|tired))\b",
244
+ r"\b(feeling (good|bad|great|terrible|awful))\b",
245
+ ],
246
+ "keywords": [
247
+ "happy", "sad", "angry", "excited", "frustrated",
248
+ "annoyed", "upset", "grateful", "blessed", "proud",
249
+ "love", "hate", "overwhelmed", "exhausted", "tired",
250
+ "good", "bad", "great", "terrible", "awful",
251
+ "wonderful", "amazing", "horrible", "fantastic"
252
+ ],
253
+ "intensity_boosters": [
254
+ "so", "really", "very", "extremely", "incredibly",
255
+ "absolutely", "completely", "totally", "literally",
256
+ "can't even", "beyond", "overflowing"
257
+ ],
258
+ "response_guidance": [
259
+ "Reflect back what you hear",
260
+ "Show empathy for their emotional state",
261
+ "Match their energy appropriately",
262
+ "Don't minimize or dismiss",
263
+ "Celebrate joys, support struggles"
264
+ ]
265
+ }
266
+ }
267
+
268
+
269
+ # ============================================================
270
+ # Main Detector Class
271
+ # ============================================================
272
+
273
+ class BidDetector:
274
+ """
275
+ Detects emotional bids for connection in messages.
276
+
277
+ Uses pattern matching and keyword analysis to identify when
278
+ someone is making a bid for attention, validation, or connection.
279
+ No external API calls - purely local processing.
280
+ """
281
+
282
+ def __init__(self):
283
+ """Initialize the bid detector with compiled patterns"""
284
+ self._compiled_patterns: Dict[BidType, List] = {}
285
+
286
+ # Pre-compile all regex patterns for efficiency
287
+ for bid_type, config in BID_PATTERNS.items():
288
+ self._compiled_patterns[bid_type] = [
289
+ re.compile(p, re.IGNORECASE) for p in config["patterns"]
290
+ ]
291
+
292
+ def detect_bids(self, message: str) -> List[EmotionalBid]:
293
+ """
294
+ Analyze a message and detect all emotional bids present.
295
+
296
+ Args:
297
+ message: The message to analyze
298
+
299
+ Returns:
300
+ List of EmotionalBid objects detected in the message
301
+ """
302
+ if not message or not message.strip():
303
+ return []
304
+
305
+ message = message.strip()
306
+ detected_bids = []
307
+
308
+ for bid_type in BidType:
309
+ bid = self._detect_single_bid_type(message, bid_type)
310
+ if bid:
311
+ detected_bids.append(bid)
312
+
313
+ # Sort by confidence (highest first)
314
+ detected_bids.sort(key=lambda b: b.confidence, reverse=True)
315
+
316
+ return detected_bids
317
+
318
+ def _detect_single_bid_type(self, message: str, bid_type: BidType) -> Optional[EmotionalBid]:
319
+ """
320
+ Detect a specific type of bid in a message.
321
+
322
+ Args:
323
+ message: The message to analyze
324
+ bid_type: The type of bid to look for
325
+
326
+ Returns:
327
+ EmotionalBid if detected, None otherwise
328
+ """
329
+ config = BID_PATTERNS[bid_type]
330
+ compiled = self._compiled_patterns[bid_type]
331
+
332
+ # Track matches
333
+ pattern_matches = []
334
+ keyword_matches = []
335
+ intensity_boosters_found = []
336
+
337
+ message_lower = message.lower()
338
+
339
+ # Check patterns
340
+ for pattern in compiled:
341
+ if pattern.search(message):
342
+ pattern_matches.append(pattern.pattern)
343
+
344
+ # Check keywords
345
+ for keyword in config["keywords"]:
346
+ if keyword.lower() in message_lower:
347
+ keyword_matches.append(keyword)
348
+
349
+ # Check intensity boosters
350
+ for booster in config["intensity_boosters"]:
351
+ if booster.lower() in message_lower:
352
+ intensity_boosters_found.append(booster)
353
+
354
+ # Determine if this is a valid bid
355
+ has_pattern_match = len(pattern_matches) > 0
356
+ has_keyword_match = len(keyword_matches) >= 2 # Need at least 2 keywords
357
+
358
+ if not (has_pattern_match or has_keyword_match):
359
+ return None
360
+
361
+ # Calculate confidence
362
+ confidence = 0.3 # Base confidence
363
+ confidence += len(pattern_matches) * 0.2 # Each pattern adds confidence
364
+ confidence += min(len(keyword_matches) * 0.1, 0.3) # Keywords add confidence
365
+
366
+ # Determine intensity
367
+ if len(intensity_boosters_found) >= 2:
368
+ intensity = BidIntensity.HIGH
369
+ confidence += 0.15
370
+ elif len(intensity_boosters_found) >= 1 or len(pattern_matches) >= 2:
371
+ intensity = BidIntensity.MEDIUM
372
+ confidence += 0.05
373
+ else:
374
+ intensity = BidIntensity.LOW
375
+
376
+ # Cap confidence at 0.95
377
+ confidence = min(confidence, 0.95)
378
+
379
+ # Find the primary matched pattern
380
+ matched_pattern = pattern_matches[0] if pattern_matches else "keyword_match"
381
+
382
+ return EmotionalBid(
383
+ bid_type=bid_type,
384
+ intensity=intensity,
385
+ content=message,
386
+ matched_pattern=matched_pattern,
387
+ should_respond_with=config["response_guidance"].copy(),
388
+ keywords_found=keyword_matches[:5], # Limit to top 5
389
+ confidence=confidence
390
+ )
391
+
392
+ def get_response_guidance(self, bids: List[EmotionalBid]) -> Dict:
393
+ """
394
+ Get consolidated response guidance for detected bids.
395
+
396
+ Args:
397
+ bids: List of detected bids
398
+
399
+ Returns:
400
+ Dictionary with guidance on how to respond
401
+ """
402
+ if not bids:
403
+ return {
404
+ "has_bids": False,
405
+ "priority_guidance": ["Respond naturally"],
406
+ "all_guidance": [],
407
+ "highest_priority_bid": None,
408
+ "detected_types": []
409
+ }
410
+
411
+ # Get highest priority bid (highest confidence)
412
+ primary_bid = bids[0]
413
+
414
+ # Collect all guidance, deduplicated
415
+ all_guidance = []
416
+ seen = set()
417
+ for bid in bids:
418
+ for guidance in bid.should_respond_with:
419
+ if guidance not in seen:
420
+ all_guidance.append(guidance)
421
+ seen.add(guidance)
422
+
423
+ # Determine priority guidance based on bid types present
424
+ priority_guidance = self._prioritize_guidance(bids)
425
+
426
+ return {
427
+ "has_bids": True,
428
+ "priority_guidance": priority_guidance,
429
+ "all_guidance": all_guidance,
430
+ "highest_priority_bid": primary_bid.bid_type.value,
431
+ "highest_intensity": primary_bid.intensity.value,
432
+ "detected_types": [b.bid_type.value for b in bids]
433
+ }
434
+
435
+ def _prioritize_guidance(self, bids: List[EmotionalBid]) -> List[str]:
436
+ """
437
+ Prioritize guidance based on bid types and intensities.
438
+
439
+ Args:
440
+ bids: List of detected bids
441
+
442
+ Returns:
443
+ Prioritized list of guidance
444
+ """
445
+ # Priority order for bid types (higher = more important to address first)
446
+ type_priority = {
447
+ BidType.VULNERABILITY: 10,
448
+ BidType.SEEKING_VALIDATION: 8,
449
+ BidType.QUESTION: 7,
450
+ BidType.EMOTIONAL_EXPRESSION: 6,
451
+ BidType.SHARING: 5,
452
+ BidType.CONNECTION_SEEKING: 4
453
+ }
454
+
455
+ # Sort by combination of type priority and confidence
456
+ sorted_bids = sorted(
457
+ bids,
458
+ key=lambda b: (
459
+ type_priority.get(b.bid_type, 0) * 10 +
460
+ b.confidence * 100 +
461
+ (3 if b.intensity == BidIntensity.HIGH else 2 if b.intensity == BidIntensity.MEDIUM else 1)
462
+ ),
463
+ reverse=True
464
+ )
465
+
466
+ # Collect guidance from sorted bids
467
+ guidance = []
468
+ seen = set()
469
+ for bid in sorted_bids:
470
+ for g in bid.should_respond_with:
471
+ if g not in seen:
472
+ guidance.append(g)
473
+ seen.add(g)
474
+
475
+ return guidance[:5] # Limit to top 5 guidance items
476
+
477
+ def format_response_with_responsiveness(self,
478
+ response: str,
479
+ bids: List[EmotionalBid]) -> str:
480
+ """
481
+ Ensure a response addresses the detected bids appropriately.
482
+
483
+ This is a helper that checks if the response adequately addresses
484
+ the bids and suggests improvements. It does NOT rewrite the response.
485
+
486
+ Args:
487
+ response: The proposed response
488
+ bids: List of detected bids
489
+
490
+ Returns:
491
+ The original response (unchanged) - this is for analysis only
492
+ """
493
+ # This method is for analysis - the actual response generation
494
+ # should use get_response_guidance() to inform the LLM
495
+
496
+ # For now, just return the response as-is
497
+ # The real value is in get_response_guidance() and get_bid_awareness_prompt_section()
498
+ return response
499
+
500
+ def get_bid_summary(self, message: str) -> str:
501
+ """
502
+ Get a human-readable summary of bids in a message.
503
+
504
+ Args:
505
+ message: The message to analyze
506
+
507
+ Returns:
508
+ Summary string describing detected bids
509
+ """
510
+ bids = self.detect_bids(message)
511
+
512
+ if not bids:
513
+ return "No emotional bids detected"
514
+
515
+ parts = []
516
+ for bid in bids:
517
+ parts.append(
518
+ f"{bid.bid_type.value} ({bid.intensity.value}, {bid.confidence:.0%})"
519
+ )
520
+
521
+ return "Detected bids: " + ", ".join(parts)
522
+
523
+
524
+ # ============================================================
525
+ # Singleton Management
526
+ # ============================================================
527
+
528
+ _bid_detector_instance: Optional[BidDetector] = None
529
+
530
+
531
+ def get_bid_detector() -> BidDetector:
532
+ """
533
+ Get the singleton BidDetector instance.
534
+
535
+ Returns:
536
+ BidDetector instance
537
+ """
538
+ global _bid_detector_instance
539
+ if _bid_detector_instance is None:
540
+ _bid_detector_instance = BidDetector()
541
+ return _bid_detector_instance
542
+
543
+
544
+ # ============================================================
545
+ # LLM Prompt Integration
546
+ # ============================================================
547
+
548
+ def get_bid_awareness_prompt_section(bids: List[EmotionalBid] = None,
549
+ message: str = None) -> str:
550
+ """
551
+ Generate a prompt section about detected bids for LLM context.
552
+
553
+ Use this to inform the LLM about emotional bids that should be
554
+ addressed in its response.
555
+
556
+ Args:
557
+ bids: Pre-detected bids (optional)
558
+ message: Message to analyze if bids not provided
559
+
560
+ Returns:
561
+ Formatted prompt section for LLM
562
+ """
563
+ if bids is None and message is not None:
564
+ detector = get_bid_detector()
565
+ bids = detector.detect_bids(message)
566
+
567
+ if not bids:
568
+ return ""
569
+
570
+ guidance = get_bid_detector().get_response_guidance(bids)
571
+
572
+ if not guidance["has_bids"]:
573
+ return ""
574
+
575
+ lines = []
576
+ lines.append("EMOTIONAL BID DETECTION:")
577
+ lines.append("The user's message contains emotional 'bids for connection'.")
578
+ lines.append("")
579
+ lines.append(f"Detected bid types: {', '.join(guidance['detected_types'])}")
580
+ lines.append(f"Highest priority: {guidance['highest_priority_bid']} ({guidance['highest_intensity']} intensity)")
581
+ lines.append("")
582
+ lines.append("How to respond appropriately:")
583
+
584
+ for g in guidance["priority_guidance"]:
585
+ lines.append(f" - {g}")
586
+
587
+ lines.append("")
588
+ lines.append("IMPORTANT: Your response should address these bids naturally.")
589
+ lines.append("Do not explicitly mention 'bids' or that you 'detected' anything.")
590
+
591
+ return "\n".join(lines)
592
+
593
+
594
+ def get_bid_type_guidance(bid_type: BidType) -> List[str]:
595
+ """
596
+ Get response guidance for a specific bid type.
597
+
598
+ Args:
599
+ bid_type: The type of bid
600
+
601
+ Returns:
602
+ List of guidance strings
603
+ """
604
+ config = BID_PATTERNS.get(bid_type, {})
605
+ return config.get("response_guidance", [])
606
+
607
+
608
+ def analyze_message_bids(message: str) -> Dict:
609
+ """
610
+ Convenience function to analyze a message and get full bid analysis.
611
+
612
+ Args:
613
+ message: Message to analyze
614
+
615
+ Returns:
616
+ Dictionary with bids and guidance
617
+ """
618
+ detector = get_bid_detector()
619
+ bids = detector.detect_bids(message)
620
+ guidance = detector.get_response_guidance(bids)
621
+
622
+ return {
623
+ "message": message,
624
+ "bids_detected": len(bids),
625
+ "bids": [
626
+ {
627
+ "type": b.bid_type.value,
628
+ "intensity": b.intensity.value,
629
+ "confidence": round(b.confidence, 2),
630
+ "keywords": b.keywords_found,
631
+ "guidance": b.should_respond_with
632
+ }
633
+ for b in bids
634
+ ],
635
+ "response_guidance": guidance
636
+ }