Your Name commited on
Commit
e9f0ec0
·
1 Parent(s): a9b28c4

Replace Gemini with DeepSeek Reasoner and implement quantum queue in /api/avatar endpoint

Browse files
Files changed (7) hide show
  1. agents/__init__.py +2 -2
  2. agents/gemini_agent.py +26 -26
  3. app.py +218 -109
  4. galatea_ai.py +13 -13
  5. llm_wrapper.py +65 -97
  6. models.yaml +5 -5
  7. requirements.txt +1 -0
agents/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
  """Agents package"""
2
  from .memory_agent import MemoryAgent
3
- from .gemini_agent import GeminiThinkingAgent
4
  from .pi_agent import PiResponseAgent
5
  from .emotional_agent import EmotionalStateAgent
6
  from .azure_agent import AzureTextAnalyticsAgent
@@ -8,7 +8,7 @@ from .sentiment_agent import SentimentAgent
8
 
9
  __all__ = [
10
  'MemoryAgent',
11
- 'GeminiThinkingAgent',
12
  'PiResponseAgent',
13
  'EmotionalStateAgent',
14
  'AzureTextAnalyticsAgent',
 
1
  """Agents package"""
2
  from .memory_agent import MemoryAgent
3
+ from .gemini_agent import DeepSeekThinkingAgent
4
  from .pi_agent import PiResponseAgent
5
  from .emotional_agent import EmotionalStateAgent
6
  from .azure_agent import AzureTextAnalyticsAgent
 
8
 
9
  __all__ = [
10
  'MemoryAgent',
11
+ 'DeepSeekThinkingAgent',
12
  'PiResponseAgent',
13
  'EmotionalStateAgent',
14
  'AzureTextAnalyticsAgent',
agents/gemini_agent.py CHANGED
@@ -1,4 +1,4 @@
1
- """Gemini Thinking Agent - responsible for thinking and analysis using Gemini"""
2
  import os
3
  import sys
4
  import logging
@@ -8,34 +8,34 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
  from config import MODEL_CONFIG
9
  from llm_wrapper import LLMWrapper
10
 
11
- class GeminiThinkingAgent:
12
- """Agent responsible for thinking and analysis using Gemini"""
13
 
14
  def __init__(self, config=None):
15
  self.config = config or MODEL_CONFIG or {}
16
- self.gemini_available = False
17
 
18
  # Get model from config
19
- gemini_config = self.config.get('gemini', {}) if self.config else {}
20
- gemini_model = gemini_config.get('model', 'gemini-2.0-flash-exp')
21
 
22
  # Initialize LLM wrapper with the model
23
- self.llm_wrapper = LLMWrapper(gemini_model=gemini_model, config=self.config)
24
  self._initialize()
25
 
26
  def _initialize(self):
27
- """Initialize Gemini API availability"""
28
- gemini_key = os.getenv("GEMINI_API_KEY")
29
- if gemini_key:
30
- self.gemini_available = True
31
- logging.info("[GeminiThinkingAgent] ✓ Initialized and ready")
32
  else:
33
- logging.warning("[GeminiThinkingAgent] ✗ GEMINI_API_KEY not found")
34
 
35
  def think(self, user_input, emotional_state, conversation_history, retrieved_memories=None):
36
  """Think about and analyze the conversation context"""
37
- if not self.gemini_available:
38
- logging.warning("[GeminiThinkingAgent] Not available")
39
  return None
40
 
41
  try:
@@ -78,36 +78,36 @@ Keep your analysis concise (2-3 sentences). Focus on what matters for crafting a
78
  {"role": "user", "content": thinking_prompt}
79
  ]
80
 
81
- logging.info("[GeminiThinkingAgent] Processing thinking request...")
82
 
83
  # Get hyperparameters from config
84
- gemini_config = self.config.get('gemini', {}) if self.config else {}
85
- temperature = gemini_config.get('temperature', 0.5)
86
- max_tokens = gemini_config.get('max_tokens', 200)
87
 
88
- # Call Gemini model (model is set in wrapper initialization)
89
  try:
90
- thinking_result = self.llm_wrapper.call_gemini(
91
  messages=messages,
92
  temperature=temperature,
93
  max_tokens=max_tokens
94
  )
95
 
96
  if thinking_result and len(thinking_result) > 0:
97
- logging.info("[GeminiThinkingAgent] ✓ Thinking completed")
98
  return thinking_result
99
  else:
100
- logging.error("[GeminiThinkingAgent] Model returned empty result")
101
  return None
102
  except Exception as e:
103
- logging.error(f"[GeminiThinkingAgent] Model {self.llm_wrapper.gemini_model} failed: {e}")
104
  return None
105
 
106
  except Exception as e:
107
- logging.error(f"[GeminiThinkingAgent] Error: {e}")
108
  return None
109
 
110
  def is_ready(self):
111
  """Check if agent is ready"""
112
- return self.gemini_available
113
 
 
1
+ """DeepSeek Thinking Agent - responsible for thinking and analysis using DeepSeek Reasoner"""
2
  import os
3
  import sys
4
  import logging
 
8
  from config import MODEL_CONFIG
9
  from llm_wrapper import LLMWrapper
10
 
11
+ class DeepSeekThinkingAgent:
12
+ """Agent responsible for thinking and analysis using DeepSeek Reasoner"""
13
 
14
  def __init__(self, config=None):
15
  self.config = config or MODEL_CONFIG or {}
16
+ self.deepseek_available = False
17
 
18
  # Get model from config
19
+ deepseek_config = self.config.get('deepseek', {}) if self.config else {}
20
+ deepseek_model = deepseek_config.get('model', 'deepseek-reasoner')
21
 
22
  # Initialize LLM wrapper with the model
23
+ self.llm_wrapper = LLMWrapper(deepseek_model=deepseek_model, config=self.config)
24
  self._initialize()
25
 
26
  def _initialize(self):
27
+ """Initialize DeepSeek API availability"""
28
+ deepseek_key = os.getenv("DEEPSEEK_API_KEY")
29
+ if deepseek_key:
30
+ self.deepseek_available = True
31
+ logging.info("[DeepSeekThinkingAgent] ✓ Initialized and ready")
32
  else:
33
+ logging.warning("[DeepSeekThinkingAgent] ✗ DEEPSEEK_API_KEY not found")
34
 
35
  def think(self, user_input, emotional_state, conversation_history, retrieved_memories=None):
36
  """Think about and analyze the conversation context"""
37
+ if not self.deepseek_available:
38
+ logging.warning("[DeepSeekThinkingAgent] Not available")
39
  return None
40
 
41
  try:
 
78
  {"role": "user", "content": thinking_prompt}
79
  ]
80
 
81
+ logging.info("[DeepSeekThinkingAgent] Processing thinking request...")
82
 
83
  # Get hyperparameters from config
84
+ deepseek_config = self.config.get('deepseek', {}) if self.config else {}
85
+ temperature = deepseek_config.get('temperature', 0.5)
86
+ max_tokens = deepseek_config.get('max_tokens', 200)
87
 
88
+ # Call DeepSeek model (model is set in wrapper initialization)
89
  try:
90
+ thinking_result = self.llm_wrapper.call_deepseek(
91
  messages=messages,
92
  temperature=temperature,
93
  max_tokens=max_tokens
94
  )
95
 
96
  if thinking_result and len(thinking_result) > 0:
97
+ logging.info("[DeepSeekThinkingAgent] ✓ Thinking completed")
98
  return thinking_result
99
  else:
100
+ logging.error("[DeepSeekThinkingAgent] Model returned empty result")
101
  return None
102
  except Exception as e:
103
+ logging.error(f"[DeepSeekThinkingAgent] Model {self.llm_wrapper.deepseek_model} failed: {e}")
104
  return None
105
 
106
  except Exception as e:
107
+ logging.error(f"[DeepSeekThinkingAgent] Error: {e}")
108
  return None
109
 
110
  def is_ready(self):
111
  """Check if agent is ready"""
112
+ return self.deepseek_available
113
 
app.py CHANGED
@@ -5,8 +5,9 @@ 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
 
@@ -20,34 +21,32 @@ load_dotenv()
20
  logging.info("=" * 60)
21
  logging.info("ENVIRONMENT VARIABLES CHECK")
22
  logging.info("=" * 60)
23
- gemini_key = os.environ.get('GEMINI_API_KEY')
24
- missing_gemini_key = False
25
- if gemini_key:
26
- logging.info(f"✓ GEMINI_API_KEY found (length: {len(gemini_key)} chars)")
27
- logging.info(f" First 10 chars: {gemini_key[:10]}...")
28
  else:
29
- missing_gemini_key = True
30
  logging.error("=" * 60)
31
- logging.error("✗ GEMINI_API_KEY not found in environment!")
32
  logging.error("=" * 60)
33
  logging.error("")
34
- logging.error("The GEMINI_API_KEY environment variable is required for full functionality.")
35
  logging.error("")
36
  logging.error("For Hugging Face Spaces:")
37
  logging.error(" 1. Go to Settings → Repository secrets")
38
  logging.error(" 2. Click 'New secret'")
39
- logging.error(" 3. Name: GEMINI_API_KEY")
40
- logging.error(" 4. Value: [Your Google Gemini API key]")
41
- logging.error(" 5. Get a key from: https://ai.google.dev/")
42
  logging.error("")
43
  logging.error("For local development:")
44
  logging.error(" 1. Copy .env.example to .env")
45
  logging.error(" 2. Add your API key to the .env file")
46
  logging.error("")
47
- logging.error("Available env vars starting with 'GEMINI': " +
48
- str([k for k in os.environ.keys() if 'GEMINI' in k.upper()]))
49
- logging.error("Available env vars starting with 'GOOGLE': " +
50
- str([k for k in os.environ.keys() if 'GOOGLE' in k.upper()]))
51
  logging.error("=" * 60)
52
 
53
  logging.info("=" * 60)
@@ -75,15 +74,19 @@ app = Flask(__name__, static_folder='static', template_folder='templates')
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']
87
  missing_vars = [var for var in required_env_vars if not os.environ.get(var)]
88
  if missing_vars:
89
  logging.error(f"Missing required environment variables: {', '.join(missing_vars)}")
@@ -91,43 +94,43 @@ if missing_vars:
91
  print(f"⚠️ Missing required environment variables: {', '.join(missing_vars)}")
92
  print("Please set these in your .env file or environment")
93
 
94
- def initialize_gemini():
95
- """Initialize Gemini API specifically"""
96
- global gemini_initialized
97
 
98
  if not galatea_ai:
99
- logging.warning("Cannot initialize Gemini: GalateaAI instance not created yet")
100
  return False
101
 
102
- if missing_gemini_key:
103
- logging.error("Cannot initialize Gemini: GEMINI_API_KEY is missing")
104
  return False
105
 
106
  try:
107
- # Check for GEMINI_API_KEY
108
- if not os.environ.get('GEMINI_API_KEY'):
109
- logging.error("GEMINI_API_KEY not found in environment variables")
110
  return False
111
 
112
- # Check if Gemini agent is ready (initialization happens automatically in GalateaAI.__init__)
113
- gemini_success = hasattr(galatea_ai, 'gemini_agent') and galatea_ai.gemini_agent.is_ready()
114
 
115
- if gemini_success:
116
- gemini_initialized = True
117
- logging.info("Gemini API initialized successfully")
118
  return True
119
  else:
120
- logging.error("Failed to initialize Gemini API")
121
  return False
122
  except Exception as e:
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
  }
@@ -195,38 +198,38 @@ def initialize_sentiment_analyzer():
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)
@@ -236,27 +239,27 @@ def validate_gemini_api():
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():
@@ -353,7 +356,7 @@ def run_parallel_initialization():
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
  ]
@@ -400,7 +403,7 @@ def run_parallel_initialization():
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
 
@@ -437,13 +440,13 @@ def run_parallel_initialization():
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")
447
  return
448
 
449
  initializing = True
@@ -465,19 +468,6 @@ def initialize_components():
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
 
@@ -487,7 +477,7 @@ def initialize_components():
487
  logging.info(f"Memory System (JSON): {init_status['memory_system']}")
488
  logging.info(f"Sentiment Analyzer: {init_status['sentiment_analyzer']}")
489
  logging.info(f"Models Ready: {init_status['models']}")
490
- logging.info(f" - Gemini available: {init_status['gemini_available']}")
491
  logging.info(f" - Inflection AI available: {init_status['inflection_ai_available']}")
492
  logging.info(f"API Keys Valid: {init_status['api_keys']}")
493
  logging.info(f"Fully Initialized: {init_status['fully_initialized']}")
@@ -496,9 +486,9 @@ def initialize_components():
496
  # CRITICAL: Only mark as initialized if ALL components are ready
497
  # If any component fails, EXIT the application immediately
498
  if init_status['fully_initialized']:
499
- is_initialized = True
500
  logging.info("✓ Galatea AI system fully initialized and ready")
501
- logging.info(f"Emotions initialized: {galatea_ai.emotional_state}")
502
  else:
503
  logging.error("=" * 60)
504
  logging.error("❌ INITIALIZATION FAILED - EXITING APPLICATION")
@@ -535,7 +525,7 @@ 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
 
541
  return render_template('index.html')
@@ -554,10 +544,10 @@ def chat():
554
  }), 503 # Service Unavailable
555
 
556
  # Check if API key is missing
557
- if missing_gemini_key:
558
  return jsonify({
559
- 'error': 'GEMINI_API_KEY is missing. Chat is unavailable.',
560
- 'status': 'missing_gemini_key',
561
  'is_initialized': False
562
  }), 503
563
 
@@ -720,10 +710,102 @@ def analyze_sentiment(text):
720
  # Track avatar updates with timestamp
721
  last_avatar_update = time.time()
722
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
  @app.route('/api/avatar')
724
  def get_avatar():
725
  """Endpoint to get the current avatar shape and state with enhanced responsiveness"""
726
- global last_avatar_update
727
 
728
  if not is_initialized:
729
  return jsonify({
@@ -734,6 +816,16 @@ def get_avatar():
734
  })
735
 
736
  try:
 
 
 
 
 
 
 
 
 
 
737
  avatar_shape = avatar_engine.avatar_model if avatar_engine else 'Circle'
738
 
739
  # Update timestamp when the avatar changes (you would track this in AvatarEngine normally)
@@ -749,6 +841,19 @@ def get_avatar():
749
 
750
  # Force avatar update based on emotions if available
751
  if avatar_engine and galatea_ai:
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  # If we have sentiment data, incorporate it into emotional state
753
  if sentiment_data:
754
  # Update emotional state based on sentiment (enhanced mapping)
@@ -760,6 +865,10 @@ def get_avatar():
760
  # Amplify anger emotion when detected
761
  galatea_ai.emotional_state["anger"] = max(galatea_ai.emotional_state["anger"], 0.8)
762
 
 
 
 
 
763
  avatar_engine.update_avatar(galatea_ai.emotional_state)
764
  avatar_shape = avatar_engine.avatar_model
765
  last_avatar_update = current_timestamp
@@ -785,21 +894,21 @@ def health():
785
  """Simple health check endpoint to verify the server is running"""
786
  return jsonify({
787
  'status': 'ok',
788
- 'gemini_available': hasattr(galatea_ai, 'gemini_available') and galatea_ai.gemini_available if galatea_ai else False,
789
  'is_initialized': is_initialized,
790
- 'missing_gemini_key': missing_gemini_key
791
  })
792
 
793
  @app.route('/api/availability')
794
  def availability():
795
  """Report overall availability state to the frontend"""
796
- if missing_gemini_key:
797
  return jsonify({
798
  'available': False,
799
- 'status': 'missing_gemini_key',
800
  'is_initialized': False,
801
  'initializing': False,
802
- 'missing_gemini_key': True,
803
  'error_page': url_for('error_page')
804
  })
805
 
@@ -809,7 +918,7 @@ def availability():
809
  'status': 'initializing',
810
  'is_initialized': is_initialized,
811
  'initializing': initializing,
812
- 'missing_gemini_key': False
813
  })
814
 
815
  return jsonify({
@@ -817,18 +926,18 @@ def availability():
817
  'status': 'ready',
818
  'is_initialized': True,
819
  'initializing': False,
820
- 'missing_gemini_key': False
821
  })
822
 
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({
829
  'is_initialized': False,
830
  'initializing': False,
831
- 'missing_gemini_key': True,
832
  'error_page': url_for('error_page'),
833
  'status': 'missing_api_key'
834
  })
@@ -838,7 +947,7 @@ def is_initialized_endpoint():
838
  return jsonify({
839
  'is_initialized': False,
840
  'initializing': True,
841
- 'missing_gemini_key': False,
842
  'status': 'initializing_components',
843
  'message': 'Initializing AI components...'
844
  })
@@ -848,7 +957,7 @@ def is_initialized_endpoint():
848
  return jsonify({
849
  'is_initialized': True,
850
  'initializing': False,
851
- 'missing_gemini_key': False,
852
  'status': 'ready',
853
  'message': 'System ready'
854
  })
@@ -857,7 +966,7 @@ def is_initialized_endpoint():
857
  return jsonify({
858
  'is_initialized': False,
859
  'initializing': True,
860
- 'missing_gemini_key': False,
861
  'status': 'waiting',
862
  'message': 'Waiting for initialization...'
863
  })
@@ -870,13 +979,13 @@ def status():
870
  'initializing': initializing,
871
  'emotions': galatea_ai.emotional_state if galatea_ai else {'joy': 0.2, 'sadness': 0.2, 'anger': 0.2, 'fear': 0.2, 'curiosity': 0.2},
872
  'avatar_shape': avatar_engine.avatar_model if avatar_engine and is_initialized else 'Circle',
873
- 'missing_gemini_key': missing_gemini_key
874
  })
875
 
876
  @app.route('/error')
877
  def error_page():
878
  """Render an informative error page when the app is unavailable"""
879
- return render_template('error.html', missing_gemini_key=missing_gemini_key)
880
 
881
  if __name__ == '__main__':
882
  print("Starting Galatea Web Interface...")
@@ -921,7 +1030,7 @@ if __name__ == '__main__':
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
 
@@ -932,6 +1041,6 @@ if __name__ == '__main__':
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)
 
5
  import json
6
  from dotenv import load_dotenv
7
  import logging
8
+ from threading import Thread, Lock
9
  from concurrent.futures import ThreadPoolExecutor, as_completed
10
+ from collections import deque
11
  import nltk
12
  import requests
13
 
 
21
  logging.info("=" * 60)
22
  logging.info("ENVIRONMENT VARIABLES CHECK")
23
  logging.info("=" * 60)
24
+ deepseek_key = os.environ.get('DEEPSEEK_API_KEY')
25
+ missing_deepseek_key = False
26
+ if deepseek_key:
27
+ logging.info(f"✓ DEEPSEEK_API_KEY found (length: {len(deepseek_key)} chars)")
28
+ logging.info(f" First 10 chars: {deepseek_key[:10]}...")
29
  else:
30
+ missing_deepseek_key = True
31
  logging.error("=" * 60)
32
+ logging.error("✗ DEEPSEEK_API_KEY not found in environment!")
33
  logging.error("=" * 60)
34
  logging.error("")
35
+ logging.error("The DEEPSEEK_API_KEY environment variable is required for full functionality.")
36
  logging.error("")
37
  logging.error("For Hugging Face Spaces:")
38
  logging.error(" 1. Go to Settings → Repository secrets")
39
  logging.error(" 2. Click 'New secret'")
40
+ logging.error(" 3. Name: DEEPSEEK_API_KEY")
41
+ logging.error(" 4. Value: [Your DeepSeek API key]")
42
+ logging.error(" 5. Get a key from: https://platform.deepseek.com/")
43
  logging.error("")
44
  logging.error("For local development:")
45
  logging.error(" 1. Copy .env.example to .env")
46
  logging.error(" 2. Add your API key to the .env file")
47
  logging.error("")
48
+ logging.error("Available env vars starting with 'DEEPSEEK': " +
49
+ str([k for k in os.environ.keys() if 'DEEPSEEK' in k.upper()]))
 
 
50
  logging.error("=" * 60)
51
 
52
  logging.info("=" * 60)
 
74
  galatea_ai = None
75
  dialogue_engine = None
76
  avatar_engine = None
 
77
  is_initialized = False
78
  initializing = False
79
+ deepseek_initialized = False
80
  max_init_retries = 3
81
  current_init_retry = 0
82
 
83
+ # Quantum numbers queue for /api/avatar endpoint
84
+ quantum_numbers_queue = deque(maxlen=100)
85
+ quantum_queue_lock = Lock()
86
+ quantum_filling = False
87
+
88
  # Check for required environment variables
89
+ required_env_vars = ['DEEPSEEK_API_KEY']
90
  missing_vars = [var for var in required_env_vars if not os.environ.get(var)]
91
  if missing_vars:
92
  logging.error(f"Missing required environment variables: {', '.join(missing_vars)}")
 
94
  print(f"⚠️ Missing required environment variables: {', '.join(missing_vars)}")
95
  print("Please set these in your .env file or environment")
96
 
97
+ def initialize_deepseek():
98
+ """Initialize DeepSeek API specifically"""
99
+ global deepseek_initialized
100
 
101
  if not galatea_ai:
102
+ logging.warning("Cannot initialize DeepSeek: GalateaAI instance not created yet")
103
  return False
104
 
105
+ if missing_deepseek_key:
106
+ logging.error("Cannot initialize DeepSeek: DEEPSEEK_API_KEY is missing")
107
  return False
108
 
109
  try:
110
+ # Check for DEEPSEEK_API_KEY
111
+ if not os.environ.get('DEEPSEEK_API_KEY'):
112
+ logging.error("DEEPSEEK_API_KEY not found in environment variables")
113
  return False
114
 
115
+ # Check if DeepSeek agent is ready (initialization happens automatically in GalateaAI.__init__)
116
+ deepseek_success = hasattr(galatea_ai, 'deepseek_agent') and galatea_ai.deepseek_agent.is_ready()
117
 
118
+ if deepseek_success:
119
+ deepseek_initialized = True
120
+ logging.info("DeepSeek API initialized successfully")
121
  return True
122
  else:
123
+ logging.error("Failed to initialize DeepSeek API")
124
  return False
125
  except Exception as e:
126
+ logging.error(f"Error initializing DeepSeek API: {e}")
127
  return False
128
 
129
  # Global status tracking for parallel initialization
130
  init_status = {
131
  'json_memory': {'ready': False, 'error': None},
132
  'sentiment_analyzer': {'ready': False, 'error': None},
133
+ 'deepseek_api': {'ready': False, 'error': None},
134
  'inflection_api': {'ready': False, 'error': None},
135
  'quantum_api': {'ready': False, 'error': None},
136
  }
 
198
  init_status['sentiment_analyzer']['ready'] = True
199
  return True
200
 
201
+ def validate_deepseek_api():
202
+ """Validate DeepSeek API key"""
203
  try:
204
+ logging.info("🔄 [DeepSeek API] Validating API key...")
205
+ print("🔄 [DeepSeek API] Validating API key...")
206
+ api_key = os.getenv("DEEPSEEK_API_KEY")
207
  if not api_key:
208
+ logging.warning("⚠ [DeepSeek API] API key not found")
209
+ print("⚠ [DeepSeek API] API key not found")
210
+ init_status['deepseek_api']['ready'] = False
211
  return False
212
  try:
213
  from llm_wrapper import LLMWrapper
214
  from config import MODEL_CONFIG
215
 
216
  # Get model from config
217
+ deepseek_config = MODEL_CONFIG.get('deepseek', {}) if MODEL_CONFIG else {}
218
+ deepseek_model = deepseek_config.get('model', 'deepseek-reasoner')
219
 
220
+ wrapper = LLMWrapper(deepseek_model=deepseek_model)
221
+ response = wrapper.call_deepseek(
222
  messages=[{"role": "user", "content": "test"}],
223
  max_tokens=5
224
  )
225
  if response:
226
+ logging.info("✓ [DeepSeek API] API key validated")
227
+ print("✓ [DeepSeek API] API key validated")
228
+ init_status['deepseek_api']['ready'] = True
229
  return True
230
  else:
231
+ logging.warning("⚠ [DeepSeek API] Validation failed - no response")
232
+ print("⚠ [DeepSeek API] Validation failed - key exists, may be network issue")
233
  return False
234
  except Exception as e:
235
  error_msg = str(e)
 
239
 
240
  # Check if it's a 404 (model not found) - this is a real error
241
  if status_code == 404 or '404' in error_msg or 'NOT_FOUND' in error_msg:
242
+ logging.error(f"✗ [DeepSeek API] Model not found: {error_msg}")
243
+ print(f"✗ [DeepSeek API] Model not found - check models.yaml configuration")
244
+ init_status['deepseek_api']['error'] = error_msg
245
  return False
246
  # Check if it's a 429 (rate limit/quota exceeded) - API key is valid, just quota issue
247
  elif status_code == 429 or '429' in error_msg or 'RESOURCE_EXHAUSTED' in error_msg or 'quota' in response_text.lower():
248
+ logging.info("ℹ️ [DeepSeek API] Rate limit/quota exceeded (API key is valid)")
249
+ print("ℹ️ [DeepSeek API] Rate limit/quota exceeded (API key is valid, will work when quota resets)")
250
+ init_status['deepseek_api']['ready'] = True # Key is valid, just quota issue
251
+ init_status['deepseek_api']['error'] = "Rate limit/quota exceeded"
252
  return True # Don't fail initialization - key is valid
253
  else:
254
+ logging.warning(f"⚠ [DeepSeek API] Validation failed: {e}")
255
+ print("⚠ [DeepSeek API] Validation failed - key exists, may be network issue")
256
+ init_status['deepseek_api']['ready'] = True
257
  return True
258
  except Exception as e:
259
+ error_msg = f"DeepSeek API validation failed: {e}"
260
+ logging.error(f"✗ [DeepSeek API] {error_msg}")
261
+ print(f"✗ [DeepSeek API] {error_msg}")
262
+ init_status['deepseek_api']['error'] = str(e)
263
  return False
264
 
265
  def validate_inflection_api():
 
356
  tasks = [
357
  ("JSON Memory", initialize_json_memory),
358
  ("Sentiment Analyzer", initialize_sentiment_analyzer),
359
+ ("DeepSeek API", validate_deepseek_api),
360
  ("Inflection AI", validate_inflection_api),
361
  ("Quantum API", validate_quantum_api),
362
  ]
 
403
  logging.info(status_msg)
404
  print(status_msg)
405
 
406
+ if component in ['json_memory', 'sentiment_analyzer', 'deepseek_api']:
407
  if not status['ready']:
408
  critical_ready = False
409
 
 
440
  def initialize_components():
441
  """Initialize Galatea components"""
442
  global galatea_ai, dialogue_engine, avatar_engine, is_initialized, initializing
443
+ global current_init_retry, deepseek_initialized
444
 
445
  if initializing or is_initialized:
446
  return
447
 
448
+ if missing_deepseek_key:
449
+ logging.error("Initialization aborted: DEEPSEEK_API_KEY missing")
450
  return
451
 
452
  initializing = True
 
468
  avatar_engine = AvatarEngine()
469
  avatar_engine.update_avatar(galatea_ai.emotional_state)
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  # Check if all components are fully initialized
472
  init_status = galatea_ai.get_initialization_status()
473
 
 
477
  logging.info(f"Memory System (JSON): {init_status['memory_system']}")
478
  logging.info(f"Sentiment Analyzer: {init_status['sentiment_analyzer']}")
479
  logging.info(f"Models Ready: {init_status['models']}")
480
+ logging.info(f" - DeepSeek available: {init_status['deepseek_available']}")
481
  logging.info(f" - Inflection AI available: {init_status['inflection_ai_available']}")
482
  logging.info(f"API Keys Valid: {init_status['api_keys']}")
483
  logging.info(f"Fully Initialized: {init_status['fully_initialized']}")
 
486
  # CRITICAL: Only mark as initialized if ALL components are ready
487
  # If any component fails, EXIT the application immediately
488
  if init_status['fully_initialized']:
489
+ is_initialized = True
490
  logging.info("✓ Galatea AI system fully initialized and ready")
491
+ logging.info(f"Emotions initialized: {galatea_ai.emotional_state}")
492
  else:
493
  logging.error("=" * 60)
494
  logging.error("❌ INITIALIZATION FAILED - EXITING APPLICATION")
 
525
  # Add error handling for template rendering
526
  try:
527
  # Start component initialization if not already started
528
+ if not is_initialized and not initializing and not missing_deepseek_key:
529
  Thread(target=initialize_components, daemon=True).start()
530
 
531
  return render_template('index.html')
 
544
  }), 503 # Service Unavailable
545
 
546
  # Check if API key is missing
547
+ if missing_deepseek_key:
548
  return jsonify({
549
+ 'error': 'DEEPSEEK_API_KEY is missing. Chat is unavailable.',
550
+ 'status': 'missing_deepseek_key',
551
  'is_initialized': False
552
  }), 503
553
 
 
710
  # Track avatar updates with timestamp
711
  last_avatar_update = time.time()
712
 
713
+ def fill_quantum_queue():
714
+ """Asynchronously fill the quantum numbers queue with 100 numbers"""
715
+ global quantum_filling, quantum_numbers_queue
716
+
717
+ with quantum_queue_lock:
718
+ if quantum_filling:
719
+ return # Already filling
720
+ quantum_filling = True
721
+
722
+ def _fill_queue():
723
+ global quantum_filling
724
+ try:
725
+ quantum_api_key = os.getenv("ANU_QUANTUM_API_KEY")
726
+ if not quantum_api_key:
727
+ logging.debug("[Quantum Queue] No API key, using pseudo-random")
728
+ import random
729
+ with quantum_queue_lock:
730
+ while len(quantum_numbers_queue) < 100:
731
+ quantum_numbers_queue.append(random.random())
732
+ return
733
+
734
+ from config import MODEL_CONFIG
735
+ quantum_config = MODEL_CONFIG.get('quantum', {}) if MODEL_CONFIG else {}
736
+ api_endpoint = quantum_config.get('api_endpoint', 'https://api.quantumnumbers.anu.edu.au')
737
+
738
+ headers = {"x-api-key": quantum_api_key}
739
+ params = {"length": 1, "type": "uint8"}
740
+
741
+ numbers_fetched = 0
742
+ with quantum_queue_lock:
743
+ current_size = len(quantum_numbers_queue)
744
+
745
+ while numbers_fetched < 100:
746
+ try:
747
+ response = requests.get(api_endpoint, headers=headers, params=params, timeout=5)
748
+ if response.status_code == 200:
749
+ result = response.json()
750
+ if result.get('success') and 'data' in result and len(result['data']) > 0:
751
+ normalized = result['data'][0] / 255.0
752
+ with quantum_queue_lock:
753
+ quantum_numbers_queue.append(normalized)
754
+ numbers_fetched += 1
755
+ else:
756
+ # Fallback to pseudo-random
757
+ import random
758
+ with quantum_queue_lock:
759
+ quantum_numbers_queue.append(random.random())
760
+ numbers_fetched += 1
761
+ elif response.status_code == 429:
762
+ # Rate limited - use pseudo-random
763
+ import random
764
+ with quantum_queue_lock:
765
+ quantum_numbers_queue.append(random.random())
766
+ numbers_fetched += 1
767
+ else:
768
+ # Error - use pseudo-random
769
+ import random
770
+ with quantum_queue_lock:
771
+ quantum_numbers_queue.append(random.random())
772
+ numbers_fetched += 1
773
+ except Exception as e:
774
+ logging.debug(f"[Quantum Queue] Error fetching number: {e}, using pseudo-random")
775
+ import random
776
+ with quantum_queue_lock:
777
+ quantum_numbers_queue.append(random.random())
778
+ numbers_fetched += 1
779
+
780
+ # Small delay to avoid rate limits
781
+ time.sleep(0.1)
782
+
783
+ logging.info(f"[Quantum Queue] Filled queue with {numbers_fetched} numbers")
784
+ except Exception as e:
785
+ logging.error(f"[Quantum Queue] Error filling queue: {e}")
786
+ finally:
787
+ with quantum_queue_lock:
788
+ quantum_filling = False
789
+
790
+ # Start filling in background thread
791
+ Thread(target=_fill_queue, daemon=True).start()
792
+
793
+ def pop_quantum_number():
794
+ """Pop a quantum number from the queue, or return pseudo-random if empty"""
795
+ global quantum_numbers_queue
796
+
797
+ with quantum_queue_lock:
798
+ if len(quantum_numbers_queue) > 0:
799
+ return quantum_numbers_queue.popleft()
800
+ else:
801
+ # Queue is empty, use pseudo-random
802
+ import random
803
+ return random.random()
804
+
805
  @app.route('/api/avatar')
806
  def get_avatar():
807
  """Endpoint to get the current avatar shape and state with enhanced responsiveness"""
808
+ global last_avatar_update, quantum_numbers_queue, quantum_filling
809
 
810
  if not is_initialized:
811
  return jsonify({
 
816
  })
817
 
818
  try:
819
+ # Check if quantum queue is empty, fill it asynchronously if needed
820
+ with quantum_queue_lock:
821
+ queue_empty = len(quantum_numbers_queue) == 0
822
+
823
+ if queue_empty and not quantum_filling:
824
+ fill_quantum_queue()
825
+
826
+ # Pop one quantum number to update emotions
827
+ quantum_num = pop_quantum_number()
828
+
829
  avatar_shape = avatar_engine.avatar_model if avatar_engine else 'Circle'
830
 
831
  # Update timestamp when the avatar changes (you would track this in AvatarEngine normally)
 
841
 
842
  # Force avatar update based on emotions if available
843
  if avatar_engine and galatea_ai:
844
+ # Apply quantum influence to emotions
845
+ emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
846
+ # Use quantum number to influence a random emotion
847
+ import random
848
+ emotion_index = int(quantum_num * len(emotions)) % len(emotions)
849
+ selected_emotion = emotions[emotion_index]
850
+
851
+ # Apply subtle quantum influence (-0.05 to +0.05)
852
+ influence = (quantum_num - 0.5) * 0.1
853
+ current_value = galatea_ai.emotional_state[selected_emotion]
854
+ new_value = max(0.05, min(1.0, current_value + influence))
855
+ galatea_ai.emotional_state[selected_emotion] = new_value
856
+
857
  # If we have sentiment data, incorporate it into emotional state
858
  if sentiment_data:
859
  # Update emotional state based on sentiment (enhanced mapping)
 
865
  # Amplify anger emotion when detected
866
  galatea_ai.emotional_state["anger"] = max(galatea_ai.emotional_state["anger"], 0.8)
867
 
868
+ # Save emotional state to JSON
869
+ if hasattr(galatea_ai, 'emotional_agent'):
870
+ galatea_ai.emotional_agent._save_to_json()
871
+
872
  avatar_engine.update_avatar(galatea_ai.emotional_state)
873
  avatar_shape = avatar_engine.avatar_model
874
  last_avatar_update = current_timestamp
 
894
  """Simple health check endpoint to verify the server is running"""
895
  return jsonify({
896
  'status': 'ok',
897
+ 'deepseek_available': hasattr(galatea_ai, 'deepseek_available') and galatea_ai.deepseek_available if galatea_ai else False,
898
  'is_initialized': is_initialized,
899
+ 'missing_deepseek_key': missing_deepseek_key
900
  })
901
 
902
  @app.route('/api/availability')
903
  def availability():
904
  """Report overall availability state to the frontend"""
905
+ if missing_deepseek_key:
906
  return jsonify({
907
  'available': False,
908
+ 'status': 'missing_deepseek_key',
909
  'is_initialized': False,
910
  'initializing': False,
911
+ 'missing_deepseek_key': True,
912
  'error_page': url_for('error_page')
913
  })
914
 
 
918
  'status': 'initializing',
919
  'is_initialized': is_initialized,
920
  'initializing': initializing,
921
+ 'missing_deepseek_key': False
922
  })
923
 
924
  return jsonify({
 
926
  'status': 'ready',
927
  'is_initialized': True,
928
  'initializing': False,
929
+ 'missing_deepseek_key': False
930
  })
931
 
932
  @app.route('/api/is_initialized')
933
  def is_initialized_endpoint():
934
  """Lightweight endpoint for polling initialization progress"""
935
  # Determine current initialization state
936
+ if missing_deepseek_key:
937
  return jsonify({
938
  'is_initialized': False,
939
  'initializing': False,
940
+ 'missing_deepseek_key': True,
941
  'error_page': url_for('error_page'),
942
  'status': 'missing_api_key'
943
  })
 
947
  return jsonify({
948
  'is_initialized': False,
949
  'initializing': True,
950
+ 'missing_deepseek_key': False,
951
  'status': 'initializing_components',
952
  'message': 'Initializing AI components...'
953
  })
 
957
  return jsonify({
958
  'is_initialized': True,
959
  'initializing': False,
960
+ 'missing_deepseek_key': False,
961
  'status': 'ready',
962
  'message': 'System ready'
963
  })
 
966
  return jsonify({
967
  'is_initialized': False,
968
  'initializing': True,
969
+ 'missing_deepseek_key': False,
970
  'status': 'waiting',
971
  'message': 'Waiting for initialization...'
972
  })
 
979
  'initializing': initializing,
980
  'emotions': galatea_ai.emotional_state if galatea_ai else {'joy': 0.2, 'sadness': 0.2, 'anger': 0.2, 'fear': 0.2, 'curiosity': 0.2},
981
  'avatar_shape': avatar_engine.avatar_model if avatar_engine and is_initialized else 'Circle',
982
+ 'missing_deepseek_key': missing_deepseek_key
983
  })
984
 
985
  @app.route('/error')
986
  def error_page():
987
  """Render an informative error page when the app is unavailable"""
988
+ return render_template('error.html', missing_deepseek_key=missing_deepseek_key)
989
 
990
  if __name__ == '__main__':
991
  print("Starting Galatea Web Interface...")
 
1030
  print("Application will exit")
1031
  print("=" * 70)
1032
  sys.exit(1)
1033
+
1034
  # Add debug logs for avatar shape changes
1035
  logging.info("Avatar system initialized with default shape.")
1036
 
 
1041
  logging.info("Frontend will poll /api/is_initialized for status")
1042
  print(f"\nFlask server starting on port {port}...")
1043
  print("Frontend will poll /api/is_initialized for status\n")
1044
+
1045
  # Bind to 0.0.0.0 for external access (required for Hugging Face Spaces)
1046
  app.run(host='0.0.0.0', port=port, debug=True)
galatea_ai.py CHANGED
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
  from config import MODEL_CONFIG
10
  from systems import MemorySystem
11
  from agents import (
12
- MemoryAgent, GeminiThinkingAgent, PiResponseAgent,
13
  EmotionalStateAgent, SentimentAgent
14
  )
15
 
@@ -51,7 +51,7 @@ class GalateaAI:
51
  # Initialize agents
52
  logging.info("Initializing agents...")
53
  self.memory_agent = MemoryAgent(self.memory_system, config=self.config)
54
- self.gemini_agent = GeminiThinkingAgent(config=self.config)
55
  self.pi_agent = PiResponseAgent(config=self.config)
56
  self.emotional_agent = EmotionalStateAgent(config=self.config)
57
  self.sentiment_agent = SentimentAgent(config=self.config)
@@ -59,8 +59,8 @@ class GalateaAI:
59
  # Track initialization status
60
  self.memory_system_ready = self.memory_agent.is_ready()
61
  self.sentiment_analyzer_ready = self.sentiment_agent.is_ready()
62
- self.models_ready = self.gemini_agent.is_ready() or self.pi_agent.is_ready()
63
- self.api_keys_valid = self.gemini_agent.is_ready() or self.pi_agent.is_ready()
64
 
65
  # CRITICAL: Verify all critical systems are ready, raise exception if not
66
  if not self.memory_system_ready:
@@ -68,16 +68,16 @@ class GalateaAI:
68
  if not self.sentiment_analyzer_ready:
69
  raise RuntimeError("Sentiment analyzer failed to initialize - application cannot continue")
70
  if not self.models_ready:
71
- raise RuntimeError("No AI models available (Gemini or Pi-3.1) - application cannot continue")
72
  if not self.api_keys_valid:
73
  raise RuntimeError("API keys are invalid or missing - application cannot continue")
74
  if not self.pi_agent.is_ready():
75
  raise RuntimeError("Pi-3.1 (PHI) model is not available - application cannot continue")
76
- if not self.gemini_agent.is_ready():
77
- raise RuntimeError("Gemini model is not available - application cannot continue")
78
 
79
  # Legacy compatibility
80
- self.gemini_available = self.gemini_agent.is_ready()
81
  self.inflection_ai_available = self.pi_agent.is_ready()
82
  self.quantum_random_available = self.emotional_agent.quantum_random_available
83
 
@@ -108,7 +108,7 @@ class GalateaAI:
108
  "sentiment_analyzer": self.sentiment_analyzer_ready,
109
  "models": self.models_ready,
110
  "api_keys": self.api_keys_valid,
111
- "gemini_available": self.gemini_agent.is_ready() if hasattr(self, 'gemini_agent') else False,
112
  "inflection_ai_available": self.pi_agent.is_ready() if hasattr(self, 'pi_agent') else False,
113
  "azure_text_analytics_available": self.sentiment_agent.azure_agent.is_ready() if hasattr(self, 'sentiment_agent') else False,
114
  "fully_initialized": self.is_fully_initialized()
@@ -189,16 +189,16 @@ class GalateaAI:
189
  # Step 4: Retrieve memories
190
  retrieved_memories = self.memory_agent.retrieve_memories(user_input)
191
 
192
- # Step 5: Chain workflow: PHI(GEMINI(User inputs, read with past memory), emotionalstate)
193
- # Step 5a: GEMINI(User inputs, read with past memory)
194
- thinking_context = self.gemini_agent.think(
195
  user_input,
196
  current_emotional_state,
197
  self.conversation_history,
198
  retrieved_memories=retrieved_memories
199
  )
200
 
201
- # Step 5b: PHI(GEMINI result, emotionalstate)
202
  response = self.pi_agent.respond(
203
  user_input,
204
  current_emotional_state,
 
9
  from config import MODEL_CONFIG
10
  from systems import MemorySystem
11
  from agents import (
12
+ MemoryAgent, DeepSeekThinkingAgent, PiResponseAgent,
13
  EmotionalStateAgent, SentimentAgent
14
  )
15
 
 
51
  # Initialize agents
52
  logging.info("Initializing agents...")
53
  self.memory_agent = MemoryAgent(self.memory_system, config=self.config)
54
+ self.deepseek_agent = DeepSeekThinkingAgent(config=self.config)
55
  self.pi_agent = PiResponseAgent(config=self.config)
56
  self.emotional_agent = EmotionalStateAgent(config=self.config)
57
  self.sentiment_agent = SentimentAgent(config=self.config)
 
59
  # Track initialization status
60
  self.memory_system_ready = self.memory_agent.is_ready()
61
  self.sentiment_analyzer_ready = self.sentiment_agent.is_ready()
62
+ self.models_ready = self.deepseek_agent.is_ready() or self.pi_agent.is_ready()
63
+ self.api_keys_valid = self.deepseek_agent.is_ready() or self.pi_agent.is_ready()
64
 
65
  # CRITICAL: Verify all critical systems are ready, raise exception if not
66
  if not self.memory_system_ready:
 
68
  if not self.sentiment_analyzer_ready:
69
  raise RuntimeError("Sentiment analyzer failed to initialize - application cannot continue")
70
  if not self.models_ready:
71
+ raise RuntimeError("No AI models available (DeepSeek or Pi-3.1) - application cannot continue")
72
  if not self.api_keys_valid:
73
  raise RuntimeError("API keys are invalid or missing - application cannot continue")
74
  if not self.pi_agent.is_ready():
75
  raise RuntimeError("Pi-3.1 (PHI) model is not available - application cannot continue")
76
+ if not self.deepseek_agent.is_ready():
77
+ raise RuntimeError("DeepSeek model is not available - application cannot continue")
78
 
79
  # Legacy compatibility
80
+ self.deepseek_available = self.deepseek_agent.is_ready()
81
  self.inflection_ai_available = self.pi_agent.is_ready()
82
  self.quantum_random_available = self.emotional_agent.quantum_random_available
83
 
 
108
  "sentiment_analyzer": self.sentiment_analyzer_ready,
109
  "models": self.models_ready,
110
  "api_keys": self.api_keys_valid,
111
+ "deepseek_available": self.deepseek_agent.is_ready() if hasattr(self, 'deepseek_agent') else False,
112
  "inflection_ai_available": self.pi_agent.is_ready() if hasattr(self, 'pi_agent') else False,
113
  "azure_text_analytics_available": self.sentiment_agent.azure_agent.is_ready() if hasattr(self, 'sentiment_agent') else False,
114
  "fully_initialized": self.is_fully_initialized()
 
189
  # Step 4: Retrieve memories
190
  retrieved_memories = self.memory_agent.retrieve_memories(user_input)
191
 
192
+ # Step 5: Chain workflow: PHI(DEEPSEEK(User inputs, read with past memory), emotionalstate)
193
+ # Step 5a: DEEPSEEK(User inputs, read with past memory)
194
+ thinking_context = self.deepseek_agent.think(
195
  user_input,
196
  current_emotional_state,
197
  self.conversation_history,
198
  retrieved_memories=retrieved_memories
199
  )
200
 
201
+ # Step 5b: PHI(DEEPSEEK result, emotionalstate)
202
  response = self.pi_agent.respond(
203
  user_input,
204
  current_emotional_state,
llm_wrapper.py CHANGED
@@ -1,4 +1,4 @@
1
- """Custom LLM Wrapper - Direct API calls using requests (no LiteLLM)"""
2
  import os
3
  import sys
4
  import logging
@@ -8,28 +8,35 @@ import requests # type: ignore[import-untyped]
8
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
  from config import MODEL_CONFIG
10
 
 
 
 
 
 
 
 
11
  class LLMWrapper:
12
- """Custom LLM wrapper for Gemini and Inflection AI using direct API calls"""
13
 
14
- def __init__(self, gemini_model=None, inflection_model=None, config=None):
15
  """
16
  Initialize LLM Wrapper with models and configuration
17
 
18
  Args:
19
- gemini_model: Gemini model name (e.g., 'gemini-2.0-flash-exp')
20
  inflection_model: Inflection AI model name (e.g., 'Pi-3.1')
21
  config: Configuration dict (optional, will load from MODEL_CONFIG if not provided)
22
  """
23
  self.config = config or MODEL_CONFIG or {}
24
- self.gemini_api_key = os.getenv("GEMINI_API_KEY")
25
  self.inflection_ai_api_key = os.getenv("INFLECTION_AI_API_KEY")
26
 
27
  # Set models from parameters or config
28
- if gemini_model:
29
- self.gemini_model = gemini_model
30
  else:
31
- gemini_config = self.config.get('gemini', {}) if self.config else {}
32
- self.gemini_model = gemini_config.get('model', 'gemini-2.0-flash-exp')
33
 
34
  if inflection_model:
35
  self.inflection_model = inflection_model
@@ -37,15 +44,20 @@ class LLMWrapper:
37
  inflection_config = self.config.get('inflection_ai', {}) if self.config else {}
38
  self.inflection_model = inflection_config.get('model', 'Pi-3.1')
39
 
40
- # Remove 'gemini/' prefix if present
41
- if self.gemini_model.startswith('gemini/'):
42
- self.gemini_model = self.gemini_model.replace('gemini/', '')
 
 
 
 
 
43
 
44
- logging.info(f"[LLMWrapper] Initialized with Gemini model: {self.gemini_model}, Inflection model: {self.inflection_model}")
45
 
46
- def call_gemini(self, messages, temperature=0.7, max_tokens=1024):
47
  """
48
- Call Gemini API directly using requests
49
 
50
  Args:
51
  messages: List of message dicts with 'role' and 'content'
@@ -55,97 +67,53 @@ class LLMWrapper:
55
  Returns:
56
  Response text or None if failed
57
  """
58
- if not self.gemini_api_key:
59
- logging.error("[LLMWrapper] GEMINI_API_KEY not found")
60
  return None
61
 
62
- # Use the model set during initialization
63
- model = self.gemini_model
64
-
65
- # Gemini API endpoint
66
- url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
67
-
68
- headers = {
69
- "Content-Type": "application/json",
70
- "X-goog-api-key": self.gemini_api_key
71
- }
72
-
73
- # Convert messages to Gemini format
74
- contents = []
75
- system_instruction = None
76
-
77
- for msg in messages:
78
- role = msg.get('role', 'user')
79
- content = msg.get('content', '')
80
-
81
- if role == 'system':
82
- system_instruction = content
83
- elif role == 'user':
84
- contents.append({
85
- "role": "user",
86
- "parts": [{"text": content}]
87
- })
88
- elif role == 'assistant':
89
- contents.append({
90
- "role": "model",
91
- "parts": [{"text": content}]
92
- })
93
 
94
- # Build request payload
95
- payload = {
96
- "contents": contents,
97
- "generationConfig": {
98
- "temperature": temperature,
99
- "maxOutputTokens": max_tokens
100
- }
101
- }
102
 
103
- # Add system instruction if present
104
- if system_instruction:
105
- payload["systemInstruction"] = {
106
- "parts": [{"text": system_instruction}]
107
- }
108
 
109
  try:
110
- logging.info(f"[LLMWrapper] Calling Gemini API: {model}")
111
- response = requests.post(url, headers=headers, json=payload, timeout=30)
 
 
 
 
 
 
112
 
113
- if response.status_code == 200:
114
- result = response.json()
115
-
116
- # Extract text from Gemini response
117
- if 'candidates' in result and len(result['candidates']) > 0:
118
- candidate = result['candidates'][0]
119
- if 'content' in candidate and 'parts' in candidate['content']:
120
- parts = candidate['content']['parts']
121
- if len(parts) > 0 and 'text' in parts[0]:
122
- text = parts[0]['text']
123
- logging.info("[LLMWrapper] ✓ Gemini response received")
124
- return text.strip()
125
-
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
 
150
  def call_inflection_ai(self, context_parts):
151
  """
 
1
+ """Custom LLM Wrapper - Direct API calls using requests and OpenAI SDK"""
2
  import os
3
  import sys
4
  import logging
 
8
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
  from config import MODEL_CONFIG
10
 
11
+ try:
12
+ from openai import OpenAI
13
+ OPENAI_AVAILABLE = True
14
+ except ImportError:
15
+ OPENAI_AVAILABLE = False
16
+ logging.warning("[LLMWrapper] OpenAI SDK not available. DeepSeek API calls will fail.")
17
+
18
  class LLMWrapper:
19
+ """Custom LLM wrapper for DeepSeek and Inflection AI using direct API calls"""
20
 
21
+ def __init__(self, deepseek_model=None, inflection_model=None, config=None):
22
  """
23
  Initialize LLM Wrapper with models and configuration
24
 
25
  Args:
26
+ deepseek_model: DeepSeek model name (e.g., 'deepseek-reasoner')
27
  inflection_model: Inflection AI model name (e.g., 'Pi-3.1')
28
  config: Configuration dict (optional, will load from MODEL_CONFIG if not provided)
29
  """
30
  self.config = config or MODEL_CONFIG or {}
31
+ self.deepseek_api_key = os.getenv("DEEPSEEK_API_KEY")
32
  self.inflection_ai_api_key = os.getenv("INFLECTION_AI_API_KEY")
33
 
34
  # Set models from parameters or config
35
+ if deepseek_model:
36
+ self.deepseek_model = deepseek_model
37
  else:
38
+ deepseek_config = self.config.get('deepseek', {}) if self.config else {}
39
+ self.deepseek_model = deepseek_config.get('model', 'deepseek-reasoner')
40
 
41
  if inflection_model:
42
  self.inflection_model = inflection_model
 
44
  inflection_config = self.config.get('inflection_ai', {}) if self.config else {}
45
  self.inflection_model = inflection_config.get('model', 'Pi-3.1')
46
 
47
+ # Initialize OpenAI client for DeepSeek
48
+ if OPENAI_AVAILABLE and self.deepseek_api_key:
49
+ self.deepseek_client = OpenAI(
50
+ api_key=self.deepseek_api_key,
51
+ base_url="https://api.deepseek.com"
52
+ )
53
+ else:
54
+ self.deepseek_client = None
55
 
56
+ logging.info(f"[LLMWrapper] Initialized with DeepSeek model: {self.deepseek_model}, Inflection model: {self.inflection_model}")
57
 
58
+ def call_deepseek(self, messages, temperature=0.7, max_tokens=1024):
59
  """
60
+ Call DeepSeek API using OpenAI SDK
61
 
62
  Args:
63
  messages: List of message dicts with 'role' and 'content'
 
67
  Returns:
68
  Response text or None if failed
69
  """
70
+ if not self.deepseek_api_key:
71
+ logging.error("[LLMWrapper] DEEPSEEK_API_KEY not found")
72
  return None
73
 
74
+ if not OPENAI_AVAILABLE:
75
+ logging.error("[LLMWrapper] OpenAI SDK not available. Install with: pip install openai")
76
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ if not self.deepseek_client:
79
+ logging.error("[LLMWrapper] DeepSeek client not initialized")
80
+ return None
 
 
 
 
 
81
 
82
+ # Use the model set during initialization
83
+ model = self.deepseek_model
 
 
 
84
 
85
  try:
86
+ logging.info(f"[LLMWrapper] Calling DeepSeek API: {model}")
87
+ response = self.deepseek_client.chat.completions.create(
88
+ model=model,
89
+ messages=messages,
90
+ temperature=temperature,
91
+ max_tokens=max_tokens,
92
+ stream=False
93
+ )
94
 
95
+ if response and response.choices and len(response.choices) > 0:
96
+ text = response.choices[0].message.content
97
+ if text:
98
+ logging.info("[LLMWrapper] DeepSeek response received")
99
+ return text.strip()
100
+
101
+ logging.error(f"[LLMWrapper] Unexpected DeepSeek response format: {response}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  return None
103
+
104
  except Exception as e:
105
+ # Check if it's an API error with status code
106
+ error_msg = str(e)
107
+ status_code = getattr(e, 'status_code', None)
108
+ response_text = getattr(e, 'response_text', error_msg)
109
+
110
+ # Create exception with status code info for validation to catch
111
+ api_error = Exception(f"DeepSeek API error: {error_msg}")
112
+ if status_code:
113
+ api_error.status_code = status_code
114
+ api_error.response_text = response_text
115
+ logging.error(f"[LLMWrapper] Error calling DeepSeek API: {e}")
116
+ raise api_error
117
 
118
  def call_inflection_ai(self, context_parts):
119
  """
models.yaml CHANGED
@@ -1,17 +1,17 @@
1
  # Galatea AI Model Configuration
2
  # This file contains all model settings and hyperparameters
3
 
4
- # Gemini Agent Configuration (Thinking/Analysis)
5
- gemini:
6
  # Single model to use for thinking/analysis
7
- model: "gemini-2.0-flash-exp"
8
 
9
  # Hyperparameters
10
  temperature: 0.5 # Lower temperature for more focused thinking
11
  max_tokens: 200
12
 
13
- # API endpoint (automatically constructed)
14
- api_endpoint: "https://generativelanguage.googleapis.com/v1beta/models"
15
 
16
  # Pi/Phi Agent Configuration (Response Generation)
17
  inflection_ai:
 
1
  # Galatea AI Model Configuration
2
  # This file contains all model settings and hyperparameters
3
 
4
+ # DeepSeek Agent Configuration (Thinking/Analysis)
5
+ deepseek:
6
  # Single model to use for thinking/analysis
7
+ model: "deepseek-reasoner"
8
 
9
  # Hyperparameters
10
  temperature: 0.5 # Lower temperature for more focused thinking
11
  max_tokens: 200
12
 
13
+ # API endpoint (using OpenAI SDK)
14
+ api_endpoint: "https://api.deepseek.com"
15
 
16
  # Pi/Phi Agent Configuration (Response Generation)
17
  inflection_ai:
requirements.txt CHANGED
@@ -7,3 +7,4 @@ torch>=2.2.0
7
  numpy<2.0.0
8
  requests==2.31.0
9
  pyyaml==6.0.1
 
 
7
  numpy<2.0.0
8
  requests==2.31.0
9
  pyyaml==6.0.1
10
+ openai>=1.0.0