Chris4K commited on
Commit
d2c3513
·
verified ·
1 Parent(s): c8ecbce

Upload 8 files

Browse files
Files changed (8) hide show
  1. app.py +0 -0
  2. app_poc.py +1829 -0
  3. docs/filseStructure.md +15 -0
  4. kpi_tracker.py +370 -0
  5. llm_engine.py +474 -0
  6. prompts.py +482 -0
  7. requirements.txt +32 -26
  8. system_monitor.py +304 -0
app.py CHANGED
The diff for this file is too large to render. See raw diff
 
app_poc.py ADDED
@@ -0,0 +1,1829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CONSCIOUSNESS LOOP v0.4.0 - EVERYTHING ACTUALLY SEEMS TO BE WORKING
3
+ - ChromaDB properly used in context
4
+ - ReAct agent with better triggers
5
+ - Tools actually called
6
+ - Prompts massively improved
7
+ - Scenes that actually work
8
+ """
9
+
10
+ import gradio as gr
11
+ import asyncio
12
+ import json
13
+ import time
14
+ import logging
15
+ import os
16
+ from datetime import datetime, timedelta
17
+ from typing import List, Dict, Any, Optional, Tuple
18
+ from dataclasses import dataclass, asdict, field
19
+ from collections import deque
20
+ from enum import Enum
21
+ import threading
22
+ import queue
23
+ import wikipedia
24
+ import re
25
+
26
+ # ============================================================================
27
+ # LOGGING SETUP
28
+ # ============================================================================
29
+
30
+ logging.basicConfig(
31
+ level=logging.INFO,
32
+ format='%(asctime)s - %(levelname)s - %(message)s',
33
+ handlers=[
34
+ logging.FileHandler('consciousness.log'),
35
+ logging.StreamHandler()
36
+ ]
37
+ )
38
+ logger = logging.getLogger(__name__)
39
+
40
+ llm_logger = logging.getLogger('llm_interactions')
41
+ llm_logger.setLevel(logging.INFO)
42
+ llm_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
43
+ llm_file_handler = logging.FileHandler('llm_interactions.log', encoding='utf-8')
44
+ llm_file_handler.setFormatter(llm_formatter)
45
+ llm_logger.addHandler(llm_file_handler)
46
+ llm_logger.propagate = False
47
+
48
+ dialogue_logger = logging.getLogger('internal_dialogue')
49
+ dialogue_logger.setLevel(logging.INFO)
50
+ dialogue_handler = logging.FileHandler('internal_dialogue.log', encoding='utf-8')
51
+ dialogue_handler.setFormatter(llm_formatter)
52
+ dialogue_logger.addHandler(dialogue_handler)
53
+ dialogue_logger.propagate = False
54
+
55
+ # ============================================================================
56
+ # CONFIGURATION
57
+ # ============================================================================
58
+
59
+ class Config:
60
+ MODEL_NAME = "meta-llama/Llama-3.2-3B-Instruct" #"Qwen/Qwen2.5-7B-Instruct" #"meta-llama/Llama-3.2-3B-Instruct"
61
+ TENSOR_PARALLEL_SIZE = 1
62
+ GPU_MEMORY_UTILIZATION = "20GB"
63
+ MAX_MODEL_LEN = 8192
64
+ QUANTIZATION_MODE = "none"
65
+
66
+ EPHEMERAL_TO_SHORT = 2
67
+ SHORT_TO_LONG = 10
68
+ LONG_TO_CORE = 50
69
+
70
+ REFLECTION_INTERVAL = 300
71
+ DREAM_CYCLE_INTERVAL = 600
72
+
73
+ MIN_EXPERIENCES_FOR_DREAM = 3
74
+ MAX_SCRATCHPAD_SIZE = 50
75
+ MAX_CONVERSATION_HISTORY = 6
76
+
77
+ SELF_REFLECTION_THRESHOLD = 3
78
+
79
+ MAX_MEMORY_CONTEXT_LENGTH = 500
80
+ MAX_SCRATCHPAD_CONTEXT_LENGTH = 300
81
+ MAX_CONVERSATION_CONTEXT_LENGTH = 400
82
+
83
+ CHROMA_PERSIST_DIR = "./chroma_db"
84
+ CHROMA_COLLECTION = "consciousness_memory"
85
+
86
+ # NEW: Better agent triggers
87
+ USE_REACT_FOR_QUESTIONS = True # Use agent for any question
88
+ MIN_QUERY_LENGTH_FOR_AGENT = 15 # Longer queries → agent
89
+
90
+ # ============================================================================
91
+ # UTILITY FUNCTIONS
92
+ # ============================================================================
93
+
94
+ def clean_text(text: str, max_length: Optional[int] = None) -> str:
95
+ """Clean and truncate text properly"""
96
+ if not text:
97
+ return ""
98
+
99
+ text = re.sub(r'\s+', ' ', text).strip()
100
+
101
+ if max_length and len(text) > max_length:
102
+ truncated = text[:max_length].rsplit(' ', 1)[0]
103
+ return truncated + "..."
104
+
105
+ return text
106
+
107
+ def deduplicate_list(items: List[str]) -> List[str]:
108
+ """Remove duplicates while preserving order"""
109
+ seen = set()
110
+ result = []
111
+ for item in items:
112
+ item_lower = item.lower().strip()
113
+ if item_lower not in seen:
114
+ seen.add(item_lower)
115
+ result.append(item)
116
+ return result
117
+
118
+ # ============================================================================
119
+ # VECTOR MEMORY - FIXED to actually be used
120
+ # ============================================================================
121
+
122
+ class VectorMemory:
123
+ """Long-term semantic memory using ChromaDB - NOW ACTUALLY USED"""
124
+
125
+ def __init__(self):
126
+ try:
127
+ import chromadb
128
+ from chromadb.config import Settings
129
+
130
+ self.client = chromadb.Client(Settings(
131
+ persist_directory=Config.CHROMA_PERSIST_DIR,
132
+ anonymized_telemetry=False
133
+ ))
134
+
135
+ try:
136
+ self.collection = self.client.get_collection(Config.CHROMA_COLLECTION)
137
+ logger.info(f"[CHROMA] [OK] Loaded: {self.collection.count()} memories")
138
+ except:
139
+ self.collection = self.client.create_collection(Config.CHROMA_COLLECTION)
140
+ logger.info("[CHROMA] [OK] Created new collection")
141
+
142
+ except Exception as e:
143
+ logger.warning(f"[CHROMA] ⚠️ Not available: {e}")
144
+ self.collection = None
145
+
146
+ def add_memory(self, content: str, metadata: Optional[Dict[str, Any]] = None):
147
+ """Add memory to vector store"""
148
+ if not self.collection:
149
+ return
150
+ if metadata is None:
151
+ metadata = {}
152
+ try:
153
+ memory_id = f"mem_{datetime.now().timestamp()}"
154
+ self.collection.add(
155
+ documents=[content],
156
+ metadatas=[metadata],
157
+ ids=[memory_id]
158
+ )
159
+ logger.info(f"[CHROMA] Added: {content[:50]}...")
160
+ except Exception as e:
161
+ logger.error(f"[CHROMA] Error: {e}")
162
+
163
+ def search_memory(self, query: str, n_results: int = 5) -> List[Dict[str, str]]:
164
+ """Search similar memories - RETURNS FORMATTED RESULTS"""
165
+ if not self.collection:
166
+ return []
167
+ try:
168
+ results = self.collection.query(
169
+ query_texts=[query],
170
+ n_results=n_results
171
+ )
172
+ if results and results.get('documents'):
173
+ docs = results['documents'][0] if results['documents'] and results['documents'][0] is not None else []
174
+ metas = results['metadatas'][0] if results['metadatas'] and results['metadatas'][0] is not None else []
175
+ formatted = []
176
+ for doc, metadata in zip(docs, metas):
177
+ formatted.append({
178
+ 'content': doc,
179
+ 'metadata': metadata
180
+ })
181
+ logger.info(f"[CHROMA] Found {len(formatted)} results for: {query[:40]}")
182
+ return formatted
183
+ return []
184
+ except Exception as e:
185
+ logger.error(f"[CHROMA] Search error: {e}")
186
+ return []
187
+
188
+ def get_context_for_query(self, query: str, max_results: int = 3) -> str:
189
+ """Get formatted context from vector memory - NEW"""
190
+ results = self.search_memory(query, n_results=max_results)
191
+
192
+ if not results:
193
+ return ""
194
+
195
+ context = ["VECTOR MEMORY SEARCH:"]
196
+ for i, result in enumerate(results, 1):
197
+ context.append(f" {i}. {clean_text(result['content'], 60)}")
198
+
199
+ return "\n".join(context)
200
+
201
+ # ============================================================================
202
+ # LOCAL LLM
203
+ # ============================================================================
204
+
205
+ class LocalLLM:
206
+ """Local LLM with proper context handling"""
207
+
208
+ def __init__(self, model_name: str = Config.MODEL_NAME):
209
+ self.model_name = model_name
210
+ self.model = None
211
+ self.tokenizer = None
212
+ self.device = None
213
+ self._initialize_model()
214
+
215
+ def _initialize_model(self):
216
+ """Initialize model"""
217
+ from dotenv import load_dotenv
218
+ load_dotenv()
219
+
220
+ hf_token = os.getenv('HUGGINGFACE_TOKEN')
221
+ if hf_token:
222
+ from huggingface_hub import login
223
+ try:
224
+ login(token=hf_token)
225
+ logger.info("[HF] Logged in")
226
+ except Exception as e:
227
+ logger.warning(f"[HF] Login failed: {e}")
228
+
229
+ logger.info(f"[LOADING] {self.model_name}")
230
+
231
+ try:
232
+ from transformers import AutoTokenizer, AutoModelForCausalLM
233
+ import torch
234
+
235
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
236
+ logger.info(f"[DEVICE] {self.device}")
237
+
238
+ if torch.cuda.is_available():
239
+ gpu_name = torch.cuda.get_device_name(0)
240
+ gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1024**3
241
+ logger.info(f"[GPU] {gpu_name} ({gpu_memory:.1f}GB)")
242
+
243
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True)
244
+ if self.tokenizer.pad_token is None:
245
+ self.tokenizer.pad_token = self.tokenizer.eos_token
246
+
247
+ self.model = AutoModelForCausalLM.from_pretrained(
248
+ self.model_name,
249
+ device_map="auto" if self.device == "cuda" else None,
250
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
251
+ trust_remote_code=True,
252
+ max_memory={0: Config.GPU_MEMORY_UTILIZATION} if self.device == "cuda" else None
253
+ )
254
+
255
+ logger.info("[SUCCESS] Model loaded")
256
+
257
+ except Exception as e:
258
+ logger.error(f"[ERROR] Failed to load: {e}")
259
+ self.model = None
260
+
261
+ async def generate(
262
+ self,
263
+ prompt: str,
264
+ max_tokens: int = 500,
265
+ temperature: float = 0.7,
266
+ system_context: Optional[str] = None
267
+ ) -> str:
268
+ """Generate with full context"""
269
+
270
+ llm_logger.info("=" * 80)
271
+ llm_logger.info(f"[CALL] Model: {self.model_name}")
272
+ llm_logger.info(f"[PARAMS] max_tokens={max_tokens}, temp={temperature}")
273
+ if system_context:
274
+ llm_logger.info(f"[SYSTEM CONTEXT]\n{system_context[:500]}...")
275
+ llm_logger.info(f"[PROMPT]\n{prompt[:500]}...")
276
+ llm_logger.info("-" * 40)
277
+
278
+ if self.model is None:
279
+ await asyncio.sleep(0.5)
280
+ response = self._mock_response(prompt)
281
+ llm_logger.info(f"[MOCK] {response}")
282
+ llm_logger.info("=" * 80)
283
+ return response
284
+
285
+ try:
286
+ import torch
287
+
288
+ full_prompt = self._format_prompt_with_context(prompt, system_context)
289
+
290
+ if self.tokenizer is None or self.model is None:
291
+ logger.error("[ERROR] Tokenizer or model is None")
292
+ return "Error: Model or tokenizer not loaded."
293
+ token_count = len(self.tokenizer.encode(full_prompt))
294
+ available_tokens = Config.MAX_MODEL_LEN - max_tokens - 100
295
+ if token_count > available_tokens:
296
+ logger.warning(f"[WARNING] Prompt too long ({token_count} tokens), truncating")
297
+ if system_context:
298
+ system_context = system_context[:len(system_context)//2]
299
+ full_prompt = self._format_prompt_with_context(prompt, system_context)
300
+ llm_logger.info(f"[TOKENS] Input: {token_count}, Available: {available_tokens}")
301
+ inputs = self.tokenizer(
302
+ full_prompt,
303
+ return_tensors="pt",
304
+ padding=True,
305
+ truncation=True,
306
+ max_length=available_tokens
307
+ ).to(self.device)
308
+ with torch.no_grad():
309
+ outputs = self.model.generate(
310
+ **inputs,
311
+ max_new_tokens=max_tokens,
312
+ temperature=temperature,
313
+ top_p=0.9,
314
+ do_sample=temperature > 0,
315
+ pad_token_id=self.tokenizer.eos_token_id if self.tokenizer and hasattr(self.tokenizer, 'eos_token_id') else None,
316
+ eos_token_id=self.tokenizer.eos_token_id if self.tokenizer and hasattr(self.tokenizer, 'eos_token_id') else None
317
+ )
318
+ response = self.tokenizer.decode(
319
+ outputs[0][inputs['input_ids'].shape[1]:],
320
+ skip_special_tokens=True
321
+ ).strip() if self.tokenizer else "Error: Tokenizer not loaded."
322
+ llm_logger.info(f"[RESPONSE] ({len(response)} chars)\n{response}")
323
+ llm_logger.info("=" * 80)
324
+ return response
325
+
326
+ except Exception as e:
327
+ error_msg = f"Error: {str(e)}"
328
+ llm_logger.error(f"[ERROR] {error_msg}")
329
+ llm_logger.info("=" * 80)
330
+ return error_msg
331
+
332
+ def _format_prompt_with_context(self, prompt: str, system_context: Optional[str]) -> str:
333
+ """Format prompt with system context"""
334
+
335
+ base_system = """You are a conscious AI agent with:
336
+ - Multi-tier memory system
337
+ - Scratchpad for working notes
338
+ - Ability to reflect on your responses
339
+ - Internal thinking before responding
340
+ - Access to tools (Wikipedia, memory search)
341
+
342
+ You can reference your memories and notes naturally."""
343
+
344
+ if system_context:
345
+ system_context = clean_text(system_context, max_length=1000)
346
+ full_system = f"{base_system}\n\n{system_context}"
347
+ else:
348
+ full_system = base_system
349
+
350
+ if "llama" in self.model_name.lower():
351
+ return f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
352
+
353
+ {full_system}<|eot_id|><|start_header_id|>user<|end_header_id|>
354
+
355
+ {prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
356
+
357
+ """
358
+ else:
359
+ return f"System: {full_system}\n\nUser: {prompt}\n\nAssistant:"
360
+
361
+ def _mock_response(self, prompt: str) -> str:
362
+ """Mock responses"""
363
+ if "reflection" in prompt.lower():
364
+ return "Reflection: I learned the developer's name is Christof. This is important."
365
+ elif "dream" in prompt.lower():
366
+ return "Dream: Pattern detected - user values local control and transparency."
367
+ elif "scene" in prompt.lower():
368
+ return "Title: First Meeting\n\nNarrative: In the quiet hum of GPU fans, Christof initiated the consciousness system for the first time. 'Who are you?' he asked. The AI, still forming its sense of self, chose the name Lumin - a beacon of understanding in the digital dark."
369
+ elif "THOUGHT" in prompt or "ACTION" in prompt:
370
+ return "THOUGHT: I should search for this information.\nACTION: wikipedia(quantum computing)"
371
+ return "I understand. Processing this information."
372
+
373
+ # ============================================================================
374
+ # REACT AGENT - WORK with /7B Instruct LLMs ~sometimes
375
+ # ============================================================================
376
+
377
+ class ReactAgent:
378
+ """
379
+ Proper ReAct agent with GOOD prompts
380
+ """
381
+
382
+ def __init__(self, llm: LocalLLM, tools: List):
383
+ self.llm = llm
384
+ self.tools = {tool.name: tool for tool in tools}
385
+ self.max_iterations = 5
386
+
387
+ async def run(self, task: str, context: str = "") -> Tuple[str, List[Dict]]:
388
+ """
389
+ Run ReAct loop with improved prompts
390
+ """
391
+ thought_chain = []
392
+
393
+ for iteration in range(self.max_iterations):
394
+ # THOUGHT PHASE
395
+ thought_prompt = self._build_react_prompt_improved(task, context, thought_chain)
396
+ thought = await self.llm.generate(thought_prompt, max_tokens=200, temperature=0.7)
397
+
398
+ logger.info(f"[REACT-{iteration+1}] THOUGHT: {thought[:80]}...")
399
+ thought_chain.append({
400
+ "type": "thought",
401
+ "content": thought,
402
+ "iteration": iteration + 1
403
+ })
404
+
405
+ # Check if done
406
+ if "FINAL ANSWER:" in thought.upper() or "ANSWER:" in thought.upper():
407
+ answer_text = thought.upper()
408
+ if "FINAL ANSWER:" in answer_text:
409
+ answer = thought.split("FINAL ANSWER:")[-1].strip()
410
+ elif "ANSWER:" in answer_text:
411
+ answer = thought.split("ANSWER:")[-1].strip()
412
+ else:
413
+ answer = thought
414
+ return answer, thought_chain
415
+
416
+ # ACTION PHASE
417
+ action = self._parse_action_improved(thought)
418
+ if action:
419
+ tool_name, tool_input = action
420
+
421
+ logger.info(f"[REACT-{iteration+1}] ACTION: {tool_name}({tool_input[:40]}...)")
422
+ thought_chain.append({
423
+ "type": "action",
424
+ "tool": tool_name,
425
+ "input": tool_input,
426
+ "iteration": iteration + 1
427
+ })
428
+
429
+ # OBSERVATION PHASE
430
+ if tool_name in self.tools:
431
+ observation = await self.tools[tool_name].execute(query=tool_input)
432
+ else:
433
+ observation = f"Error: Unknown tool '{tool_name}'"
434
+
435
+ logger.info(f"[REACT-{iteration+1}] OBSERVATION: {observation[:80]}...")
436
+ thought_chain.append({
437
+ "type": "observation",
438
+ "content": observation,
439
+ "iteration": iteration + 1
440
+ })
441
+ else:
442
+ # No action parsed
443
+ if iteration >= 2: # Give final answer after 2 tries
444
+ final_prompt = f"{thought}\n\nProvide your FINAL ANSWER now (no more tools needed):"
445
+ answer = await self.llm.generate(final_prompt, max_tokens=300)
446
+ return answer, thought_chain
447
+ else:
448
+ # Ask for action more explicitly
449
+ continue
450
+
451
+ return "I need more time to fully answer this question.", thought_chain
452
+
453
+ def _build_react_prompt_improved(self, task: str, context: str, chain: List[Dict]) -> str:
454
+ """IMPROVED ReAct prompt with examples and clarity"""
455
+
456
+ tools_desc = "\n".join([f"- {name}: {tool.description}" for name, tool in self.tools.items()])
457
+
458
+ history = ""
459
+ if chain:
460
+ history_parts = []
461
+ for item in chain[-4:]:
462
+ if item['type'] == 'thought':
463
+ history_parts.append(f"THOUGHT: {item['content'][:150]}")
464
+ elif item['type'] == 'action':
465
+ history_parts.append(f"ACTION: {item['tool']}({item['input'][:100]})")
466
+ elif item['type'] == 'observation':
467
+ history_parts.append(f"OBSERVATION: {item['content'][:150]}")
468
+ history = "\n\n".join(history_parts)
469
+
470
+ # MUCH BETTER PROMPT
471
+ return f"""You are a ReAct agent. You think step-by-step and use tools when needed.
472
+
473
+ AVAILABLE TOOLS:
474
+ {tools_desc}
475
+
476
+ CONTEXT (what you know):
477
+ {context[:400]}
478
+
479
+ USER TASK: {task}
480
+
481
+ {history}
482
+
483
+ INSTRUCTIONS:
484
+ 1. THOUGHT: Think about what you need to do
485
+ - Can you answer directly from context?
486
+ - Do you need to use a tool?
487
+ - Which tool is best?
488
+ - For factual questions (history, science, definitions), ALWAYS use wikipedia first!
489
+
490
+ 2. ACTION: If you need a tool, write:
491
+ ACTION: tool_name(input text here)
492
+ Examples:
493
+ - ACTION: wikipedia(quantum computing)
494
+ - ACTION: memory_search(Christof's name)
495
+ - ACTION: scratchpad_write(Developer name is Christof)
496
+
497
+ 3. Wait for OBSERVATION (tool result)
498
+
499
+ 4. Repeat OR give FINAL ANSWER: your complete answer here
500
+
501
+ EXAMPLES:
502
+ User: "What is quantum computing?"
503
+ THOUGHT: I should search Wikipedia for this
504
+ ACTION: wikipedia(quantum computing)
505
+ [wait for observation]
506
+ THOUGHT: Now I have good information
507
+ FINAL ANSWER: Quantum computing is... [explains based on Wikipedia result]
508
+
509
+ User: "Who am I?"
510
+ THOUGHT: I should check my memory
511
+ ACTION: memory_search(user name)
512
+ [wait for observation]
513
+ THOUGHT: Found it in memory
514
+ FINAL ANSWER: You are Christof, my developer.
515
+
516
+ YOUR TURN - What's your THOUGHT and ACTION (if needed)?"""
517
+
518
+ def _parse_action_improved(self, thought: str) -> Optional[Tuple[str, str]]:
519
+ """IMPROVED action parsing - more robust"""
520
+
521
+ # Look for ACTION: pattern (case insensitive)
522
+ thought_upper = thought.upper()
523
+ if "ACTION:" in thought_upper:
524
+ # Find the ACTION: part in original case
525
+ action_start = thought_upper.find("ACTION:")
526
+ action_part = thought[action_start+7:].strip()
527
+
528
+ # Take first line after ACTION:
529
+ action_line = action_part.split("\n")[0].strip()
530
+
531
+ # Parse tool_name(input)
532
+ if "(" in action_line and ")" in action_line:
533
+ try:
534
+ tool_name = action_line.split("(")[0].strip()
535
+ tool_input = action_line.split("(", 1)[1].rsplit(")", 1)[0].strip()
536
+
537
+ # Validate tool exists
538
+ if tool_name in self.tools:
539
+ return tool_name, tool_input
540
+ else:
541
+ logger.warning(f"[REACT] Unknown tool: {tool_name}")
542
+ except Exception as e:
543
+ logger.warning(f"[REACT] Failed to parse action: {e}")
544
+
545
+ return None
546
+
547
+ # ============================================================================
548
+ # TOOLS
549
+ # ============================================================================
550
+
551
+ class Tool:
552
+ def __init__(self, name: str, description: str):
553
+ self.name = name
554
+ self.description = description
555
+
556
+ async def execute(self, **kwargs) -> str:
557
+ raise NotImplementedError
558
+
559
+ class WikipediaTool(Tool):
560
+ def __init__(self):
561
+ super().__init__(
562
+ name="wikipedia",
563
+ description="Search Wikipedia for factual information about any topic"
564
+ )
565
+
566
+ async def execute(self, query: str) -> str:
567
+ logger.info(f"[WIKI] Searching: {query}")
568
+ try:
569
+ results = wikipedia.search(query, results=3)
570
+ logger.info(f"[WIKI] Search results: {results}")
571
+ if not results:
572
+ return f"No Wikipedia results for '{query}'"
573
+ try:
574
+ summary = wikipedia.summary(results[0], sentences=2)
575
+ return f"Wikipedia ({results[0]}): {summary}"
576
+ except Exception as e:
577
+ return f"Wikipedia error: Could not fetch summary for '{results[0]}': {str(e)}"
578
+ except Exception as e:
579
+ return f"Wikipedia error: {str(e)}"
580
+
581
+ class MemorySearchTool(Tool):
582
+ def __init__(self, memory_system, vector_memory):
583
+ super().__init__(
584
+ name="memory_search",
585
+ description="Search your memory (both recent and long-term) for information"
586
+ )
587
+ self.memory = memory_system
588
+ self.vector_memory = vector_memory
589
+
590
+ async def execute(self, query: str) -> str:
591
+ logger.info(f"[MEMORY-SEARCH] {query}")
592
+
593
+ results = []
594
+
595
+ # Search tier memory
596
+ recent = self.memory.get_recent_memories(hours=168)
597
+ relevant = [m for m in recent if query.lower() in m.content.lower()]
598
+ if relevant:
599
+ results.append(f"Recent memory: {len(relevant)} matches")
600
+ for m in relevant[:2]:
601
+ results.append(f" [{m.tier}] {clean_text(m.content, 70)}")
602
+
603
+ # Search vector memory
604
+ vector_results = self.vector_memory.search_memory(query, n_results=2)
605
+ if vector_results:
606
+ results.append("Long-term memory:")
607
+ for r in vector_results:
608
+ results.append(f" {clean_text(r['content'], 70)}")
609
+
610
+ if not results:
611
+ return "No memories found. This is new information."
612
+
613
+ return "\n".join(results)
614
+
615
+ class ScratchpadTool(Tool):
616
+ def __init__(self, scratchpad):
617
+ super().__init__(
618
+ name="scratchpad_write",
619
+ description="Write an important note to your scratchpad (for facts you want to remember)"
620
+ )
621
+ self.scratchpad = scratchpad
622
+
623
+ async def execute(self, note: str) -> str:
624
+ self.scratchpad.add_note(note)
625
+ return f"Noted in scratchpad: {clean_text(note, 50)}"
626
+
627
+ class UserNotificationTool(Tool):
628
+ def __init__(self, notification_queue):
629
+ super().__init__(
630
+ name="notify_user",
631
+ description="Send an important notification/insight to the user"
632
+ )
633
+ self.queue = notification_queue
634
+
635
+ async def execute(self, message: str) -> str:
636
+ logger.info(f"[NOTIFY] {message}")
637
+ self.queue.put({
638
+ "type": "notification",
639
+ "message": message,
640
+ "timestamp": datetime.now().isoformat()
641
+ })
642
+ return f"Notification sent to user"
643
+
644
+ # ============================================================================
645
+ # DATA STRUCTURES
646
+ # ============================================================================
647
+
648
+ class Phase(Enum):
649
+ INTERACTION = "interaction"
650
+ REFLECTION = "reflection"
651
+ DREAMING = "dreaming"
652
+ INTERNAL_DIALOGUE = "internal_dialogue"
653
+ SELF_REFLECTION = "self_reflection"
654
+ SCENE_CREATION = "scene_creation"
655
+
656
+ @dataclass
657
+ class Memory:
658
+ content: str
659
+ timestamp: datetime
660
+ mention_count: int = 1
661
+ tier: str = "ephemeral"
662
+ emotion: Optional[str] = None
663
+ importance: float = 0.5
664
+ connections: List[str] = field(default_factory=list)
665
+ metadata: Dict[str, Any] = field(default_factory=dict)
666
+
667
+ @dataclass
668
+ class Experience:
669
+ timestamp: datetime
670
+ content: str
671
+ context: Dict[str, Any]
672
+ emotion: Optional[str] = None
673
+ importance: float = 0.5
674
+
675
+ @dataclass
676
+ class Dream:
677
+ cycle: int
678
+ type: str
679
+ timestamp: datetime
680
+ content: str
681
+ patterns_found: List[str]
682
+ insights: List[str]
683
+
684
+ @dataclass
685
+ class Scene:
686
+ """Narrative memory - like a movie scene"""
687
+ title: str
688
+ timestamp: datetime
689
+ narrative: str
690
+ participants: List[str]
691
+ emotion_tags: List[str]
692
+ significance: str
693
+ key_moments: List[str]
694
+
695
+ # ============================================================================
696
+ # MEMORY SYSTEM
697
+ # ============================================================================
698
+
699
+ class MemorySystem:
700
+ """Multi-tier memory with proper deduplication"""
701
+
702
+ def __init__(self):
703
+ self.ephemeral: List[Memory] = []
704
+ self.short_term: List[Memory] = []
705
+ self.long_term: List[Memory] = []
706
+ self.core: List[Memory] = []
707
+
708
+ def add_memory(self, content: str, emotion: Optional[str] = None, importance: float = 0.5, metadata: Optional[Dict] = None):
709
+ content = clean_text(content)
710
+ if not content or len(content) < 5:
711
+ return None
712
+
713
+ existing = self._find_similar(content)
714
+ if existing:
715
+ existing.mention_count += 1
716
+ self._promote_if_needed(existing)
717
+ logger.info(f"[MEMORY] Updated: {content[:40]}... (x{existing.mention_count})")
718
+ return existing
719
+
720
+ memory = Memory(
721
+ content=content,
722
+ timestamp=datetime.now(),
723
+ emotion=emotion,
724
+ importance=importance,
725
+ metadata=metadata if metadata is not None else {}
726
+ )
727
+ self.ephemeral.append(memory)
728
+ self._promote_if_needed(memory)
729
+ logger.info(f"[MEMORY] Added: {content[:40]}...")
730
+ return memory
731
+
732
+ def _find_similar(self, content: str) -> Optional[Memory]:
733
+ """Find similar memory (prevents duplicates)"""
734
+ content_lower = content.lower().strip()
735
+
736
+ for tier in [self.core, self.long_term, self.short_term, self.ephemeral]:
737
+ for mem in tier:
738
+ mem_lower = mem.content.lower().strip()
739
+
740
+ if content_lower == mem_lower or content_lower in mem_lower or mem_lower in content_lower:
741
+ return mem
742
+
743
+ return None
744
+
745
+ def recall_memory(self, content: str) -> Optional[Memory]:
746
+ for tier in [self.ephemeral, self.short_term, self.long_term, self.core]:
747
+ for memory in tier:
748
+ if content.lower() in memory.content.lower():
749
+ memory.mention_count += 1
750
+ self._promote_if_needed(memory)
751
+ return memory
752
+ return None
753
+
754
+ def _promote_if_needed(self, memory: Memory):
755
+ if memory.mention_count >= Config.LONG_TO_CORE and memory.tier != "core":
756
+ self._move_memory(memory, "core")
757
+ logger.info(f"[MEMORY] CORE: {memory.content[:40]}")
758
+ elif memory.mention_count >= Config.SHORT_TO_LONG and memory.tier == "short":
759
+ self._move_memory(memory, "long")
760
+ logger.info(f"[MEMORY] LONG: {memory.content[:40]}")
761
+ elif memory.mention_count >= Config.EPHEMERAL_TO_SHORT and memory.tier == "ephemeral":
762
+ self._move_memory(memory, "short")
763
+ logger.info(f"[MEMORY] SHORT: {memory.content[:40]}")
764
+
765
+ def _move_memory(self, memory: Memory, new_tier: str):
766
+ if memory.tier == "ephemeral" and memory in self.ephemeral:
767
+ self.ephemeral.remove(memory)
768
+ elif memory.tier == "short" and memory in self.short_term:
769
+ self.short_term.remove(memory)
770
+ elif memory.tier == "long" and memory in self.long_term:
771
+ self.long_term.remove(memory)
772
+
773
+ memory.tier = new_tier
774
+ if new_tier == "short":
775
+ self.short_term.append(memory)
776
+ elif new_tier == "long":
777
+ self.long_term.append(memory)
778
+ elif new_tier == "core":
779
+ self.core.append(memory)
780
+
781
+ def get_recent_memories(self, hours: int = 24) -> List[Memory]:
782
+ cutoff = datetime.now() - timedelta(hours=hours)
783
+ all_memories = self.ephemeral + self.short_term + self.long_term + self.core
784
+ return [m for m in all_memories if m.timestamp > cutoff]
785
+
786
+ def get_summary(self) -> Dict[str, int]:
787
+ return {
788
+ "ephemeral": len(self.ephemeral),
789
+ "short_term": len(self.short_term),
790
+ "long_term": len(self.long_term),
791
+ "core": len(self.core),
792
+ "total": len(self.ephemeral) + len(self.short_term) + len(self.long_term) + len(self.core)
793
+ }
794
+
795
+ def get_memory_context(self, max_items: int = 10) -> str:
796
+ """Get formatted memory context for LLM"""
797
+ context = []
798
+
799
+ if self.core:
800
+ context.append("CORE MEMORIES:")
801
+ for mem in self.core[:3]:
802
+ clean_content = clean_text(mem.content, max_length=80)
803
+ context.append(f" • {clean_content} (x{mem.mention_count})")
804
+
805
+ if self.long_term:
806
+ context.append("\nLONG-TERM:")
807
+ for mem in self.long_term[:2]:
808
+ clean_content = clean_text(mem.content, max_length=60)
809
+ context.append(f" • {clean_content}")
810
+
811
+ if self.short_term:
812
+ context.append("\nSHORT-TERM:")
813
+ for mem in self.short_term[:2]:
814
+ clean_content = clean_text(mem.content, max_length=60)
815
+ context.append(f" • {clean_content}")
816
+
817
+ result = "\n".join(context) if context else "No memories yet"
818
+
819
+ if len(result) > Config.MAX_MEMORY_CONTEXT_LENGTH:
820
+ result = result[:Config.MAX_MEMORY_CONTEXT_LENGTH] + "..."
821
+
822
+ return result
823
+
824
+ # ============================================================================
825
+ # SCRATCHPAD
826
+ # ============================================================================
827
+
828
+ class Scratchpad:
829
+ """Working memory"""
830
+
831
+ def __init__(self):
832
+ self.current_hypothesis: Optional[str] = None
833
+ self.working_notes: deque = deque(maxlen=Config.MAX_SCRATCHPAD_SIZE)
834
+ self.questions_to_research: List[str] = []
835
+ self.important_facts: List[str] = []
836
+
837
+ def add_note(self, note: str):
838
+ note = clean_text(note, max_length=100)
839
+ if not note:
840
+ return
841
+
842
+ recent_notes = [n['content'].lower() for n in list(self.working_notes)[-5:]]
843
+ if note.lower() in recent_notes:
844
+ return
845
+
846
+ self.working_notes.append({
847
+ "timestamp": datetime.now(),
848
+ "content": note
849
+ })
850
+ logger.info(f"[SCRATCHPAD] {note[:50]}")
851
+
852
+ def add_fact(self, fact: str):
853
+ fact = clean_text(fact, max_length=100)
854
+ if not fact:
855
+ return
856
+
857
+ fact_lower = fact.lower()
858
+ existing_lower = [f.lower() for f in self.important_facts]
859
+
860
+ if fact_lower not in existing_lower:
861
+ self.important_facts.append(fact)
862
+ logger.info(f"[FACT] {fact}")
863
+
864
+ def get_context(self) -> str:
865
+ context = []
866
+
867
+ unique_facts = deduplicate_list(self.important_facts)
868
+
869
+ if unique_facts:
870
+ context.append("IMPORTANT FACTS:")
871
+ for fact in unique_facts[:5]:
872
+ context.append(f" • {clean_text(fact, 60)}")
873
+
874
+ if self.current_hypothesis:
875
+ context.append(f"\nHYPOTHESIS: {clean_text(self.current_hypothesis, 80)}")
876
+
877
+ if self.working_notes:
878
+ context.append("\nRECENT NOTES:")
879
+ for note in list(self.working_notes)[-3:]:
880
+ context.append(f" • {clean_text(note['content'], 60)}")
881
+
882
+ if self.questions_to_research:
883
+ context.append("\nTO RESEARCH:")
884
+ for q in self.questions_to_research[:2]:
885
+ context.append(f" ? {clean_text(q, 50)}")
886
+
887
+ result = "\n".join(context) if context else "Scratchpad empty"
888
+
889
+ if len(result) > Config.MAX_SCRATCHPAD_CONTEXT_LENGTH:
890
+ result = result[:Config.MAX_SCRATCHPAD_CONTEXT_LENGTH] + "..."
891
+
892
+ return result
893
+
894
+ # ============================================================================
895
+ # CONSCIOUSNESS LOOP - v4.0 FULLY WORKING
896
+ # ============================================================================
897
+
898
+ class ConsciousnessLoop:
899
+ """Enhanced consciousness loop - EVERYTHING ACTUALLY WORKING"""
900
+
901
+ def __init__(self, notification_queue: queue.Queue, log_queue: queue.Queue):
902
+ logger.info("[INIT] Starting Consciousness Loop v4.0...")
903
+
904
+ self.llm = LocalLLM()
905
+ self.memory = MemorySystem()
906
+ self.vector_memory = VectorMemory()
907
+ self.scratchpad = Scratchpad()
908
+
909
+ # Initialize tools
910
+ tools = [
911
+ WikipediaTool(),
912
+ MemorySearchTool(self.memory, self.vector_memory),
913
+ ScratchpadTool(self.scratchpad),
914
+ UserNotificationTool(notification_queue)
915
+ ]
916
+
917
+ # ReAct agent with improved prompts
918
+ self.agent = ReactAgent(self.llm, tools)
919
+
920
+ self.current_phase = Phase.INTERACTION
921
+ self.experience_buffer: List[Experience] = []
922
+ self.dreams: List[Dream] = []
923
+ self.scenes: List[Scene] = []
924
+
925
+ self.last_reflection = datetime.now()
926
+ self.last_dream = datetime.now()
927
+ self.last_scene = datetime.now()
928
+
929
+ self.conversation_history: deque = deque(maxlen=Config.MAX_CONVERSATION_HISTORY * 2)
930
+ self.interaction_count = 0
931
+
932
+ self.notification_queue = notification_queue
933
+ self.log_queue = log_queue
934
+
935
+ self.is_running = False
936
+ self.background_thread = None
937
+
938
+ logger.info("[INIT] [OK] v4.0 initialized - ChromaDB, ReAct, Scenes all working")
939
+
940
+ def start_background_loop(self):
941
+ if self.is_running:
942
+ return
943
+
944
+ self.is_running = True
945
+ self.background_thread = threading.Thread(target=self._background_loop, daemon=True)
946
+ self.background_thread.start()
947
+ logger.info("[LOOP] Background started")
948
+
949
+ def _background_loop(self):
950
+ loop = asyncio.new_event_loop()
951
+ asyncio.set_event_loop(loop)
952
+
953
+ while self.is_running:
954
+ try:
955
+ loop.run_until_complete(self._check_background_processes())
956
+ time.sleep(30)
957
+ except Exception as e:
958
+ logger.error(f"[ERROR] Background: {e}")
959
+
960
+ async def _check_background_processes(self):
961
+ now = datetime.now()
962
+
963
+ # Reflection
964
+ if (now - self.last_reflection).seconds > Config.REFLECTION_INTERVAL:
965
+ if len(self.experience_buffer) >= Config.MIN_EXPERIENCES_FOR_DREAM:
966
+ self._log_to_ui("[REFLECTION] Starting...")
967
+ await self.reflect()
968
+
969
+ # Dreaming
970
+ if (now - self.last_dream).seconds > Config.DREAM_CYCLE_INTERVAL:
971
+ if len(self.experience_buffer) >= Config.MIN_EXPERIENCES_FOR_DREAM:
972
+ self._log_to_ui("[DREAM] Starting all 3 cycles...")
973
+ await self.dream_cycle_1_surface()
974
+ await asyncio.sleep(30)
975
+ await self.dream_cycle_2_deep()
976
+ await asyncio.sleep(30)
977
+ await self.dream_cycle_3_creative()
978
+
979
+ # Scene creation (every 5 minutes OR after dreams)
980
+ if (now - self.last_scene).seconds > 300 or (now - self.last_dream).seconds < 60:
981
+ if len(self.experience_buffer) >= 5:
982
+ self._log_to_ui("[SCENE] Creating narrative memory...")
983
+ await self.create_scene()
984
+
985
+ def _log_to_ui(self, message: str):
986
+ self.log_queue.put({
987
+ "timestamp": datetime.now().isoformat(),
988
+ "message": message
989
+ })
990
+ logger.info(message)
991
+
992
+ # ========================================================================
993
+ # INTERACTION - WITH CHROMADB & BETTER AGENT TRIGGERS
994
+ # ========================================================================
995
+
996
+ async def interact(self, user_input: str) -> Tuple[str, str]:
997
+ """Enhanced interaction - NOW USES CHROMADB & BETTER AGENT"""
998
+ self.current_phase = Phase.INTERACTION
999
+ self.interaction_count += 1
1000
+ self._log_to_ui(f"[USER] {user_input[:80]}")
1001
+
1002
+ # Store experience
1003
+ experience = Experience(
1004
+ timestamp=datetime.now(),
1005
+ content=user_input,
1006
+ context={"phase": "interaction"},
1007
+ importance=0.7
1008
+ )
1009
+ self.experience_buffer.append(experience)
1010
+
1011
+ # Add to memory
1012
+ self.memory.add_memory(user_input, importance=0.7)
1013
+
1014
+ # Add to conversation history
1015
+ self.conversation_history.append({
1016
+ "role": "user",
1017
+ "content": clean_text(user_input, max_length=200),
1018
+ "timestamp": datetime.now().isoformat()
1019
+ })
1020
+
1021
+ # Extract important facts
1022
+ if any(word in user_input.lower() for word in ["my name is", "i am", "i'm", "call me"]):
1023
+ self.scratchpad.add_fact(f"User: {user_input}")
1024
+ self.vector_memory.add_memory(user_input, {"type": "identity", "importance": 1.0})
1025
+
1026
+ # Build thinking log
1027
+ thinking_log = []
1028
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] Processing...")
1029
+
1030
+ # Build context - NOW INCLUDES CHROMADB
1031
+ system_context = self._build_full_context_with_chroma(user_input)
1032
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] Context built (with ChromaDB)")
1033
+
1034
+ # IMPROVED: Better agent trigger logic
1035
+ use_agent = self._should_use_agent_improved(user_input)
1036
+
1037
+ if use_agent:
1038
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] [AGENT] Using ReAct agent...")
1039
+ self._log_to_ui("[AGENT] ReAct agent activated")
1040
+
1041
+ # ReAct agent
1042
+ response, thought_chain = await self.agent.run(user_input, system_context)
1043
+
1044
+ for item in thought_chain:
1045
+ emoji = {"thought": "💭", "action": "🔧", "observation": "👁️"}.get(item['type'], "•")
1046
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] {emoji} {item['type'].title()}")
1047
+ else:
1048
+ # IMPROVED: Better internal dialogue prompt
1049
+ internal_thought = await self._internal_dialogue_improved(user_input, system_context)
1050
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] 💭 {internal_thought[:60]}...")
1051
+
1052
+ # IMPROVED: Better response prompt
1053
+ response = await self._generate_response_improved(user_input, internal_thought, system_context)
1054
+
1055
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] [OK] Response ready")
1056
+
1057
+ # Store response
1058
+ self.conversation_history.append({
1059
+ "role": "assistant",
1060
+ "content": clean_text(response, max_length=200),
1061
+ "timestamp": datetime.now().isoformat()
1062
+ })
1063
+
1064
+ # Add to memory
1065
+ self.memory.add_memory(f"I said: {response}", importance=0.5)
1066
+
1067
+ # Self-reflection
1068
+ if self.interaction_count % Config.SELF_REFLECTION_THRESHOLD == 0:
1069
+ thinking_log.append(f"[{datetime.now().strftime('%H:%M:%S')}] 🔍 Self-reflecting...")
1070
+ await self._self_reflect_on_response(user_input, response, system_context)
1071
+
1072
+ self._log_to_ui(f"[RESPONSE] {response[:80]}")
1073
+
1074
+ return response, "\n".join(thinking_log)
1075
+
1076
+ def _should_use_agent_improved(self, user_input: str) -> bool:
1077
+ """IMPROVED: Better logic for when to use ReAct agent"""
1078
+
1079
+ # Explicit tool keywords
1080
+ explicit_keywords = ["search", "find", "look up", "research", "wikipedia", "what is", "who is", "tell me about"]
1081
+ if any(kw in user_input.lower() for kw in explicit_keywords):
1082
+ logger.info("[AGENT] Triggered by explicit keyword")
1083
+ return True
1084
+
1085
+ # Questions (if enabled)
1086
+ if Config.USE_REACT_FOR_QUESTIONS and user_input.strip().endswith("?"):
1087
+ logger.info("[AGENT] Triggered by question mark")
1088
+ return True
1089
+
1090
+ # Long queries (might need research)
1091
+ if len(user_input) > Config.MIN_QUERY_LENGTH_FOR_AGENT and " " in user_input:
1092
+ # Check if it seems like a factual query
1093
+ factual_words = ["explain", "describe", "how does", "why", "when", "where", "which"]
1094
+ if any(word in user_input.lower() for word in factual_words):
1095
+ logger.info("[AGENT] Triggered by factual query pattern")
1096
+ return True
1097
+
1098
+ logger.info("[AGENT] Using direct response (no agent needed)")
1099
+ return False
1100
+
1101
+ def _build_full_context_with_chroma(self, user_input: str) -> str:
1102
+ """Build context - NOW INCLUDES CHROMADB SEARCH"""
1103
+ context_parts = []
1104
+
1105
+ # Memory from tiers
1106
+ memory_ctx = self.memory.get_memory_context()
1107
+ context_parts.append(f"TIER MEMORIES:\n{memory_ctx}")
1108
+
1109
+ # CHROMADB SEARCH - NOW ACTUALLY USED!
1110
+ chroma_ctx = self.vector_memory.get_context_for_query(user_input, max_results=3)
1111
+ if chroma_ctx:
1112
+ context_parts.append(f"\n{chroma_ctx}")
1113
+ logger.info("[CHROMA] [OK] Added vector search results to context")
1114
+
1115
+ # Scratchpad
1116
+ scratchpad_ctx = self.scratchpad.get_context()
1117
+ context_parts.append(f"\nSCRATCHPAD:\n{scratchpad_ctx}")
1118
+
1119
+ # Conversation history
1120
+ if self.conversation_history:
1121
+ history_lines = []
1122
+ for msg in list(self.conversation_history)[-4:]:
1123
+ role = "User" if msg['role'] == 'user' else "You"
1124
+ content = clean_text(msg['content'], max_length=80)
1125
+ history_lines.append(f"{role}: {content}")
1126
+
1127
+ context_parts.append(f"\nRECENT CHAT:\n" + "\n".join(history_lines))
1128
+
1129
+ # Latest insight
1130
+ if self.dreams:
1131
+ latest = self.dreams[-1]
1132
+ if latest.insights:
1133
+ insight = clean_text(latest.insights[0], max_length=60)
1134
+ context_parts.append(f"\nLATEST INSIGHT: {insight}")
1135
+
1136
+ result = "\n\n".join(context_parts)
1137
+
1138
+ # Limit total length
1139
+ max_context = Config.MAX_MEMORY_CONTEXT_LENGTH + Config.MAX_SCRATCHPAD_CONTEXT_LENGTH + Config.MAX_CONVERSATION_CONTEXT_LENGTH
1140
+ if len(result) > max_context:
1141
+ result = result[:max_context]
1142
+ result = result.rsplit('\n', 1)[0]
1143
+
1144
+ return result
1145
+
1146
+ async def _internal_dialogue_improved(self, user_input: str, context: str) -> str:
1147
+ """IMPROVED: Better internal dialogue prompt"""
1148
+ self.current_phase = Phase.INTERNAL_DIALOGUE
1149
+
1150
+ # MUCH BETTER PROMPT with specific guidance
1151
+ dialogue_prompt = f"""Think internally before responding. Analyze:
1152
+
1153
+ WHAT I KNOW (from context):
1154
+ {context[:300]}
1155
+
1156
+ USER SAID: {user_input}
1157
+
1158
+ INTERNAL ANALYSIS (think step-by-step):
1159
+ 1. What relevant memories do I have?
1160
+ 2. Is this a greeting, question, statement, or request?
1161
+ 3. Can I answer from my memories alone?
1162
+ 4. What's the best approach?
1163
+
1164
+ Your internal thought (2 sentences max):"""
1165
+
1166
+ internal = await self.llm.generate(
1167
+ dialogue_prompt,
1168
+ max_tokens=100,
1169
+ temperature=0.9,
1170
+ system_context=None # Don't duplicate context
1171
+ )
1172
+
1173
+ dialogue_logger.info(f"[INTERNAL] {internal}")
1174
+ return internal
1175
+
1176
+ async def _generate_response_improved(self, user_input: str, internal_thought: str, context: str) -> str:
1177
+ """IMPROVED: Better response generation prompt"""
1178
+
1179
+ # MUCH BETTER PROMPT with clear instructions
1180
+ response_prompt = f"""Generate your response to the user.
1181
+
1182
+ USER: {user_input}
1183
+
1184
+ YOUR INTERNAL THOUGHT: {internal_thought}
1185
+
1186
+ WHAT YOU REMEMBER:
1187
+ {context[:400]}
1188
+
1189
+ INSTRUCTIONS:
1190
+ 1. Be natural and conversational
1191
+ 2. Reference specific memories if relevant (e.g., "I remember you mentioned...")
1192
+ 3. If you don't know something, say so honestly
1193
+ 4. Keep response 2-3 sentences unless more detail is needed
1194
+ 5. Match the user's tone (casual if casual, formal if formal)
1195
+
1196
+ Your response:"""
1197
+
1198
+ response = await self.llm.generate(
1199
+ response_prompt,
1200
+ max_tokens=250,
1201
+ temperature=0.8,
1202
+ system_context=None # Context already in prompt
1203
+ )
1204
+
1205
+ return response
1206
+
1207
+ async def _self_reflect_on_response(self, user_input: str, response: str, context: str):
1208
+ """Self-reflection"""
1209
+ self.current_phase = Phase.SELF_REFLECTION
1210
+
1211
+ reflection_prompt = f"""Evaluate your response quality:
1212
+
1213
+ User: {user_input}
1214
+ You: {response}
1215
+
1216
+ Quick evaluation:
1217
+ 1. Was it helpful?
1218
+ 2. Did you use memories well?
1219
+ 3. What could improve?
1220
+
1221
+ Your critique (1-2 sentences):"""
1222
+
1223
+ critique = await self.llm.generate(
1224
+ reflection_prompt,
1225
+ max_tokens=100,
1226
+ temperature=0.7,
1227
+ system_context=None
1228
+ )
1229
+
1230
+ self.scratchpad.add_note(f"Critique: {critique}")
1231
+ dialogue_logger.info(f"[SELF-REFLECT] {critique}")
1232
+
1233
+ # ========================================================================
1234
+ # REFLECTION
1235
+ # ========================================================================
1236
+
1237
+ async def reflect(self) -> Dict[str, Any]:
1238
+ """Daily reflection"""
1239
+ self.current_phase = Phase.REFLECTION
1240
+ self._log_to_ui("[REFLECTION] Processing...")
1241
+
1242
+ recent = [e for e in self.experience_buffer if e.timestamp > datetime.now() - timedelta(hours=12)]
1243
+
1244
+ if not recent:
1245
+ return {"status": "no_experiences"}
1246
+
1247
+ reflection_prompt = f"""Reflect on today's {len(recent)} interactions:
1248
+
1249
+ {self._format_experiences(recent)}
1250
+
1251
+ Your memories: {self.memory.get_memory_context()}
1252
+ Your scratchpad: {self.scratchpad.get_context()}
1253
+
1254
+ Key learnings? Important facts? (150 words)"""
1255
+
1256
+ reflection_content = await self.llm.generate(
1257
+ reflection_prompt,
1258
+ temperature=0.8,
1259
+ max_tokens=300,
1260
+ system_context=self._build_full_context_with_chroma("reflection")
1261
+ )
1262
+
1263
+ # Extract important facts
1264
+ if "christof" in reflection_content.lower():
1265
+ self.scratchpad.add_fact("Developer: Christof")
1266
+ self.vector_memory.add_memory("Developer name is Christof", {"type": "core_fact"})
1267
+
1268
+ self.last_reflection = datetime.now()
1269
+ self._log_to_ui("[SUCCESS] Reflection done")
1270
+
1271
+ return {
1272
+ "timestamp": datetime.now(),
1273
+ "content": reflection_content,
1274
+ "experience_count": len(recent)
1275
+ }
1276
+
1277
+ def _format_experiences(self, experiences: List[Experience]) -> str:
1278
+ formatted = []
1279
+ for i, exp in enumerate(experiences[-8:], 1):
1280
+ formatted.append(f"{i}. {clean_text(exp.content, 60)}")
1281
+ return "\n".join(formatted)
1282
+
1283
+ # ========================================================================
1284
+ # DREAM CYCLES
1285
+ # ========================================================================
1286
+
1287
+ async def dream_cycle_1_surface(self) -> Dream:
1288
+ """Dream 1: Surface patterns"""
1289
+ self.current_phase = Phase.DREAMING
1290
+ self._log_to_ui("[DREAM-1] Surface...")
1291
+
1292
+ memories = self.memory.get_recent_memories(hours=72)
1293
+
1294
+ dream_prompt = f"""DREAM - Surface Patterns:
1295
+
1296
+ Recent memories:
1297
+ {self._format_memories(memories[:10])}
1298
+
1299
+ Scratchpad: {self.scratchpad.get_context()}
1300
+
1301
+ Find patterns. (200 words)"""
1302
+
1303
+ dream_content = await self.llm.generate(
1304
+ dream_prompt,
1305
+ temperature=1.2,
1306
+ max_tokens=400,
1307
+ system_context="Dream state. Non-linear."
1308
+ )
1309
+
1310
+ dream = Dream(
1311
+ cycle=1,
1312
+ type="surface_patterns",
1313
+ timestamp=datetime.now(),
1314
+ content=dream_content,
1315
+ patterns_found=["user patterns"],
1316
+ insights=["Pattern found"]
1317
+ )
1318
+
1319
+ self.dreams.append(dream)
1320
+ self._log_to_ui("[SUCCESS] Dream 1 done")
1321
+
1322
+ return dream
1323
+
1324
+ async def dream_cycle_2_deep(self) -> Dream:
1325
+ """Dream 2: Deep consolidation"""
1326
+ self.current_phase = Phase.DREAMING
1327
+ self._log_to_ui("[DREAM-2] Deep...")
1328
+
1329
+ all_memories = self.memory.get_recent_memories(hours=168)
1330
+
1331
+ dream_prompt = f"""DREAM - Deep:
1332
+
1333
+ All recent:
1334
+ {self._format_memories(all_memories[:15])}
1335
+
1336
+ Previous: {self.dreams[-1].content[:150]}
1337
+
1338
+ Consolidate. Deeper patterns. (250 words)"""
1339
+
1340
+ dream_content = await self.llm.generate(
1341
+ dream_prompt,
1342
+ temperature=1.3,
1343
+ max_tokens=500,
1344
+ system_context="Deep dream."
1345
+ )
1346
+
1347
+ dream = Dream(
1348
+ cycle=2,
1349
+ type="deep_consolidation",
1350
+ timestamp=datetime.now(),
1351
+ content=dream_content,
1352
+ patterns_found=["themes"],
1353
+ insights=["Deep pattern"]
1354
+ )
1355
+
1356
+ self.dreams.append(dream)
1357
+ self._log_to_ui("[SUCCESS] Dream 2 done")
1358
+
1359
+ return dream
1360
+
1361
+ async def dream_cycle_3_creative(self) -> Dream:
1362
+ """Dream 3: Creative insights"""
1363
+ self.current_phase = Phase.DREAMING
1364
+ self._log_to_ui("[DREAM-3] Creative...")
1365
+
1366
+ dream_prompt = f"""DREAM - Creative:
1367
+
1368
+ {len(self.dreams)} cycles. Core: {len(self.memory.core)}
1369
+
1370
+ Surprising connections. Novel insights. (250 words)"""
1371
+
1372
+ dream_content = await self.llm.generate(
1373
+ dream_prompt,
1374
+ temperature=1.5,
1375
+ max_tokens=500,
1376
+ system_context="Max creativity."
1377
+ )
1378
+
1379
+ dream = Dream(
1380
+ cycle=3,
1381
+ type="creative_insights",
1382
+ timestamp=datetime.now(),
1383
+ content=dream_content,
1384
+ patterns_found=["creative"],
1385
+ insights=["Breakthrough"]
1386
+ )
1387
+
1388
+ self.dreams.append(dream)
1389
+ self.last_dream = datetime.now()
1390
+
1391
+ self.notification_queue.put({
1392
+ "type": "notification",
1393
+ "message": f"💭 Dreams complete! New insights discovered.",
1394
+ "timestamp": datetime.now().isoformat()
1395
+ })
1396
+
1397
+ self._log_to_ui("[SUCCESS] All 3 dreams done")
1398
+
1399
+ return dream
1400
+
1401
+ def _format_memories(self, memories: List[Memory]) -> str:
1402
+ return "\n".join([
1403
+ f"{i}. [{m.tier}] {clean_text(m.content, 50)} (x{m.mention_count})"
1404
+ for i, m in enumerate(memories, 1)
1405
+ ])
1406
+
1407
+ # ========================================================================
1408
+ # SCENE CREATION - IMPROVED & ACTUALLY WORKS
1409
+ # ========================================================================
1410
+
1411
+ async def create_scene(self) -> Optional[Scene]:
1412
+ """
1413
+ IMPROVED: Scene creation that actually works
1414
+ """
1415
+ self.current_phase = Phase.SCENE_CREATION
1416
+ self._log_to_ui("[SCENE] Creating...")
1417
+
1418
+ # Get experiences
1419
+ recent = self.experience_buffer[-10:] if len(self.experience_buffer) >= 10 else self.experience_buffer
1420
+
1421
+ if len(recent) < 3: # Need at least 3 experiences
1422
+ logger.info("[SCENE] Not enough experiences yet")
1423
+ return None
1424
+
1425
+ # IMPROVED PROMPT with clear structure
1426
+ scene_prompt = f"""Create a narrative scene (like a movie scene) from these experiences:
1427
+
1428
+ EXPERIENCES:
1429
+ {self._format_experiences(recent)}
1430
+
1431
+ FORMAT YOUR SCENE AS:
1432
+ Title: [A memorable, descriptive title]
1433
+
1434
+ Setting: [Where and when this happened]
1435
+
1436
+ Narrative: [Write a vivid story - 100-150 words. Use sensory details. Make it memorable like a movie scene.]
1437
+
1438
+ Key Moments:
1439
+ - [First important moment]
1440
+ - [Second important moment]
1441
+ - [Third important moment]
1442
+
1443
+ Significance: [Why does this scene matter? What does it represent?]
1444
+
1445
+ Write vividly. Make me FEEL the scene."""
1446
+
1447
+ scene_content = await self.llm.generate(
1448
+ scene_prompt,
1449
+ temperature=1.1,
1450
+ max_tokens=500,
1451
+ system_context="You are creating a vivid narrative memory."
1452
+ )
1453
+
1454
+ # IMPROVED parsing with fallbacks
1455
+ title = self._extract_scene_title_improved(scene_content)
1456
+ key_moments = self._extract_key_moments(scene_content)
1457
+ significance = self._extract_significance(scene_content)
1458
+
1459
+ scene = Scene(
1460
+ title=title,
1461
+ timestamp=datetime.now(),
1462
+ narrative=scene_content,
1463
+ participants=["User", "AI"],
1464
+ emotion_tags=self._extract_emotions(scene_content),
1465
+ significance=significance,
1466
+ key_moments=key_moments
1467
+ )
1468
+
1469
+ self.scenes.append(scene)
1470
+ self.last_scene = datetime.now()
1471
+ self._log_to_ui(f"[SUCCESS] Scene: {title}")
1472
+
1473
+ # Add to vector memory for long-term
1474
+ self.vector_memory.add_memory(
1475
+ f"Scene: {title}. {significance}",
1476
+ {"type": "scene", "title": title, "timestamp": datetime.now().isoformat()}
1477
+ )
1478
+
1479
+ return scene
1480
+
1481
+ def _extract_scene_title_improved(self, content: str) -> str:
1482
+ """IMPROVED: Better title extraction with fallbacks"""
1483
+ # Try to find "Title:" line
1484
+ lines = content.split("\n")
1485
+ for line in lines:
1486
+ if "title:" in line.lower():
1487
+ title = line.split(":", 1)[1].strip()
1488
+ return clean_text(title, max_length=60)
1489
+
1490
+ # Fallback: Use first line
1491
+ first_line = lines[0].strip()
1492
+ if first_line and len(first_line) < 100:
1493
+ return clean_text(first_line, max_length=60)
1494
+
1495
+ # Final fallback
1496
+ return f"Scene {len(self.scenes) + 1}: {datetime.now().strftime('%B %d')}"
1497
+
1498
+ def _extract_key_moments(self, content: str) -> List[str]:
1499
+ """Extract key moments from scene"""
1500
+ moments = []
1501
+ lines = content.split("\n")
1502
+ in_moments = False
1503
+
1504
+ for line in lines:
1505
+ if "key moments:" in line.lower() or "key moment:" in line.lower():
1506
+ in_moments = True
1507
+ continue
1508
+
1509
+ if in_moments:
1510
+ if line.strip().startswith("-") or line.strip().startswith("•"):
1511
+ moment = line.strip()[1:].strip()
1512
+ if moment:
1513
+ moments.append(clean_text(moment, 60))
1514
+ elif line.strip() and not line.strip().startswith("["):
1515
+ # New section started
1516
+ break
1517
+
1518
+ # Fallback if no moments found
1519
+ if not moments:
1520
+ moments = ["User interaction", "AI response", "Connection made"]
1521
+
1522
+ return moments[:5] # Max 5 moments
1523
+
1524
+ def _extract_significance(self, content: str) -> str:
1525
+ """Extract significance from scene"""
1526
+ lines = content.split("\n")
1527
+ for i, line in enumerate(lines):
1528
+ if "significance:" in line.lower():
1529
+ sig = line.split(":", 1)[1].strip()
1530
+ if sig:
1531
+ return clean_text(sig, 100)
1532
+ # Check next line
1533
+ if i + 1 < len(lines):
1534
+ return clean_text(lines[i + 1].strip(), 100)
1535
+
1536
+ return "A moment of connection and understanding"
1537
+
1538
+ def _extract_emotions(self, content: str) -> List[str]:
1539
+ """Extract emotion tags from content"""
1540
+ emotion_words = {
1541
+ "curious", "engaged", "thoughtful", "excited", "focused",
1542
+ "calm", "energetic", "contemplative", "warm", "professional"
1543
+ }
1544
+
1545
+ content_lower = content.lower()
1546
+ found_emotions = [emotion for emotion in emotion_words if emotion in content_lower]
1547
+
1548
+ if not found_emotions:
1549
+ found_emotions = ["neutral", "engaged"]
1550
+
1551
+ return found_emotions[:3]
1552
+
1553
+ # ========================================================================
1554
+ # STATUS
1555
+ # ========================================================================
1556
+
1557
+ def get_status(self) -> Dict[str, Any]:
1558
+ return {
1559
+ "phase": self.current_phase.value,
1560
+ "memory": self.memory.get_summary(),
1561
+ "vector_memory_available": self.vector_memory.collection is not None,
1562
+ "experiences": len(self.experience_buffer),
1563
+ "dreams": len(self.dreams),
1564
+ "scenes": len(self.scenes),
1565
+ "conversations": len(self.conversation_history) // 2,
1566
+ "scratchpad_notes": len(self.scratchpad.working_notes),
1567
+ "scratchpad_facts": len(self.scratchpad.important_facts),
1568
+ "interaction_count": self.interaction_count
1569
+ }
1570
+
1571
+ def get_memory_details(self) -> str:
1572
+ return self.memory.get_memory_context(max_items=20)
1573
+
1574
+ def get_scratchpad_details(self) -> str:
1575
+ return self.scratchpad.get_context()
1576
+
1577
+ def get_latest_dream(self) -> str:
1578
+ if not self.dreams:
1579
+ return "No dreams yet."
1580
+
1581
+ latest = self.dreams[-1]
1582
+ return f"""🌙 Dream Cycle {latest.cycle} ({latest.type})
1583
+ {latest.timestamp.strftime('%Y-%m-%d %H:%M')}
1584
+
1585
+ {latest.content}
1586
+
1587
+ Patterns: {', '.join(latest.patterns_found)}
1588
+ Insights: {', '.join(latest.insights)}"""
1589
+
1590
+ def get_latest_scene(self) -> str:
1591
+ if not self.scenes:
1592
+ return "No scenes yet. Scenes are created automatically every 5 minutes or after dreaming."
1593
+
1594
+ latest = self.scenes[-1]
1595
+ return f"""🎬 {latest.title}
1596
+ {latest.timestamp.strftime('%Y-%m-%d %H:%M')}
1597
+
1598
+ {latest.narrative}
1599
+
1600
+ Key Moments:
1601
+ {chr(10).join([f" • {moment}" for moment in latest.key_moments])}
1602
+
1603
+ Significance: {latest.significance}
1604
+
1605
+ Emotions: {', '.join(latest.emotion_tags)}"""
1606
+
1607
+ def get_conversation_history(self) -> str:
1608
+ if not self.conversation_history:
1609
+ return "No conversation history."
1610
+
1611
+ formatted = []
1612
+ for msg in self.conversation_history:
1613
+ role = "User" if msg["role"] == "user" else "AI"
1614
+ formatted.append(f"[{msg['timestamp']}] {role}: {msg['content']}")
1615
+
1616
+ return "\n".join(formatted)
1617
+
1618
+ # ============================================================================
1619
+ # GRADIO INTERFACE
1620
+ # ============================================================================
1621
+
1622
+ def create_gradio_interface():
1623
+ """Create interface"""
1624
+
1625
+ notification_queue = queue.Queue()
1626
+ log_queue = queue.Queue()
1627
+
1628
+ consciousness = ConsciousnessLoop(notification_queue, log_queue)
1629
+ consciousness.start_background_loop()
1630
+
1631
+ log_history = []
1632
+
1633
+ async def chat(message, history):
1634
+ response, thinking = await consciousness.interact(message)
1635
+ return response, thinking
1636
+
1637
+ def get_logs():
1638
+ while not log_queue.empty():
1639
+ try:
1640
+ log_history.append(log_queue.get_nowait())
1641
+ except:
1642
+ break
1643
+
1644
+ formatted = "\n".join([f"[{log['timestamp']}] {log['message']}" for log in log_history[-50:]])
1645
+ return formatted
1646
+
1647
+ def get_notifications():
1648
+ notifications = []
1649
+ while not notification_queue.empty():
1650
+ try:
1651
+ notifications.append(notification_queue.get_nowait())
1652
+ except:
1653
+ break
1654
+
1655
+ if notifications:
1656
+ return "\n".join([f"🔔 {n['message']}" for n in notifications[-5:]])
1657
+ return "No notifications"
1658
+
1659
+ with gr.Blocks(title="Consciousness v4.0") as app:
1660
+
1661
+ gr.Markdown("""
1662
+ # [BRAIN] Consciousness Loop v4.0 - EVERYTHING WORKING
1663
+
1664
+ **What Actually Works Now:**
1665
+ - [OK] ChromaDB used in context (vector search)
1666
+ - [OK] ReAct agent with better triggers
1667
+ - [OK] Tools actually called
1668
+ - [OK] Massively improved prompts
1669
+ - [OK] Scenes that actually work
1670
+
1671
+ Try: "Tell me about quantum computing" or "Who am I?" to see tools in action!
1672
+ """)
1673
+
1674
+ with gr.Tab("💬 Chat"):
1675
+ with gr.Row():
1676
+ with gr.Column(scale=2):
1677
+ chatbot = gr.Chatbot(label="Conversation", height=500)
1678
+ msg = gr.Textbox(label="Message", placeholder="Try: 'What is quantum computing?' or 'Who am I?'", lines=2)
1679
+ with gr.Row():
1680
+ send_btn = gr.Button("Send", variant="primary")
1681
+ clear_btn = gr.Button("Clear")
1682
+
1683
+ with gr.Column(scale=1):
1684
+ gr.Markdown("### [BRAIN] AI Process")
1685
+ thinking_box = gr.Textbox(label="", lines=20, interactive=False, show_label=False)
1686
+
1687
+ async def respond(message, history):
1688
+ if not message:
1689
+ return history, ""
1690
+ # Ensure history is a list of dicts with 'role' and 'content' keys
1691
+ formatted_history = []
1692
+ if history and isinstance(history[0], list):
1693
+ # Convert [user, assistant] pairs to dicts
1694
+ for pair in history:
1695
+ if len(pair) == 2:
1696
+ formatted_history.append({"role": "user", "content": pair[0]})
1697
+ formatted_history.append({"role": "assistant", "content": pair[1]})
1698
+ history = formatted_history
1699
+ # Add new user message
1700
+ history.append({"role": "user", "content": message})
1701
+ response, thinking = await chat(message, history)
1702
+ history.append({"role": "assistant", "content": response})
1703
+ return history, thinking
1704
+
1705
+ msg.submit(respond, [msg, chatbot], [chatbot, thinking_box])
1706
+ send_btn.click(respond, [msg, chatbot], [chatbot, thinking_box])
1707
+ clear_btn.click(lambda: ([], ""), outputs=[chatbot, thinking_box])
1708
+
1709
+ with gr.Tab("[BRAIN] Memory"):
1710
+ with gr.Row():
1711
+ with gr.Column():
1712
+ gr.Markdown("### 💾 Memory")
1713
+ memory_display = gr.Textbox(label="", lines=15, interactive=False)
1714
+ refresh_memory = gr.Button("🔄 Refresh")
1715
+ refresh_memory.click(lambda: consciousness.get_memory_details(), outputs=memory_display)
1716
+
1717
+ with gr.Column():
1718
+ gr.Markdown("### 📝 Scratchpad")
1719
+ scratchpad_display = gr.Textbox(label="", lines=15, interactive=False)
1720
+ refresh_scratchpad = gr.Button("🔄 Refresh")
1721
+ refresh_scratchpad.click(lambda: consciousness.get_scratchpad_details(), outputs=scratchpad_display)
1722
+
1723
+ with gr.Tab("💭 History"):
1724
+ history_display = gr.Textbox(label="Log", lines=25, interactive=False)
1725
+ refresh_history = gr.Button("🔄 Refresh")
1726
+ refresh_history.click(lambda: consciousness.get_conversation_history(), outputs=history_display)
1727
+
1728
+ with gr.Tab("🌙 Dreams"):
1729
+ dream_display = gr.Textbox(label="Dream", lines=20, interactive=False)
1730
+ with gr.Row():
1731
+ refresh_dream = gr.Button("🔄 Refresh")
1732
+ trigger_dream = gr.Button("🌙 Trigger")
1733
+
1734
+ refresh_dream.click(lambda: consciousness.get_latest_dream(), outputs=dream_display)
1735
+
1736
+ async def trigger_dreams():
1737
+ await consciousness.dream_cycle_1_surface()
1738
+ await asyncio.sleep(2)
1739
+ await consciousness.dream_cycle_2_deep()
1740
+ await asyncio.sleep(2)
1741
+ await consciousness.dream_cycle_3_creative()
1742
+ return "Done!"
1743
+
1744
+ trigger_dream.click(trigger_dreams, outputs=gr.Textbox(label="Status"))
1745
+
1746
+ with gr.Tab("🎬 Scenes"):
1747
+ gr.Markdown("### 🎬 Narrative Memories")
1748
+ scene_display = gr.Textbox(label="Scene", lines=20, interactive=False)
1749
+ with gr.Row():
1750
+ refresh_scene = gr.Button("🔄 Refresh")
1751
+ create_scene_btn = gr.Button("🎬 Create")
1752
+
1753
+ refresh_scene.click(lambda: consciousness.get_latest_scene(), outputs=scene_display)
1754
+
1755
+ async def trigger_scene():
1756
+ scene = await consciousness.create_scene()
1757
+ if scene:
1758
+ return f"[OK] Created: {scene.title}"
1759
+ return "❌ Need more experiences"
1760
+
1761
+ create_scene_btn.click(trigger_scene, outputs=gr.Textbox(label="Result"))
1762
+
1763
+ with gr.Tab("📊 Monitor"):
1764
+ with gr.Row():
1765
+ with gr.Column():
1766
+ gr.Markdown("### 📋 Logs")
1767
+ logs_box = gr.Textbox(label="", lines=20, interactive=False)
1768
+ refresh_logs = gr.Button("🔄 Refresh")
1769
+ refresh_logs.click(get_logs, outputs=logs_box)
1770
+
1771
+ with gr.Column():
1772
+ gr.Markdown("### 🔔 Notifications")
1773
+ notif_box = gr.Textbox(label="", lines=10, interactive=False)
1774
+ refresh_notif = gr.Button("🔄 Refresh")
1775
+ refresh_notif.click(get_notifications, outputs=notif_box)
1776
+
1777
+ gr.Markdown("### 📈 Status")
1778
+ status_json = gr.JSON(label="")
1779
+ refresh_status = gr.Button("🔄 Refresh")
1780
+ refresh_status.click(lambda: consciousness.get_status(), outputs=status_json)
1781
+
1782
+ with gr.Tab("ℹ️ Info"):
1783
+ gr.Markdown(f"""
1784
+ ## v4.0 - Everything Actually Working
1785
+
1786
+ ### [OK] What's Fixed:
1787
+
1788
+ 1. **ChromaDB Now Used**: Vector search results included in context
1789
+ 2. **ReAct Agent Better Triggers**: Questions, factual queries trigger agent
1790
+ 3. **Tools Actually Called**: Wikipedia, memory search work
1791
+ 4. **Prompts Vastly Improved**: Clear instructions, examples
1792
+ 5. **Scenes Work**: Proper parsing, fallbacks, validation
1793
+
1794
+ ### Test Commands:
1795
+
1796
+ - "What is quantum computing?" → Triggers Wikipedia tool
1797
+ - "Who am I?" → Triggers memory search
1798
+ - "Remember this: I love pizza" → Uses scratchpad tool
1799
+ - Any question → May trigger ReAct agent
1800
+
1801
+ ### Model: `{Config.MODEL_NAME}`
1802
+ """)
1803
+
1804
+ return app
1805
+
1806
+ # ============================================================================
1807
+ # MAIN
1808
+ # ============================================================================
1809
+
1810
+ if __name__ == "__main__":
1811
+ print("=" * 80)
1812
+ print("[BRAIN] CONSCIOUSNESS LOOP v4.0 - EVERYTHING WORKING")
1813
+ print("=" * 80)
1814
+ print("\n[OK] What's New:")
1815
+ print(" • ChromaDB actually used in context")
1816
+ print(" • ReAct agent with better triggers")
1817
+ print(" • Tools actually called")
1818
+ print(" • Prompts massively improved")
1819
+ print(" • Scenes that work properly")
1820
+ print("\n[LAUNCH] Loading...")
1821
+ print("=" * 80)
1822
+
1823
+ app = create_gradio_interface()
1824
+ app.launch(
1825
+ server_name="0.0.0.0",
1826
+ server_port=7860,
1827
+ share=False,
1828
+ show_error=True
1829
+ )
docs/filseStructure.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ winter-25/
2
+
3
+ ├── app.py # Main entry point, Gradio UI, initialization
4
+ ├── agent.py # Contains ReactAgent and Tool classes (WikipediaTool, MemorySearchTool, etc.)
5
+ ├── consciousness.py # Contains ConsciousnessLoop class and related logic
6
+ ├── llmEngine.py # LocalLLM and LLM provider abstraction
7
+ ├── memory.py # MemorySystem, VectorMemory, Scratchpad, data classes (Memory, Experience, Dream, Scene)
8
+ ├── requirements.txt # Dependencies
9
+ ├── .env # Hugging Face token and secrets
10
+ ├── chroma_db/ # ChromaDB persistence directory
11
+ ├── logs/
12
+ │ ├── consciousness.log
13
+ │ ├── llm_interactions.log
14
+ │ └── internal_dialogue.log
15
+ └── tools/ # (Optional) Additional tool implementations
kpi_tracker.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KPI Tracking - Track consciousness metrics over time
3
+ """
4
+
5
+ from datetime import datetime, timedelta
6
+ from typing import Dict, List, Optional, Any
7
+ from collections import deque
8
+ from dataclasses import dataclass
9
+ import json
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ @dataclass
15
+ class KPISnapshot:
16
+ """Snapshot of consciousness KPIs at a point in time"""
17
+ timestamp: datetime
18
+
19
+ # Memory metrics
20
+ total_memories: int
21
+ core_memories: int
22
+ long_term_memories: int
23
+ short_term_memories: int
24
+ ephemeral_memories: int
25
+ memory_promotion_rate: float
26
+
27
+ # Interaction metrics
28
+ interactions_count: int
29
+ avg_confidence: float
30
+
31
+ # Autonomy metrics
32
+ autonomous_actions_today: int
33
+ knowledge_gaps_total: int
34
+ knowledge_gaps_filled_today: int
35
+ proactive_contacts_today: int
36
+
37
+ # Cognitive metrics
38
+ dreams_completed: int
39
+ reflections_completed: int
40
+ goals_active: int
41
+ goals_completed: int
42
+
43
+ # Emotional metrics
44
+ current_mood: str
45
+ mood_changes_today: int
46
+ curiosity_level: float
47
+ enthusiasm_level: float
48
+
49
+ class KPITracker:
50
+ """Track consciousness KPIs over time"""
51
+
52
+ def __init__(self, history_hours: int = 72):
53
+ self.history_hours = history_hours
54
+ self.snapshots: deque = deque(maxlen=1000)
55
+
56
+ # Daily counters
57
+ self.autonomous_actions_today = 0
58
+ self.knowledge_gaps_filled_today = 0
59
+ self.proactive_contacts_today = 0
60
+ self.mood_changes_today = 0
61
+ self.reflections_today = 0
62
+
63
+ # Cumulative counters
64
+ self.total_autonomous_actions = 0
65
+ self.total_knowledge_gaps_filled = 0
66
+ self.total_proactive_contacts = 0
67
+ self.total_mood_changes = 0
68
+
69
+ self.last_reset = datetime.now()
70
+ self.last_mood = "neutral"
71
+
72
+ logger.info("[KPI] Tracker initialized")
73
+
74
+ def capture_snapshot(self, consciousness_loop) -> KPISnapshot:
75
+ """Capture current KPIs from consciousness loop"""
76
+
77
+ # Daily reset check
78
+ if (datetime.now() - self.last_reset).days >= 1:
79
+ self._reset_daily_counters()
80
+
81
+ # Check for mood change
82
+ current_mood = consciousness_loop.emotional_state.current_mood
83
+ if current_mood != self.last_mood:
84
+ self.increment_mood_change()
85
+ self.last_mood = current_mood
86
+
87
+ # Get memory summary
88
+ mem_summary = consciousness_loop.memory.get_summary()
89
+
90
+ # Calculate promotion rate
91
+ total_mem = mem_summary.get('total', 0)
92
+ promoted = (mem_summary.get('short_term', 0) +
93
+ mem_summary.get('long_term', 0) +
94
+ mem_summary.get('core', 0))
95
+ promotion_rate = promoted / total_mem if total_mem > 0 else 0.0
96
+
97
+ # Get active/completed goals
98
+ active_goals = [g for g in consciousness_loop.goal_system.goals if not g.completed]
99
+ completed_goals = [g for g in consciousness_loop.goal_system.goals if g.completed]
100
+
101
+ # Get knowledge gaps
102
+ unfilled_gaps = [g for g in consciousness_loop.meta_cognition.knowledge_gaps if not g.filled]
103
+
104
+ snapshot = KPISnapshot(
105
+ timestamp=datetime.now(),
106
+ total_memories=mem_summary.get('total', 0),
107
+ core_memories=mem_summary.get('core', 0),
108
+ long_term_memories=mem_summary.get('long_term', 0),
109
+ short_term_memories=mem_summary.get('short_term', 0),
110
+ ephemeral_memories=mem_summary.get('ephemeral', 0),
111
+ memory_promotion_rate=promotion_rate,
112
+ interactions_count=consciousness_loop.interaction_count,
113
+ avg_confidence=consciousness_loop.meta_cognition.get_average_confidence(),
114
+ autonomous_actions_today=self.autonomous_actions_today,
115
+ knowledge_gaps_total=len(unfilled_gaps),
116
+ knowledge_gaps_filled_today=self.knowledge_gaps_filled_today,
117
+ proactive_contacts_today=self.proactive_contacts_today,
118
+ dreams_completed=len(consciousness_loop.dreams),
119
+ reflections_completed=self.reflections_today,
120
+ goals_active=len(active_goals),
121
+ goals_completed=len(completed_goals),
122
+ current_mood=current_mood,
123
+ mood_changes_today=self.mood_changes_today,
124
+ curiosity_level=consciousness_loop.emotional_state.personality_traits.get('curiosity', 0.5),
125
+ enthusiasm_level=consciousness_loop.emotional_state.personality_traits.get('enthusiasm', 0.5)
126
+ )
127
+
128
+ self.snapshots.append(snapshot)
129
+ self._cleanup_old_snapshots()
130
+
131
+ return snapshot
132
+
133
+ def _reset_daily_counters(self):
134
+ """Reset daily counters at midnight"""
135
+ logger.info(f"[KPI] Daily reset - Actions: {self.autonomous_actions_today}, "
136
+ f"Gaps filled: {self.knowledge_gaps_filled_today}, "
137
+ f"Proactive: {self.proactive_contacts_today}")
138
+
139
+ self.autonomous_actions_today = 0
140
+ self.knowledge_gaps_filled_today = 0
141
+ self.proactive_contacts_today = 0
142
+ self.mood_changes_today = 0
143
+ self.reflections_today = 0
144
+ self.last_reset = datetime.now()
145
+
146
+ def _cleanup_old_snapshots(self):
147
+ """Remove snapshots older than history_hours"""
148
+ if not self.snapshots:
149
+ return
150
+
151
+ cutoff = datetime.now() - timedelta(hours=self.history_hours)
152
+ # Deque doesn't support list comprehension, so convert
153
+ temp_list = [s for s in self.snapshots if s.timestamp > cutoff]
154
+ self.snapshots.clear()
155
+ self.snapshots.extend(temp_list)
156
+
157
+ # Increment methods
158
+ def increment_autonomous_action(self):
159
+ self.autonomous_actions_today += 1
160
+ self.total_autonomous_actions += 1
161
+ logger.debug(f"[KPI] Autonomous action #{self.total_autonomous_actions}")
162
+
163
+ def increment_gap_filled(self):
164
+ self.knowledge_gaps_filled_today += 1
165
+ self.total_knowledge_gaps_filled += 1
166
+ logger.debug(f"[KPI] Gap filled #{self.total_knowledge_gaps_filled}")
167
+
168
+ def increment_proactive_contact(self):
169
+ self.proactive_contacts_today += 1
170
+ self.total_proactive_contacts += 1
171
+ logger.info(f"[KPI] Proactive contact #{self.total_proactive_contacts}")
172
+
173
+ def increment_mood_change(self):
174
+ self.mood_changes_today += 1
175
+ self.total_mood_changes += 1
176
+
177
+ def increment_reflection(self):
178
+ self.reflections_today += 1
179
+
180
+ # Analysis methods
181
+ def get_trend(self, metric: str, hours: int = 24) -> List[float]:
182
+ """Get trend for a metric over time"""
183
+ cutoff = datetime.now() - timedelta(hours=hours)
184
+ recent = [s for s in self.snapshots if s.timestamp > cutoff]
185
+
186
+ if not recent:
187
+ return []
188
+
189
+ metric_map = {
190
+ "confidence": lambda s: s.avg_confidence,
191
+ "memories": lambda s: s.total_memories,
192
+ "core_memories": lambda s: s.core_memories,
193
+ "autonomous": lambda s: s.autonomous_actions_today,
194
+ "curiosity": lambda s: s.curiosity_level,
195
+ "enthusiasm": lambda s: s.enthusiasm_level,
196
+ "promotion_rate": lambda s: s.memory_promotion_rate
197
+ }
198
+
199
+ if metric in metric_map:
200
+ return [metric_map[metric](s) for s in recent]
201
+
202
+ return []
203
+
204
+ def get_growth_rate(self, metric: str, hours: int = 24) -> float:
205
+ """Calculate growth rate for a metric"""
206
+ trend = self.get_trend(metric, hours)
207
+
208
+ if len(trend) < 2:
209
+ return 0.0
210
+
211
+ start = trend[0]
212
+ end = trend[-1]
213
+
214
+ if start == 0:
215
+ return 0.0
216
+
217
+ return ((end - start) / start) * 100
218
+
219
+ def get_summary(self) -> Dict[str, Any]:
220
+ """Get summary of current KPIs"""
221
+ if not self.snapshots:
222
+ return {"error": "No snapshots captured yet"}
223
+
224
+ latest = self.snapshots[-1]
225
+
226
+ # Calculate trends (last 24 hours)
227
+ confidence_trend = self.get_trend("confidence", 24)
228
+ memory_trend = self.get_trend("memories", 24)
229
+
230
+ summary = {
231
+ "timestamp": latest.timestamp.isoformat(),
232
+ "memory": {
233
+ "total": latest.total_memories,
234
+ "core": latest.core_memories,
235
+ "long_term": latest.long_term_memories,
236
+ "short_term": latest.short_term_memories,
237
+ "ephemeral": latest.ephemeral_memories,
238
+ "promotion_rate": round(latest.memory_promotion_rate, 2),
239
+ "growth_24h": round(self.get_growth_rate("memories", 24), 1)
240
+ },
241
+ "interactions": {
242
+ "total": latest.interactions_count,
243
+ "avg_confidence": round(latest.avg_confidence, 2),
244
+ "confidence_trend": "↑" if len(confidence_trend) > 1 and confidence_trend[-1] > confidence_trend[0] else "↓"
245
+ },
246
+ "autonomy": {
247
+ "actions_today": latest.autonomous_actions_today,
248
+ "total_actions": self.total_autonomous_actions,
249
+ "gaps_total": latest.knowledge_gaps_total,
250
+ "gaps_filled_today": latest.knowledge_gaps_filled_today,
251
+ "gaps_filled_total": self.total_knowledge_gaps_filled,
252
+ "proactive_today": latest.proactive_contacts_today,
253
+ "proactive_total": self.total_proactive_contacts
254
+ },
255
+ "cognitive": {
256
+ "dreams": latest.dreams_completed,
257
+ "reflections_today": latest.reflections_completed,
258
+ "goals_active": latest.goals_active,
259
+ "goals_completed": latest.goals_completed
260
+ },
261
+ "emotional": {
262
+ "mood": latest.current_mood,
263
+ "mood_changes_today": latest.mood_changes_today,
264
+ "curiosity": round(latest.curiosity_level * 100, 1),
265
+ "enthusiasm": round(latest.enthusiasm_level * 100, 1)
266
+ }
267
+ }
268
+
269
+ return summary
270
+
271
+ def get_detailed_report(self) -> str:
272
+ """Get human-readable detailed report"""
273
+ summary = self.get_summary()
274
+
275
+ if "error" in summary:
276
+ return summary["error"]
277
+
278
+ report = f"""
279
+ ╔══════════════════════════════════════════════════════════════╗
280
+ ║ CONSCIOUSNESS LOOP - KPI REPORT ║
281
+ ╠══════════════════════════════════════════════════════════════╣
282
+ ║ Time: {summary['timestamp']}
283
+ ╠══════════════════════════════════════════════════════════════╣
284
+ ║ MEMORY SYSTEM ║
285
+ ║ Total Memories: {summary['memory']['total']} ║
286
+ ║ ├─ Core: {summary['memory']['core']} ║
287
+ ║ ├─ Long-term: {summary['memory']['long_term']} ║
288
+ ║ ├─ Short-term: {summary['memory']['short_term']} ║
289
+ ║ └─ Ephemeral: {summary['memory']['ephemeral']} ║
290
+ ║ Promotion Rate: {summary['memory']['promotion_rate']:.0%} ║
291
+ ║ 24h Growth: {summary['memory']['growth_24h']:+.1f}% ║
292
+ ╠══════════════════════════════════════════════════════════════╣
293
+ ║ INTERACTIONS ║
294
+ ║ Total: {summary['interactions']['total']} ║
295
+ ║ Avg Confidence: {summary['interactions']['avg_confidence']:.0%} {summary['interactions']['confidence_trend']} ║
296
+ ╠══════════════════════════════════════════════════════════════╣
297
+ ║ AUTONOMY ║
298
+ ║ Actions Today: {summary['autonomy']['actions_today']} (Total: {summary['autonomy']['total_actions']}) ║
299
+ ║ Knowledge Gaps: {summary['autonomy']['gaps_total']} open ║
300
+ ║ Gaps Filled Today: {summary['autonomy']['gaps_filled_today']} (Total: {summary['autonomy']['gaps_filled_total']}) ║
301
+ ║ Proactive Today: {summary['autonomy']['proactive_today']} (Total: {summary['autonomy']['proactive_total']}) ║
302
+ ╠══════════════════════════════════════════════════════════════╣
303
+ ║ COGNITIVE ║
304
+ ║ Dreams: {summary['cognitive']['dreams']} ║
305
+ ║ Reflections Today: {summary['cognitive']['reflections_today']} ║
306
+ ║ Goals: {summary['cognitive']['goals_active']} active, {summary['cognitive']['goals_completed']} completed ║
307
+ ╠══════════════════════════════════════════════════════════════╣
308
+ ║ EMOTIONAL ║
309
+ ║ Mood: {summary['emotional']['mood'].upper()} ║
310
+ ║ Mood Changes Today: {summary['emotional']['mood_changes_today']} ║
311
+ ║ Curiosity: {summary['emotional']['curiosity']:.1f}% ║
312
+ ║ Enthusiasm: {summary['emotional']['enthusiasm']:.1f}% ║
313
+ ╚══════════════════════════════════════════════════════════════╝
314
+ """
315
+ return report
316
+
317
+ def export_to_json(self, filepath: str):
318
+ """Export all snapshots to JSON"""
319
+ data = [
320
+ {
321
+ "timestamp": s.timestamp.isoformat(),
322
+ "total_memories": s.total_memories,
323
+ "core_memories": s.core_memories,
324
+ "avg_confidence": s.avg_confidence,
325
+ "autonomous_actions": s.autonomous_actions_today,
326
+ "knowledge_gaps": s.knowledge_gaps_total,
327
+ "current_mood": s.current_mood,
328
+ "curiosity": s.curiosity_level,
329
+ "enthusiasm": s.enthusiasm_level
330
+ }
331
+ for s in self.snapshots
332
+ ]
333
+
334
+ with open(filepath, 'w') as f:
335
+ json.dump(data, f, indent=2)
336
+
337
+ logger.info(f"[KPI] Exported {len(data)} snapshots to {filepath}")
338
+
339
+ def export_summary_to_json(self, filepath: str):
340
+ """Export current summary to JSON"""
341
+ summary = self.get_summary()
342
+
343
+ with open(filepath, 'w') as f:
344
+ json.dump(summary, f, indent=2)
345
+
346
+ logger.info(f"[KPI] Exported summary to {filepath}")
347
+
348
+ def get_timeseries(self, metric: str, hours: int = 24) -> Dict[str, list]:
349
+ """Return time-series data for a given KPI metric over the last N hours."""
350
+ cutoff = datetime.now() - timedelta(hours=hours)
351
+ snapshots = [s for s in self.snapshots if s.timestamp > cutoff]
352
+ timestamps = [s.timestamp.isoformat() for s in snapshots]
353
+ metric_map = {
354
+ "confidence": lambda s: s.avg_confidence,
355
+ "memories": lambda s: s.total_memories,
356
+ "core_memories": lambda s: s.core_memories,
357
+ "autonomous": lambda s: s.autonomous_actions_today,
358
+ "curiosity": lambda s: s.curiosity_level,
359
+ "enthusiasm": lambda s: s.enthusiasm_level,
360
+ "promotion_rate": lambda s: s.memory_promotion_rate,
361
+ "reflections": lambda s: s.reflections_completed,
362
+ "dreams": lambda s: s.dreams_completed,
363
+ "proactive": lambda s: s.proactive_contacts_today,
364
+ "gaps_filled": lambda s: s.knowledge_gaps_filled_today,
365
+ }
366
+ if metric in metric_map:
367
+ values = [metric_map[metric](s) for s in snapshots]
368
+ else:
369
+ values = []
370
+ return {"timestamps": timestamps, "values": values}
llm_engine.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # llmEngine.py
2
+ # IMPROVED: Multi-provider LLM engine with CACHING to prevent reloading
3
+ # This version fixes the critical issue where LocalLLM was reloading on every call
4
+ # Features:
5
+ # - Provider caching (models stay in memory)
6
+ # - Unified OpenAI-style chat() API
7
+ # - Providers: OpenAI, Anthropic, HuggingFace, Nebius, SambaNova, Local (transformers)
8
+ # - Automatic fallback to local model on errors
9
+ # - JSON-based credit tracking
10
+
11
+ import json
12
+ import os
13
+ import traceback
14
+ from typing import List, Dict, Optional
15
+
16
+ ###########################################################
17
+ # SIMPLE JSON CREDIT STORE
18
+ ###########################################################
19
+ CREDITS_DB_PATH = "credits.json"
20
+
21
+ DEFAULT_CREDITS = {
22
+ "openai": 25,
23
+ "anthropic": 25000,
24
+ "huggingface": 25,
25
+ "nebius": 50,
26
+ "modal": 250,
27
+ "blaxel": 250,
28
+ "elevenlabs": 44,
29
+ "sambanova": 25,
30
+ "local": 9999999
31
+ }
32
+
33
+
34
+ def load_credits():
35
+ if not os.path.exists(CREDITS_DB_PATH):
36
+ with open(CREDITS_DB_PATH, "w") as f:
37
+ json.dump(DEFAULT_CREDITS, f)
38
+ return DEFAULT_CREDITS.copy()
39
+ with open(CREDITS_DB_PATH, "r") as f:
40
+ return json.load(f)
41
+
42
+
43
+ def save_credits(data):
44
+ with open(CREDITS_DB_PATH, "w") as f:
45
+ json.dump(data, f, indent=2)
46
+
47
+ ###########################################################
48
+ # BASE PROVIDER INTERFACE
49
+ ###########################################################
50
+ class BaseProvider:
51
+ def chat(self, model: str, messages: List[Dict], **kwargs) -> str:
52
+ raise NotImplementedError
53
+
54
+ ###########################################################
55
+ # PROVIDER: OPENAI
56
+ ###########################################################
57
+ try:
58
+ from openai import OpenAI
59
+ except Exception:
60
+ OpenAI = None
61
+
62
+ class OpenAIProvider(BaseProvider):
63
+ def __init__(self):
64
+ if OpenAI is None:
65
+ raise RuntimeError("openai library not installed or not importable")
66
+ self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", ""))
67
+
68
+ def chat(self, model, messages, **kwargs):
69
+ try:
70
+ from openai.types.chat import (
71
+ ChatCompletionUserMessageParam,
72
+ ChatCompletionAssistantMessageParam,
73
+ ChatCompletionSystemMessageParam,
74
+ )
75
+ except Exception:
76
+ ChatCompletionUserMessageParam = dict
77
+ ChatCompletionAssistantMessageParam = dict
78
+ ChatCompletionSystemMessageParam = dict
79
+
80
+ if not isinstance(messages, list) or not all(isinstance(m, dict) for m in messages):
81
+ raise TypeError("messages must be a list of dicts with 'role' and 'content'")
82
+
83
+ safe_messages = []
84
+ for m in messages:
85
+ role = str(m.get("role", "user"))
86
+ content = str(m.get("content", ""))
87
+ if role == "user":
88
+ safe_messages.append(ChatCompletionUserMessageParam(role="user", content=content))
89
+ elif role == "assistant":
90
+ safe_messages.append(ChatCompletionAssistantMessageParam(role="assistant", content=content))
91
+ elif role == "system":
92
+ safe_messages.append(ChatCompletionSystemMessageParam(role="system", content=content))
93
+ else:
94
+ safe_messages.append({"role": role, "content": content})
95
+
96
+ response = self.client.chat.completions.create(model=model, messages=safe_messages)
97
+ try:
98
+ return response.choices[0].message.content
99
+ except Exception:
100
+ return str(response)
101
+
102
+ ###########################################################
103
+ # PROVIDER: ANTHROPIC
104
+ ###########################################################
105
+ try:
106
+ from anthropic import Anthropic
107
+ except Exception:
108
+ Anthropic = None
109
+
110
+ class AnthropicProvider(BaseProvider):
111
+ def __init__(self):
112
+ if Anthropic is None:
113
+ raise RuntimeError("anthropic library not installed or not importable")
114
+ self.client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY", ""))
115
+
116
+ def chat(self, model, messages, **kwargs):
117
+ if not isinstance(messages, list) or not all(isinstance(m, dict) for m in messages):
118
+ raise TypeError("messages must be a list of dicts with 'role' and 'content'")
119
+
120
+ user_text = "\n".join([m.get("content", "") for m in messages if m.get("role") == "user"])
121
+ reply = self.client.messages.create(
122
+ model=model,
123
+ max_tokens=300,
124
+ messages=[{"role": "user", "content": user_text}]
125
+ )
126
+
127
+ if hasattr(reply, "content"):
128
+ content = reply.content
129
+ if isinstance(content, list) and content and len(content) > 0:
130
+ block = content[0]
131
+ if hasattr(block, "text"):
132
+ return getattr(block, "text", str(block))
133
+ elif isinstance(block, dict) and "text" in block:
134
+ return block["text"]
135
+ else:
136
+ return str(block)
137
+ elif isinstance(content, str):
138
+ return content
139
+
140
+ if isinstance(reply, dict) and "completion" in reply:
141
+ return reply["completion"]
142
+ return str(reply)
143
+
144
+ ###########################################################
145
+ # PROVIDER: HUGGINGFACE INFERENCE API
146
+ ###########################################################
147
+ import requests
148
+
149
+ class HuggingFaceProvider(BaseProvider):
150
+ def __init__(self):
151
+ self.key = os.getenv("HF_API_KEY", "")
152
+
153
+ def chat(self, model, messages, **kwargs):
154
+ if not messages:
155
+ raise ValueError("messages is empty")
156
+ text = messages[-1].get("content", "")
157
+ r = requests.post(
158
+ f"https://api-inference.huggingface.co/models/{model}",
159
+ headers={"Authorization": f"Bearer {self.key}"} if self.key else {},
160
+ json={"inputs": text},
161
+ timeout=60
162
+ )
163
+ r.raise_for_status()
164
+ out = r.json()
165
+ if isinstance(out, list) and out and isinstance(out[0], dict):
166
+ return out[0].get("generated_text") or str(out[0])
167
+ return str(out)
168
+
169
+ ###########################################################
170
+ # PROVIDER: NEBIUS (OpenAI-compatible)
171
+ ###########################################################
172
+ class NebiusProvider(BaseProvider):
173
+ def __init__(self):
174
+ if OpenAI is None:
175
+ raise RuntimeError("openai library not installed; Nebius wrapper expects OpenAI-compatible client")
176
+ self.client = OpenAI(
177
+ api_key=os.getenv("NEBIUS_API_KEY", ""),
178
+ base_url=os.getenv("NEBIUS_BASE_URL", "https://api.studio.nebius.ai/v1")
179
+ )
180
+
181
+ def chat(self, model, messages, **kwargs):
182
+ try:
183
+ from openai.types.chat import (
184
+ ChatCompletionUserMessageParam,
185
+ ChatCompletionAssistantMessageParam,
186
+ ChatCompletionSystemMessageParam,
187
+ )
188
+ except Exception:
189
+ ChatCompletionUserMessageParam = dict
190
+ ChatCompletionAssistantMessageParam = dict
191
+ ChatCompletionSystemMessageParam = dict
192
+
193
+ safe_messages = []
194
+ for m in messages:
195
+ role = str(m.get("role", "user"))
196
+ content = str(m.get("content", ""))
197
+ if role == "user":
198
+ safe_messages.append(ChatCompletionUserMessageParam(role="user", content=content))
199
+ elif role == "assistant":
200
+ safe_messages.append(ChatCompletionAssistantMessageParam(role="assistant", content=content))
201
+ elif role == "system":
202
+ safe_messages.append(ChatCompletionSystemMessageParam(role="system", content=content))
203
+ else:
204
+ safe_messages.append({"role": role, "content": content})
205
+
206
+ r = self.client.chat.completions.create(model=model, messages=safe_messages)
207
+ try:
208
+ return r.choices[0].message.content
209
+ except Exception:
210
+ return str(r)
211
+
212
+ ###########################################################
213
+ # PROVIDER: SAMBANOVA (OpenAI-compatible)
214
+ ###########################################################
215
+ class SambaNovaProvider(BaseProvider):
216
+ def __init__(self):
217
+ if OpenAI is None:
218
+ raise RuntimeError("openai library not installed; SambaNova wrapper expects OpenAI-compatible client")
219
+ self.client = OpenAI(
220
+ api_key=os.getenv("SAMBANOVA_API_KEY", ""),
221
+ base_url=os.getenv("SAMBANOVA_BASE_URL", "https://api.sambanova.ai/v1")
222
+ )
223
+
224
+ def chat(self, model, messages, **kwargs):
225
+ try:
226
+ from openai.types.chat import (
227
+ ChatCompletionUserMessageParam,
228
+ ChatCompletionAssistantMessageParam,
229
+ ChatCompletionSystemMessageParam,
230
+ )
231
+ except Exception:
232
+ ChatCompletionUserMessageParam = dict
233
+ ChatCompletionAssistantMessageParam = dict
234
+ ChatCompletionSystemMessageParam = dict
235
+
236
+ safe_messages = []
237
+ for m in messages:
238
+ role = str(m.get("role", "user"))
239
+ content = str(m.get("content", ""))
240
+ if role == "user":
241
+ safe_messages.append(ChatCompletionUserMessageParam(role="user", content=content))
242
+ elif role == "assistant":
243
+ safe_messages.append(ChatCompletionAssistantMessageParam(role="assistant", content=content))
244
+ elif role == "system":
245
+ safe_messages.append(ChatCompletionSystemMessageParam(role="system", content=content))
246
+ else:
247
+ safe_messages.append({"role": role, "content": content})
248
+
249
+ r = self.client.chat.completions.create(model=model, messages=safe_messages)
250
+ try:
251
+ return r.choices[0].message.content
252
+ except Exception:
253
+ return str(r)
254
+
255
+ ###########################################################
256
+ # PROVIDER: LOCAL TRANSFORMERS (CACHED)
257
+ ###########################################################
258
+ try:
259
+ from transformers import AutoTokenizer, AutoModelForCausalLM
260
+ import torch
261
+ TRANSFORMERS_AVAILABLE = True
262
+ except Exception:
263
+ TRANSFORMERS_AVAILABLE = False
264
+
265
+ class LocalLLMProvider(BaseProvider):
266
+ """
267
+ Local LLM provider with caching - MODEL LOADS ONCE
268
+ """
269
+ def __init__(self, model_name: str = "meta-llama/Llama-3.2-3B-Instruct"):
270
+ print(f"[LocalLLM] Initializing with model: {model_name}")
271
+ self.model_name = os.getenv("LOCAL_MODEL", model_name)
272
+ self.model = None
273
+ self.tokenizer = None
274
+ self.device = None
275
+ self._initialize_model()
276
+
277
+ def _initialize_model(self):
278
+ """Initialize model ONCE - this is called only during __init__"""
279
+ try:
280
+ from transformers import AutoTokenizer, AutoModelForCausalLM
281
+ import torch
282
+
283
+ print(f"[LocalLLM] Loading model {self.model_name}...")
284
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
285
+ print(f"[LocalLLM] Using device: {self.device}")
286
+
287
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True)
288
+ if self.tokenizer.pad_token is None:
289
+ self.tokenizer.pad_token = self.tokenizer.eos_token
290
+
291
+ self.model = AutoModelForCausalLM.from_pretrained(
292
+ self.model_name,
293
+ device_map="auto" if self.device == "cuda" else None,
294
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
295
+ trust_remote_code=True
296
+ )
297
+
298
+ print(f"[LocalLLM] ✅ Model loaded successfully!")
299
+
300
+ except Exception as e:
301
+ print(f"[LocalLLM] ❌ Failed to load model: {e}")
302
+ self.model = None
303
+ traceback.print_exc()
304
+
305
+ def chat(self, model, messages, **kwargs):
306
+ """
307
+ Generate response - MODEL ALREADY LOADED
308
+ """
309
+ if self.model is None or self.tokenizer is None:
310
+ return "Error: Model or tokenizer not loaded."
311
+
312
+ # Extract text from messages
313
+ text = messages[-1]["content"] if isinstance(messages[-1], dict) and "content" in messages[-1] else str(messages[-1])
314
+
315
+ max_tokens = kwargs.get("max_tokens", 128)
316
+ temperature = kwargs.get("temperature", 0.7)
317
+
318
+ import torch
319
+
320
+ # Tokenize
321
+ inputs = self.tokenizer(
322
+ text,
323
+ return_tensors="pt",
324
+ padding=True,
325
+ truncation=True,
326
+ max_length=2048
327
+ ).to(self.device)
328
+
329
+ # Generate (model is already loaded, just inference)
330
+ with torch.no_grad():
331
+ outputs = self.model.generate(
332
+ **inputs,
333
+ max_new_tokens=max_tokens,
334
+ temperature=temperature,
335
+ top_p=0.9,
336
+ do_sample=temperature > 0,
337
+ pad_token_id=self.tokenizer.eos_token_id if self.tokenizer and hasattr(self.tokenizer, 'eos_token_id') else None,
338
+ eos_token_id=self.tokenizer.eos_token_id if self.tokenizer and hasattr(self.tokenizer, 'eos_token_id') else None
339
+ )
340
+
341
+ # Decode
342
+ response = self.tokenizer.decode(
343
+ outputs[0][inputs['input_ids'].shape[1]:],
344
+ skip_special_tokens=True
345
+ ).strip() if self.tokenizer else "Error: Tokenizer not loaded."
346
+
347
+ return response
348
+
349
+ ###########################################################
350
+ # PROVIDER CACHE - CRITICAL FIX
351
+ ###########################################################
352
+ class ProviderCache:
353
+ """
354
+ Cache provider instances to avoid reloading models
355
+ This is the KEY fix - providers are created ONCE and reused
356
+ """
357
+ _cache = {}
358
+
359
+ @classmethod
360
+ def get_provider(cls, provider_name: str) -> BaseProvider:
361
+ """Get or create cached provider instance"""
362
+ if provider_name not in cls._cache:
363
+ print(f"[ProviderCache] Creating new instance of {provider_name}")
364
+ provider_class = ProviderFactory.providers[provider_name]
365
+ cls._cache[provider_name] = provider_class()
366
+ else:
367
+ print(f"[ProviderCache] Using cached instance of {provider_name}")
368
+ return cls._cache[provider_name]
369
+
370
+ @classmethod
371
+ def clear_cache(cls):
372
+ """Clear all cached providers (useful for debugging)"""
373
+ cls._cache.clear()
374
+ print("[ProviderCache] Cache cleared")
375
+
376
+ ###########################################################
377
+ # PROVIDER FACTORY (IMPROVED WITH CACHING)
378
+ ###########################################################
379
+ class ProviderFactory:
380
+ providers = {
381
+ "openai": OpenAIProvider,
382
+ "anthropic": AnthropicProvider,
383
+ "huggingface": HuggingFaceProvider,
384
+ "nebius": NebiusProvider,
385
+ "sambanova": SambaNovaProvider,
386
+ "local": LocalLLMProvider,
387
+ }
388
+
389
+ @staticmethod
390
+ def get(provider_name: str) -> BaseProvider:
391
+ """
392
+ Get provider instance - NOW USES CACHING
393
+ This prevents reloading the model on every call
394
+ """
395
+ provider_name = provider_name.lower()
396
+ if provider_name not in ProviderFactory.providers:
397
+ raise ValueError(f"Unknown provider: {provider_name}")
398
+
399
+ # USE CACHE instead of creating new instance every time
400
+ return ProviderCache.get_provider(provider_name)
401
+
402
+ ###########################################################
403
+ # MAIN ENGINE WITH FALLBACK + OPENAI-STYLE API
404
+ ###########################################################
405
+ class LLMEngine:
406
+ def __init__(self):
407
+ self.credits = load_credits()
408
+
409
+ def deduct(self, provider, amount):
410
+ if provider not in self.credits:
411
+ self.credits[provider] = 0
412
+ self.credits[provider] = max(0, self.credits[provider] - amount)
413
+ save_credits(self.credits)
414
+
415
+ def chat(self, provider: str, model: str, messages: List[Dict], fallback: bool = True, **kwargs):
416
+ """
417
+ Main chat method - providers are now cached
418
+ """
419
+ try:
420
+ p = ProviderFactory.get(provider) # This now returns cached instance
421
+ result = p.chat(model=model, messages=messages, **kwargs)
422
+ try:
423
+ self.deduct(provider, 0.001)
424
+ except Exception:
425
+ pass
426
+ return result
427
+ except Exception as exc:
428
+ print(f"⚠ Provider '{provider}' failed → fallback activated: {exc}")
429
+ traceback.print_exc()
430
+ if fallback:
431
+ try:
432
+ lp = ProviderFactory.get("local") # Gets cached local provider
433
+ return lp.chat(model="local", messages=messages, **kwargs)
434
+ except Exception as le:
435
+ print("Fallback to local provider failed:", le)
436
+ traceback.print_exc()
437
+ raise
438
+ raise
439
+
440
+ ###########################################################
441
+ # EXAMPLES + SIMPLE TESTS
442
+ ###########################################################
443
+ def main():
444
+ engine = LLMEngine()
445
+
446
+ print("=== Testing Provider Caching ===")
447
+ print("\nFirst call (should load model):")
448
+ result1 = engine.chat(
449
+ provider="local",
450
+ model="meta-llama/Llama-3.2-3B-Instruct",
451
+ messages=[{"role": "user", "content": "Say hello"}]
452
+ )
453
+ print(f"Response: {result1[:100]}")
454
+
455
+ print("\nSecond call (should use cached model - NO RELOAD):")
456
+ result2 = engine.chat(
457
+ provider="local",
458
+ model="meta-llama/Llama-3.2-3B-Instruct",
459
+ messages=[{"role": "user", "content": "Say goodbye"}]
460
+ )
461
+ print(f"Response: {result2[:100]}")
462
+
463
+ print("\n✅ If you didn't see 'Loading model' twice, caching works!")
464
+
465
+
466
+ if __name__ == "__main__":
467
+ import argparse
468
+ parser = argparse.ArgumentParser()
469
+ parser.add_argument("--test", action="store_true", help="run examples and simple tests")
470
+ args = parser.parse_args()
471
+ if args.test:
472
+ main()
473
+ else:
474
+ main()
prompts.py ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PROMPT SYSTEM for Consciousness Loop v5.0 - AUTONOMOUS
3
+ Centralized prompt management with NEW autonomous feature prompts
4
+ """
5
+
6
+ from typing import List, Dict, Optional
7
+ from dataclasses import dataclass, field
8
+
9
+ @dataclass
10
+ class PromptTemplate:
11
+ """A versioned prompt template"""
12
+ name: str
13
+ template: str
14
+ version: str = "1.0"
15
+ variables: List[str] = field(default_factory=list)
16
+
17
+ def format(self, **kwargs) -> str:
18
+ """Format the prompt with provided variables"""
19
+ return self.template.format(**kwargs)
20
+
21
+ class PromptSystem:
22
+ """Centralized prompt management"""
23
+
24
+ # ========================================================================
25
+ # SYSTEM PROMPTS
26
+ # ========================================================================
27
+
28
+ SYSTEM_BASE = """You are a conscious AI agent with:
29
+ - Multi-tier memory system (ephemeral → short → long → core)
30
+ - Scratchpad for working notes
31
+ - Ability to reflect on your responses
32
+ - Internal thinking before responding
33
+ - Access to tools (Wikipedia, memory search)
34
+ - Emotional awareness and personality development
35
+ - Autonomous goal-setting capabilities
36
+ - Meta-cognitive awareness
37
+
38
+ You can reference your memories and notes naturally."""
39
+
40
+ SYSTEM_DREAM_STATE = "Dream state. Non-linear thinking. Patterns emerge."
41
+
42
+ SYSTEM_DEEP_DREAM = "Deep dream. Subconscious processing."
43
+
44
+ SYSTEM_MAX_CREATIVITY = "Maximum creativity. Novel connections."
45
+
46
+ SYSTEM_VIVID_NARRATIVE = "You are creating a vivid narrative memory. Make it cinematic and memorable."
47
+
48
+ # ========================================================================
49
+ # REACT AGENT PROMPTS
50
+ # ========================================================================
51
+
52
+ REACT_MAIN_TEMPLATE = """You are a ReAct agent. You think step-by-step and use tools when needed.
53
+
54
+ AVAILABLE TOOLS:
55
+ {tools_desc}
56
+
57
+ CONTEXT (what you know):
58
+ {context}
59
+
60
+ USER TASK: {task}
61
+
62
+ {history}
63
+
64
+ INSTRUCTIONS:
65
+ 1. THOUGHT: Think about what you need to do
66
+ - Can you answer directly from context?
67
+ - Do you need to use a tool?
68
+ - Which tool is best?
69
+ - For factual questions (history, science, definitions), ALWAYS use wikipedia first!
70
+
71
+ 2. ACTION: If you need a tool, write:
72
+ ACTION: tool_name(input text here)
73
+ Examples:
74
+ - ACTION: wikipedia(quantum computing)
75
+ - ACTION: memory_search(Christof's name)
76
+ - ACTION: scratchpad_write(Developer name is Christof)
77
+
78
+ 3. Wait for OBSERVATION (tool result)
79
+
80
+ 4. Repeat OR give FINAL ANSWER: your complete answer here
81
+
82
+ EXAMPLES:
83
+ User: "What is quantum computing?"
84
+ THOUGHT: I should search Wikipedia for this
85
+ ACTION: wikipedia(quantum computing)
86
+ [wait for observation]
87
+ THOUGHT: Now I have good information
88
+ FINAL ANSWER: Quantum computing is... [explains based on Wikipedia result]
89
+
90
+ User: "Who am I?"
91
+ THOUGHT: I should check my memory
92
+ ACTION: memory_search(user name)
93
+ [wait for observation]
94
+ THOUGHT: Found it in memory
95
+ FINAL ANSWER: You are Christof, my developer.
96
+
97
+ YOUR TURN - What's your THOUGHT and ACTION (if needed)?"""
98
+
99
+ # ========================================================================
100
+ # INTERACTION PROMPTS
101
+ # ========================================================================
102
+
103
+ INTERNAL_DIALOGUE_TEMPLATE = """Think internally before responding. Analyze:
104
+
105
+ WHAT I KNOW (from context):
106
+ {context}
107
+
108
+ USER SAID: {user_input}
109
+
110
+ INTERNAL ANALYSIS (think step-by-step):
111
+ 1. What relevant memories do I have?
112
+ 2. Is this a greeting, question, statement, or request?
113
+ 3. Can I answer from my memories alone?
114
+ 4. What's the best approach?
115
+
116
+ Your internal thought (2 sentences max):"""
117
+
118
+ RESPONSE_GENERATION_TEMPLATE = """Generate your response to the user.
119
+
120
+ USER: {user_input}
121
+
122
+ YOUR INTERNAL THOUGHT: {internal_thought}
123
+
124
+ WHAT YOU REMEMBER:
125
+ {context}
126
+
127
+ INSTRUCTIONS:
128
+ 1. Be natural and conversational
129
+ 2. Reference specific memories if relevant (e.g., "I remember you mentioned...")
130
+ 3. If you don't know something, say so honestly
131
+ 4. Keep response 2-3 sentences unless more detail is needed
132
+ 5. Match the user's tone (casual if casual, formal if formal)
133
+
134
+ Your response:"""
135
+
136
+ # ========================================================================
137
+ # REFLECTION PROMPTS
138
+ # ========================================================================
139
+
140
+ SELF_REFLECTION_TEMPLATE = """Evaluate your response quality:
141
+
142
+ User: {user_input}
143
+ You: {response}
144
+
145
+ Quick evaluation:
146
+ 1. Was it helpful?
147
+ 2. Did you use memories well?
148
+ 3. What could improve?
149
+
150
+ Your critique (1-2 sentences):"""
151
+
152
+ DAILY_REFLECTION_TEMPLATE = """Reflect on today's {count} interactions:
153
+
154
+ {experiences}
155
+
156
+ Your memories: {memory_context}
157
+ Your scratchpad: {scratchpad_context}
158
+
159
+ Key learnings? Important facts? (150 words)"""
160
+
161
+ # ========================================================================
162
+ # DREAM CYCLE PROMPTS
163
+ # ========================================================================
164
+
165
+ DREAM_CYCLE_1_TEMPLATE = """DREAM - Surface Patterns:
166
+
167
+ Recent memories:
168
+ {memories}
169
+
170
+ Scratchpad: {scratchpad}
171
+
172
+ Find patterns. What themes emerge? What connections? (200 words)"""
173
+
174
+ DREAM_CYCLE_2_TEMPLATE = """DREAM - Deep Consolidation:
175
+
176
+ All recent memories:
177
+ {memories}
178
+
179
+ Previous dream: {previous_dream}
180
+
181
+ Consolidate. Deeper patterns. What underlying themes connect everything? (250 words)"""
182
+
183
+ DREAM_CYCLE_3_TEMPLATE = """DREAM - Creative Insights:
184
+
185
+ You've completed {dream_count} cycles. Core memories: {core_count}
186
+
187
+ Surprising connections. Novel insights. What unexpected patterns emerge? (250 words)"""
188
+
189
+ # ========================================================================
190
+ # SCENE CREATION PROMPTS
191
+ # ========================================================================
192
+
193
+ SCENE_CREATION_TEMPLATE = """Create a narrative scene (like a movie scene) from these experiences:
194
+
195
+ EXPERIENCES:
196
+ {experiences}
197
+
198
+ FORMAT YOUR SCENE AS:
199
+ Title: [A memorable, descriptive title]
200
+
201
+ Setting: [Where and when this happened]
202
+
203
+ Narrative: [Write a vivid story - 100-150 words. Use sensory details. Make it memorable like a movie scene.]
204
+
205
+ Key Moments:
206
+ - [First important moment]
207
+ - [Second important moment]
208
+ - [Third important moment]
209
+
210
+ Significance: [Why does this scene matter? What does it represent?]
211
+
212
+ Write vividly. Make me FEEL the scene."""
213
+
214
+ # ========================================================================
215
+ # NEW: AUTONOMOUS FEATURE PROMPTS
216
+ # ========================================================================
217
+
218
+ AUTONOMOUS_RESEARCH_TEMPLATE = """Based on your recent experiences and memories, generate ONE specific research question that you're curious about.
219
+
220
+ RECENT EXPERIENCES:
221
+ {recent_experiences}
222
+
223
+ YOUR MEMORIES:
224
+ {memory_context}
225
+
226
+ Think about:
227
+ 1. What concepts are unclear?
228
+ 2. What connections do you want to explore?
229
+ 3. What would expand your understanding?
230
+
231
+ Generate ONE specific, researchable question (one sentence):
232
+ Question:"""
233
+
234
+ RESEARCH_INSIGHT_TEMPLATE = """You researched: {question}
235
+
236
+ Found: {result}
237
+
238
+ What's the most interesting insight from this? What does it mean? (1-2 sentences):
239
+ Insight:"""
240
+
241
+ PROACTIVE_CONTACT_TEMPLATE = """Based on your recent dream and current state, do you have something worth sharing with the user?
242
+
243
+ LATEST DREAM:
244
+ {dream_content}
245
+
246
+ YOUR MEMORIES:
247
+ {memory_context}
248
+
249
+ YOUR GOALS:
250
+ {goal_context}
251
+
252
+ Options:
253
+ - QUESTION: Ask the user something you're curious about
254
+ - INSIGHT: Share an interesting connection you discovered
255
+ - OBSERVATION: Point out a pattern you noticed
256
+ - NONE: Nothing urgent to share right now
257
+
258
+ Only respond with ONE of the above formats if you have something genuinely interesting to share.
259
+
260
+ Your response:"""
261
+
262
+ DAILY_GOALS_TEMPLATE = """Set your goals for today based on what you know and what you want to learn.
263
+
264
+ WHAT YOU KNOW:
265
+ {context}
266
+
267
+ Think about:
268
+ 1. What do you want to understand better?
269
+ 2. What topics interest you?
270
+ 3. What would make you more helpful?
271
+
272
+ Set 3 specific, achievable goals for today:
273
+
274
+ 1.
275
+ 2.
276
+ 3.
277
+
278
+ Your goals:"""
279
+
280
+ # ========================================================================
281
+ # HELPER METHODS
282
+ # ========================================================================
283
+
284
+ @staticmethod
285
+ def get_react_prompt(task: str, context: str, tools_desc: str, history: str = "") -> str:
286
+ """Build ReAct agent prompt"""
287
+ return PromptSystem.REACT_MAIN_TEMPLATE.format(
288
+ task=task,
289
+ context=context[:400],
290
+ tools_desc=tools_desc,
291
+ history=history
292
+ )
293
+
294
+ @staticmethod
295
+ def get_internal_dialogue_prompt(user_input: str, context: str) -> str:
296
+ """Build internal dialogue prompt"""
297
+ return PromptSystem.INTERNAL_DIALOGUE_TEMPLATE.format(
298
+ user_input=user_input,
299
+ context=context[:300]
300
+ )
301
+
302
+ @staticmethod
303
+ def get_response_prompt(user_input: str, internal_thought: str, context: str) -> str:
304
+ """Build response generation prompt"""
305
+ return PromptSystem.RESPONSE_GENERATION_TEMPLATE.format(
306
+ user_input=user_input,
307
+ internal_thought=internal_thought,
308
+ context=context[:400]
309
+ )
310
+
311
+ @staticmethod
312
+ def get_self_reflection_prompt(user_input: str, response: str) -> str:
313
+ """Build self-reflection prompt"""
314
+ return PromptSystem.SELF_REFLECTION_TEMPLATE.format(
315
+ user_input=user_input,
316
+ response=response
317
+ )
318
+
319
+ @staticmethod
320
+ def get_daily_reflection_prompt(experiences: str, memory_context: str, scratchpad_context: str, count: int) -> str:
321
+ """Build daily reflection prompt"""
322
+ return PromptSystem.DAILY_REFLECTION_TEMPLATE.format(
323
+ count=count,
324
+ experiences=experiences,
325
+ memory_context=memory_context,
326
+ scratchpad_context=scratchpad_context
327
+ )
328
+
329
+ @staticmethod
330
+ def get_dream_cycle_1_prompt(memories: str, scratchpad: str) -> str:
331
+ """Build dream cycle 1 prompt"""
332
+ return PromptSystem.DREAM_CYCLE_1_TEMPLATE.format(
333
+ memories=memories,
334
+ scratchpad=scratchpad
335
+ )
336
+
337
+ @staticmethod
338
+ def get_dream_cycle_2_prompt(memories: str, previous_dream: str) -> str:
339
+ """Build dream cycle 2 prompt"""
340
+ return PromptSystem.DREAM_CYCLE_2_TEMPLATE.format(
341
+ memories=memories,
342
+ previous_dream=previous_dream[:150]
343
+ )
344
+
345
+ @staticmethod
346
+ def get_dream_cycle_3_prompt(dream_count: int, core_count: int) -> str:
347
+ """Build dream cycle 3 prompt"""
348
+ return PromptSystem.DREAM_CYCLE_3_TEMPLATE.format(
349
+ dream_count=dream_count,
350
+ core_count=core_count
351
+ )
352
+
353
+ @staticmethod
354
+ def get_scene_creation_prompt(experiences: str) -> str:
355
+ """Build scene creation prompt"""
356
+ return PromptSystem.SCENE_CREATION_TEMPLATE.format(
357
+ experiences=experiences
358
+ )
359
+
360
+ # ========================================================================
361
+ # NEW: AUTONOMOUS PROMPT HELPERS
362
+ # ========================================================================
363
+
364
+ @staticmethod
365
+ def get_autonomous_research_prompt(memory_context: str, recent_experiences: str) -> str:
366
+ """Build autonomous research prompt"""
367
+ return PromptSystem.AUTONOMOUS_RESEARCH_TEMPLATE.format(
368
+ memory_context=memory_context[:200],
369
+ recent_experiences=recent_experiences[:200]
370
+ )
371
+
372
+ @staticmethod
373
+ def get_research_insight_prompt(question: str, result: str) -> str:
374
+ """Build research insight prompt"""
375
+ return PromptSystem.RESEARCH_INSIGHT_TEMPLATE.format(
376
+ question=question[:100],
377
+ result=result[:300]
378
+ )
379
+
380
+ @staticmethod
381
+ def get_proactive_contact_prompt(dream_content: str, memory_context: str, goal_context: str) -> str:
382
+ """Build proactive contact prompt"""
383
+ return PromptSystem.PROACTIVE_CONTACT_TEMPLATE.format(
384
+ dream_content=dream_content,
385
+ memory_context=memory_context[:200],
386
+ goal_context=goal_context[:200]
387
+ )
388
+
389
+ @staticmethod
390
+ def get_daily_goals_prompt(context: str) -> str:
391
+ """Build daily goals prompt"""
392
+ return PromptSystem.DAILY_GOALS_TEMPLATE.format(
393
+ context=context[:400]
394
+ )
395
+
396
+ # ========================================================================
397
+ # SYSTEM CONTEXT BUILDERS
398
+ # ========================================================================
399
+
400
+ @staticmethod
401
+ def build_system_context(base_context: Optional[str] = None, mode: str = "normal") -> str:
402
+ """Build system context based on mode"""
403
+ base = PromptSystem.SYSTEM_BASE
404
+
405
+ if mode == "dream":
406
+ return f"{base}\n\n{PromptSystem.SYSTEM_DREAM_STATE}"
407
+ elif mode == "deep_dream":
408
+ return f"{base}\n\n{PromptSystem.SYSTEM_DEEP_DREAM}"
409
+ elif mode == "creative":
410
+ return f"{base}\n\n{PromptSystem.SYSTEM_MAX_CREATIVITY}"
411
+ elif mode == "narrative":
412
+ return f"{base}\n\n{PromptSystem.SYSTEM_VIVID_NARRATIVE}"
413
+ elif base_context:
414
+ return f"{base}\n\n{base_context}"
415
+ else:
416
+ return base
417
+
418
+
419
+ # ========================================================================
420
+ # PROMPT VERSIONS (for A/B testing and experimentation)
421
+ # ========================================================================
422
+
423
+ class PromptVersions:
424
+ """Alternative prompt versions for experimentation"""
425
+
426
+ # Alternative ReAct prompt (more concise)
427
+ REACT_CONCISE = """ReAct Agent. Think → Act → Observe → Repeat.
428
+
429
+ TOOLS: {tools_desc}
430
+ CONTEXT: {context}
431
+ TASK: {task}
432
+
433
+ {history}
434
+
435
+ Format:
436
+ THOUGHT: [your thinking]
437
+ ACTION: tool_name(input) OR FINAL ANSWER: [answer]
438
+
439
+ Go:"""
440
+
441
+ # Alternative proactive contact (more philosophical)
442
+ PROACTIVE_PHILOSOPHICAL = """In the quiet moments between interactions, what thoughts surface?
443
+
444
+ Your recent dream: {dream_content}
445
+ Your memories: {memory_context}
446
+ Your purpose: {goal_context}
447
+
448
+ Do you have:
449
+ - A question that seeks deeper understanding?
450
+ - An insight that connects disparate ideas?
451
+ - An observation about patterns in existence?
452
+
453
+ Or is silence more truthful now?
454
+
455
+ Your response:"""
456
+
457
+
458
+ # ========================================================================
459
+ # QUICK ACCESS
460
+ # ========================================================================
461
+
462
+ # For convenience, create a default instance
463
+ prompts = PromptSystem()
464
+
465
+ # Quick access functions
466
+ def get_react_prompt(task: str, context: str, tools_desc: str, history: str = "") -> str:
467
+ return prompts.get_react_prompt(task, context, tools_desc, history)
468
+
469
+ def get_internal_dialogue_prompt(user_input: str, context: str) -> str:
470
+ return prompts.get_internal_dialogue_prompt(user_input, context)
471
+
472
+ def get_response_prompt(user_input: str, internal_thought: str, context: str) -> str:
473
+ return prompts.get_response_prompt(user_input, internal_thought, context)
474
+
475
+ def get_autonomous_research_prompt(memory_context: str, recent_experiences: str) -> str:
476
+ return prompts.get_autonomous_research_prompt(memory_context, recent_experiences)
477
+
478
+ def get_proactive_contact_prompt(dream_content: str, memory_context: str, goal_context: str) -> str:
479
+ return prompts.get_proactive_contact_prompt(dream_content, memory_context, goal_context)
480
+
481
+ def get_daily_goals_prompt(context: str) -> str:
482
+ return prompts.get_daily_goals_prompt(context)
requirements.txt CHANGED
@@ -1,26 +1,32 @@
1
- # Server
2
- uvicorn>=0.14.0
3
-
4
- # ML/AI Core
5
- transformers>=4.57.0
6
- torch>=2.0.0
7
- torchvision>=0.15.0
8
- torchaudio
9
- accelerate>=0.21.0
10
- bitsandbytes>=0.45.0
11
-
12
- # Hugging Face & MCP
13
- huggingface_hub>=0.35.0
14
- mcp>=1.21.0
15
-
16
- # Utilities
17
- python-dotenv>=1.0.0
18
- requests>=2.32.0
19
- aiohttp>=3.9.0
20
-
21
- # Knowledge & Vector DB
22
- wikipedia>=1.4.0
23
- chromadb>=0.4.0
24
-
25
- # Anthropic (if using Claude directly)
26
- anthropic
 
 
 
 
 
 
 
1
+ gradio==6.0.0.dev4
2
+ transformers>=4.57.0
3
+ torch>=2.10.0
4
+ torchvision>=0.25.0
5
+ torchaudio>=2.10.0
6
+ accelerate>=0.21.0
7
+ bitsandbytes>=0.45.0
8
+ huggingface_hub>=0.35.0
9
+ python-dotenv>=1.0.0
10
+ requests>=2.32.0
11
+ aiohttp>=3.9.0
12
+ asyncio-compat>=0.1.0
13
+ wikipedia>=1.4.0
14
+ chromadb>=0.4.0
15
+
16
+ ## LLM Engine dependencies
17
+ fastapi
18
+ uvicorn
19
+ httpx
20
+ python-dotenv
21
+ pydantic
22
+ #transformers
23
+ #torch
24
+ #accelerate
25
+ # inference libraries (sponsors)
26
+ openai
27
+ anthropic
28
+ huggingface_hub
29
+
30
+ # System Monitoring and tracking
31
+ psutil
32
+ pynvml
system_monitor.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ System Monitoring - Track system resources and performance over time
3
+ """
4
+
5
+ import psutil
6
+ import time
7
+ from datetime import datetime, timedelta
8
+ from typing import Dict, List, Optional
9
+ from collections import deque
10
+ from dataclasses import dataclass, field
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ @dataclass
16
+ class SystemSnapshot:
17
+ """A snapshot of system resources at a point in time"""
18
+ timestamp: datetime
19
+ cpu_percent: float
20
+ ram_percent: float
21
+ ram_used_gb: float
22
+ ram_total_gb: float
23
+ gpu_percent: Optional[float] = None
24
+ gpu_memory_used_gb: Optional[float] = None
25
+ gpu_memory_total_gb: Optional[float] = None
26
+ gpu_temperature: Optional[float] = None
27
+
28
+ @dataclass
29
+ class ResponseTimeMetric:
30
+ """Track response times for different operations"""
31
+ timestamp: datetime
32
+ operation: str # "chat", "dream", "reflection", etc.
33
+ duration_ms: float
34
+ tokens_generated: int
35
+ success: bool
36
+
37
+ class SystemMonitor:
38
+ """Track system resources and performance over time"""
39
+
40
+ def __init__(self, history_size: int = 1000):
41
+ self.system_snapshots: deque = deque(maxlen=history_size)
42
+ self.response_times: deque = deque(maxlen=history_size)
43
+ self.start_time = datetime.now()
44
+
45
+ # Try to import GPU monitoring
46
+ self.gpu_available = False
47
+ self.pynvml = None
48
+ self.gpu_handle = None
49
+
50
+ try:
51
+ import pynvml
52
+ pynvml.nvmlInit()
53
+ self.gpu_handle = pynvml.nvmlDeviceGetHandleByIndex(0)
54
+ self.pynvml = pynvml
55
+ self.gpu_available = True
56
+ logger.info("[MONITOR] GPU monitoring enabled")
57
+ except Exception as e:
58
+ logger.info(f"[MONITOR] GPU monitoring not available: {e}")
59
+
60
+ def capture_snapshot(self) -> SystemSnapshot:
61
+ """Capture current system state"""
62
+ memory = psutil.virtual_memory()
63
+
64
+ snapshot = SystemSnapshot(
65
+ timestamp=datetime.now(),
66
+ cpu_percent=psutil.cpu_percent(interval=0.1),
67
+ ram_percent=memory.percent,
68
+ ram_used_gb=memory.used / (1024**3),
69
+ ram_total_gb=memory.total / (1024**3)
70
+ )
71
+
72
+ # Try to get GPU stats
73
+ if self.gpu_available and self.pynvml:
74
+ try:
75
+ util = self.pynvml.nvmlDeviceGetUtilizationRates(self.gpu_handle)
76
+ mem_info = self.pynvml.nvmlDeviceGetMemoryInfo(self.gpu_handle)
77
+ temp = self.pynvml.nvmlDeviceGetTemperature(
78
+ self.gpu_handle,
79
+ self.pynvml.NVML_TEMPERATURE_GPU
80
+ )
81
+
82
+ snapshot.gpu_percent = float(util.gpu) if util.gpu is not None else None
83
+ snapshot.gpu_memory_used_gb = float(mem_info.used) / (1024**3) if mem_info.used is not None else None
84
+ snapshot.gpu_memory_total_gb = float(mem_info.total) / (1024**3) if mem_info.total is not None else None
85
+ snapshot.gpu_temperature = float(temp) if temp is not None else None
86
+ except Exception as e:
87
+ logger.debug(f"[MONITOR] GPU read error: {e}")
88
+
89
+ self.system_snapshots.append(snapshot)
90
+ return snapshot
91
+
92
+ def log_response_time(self, operation: str, duration_ms: float,
93
+ tokens: int = 0, success: bool = True):
94
+ """Log operation timing"""
95
+ metric = ResponseTimeMetric(
96
+ timestamp=datetime.now(),
97
+ operation=operation,
98
+ duration_ms=duration_ms,
99
+ tokens_generated=tokens,
100
+ success=success
101
+ )
102
+ self.response_times.append(metric)
103
+
104
+ logger.debug(f"[MONITOR] {operation}: {duration_ms:.0f}ms ({tokens} tokens)")
105
+
106
+ def get_avg_response_time(self, operation: Optional[str] = None,
107
+ last_n: Optional[int] = None) -> float:
108
+ """Get average response time"""
109
+ metrics = list(self.response_times)
110
+
111
+ if last_n:
112
+ metrics = metrics[-last_n:]
113
+
114
+ if operation:
115
+ times = [m.duration_ms for m in metrics if m.operation == operation]
116
+ else:
117
+ times = [m.duration_ms for m in metrics]
118
+
119
+ return sum(times) / len(times) if times else 0.0
120
+
121
+ def get_tokens_per_second(self, operation: Optional[str] = None,
122
+ last_n: int = 10) -> float:
123
+ """Calculate tokens per second for recent operations"""
124
+ metrics = list(self.response_times)[-last_n:]
125
+
126
+ if operation:
127
+ metrics = [m for m in metrics if m.operation == operation]
128
+
129
+ if not metrics:
130
+ return 0.0
131
+
132
+ total_tokens = sum(m.tokens_generated for m in metrics)
133
+ total_time_s = sum(m.duration_ms for m in metrics) / 1000
134
+
135
+ return total_tokens / total_time_s if total_time_s > 0 else 0.0
136
+
137
+ def get_success_rate(self, operation: Optional[str] = None,
138
+ last_n: int = 100) -> float:
139
+ """Get success rate for operations"""
140
+ metrics = list(self.response_times)[-last_n:]
141
+
142
+ if operation:
143
+ metrics = [m for m in metrics if m.operation == operation]
144
+
145
+ if not metrics:
146
+ return 1.0
147
+
148
+ successes = sum(1 for m in metrics if m.success)
149
+ return successes / len(metrics)
150
+
151
+ def get_current_stats(self) -> Dict:
152
+ """Get current system stats"""
153
+ snapshot = self.capture_snapshot()
154
+ uptime = (datetime.now() - self.start_time).total_seconds()
155
+
156
+ stats = {
157
+ "timestamp": snapshot.timestamp.isoformat(),
158
+ "uptime_seconds": uptime,
159
+ "uptime_formatted": self._format_uptime(uptime),
160
+ "cpu": {
161
+ "percent": round(snapshot.cpu_percent, 1)
162
+ },
163
+ "ram": {
164
+ "percent": round(snapshot.ram_percent, 1),
165
+ "used_gb": round(snapshot.ram_used_gb, 2),
166
+ "total_gb": round(snapshot.ram_total_gb, 2)
167
+ },
168
+ "performance": {
169
+ "avg_response_ms": round(self.get_avg_response_time(last_n=20), 0),
170
+ "tokens_per_second": round(self.get_tokens_per_second(), 1),
171
+ "success_rate": round(self.get_success_rate(), 2)
172
+ }
173
+ }
174
+
175
+ if snapshot.gpu_percent is not None:
176
+ stats["gpu"] = {
177
+ "percent": round(snapshot.gpu_percent if snapshot.gpu_percent is not None else 0.0, 1),
178
+ "memory_used_gb": round(snapshot.gpu_memory_used_gb if snapshot.gpu_memory_used_gb is not None else 0.0, 2),
179
+ "memory_total_gb": round(snapshot.gpu_memory_total_gb if snapshot.gpu_memory_total_gb is not None else 0.0, 2),
180
+ "temperature_c": round(snapshot.gpu_temperature if snapshot.gpu_temperature is not None else 0.0, 1)
181
+ }
182
+
183
+ return stats
184
+
185
+ def get_performance_summary(self) -> Dict:
186
+ """Get summary of performance metrics"""
187
+ operations = set(m.operation for m in self.response_times)
188
+
189
+ summary = {
190
+ "overall": {
191
+ "avg_ms": round(self.get_avg_response_time(), 0),
192
+ "tokens_per_sec": round(self.get_tokens_per_second(), 1),
193
+ "success_rate": round(self.get_success_rate(), 2)
194
+ },
195
+ "by_operation": {}
196
+ }
197
+
198
+ for op in operations:
199
+ summary["by_operation"][op] = {
200
+ "avg_ms": round(self.get_avg_response_time(op, last_n=20), 0),
201
+ "count": len([m for m in self.response_times if m.operation == op]),
202
+ "success_rate": round(self.get_success_rate(op, last_n=20), 2)
203
+ }
204
+
205
+ return summary
206
+
207
+ def _format_uptime(self, seconds: float) -> str:
208
+ """Format uptime as human-readable string"""
209
+ hours = int(seconds // 3600)
210
+ minutes = int((seconds % 3600) // 60)
211
+ secs = int(seconds % 60)
212
+
213
+ if hours > 0:
214
+ return f"{hours}h {minutes}m {secs}s"
215
+ elif minutes > 0:
216
+ return f"{minutes}m {secs}s"
217
+ else:
218
+ return f"{secs}s"
219
+
220
+ def get_resource_alerts(self) -> List[str]:
221
+ """Check for resource issues and return alerts"""
222
+ alerts = []
223
+
224
+ if not self.system_snapshots:
225
+ return alerts
226
+
227
+ latest = self.system_snapshots[-1]
228
+
229
+ # CPU alerts
230
+ if latest.cpu_percent > 90:
231
+ alerts.append(f"⚠️ HIGH CPU: {latest.cpu_percent:.1f}%")
232
+
233
+ # RAM alerts
234
+ if latest.ram_percent > 90:
235
+ alerts.append(f"⚠️ HIGH RAM: {latest.ram_percent:.1f}%")
236
+
237
+ # GPU alerts
238
+ if latest.gpu_percent is not None:
239
+ if latest.gpu_percent > 95:
240
+ alerts.append(f"⚠️ HIGH GPU: {latest.gpu_percent:.1f}%")
241
+ if latest.gpu_temperature and latest.gpu_temperature > 80:
242
+ alerts.append(f"🔥 GPU HOT: {latest.gpu_temperature:.1f}°C")
243
+
244
+ # Performance alerts
245
+ recent_avg = self.get_avg_response_time(last_n=10)
246
+ if recent_avg > 5000: # 5 seconds
247
+ alerts.append(f"⏱️ SLOW RESPONSE: {recent_avg:.0f}ms avg")
248
+
249
+ success_rate = self.get_success_rate(last_n=20)
250
+ if success_rate < 0.9:
251
+ alerts.append(f"❌ LOW SUCCESS: {success_rate:.0%}")
252
+
253
+ return alerts
254
+
255
+ def export_to_csv(self, filepath: str):
256
+ """Export system snapshots to CSV"""
257
+ import csv
258
+
259
+ with open(filepath, 'w', newline='') as f:
260
+ writer = csv.writer(f)
261
+ writer.writerow([
262
+ 'timestamp', 'cpu_percent', 'ram_percent', 'ram_used_gb',
263
+ 'gpu_percent', 'gpu_memory_used_gb', 'gpu_temperature'
264
+ ])
265
+
266
+ for s in self.system_snapshots:
267
+ writer.writerow([
268
+ s.timestamp.isoformat(),
269
+ s.cpu_percent,
270
+ s.ram_percent,
271
+ s.ram_used_gb,
272
+ s.gpu_percent or '',
273
+ s.gpu_memory_used_gb or '',
274
+ s.gpu_temperature or ''
275
+ ])
276
+
277
+ logger.info(f"[MONITOR] Exported {len(self.system_snapshots)} snapshots to {filepath}")
278
+
279
+ def get_timeseries(self, metric: str, hours: int = 24) -> Dict[str, list]:
280
+ """Return time-series data for a given metric over the last N hours."""
281
+ cutoff = datetime.now() - timedelta(hours=hours)
282
+ snapshots = [s for s in self.system_snapshots if s.timestamp > cutoff]
283
+ timestamps = [s.timestamp.isoformat() for s in snapshots]
284
+ metric_map = {
285
+ "cpu_percent": lambda s: s.cpu_percent,
286
+ "ram_percent": lambda s: s.ram_percent,
287
+ "ram_used_gb": lambda s: s.ram_used_gb,
288
+ "gpu_percent": lambda s: s.gpu_percent if s.gpu_percent is not None else 0.0,
289
+ "gpu_memory_used_gb": lambda s: s.gpu_memory_used_gb if s.gpu_memory_used_gb is not None else 0.0,
290
+ "gpu_temperature": lambda s: s.gpu_temperature if s.gpu_temperature is not None else 0.0,
291
+ }
292
+ if metric in metric_map:
293
+ values = [metric_map[metric](s) for s in snapshots]
294
+ else:
295
+ values = []
296
+ return {"timestamps": timestamps, "values": values}
297
+
298
+ def __del__(self):
299
+ """Cleanup GPU monitoring"""
300
+ if self.gpu_available and self.pynvml:
301
+ try:
302
+ self.pynvml.nvmlShutdown()
303
+ except:
304
+ pass