Spaces:
Sleeping
Replace quantum random number API with regular random numbers
Browse files- Remove ANU Quantum API dependency and all related configuration
- Replace quantum_numbers_queue with random_numbers_queue in app.py
- Simplify quantum_emotion_service.py to use standard random.random()
- Remove quantum API validation from parallel initialization
- Update emotional_agent.py to use regular random numbers
- Clean up environment variables (remove ANU_QUANTUM_API_KEY)
- Update documentation (README, DOCKER.md, Endpoints.md)
- Remove quantum config from models.yaml and app.json
All emotion randomness now uses Python's built-in random module
instead of making external API calls. Functionality remains the same
but without the external dependency.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
- .claude/settings.local.json +9 -0
- .env.example +0 -5
- DOCKER.md +0 -1
- Endpoints.md +4 -6
- README.md +0 -4
- agents/emotional_agent.py +12 -51
- app.json +0 -4
- app.py +50 -141
- docker-compose.yml +0 -2
- galatea_ai.py +1 -2
- models.yaml +0 -12
- quantum_emotion_service.py +52 -98
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"permissions": {
|
| 3 |
+
"allow": [
|
| 4 |
+
"Bash(git add:*)"
|
| 5 |
+
],
|
| 6 |
+
"deny": [],
|
| 7 |
+
"ask": []
|
| 8 |
+
}
|
| 9 |
+
}
|
|
@@ -12,10 +12,5 @@ INFLECTION_AI_API_KEY=your_inflection_ai_api_key_here
|
|
| 12 |
AZURE_TEXT_ANALYTICS_KEY=your_azure_text_analytics_key_here
|
| 13 |
AZURE_TEXT_ANALYTICS_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
|
| 14 |
|
| 15 |
-
# ANU Quantum API Key (Optional - for quantum randomness)
|
| 16 |
-
# If not provided, the app will use pseudo-random numbers
|
| 17 |
-
# Get your key from: https://quantumnumbers.anu.edu.au/
|
| 18 |
-
ANU_QUANTUM_API_KEY=your_quantum_api_key_here
|
| 19 |
-
|
| 20 |
# Port (Optional - defaults to 7860 for Hugging Face Spaces)
|
| 21 |
PORT=7860
|
|
|
|
| 12 |
AZURE_TEXT_ANALYTICS_KEY=your_azure_text_analytics_key_here
|
| 13 |
AZURE_TEXT_ANALYTICS_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# Port (Optional - defaults to 7860 for Hugging Face Spaces)
|
| 16 |
PORT=7860
|
|
@@ -71,7 +71,6 @@ docker build -t codejediondockerhub/digital-galatea:latest .
|
|
| 71 |
|
| 72 |
- `AZURE_TEXT_ANALYTICS_KEY` - Azure Text Analytics key
|
| 73 |
- `AZURE_TEXT_ANALYTICS_ENDPOINT` - Azure endpoint URL
|
| 74 |
-
- `ANU_QUANTUM_API_KEY` - Quantum randomness API key
|
| 75 |
- `PORT` - Server port (default: 7860)
|
| 76 |
|
| 77 |
## Running the Container
|
|
|
|
| 71 |
|
| 72 |
- `AZURE_TEXT_ANALYTICS_KEY` - Azure Text Analytics key
|
| 73 |
- `AZURE_TEXT_ANALYTICS_ENDPOINT` - Azure endpoint URL
|
|
|
|
| 74 |
- `PORT` - Server port (default: 7860)
|
| 75 |
|
| 76 |
## Running the Container
|
|
@@ -250,13 +250,12 @@ Renders an informative error page when the app is unavailable.
|
|
| 250 |
- Sentiment Analyzer initialization
|
| 251 |
- Gemini API validation
|
| 252 |
- Inflection AI API validation
|
| 253 |
-
- Quantum API validation (optional)
|
| 254 |
|
| 255 |
2. **Component Initialization** - After parallel init completes:
|
| 256 |
- GalateaAI instance creation
|
| 257 |
- DialogueEngine initialization
|
| 258 |
- AvatarEngine initialization
|
| 259 |
-
-
|
| 260 |
|
| 261 |
3. **Flask Server Start** - Only starts if all critical components are ready
|
| 262 |
|
|
@@ -267,7 +266,7 @@ Renders an informative error page when the app is unavailable.
|
|
| 267 |
- **Inflection AI API**: Required
|
| 268 |
|
| 269 |
### Optional Components
|
| 270 |
-
-
|
| 271 |
|
| 272 |
---
|
| 273 |
|
|
@@ -275,7 +274,6 @@ Renders an informative error page when the app is unavailable.
|
|
| 275 |
|
| 276 |
### Rate Limits
|
| 277 |
- **Gemini API 429**: Treated as valid API key, initialization continues
|
| 278 |
-
- **Quantum API 429**: Falls back to pseudo-random, initialization continues
|
| 279 |
|
| 280 |
### Critical Failures
|
| 281 |
If any critical component fails to initialize, the application will:
|
|
@@ -289,7 +287,7 @@ If any critical component fails to initialize, the application will:
|
|
| 289 |
|
| 290 |
Emotions are stored in `emotions.json` and persist across restarts. The system uses:
|
| 291 |
- **Sentiment Analysis**: Updates emotions based on user message sentiment
|
| 292 |
-
- **
|
| 293 |
- **Decay**: Emotions slowly decay over time (3% per interaction)
|
| 294 |
|
| 295 |
### Emotion Values
|
|
@@ -329,7 +327,7 @@ The frontend polls `GET /api/avatar` every 1 second to get real-time emotion upd
|
|
| 329 |
- **Purpose**: Updates emotions even when the user is not chatting
|
| 330 |
- **Updates Include**:
|
| 331 |
- Sentiment-based emotion changes from user messages
|
| 332 |
-
-
|
| 333 |
- Natural emotion decay over time
|
| 334 |
|
| 335 |
### Emotion Bar Update Function
|
|
|
|
| 250 |
- Sentiment Analyzer initialization
|
| 251 |
- Gemini API validation
|
| 252 |
- Inflection AI API validation
|
|
|
|
| 253 |
|
| 254 |
2. **Component Initialization** - After parallel init completes:
|
| 255 |
- GalateaAI instance creation
|
| 256 |
- DialogueEngine initialization
|
| 257 |
- AvatarEngine initialization
|
| 258 |
+
- Random Emotion Service startup
|
| 259 |
|
| 260 |
3. **Flask Server Start** - Only starts if all critical components are ready
|
| 261 |
|
|
|
|
| 266 |
- **Inflection AI API**: Required
|
| 267 |
|
| 268 |
### Optional Components
|
| 269 |
+
- None (all randomness uses standard pseudo-random numbers)
|
| 270 |
|
| 271 |
---
|
| 272 |
|
|
|
|
| 274 |
|
| 275 |
### Rate Limits
|
| 276 |
- **Gemini API 429**: Treated as valid API key, initialization continues
|
|
|
|
| 277 |
|
| 278 |
### Critical Failures
|
| 279 |
If any critical component fails to initialize, the application will:
|
|
|
|
| 287 |
|
| 288 |
Emotions are stored in `emotions.json` and persist across restarts. The system uses:
|
| 289 |
- **Sentiment Analysis**: Updates emotions based on user message sentiment
|
| 290 |
+
- **Random Influence**: Background service updates emotions every 10 seconds using random numbers
|
| 291 |
- **Decay**: Emotions slowly decay over time (3% per interaction)
|
| 292 |
|
| 293 |
### Emotion Values
|
|
|
|
| 327 |
- **Purpose**: Updates emotions even when the user is not chatting
|
| 328 |
- **Updates Include**:
|
| 329 |
- Sentiment-based emotion changes from user messages
|
| 330 |
+
- Random-influenced emotion changes (every 10 seconds)
|
| 331 |
- Natural emotion decay over time
|
| 332 |
|
| 333 |
### Emotion Bar Update Function
|
|
@@ -74,7 +74,6 @@ These environment variables enhance functionality but are not required:
|
|
| 74 |
|----------|-------------|------------------|
|
| 75 |
| `AZURE_TEXT_ANALYTICS_KEY` | Azure Text Analytics API key for enhanced sentiment analysis | Falls back to NLTK VADER if not provided |
|
| 76 |
| `AZURE_TEXT_ANALYTICS_ENDPOINT` | Azure Text Analytics endpoint URL | Required if `AZURE_TEXT_ANALYTICS_KEY` is set |
|
| 77 |
-
| `ANU_QUANTUM_API_KEY` | ANU Quantum Random Numbers API key | Uses pseudo-random numbers if not provided |
|
| 78 |
| `PORT` | Port number for the Flask server | Defaults to `7860` |
|
| 79 |
|
| 80 |
### Environment Variable Examples
|
|
@@ -105,9 +104,6 @@ INFLECTION_AI_API_KEY=your-inflection-key
|
|
| 105 |
AZURE_TEXT_ANALYTICS_KEY=your-azure-key
|
| 106 |
AZURE_TEXT_ANALYTICS_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
|
| 107 |
|
| 108 |
-
# Optional - Quantum Randomness
|
| 109 |
-
ANU_QUANTUM_API_KEY=your-quantum-key
|
| 110 |
-
|
| 111 |
# Optional - Port Configuration
|
| 112 |
PORT=7860
|
| 113 |
```
|
|
|
|
| 74 |
|----------|-------------|------------------|
|
| 75 |
| `AZURE_TEXT_ANALYTICS_KEY` | Azure Text Analytics API key for enhanced sentiment analysis | Falls back to NLTK VADER if not provided |
|
| 76 |
| `AZURE_TEXT_ANALYTICS_ENDPOINT` | Azure Text Analytics endpoint URL | Required if `AZURE_TEXT_ANALYTICS_KEY` is set |
|
|
|
|
| 77 |
| `PORT` | Port number for the Flask server | Defaults to `7860` |
|
| 78 |
|
| 79 |
### Environment Variable Examples
|
|
|
|
| 104 |
AZURE_TEXT_ANALYTICS_KEY=your-azure-key
|
| 105 |
AZURE_TEXT_ANALYTICS_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
|
| 106 |
|
|
|
|
|
|
|
|
|
|
| 107 |
# Optional - Port Configuration
|
| 108 |
PORT=7860
|
| 109 |
```
|
|
@@ -25,10 +25,7 @@ class EmotionalStateAgent:
|
|
| 25 |
|
| 26 |
# Slower learning rate for more gradual emotion changes
|
| 27 |
self.learning_rate = 0.03
|
| 28 |
-
|
| 29 |
-
self.quantum_api_key = None
|
| 30 |
-
self._initialize_quantum()
|
| 31 |
-
|
| 32 |
# Save initial state to JSON
|
| 33 |
self._save_to_json()
|
| 34 |
|
|
@@ -60,48 +57,16 @@ class EmotionalStateAgent:
|
|
| 60 |
except Exception as e:
|
| 61 |
logging.error(f"[EmotionalStateAgent] Error saving emotional state to JSON: {e}")
|
| 62 |
|
| 63 |
-
def
|
| 64 |
-
"""
|
| 65 |
-
quantum_key = os.getenv("ANU_QUANTUM_API_KEY")
|
| 66 |
-
if quantum_key:
|
| 67 |
-
self.quantum_api_key = quantum_key
|
| 68 |
-
self.quantum_random_available = True
|
| 69 |
-
logging.info("[EmotionalStateAgent] ✓ Quantum randomness available")
|
| 70 |
-
else:
|
| 71 |
-
logging.warning("[EmotionalStateAgent] Quantum randomness unavailable")
|
| 72 |
-
|
| 73 |
-
def get_quantum_random_float(self, min_val=0.0, max_val=1.0):
|
| 74 |
-
"""Get a quantum random float between min_val and max_val"""
|
| 75 |
-
if not self.quantum_random_available:
|
| 76 |
-
return random.uniform(min_val, max_val)
|
| 77 |
-
|
| 78 |
-
try:
|
| 79 |
-
quantum_config = self.config.get('quantum', {}) if self.config else {}
|
| 80 |
-
url = quantum_config.get('api_endpoint', 'https://api.quantumnumbers.anu.edu.au')
|
| 81 |
-
headers = {"x-api-key": self.quantum_api_key}
|
| 82 |
-
params = {"length": 1, "type": "uint8"}
|
| 83 |
-
|
| 84 |
-
response = requests.get(url, headers=headers, params=params, timeout=10)
|
| 85 |
-
|
| 86 |
-
if response.status_code == 200:
|
| 87 |
-
result = response.json()
|
| 88 |
-
if result.get('success') and 'data' in result and len(result['data']) > 0:
|
| 89 |
-
normalized = result['data'][0] / 255.0
|
| 90 |
-
return min_val + (max_val - min_val) * normalized
|
| 91 |
-
except Exception as e:
|
| 92 |
-
logging.warning(f"[EmotionalStateAgent] Quantum API failed: {e}")
|
| 93 |
-
|
| 94 |
return random.uniform(min_val, max_val)
|
| 95 |
|
| 96 |
def update_with_sentiment(self, sentiment_score):
|
| 97 |
"""Update emotional state based on sentiment"""
|
| 98 |
# Slower decay factor to prevent emotions from crashing to minimum too fast
|
| 99 |
# Changed from 0.9 to 0.97 (only 3% decay per interaction instead of 10%)
|
| 100 |
-
decay_factor = 0.
|
| 101 |
-
|
| 102 |
-
quantum_decay_variation = self.get_quantum_random_float(0.95, 0.99)
|
| 103 |
-
decay_factor = quantum_decay_variation
|
| 104 |
-
|
| 105 |
for emotion in self.emotional_state:
|
| 106 |
# Decay emotions more slowly (preserves emotional state longer)
|
| 107 |
self.emotional_state[emotion] *= decay_factor
|
|
@@ -109,20 +74,16 @@ class EmotionalStateAgent:
|
|
| 109 |
self.emotional_state[emotion] = max(0.0, min(1.0, self.emotional_state[emotion]))
|
| 110 |
|
| 111 |
# Apply sentiment with slower learning rate for gradual changes
|
| 112 |
-
learning_rate = self.
|
| 113 |
-
|
| 114 |
-
quantum_lr_variation = self.get_quantum_random_float(0.02, 0.04)
|
| 115 |
-
learning_rate = quantum_lr_variation
|
| 116 |
-
|
| 117 |
# Update emotions based on sentiment (slower, more gradual)
|
| 118 |
self.emotional_state["joy"] += sentiment_score * learning_rate
|
| 119 |
self.emotional_state["sadness"] -= sentiment_score * learning_rate
|
| 120 |
-
|
| 121 |
-
# Add
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
self.emotional_state["curiosity"]
|
| 125 |
-
self.emotional_state["curiosity"] + quantum_curiosity_boost))
|
| 126 |
|
| 127 |
# Soft normalization - only normalize if emotions get too extreme
|
| 128 |
# This prevents emotions from being forced to equal values
|
|
|
|
| 25 |
|
| 26 |
# Slower learning rate for more gradual emotion changes
|
| 27 |
self.learning_rate = 0.03
|
| 28 |
+
|
|
|
|
|
|
|
|
|
|
| 29 |
# Save initial state to JSON
|
| 30 |
self._save_to_json()
|
| 31 |
|
|
|
|
| 57 |
except Exception as e:
|
| 58 |
logging.error(f"[EmotionalStateAgent] Error saving emotional state to JSON: {e}")
|
| 59 |
|
| 60 |
+
def get_random_float(self, min_val=0.0, max_val=1.0):
|
| 61 |
+
"""Get a random float between min_val and max_val"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
return random.uniform(min_val, max_val)
|
| 63 |
|
| 64 |
def update_with_sentiment(self, sentiment_score):
|
| 65 |
"""Update emotional state based on sentiment"""
|
| 66 |
# Slower decay factor to prevent emotions from crashing to minimum too fast
|
| 67 |
# Changed from 0.9 to 0.97 (only 3% decay per interaction instead of 10%)
|
| 68 |
+
decay_factor = self.get_random_float(0.95, 0.99)
|
| 69 |
+
|
|
|
|
|
|
|
|
|
|
| 70 |
for emotion in self.emotional_state:
|
| 71 |
# Decay emotions more slowly (preserves emotional state longer)
|
| 72 |
self.emotional_state[emotion] *= decay_factor
|
|
|
|
| 74 |
self.emotional_state[emotion] = max(0.0, min(1.0, self.emotional_state[emotion]))
|
| 75 |
|
| 76 |
# Apply sentiment with slower learning rate for gradual changes
|
| 77 |
+
learning_rate = self.get_random_float(0.02, 0.04)
|
| 78 |
+
|
|
|
|
|
|
|
|
|
|
| 79 |
# Update emotions based on sentiment (slower, more gradual)
|
| 80 |
self.emotional_state["joy"] += sentiment_score * learning_rate
|
| 81 |
self.emotional_state["sadness"] -= sentiment_score * learning_rate
|
| 82 |
+
|
| 83 |
+
# Add randomness to curiosity (making responses more unpredictable)
|
| 84 |
+
curiosity_boost = self.get_random_float(-0.03, 0.03)
|
| 85 |
+
self.emotional_state["curiosity"] = max(0.0, min(1.0,
|
| 86 |
+
self.emotional_state["curiosity"] + curiosity_boost))
|
|
|
|
| 87 |
|
| 88 |
# Soft normalization - only normalize if emotions get too extreme
|
| 89 |
# This prevents emotions from being forced to equal values
|
|
@@ -20,10 +20,6 @@
|
|
| 20 |
"AZURE_TEXT_ANALYTICS_ENDPOINT": {
|
| 21 |
"description": "Your Azure Text Analytics endpoint (optional)",
|
| 22 |
"required": false
|
| 23 |
-
},
|
| 24 |
-
"ANU_QUANTUM_API_KEY": {
|
| 25 |
-
"description": "Your ANU Quantum API key (optional)",
|
| 26 |
-
"required": false
|
| 27 |
}
|
| 28 |
},
|
| 29 |
"formation": {
|
|
|
|
| 20 |
"AZURE_TEXT_ANALYTICS_ENDPOINT": {
|
| 21 |
"description": "Your Azure Text Analytics endpoint (optional)",
|
| 22 |
"required": false
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
},
|
| 25 |
"formation": {
|
|
@@ -80,10 +80,10 @@ deepseek_initialized = False
|
|
| 80 |
max_init_retries = 3
|
| 81 |
current_init_retry = 0
|
| 82 |
|
| 83 |
-
#
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
|
| 88 |
# Check for required environment variables
|
| 89 |
required_env_vars = ['DEEPSEEK_API_KEY']
|
|
@@ -132,7 +132,6 @@ init_status = {
|
|
| 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 |
}
|
| 137 |
|
| 138 |
def initialize_json_memory():
|
|
@@ -300,43 +299,6 @@ def validate_inflection_api():
|
|
| 300 |
init_status['inflection_api']['ready'] = False
|
| 301 |
return False
|
| 302 |
|
| 303 |
-
def validate_quantum_api():
|
| 304 |
-
"""Validate Quantum Random Numbers API key (optional component)"""
|
| 305 |
-
try:
|
| 306 |
-
logging.info("🔄 [Quantum API] Validating API key...")
|
| 307 |
-
print("🔄 [Quantum API] Validating API key...")
|
| 308 |
-
api_key = os.getenv("ANU_QUANTUM_API_KEY")
|
| 309 |
-
if not api_key:
|
| 310 |
-
logging.info("ℹ️ [Quantum API] API key not found (optional - will use pseudo-random)")
|
| 311 |
-
print("ℹ️ [Quantum API] API key not found (optional - will use pseudo-random)")
|
| 312 |
-
init_status['quantum_api']['ready'] = False
|
| 313 |
-
return True # Not an error - optional component
|
| 314 |
-
url = "https://api.quantumnumbers.anu.edu.au"
|
| 315 |
-
headers = {"x-api-key": api_key}
|
| 316 |
-
params = {"length": 1, "type": "uint8"}
|
| 317 |
-
response = requests.get(url, headers=headers, params=params, timeout=10)
|
| 318 |
-
if response.status_code == 200:
|
| 319 |
-
logging.info("✓ [Quantum API] API key validated")
|
| 320 |
-
print("✓ [Quantum API] API key validated")
|
| 321 |
-
init_status['quantum_api']['ready'] = True
|
| 322 |
-
return True
|
| 323 |
-
elif response.status_code == 429:
|
| 324 |
-
# Rate limit - not an error, just unavailable temporarily
|
| 325 |
-
logging.info("ℹ️ [Quantum API] Rate limited (optional - will use pseudo-random)")
|
| 326 |
-
print("ℹ️ [Quantum API] Rate limited (optional - will use pseudo-random)")
|
| 327 |
-
init_status['quantum_api']['ready'] = False
|
| 328 |
-
return True # Not an error - optional component
|
| 329 |
-
else:
|
| 330 |
-
logging.info(f"ℹ️ [Quantum API] Validation failed: {response.status_code} (optional - will use pseudo-random)")
|
| 331 |
-
print(f"ℹ️ [Quantum API] Validation failed: {response.status_code} (optional - will use pseudo-random)")
|
| 332 |
-
init_status['quantum_api']['ready'] = False
|
| 333 |
-
return True # Not an error - optional component
|
| 334 |
-
except Exception as e:
|
| 335 |
-
# Any exception is not critical - quantum randomness is optional
|
| 336 |
-
logging.info(f"ℹ️ [Quantum API] Unavailable: {e} (optional - will use pseudo-random)")
|
| 337 |
-
print(f"ℹ️ [Quantum API] Unavailable: {e} (optional - will use pseudo-random)")
|
| 338 |
-
init_status['quantum_api']['ready'] = False
|
| 339 |
-
return True # Not an error - optional component
|
| 340 |
|
| 341 |
def run_parallel_initialization():
|
| 342 |
"""Run all initialization steps in parallel"""
|
|
@@ -358,7 +320,6 @@ def run_parallel_initialization():
|
|
| 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 |
]
|
| 363 |
|
| 364 |
completed_count = 0
|
|
@@ -719,103 +680,51 @@ def analyze_sentiment(text):
|
|
| 719 |
# Track avatar updates with timestamp
|
| 720 |
last_avatar_update = time.time()
|
| 721 |
|
| 722 |
-
def
|
| 723 |
-
"""Asynchronously fill the
|
| 724 |
-
global
|
| 725 |
-
|
| 726 |
-
with
|
| 727 |
-
if
|
| 728 |
return # Already filling
|
| 729 |
-
|
| 730 |
-
|
| 731 |
def _fill_queue():
|
| 732 |
-
global
|
| 733 |
try:
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
return
|
| 742 |
-
|
| 743 |
-
from config import MODEL_CONFIG
|
| 744 |
-
quantum_config = MODEL_CONFIG.get('quantum', {}) if MODEL_CONFIG else {}
|
| 745 |
-
api_endpoint = quantum_config.get('api_endpoint', 'https://api.quantumnumbers.anu.edu.au')
|
| 746 |
-
|
| 747 |
-
headers = {"x-api-key": quantum_api_key}
|
| 748 |
-
params = {"length": 1, "type": "uint8"}
|
| 749 |
-
|
| 750 |
-
numbers_fetched = 0
|
| 751 |
-
with quantum_queue_lock:
|
| 752 |
-
current_size = len(quantum_numbers_queue)
|
| 753 |
-
|
| 754 |
-
while numbers_fetched < 100:
|
| 755 |
-
try:
|
| 756 |
-
response = requests.get(api_endpoint, headers=headers, params=params, timeout=5)
|
| 757 |
-
if response.status_code == 200:
|
| 758 |
-
result = response.json()
|
| 759 |
-
if result.get('success') and 'data' in result and len(result['data']) > 0:
|
| 760 |
-
normalized = result['data'][0] / 255.0
|
| 761 |
-
with quantum_queue_lock:
|
| 762 |
-
quantum_numbers_queue.append(normalized)
|
| 763 |
-
numbers_fetched += 1
|
| 764 |
-
else:
|
| 765 |
-
# Fallback to pseudo-random
|
| 766 |
-
import random
|
| 767 |
-
with quantum_queue_lock:
|
| 768 |
-
quantum_numbers_queue.append(random.random())
|
| 769 |
-
numbers_fetched += 1
|
| 770 |
-
elif response.status_code == 429:
|
| 771 |
-
# Rate limited - use pseudo-random
|
| 772 |
-
import random
|
| 773 |
-
with quantum_queue_lock:
|
| 774 |
-
quantum_numbers_queue.append(random.random())
|
| 775 |
-
numbers_fetched += 1
|
| 776 |
-
else:
|
| 777 |
-
# Error - use pseudo-random
|
| 778 |
-
import random
|
| 779 |
-
with quantum_queue_lock:
|
| 780 |
-
quantum_numbers_queue.append(random.random())
|
| 781 |
-
numbers_fetched += 1
|
| 782 |
-
except Exception as e:
|
| 783 |
-
logging.debug(f"[Quantum Queue] Error fetching number: {e}, using pseudo-random")
|
| 784 |
-
import random
|
| 785 |
-
with quantum_queue_lock:
|
| 786 |
-
quantum_numbers_queue.append(random.random())
|
| 787 |
-
numbers_fetched += 1
|
| 788 |
-
|
| 789 |
-
# Small delay to avoid rate limits
|
| 790 |
-
time.sleep(0.1)
|
| 791 |
-
|
| 792 |
-
logging.info(f"[Quantum Queue] Filled queue with {numbers_fetched} numbers")
|
| 793 |
except Exception as e:
|
| 794 |
-
logging.error(f"[
|
| 795 |
finally:
|
| 796 |
-
with
|
| 797 |
-
|
| 798 |
-
|
| 799 |
# Start filling in background thread
|
| 800 |
Thread(target=_fill_queue, daemon=True).start()
|
| 801 |
|
| 802 |
-
def
|
| 803 |
-
"""Pop a
|
| 804 |
-
global
|
| 805 |
-
|
| 806 |
-
with
|
| 807 |
-
if len(
|
| 808 |
-
return
|
| 809 |
else:
|
| 810 |
-
# Queue is empty,
|
| 811 |
import random
|
| 812 |
return random.random()
|
| 813 |
|
| 814 |
@app.route('/api/avatar')
|
| 815 |
def get_avatar():
|
| 816 |
"""Endpoint to get the current avatar shape and state with enhanced responsiveness"""
|
| 817 |
-
global last_avatar_update,
|
| 818 |
-
|
| 819 |
if not is_initialized:
|
| 820 |
return jsonify({
|
| 821 |
'avatar_shape': 'Circle',
|
|
@@ -823,17 +732,17 @@ def get_avatar():
|
|
| 823 |
'last_updated': last_avatar_update,
|
| 824 |
'status': 'initializing'
|
| 825 |
})
|
| 826 |
-
|
| 827 |
try:
|
| 828 |
-
# Check if
|
| 829 |
-
with
|
| 830 |
-
queue_empty = len(
|
| 831 |
-
|
| 832 |
-
if queue_empty and not
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
# Pop one
|
| 836 |
-
|
| 837 |
|
| 838 |
avatar_shape = avatar_engine.avatar_model if avatar_engine else 'Circle'
|
| 839 |
|
|
@@ -850,15 +759,15 @@ def get_avatar():
|
|
| 850 |
|
| 851 |
# Force avatar update based on emotions if available
|
| 852 |
if avatar_engine and galatea_ai:
|
| 853 |
-
# Apply
|
| 854 |
emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
|
| 855 |
-
# Use
|
| 856 |
import random
|
| 857 |
-
emotion_index = int(
|
| 858 |
selected_emotion = emotions[emotion_index]
|
| 859 |
-
|
| 860 |
-
# Apply subtle
|
| 861 |
-
influence = (
|
| 862 |
current_value = galatea_ai.emotional_state[selected_emotion]
|
| 863 |
new_value = max(0.05, min(1.0, current_value + influence))
|
| 864 |
galatea_ai.emotional_state[selected_emotion] = new_value
|
|
|
|
| 80 |
max_init_retries = 3
|
| 81 |
current_init_retry = 0
|
| 82 |
|
| 83 |
+
# Random numbers queue for /api/avatar endpoint
|
| 84 |
+
random_numbers_queue = deque(maxlen=100)
|
| 85 |
+
random_queue_lock = Lock()
|
| 86 |
+
random_filling = False
|
| 87 |
|
| 88 |
# Check for required environment variables
|
| 89 |
required_env_vars = ['DEEPSEEK_API_KEY']
|
|
|
|
| 132 |
'sentiment_analyzer': {'ready': False, 'error': None},
|
| 133 |
'deepseek_api': {'ready': False, 'error': None},
|
| 134 |
'inflection_api': {'ready': False, 'error': None},
|
|
|
|
| 135 |
}
|
| 136 |
|
| 137 |
def initialize_json_memory():
|
|
|
|
| 299 |
init_status['inflection_api']['ready'] = False
|
| 300 |
return False
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
def run_parallel_initialization():
|
| 304 |
"""Run all initialization steps in parallel"""
|
|
|
|
| 320 |
("Sentiment Analyzer", initialize_sentiment_analyzer),
|
| 321 |
("DeepSeek API", validate_deepseek_api),
|
| 322 |
("Inflection AI", validate_inflection_api),
|
|
|
|
| 323 |
]
|
| 324 |
|
| 325 |
completed_count = 0
|
|
|
|
| 680 |
# Track avatar updates with timestamp
|
| 681 |
last_avatar_update = time.time()
|
| 682 |
|
| 683 |
+
def fill_random_queue():
|
| 684 |
+
"""Asynchronously fill the random numbers queue with 100 numbers"""
|
| 685 |
+
global random_filling, random_numbers_queue
|
| 686 |
+
|
| 687 |
+
with random_queue_lock:
|
| 688 |
+
if random_filling:
|
| 689 |
return # Already filling
|
| 690 |
+
random_filling = True
|
| 691 |
+
|
| 692 |
def _fill_queue():
|
| 693 |
+
global random_filling
|
| 694 |
try:
|
| 695 |
+
import random
|
| 696 |
+
logging.debug("[Random Queue] Filling with regular random numbers")
|
| 697 |
+
with random_queue_lock:
|
| 698 |
+
while len(random_numbers_queue) < 100:
|
| 699 |
+
random_numbers_queue.append(random.random())
|
| 700 |
+
|
| 701 |
+
logging.info(f"[Random Queue] Filled queue with 100 random numbers")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
except Exception as e:
|
| 703 |
+
logging.error(f"[Random Queue] Error filling queue: {e}")
|
| 704 |
finally:
|
| 705 |
+
with random_queue_lock:
|
| 706 |
+
random_filling = False
|
| 707 |
+
|
| 708 |
# Start filling in background thread
|
| 709 |
Thread(target=_fill_queue, daemon=True).start()
|
| 710 |
|
| 711 |
+
def pop_random_number():
|
| 712 |
+
"""Pop a random number from the queue, or generate one if empty"""
|
| 713 |
+
global random_numbers_queue
|
| 714 |
+
|
| 715 |
+
with random_queue_lock:
|
| 716 |
+
if len(random_numbers_queue) > 0:
|
| 717 |
+
return random_numbers_queue.popleft()
|
| 718 |
else:
|
| 719 |
+
# Queue is empty, generate on the fly
|
| 720 |
import random
|
| 721 |
return random.random()
|
| 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, random_numbers_queue, random_filling
|
| 727 |
+
|
| 728 |
if not is_initialized:
|
| 729 |
return jsonify({
|
| 730 |
'avatar_shape': 'Circle',
|
|
|
|
| 732 |
'last_updated': last_avatar_update,
|
| 733 |
'status': 'initializing'
|
| 734 |
})
|
| 735 |
+
|
| 736 |
try:
|
| 737 |
+
# Check if random queue is empty, fill it asynchronously if needed
|
| 738 |
+
with random_queue_lock:
|
| 739 |
+
queue_empty = len(random_numbers_queue) == 0
|
| 740 |
+
|
| 741 |
+
if queue_empty and not random_filling:
|
| 742 |
+
fill_random_queue()
|
| 743 |
+
|
| 744 |
+
# Pop one random number to update emotions
|
| 745 |
+
random_num = pop_random_number()
|
| 746 |
|
| 747 |
avatar_shape = avatar_engine.avatar_model if avatar_engine else 'Circle'
|
| 748 |
|
|
|
|
| 759 |
|
| 760 |
# Force avatar update based on emotions if available
|
| 761 |
if avatar_engine and galatea_ai:
|
| 762 |
+
# Apply random influence to emotions
|
| 763 |
emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
|
| 764 |
+
# Use random number to influence a random emotion
|
| 765 |
import random
|
| 766 |
+
emotion_index = int(random_num * len(emotions)) % len(emotions)
|
| 767 |
selected_emotion = emotions[emotion_index]
|
| 768 |
+
|
| 769 |
+
# Apply subtle random influence (-0.05 to +0.05)
|
| 770 |
+
influence = (random_num - 0.5) * 0.1
|
| 771 |
current_value = galatea_ai.emotional_state[selected_emotion]
|
| 772 |
new_value = max(0.05, min(1.0, current_value + influence))
|
| 773 |
galatea_ai.emotional_state[selected_emotion] = new_value
|
|
@@ -14,8 +14,6 @@ services:
|
|
| 14 |
# Optional - Azure Text Analytics
|
| 15 |
- AZURE_TEXT_ANALYTICS_KEY=${AZURE_TEXT_ANALYTICS_KEY:-}
|
| 16 |
- AZURE_TEXT_ANALYTICS_ENDPOINT=${AZURE_TEXT_ANALYTICS_ENDPOINT:-}
|
| 17 |
-
# Optional - Quantum API
|
| 18 |
-
- ANU_QUANTUM_API_KEY=${ANU_QUANTUM_API_KEY:-}
|
| 19 |
# Optional - Port (defaults to 7860)
|
| 20 |
- PORT=${PORT:-7860}
|
| 21 |
env_file:
|
|
|
|
| 14 |
# Optional - Azure Text Analytics
|
| 15 |
- AZURE_TEXT_ANALYTICS_KEY=${AZURE_TEXT_ANALYTICS_KEY:-}
|
| 16 |
- AZURE_TEXT_ANALYTICS_ENDPOINT=${AZURE_TEXT_ANALYTICS_ENDPOINT:-}
|
|
|
|
|
|
|
| 17 |
# Optional - Port (defaults to 7860)
|
| 18 |
- PORT=${PORT:-7860}
|
| 19 |
env_file:
|
|
@@ -79,8 +79,7 @@ class GalateaAI:
|
|
| 79 |
# Legacy compatibility
|
| 80 |
self.deepseek_available = self.deepseek_agent.is_ready()
|
| 81 |
self.inflection_ai_available = self.pi_agent.is_ready()
|
| 82 |
-
|
| 83 |
-
|
| 84 |
logging.info("✓ All agents initialized and verified")
|
| 85 |
|
| 86 |
def _check_pre_initialization(self):
|
|
|
|
| 79 |
# Legacy compatibility
|
| 80 |
self.deepseek_available = self.deepseek_agent.is_ready()
|
| 81 |
self.inflection_ai_available = self.pi_agent.is_ready()
|
| 82 |
+
|
|
|
|
| 83 |
logging.info("✓ All agents initialized and verified")
|
| 84 |
|
| 85 |
def _check_pre_initialization(self):
|
|
@@ -56,16 +56,4 @@ conversation:
|
|
| 56 |
system_prompts:
|
| 57 |
galatea_identity: "You are Galatea, an AI assistant with emotional awareness and memory."
|
| 58 |
response_style: "Respond in character, keeping responses concise (under 50 words)."
|
| 59 |
-
|
| 60 |
-
# Quantum Randomness Configuration
|
| 61 |
-
quantum:
|
| 62 |
-
api_endpoint: "https://api.quantumnumbers.anu.edu.au"
|
| 63 |
-
default_length: 128
|
| 64 |
-
default_type: "uint8"
|
| 65 |
-
|
| 66 |
-
# Usage settings
|
| 67 |
-
use_for_temperature: true
|
| 68 |
-
use_for_emotion_decay: true
|
| 69 |
-
use_for_learning_rate: true
|
| 70 |
-
use_for_curiosity: true
|
| 71 |
|
|
|
|
| 56 |
system_prompts:
|
| 57 |
galatea_identity: "You are Galatea, an AI assistant with emotional awareness and memory."
|
| 58 |
response_style: "Respond in character, keeping responses concise (under 50 words)."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
|
@@ -1,9 +1,9 @@
|
|
| 1 |
-
"""
|
| 2 |
import os
|
| 3 |
import sys
|
| 4 |
import time
|
| 5 |
import logging
|
| 6 |
-
import
|
| 7 |
import threading
|
| 8 |
from collections import deque
|
| 9 |
|
|
@@ -12,67 +12,33 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
| 12 |
from config import MODEL_CONFIG
|
| 13 |
|
| 14 |
class QuantumEmotionService:
|
| 15 |
-
"""Background service that
|
| 16 |
-
|
| 17 |
def __init__(self, emotional_agent, config=None):
|
| 18 |
self.config = config or MODEL_CONFIG or {}
|
| 19 |
self.emotional_agent = emotional_agent
|
| 20 |
-
self.quantum_api_key = os.getenv("ANU_QUANTUM_API_KEY")
|
| 21 |
self.running = False
|
| 22 |
self.thread = None
|
| 23 |
-
self.
|
| 24 |
self.last_update_time = time.time()
|
| 25 |
self.update_interval = 10.0 # Update emotions every 10 seconds
|
| 26 |
-
self.
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
logging.info("[QuantumEmotionService] Quantum API key not found - service will not run")
|
| 30 |
-
return
|
| 31 |
-
|
| 32 |
-
quantum_config = self.config.get('quantum', {}) if self.config else {}
|
| 33 |
-
self.api_endpoint = quantum_config.get('api_endpoint', 'https://api.quantumnumbers.anu.edu.au')
|
| 34 |
-
|
| 35 |
-
logging.info("[QuantumEmotionService] Initialized - will call API once per second")
|
| 36 |
|
| 37 |
-
def
|
| 38 |
-
"""
|
| 39 |
-
|
| 40 |
-
return None
|
| 41 |
-
|
| 42 |
-
try:
|
| 43 |
-
headers = {"x-api-key": self.quantum_api_key}
|
| 44 |
-
params = {"length": 1, "type": "uint8"}
|
| 45 |
-
|
| 46 |
-
response = requests.get(self.api_endpoint, headers=headers, params=params, timeout=5)
|
| 47 |
-
|
| 48 |
-
if response.status_code == 200:
|
| 49 |
-
result = response.json()
|
| 50 |
-
if result.get('success') and 'data' in result and len(result['data']) > 0:
|
| 51 |
-
# Normalize to 0-1 range
|
| 52 |
-
normalized = result['data'][0] / 255.0
|
| 53 |
-
return normalized
|
| 54 |
-
elif response.status_code == 429:
|
| 55 |
-
# Rate limited - return None, will use pseudo-random
|
| 56 |
-
logging.debug("[QuantumEmotionService] Rate limited, using pseudo-random")
|
| 57 |
-
return None
|
| 58 |
-
else:
|
| 59 |
-
logging.debug(f"[QuantumEmotionService] API returned {response.status_code}")
|
| 60 |
-
return None
|
| 61 |
-
except Exception as e:
|
| 62 |
-
logging.debug(f"[QuantumEmotionService] API call failed: {e}")
|
| 63 |
-
return None
|
| 64 |
-
|
| 65 |
-
return None
|
| 66 |
|
| 67 |
-
def
|
| 68 |
-
"""Apply the collected 6
|
| 69 |
-
if len(self.
|
| 70 |
-
logging.debug(f"[
|
| 71 |
return
|
| 72 |
-
|
| 73 |
# Convert deque to list
|
| 74 |
-
numbers = list(self.
|
| 75 |
-
|
| 76 |
# Map 6 numbers to 5 emotions + overall variation
|
| 77 |
# numbers[0] -> joy
|
| 78 |
# numbers[1] -> sadness
|
|
@@ -80,25 +46,25 @@ class QuantumEmotionService:
|
|
| 80 |
# numbers[3] -> fear
|
| 81 |
# numbers[4] -> curiosity
|
| 82 |
# numbers[5] -> overall variation factor
|
| 83 |
-
|
| 84 |
emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
|
| 85 |
current_state = self.emotional_agent.get_state()
|
| 86 |
-
|
| 87 |
-
# Apply
|
| 88 |
for i, emotion in enumerate(emotions):
|
| 89 |
if i < len(numbers) and numbers[i] is not None:
|
| 90 |
# Convert 0-1 to -0.05 to +0.05 influence
|
| 91 |
influence = (numbers[i] - 0.5) * 0.1 # Scale to ±0.05
|
| 92 |
-
|
| 93 |
# Apply with overall variation factor
|
| 94 |
if len(numbers) > 5 and numbers[5] is not None:
|
| 95 |
variation = (numbers[5] - 0.5) * 0.02 # Additional ±0.01 variation
|
| 96 |
influence += variation
|
| 97 |
-
|
| 98 |
# Update emotion
|
| 99 |
new_value = current_state[emotion] + influence
|
| 100 |
current_state[emotion] = max(0.05, min(1.0, new_value))
|
| 101 |
-
|
| 102 |
# Soft normalization to keep emotions balanced (only if they get too extreme)
|
| 103 |
total = sum(current_state.values())
|
| 104 |
if total > 1.5 or total < 0.5:
|
|
@@ -110,74 +76,62 @@ class QuantumEmotionService:
|
|
| 110 |
for emotion in emotions:
|
| 111 |
if current_state[emotion] < 0.05:
|
| 112 |
current_state[emotion] = 0.05
|
| 113 |
-
|
| 114 |
# Update the emotional agent's state
|
| 115 |
self.emotional_agent.emotional_state = current_state
|
| 116 |
-
|
| 117 |
-
# Save to JSON after
|
| 118 |
self.emotional_agent._save_to_json()
|
| 119 |
-
|
| 120 |
-
logging.info(f"[
|
| 121 |
|
| 122 |
def _service_loop(self):
|
| 123 |
-
"""Main service loop -
|
| 124 |
-
logging.info("[
|
| 125 |
-
|
| 126 |
while self.running:
|
| 127 |
try:
|
| 128 |
-
#
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
logging.debug(f"[QuantumEmotionService] Collected quantum number: {quantum_num:.4f}")
|
| 134 |
-
else:
|
| 135 |
-
# Use pseudo-random as fallback
|
| 136 |
-
import random
|
| 137 |
-
pseudo_random = random.random()
|
| 138 |
-
self.quantum_numbers.append(pseudo_random)
|
| 139 |
-
logging.debug(f"[QuantumEmotionService] Using pseudo-random: {pseudo_random:.4f}")
|
| 140 |
-
|
| 141 |
# Check if it's time to update emotions (every 10 seconds)
|
| 142 |
current_time = time.time()
|
| 143 |
if current_time - self.last_update_time >= self.update_interval:
|
| 144 |
-
if len(self.
|
| 145 |
-
self.
|
| 146 |
self.last_update_time = current_time
|
| 147 |
-
logging.info(f"[
|
| 148 |
-
|
| 149 |
-
# Wait 1 second before next
|
| 150 |
-
time.sleep(self.
|
| 151 |
-
|
| 152 |
except Exception as e:
|
| 153 |
-
logging.error(f"[
|
| 154 |
-
time.sleep(self.
|
| 155 |
|
| 156 |
def start(self):
|
| 157 |
"""Start the background service"""
|
| 158 |
-
if not self.quantum_api_key:
|
| 159 |
-
logging.info("[QuantumEmotionService] Cannot start - no API key")
|
| 160 |
-
return False
|
| 161 |
-
|
| 162 |
if self.running:
|
| 163 |
-
logging.warning("[
|
| 164 |
return False
|
| 165 |
-
|
| 166 |
self.running = True
|
| 167 |
self.thread = threading.Thread(target=self._service_loop, daemon=True)
|
| 168 |
self.thread.start()
|
| 169 |
-
logging.info("[
|
| 170 |
return True
|
| 171 |
-
|
| 172 |
def stop(self):
|
| 173 |
"""Stop the background service"""
|
| 174 |
if not self.running:
|
| 175 |
return
|
| 176 |
-
|
| 177 |
self.running = False
|
| 178 |
if self.thread:
|
| 179 |
self.thread.join(timeout=2.0)
|
| 180 |
-
logging.info("[
|
| 181 |
|
| 182 |
def is_running(self):
|
| 183 |
"""Check if service is running"""
|
|
|
|
| 1 |
+
"""Random Emotion Service - Background service for random-influenced emotion updates"""
|
| 2 |
import os
|
| 3 |
import sys
|
| 4 |
import time
|
| 5 |
import logging
|
| 6 |
+
import random
|
| 7 |
import threading
|
| 8 |
from collections import deque
|
| 9 |
|
|
|
|
| 12 |
from config import MODEL_CONFIG
|
| 13 |
|
| 14 |
class QuantumEmotionService:
|
| 15 |
+
"""Background service that generates random numbers and applies them to emotions"""
|
| 16 |
+
|
| 17 |
def __init__(self, emotional_agent, config=None):
|
| 18 |
self.config = config or MODEL_CONFIG or {}
|
| 19 |
self.emotional_agent = emotional_agent
|
|
|
|
| 20 |
self.running = False
|
| 21 |
self.thread = None
|
| 22 |
+
self.random_numbers = deque(maxlen=6) # Store last 6 numbers
|
| 23 |
self.last_update_time = time.time()
|
| 24 |
self.update_interval = 10.0 # Update emotions every 10 seconds
|
| 25 |
+
self.generation_interval = 1.0 # Generate number once per second
|
| 26 |
+
|
| 27 |
+
logging.info("[RandomEmotionService] Initialized - will generate random numbers once per second")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
def _generate_random_number(self):
|
| 30 |
+
"""Generate a single random number"""
|
| 31 |
+
return random.random()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
def _apply_random_emotions(self):
|
| 34 |
+
"""Apply the collected 6 random numbers to influence emotion state"""
|
| 35 |
+
if len(self.random_numbers) < 6:
|
| 36 |
+
logging.debug(f"[RandomEmotionService] Not enough numbers yet ({len(self.random_numbers)}/6)")
|
| 37 |
return
|
| 38 |
+
|
| 39 |
# Convert deque to list
|
| 40 |
+
numbers = list(self.random_numbers)
|
| 41 |
+
|
| 42 |
# Map 6 numbers to 5 emotions + overall variation
|
| 43 |
# numbers[0] -> joy
|
| 44 |
# numbers[1] -> sadness
|
|
|
|
| 46 |
# numbers[3] -> fear
|
| 47 |
# numbers[4] -> curiosity
|
| 48 |
# numbers[5] -> overall variation factor
|
| 49 |
+
|
| 50 |
emotions = ["joy", "sadness", "anger", "fear", "curiosity"]
|
| 51 |
current_state = self.emotional_agent.get_state()
|
| 52 |
+
|
| 53 |
+
# Apply random influence (subtle changes, -0.05 to +0.05 range)
|
| 54 |
for i, emotion in enumerate(emotions):
|
| 55 |
if i < len(numbers) and numbers[i] is not None:
|
| 56 |
# Convert 0-1 to -0.05 to +0.05 influence
|
| 57 |
influence = (numbers[i] - 0.5) * 0.1 # Scale to ±0.05
|
| 58 |
+
|
| 59 |
# Apply with overall variation factor
|
| 60 |
if len(numbers) > 5 and numbers[5] is not None:
|
| 61 |
variation = (numbers[5] - 0.5) * 0.02 # Additional ±0.01 variation
|
| 62 |
influence += variation
|
| 63 |
+
|
| 64 |
# Update emotion
|
| 65 |
new_value = current_state[emotion] + influence
|
| 66 |
current_state[emotion] = max(0.05, min(1.0, new_value))
|
| 67 |
+
|
| 68 |
# Soft normalization to keep emotions balanced (only if they get too extreme)
|
| 69 |
total = sum(current_state.values())
|
| 70 |
if total > 1.5 or total < 0.5:
|
|
|
|
| 76 |
for emotion in emotions:
|
| 77 |
if current_state[emotion] < 0.05:
|
| 78 |
current_state[emotion] = 0.05
|
| 79 |
+
|
| 80 |
# Update the emotional agent's state
|
| 81 |
self.emotional_agent.emotional_state = current_state
|
| 82 |
+
|
| 83 |
+
# Save to JSON after random update
|
| 84 |
self.emotional_agent._save_to_json()
|
| 85 |
+
|
| 86 |
+
logging.info(f"[RandomEmotionService] Applied random influence: {current_state}")
|
| 87 |
|
| 88 |
def _service_loop(self):
|
| 89 |
+
"""Main service loop - generates random numbers once per second, updates emotions every 10 seconds"""
|
| 90 |
+
logging.info("[RandomEmotionService] Service started")
|
| 91 |
+
|
| 92 |
while self.running:
|
| 93 |
try:
|
| 94 |
+
# Generate random number (once per second)
|
| 95 |
+
random_num = self._generate_random_number()
|
| 96 |
+
self.random_numbers.append(random_num)
|
| 97 |
+
logging.debug(f"[RandomEmotionService] Generated random number: {random_num:.4f}")
|
| 98 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
# Check if it's time to update emotions (every 10 seconds)
|
| 100 |
current_time = time.time()
|
| 101 |
if current_time - self.last_update_time >= self.update_interval:
|
| 102 |
+
if len(self.random_numbers) >= 6:
|
| 103 |
+
self._apply_random_emotions()
|
| 104 |
self.last_update_time = current_time
|
| 105 |
+
logging.info(f"[RandomEmotionService] Updated emotions with {len(self.random_numbers)} random numbers")
|
| 106 |
+
|
| 107 |
+
# Wait 1 second before next generation
|
| 108 |
+
time.sleep(self.generation_interval)
|
| 109 |
+
|
| 110 |
except Exception as e:
|
| 111 |
+
logging.error(f"[RandomEmotionService] Error in service loop: {e}")
|
| 112 |
+
time.sleep(self.generation_interval) # Wait before retrying
|
| 113 |
|
| 114 |
def start(self):
|
| 115 |
"""Start the background service"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
if self.running:
|
| 117 |
+
logging.warning("[RandomEmotionService] Already running")
|
| 118 |
return False
|
| 119 |
+
|
| 120 |
self.running = True
|
| 121 |
self.thread = threading.Thread(target=self._service_loop, daemon=True)
|
| 122 |
self.thread.start()
|
| 123 |
+
logging.info("[RandomEmotionService] Background service started")
|
| 124 |
return True
|
| 125 |
+
|
| 126 |
def stop(self):
|
| 127 |
"""Stop the background service"""
|
| 128 |
if not self.running:
|
| 129 |
return
|
| 130 |
+
|
| 131 |
self.running = False
|
| 132 |
if self.thread:
|
| 133 |
self.thread.join(timeout=2.0)
|
| 134 |
+
logging.info("[RandomEmotionService] Background service stopped")
|
| 135 |
|
| 136 |
def is_running(self):
|
| 137 |
"""Check if service is running"""
|