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,151 @@
1
+ """
2
+ Brain: LLM - OpenRouter API Client
3
+ OpenRouter provides unified access to many LLM providers
4
+ """
5
+
6
+ import aiohttp
7
+ import asyncio
8
+ import time
9
+ from typing import Optional, List, Dict
10
+ from .base import BaseLLM
11
+
12
+
13
+ class OpenRouterClient(BaseLLM):
14
+ """OpenRouter API client - unified access to many models"""
15
+
16
+ BASE_URL = "https://openrouter.ai/api/v1"
17
+
18
+ # Site URL for rankings (required by OpenRouter)
19
+ SITE_URL = "https://alive_ai.ai"
20
+ SITE_NAME = "Alive-AI Girlfriend"
21
+
22
+ def __init__(self, api_key: str, model: str = "anthropic/claude-3.5-sonnet"):
23
+ super().__init__(api_key, model)
24
+ self.session: Optional[aiohttp.ClientSession] = None
25
+ self._available: Optional[bool] = None
26
+ self._last_check: float = 0
27
+
28
+ async def _get_session(self) -> aiohttp.ClientSession:
29
+ if self.session is None or self.session.closed:
30
+ self.session = aiohttp.ClientSession()
31
+ return self.session
32
+
33
+ async def is_available(self) -> bool:
34
+ """Check if OpenRouter API is accessible and configured"""
35
+ # Cache availability for 60 seconds
36
+ if self._available is not None and time.time() - self._last_check < 60:
37
+ return self._available
38
+
39
+ if not self.api_key:
40
+ self._available = False
41
+ self._last_check = time.time()
42
+ return False
43
+
44
+ try:
45
+ session = await self._get_session()
46
+
47
+ # Check API key validity by listing models
48
+ async with session.get(
49
+ f"{self.BASE_URL}/models",
50
+ headers={
51
+ "Authorization": f"Bearer {self.api_key}",
52
+ },
53
+ timeout=aiohttp.ClientTimeout(total=10)
54
+ ) as resp:
55
+ if resp.status == 200:
56
+ self._available = True
57
+ self._last_check = time.time()
58
+ return True
59
+ elif resp.status == 401:
60
+ print("[OpenRouter] Invalid API key")
61
+ self._available = False
62
+ self._last_check = time.time()
63
+ return False
64
+ except Exception as e:
65
+ print(f"[OpenRouter] Availability check failed: {e}")
66
+
67
+ self._available = False
68
+ self._last_check = time.time()
69
+ return False
70
+
71
+ async def chat(
72
+ self,
73
+ messages: List[Dict[str, str]],
74
+ max_tokens: int = 500,
75
+ temperature: float = None
76
+ ) -> Optional[str]:
77
+ """Send chat completion request via OpenRouter"""
78
+ import os
79
+ # Use passed temperature, or environment variable, or default high value
80
+ if temperature is None:
81
+ temperature = float(os.environ.get("LLM_TEMPERATURE", "0.95"))
82
+
83
+ session = await self._get_session()
84
+
85
+ headers = {
86
+ "Authorization": f"Bearer {self.api_key}",
87
+ "Content-Type": "application/json",
88
+ "HTTP-Referer": self.SITE_URL,
89
+ "X-Title": self.SITE_NAME
90
+ }
91
+
92
+ payload = {
93
+ "model": self.model,
94
+ "messages": messages,
95
+ "max_tokens": max_tokens,
96
+ "temperature": temperature,
97
+ "frequency_penalty": 0.8, # Penalize repeated phrases - increased from 0.5
98
+ "presence_penalty": 0.6, # Encourage topic diversity - increased from 0.3
99
+ }
100
+
101
+ try:
102
+ async with session.post(
103
+ f"{self.BASE_URL}/chat/completions",
104
+ headers=headers,
105
+ json=payload,
106
+ timeout=aiohttp.ClientTimeout(total=60)
107
+ ) as resp:
108
+ if resp.status != 200:
109
+ error = await resp.text()
110
+ print(f"[OpenRouter] Error {resp.status}: {error[:300]}")
111
+ self._available = False
112
+ return None
113
+
114
+ data = await resp.json()
115
+ # Check for error response
116
+ if "error" in data:
117
+ print(f"[OpenRouter] API Error: {data['error']}")
118
+ self._available = False
119
+ return None
120
+ # Log token usage
121
+ if "usage" in data:
122
+ usage = data["usage"]
123
+ print(f"[LLM] Tokens - Input: {usage.get('prompt_tokens', '?')} | Output: {usage.get('completion_tokens', '?')} | Total: {usage.get('total_tokens', '?')}")
124
+ if "choices" not in data or not data["choices"]:
125
+ print(f"[OpenRouter] No choices in response: {list(data.keys())}")
126
+ return None
127
+
128
+ content = data["choices"][0]["message"]["content"]
129
+
130
+ if not content or not content.strip():
131
+ print(f"[OpenRouter] Empty content!")
132
+ return None
133
+
134
+ print(f"[OpenRouter] Response: {content[:100]}...")
135
+
136
+ # Mark as available since we got a response
137
+ self._available = True
138
+ self._last_check = time.time()
139
+ return content
140
+
141
+ except asyncio.TimeoutError:
142
+ print(f"[OpenRouter] Timeout (60s)")
143
+ return None
144
+ except Exception as e:
145
+ print(f"[OpenRouter] Exception: {e}")
146
+ return None
147
+
148
+ async def close(self):
149
+ """Close the client session"""
150
+ if self.session and not self.session.closed:
151
+ await self.session.close()
@@ -0,0 +1,205 @@
1
+ """
2
+ Brain: LLM - Provider Factory
3
+ Creates the appropriate LLM client based on configuration.
4
+ Supports both legacy single-provider mode and new unified fallback mode.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional, Tuple
9
+ from .base import BaseLLM
10
+ from .zai import ZAIClient
11
+ from .openrouter import OpenRouterClient
12
+ from .ollama import OllamaClient
13
+ from .unified import UnifiedLLM, get_unified_llm
14
+ from .fallback_router import FallbackRouter, get_fallback_router
15
+
16
+
17
+ def get_provider_config(task: str = "main") -> Tuple[str, str, str]:
18
+ """Get provider and model for a specific task
19
+
20
+ Tasks:
21
+ - main: Main conversation model
22
+ - thinking: Deep reasoning, complex decisions
23
+ - fast: Quick responses, impulses, subconscious
24
+
25
+ Returns:
26
+ Tuple of (provider_name, api_key, model_name)
27
+ """
28
+ # Try to get from settings.json first
29
+ try:
30
+ from core.settings import get as settings_get
31
+
32
+ # Check if fallback mode is enabled
33
+ llm_fallback = settings_get("LLM_FALLBACK", {})
34
+ if llm_fallback.get("ENABLED", False):
35
+ # Use unified mode - get config from settings
36
+ provider = "unified"
37
+ api_key = "" # Not used in unified mode
38
+ model = "" # Not used in unified mode
39
+ return provider, api_key, model
40
+
41
+ # Legacy mode - use settings.json
42
+ provider = settings_get("LLM_PROVIDER", "zai").lower()
43
+
44
+ if provider == "openrouter":
45
+ api_key = settings_get("OPENROUTER_API_KEY", "") or os.environ.get("OPENROUTER_API_KEY", "")
46
+ if task == "fast":
47
+ model = settings_get("OPENROUTER_MODEL_FAST", "openai/gpt-4o-mini")
48
+ elif task == "thinking":
49
+ model = settings_get("OPENROUTER_MODEL_THINKING", "anthropic/claude-3.5-sonnet")
50
+ else:
51
+ model = settings_get("OPENROUTER_MODEL_MAIN", "anthropic/claude-3.5-sonnet")
52
+ else:
53
+ # Default to ZAI
54
+ api_key = settings_get("ZAI_API_KEY", "") or os.environ.get("ZAI_API_KEY", "")
55
+ if task == "fast":
56
+ model = settings_get("ZAI_MODEL_FAST", "glm-4-flash")
57
+ elif task == "thinking":
58
+ model = settings_get("ZAI_MODEL_THINKING", "glm-4.6v")
59
+ else:
60
+ model = settings_get("ZAI_MODEL_MAIN", "glm-4.6v")
61
+
62
+ return provider, api_key, model
63
+
64
+ except ImportError:
65
+ # Fallback to environment variables
66
+ pass
67
+
68
+ # Environment variable fallback
69
+ provider = os.environ.get("LLM_PROVIDER", "zai").lower()
70
+
71
+ if provider == "openrouter":
72
+ api_key = os.environ.get("OPENROUTER_API_KEY", "")
73
+ if task == "fast":
74
+ model = os.environ.get("OPENROUTER_MODEL_FAST", "openai/gpt-4o-mini")
75
+ elif task == "thinking":
76
+ model = os.environ.get("OPENROUTER_MODEL_THINKING", "anthropic/claude-3.5-sonnet")
77
+ else:
78
+ model = os.environ.get("OPENROUTER_MODEL_MAIN", "anthropic/claude-3.5-sonnet")
79
+ else:
80
+ # Default to ZAI
81
+ api_key = os.environ.get("ZAI_API_KEY", "")
82
+ if task == "fast":
83
+ model = os.environ.get("ZAI_MODEL_FAST", "glm-4-flash")
84
+ elif task == "thinking":
85
+ model = os.environ.get("ZAI_MODEL_THINKING", "glm-4.6v")
86
+ else:
87
+ model = os.environ.get("ZAI_MODEL_MAIN", "glm-4.6v")
88
+
89
+ return provider, api_key, model
90
+
91
+
92
+ def get_llm_client(task: str = "main") -> Optional[BaseLLM]:
93
+ """Get LLM client for a specific task
94
+
95
+ Usage:
96
+ # Main conversation
97
+ llm = get_llm_client("main")
98
+
99
+ # Quick impulse generation
100
+ fast_llm = get_llm_client("fast")
101
+
102
+ # Deep thinking
103
+ think_llm = get_llm_client("thinking")
104
+ """
105
+ provider, api_key, model = get_provider_config(task)
106
+
107
+ # Handle unified mode
108
+ if provider == "unified":
109
+ return get_unified_llm_client()
110
+
111
+ if not api_key:
112
+ print(f"[LLM] No API key for provider: {provider}")
113
+ return None
114
+
115
+ if provider == "openrouter":
116
+ return OpenRouterClient(api_key, model)
117
+ else:
118
+ return ZAIClient(api_key, model)
119
+
120
+
121
+ def get_fast_llm() -> Optional[BaseLLM]:
122
+ """Get fast LLM for quick tasks (impulses, subconscious)"""
123
+ return get_llm_client("fast")
124
+
125
+
126
+ def get_thinking_llm() -> Optional[BaseLLM]:
127
+ """Get thinking LLM for complex reasoning"""
128
+ return get_llm_client("thinking")
129
+
130
+
131
+ def get_main_llm() -> Optional[BaseLLM]:
132
+ """Get main LLM for conversations"""
133
+ return get_llm_client("main")
134
+
135
+
136
+ # ============================================================
137
+ # Unified LLM Support
138
+ # ============================================================
139
+
140
+ # Cache for the unified client
141
+ _unified_client: Optional[UnifiedLLM] = None
142
+
143
+
144
+ def get_unified_llm_client() -> Optional[UnifiedLLM]:
145
+ """
146
+ Get the unified LLM client with fallback chain.
147
+
148
+ This is the recommended way to get an LLM client as it:
149
+ - Tries ZAI first
150
+ - Falls back to OpenRouter if ZAI fails
151
+ - Falls back to local Ollama if both fail
152
+ - Handles empty responses and errors gracefully
153
+
154
+ Returns:
155
+ UnifiedLLM instance or None if not configured
156
+ """
157
+ global _unified_client
158
+
159
+ if _unified_client is not None:
160
+ return _unified_client
161
+
162
+ try:
163
+ from core.settings import get as settings_get
164
+
165
+ # Get fallback configuration
166
+ llm_config = settings_get("LLM_FALLBACK", {})
167
+ if not llm_config.get("ENABLED", False):
168
+ print("[LLM] Fallback not enabled, using single provider")
169
+ return None
170
+
171
+ _unified_client = get_unified_llm(settings_getter=settings_get)
172
+ print(f"[LLM] Unified LLM initialized with fallback chain")
173
+ return _unified_client
174
+
175
+ except ImportError:
176
+ print("[LLM] Settings not available for unified LLM")
177
+ return None
178
+ except Exception as e:
179
+ print(f"[LLM] Error creating unified LLM: {e}")
180
+ return None
181
+
182
+
183
+ def get_fallback_llm_client() -> Optional[FallbackRouter]:
184
+ """
185
+ Get the fallback router for LLM requests.
186
+
187
+ This provides a simpler interface than UnifiedLLM with:
188
+ - Direct provider list
189
+ - Simple logging
190
+ - No state management
191
+
192
+ Returns:
193
+ FallbackRouter instance or None if not configured
194
+ """
195
+ try:
196
+ return get_fallback_router()
197
+ except Exception as e:
198
+ print(f"[LLM] Error creating fallback router: {e}")
199
+ return None
200
+
201
+
202
+ def reset_unified_client():
203
+ """Reset the unified client cache (for testing or reconfiguration)"""
204
+ global _unified_client
205
+ _unified_client = None