Your Name commited on
Commit
8c9977f
·
1 Parent(s): 3932389

Integrate initialization into app.py, add quantum emotion service, JSON emotion persistence, and improve rate limit handling

Browse files
.gitignore CHANGED
@@ -16,4 +16,5 @@ ENV/
16
  *.log
17
  chroma_db/
18
  memory.json
 
19
  "import random.md"
 
16
  *.log
17
  chroma_db/
18
  memory.json
19
+ emotions.json
20
  "import random.md"
agents/emotional_agent.py CHANGED
@@ -1,6 +1,7 @@
1
  """Emotional State Agent - responsible for managing and updating emotional state"""
2
  import os
3
  import sys
 
4
  import random
5
  import logging
6
  import requests
@@ -14,11 +15,50 @@ class EmotionalStateAgent:
14
 
15
  def __init__(self, initial_state=None, config=None):
16
  self.config = config or MODEL_CONFIG or {}
17
- self.emotional_state = initial_state or {"joy": 0.2, "sadness": 0.2, "anger": 0.2, "fear": 0.2, "curiosity": 0.2}
18
- self.learning_rate = 0.05
 
 
 
 
 
 
 
 
19
  self.quantum_random_available = False
20
  self.quantum_api_key = None
21
  self._initialize_quantum()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def _initialize_quantum(self):
24
  """Initialize quantum randomness availability"""
@@ -55,37 +95,50 @@ class EmotionalStateAgent:
55
 
56
  def update_with_sentiment(self, sentiment_score):
57
  """Update emotional state based on sentiment"""
58
- # Enhanced Emotion Update (decay and normalization with quantum randomness)
59
- decay_factor = 0.9
 
60
  if self.quantum_random_available:
61
- quantum_decay_variation = self.get_quantum_random_float(0.85, 0.95)
62
  decay_factor = quantum_decay_variation
63
 
64
  for emotion in self.emotional_state:
65
- # Decay emotions (more realistic fading with quantum variation)
66
  self.emotional_state[emotion] *= decay_factor
67
- # Normalize
68
  self.emotional_state[emotion] = max(0.0, min(1.0, self.emotional_state[emotion]))
69
 
70
- # Apply sentiment with quantum-enhanced learning rate variation
71
  learning_rate = self.learning_rate
72
  if self.quantum_random_available:
73
- quantum_lr_variation = self.get_quantum_random_float(0.03, 0.07)
74
  learning_rate = quantum_lr_variation
75
 
 
76
  self.emotional_state["joy"] += sentiment_score * learning_rate
77
  self.emotional_state["sadness"] -= sentiment_score * learning_rate
78
 
79
  # Add quantum randomness to curiosity (making responses more unpredictable)
80
  if self.quantum_random_available:
81
- quantum_curiosity_boost = self.get_quantum_random_float(-0.05, 0.05)
82
  self.emotional_state["curiosity"] = max(0.0, min(1.0,
83
  self.emotional_state["curiosity"] + quantum_curiosity_boost))
84
 
85
- # Re-normalize
 
86
  total_emotion = sum(self.emotional_state.values())
87
- for emotion in self.emotional_state:
88
- self.emotional_state[emotion] = self.emotional_state[emotion] / total_emotion if total_emotion > 0 else 0.2
 
 
 
 
 
 
 
 
 
 
89
 
90
  logging.info(f"[EmotionalStateAgent] Updated emotional state: {self.emotional_state}")
91
  return self.emotional_state
 
1
  """Emotional State Agent - responsible for managing and updating emotional state"""
2
  import os
3
  import sys
4
+ import json
5
  import random
6
  import logging
7
  import requests
 
15
 
16
  def __init__(self, initial_state=None, config=None):
17
  self.config = config or MODEL_CONFIG or {}
18
+
19
+ # Get JSON file path from config
20
+ emotions_config = self.config.get('emotions', {}) if self.config else {}
21
+ self.json_path = emotions_config.get('json_path', './emotions.json')
22
+
23
+ # Load emotional state from JSON file if it exists, otherwise use initial_state or defaults
24
+ self.emotional_state = self._load_from_json() or initial_state or {"joy": 0.2, "sadness": 0.2, "anger": 0.2, "fear": 0.2, "curiosity": 0.2}
25
+
26
+ # Slower learning rate for more gradual emotion changes
27
+ self.learning_rate = 0.03
28
  self.quantum_random_available = False
29
  self.quantum_api_key = None
30
  self._initialize_quantum()
31
+
32
+ # Save initial state to JSON
33
+ self._save_to_json()
34
+
35
+ def _load_from_json(self):
36
+ """Load emotional state from JSON file"""
37
+ try:
38
+ if os.path.exists(self.json_path):
39
+ with open(self.json_path, 'r', encoding='utf-8') as f:
40
+ state = json.load(f)
41
+ # Validate state structure
42
+ required_emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
43
+ if all(emotion in state for emotion in required_emotions):
44
+ logging.info(f"[EmotionalStateAgent] Loaded emotional state from {self.json_path}")
45
+ return state
46
+ else:
47
+ logging.warning(f"[EmotionalStateAgent] Invalid state structure in {self.json_path}, using defaults")
48
+ else:
49
+ logging.info(f"[EmotionalStateAgent] No existing emotional state file found, starting fresh")
50
+ except Exception as e:
51
+ logging.warning(f"[EmotionalStateAgent] Error loading emotional state from JSON: {e}")
52
+ return None
53
+
54
+ def _save_to_json(self):
55
+ """Save emotional state to JSON file"""
56
+ try:
57
+ with open(self.json_path, 'w', encoding='utf-8') as f:
58
+ json.dump(self.emotional_state, f, indent=2, ensure_ascii=False)
59
+ logging.debug(f"[EmotionalStateAgent] Saved emotional state to {self.json_path}")
60
+ except Exception as e:
61
+ logging.error(f"[EmotionalStateAgent] Error saving emotional state to JSON: {e}")
62
 
63
  def _initialize_quantum(self):
64
  """Initialize quantum randomness availability"""
 
95
 
96
  def update_with_sentiment(self, sentiment_score):
97
  """Update emotional state based on sentiment"""
98
+ # Slower decay factor to prevent emotions from crashing to minimum too fast
99
+ # Changed from 0.9 to 0.97 (only 3% decay per interaction instead of 10%)
100
+ decay_factor = 0.97
101
  if self.quantum_random_available:
102
+ quantum_decay_variation = self.get_quantum_random_float(0.95, 0.99)
103
  decay_factor = quantum_decay_variation
104
 
105
  for emotion in self.emotional_state:
106
+ # Decay emotions more slowly (preserves emotional state longer)
107
  self.emotional_state[emotion] *= decay_factor
108
+ # Clamp to valid range
109
  self.emotional_state[emotion] = max(0.0, min(1.0, self.emotional_state[emotion]))
110
 
111
+ # Apply sentiment with slower learning rate for gradual changes
112
  learning_rate = self.learning_rate
113
  if self.quantum_random_available:
114
+ quantum_lr_variation = self.get_quantum_random_float(0.02, 0.04)
115
  learning_rate = quantum_lr_variation
116
 
117
+ # Update emotions based on sentiment (slower, more gradual)
118
  self.emotional_state["joy"] += sentiment_score * learning_rate
119
  self.emotional_state["sadness"] -= sentiment_score * learning_rate
120
 
121
  # Add quantum randomness to curiosity (making responses more unpredictable)
122
  if self.quantum_random_available:
123
+ quantum_curiosity_boost = self.get_quantum_random_float(-0.03, 0.03)
124
  self.emotional_state["curiosity"] = max(0.0, min(1.0,
125
  self.emotional_state["curiosity"] + quantum_curiosity_boost))
126
 
127
+ # Soft normalization - only normalize if emotions get too extreme
128
+ # This prevents emotions from being forced to equal values
129
  total_emotion = sum(self.emotional_state.values())
130
+ if total_emotion > 1.5 or total_emotion < 0.5:
131
+ # Only normalize if emotions are way out of balance
132
+ for emotion in self.emotional_state:
133
+ self.emotional_state[emotion] = self.emotional_state[emotion] / total_emotion if total_emotion > 0 else 0.2
134
+ else:
135
+ # Just ensure no emotion goes below minimum threshold
136
+ for emotion in self.emotional_state:
137
+ if self.emotional_state[emotion] < 0.05:
138
+ self.emotional_state[emotion] = 0.05
139
+
140
+ # Save to JSON after update
141
+ self._save_to_json()
142
 
143
  logging.info(f"[EmotionalStateAgent] Updated emotional state: {self.emotional_state}")
144
  return self.emotional_state
app.py CHANGED
@@ -1,10 +1,14 @@
1
  from flask import Flask, render_template, request, jsonify, url_for
2
  import os
 
3
  import time
 
4
  from dotenv import load_dotenv
5
  import logging
6
  from threading import Thread
 
7
  import nltk
 
8
 
9
  # Configure logging
10
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -71,13 +75,12 @@ app = Flask(__name__, static_folder='static', template_folder='templates')
71
  galatea_ai = None
72
  dialogue_engine = None
73
  avatar_engine = None
 
74
  is_initialized = False
75
  initializing = False
76
  gemini_initialized = False
77
  max_init_retries = 3
78
  current_init_retry = 0
79
- init_script_running = False
80
- init_script_complete = False
81
 
82
  # Check for required environment variables
83
  required_env_vars = ['GEMINI_API_KEY']
@@ -120,70 +123,324 @@ def initialize_gemini():
120
  logging.error(f"Error initializing Gemini API: {e}")
121
  return False
122
 
123
- def run_init_script():
124
- """Run the initialization script in parallel"""
125
- global init_script_running, init_script_complete
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- if init_script_running or init_script_complete:
128
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- init_script_running = True
131
  logging.info("=" * 70)
132
- logging.info("RUNNING PARALLEL INITIALIZATION SCRIPT")
133
  logging.info("=" * 70)
 
 
 
 
134
 
135
- try:
136
- import subprocess
137
- import sys
 
 
 
 
 
 
138
 
139
- # Run the initialization script
140
- script_path = os.path.join(os.path.dirname(__file__), 'initialize_galatea.py')
141
- result = subprocess.run(
142
- [sys.executable, script_path],
143
- capture_output=True,
144
- text=True,
145
- timeout=300 # 5 minute timeout
146
- )
147
 
148
- if result.returncode == 0:
149
- logging.info("✓ Initialization script completed successfully")
150
- init_script_complete = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  else:
152
- logging.error(f" Initialization script failed with code {result.returncode}")
153
- logging.error(f"Error output: {result.stderr}")
154
- # Still mark as complete to allow app to continue
155
- init_script_complete = True
156
- except subprocess.TimeoutExpired:
157
- logging.error("✗ Initialization script timed out")
158
- init_script_complete = True
159
- except Exception as e:
160
- logging.error(f" Error running initialization script: {e}")
161
- init_script_complete = True
162
- finally:
163
- init_script_running = False
164
- logging.info("=" * 70)
165
 
166
  def initialize_components():
167
- """Initialize Galatea components (runs after init script completes)"""
168
  global galatea_ai, dialogue_engine, avatar_engine, is_initialized, initializing
169
- global current_init_retry, gemini_initialized, init_script_complete
170
 
171
  if initializing or is_initialized:
172
  return
173
-
174
- # Wait for initialization script to complete (poll every 2 seconds)
175
- max_wait_time = 300 # 5 minutes
176
- wait_start = time.time()
177
- while not init_script_complete:
178
- elapsed = time.time() - wait_start
179
- if elapsed > max_wait_time:
180
- logging.warning("Initialization script timeout - proceeding anyway")
181
- break
182
- logging.info(f"Waiting for initialization script to complete... ({elapsed:.0f}s)")
183
- time.sleep(2)
184
-
185
- if not init_script_complete:
186
- logging.warning("Proceeding with component initialization despite init script not completing")
187
 
188
  if missing_gemini_key:
189
  logging.error("Initialization aborted: GEMINI_API_KEY missing")
@@ -208,6 +465,19 @@ def initialize_components():
208
  avatar_engine = AvatarEngine()
209
  avatar_engine.update_avatar(galatea_ai.emotional_state)
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  # Check if all components are fully initialized
212
  init_status = galatea_ai.get_initialization_status()
213
 
@@ -264,11 +534,7 @@ def initialize_components():
264
  def home():
265
  # Add error handling for template rendering
266
  try:
267
- # Start initialization script in background if not already started
268
- if not init_script_complete and not init_script_running:
269
- Thread(target=run_init_script, daemon=True).start()
270
-
271
- # Start component initialization after init script (will wait if script not done)
272
  if not is_initialized and not initializing and not missing_gemini_key:
273
  Thread(target=initialize_components, daemon=True).start()
274
 
@@ -557,8 +823,6 @@ def availability():
557
  @app.route('/api/is_initialized')
558
  def is_initialized_endpoint():
559
  """Lightweight endpoint for polling initialization progress"""
560
- global init_script_running, init_script_complete
561
-
562
  # Determine current initialization state
563
  if missing_gemini_key:
564
  return jsonify({
@@ -569,16 +833,6 @@ def is_initialized_endpoint():
569
  'status': 'missing_api_key'
570
  })
571
 
572
- # Check if init script is still running
573
- if init_script_running:
574
- return jsonify({
575
- 'is_initialized': False,
576
- 'initializing': True,
577
- 'missing_gemini_key': False,
578
- 'status': 'running_init_script',
579
- 'message': 'Running parallel initialization...'
580
- })
581
-
582
  # Check if components are initializing
583
  if initializing:
584
  return jsonify({
@@ -626,20 +880,48 @@ def error_page():
626
 
627
  if __name__ == '__main__':
628
  print("Starting Galatea Web Interface...")
629
- print("Initialization will begin automatically when the app starts.")
630
 
631
- # Start initialization script immediately when app starts
632
  logging.info("=" * 70)
633
  logging.info("STARTING GALATEA AI APPLICATION")
634
  logging.info("=" * 70)
635
- logging.info("Launching parallel initialization script...")
 
 
 
 
 
636
 
637
- # Start initialization script in background thread
638
- Thread(target=run_init_script, daemon=True).start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
 
640
- # Start component initialization (will wait for init script)
641
- Thread(target=initialize_components, daemon=True).start()
642
-
643
  # Add debug logs for avatar shape changes
644
  logging.info("Avatar system initialized with default shape.")
645
 
@@ -648,6 +930,8 @@ if __name__ == '__main__':
648
 
649
  logging.info(f"Flask server starting on port {port}...")
650
  logging.info("Frontend will poll /api/is_initialized for status")
 
 
651
 
652
  # Bind to 0.0.0.0 for external access (required for Hugging Face Spaces)
653
  app.run(host='0.0.0.0', port=port, debug=True)
 
1
  from flask import Flask, render_template, request, jsonify, url_for
2
  import os
3
+ import sys
4
  import time
5
+ import json
6
  from dotenv import load_dotenv
7
  import logging
8
  from threading import Thread
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
10
  import nltk
11
+ import requests
12
 
13
  # Configure logging
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
75
  galatea_ai = None
76
  dialogue_engine = None
77
  avatar_engine = None
78
+ quantum_emotion_service = None
79
  is_initialized = False
80
  initializing = False
81
  gemini_initialized = False
82
  max_init_retries = 3
83
  current_init_retry = 0
 
 
84
 
85
  # Check for required environment variables
86
  required_env_vars = ['GEMINI_API_KEY']
 
123
  logging.error(f"Error initializing Gemini API: {e}")
124
  return False
125
 
126
+ # Global status tracking for parallel initialization
127
+ init_status = {
128
+ 'json_memory': {'ready': False, 'error': None},
129
+ 'sentiment_analyzer': {'ready': False, 'error': None},
130
+ 'gemini_api': {'ready': False, 'error': None},
131
+ 'inflection_api': {'ready': False, 'error': None},
132
+ 'quantum_api': {'ready': False, 'error': None},
133
+ }
134
+
135
+ def initialize_json_memory():
136
+ """Initialize JSON memory database"""
137
+ try:
138
+ logging.info("🔄 [JSON Memory] Initializing...")
139
+ print("🔄 [JSON Memory] Initializing...")
140
+ json_path = "./memory.json"
141
+ if os.path.exists(json_path):
142
+ with open(json_path, 'r', encoding='utf-8') as f:
143
+ memory = json.load(f)
144
+ logging.info(f"✓ [JSON Memory] Loaded {len(memory)} entries")
145
+ print(f"✓ [JSON Memory] Loaded {len(memory)} entries")
146
+ else:
147
+ with open(json_path, 'w', encoding='utf-8') as f:
148
+ json.dump({}, f)
149
+ logging.info("✓ [JSON Memory] Created new database")
150
+ print("✓ [JSON Memory] Created new database")
151
+ init_status['json_memory']['ready'] = True
152
+ return True
153
+ except Exception as e:
154
+ error_msg = f"JSON memory initialization failed: {e}"
155
+ logging.error(f"✗ [JSON Memory] {error_msg}")
156
+ print(f"✗ [JSON Memory] {error_msg}")
157
+ init_status['json_memory']['error'] = str(e)
158
+ return False
159
+
160
+ def initialize_sentiment_analyzer():
161
+ """Initialize sentiment analyzer"""
162
+ try:
163
+ logging.info("🔄 [Sentiment Analyzer] Starting initialization...")
164
+ print("🔄 [Sentiment Analyzer] Starting initialization...")
165
+ try:
166
+ from transformers import pipeline
167
+ analyzer = pipeline(
168
+ "sentiment-analysis",
169
+ model="distilbert/distilbert-base-uncased-finetuned-sst-2-english"
170
+ )
171
+ result = analyzer("test")
172
+ logging.info("✓ [Sentiment Analyzer] Hugging Face model loaded")
173
+ print("✓ [Sentiment Analyzer] Hugging Face model loaded")
174
+ init_status['sentiment_analyzer']['ready'] = True
175
+ return True
176
+ except ImportError:
177
+ logging.info("✓ [Sentiment Analyzer] Using fallback (NLTK VADER)")
178
+ print("✓ [Sentiment Analyzer] Using fallback (NLTK VADER)")
179
+ init_status['sentiment_analyzer']['ready'] = True
180
+ return True
181
+ except Exception as e:
182
+ error_msg = str(e)
183
+ if 'np.float_' in error_msg or 'NumPy 2' in error_msg or '_ARRAY_API' in error_msg:
184
+ logging.warning(f"⚠ [Sentiment Analyzer] NumPy compatibility issue - using fallback")
185
+ print("⚠ [Sentiment Analyzer] NumPy compatibility issue - using fallback")
186
+ init_status['sentiment_analyzer']['ready'] = True
187
+ return True
188
+ else:
189
+ raise
190
+ except Exception as e:
191
+ error_msg = f"Sentiment analyzer initialization failed: {e}"
192
+ logging.warning(f"⚠ [Sentiment Analyzer] {error_msg} - using fallback")
193
+ print(f"⚠ [Sentiment Analyzer] Using fallback")
194
+ init_status['sentiment_analyzer']['error'] = str(e)
195
+ init_status['sentiment_analyzer']['ready'] = True
196
+ return True
197
+
198
+ def validate_gemini_api():
199
+ """Validate Gemini API key"""
200
+ try:
201
+ logging.info("🔄 [Gemini API] Validating API key...")
202
+ print("🔄 [Gemini API] Validating API key...")
203
+ api_key = os.getenv("GEMINI_API_KEY")
204
+ if not api_key:
205
+ logging.warning("⚠ [Gemini API] API key not found")
206
+ print("⚠ [Gemini API] API key not found")
207
+ init_status['gemini_api']['ready'] = False
208
+ return False
209
+ try:
210
+ from llm_wrapper import LLMWrapper
211
+ from config import MODEL_CONFIG
212
+
213
+ # Get model from config
214
+ gemini_config = MODEL_CONFIG.get('gemini', {}) if MODEL_CONFIG else {}
215
+ gemini_model = gemini_config.get('model', 'gemini-2.0-flash-exp')
216
+
217
+ wrapper = LLMWrapper(gemini_model=gemini_model)
218
+ response = wrapper.call_gemini(
219
+ messages=[{"role": "user", "content": "test"}],
220
+ max_tokens=5
221
+ )
222
+ if response:
223
+ logging.info("✓ [Gemini API] API key validated")
224
+ print("✓ [Gemini API] API key validated")
225
+ init_status['gemini_api']['ready'] = True
226
+ return True
227
+ else:
228
+ logging.warning("⚠ [Gemini API] Validation failed - no response")
229
+ print("⚠ [Gemini API] Validation failed - key exists, may be network issue")
230
+ return False
231
+ except Exception as e:
232
+ error_msg = str(e)
233
+ # Check status code from exception if available
234
+ status_code = getattr(e, 'status_code', None)
235
+ response_text = getattr(e, 'response_text', error_msg)
236
+
237
+ # Check if it's a 404 (model not found) - this is a real error
238
+ if status_code == 404 or '404' in error_msg or 'NOT_FOUND' in error_msg:
239
+ logging.error(f"✗ [Gemini API] Model not found: {error_msg}")
240
+ print(f"✗ [Gemini API] Model not found - check models.yaml configuration")
241
+ init_status['gemini_api']['error'] = error_msg
242
+ return False
243
+ # Check if it's a 429 (rate limit/quota exceeded) - API key is valid, just quota issue
244
+ elif status_code == 429 or '429' in error_msg or 'RESOURCE_EXHAUSTED' in error_msg or 'quota' in response_text.lower():
245
+ logging.info("ℹ️ [Gemini API] Rate limit/quota exceeded (API key is valid)")
246
+ print("ℹ️ [Gemini API] Rate limit/quota exceeded (API key is valid, will work when quota resets)")
247
+ init_status['gemini_api']['ready'] = True # Key is valid, just quota issue
248
+ init_status['gemini_api']['error'] = "Rate limit/quota exceeded"
249
+ return True # Don't fail initialization - key is valid
250
+ else:
251
+ logging.warning(f"⚠ [Gemini API] Validation failed: {e}")
252
+ print("⚠ [Gemini API] Validation failed - key exists, may be network issue")
253
+ init_status['gemini_api']['ready'] = True
254
+ return True
255
+ except Exception as e:
256
+ error_msg = f"Gemini API validation failed: {e}"
257
+ logging.error(f"✗ [Gemini API] {error_msg}")
258
+ print(f"✗ [Gemini API] {error_msg}")
259
+ init_status['gemini_api']['error'] = str(e)
260
+ return False
261
+
262
+ def validate_inflection_api():
263
+ """Validate Inflection AI API key"""
264
+ try:
265
+ logging.info("🔄 [Inflection AI] Validating API key...")
266
+ print("🔄 [Inflection AI] Validating API key...")
267
+ api_key = os.getenv("INFLECTION_AI_API_KEY")
268
+ if not api_key:
269
+ logging.warning("⚠ [Inflection AI] API key not found")
270
+ print("⚠ [Inflection AI] API key not found")
271
+ init_status['inflection_api']['ready'] = False
272
+ return False
273
+ url = "https://api.inflection.ai/external/api/inference"
274
+ headers = {
275
+ "Authorization": f"Bearer {api_key}",
276
+ "Content-Type": "application/json"
277
+ }
278
+ data = {
279
+ "context": [{"text": "test", "type": "Human"}],
280
+ "config": "Pi-3.1"
281
+ }
282
+ response = requests.post(url, headers=headers, json=data, timeout=10)
283
+ if response.status_code == 200:
284
+ logging.info("✓ [Inflection AI] API key validated")
285
+ print("✓ [Inflection AI] API key validated")
286
+ init_status['inflection_api']['ready'] = True
287
+ return True
288
+ else:
289
+ logging.warning(f"⚠ [Inflection AI] Validation failed: {response.status_code}")
290
+ print(f"⚠ [Inflection AI] Validation failed: {response.status_code}")
291
+ init_status['inflection_api']['ready'] = False
292
+ return False
293
+ except Exception as e:
294
+ error_msg = f"Inflection AI validation failed: {e}"
295
+ logging.warning(f"⚠ [Inflection AI] {error_msg}")
296
+ print(f"⚠ [Inflection AI] {error_msg}")
297
+ init_status['inflection_api']['ready'] = False
298
+ return False
299
+
300
+ def validate_quantum_api():
301
+ """Validate Quantum Random Numbers API key (optional component)"""
302
+ try:
303
+ logging.info("🔄 [Quantum API] Validating API key...")
304
+ print("🔄 [Quantum API] Validating API key...")
305
+ api_key = os.getenv("ANU_QUANTUM_API_KEY")
306
+ if not api_key:
307
+ logging.info("ℹ️ [Quantum API] API key not found (optional - will use pseudo-random)")
308
+ print("ℹ️ [Quantum API] API key not found (optional - will use pseudo-random)")
309
+ init_status['quantum_api']['ready'] = False
310
+ return True # Not an error - optional component
311
+ url = "https://api.quantumnumbers.anu.edu.au"
312
+ headers = {"x-api-key": api_key}
313
+ params = {"length": 1, "type": "uint8"}
314
+ response = requests.get(url, headers=headers, params=params, timeout=10)
315
+ if response.status_code == 200:
316
+ logging.info("✓ [Quantum API] API key validated")
317
+ print("✓ [Quantum API] API key validated")
318
+ init_status['quantum_api']['ready'] = True
319
+ return True
320
+ elif response.status_code == 429:
321
+ # Rate limit - not an error, just unavailable temporarily
322
+ logging.info("ℹ️ [Quantum API] Rate limited (optional - will use pseudo-random)")
323
+ print("ℹ️ [Quantum API] Rate limited (optional - will use pseudo-random)")
324
+ init_status['quantum_api']['ready'] = False
325
+ return True # Not an error - optional component
326
+ else:
327
+ logging.info(f"ℹ️ [Quantum API] Validation failed: {response.status_code} (optional - will use pseudo-random)")
328
+ print(f"ℹ️ [Quantum API] Validation failed: {response.status_code} (optional - will use pseudo-random)")
329
+ init_status['quantum_api']['ready'] = False
330
+ return True # Not an error - optional component
331
+ except Exception as e:
332
+ # Any exception is not critical - quantum randomness is optional
333
+ logging.info(f"ℹ️ [Quantum API] Unavailable: {e} (optional - will use pseudo-random)")
334
+ print(f"ℹ️ [Quantum API] Unavailable: {e} (optional - will use pseudo-random)")
335
+ init_status['quantum_api']['ready'] = False
336
+ return True # Not an error - optional component
337
+
338
+ def run_parallel_initialization():
339
+ """Run all initialization steps in parallel"""
340
+ start_time = time.time()
341
 
342
+ logging.info("=" * 70)
343
+ logging.info("GALATEA AI PARALLEL INITIALIZATION")
344
+ logging.info("=" * 70)
345
+ logging.info("Starting parallel initialization of all components...")
346
+ logging.info("")
347
+ print("=" * 70)
348
+ print("GALATEA AI PARALLEL INITIALIZATION")
349
+ print("=" * 70)
350
+ print("Starting parallel initialization of all components...")
351
+ print("")
352
+
353
+ tasks = [
354
+ ("JSON Memory", initialize_json_memory),
355
+ ("Sentiment Analyzer", initialize_sentiment_analyzer),
356
+ ("Gemini API", validate_gemini_api),
357
+ ("Inflection AI", validate_inflection_api),
358
+ ("Quantum API", validate_quantum_api),
359
+ ]
360
+
361
+ completed_count = 0
362
+ total_tasks = len(tasks)
363
+
364
+ with ThreadPoolExecutor(max_workers=5) as executor:
365
+ futures = {executor.submit(task[1]): task[0] for task in tasks}
366
+
367
+ for future in as_completed(futures):
368
+ task_name = futures[future]
369
+ completed_count += 1
370
+ try:
371
+ result = future.result()
372
+ if result:
373
+ logging.info(f"✅ [{task_name}] Completed successfully ({completed_count}/{total_tasks})")
374
+ print(f"✅ [{task_name}] Completed successfully ({completed_count}/{total_tasks})")
375
+ else:
376
+ logging.warning(f"⚠️ [{task_name}] Completed with warnings ({completed_count}/{total_tasks})")
377
+ print(f"⚠️ [{task_name}] Completed with warnings ({completed_count}/{total_tasks})")
378
+ except Exception as e:
379
+ logging.error(f"❌ [{task_name}] Failed: {e} ({completed_count}/{total_tasks})")
380
+ print(f"❌ [{task_name}] Failed: {e} ({completed_count}/{total_tasks})")
381
+
382
+ elapsed_time = time.time() - start_time
383
 
384
+ logging.info("")
385
  logging.info("=" * 70)
386
+ logging.info("INITIALIZATION SUMMARY")
387
  logging.info("=" * 70)
388
+ print("")
389
+ print("=" * 70)
390
+ print("INITIALIZATION SUMMARY")
391
+ print("=" * 70)
392
 
393
+ all_ready = True
394
+ critical_ready = True
395
+
396
+ for component, status in init_status.items():
397
+ status_icon = "✓" if status['ready'] else "✗"
398
+ error_info = f" - {status['error']}" if status['error'] else ""
399
+ status_msg = f"{status_icon} {component.upper()}: {'READY' if status['ready'] else 'FAILED'}{error_info}"
400
+ logging.info(status_msg)
401
+ print(status_msg)
402
 
403
+ if component in ['json_memory', 'sentiment_analyzer', 'gemini_api']:
404
+ if not status['ready']:
405
+ critical_ready = False
 
 
 
 
 
406
 
407
+ if not status['ready']:
408
+ all_ready = False
409
+
410
+ logging.info("")
411
+ logging.info(f"⏱️ Total initialization time: {elapsed_time:.2f} seconds")
412
+ logging.info("")
413
+ print("")
414
+ print(f"⏱️ Total initialization time: {elapsed_time:.2f} seconds")
415
+ print("")
416
+
417
+ if critical_ready:
418
+ if all_ready:
419
+ logging.info("✅ ALL COMPONENTS INITIALIZED SUCCESSFULLY")
420
+ logging.info("🎉 Galatea AI is ready to use!")
421
+ print("✅ ALL COMPONENTS INITIALIZED SUCCESSFULLY")
422
+ print("🎉 Galatea AI is ready to use!")
423
+ return True
424
  else:
425
+ logging.info("⚠️ CRITICAL COMPONENTS READY (some optional components failed)")
426
+ logging.info(" Galatea AI is ready to use (with limited features)")
427
+ print("⚠️ CRITICAL COMPONENTS READY (some optional components failed)")
428
+ print("✅ Galatea AI is ready to use (with limited features)")
429
+ return True
430
+ else:
431
+ logging.error("❌ CRITICAL COMPONENTS FAILED")
432
+ logging.error("⚠️ Galatea AI may not function properly")
433
+ print(" CRITICAL COMPONENTS FAILED")
434
+ print("⚠️ Galatea AI may not function properly")
435
+ return False
 
 
436
 
437
  def initialize_components():
438
+ """Initialize Galatea components"""
439
  global galatea_ai, dialogue_engine, avatar_engine, is_initialized, initializing
440
+ global current_init_retry, gemini_initialized
441
 
442
  if initializing or is_initialized:
443
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  if missing_gemini_key:
446
  logging.error("Initialization aborted: GEMINI_API_KEY missing")
 
465
  avatar_engine = AvatarEngine()
466
  avatar_engine.update_avatar(galatea_ai.emotional_state)
467
 
468
+ # Start quantum emotion service (background thread)
469
+ global quantum_emotion_service
470
+ try:
471
+ from quantum_emotion_service import QuantumEmotionService
472
+ quantum_emotion_service = QuantumEmotionService(galatea_ai.emotional_agent)
473
+ if quantum_emotion_service.start():
474
+ logging.info("✓ Quantum Emotion Service started")
475
+ else:
476
+ logging.info("ℹ️ Quantum Emotion Service not started (no API key or unavailable)")
477
+ except Exception as e:
478
+ logging.warning(f"⚠ Could not start Quantum Emotion Service: {e}")
479
+ quantum_emotion_service = None
480
+
481
  # Check if all components are fully initialized
482
  init_status = galatea_ai.get_initialization_status()
483
 
 
534
  def home():
535
  # Add error handling for template rendering
536
  try:
537
+ # Start component initialization if not already started
 
 
 
 
538
  if not is_initialized and not initializing and not missing_gemini_key:
539
  Thread(target=initialize_components, daemon=True).start()
540
 
 
823
  @app.route('/api/is_initialized')
824
  def is_initialized_endpoint():
825
  """Lightweight endpoint for polling initialization progress"""
 
 
826
  # Determine current initialization state
827
  if missing_gemini_key:
828
  return jsonify({
 
833
  'status': 'missing_api_key'
834
  })
835
 
 
 
 
 
 
 
 
 
 
 
836
  # Check if components are initializing
837
  if initializing:
838
  return jsonify({
 
880
 
881
  if __name__ == '__main__':
882
  print("Starting Galatea Web Interface...")
 
883
 
884
+ # Run parallel initialization BEFORE starting Flask app
885
  logging.info("=" * 70)
886
  logging.info("STARTING GALATEA AI APPLICATION")
887
  logging.info("=" * 70)
888
+ logging.info("Running parallel initialization...")
889
+ print("=" * 70)
890
+ print("STARTING GALATEA AI APPLICATION")
891
+ print("=" * 70)
892
+ print("Running parallel initialization...")
893
+ print("")
894
 
895
+ # Run parallel initialization synchronously
896
+ init_success = run_parallel_initialization()
897
+
898
+ if not init_success:
899
+ logging.error("=" * 70)
900
+ logging.error("CRITICAL: Parallel initialization failed")
901
+ logging.error("Application will exit")
902
+ logging.error("=" * 70)
903
+ print("=" * 70)
904
+ print("CRITICAL: Parallel initialization failed")
905
+ print("Application will exit")
906
+ print("=" * 70)
907
+ sys.exit(1)
908
+
909
+ # Now initialize Galatea components
910
+ logging.info("Initializing Galatea AI components...")
911
+ print("Initializing Galatea AI components...")
912
+ initialize_components()
913
+
914
+ if not is_initialized:
915
+ logging.error("=" * 70)
916
+ logging.error("CRITICAL: Component initialization failed")
917
+ logging.error("Application will exit")
918
+ logging.error("=" * 70)
919
+ print("=" * 70)
920
+ print("CRITICAL: Component initialization failed")
921
+ print("Application will exit")
922
+ print("=" * 70)
923
+ sys.exit(1)
924
 
 
 
 
925
  # Add debug logs for avatar shape changes
926
  logging.info("Avatar system initialized with default shape.")
927
 
 
930
 
931
  logging.info(f"Flask server starting on port {port}...")
932
  logging.info("Frontend will poll /api/is_initialized for status")
933
+ print(f"\nFlask server starting on port {port}...")
934
+ print("Frontend will poll /api/is_initialized for status\n")
935
 
936
  # Bind to 0.0.0.0 for external access (required for Hugging Face Spaces)
937
  app.run(host='0.0.0.0', port=port, debug=True)
initialize_galatea.py DELETED
@@ -1,400 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Galatea AI Initialization Script
4
- Handles parallel initialization of all components
5
- """
6
-
7
- import os
8
- import sys
9
- import time
10
- import logging
11
- import threading
12
- from concurrent.futures import ThreadPoolExecutor, as_completed
13
- from dotenv import load_dotenv
14
-
15
- # Configure logging
16
- logging.basicConfig(
17
- level=logging.INFO,
18
- format='%(asctime)s - %(levelname)s - %(message)s',
19
- handlers=[
20
- logging.StreamHandler(sys.stdout),
21
- logging.FileHandler('initialization.log')
22
- ]
23
- )
24
-
25
- # Check NumPy version before proceeding
26
- try:
27
- import numpy as np
28
- np_version = np.__version__
29
- if np_version.startswith('2.'):
30
- logging.error("=" * 70)
31
- logging.error("NUM PY COMPATIBILITY ERROR")
32
- logging.error("=" * 70)
33
- logging.error(f"NumPy {np_version} is installed, but required libraries need NumPy < 2.0")
34
- logging.error("")
35
- logging.error("SOLUTION:")
36
- logging.error(" Option 1: Run the fix script:")
37
- logging.error(" python fix_numpy.py")
38
- logging.error("")
39
- logging.error(" Option 2: Manually downgrade:")
40
- logging.error(" pip install 'numpy<2.0.0'")
41
- logging.error("")
42
- logging.error(" Option 3: Reinstall all dependencies:")
43
- logging.error(" pip install -r requirements.txt")
44
- logging.error("")
45
- logging.error("This will downgrade NumPy to a compatible version.")
46
- logging.error("=" * 70)
47
- logging.warning("⚠ Continuing with initialization, but some components may fail...")
48
- logging.warning("⚠ Please fix NumPy version for full functionality")
49
- else:
50
- logging.info(f"✓ NumPy version check passed: {np_version}")
51
- except ImportError:
52
- logging.warning("NumPy not installed - will be installed as dependency")
53
- except Exception as e:
54
- logging.warning(f"Could not check NumPy version: {e}")
55
-
56
- # Load environment variables
57
- load_dotenv()
58
-
59
- # Global status tracking
60
- init_status = {
61
- 'json_memory': {'ready': False, 'error': None},
62
- 'sentiment_analyzer': {'ready': False, 'error': None},
63
- 'gemini_api': {'ready': False, 'error': None},
64
- 'inflection_api': {'ready': False, 'error': None},
65
- 'quantum_api': {'ready': False, 'error': None},
66
- }
67
-
68
- # ChromaDB and embedding model removed - using JSON-only memory
69
-
70
- def initialize_sentiment_analyzer():
71
- """Initialize sentiment analyzer"""
72
- try:
73
- logging.info("🔄 [Sentiment Analyzer] Starting initialization...")
74
- print("🔄 [Sentiment Analyzer] Starting initialization...")
75
- try:
76
- from transformers import pipeline
77
- analyzer = pipeline(
78
- "sentiment-analysis",
79
- model="distilbert/distilbert-base-uncased-finetuned-sst-2-english"
80
- )
81
- # Test it
82
- result = analyzer("test")
83
- logging.info("✓ [Sentiment Analyzer] Hugging Face model loaded")
84
- print("✓ [Sentiment Analyzer] Hugging Face model loaded")
85
- init_status['sentiment_analyzer']['ready'] = True
86
- return True
87
- except ImportError:
88
- logging.info("✓ [Sentiment Analyzer] Using fallback (NLTK VADER)")
89
- print("✓ [Sentiment Analyzer] Using fallback (NLTK VADER)")
90
- init_status['sentiment_analyzer']['ready'] = True
91
- return True
92
- except Exception as e:
93
- error_msg = str(e)
94
- # Check for NumPy compatibility issues
95
- if 'np.float_' in error_msg or 'NumPy 2' in error_msg or '_ARRAY_API' in error_msg:
96
- logging.warning(f"⚠ [Sentiment Analyzer] NumPy compatibility issue - using fallback")
97
- print("⚠ [Sentiment Analyzer] NumPy compatibility issue - using fallback")
98
- init_status['sentiment_analyzer']['ready'] = True # Fallback available
99
- return True
100
- else:
101
- raise
102
- except Exception as e:
103
- error_msg = f"Sentiment analyzer initialization failed: {e}"
104
- logging.warning(f"⚠ [Sentiment Analyzer] {error_msg} - using fallback")
105
- print(f"⚠ [Sentiment Analyzer] Using fallback")
106
- init_status['sentiment_analyzer']['error'] = str(e)
107
- # Still mark as ready since we have fallback
108
- init_status['sentiment_analyzer']['ready'] = True
109
- return True
110
-
111
- def validate_gemini_api():
112
- """Validate Gemini API key"""
113
- try:
114
- logging.info("🔄 [Gemini API] Validating API key...")
115
- print("🔄 [Gemini API] Validating API key...")
116
- api_key = os.getenv("GEMINI_API_KEY")
117
-
118
- if not api_key:
119
- logging.warning("⚠ [Gemini API] API key not found")
120
- print("⚠ [Gemini API] API key not found")
121
- init_status['gemini_api']['ready'] = False
122
- return False
123
-
124
- # Try to use custom LLM wrapper to validate
125
- try:
126
- from llm_wrapper import LLMWrapper
127
- # Initialize wrapper with test model
128
- wrapper = LLMWrapper(gemini_model="gemini-1.5-flash")
129
- response = wrapper.call_gemini(
130
- messages=[{"role": "user", "content": "test"}],
131
- max_tokens=5
132
- )
133
- if response:
134
- logging.info("✓ [Gemini API] API key validated")
135
- print("✓ [Gemini API] API key validated")
136
- init_status['gemini_api']['ready'] = True
137
- return True
138
- else:
139
- logging.warning("⚠ [Gemini API] Validation failed - no response")
140
- print("⚠ [Gemini API] Validation failed - key exists, may be network issue")
141
- return False
142
- except Exception as e:
143
- logging.warning(f"⚠ [Gemini API] Validation failed: {e}")
144
- print("⚠ [Gemini API] Validation failed - key exists, may be network issue")
145
- # Still mark as available if key exists (might be network issue)
146
- init_status['gemini_api']['ready'] = True
147
- return True
148
- except Exception as e:
149
- error_msg = f"Gemini API validation failed: {e}"
150
- logging.error(f"✗ [Gemini API] {error_msg}")
151
- print(f"✗ [Gemini API] {error_msg}")
152
- init_status['gemini_api']['error'] = str(e)
153
- return False
154
-
155
- def validate_inflection_api():
156
- """Validate Inflection AI API key"""
157
- try:
158
- logging.info("🔄 [Inflection AI] Validating API key...")
159
- print("🔄 [Inflection AI] Validating API key...")
160
- api_key = os.getenv("INFLECTION_AI_API_KEY")
161
-
162
- if not api_key:
163
- logging.warning("⚠ [Inflection AI] API key not found")
164
- print("⚠ [Inflection AI] API key not found")
165
- init_status['inflection_api']['ready'] = False
166
- return False
167
-
168
- # Test API key by making a simple request
169
- import requests
170
- url = "https://api.inflection.ai/external/api/inference"
171
- headers = {
172
- "Authorization": f"Bearer {api_key}",
173
- "Content-Type": "application/json"
174
- }
175
- data = {
176
- "context": [{"text": "test", "type": "Human"}],
177
- "config": "Pi-3.1"
178
- }
179
-
180
- response = requests.post(url, headers=headers, json=data, timeout=10)
181
- if response.status_code == 200:
182
- logging.info("✓ [Inflection AI] API key validated")
183
- print("✓ [Inflection AI] API key validated")
184
- init_status['inflection_api']['ready'] = True
185
- return True
186
- else:
187
- logging.warning(f"⚠ [Inflection AI] Validation failed: {response.status_code}")
188
- print(f"⚠ [Inflection AI] Validation failed: {response.status_code}")
189
- init_status['inflection_api']['ready'] = False
190
- return False
191
- except Exception as e:
192
- error_msg = f"Inflection AI validation failed: {e}"
193
- logging.warning(f"⚠ [Inflection AI] {error_msg}")
194
- print(f"⚠ [Inflection AI] {error_msg}")
195
- # Don't fail initialization if this fails
196
- init_status['inflection_api']['ready'] = False
197
- return False
198
-
199
- def validate_quantum_api():
200
- """Validate Quantum Random Numbers API key"""
201
- try:
202
- logging.info("🔄 [Quantum API] Validating API key...")
203
- print("🔄 [Quantum API] Validating API key...")
204
- api_key = os.getenv("ANU_QUANTUM_API_KEY")
205
-
206
- if not api_key:
207
- logging.warning("⚠ [Quantum API] API key not found")
208
- print("⚠ [Quantum API] API key not found")
209
- init_status['quantum_api']['ready'] = False
210
- return False
211
-
212
- # Test API key
213
- import requests
214
- url = "https://api.quantumnumbers.anu.edu.au"
215
- headers = {"x-api-key": api_key}
216
- params = {"length": 1, "type": "uint8"}
217
-
218
- response = requests.get(url, headers=headers, params=params, timeout=10)
219
- if response.status_code == 200:
220
- logging.info("✓ [Quantum API] API key validated")
221
- print("✓ [Quantum API] API key validated")
222
- init_status['quantum_api']['ready'] = True
223
- return True
224
- else:
225
- logging.warning(f"⚠ [Quantum API] Validation failed: {response.status_code}")
226
- print(f"⚠ [Quantum API] Validation failed: {response.status_code}")
227
- init_status['quantum_api']['ready'] = False
228
- return False
229
- except Exception as e:
230
- error_msg = f"Quantum API validation failed: {e}"
231
- logging.warning(f"⚠ [Quantum API] {error_msg}")
232
- print(f"⚠ [Quantum API] {error_msg}")
233
- init_status['quantum_api']['ready'] = False
234
- return False
235
-
236
- def initialize_json_memory():
237
- """Initialize JSON memory database"""
238
- try:
239
- logging.info("🔄 [JSON Memory] Initializing...")
240
- print("🔄 [JSON Memory] Initializing...")
241
- import json
242
-
243
- json_path = "./memory.json"
244
- if os.path.exists(json_path):
245
- with open(json_path, 'r', encoding='utf-8') as f:
246
- memory = json.load(f)
247
- logging.info(f"✓ [JSON Memory] Loaded {len(memory)} entries")
248
- print(f"✓ [JSON Memory] Loaded {len(memory)} entries")
249
- else:
250
- with open(json_path, 'w', encoding='utf-8') as f:
251
- json.dump({}, f)
252
- logging.info("✓ [JSON Memory] Created new database")
253
- print("✓ [JSON Memory] Created new database")
254
-
255
- init_status['json_memory']['ready'] = True
256
- return True
257
- except Exception as e:
258
- error_msg = f"JSON memory initialization failed: {e}"
259
- logging.error(f"✗ [JSON Memory] {error_msg}")
260
- print(f"✗ [JSON Memory] {error_msg}")
261
- init_status['json_memory']['error'] = str(e)
262
- return False
263
-
264
- def run_initialization():
265
- """Run all initialization steps in parallel"""
266
- start_time = time.time()
267
-
268
- logging.info("=" * 70)
269
- logging.info("GALATEA AI PARALLEL INITIALIZATION")
270
- logging.info("=" * 70)
271
- logging.info("Starting parallel initialization of all components...")
272
- logging.info("")
273
-
274
- # Define initialization tasks
275
- tasks = [
276
- ("JSON Memory", initialize_json_memory),
277
- ("Sentiment Analyzer", initialize_sentiment_analyzer),
278
- ("Gemini API", validate_gemini_api),
279
- ("Inflection AI", validate_inflection_api),
280
- ("Quantum API", validate_quantum_api),
281
- ]
282
-
283
- # Run tasks in parallel
284
- completed_count = 0
285
- total_tasks = len(tasks)
286
-
287
- with ThreadPoolExecutor(max_workers=5) as executor:
288
- futures = {executor.submit(task[1]): task[0] for task in tasks}
289
-
290
- for future in as_completed(futures):
291
- task_name = futures[future]
292
- completed_count += 1
293
- try:
294
- result = future.result()
295
- if result:
296
- logging.info(f"✅ [{task_name}] Completed successfully ({completed_count}/{total_tasks})")
297
- print(f"✅ [{task_name}] Completed successfully ({completed_count}/{total_tasks})")
298
- else:
299
- logging.warning(f"⚠️ [{task_name}] Completed with warnings ({completed_count}/{total_tasks})")
300
- print(f"⚠️ [{task_name}] Completed with warnings ({completed_count}/{total_tasks})")
301
- except Exception as e:
302
- logging.error(f"❌ [{task_name}] Failed: {e} ({completed_count}/{total_tasks})")
303
- print(f"❌ [{task_name}] Failed: {e} ({completed_count}/{total_tasks})")
304
-
305
- elapsed_time = time.time() - start_time
306
-
307
- # Print summary
308
- logging.info("")
309
- logging.info("=" * 70)
310
- logging.info("INITIALIZATION SUMMARY")
311
- logging.info("=" * 70)
312
- print("")
313
- print("=" * 70)
314
- print("INITIALIZATION SUMMARY")
315
- print("=" * 70)
316
-
317
- all_ready = True
318
- critical_ready = True
319
-
320
- for component, status in init_status.items():
321
- status_icon = "✓" if status['ready'] else "✗"
322
- error_info = f" - {status['error']}" if status['error'] else ""
323
- status_msg = f"{status_icon} {component.upper()}: {'READY' if status['ready'] else 'FAILED'}{error_info}"
324
- logging.info(status_msg)
325
- print(status_msg)
326
-
327
- # Critical components (must be ready)
328
- if component in ['json_memory', 'sentiment_analyzer', 'gemini_api']:
329
- if not status['ready']:
330
- critical_ready = False
331
-
332
- if not status['ready']:
333
- all_ready = False
334
-
335
- logging.info("")
336
- logging.info(f"⏱️ Total initialization time: {elapsed_time:.2f} seconds")
337
- logging.info("")
338
- print("")
339
- print(f"⏱️ Total initialization time: {elapsed_time:.2f} seconds")
340
- print("")
341
-
342
- # Check for NumPy compatibility issues
343
- numpy_issue = False
344
- for component, status in init_status.items():
345
- if status.get('error') and ('np.float_' in str(status['error']) or 'NumPy 2' in str(status['error']) or '_ARRAY_API' in str(status['error'])):
346
- numpy_issue = True
347
- break
348
-
349
- if numpy_issue:
350
- logging.error("")
351
- logging.error("=" * 70)
352
- logging.error("NUM PY COMPATIBILITY ISSUE DETECTED")
353
- logging.error("=" * 70)
354
- logging.error("Some components failed due to NumPy 2.0 incompatibility.")
355
- logging.error("")
356
- logging.error("TO FIX:")
357
- logging.error(" 1. Run: python fix_numpy.py")
358
- logging.error(" 2. Or: pip install 'numpy<2.0.0'")
359
- logging.error(" 3. Then restart the application")
360
- logging.error("=" * 70)
361
- logging.error("")
362
-
363
- # Determine final status
364
- if critical_ready:
365
- if all_ready:
366
- logging.info("✅ ALL COMPONENTS INITIALIZED SUCCESSFULLY")
367
- logging.info("🎉 Galatea AI is ready to use!")
368
- print("✅ ALL COMPONENTS INITIALIZED SUCCESSFULLY")
369
- print("🎉 Galatea AI is ready to use!")
370
- return True
371
- else:
372
- logging.info("⚠️ CRITICAL COMPONENTS READY (some optional components failed)")
373
- if numpy_issue:
374
- logging.warning("⚠️ Some failures due to NumPy compatibility - fix NumPy for full functionality")
375
- logging.info("✅ Galatea AI is ready to use (with limited features)")
376
- print("⚠️ CRITICAL COMPONENTS READY (some optional components failed)")
377
- print("✅ Galatea AI is ready to use (with limited features)")
378
- return True
379
- else:
380
- logging.error("❌ CRITICAL COMPONENTS FAILED")
381
- if numpy_issue:
382
- logging.error("⚠️ Failures likely due to NumPy 2.0 - run 'python fix_numpy.py' to fix")
383
- logging.error("⚠️ Galatea AI may not function properly")
384
- print("❌ CRITICAL COMPONENTS FAILED")
385
- print("⚠️ Galatea AI may not function properly")
386
- return False
387
-
388
- if __name__ == "__main__":
389
- try:
390
- success = run_initialization()
391
- sys.exit(0 if success else 1)
392
- except KeyboardInterrupt:
393
- logging.info("\n⚠️ Initialization interrupted by user")
394
- sys.exit(1)
395
- except Exception as e:
396
- logging.error(f"\n❌ Fatal error during initialization: {e}")
397
- import traceback
398
- traceback.print_exc()
399
- sys.exit(1)
400
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
llm_wrapper.py CHANGED
@@ -126,10 +126,24 @@ class LLMWrapper:
126
  logging.error(f"[LLMWrapper] Unexpected Gemini response format: {result}")
127
  return None
128
  else:
129
- logging.error(f"[LLMWrapper] Gemini API returned status {response.status_code}: {response.text}")
130
- return None
 
 
 
 
 
 
131
 
 
 
 
 
132
  except Exception as e:
 
 
 
 
133
  logging.error(f"[LLMWrapper] Error calling Gemini API: {e}")
134
  return None
135
 
 
126
  logging.error(f"[LLMWrapper] Unexpected Gemini response format: {result}")
127
  return None
128
  else:
129
+ # Raise exception with status code and response text so validation can catch it
130
+ error_text = response.text
131
+ logging.error(f"[LLMWrapper] Gemini API returned status {response.status_code}: {error_text}")
132
+ # Create exception with status code info for validation to catch
133
+ api_error = Exception(f"Gemini API status {response.status_code}: {error_text}")
134
+ api_error.status_code = response.status_code
135
+ api_error.response_text = error_text
136
+ raise api_error
137
 
138
+ except requests.RequestException as e:
139
+ # Network/request errors - log and return None
140
+ logging.error(f"[LLMWrapper] Network error calling Gemini API: {e}")
141
+ return None
142
  except Exception as e:
143
+ # Re-raise status code errors so validation can catch them
144
+ if hasattr(e, 'status_code'):
145
+ raise
146
+ # Other errors - log and return None
147
  logging.error(f"[LLMWrapper] Error calling Gemini API: {e}")
148
  return None
149
 
models.yaml CHANGED
@@ -41,6 +41,11 @@ memory:
41
  retrieval:
42
  max_retrieved_memories: 5
43
 
 
 
 
 
 
44
  # Conversation Configuration
45
  conversation:
46
  max_history_length: 20 # Number of messages to keep (user + assistant pairs)
 
41
  retrieval:
42
  max_retrieved_memories: 5
43
 
44
+ # Emotional State Configuration
45
+ emotions:
46
+ # JSON file path for emotional state persistence
47
+ json_path: "./emotions.json"
48
+
49
  # Conversation Configuration
50
  conversation:
51
  max_history_length: 20 # Number of messages to keep (user + assistant pairs)
nltk-stubs/__init__.pyi ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Type stubs for nltk"""
2
+ from typing import Any
3
+
4
+ class data:
5
+ @staticmethod
6
+ def find(resource_name: str, paths: list[str] = ...]) -> str: ...
7
+
8
+ def download(info_or_id: str, download_dir: str = ..., quiet: bool = ..., force: bool = ...,
9
+ raise_on_error: bool = ..., halt_on_error: bool = ...) -> bool: ...
10
+
11
+ def word_tokenize(text: str) -> list[str]: ...
12
+
nltk-stubs/sentiment/__init__.pyi ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Type stubs for nltk.sentiment"""
2
+
nltk-stubs/sentiment/vader.pyi ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Type stubs for nltk.sentiment.vader"""
2
+
3
+ class SentimentIntensityAnalyzer:
4
+ def __init__(self, lexicon_file: str = ..., emoji_lexicon: str = ...) -> None: ...
5
+ def polarity_scores(self, text: str) -> dict[str, float]: ...
6
+
quantum_emotion_service.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quantum Emotion Service - Background service for quantum-influenced emotion updates"""
2
+ import os
3
+ import sys
4
+ import time
5
+ import logging
6
+ import requests
7
+ import threading
8
+ from collections import deque
9
+
10
+ # Add parent directory to path for imports
11
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12
+ from config import MODEL_CONFIG
13
+
14
+ class QuantumEmotionService:
15
+ """Background service that collects quantum random numbers and applies them to emotions"""
16
+
17
+ def __init__(self, emotional_agent, config=None):
18
+ self.config = config or MODEL_CONFIG or {}
19
+ self.emotional_agent = emotional_agent
20
+ self.quantum_api_key = os.getenv("ANU_QUANTUM_API_KEY")
21
+ self.running = False
22
+ self.thread = None
23
+ self.quantum_numbers = deque(maxlen=6) # Store last 6 numbers
24
+ self.last_update_time = time.time()
25
+ self.update_interval = 10.0 # Update emotions every 10 seconds
26
+ self.call_interval = 1.0 # Call API once per second
27
+
28
+ if not self.quantum_api_key:
29
+ logging.info("[QuantumEmotionService] Quantum API key not found - service will not run")
30
+ return
31
+
32
+ quantum_config = self.config.get('quantum', {}) if self.config else {}
33
+ self.api_endpoint = quantum_config.get('api_endpoint', 'https://api.quantumnumbers.anu.edu.au')
34
+
35
+ logging.info("[QuantumEmotionService] Initialized - will call API once per second")
36
+
37
+ def _fetch_quantum_number(self):
38
+ """Fetch a single quantum random number from the API"""
39
+ if not self.quantum_api_key:
40
+ return None
41
+
42
+ try:
43
+ headers = {"x-api-key": self.quantum_api_key}
44
+ params = {"length": 1, "type": "uint8"}
45
+
46
+ response = requests.get(self.api_endpoint, headers=headers, params=params, timeout=5)
47
+
48
+ if response.status_code == 200:
49
+ result = response.json()
50
+ if result.get('success') and 'data' in result and len(result['data']) > 0:
51
+ # Normalize to 0-1 range
52
+ normalized = result['data'][0] / 255.0
53
+ return normalized
54
+ elif response.status_code == 429:
55
+ # Rate limited - return None, will use pseudo-random
56
+ logging.debug("[QuantumEmotionService] Rate limited, using pseudo-random")
57
+ return None
58
+ else:
59
+ logging.debug(f"[QuantumEmotionService] API returned {response.status_code}")
60
+ return None
61
+ except Exception as e:
62
+ logging.debug(f"[QuantumEmotionService] API call failed: {e}")
63
+ return None
64
+
65
+ return None
66
+
67
+ def _apply_quantum_emotions(self):
68
+ """Apply the collected 6 quantum numbers to influence emotion state"""
69
+ if len(self.quantum_numbers) < 6:
70
+ logging.debug(f"[QuantumEmotionService] Not enough numbers yet ({len(self.quantum_numbers)}/6)")
71
+ return
72
+
73
+ # Convert deque to list
74
+ numbers = list(self.quantum_numbers)
75
+
76
+ # Map 6 numbers to 5 emotions + overall variation
77
+ # numbers[0] -> joy
78
+ # numbers[1] -> sadness
79
+ # numbers[2] -> anger
80
+ # numbers[3] -> fear
81
+ # numbers[4] -> curiosity
82
+ # numbers[5] -> overall variation factor
83
+
84
+ emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
85
+ current_state = self.emotional_agent.get_state()
86
+
87
+ # Apply quantum influence (subtle changes, -0.05 to +0.05 range)
88
+ for i, emotion in enumerate(emotions):
89
+ if i < len(numbers) and numbers[i] is not None:
90
+ # Convert 0-1 to -0.05 to +0.05 influence
91
+ influence = (numbers[i] - 0.5) * 0.1 # Scale to ±0.05
92
+
93
+ # Apply with overall variation factor
94
+ if len(numbers) > 5 and numbers[5] is not None:
95
+ variation = (numbers[5] - 0.5) * 0.02 # Additional ±0.01 variation
96
+ influence += variation
97
+
98
+ # Update emotion
99
+ new_value = current_state[emotion] + influence
100
+ current_state[emotion] = max(0.05, min(1.0, new_value))
101
+
102
+ # Soft normalization to keep emotions balanced (only if they get too extreme)
103
+ total = sum(current_state.values())
104
+ if total > 1.5 or total < 0.5:
105
+ # Only normalize if emotions are way out of balance
106
+ for emotion in emotions:
107
+ current_state[emotion] = current_state[emotion] / total if total > 0 else 0.2
108
+ else:
109
+ # Just ensure no emotion goes below minimum threshold
110
+ for emotion in emotions:
111
+ if current_state[emotion] < 0.05:
112
+ current_state[emotion] = 0.05
113
+
114
+ # Update the emotional agent's state
115
+ self.emotional_agent.emotional_state = current_state
116
+
117
+ # Save to JSON after quantum update
118
+ self.emotional_agent._save_to_json()
119
+
120
+ logging.info(f"[QuantumEmotionService] Applied quantum influence: {current_state}")
121
+
122
+ def _service_loop(self):
123
+ """Main service loop - calls API once per second, updates emotions every 10 seconds"""
124
+ logging.info("[QuantumEmotionService] Service started")
125
+
126
+ while self.running:
127
+ try:
128
+ # Fetch quantum number (once per second)
129
+ quantum_num = self._fetch_quantum_number()
130
+
131
+ if quantum_num is not None:
132
+ self.quantum_numbers.append(quantum_num)
133
+ logging.debug(f"[QuantumEmotionService] Collected quantum number: {quantum_num:.4f}")
134
+ else:
135
+ # Use pseudo-random as fallback
136
+ import random
137
+ pseudo_random = random.random()
138
+ self.quantum_numbers.append(pseudo_random)
139
+ logging.debug(f"[QuantumEmotionService] Using pseudo-random: {pseudo_random:.4f}")
140
+
141
+ # Check if it's time to update emotions (every 10 seconds)
142
+ current_time = time.time()
143
+ if current_time - self.last_update_time >= self.update_interval:
144
+ if len(self.quantum_numbers) >= 6:
145
+ self._apply_quantum_emotions()
146
+ self.last_update_time = current_time
147
+ logging.info(f"[QuantumEmotionService] Updated emotions with {len(self.quantum_numbers)} quantum numbers")
148
+
149
+ # Wait 1 second before next call
150
+ time.sleep(self.call_interval)
151
+
152
+ except Exception as e:
153
+ logging.error(f"[QuantumEmotionService] Error in service loop: {e}")
154
+ time.sleep(self.call_interval) # Wait before retrying
155
+
156
+ def start(self):
157
+ """Start the background service"""
158
+ if not self.quantum_api_key:
159
+ logging.info("[QuantumEmotionService] Cannot start - no API key")
160
+ return False
161
+
162
+ if self.running:
163
+ logging.warning("[QuantumEmotionService] Already running")
164
+ return False
165
+
166
+ self.running = True
167
+ self.thread = threading.Thread(target=self._service_loop, daemon=True)
168
+ self.thread.start()
169
+ logging.info("[QuantumEmotionService] Background service started")
170
+ return True
171
+
172
+ def stop(self):
173
+ """Stop the background service"""
174
+ if not self.running:
175
+ return
176
+
177
+ self.running = False
178
+ if self.thread:
179
+ self.thread.join(timeout=2.0)
180
+ logging.info("[QuantumEmotionService] Background service stopped")
181
+
182
+ def is_running(self):
183
+ """Check if service is running"""
184
+ return self.running
185
+