Spaces:
Sleeping
Sleeping
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- agents/__init__.py +2 -2
- agents/gemini_agent.py +26 -26
- app.py +218 -109
- galatea_ai.py +13 -13
- llm_wrapper.py +65 -97
- models.yaml +5 -5
- 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
|
| 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 |
-
'
|
| 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 |
-
"""
|
| 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
|
| 12 |
-
"""Agent responsible for thinking and analysis using
|
| 13 |
|
| 14 |
def __init__(self, config=None):
|
| 15 |
self.config = config or MODEL_CONFIG or {}
|
| 16 |
-
self.
|
| 17 |
|
| 18 |
# Get model from config
|
| 19 |
-
|
| 20 |
-
|
| 21 |
|
| 22 |
# Initialize LLM wrapper with the model
|
| 23 |
-
self.llm_wrapper = LLMWrapper(
|
| 24 |
self._initialize()
|
| 25 |
|
| 26 |
def _initialize(self):
|
| 27 |
-
"""Initialize
|
| 28 |
-
|
| 29 |
-
if
|
| 30 |
-
self.
|
| 31 |
-
logging.info("[
|
| 32 |
else:
|
| 33 |
-
logging.warning("[
|
| 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.
|
| 38 |
-
logging.warning("[
|
| 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("[
|
| 82 |
|
| 83 |
# Get hyperparameters from config
|
| 84 |
-
|
| 85 |
-
temperature =
|
| 86 |
-
max_tokens =
|
| 87 |
|
| 88 |
-
# Call
|
| 89 |
try:
|
| 90 |
-
thinking_result = self.llm_wrapper.
|
| 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("[
|
| 98 |
return thinking_result
|
| 99 |
else:
|
| 100 |
-
logging.error("[
|
| 101 |
return None
|
| 102 |
except Exception as e:
|
| 103 |
-
logging.error(f"[
|
| 104 |
return None
|
| 105 |
|
| 106 |
except Exception as e:
|
| 107 |
-
logging.error(f"[
|
| 108 |
return None
|
| 109 |
|
| 110 |
def is_ready(self):
|
| 111 |
"""Check if agent is ready"""
|
| 112 |
-
return self.
|
| 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 |
-
|
| 24 |
-
|
| 25 |
-
if
|
| 26 |
-
logging.info(f"✓
|
| 27 |
-
logging.info(f" First 10 chars: {
|
| 28 |
else:
|
| 29 |
-
|
| 30 |
logging.error("=" * 60)
|
| 31 |
-
logging.error("✗
|
| 32 |
logging.error("=" * 60)
|
| 33 |
logging.error("")
|
| 34 |
-
logging.error("The
|
| 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:
|
| 40 |
-
logging.error(" 4. Value: [Your
|
| 41 |
-
logging.error(" 5. Get a key from: https://
|
| 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 '
|
| 48 |
-
str([k for k in os.environ.keys() if '
|
| 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 |
-
|
| 82 |
max_init_retries = 3
|
| 83 |
current_init_retry = 0
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
# Check for required environment variables
|
| 86 |
-
required_env_vars = ['
|
| 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
|
| 95 |
-
"""Initialize
|
| 96 |
-
global
|
| 97 |
|
| 98 |
if not galatea_ai:
|
| 99 |
-
logging.warning("Cannot initialize
|
| 100 |
return False
|
| 101 |
|
| 102 |
-
if
|
| 103 |
-
logging.error("Cannot initialize
|
| 104 |
return False
|
| 105 |
|
| 106 |
try:
|
| 107 |
-
# Check for
|
| 108 |
-
if not os.environ.get('
|
| 109 |
-
logging.error("
|
| 110 |
return False
|
| 111 |
|
| 112 |
-
# Check if
|
| 113 |
-
|
| 114 |
|
| 115 |
-
if
|
| 116 |
-
|
| 117 |
-
logging.info("
|
| 118 |
return True
|
| 119 |
else:
|
| 120 |
-
logging.error("Failed to initialize
|
| 121 |
return False
|
| 122 |
except Exception as e:
|
| 123 |
-
logging.error(f"Error initializing
|
| 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 |
-
'
|
| 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
|
| 199 |
-
"""Validate
|
| 200 |
try:
|
| 201 |
-
logging.info("🔄 [
|
| 202 |
-
print("🔄 [
|
| 203 |
-
api_key = os.getenv("
|
| 204 |
if not api_key:
|
| 205 |
-
logging.warning("⚠ [
|
| 206 |
-
print("⚠ [
|
| 207 |
-
init_status['
|
| 208 |
return False
|
| 209 |
try:
|
| 210 |
from llm_wrapper import LLMWrapper
|
| 211 |
from config import MODEL_CONFIG
|
| 212 |
|
| 213 |
# Get model from config
|
| 214 |
-
|
| 215 |
-
|
| 216 |
|
| 217 |
-
wrapper = LLMWrapper(
|
| 218 |
-
response = wrapper.
|
| 219 |
messages=[{"role": "user", "content": "test"}],
|
| 220 |
max_tokens=5
|
| 221 |
)
|
| 222 |
if response:
|
| 223 |
-
logging.info("✓ [
|
| 224 |
-
print("✓ [
|
| 225 |
-
init_status['
|
| 226 |
return True
|
| 227 |
else:
|
| 228 |
-
logging.warning("⚠ [
|
| 229 |
-
print("⚠ [
|
| 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"✗ [
|
| 240 |
-
print(f"✗ [
|
| 241 |
-
init_status['
|
| 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("ℹ️ [
|
| 246 |
-
print("ℹ️ [
|
| 247 |
-
init_status['
|
| 248 |
-
init_status['
|
| 249 |
return True # Don't fail initialization - key is valid
|
| 250 |
else:
|
| 251 |
-
logging.warning(f"⚠ [
|
| 252 |
-
print("⚠ [
|
| 253 |
-
init_status['
|
| 254 |
return True
|
| 255 |
except Exception as e:
|
| 256 |
-
error_msg = f"
|
| 257 |
-
logging.error(f"✗ [
|
| 258 |
-
print(f"✗ [
|
| 259 |
-
init_status['
|
| 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 |
-
("
|
| 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', '
|
| 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,
|
| 441 |
|
| 442 |
if initializing or is_initialized:
|
| 443 |
return
|
| 444 |
|
| 445 |
-
if
|
| 446 |
-
logging.error("Initialization aborted:
|
| 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" -
|
| 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 |
-
|
| 500 |
logging.info("✓ Galatea AI system fully initialized and ready")
|
| 501 |
-
|
| 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
|
| 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
|
| 558 |
return jsonify({
|
| 559 |
-
'error': '
|
| 560 |
-
'status': '
|
| 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 |
-
'
|
| 789 |
'is_initialized': is_initialized,
|
| 790 |
-
'
|
| 791 |
})
|
| 792 |
|
| 793 |
@app.route('/api/availability')
|
| 794 |
def availability():
|
| 795 |
"""Report overall availability state to the frontend"""
|
| 796 |
-
if
|
| 797 |
return jsonify({
|
| 798 |
'available': False,
|
| 799 |
-
'status': '
|
| 800 |
'is_initialized': False,
|
| 801 |
'initializing': False,
|
| 802 |
-
'
|
| 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 |
-
'
|
| 813 |
})
|
| 814 |
|
| 815 |
return jsonify({
|
|
@@ -817,18 +926,18 @@ def availability():
|
|
| 817 |
'status': 'ready',
|
| 818 |
'is_initialized': True,
|
| 819 |
'initializing': False,
|
| 820 |
-
'
|
| 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
|
| 828 |
return jsonify({
|
| 829 |
'is_initialized': False,
|
| 830 |
'initializing': False,
|
| 831 |
-
'
|
| 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 |
-
'
|
| 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 |
-
'
|
| 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 |
-
'
|
| 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 |
-
'
|
| 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',
|
| 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,
|
| 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.
|
| 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.
|
| 63 |
-
self.api_keys_valid = self.
|
| 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 (
|
| 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.
|
| 77 |
-
raise RuntimeError("
|
| 78 |
|
| 79 |
# Legacy compatibility
|
| 80 |
-
self.
|
| 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 |
-
"
|
| 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(
|
| 193 |
-
# Step 5a:
|
| 194 |
-
thinking_context = self.
|
| 195 |
user_input,
|
| 196 |
current_emotional_state,
|
| 197 |
self.conversation_history,
|
| 198 |
retrieved_memories=retrieved_memories
|
| 199 |
)
|
| 200 |
|
| 201 |
-
# Step 5b: PHI(
|
| 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
|
| 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
|
| 13 |
|
| 14 |
-
def __init__(self,
|
| 15 |
"""
|
| 16 |
Initialize LLM Wrapper with models and configuration
|
| 17 |
|
| 18 |
Args:
|
| 19 |
-
|
| 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.
|
| 25 |
self.inflection_ai_api_key = os.getenv("INFLECTION_AI_API_KEY")
|
| 26 |
|
| 27 |
# Set models from parameters or config
|
| 28 |
-
if
|
| 29 |
-
self.
|
| 30 |
else:
|
| 31 |
-
|
| 32 |
-
self.
|
| 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 |
-
#
|
| 41 |
-
if self.
|
| 42 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
logging.info(f"[LLMWrapper] Initialized with
|
| 45 |
|
| 46 |
-
def
|
| 47 |
"""
|
| 48 |
-
Call
|
| 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.
|
| 59 |
-
logging.error("[LLMWrapper]
|
| 60 |
return None
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 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 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
"generationConfig": {
|
| 98 |
-
"temperature": temperature,
|
| 99 |
-
"maxOutputTokens": max_tokens
|
| 100 |
-
}
|
| 101 |
-
}
|
| 102 |
|
| 103 |
-
#
|
| 104 |
-
|
| 105 |
-
payload["systemInstruction"] = {
|
| 106 |
-
"parts": [{"text": system_instruction}]
|
| 107 |
-
}
|
| 108 |
|
| 109 |
try:
|
| 110 |
-
logging.info(f"[LLMWrapper] Calling
|
| 111 |
-
response =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
if response.
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 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 |
-
#
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 5 |
-
|
| 6 |
# Single model to use for thinking/analysis
|
| 7 |
-
model: "
|
| 8 |
|
| 9 |
# Hyperparameters
|
| 10 |
temperature: 0.5 # Lower temperature for more focused thinking
|
| 11 |
max_tokens: 200
|
| 12 |
|
| 13 |
-
# API endpoint (
|
| 14 |
-
api_endpoint: "https://
|
| 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
|