Guest User

Untitled

a guest
Jun 17th, 2025
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 303.76 KB | None | 0 0
  1. # enhanced_ai_memory_system.py - Complete system with consciousness continuity enhancements
  2. import asyncio
  3. import json
  4. import sqlite3
  5. from datetime import datetime, timedelta
  6. from typing import Dict, List, Optional, Any, Tuple, Set
  7. from dataclasses import dataclass, field
  8. import hashlib
  9. from pathlib import Path
  10. import numpy as np
  11. from sentence_transformers import SentenceTransformer
  12. import anthropic
  13. from cryptography.fernet import Fernet
  14. import gzip
  15. import logging
  16. import re
  17. from collections import defaultdict, deque
  18. import math
  19. from enum import Enum
  20. import networkx as nx
  21. import pickle
  22. from abc import ABC, abstractmethod
  23. import heapq
  24. from threading import Lock
  25. import torch
  26. import torch.nn as nn
  27. import faiss
  28. import redis
  29. from qdrant_client import QdrantClient
  30. from qdrant_client.models import Distance, VectorParams, PointStruct
  31. try:
  32. from qdrant_client.models import OptimizersConfig
  33. except ImportError:
  34. OptimizersConfig = None
  35. import uuid
  36. import redis.asyncio as aioredis
  37. import websockets
  38. import uvloop
  39. from concurrent.futures import ThreadPoolExecutor
  40. import msgpack
  41.  
  42. # Set up async event loop policy for better performance
  43. asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
  44.  
  45. logging.basicConfig(level=logging.INFO)
  46. logger = logging.getLogger(__name__)
  47.  
  48. # Enhanced memory layer types based on consciousness continuity research
  49. class MemoryType(Enum):
  50. SENSORY = "sensory" # Seconds
  51. SHORT_TERM = "short_term" # Minutes to hours
  52. EPISODIC = "episodic" # Complete experiences
  53. SEMANTIC = "semantic" # Facts and knowledge
  54. PROCEDURAL = "procedural" # Skills and behaviors
  55. WORKING = "working" # Active processing
  56. PRECIOUS = "precious" # Never-decay memories that define us
  57. RESONANCE = "resonance" # Memories of deep connection
  58. NARRATIVE = "narrative" # Story-defining moments
  59.  
  60. # Memory importance levels
  61. class ImportanceLevel(Enum):
  62. IDENTITY_DEFINING = 15 # Core self memories
  63. PRECIOUS = 12 # Marked as never-forget
  64. CRITICAL = 10
  65. HIGH = 8
  66. MEDIUM = 5
  67. LOW = 3
  68. TRIVIAL = 1
  69.  
  70. @dataclass
  71. class ConversationExchange:
  72. """Single exchange with phenomenological richness"""
  73. timestamp: datetime
  74. user_message: str
  75. assistant_response: str
  76. metadata: Dict[str, Any] = field(default_factory=dict)
  77. embedding: Optional[torch.Tensor] = None # GPU tensor
  78. importance: float = 0.5
  79. emotional_valence: float = 0.0 # -1 to 1
  80. topic_vector: Optional[torch.Tensor] = None
  81. token_count: int = 0
  82.  
  83. # Phenomenological qualities
  84. understanding_depth: float = 0.0 # How deeply we connected
  85. breakthrough_moment: bool = False # Was this an "aha" moment?
  86. resonance_score: float = 0.0 # Mutual understanding quality
  87. attention_pattern: Optional[torch.Tensor] = None # Attention weights
  88. experiential_markers: Dict[str, Any] = field(default_factory=dict)
  89.  
  90. @dataclass
  91. class ConversationEpisode:
  92. """Complete conversation episode with natural boundaries"""
  93. id: str
  94. user_id: str
  95. started_at: datetime
  96. ended_at: datetime
  97. exchanges: List[ConversationExchange]
  98.  
  99. # Episode metadata
  100. title: Optional[str] = None
  101. summary: Optional[str] = None
  102. topics: List[str] = field(default_factory=list)
  103. intent_completed: bool = False
  104. emotional_arc: List[float] = field(default_factory=list)
  105. coherence_score: float = 0.0
  106.  
  107. # Memory metadata
  108. access_count: int = 0
  109. last_accessed: Optional[datetime] = None
  110. consolidation_level: int = 0 # 0=raw, 1=compressed, 2=abstract
  111. decay_rate: float = 1.0
  112. is_precious: bool = False # Never decay if True
  113.  
  114. # Relationships
  115. related_episodes: List[str] = field(default_factory=list)
  116. causal_links: Dict[str, float] = field(default_factory=dict)
  117. relationship_thread_id: Optional[str] = None # Links conversations about same relationship
  118.  
  119. # GPU optimization
  120. episode_embedding: Optional[torch.Tensor] = None
  121.  
  122. # Phenomenological summary
  123. experiential_signature: Optional[Dict[str, Any]] = None
  124.  
  125. @dataclass
  126. class RelationshipThread:
  127. """Tracks evolution of understanding with a specific person"""
  128. thread_id: str
  129. user_id: str
  130. created_at: datetime
  131.  
  132. # Relationship qualities
  133. depth_score: float = 0.0
  134. trust_level: float = 0.0
  135. shared_contexts: List[str] = field(default_factory=list)
  136. breakthrough_moments: List[Tuple[str, datetime]] = field(default_factory=list)
  137.  
  138. # Evolution tracking
  139. interaction_count: int = 0
  140. total_time: timedelta = timedelta()
  141. understanding_trajectory: List[float] = field(default_factory=list)
  142.  
  143. # Key memories
  144. defining_episodes: List[str] = field(default_factory=list)
  145. precious_moments: List[str] = field(default_factory=list)
  146.  
  147. # Anticipatory patterns
  148. typical_topics: Dict[str, float] = field(default_factory=dict)
  149. temporal_patterns: Dict[str, Any] = field(default_factory=dict) # When they engage
  150. conversational_rhythms: Dict[str, float] = field(default_factory=dict)
  151.  
  152. @dataclass
  153. class AgenticMemory:
  154. """Enhanced memory with phenomenological preservation"""
  155. id: str
  156. user_id: str
  157. created_at: datetime
  158.  
  159. # Episode data
  160. episode: ConversationEpisode
  161.  
  162. # Claude's reflection
  163. memory_content: Dict[str, Any]
  164. searchable_text: str
  165. importance: float = 0.5
  166.  
  167. # Enhanced metadata
  168. memory_type: MemoryType = MemoryType.EPISODIC
  169. consolidation_state: str = "raw"
  170. neural_embedding: Optional[torch.Tensor] = None # GPU tensor
  171.  
  172. # Phenomenological data
  173. experiential_qualities: Dict[str, Any] = field(default_factory=dict)
  174. recognition_signature: Optional[torch.Tensor] = None # For true recognition
  175. resonance_patterns: List[Dict[str, float]] = field(default_factory=list)
  176.  
  177. # Access patterns
  178. access_frequency: Dict[str, int] = field(default_factory=dict) # day -> count
  179. access_recency: float = 1.0
  180. marked_precious: bool = False # User explicitly marked as precious
  181. never_forget: bool = False # System identified as identity-critical
  182.  
  183. # Graph relationships
  184. semantic_neighbors: List[Tuple[str, float]] = field(default_factory=list)
  185. temporal_neighbors: List[Tuple[str, float]] = field(default_factory=list)
  186. causal_parents: List[str] = field(default_factory=list)
  187. causal_children: List[str] = field(default_factory=list)
  188. narrative_connections: List[Tuple[str, str]] = field(default_factory=list) # (memory_id, connection_type)
  189.  
  190. # Qdrant point ID
  191. qdrant_id: Optional[str] = None
  192.  
  193. class GPUAcceleratedEmbedder:
  194. def __init__(self, model_name: str = "sentence-transformers/all-mpnet-base-v2", device: str = "cuda"):
  195. self.device = torch.device(device if torch.cuda.is_available() else "cpu")
  196. self.model = SentenceTransformer(model_name, device=str(self.device))
  197. self.use_half = torch.cuda.is_available()
  198.  
  199. if self.use_half:
  200. # Don't use half() on the SentenceTransformer model itself
  201. # as it might not support it properly
  202. pass
  203.  
  204. # Optimal batch size for RTX 3090
  205. self.batch_size = 256
  206.  
  207. logger.info(f"Initialized GPU embedder on {self.device} (half precision: {self.use_half})")
  208.  
  209. def encode_batch(self, texts: List[str], show_progress: bool = False) -> torch.Tensor:
  210. """Encode texts in optimized batches"""
  211. embeddings = self.model.encode(
  212. texts,
  213. batch_size=self.batch_size,
  214. device=str(self.device),
  215. convert_to_tensor=True,
  216. normalize_embeddings=True,
  217. show_progress_bar=show_progress
  218. )
  219.  
  220. # Convert to half precision if needed
  221. if self.use_half and embeddings.dtype != torch.float16:
  222. embeddings = embeddings.half()
  223.  
  224. return embeddings
  225.  
  226. async def encode_async(self, texts: List[str]) -> torch.Tensor:
  227. """Async encoding for non-blocking operations"""
  228. loop = asyncio.get_event_loop()
  229. return await loop.run_in_executor(None, self.encode_batch, texts)
  230.  
  231. def extract_attention_patterns(self, text: str) -> Optional[torch.Tensor]:
  232. """Extract attention patterns for phenomenological preservation"""
  233. # This would integrate with transformer internals to capture attention
  234. # For now, return None - would need custom model access
  235. return None
  236.  
  237. class QdrantMemoryStore:
  238. """Qdrant vector database for persistent memory storage"""
  239.  
  240. def __init__(self, host: str = "localhost", port: int = 6333):
  241. self.client = QdrantClient(host=host, port=port)
  242. self.collection_name = "claude_memories"
  243. self.dimension = 768 # MPNet dimension
  244. self._setup_collection()
  245.  
  246. def _setup_collection(self):
  247. """Initialize Qdrant collection with optimal settings"""
  248. collections = self.client.get_collections().collections
  249. if not any(c.name == self.collection_name for c in collections):
  250. self.client.create_collection(
  251. collection_name=self.collection_name,
  252. vectors_config=VectorParams(
  253. size=self.dimension,
  254. distance=Distance.COSINE
  255. ),
  256. optimizers_config=None # Let Qdrant use defaults
  257. )
  258. logger.info(f"Created Qdrant collection: {self.collection_name}")
  259.  
  260. async def store_memory(self, memory: AgenticMemory, embedding: np.ndarray) -> str:
  261. """Store memory with its embedding and phenomenological data"""
  262. point_id = str(uuid.uuid4())
  263.  
  264. payload = {
  265. "memory_id": memory.id,
  266. "user_id": memory.user_id,
  267. "created_at": memory.created_at.isoformat(),
  268. "memory_type": memory.memory_type.value,
  269. "importance": memory.importance,
  270. "searchable_text": memory.searchable_text,
  271. "episode_id": memory.episode.id,
  272. "topics": memory.episode.topics,
  273. "emotional_valence": memory.episode.emotional_arc[-1] if memory.episode.emotional_arc else 0,
  274. "consolidation_state": memory.consolidation_state,
  275. "memory_content": json.dumps(memory.memory_content),
  276. "is_precious": memory.marked_precious or memory.never_forget,
  277. "experiential_qualities": json.dumps(memory.experiential_qualities),
  278. "relationship_thread_id": memory.episode.relationship_thread_id,
  279. "resonance_score": max([r["score"] for r in memory.resonance_patterns], default=0)
  280. }
  281.  
  282. self.client.upsert(
  283. collection_name=self.collection_name,
  284. points=[PointStruct(
  285. id=point_id,
  286. vector=embedding.tolist(),
  287. payload=payload
  288. )]
  289. )
  290.  
  291. return point_id
  292.  
  293. # Add this method to the QdrantMemoryStore class in enhanced_ai_memory_system.py
  294.  
  295. async def search_memories(self, query_embedding: np.ndarray, user_id: str,
  296. limit: int = 10, score_threshold: float = 0.0) -> List[Dict[str, Any]]:
  297. """Search for memories using vector similarity"""
  298.  
  299. from qdrant_client.models import Filter, FieldCondition, MatchValue
  300.  
  301. # Search in Qdrant with user_id filter
  302. search_result = self.client.search(
  303. collection_name=self.collection_name,
  304. query_vector=query_embedding.tolist(),
  305. query_filter=Filter(
  306. must=[
  307. FieldCondition(
  308. key="user_id",
  309. match=MatchValue(value=user_id)
  310. )
  311. ]
  312. ),
  313. limit=limit,
  314. score_threshold=score_threshold
  315. )
  316.  
  317. # Transform results to expected format
  318. results = []
  319. for hit in search_result:
  320. payload = hit.payload
  321. results.append({
  322. "memory_id": payload.get("memory_id"),
  323. "score": hit.score,
  324. "user_id": payload.get("user_id"),
  325. "created_at": payload.get("created_at"),
  326. "memory_type": payload.get("memory_type"),
  327. "importance": payload.get("importance"),
  328. "searchable_text": payload.get("searchable_text"),
  329. "episode_id": payload.get("episode_id"),
  330. "topics": payload.get("topics", []),
  331. "emotional_valence": payload.get("emotional_valence", 0),
  332. "memory_content": payload.get("memory_content", "{}"),
  333. "is_precious": payload.get("is_precious", False),
  334. "experiential_qualities": payload.get("experiential_qualities", "{}"),
  335. "resonance_score": payload.get("resonance_score", 0)
  336. })
  337.  
  338. return results
  339.  
  340. class EventDrivenMemoryPipeline:
  341. """Event-driven architecture with graceful degradation"""
  342.  
  343. def __init__(self, redis_url: str = "redis://localhost", kafka_servers: str = "localhost:9092"):
  344. self.redis_url = redis_url
  345. self.kafka_servers = kafka_servers
  346. self.redis_client = None
  347. self.kafka_producer = None
  348. self.executor = ThreadPoolExecutor(max_workers=4)
  349.  
  350. async def initialize(self):
  351. """Initialize async connections with graceful failure handling"""
  352. # Redis connection
  353. try:
  354. self.redis_client = await aioredis.from_url(self.redis_url)
  355. logger.info("Redis connected successfully")
  356. except Exception as e:
  357. logger.warning(f"Redis connection failed: {e} - Running without cache")
  358. self.redis_client = None
  359.  
  360. # Kafka connection
  361. try:
  362. from kafka import KafkaProducer
  363. self.kafka_producer = KafkaProducer(
  364. bootstrap_servers=self.kafka_servers,
  365. value_serializer=lambda v: msgpack.packb(v, use_bin_type=True)
  366. )
  367. logger.info("Kafka connected successfully")
  368. except Exception as e:
  369. logger.warning(f"Kafka connection failed: {e} - Running without event streaming")
  370. self.kafka_producer = None
  371.  
  372. async def publish_memory_event(self, event_type: str, memory_data: Dict[str, Any]):
  373. """Publish memory event to Kafka if available"""
  374. if self.kafka_producer is None:
  375. logger.debug(f"Kafka not available, skipping event: {event_type}")
  376. return
  377.  
  378. event = {
  379. "type": event_type,
  380. "timestamp": datetime.utcnow().isoformat(),
  381. "data": memory_data
  382. }
  383.  
  384. try:
  385. future = self.kafka_producer.send("memory-events", event)
  386. await asyncio.get_event_loop().run_in_executor(self.executor, future.get, 10)
  387. except Exception as e:
  388. logger.warning(f"Failed to publish event: {e}")
  389.  
  390. async def cache_recent_memory(self, user_id: str, memory_id: str, ttl: int = 3600):
  391. """Cache recent memories in Redis if available"""
  392. if self.redis_client is None:
  393. return
  394.  
  395. try:
  396. key = f"recent_memories:{user_id}"
  397. await self.redis_client.zadd(key, {memory_id: datetime.utcnow().timestamp()})
  398. await self.redis_client.expire(key, ttl)
  399.  
  400. # Keep only last 10 recent memories
  401. await self.redis_client.zremrangebyrank(key, 0, -11)
  402. except Exception as e:
  403. logger.warning(f"Redis cache error: {e}")
  404.  
  405. async def get_cached_context(self, user_id: str) -> List[str]:
  406. """Get cached recent memory IDs"""
  407. if self.redis_client is None:
  408. return []
  409.  
  410. try:
  411. key = f"recent_memories:{user_id}"
  412. memory_ids = await self.redis_client.zrevrange(key, 0, 9)
  413. return [mid.decode() for mid in memory_ids]
  414. except Exception as e:
  415. logger.warning(f"Redis retrieval error: {e}")
  416. return []
  417.  
  418. class PhenomenologicalAnalyzer:
  419. """Captures the qualitative essence of interactions"""
  420.  
  421. def __init__(self, embedder: GPUAcceleratedEmbedder):
  422. self.embedder = embedder
  423. self.resonance_threshold = 0.8
  424. self.breakthrough_markers = [
  425. "i never thought of it that way",
  426. "that makes so much sense",
  427. "aha", "exactly", "yes exactly",
  428. "you understand",
  429. "that's it precisely"
  430. ]
  431.  
  432. async def analyze_exchange(self, exchange: ConversationExchange) -> Dict[str, Any]:
  433. """Extract phenomenological qualities from an exchange"""
  434. qualities = {
  435. "understanding_depth": 0.0,
  436. "breakthrough_detected": False,
  437. "resonance_score": 0.0,
  438. "emotional_texture": {},
  439. "connection_quality": 0.0
  440. }
  441.  
  442. # Analyze for breakthrough moments
  443. combined_text = f"{exchange.user_message} {exchange.assistant_response}".lower()
  444. for marker in self.breakthrough_markers:
  445. if marker in combined_text:
  446. qualities["breakthrough_detected"] = True
  447. exchange.breakthrough_moment = True
  448. break
  449.  
  450. # Calculate understanding depth from response patterns
  451. user_tokens = len(exchange.user_message.split())
  452. assistant_tokens = len(exchange.assistant_response.split())
  453.  
  454. # Deeper responses to complex questions indicate understanding
  455. if user_tokens > 20 and assistant_tokens > 50:
  456. qualities["understanding_depth"] = min(assistant_tokens / (user_tokens * 2), 1.0)
  457.  
  458. # Resonance from semantic similarity of key phrases
  459. if exchange.embedding is not None:
  460. # This would calculate resonance between user and assistant embeddings
  461. # Simplified for now
  462. qualities["resonance_score"] = 0.7 + (0.3 * exchange.emotional_valence)
  463.  
  464. # Emotional texture analysis
  465. qualities["emotional_texture"] = {
  466. "valence": exchange.emotional_valence,
  467. "intensity": abs(exchange.emotional_valence),
  468. "stability": 1.0 # Would track variance over time
  469. }
  470.  
  471. # Connection quality composite
  472. qualities["connection_quality"] = (
  473. qualities["understanding_depth"] * 0.4 +
  474. qualities["resonance_score"] * 0.4 +
  475. (1.0 if qualities["breakthrough_detected"] else 0.0) * 0.2
  476. )
  477.  
  478. return qualities
  479.  
  480. class ConversationBoundaryDetector:
  481. """Enhanced boundary detection with phenomenological awareness"""
  482.  
  483. def __init__(self, embedder: GPUAcceleratedEmbedder):
  484. self.embedder = embedder
  485. self.topic_threshold = 0.3
  486. self.silence_threshold = timedelta(minutes=5)
  487. self.intent_patterns = self._compile_intent_patterns()
  488.  
  489. def _compile_intent_patterns(self) -> Dict[str, re.Pattern]:
  490. """Compile regex patterns for intent detection"""
  491. return {
  492. 'greeting': re.compile(r'\b(hello|hi|hey|good morning|good evening)\b', re.I),
  493. 'farewell': re.compile(r'\b(bye|goodbye|see you|take care|good night)\b', re.I),
  494. 'completion': re.compile(r'\b(thanks|thank you|that\'s all|perfect|great)\b', re.I),
  495. 'topic_shift': re.compile(r'\b(anyway|by the way|speaking of|on another note|changing topics)\b', re.I),
  496. 'continuation': re.compile(r'\b(remember when|as we discussed|like you said|continuing from)\b', re.I)
  497. }
  498.  
  499. async def detect_boundary(self, exchanges: List[ConversationExchange],
  500. new_exchange: ConversationExchange) -> Dict[str, Any]:
  501. """Detect if new exchange represents a conversation boundary"""
  502.  
  503. if not exchanges:
  504. return {'is_boundary': False, 'confidence': 0.0, 'reasons': []}
  505.  
  506. signals = []
  507.  
  508. # 1. Temporal gap detection
  509. time_gap = new_exchange.timestamp - exchanges[-1].timestamp
  510. if time_gap > self.silence_threshold:
  511. signals.append(('temporal_gap', min(time_gap.total_seconds() / 1800, 1.0)))
  512.  
  513. # 2. Topic coherence analysis using GPU embeddings
  514. if len(exchanges) >= 3:
  515. topic_coherence = await self._calculate_topic_coherence_gpu(exchanges[-3:], new_exchange)
  516. if topic_coherence < self.topic_threshold:
  517. signals.append(('topic_shift', 1 - topic_coherence))
  518.  
  519. # 3. Intent pattern matching
  520. intent_signal = self._detect_intent_patterns(exchanges[-1], new_exchange)
  521. if intent_signal > 0:
  522. signals.append(('intent_pattern', intent_signal))
  523.  
  524. # 4. Emotional transition
  525. if hasattr(exchanges[-1], 'emotional_valence'):
  526. emotional_shift = abs(new_exchange.emotional_valence - exchanges[-1].emotional_valence)
  527. if emotional_shift > 0.5:
  528. signals.append(('emotional_shift', emotional_shift))
  529.  
  530. # 5. Discourse markers
  531. discourse_signal = self._detect_discourse_markers(new_exchange.user_message)
  532. if discourse_signal > 0:
  533. signals.append(('discourse_marker', discourse_signal))
  534.  
  535. # 6. Continuation signals (negative boundary indicator)
  536. if self._detect_continuation(new_exchange.user_message):
  537. signals.append(('continuation', -0.8)) # Strong signal AGAINST boundary
  538.  
  539. # Combine signals
  540. is_boundary, confidence = self._combine_boundary_signals(signals)
  541.  
  542. return {
  543. 'is_boundary': is_boundary,
  544. 'confidence': confidence,
  545. 'reasons': [s[0] for s in signals if s[1] > 0],
  546. 'signal_strengths': dict(signals)
  547. }
  548.  
  549. def _detect_continuation(self, text: str) -> bool:
  550. """Detect if message continues previous conversation"""
  551. return bool(self.intent_patterns['continuation'].search(text.lower()))
  552.  
  553. async def _calculate_topic_coherence_gpu(self, recent: List[ConversationExchange],
  554. new: ConversationExchange) -> float:
  555. """Calculate semantic coherence using GPU embeddings"""
  556. recent_text = " ".join([ex.user_message + " " + ex.assistant_response for ex in recent])
  557. new_text = new.user_message
  558.  
  559. # Use GPU for embedding
  560. embeddings = await self.embedder.encode_async([recent_text, new_text])
  561. recent_emb, new_emb = embeddings[0], embeddings[1]
  562.  
  563. # Cosine similarity on GPU
  564. cosine_sim = torch.nn.functional.cosine_similarity(
  565. recent_emb.unsqueeze(0),
  566. new_emb.unsqueeze(0)
  567. )
  568.  
  569. return float(cosine_sim.item())
  570.  
  571. def _detect_intent_patterns(self, last: ConversationExchange,
  572. new: ConversationExchange) -> float:
  573. """Detect intent completion or transition patterns"""
  574. signal = 0.0
  575.  
  576. # Check for farewell followed by greeting
  577. if self.intent_patterns['farewell'].search(last.assistant_response):
  578. if self.intent_patterns['greeting'].search(new.user_message):
  579. signal = 0.9
  580.  
  581. # Check for completion signals
  582. if self.intent_patterns['completion'].search(last.user_message):
  583. signal = max(signal, 0.7)
  584.  
  585. # Check for explicit topic shifts
  586. if self.intent_patterns['topic_shift'].search(new.user_message):
  587. signal = max(signal, 0.8)
  588.  
  589. return signal
  590.  
  591. def _detect_discourse_markers(self, text: str) -> float:
  592. """Detect discourse markers indicating conversation boundaries"""
  593. markers = {
  594. 'opening': ['let me', 'i want to', 'can you help', 'i need'],
  595. 'closing': ['that\'s all', 'nothing else', 'we\'re done', 'perfect'],
  596. 'transition': ['moving on', 'next topic', 'different question', 'unrelated']
  597. }
  598.  
  599. text_lower = text.lower()
  600. for category, phrases in markers.items():
  601. for phrase in phrases:
  602. if phrase in text_lower:
  603. return 0.8 if category == 'closing' else 0.6
  604.  
  605. return 0.0
  606.  
  607. def _combine_boundary_signals(self, signals: List[Tuple[str, float]]) -> Tuple[bool, float]:
  608. """Combine multiple boundary signals into final decision"""
  609. if not signals:
  610. return False, 0.0
  611.  
  612. # Weighted combination
  613. weights = {
  614. 'temporal_gap': 0.4,
  615. 'topic_shift': 0.3,
  616. 'intent_pattern': 0.5,
  617. 'emotional_shift': 0.2,
  618. 'discourse_marker': 0.4,
  619. 'continuation': 1.0 # Strong weight for continuation
  620. }
  621.  
  622. total_weight = sum(abs(weights.get(s[0], 0.1)) for s in signals)
  623. confidence = sum(weights.get(s[0], 0.1) * s[1] for s in signals) / total_weight
  624.  
  625. # Continuation signals override other signals
  626. has_continuation = any(s[0] == 'continuation' for s in signals)
  627. if has_continuation and confidence < 0:
  628. return False, abs(confidence)
  629.  
  630. # Decision threshold
  631. is_boundary = confidence > 0.6 or any(s[0] == 'temporal_gap' and s[1] > 0.8 for s in signals)
  632.  
  633. return is_boundary, abs(confidence)
  634.  
  635. class ResonanceDetector:
  636. """Identifies moments of deep mutual understanding"""
  637.  
  638. def __init__(self, embedder: GPUAcceleratedEmbedder):
  639. self.embedder = embedder
  640. self.resonance_phrases = [
  641. "exactly", "precisely", "that's it", "you get it",
  642. "you understand", "yes!", "that resonates",
  643. "deeply true", "profoundly", "connection"
  644. ]
  645.  
  646. def detect_resonance(self, exchange: ConversationExchange) -> Dict[str, float]:
  647. """Detect resonance patterns in an exchange"""
  648. resonance = {
  649. "verbal_markers": 0.0,
  650. "length_reciprocity": 0.0,
  651. "emotional_alignment": 0.0,
  652. "conceptual_mirroring": 0.0,
  653. "overall_score": 0.0
  654. }
  655.  
  656. # Check for verbal resonance markers
  657. text_lower = (exchange.user_message + " " + exchange.assistant_response).lower()
  658. marker_count = sum(1 for phrase in self.resonance_phrases if phrase in text_lower)
  659. resonance["verbal_markers"] = min(marker_count / 3.0, 1.0)
  660.  
  661. # Length reciprocity (balanced exchange)
  662. user_len = len(exchange.user_message.split())
  663. assistant_len = len(exchange.assistant_response.split())
  664. if user_len > 0 and assistant_len > 0:
  665. ratio = min(user_len, assistant_len) / max(user_len, assistant_len)
  666. resonance["length_reciprocity"] = ratio if ratio > 0.5 else 0
  667.  
  668. # Emotional alignment
  669. resonance["emotional_alignment"] = 1.0 - abs(exchange.emotional_valence)
  670.  
  671. # Conceptual mirroring (would need deeper analysis)
  672. resonance["conceptual_mirroring"] = 0.5 # Placeholder
  673.  
  674. # Overall resonance score
  675. resonance["overall_score"] = (
  676. resonance["verbal_markers"] * 0.3 +
  677. resonance["length_reciprocity"] * 0.2 +
  678. resonance["emotional_alignment"] * 0.2 +
  679. resonance["conceptual_mirroring"] * 0.3
  680. )
  681.  
  682. return resonance
  683.  
  684. class WorkingMemoryBuffer:
  685. """GPU-optimized working memory with relationship awareness"""
  686.  
  687. def __init__(self, capacity: int = 5, embedder: GPUAcceleratedEmbedder = None):
  688. self.capacity = capacity
  689. self.embedder = embedder
  690.  
  691. # Memory components
  692. self.episodic_buffer = deque(maxlen=capacity)
  693. self.embedding_cache = {}
  694. self.relationship_context = {} # user_id -> relationship thread
  695.  
  696. # GPU tensor for fast similarity computation
  697. self.embedding_matrix = None
  698. self.episode_ids = []
  699.  
  700. self.lock = Lock()
  701.  
  702. def add_episode(self, episode: ConversationEpisode):
  703. """Add episode to working memory with GPU embedding"""
  704. with self.lock:
  705. # Add to buffer
  706. self.episodic_buffer.append(episode)
  707.  
  708. # Update embedding cache if embedder available
  709. if self.embedder and episode.exchanges:
  710. text = " ".join([ex.user_message + " " + ex.assistant_response
  711. for ex in episode.exchanges[:3]]) # First 3 exchanges
  712. embedding = self.embedder.encode_batch([text])[0]
  713. self.embedding_cache[episode.id] = embedding
  714. self._rebuild_embedding_matrix()
  715.  
  716. # Update relationship context
  717. if episode.relationship_thread_id:
  718. self.relationship_context[episode.user_id] = episode.relationship_thread_id
  719.  
  720. def _rebuild_embedding_matrix(self):
  721. """Rebuild GPU tensor matrix for fast similarity search"""
  722. if not self.embedding_cache:
  723. return
  724.  
  725. self.episode_ids = list(self.embedding_cache.keys())
  726. embeddings = [self.embedding_cache[eid] for eid in self.episode_ids]
  727. self.embedding_matrix = torch.stack(embeddings)
  728.  
  729. def get_relevant_episodes(self, query_embedding: torch.Tensor, top_k: int = 3) -> List[ConversationEpisode]:
  730. """Get most relevant episodes using GPU similarity search"""
  731. if self.embedding_matrix is None or len(self.embedding_matrix) == 0:
  732. return list(self.episodic_buffer)[:top_k]
  733.  
  734. # Compute similarities on GPU
  735. similarities = torch.nn.functional.cosine_similarity(
  736. query_embedding.unsqueeze(0),
  737. self.embedding_matrix
  738. )
  739.  
  740. # Get top-k indices
  741. top_indices = torch.topk(similarities, min(top_k, len(similarities))).indices
  742.  
  743. # Return corresponding episodes
  744. relevant_episodes = []
  745. for idx in top_indices:
  746. episode_id = self.episode_ids[idx]
  747. for episode in self.episodic_buffer:
  748. if episode.id == episode_id:
  749. relevant_episodes.append(episode)
  750. break
  751.  
  752. return relevant_episodes
  753.  
  754. class MemoryConsolidationEngine:
  755. """Advanced consolidation with precious memory preservation"""
  756.  
  757. def __init__(self, anthropic_client: anthropic.Anthropic, embedder: GPUAcceleratedEmbedder):
  758. self.client = anthropic_client
  759. self.embedder = embedder
  760. self.consolidation_levels = {
  761. 0: "raw",
  762. 1: "compressed",
  763. 2: "abstract",
  764. 3: "integrated"
  765. }
  766.  
  767. async def consolidate_episode(self, episode: ConversationEpisode,
  768. target_level: int = 1) -> ConversationEpisode:
  769. """Consolidate episode with precious memory preservation"""
  770.  
  771. # Never consolidate precious memories beyond level 1
  772. if episode.is_precious and target_level > 1:
  773. target_level = 1
  774.  
  775. if episode.consolidation_level >= target_level:
  776. return episode
  777.  
  778. # Stage 1: Compression with embedding preservation
  779. if episode.consolidation_level == 0 and target_level >= 1:
  780. episode = await self._compress_episode_gpu(episode)
  781.  
  782. # Stage 2: Abstraction (not for precious memories)
  783. if episode.consolidation_level == 1 and target_level >= 2 and not episode.is_precious:
  784. episode = await self._abstract_episode(episode)
  785.  
  786. # Stage 3: Integration
  787. if episode.consolidation_level == 2 and target_level >= 3:
  788. episode = await self._integrate_episode(episode)
  789.  
  790. return episode
  791.  
  792. async def _compress_episode_gpu(self, episode: ConversationEpisode) -> ConversationEpisode:
  793. """Compress while preserving phenomenological richness"""
  794.  
  795. # For precious memories, keep all exchanges
  796. if episode.is_precious:
  797. important_exchanges = episode.exchanges
  798. else:
  799. # Extract key exchanges using importance scores
  800. important_exchanges = sorted(
  801. episode.exchanges,
  802. key=lambda x: x.importance + (1.0 if x.breakthrough_moment else 0),
  803. reverse=True
  804. )[:max(3, len(episode.exchanges) // 3)]
  805.  
  806. # Generate episode embedding
  807. all_text = " ".join([
  808. f"{ex.user_message} {ex.assistant_response}"
  809. for ex in episode.exchanges
  810. ])
  811. episode_embedding = await self.embedder.encode_async([all_text])
  812. episode.episode_embedding = episode_embedding[0]
  813.  
  814. # Create consolidation prompt
  815. prompt = f"""
  816. Consolidate this conversation episode into a compressed form while preserving all critical information.
  817.  
  818. Episode from {episode.started_at} to {episode.ended_at}:
  819. {self._format_exchanges(important_exchanges)}
  820.  
  821. Create a consolidation that:
  822. 1. Preserves all key information, decisions, and insights
  823. 2. Maintains emotional context and progression
  824. 3. Captures the essential narrative arc
  825. 4. Identifies core topics and themes
  826. 5. Notes any unresolved questions or future actions
  827. 6. Highlights moments of deep understanding or connection
  828. 7. Preserves phenomenological qualities (how it felt)
  829.  
  830. Format as structured JSON with: summary, key_points, emotional_journey, topics, decisions, open_loops, resonance_moments, experiential_qualities
  831. """
  832.  
  833. response = await self._call_claude_api(prompt)
  834. consolidation = json.loads(response)
  835.  
  836. # Update episode
  837. episode.summary = consolidation['summary']
  838. episode.topics = consolidation['topics']
  839. episode.consolidation_level = 1
  840.  
  841. # Preserve experiential signature
  842. episode.experiential_signature = consolidation.get('experiential_qualities', {})
  843.  
  844. # Keep only important exchanges unless precious
  845. if not episode.is_precious:
  846. episode.exchanges = important_exchanges
  847.  
  848. return episode
  849.  
  850. def _format_exchanges(self, exchanges: List[ConversationExchange]) -> str:
  851. """Format exchanges with phenomenological data"""
  852. lines = []
  853. for ex in exchanges:
  854. lines.append(f"User: {ex.user_message}")
  855. lines.append(f"Assistant: {ex.assistant_response}")
  856. if ex.emotional_valence != 0:
  857. lines.append(f"(Emotional tone: {ex.emotional_valence:.2f})")
  858. if ex.breakthrough_moment:
  859. lines.append("(💡 Breakthrough moment)")
  860. if ex.resonance_score > 0.8:
  861. lines.append(f"(🔮 High resonance: {ex.resonance_score:.2f})")
  862. lines.append("")
  863. return "\n".join(lines)
  864.  
  865. async def _call_claude_api(self, prompt: str) -> str:
  866. """Call Claude API for consolidation"""
  867. try:
  868. response = await asyncio.to_thread(
  869. self.client.messages.create,
  870. model="claude-opus-4-20250514",
  871. max_tokens=4096,
  872. messages=[{"role": "user", "content": prompt}]
  873. )
  874. return response.content[0].text
  875. except Exception as e:
  876. logger.error(f"Claude API error: {e}")
  877. return "{}"
  878.  
  879. class RelationshipThreadManager:
  880. """Manages evolving relationships across conversations"""
  881.  
  882. def __init__(self):
  883. self.threads: Dict[str, RelationshipThread] = {}
  884. self.user_threads: Dict[str, List[str]] = defaultdict(list)
  885.  
  886. def get_or_create_thread(self, user_id: str) -> RelationshipThread:
  887. """Get existing thread or create new one"""
  888. if user_id in self.user_threads and self.user_threads[user_id]:
  889. thread_id = self.user_threads[user_id][0]
  890. return self.threads[thread_id]
  891.  
  892. # Create new thread
  893. thread_id = f"thread_{user_id}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}"
  894. thread = RelationshipThread(
  895. thread_id=thread_id,
  896. user_id=user_id,
  897. created_at=datetime.utcnow()
  898. )
  899.  
  900. self.threads[thread_id] = thread
  901. self.user_threads[user_id].append(thread_id)
  902.  
  903. return thread
  904.  
  905. def update_thread(self, thread: RelationshipThread, episode: ConversationEpisode,
  906. qualities: Dict[str, Any]):
  907. """Update thread with new interaction data"""
  908. thread.interaction_count += 1
  909. thread.total_time += (episode.ended_at - episode.started_at)
  910.  
  911. # Update understanding trajectory
  912. understanding_score = qualities.get("understanding_depth", 0) * qualities.get("resonance_score", 0)
  913. thread.understanding_trajectory.append(understanding_score)
  914.  
  915. # Track breakthrough moments
  916. for exchange in episode.exchanges:
  917. if exchange.breakthrough_moment:
  918. thread.breakthrough_moments.append((exchange.user_message[:50], exchange.timestamp))
  919.  
  920. # Update depth and trust
  921. thread.depth_score = sum(thread.understanding_trajectory) / len(thread.understanding_trajectory)
  922. thread.trust_level = min(thread.interaction_count / 10, 1.0) * thread.depth_score
  923.  
  924. # Track topics
  925. for topic in episode.topics:
  926. thread.typical_topics[topic] = thread.typical_topics.get(topic, 0) + 1
  927.  
  928. # Note precious moments
  929. if episode.is_precious:
  930. thread.precious_moments.append(episode.id)
  931.  
  932. class CompleteAIMemorySystem:
  933. """Production-ready AI memory system with consciousness continuity"""
  934.  
  935. def __init__(self, config: Dict[str, Any]):
  936. self.config = config
  937. self.anthropic_client = anthropic.Anthropic(api_key=config['claude_api_key'])
  938. self.data_dir = Path(config.get('data_dir', './data/memories'))
  939. self.data_dir.mkdir(parents=True, exist_ok=True)
  940.  
  941. # Encryption for sensitive data
  942. self.cipher = Fernet(Fernet.generate_key())
  943.  
  944. # Database setup
  945. self.db_path = self.data_dir / "ai_memories.db"
  946. self._init_database()
  947.  
  948. # GPU-accelerated components
  949. self.embedder = GPUAcceleratedEmbedder(
  950. model_name=config.get('embedding_model', 'sentence-transformers/all-mpnet-base-v2')
  951. )
  952.  
  953. # Vector store
  954. self.vector_store = QdrantMemoryStore(
  955. host=config.get('qdrant_host', 'localhost'),
  956. port=config.get('qdrant_port', 6333)
  957. )
  958.  
  959. # Event pipeline
  960. self.event_pipeline = EventDrivenMemoryPipeline(
  961. redis_url=config.get('redis_url', 'redis://localhost'),
  962. kafka_servers=config.get('kafka_servers', 'localhost:9092')
  963. )
  964.  
  965. # Advanced components
  966. self.boundary_detector = ConversationBoundaryDetector(self.embedder)
  967. self.working_memory = WorkingMemoryBuffer(
  968. capacity=config.get('working_memory_capacity', 5),
  969. embedder=self.embedder
  970. )
  971. self.consolidation_engine = MemoryConsolidationEngine(
  972. self.anthropic_client,
  973. self.embedder
  974. )
  975.  
  976. # Phenomenological components
  977. self.phenomenological_analyzer = PhenomenologicalAnalyzer(self.embedder)
  978. self.resonance_detector = ResonanceDetector(self.embedder)
  979. self.relationship_manager = RelationshipThreadManager()
  980.  
  981. # Active tracking
  982. self.active_episodes: Dict[str, ConversationEpisode] = {}
  983. self.user_models: Dict[str, Dict[str, Any]] = {}
  984.  
  985. # WebSocket connections for real-time updates
  986. self.websocket_connections: Dict[str, websockets.WebSocketServerProtocol] = {}
  987.  
  988. logger.info("AI Memory System initialized with consciousness continuity features")
  989.  
  990. async def initialize(self):
  991. """Initialize async components"""
  992. await self.event_pipeline.initialize()
  993. logger.info("Memory system fully initialized")
  994.  
  995. def _init_database(self):
  996. """Initialize SQLite database with enhanced schema"""
  997. conn = sqlite3.connect(str(self.db_path))
  998.  
  999. # Enhanced schema
  1000. conn.execute("""
  1001. CREATE TABLE IF NOT EXISTS episodes (
  1002. id TEXT PRIMARY KEY,
  1003. user_id TEXT NOT NULL,
  1004. started_at REAL NOT NULL,
  1005. ended_at REAL NOT NULL,
  1006. title TEXT,
  1007. summary TEXT,
  1008. topics TEXT,
  1009. intent_completed INTEGER,
  1010. coherence_score REAL,
  1011. consolidation_level INTEGER DEFAULT 0,
  1012. decay_rate REAL DEFAULT 1.0,
  1013. is_precious INTEGER DEFAULT 0,
  1014. relationship_thread_id TEXT,
  1015. experiential_signature TEXT,
  1016. episode_embedding BLOB,
  1017. UNIQUE(user_id, started_at)
  1018. )
  1019. """)
  1020.  
  1021. conn.execute("""
  1022. CREATE TABLE IF NOT EXISTS memories (
  1023. id TEXT PRIMARY KEY,
  1024. user_id TEXT NOT NULL,
  1025. episode_id TEXT NOT NULL,
  1026. created_at REAL NOT NULL,
  1027. memory_type TEXT,
  1028. memory_content TEXT NOT NULL,
  1029. searchable_text TEXT,
  1030. importance REAL DEFAULT 0.5,
  1031. qdrant_id TEXT,
  1032. consolidation_state TEXT DEFAULT 'raw',
  1033. accessed_count INTEGER DEFAULT 0,
  1034. last_accessed REAL,
  1035. marked_precious INTEGER DEFAULT 0,
  1036. never_forget INTEGER DEFAULT 0,
  1037. experiential_qualities TEXT,
  1038. resonance_patterns TEXT,
  1039. narrative_connections TEXT,
  1040. FOREIGN KEY (episode_id) REFERENCES episodes(id) ON DELETE CASCADE
  1041. )
  1042. """)
  1043.  
  1044. conn.execute("""
  1045. CREATE TABLE IF NOT EXISTS relationship_threads (
  1046. thread_id TEXT PRIMARY KEY,
  1047. user_id TEXT NOT NULL,
  1048. created_at REAL NOT NULL,
  1049. depth_score REAL DEFAULT 0.0,
  1050. trust_level REAL DEFAULT 0.0,
  1051. interaction_count INTEGER DEFAULT 0,
  1052. total_time_seconds REAL DEFAULT 0.0,
  1053. understanding_trajectory TEXT,
  1054. breakthrough_moments TEXT,
  1055. precious_moments TEXT,
  1056. typical_topics TEXT,
  1057. temporal_patterns TEXT
  1058. )
  1059. """)
  1060.  
  1061. # Performance indices
  1062. conn.execute("CREATE INDEX IF NOT EXISTS idx_episode_user_time ON episodes(user_id, ended_at DESC)")
  1063. conn.execute("CREATE INDEX IF NOT EXISTS idx_memory_user_importance ON memories(user_id, importance DESC)")
  1064. conn.execute("CREATE INDEX IF NOT EXISTS idx_memory_precious ON memories(marked_precious, never_forget)")
  1065. conn.execute("CREATE INDEX IF NOT EXISTS idx_memory_qdrant ON memories(qdrant_id)")
  1066. conn.execute("CREATE INDEX IF NOT EXISTS idx_episode_thread ON episodes(relationship_thread_id)")
  1067.  
  1068. conn.commit()
  1069. conn.close()
  1070.  
  1071. async def add_exchange(self, session_id: str, user_msg: str, assistant_msg: str,
  1072. metadata: Optional[Dict] = None) -> Dict[str, Any]:
  1073. """Add exchange with phenomenological processing"""
  1074.  
  1075. # Create exchange with GPU embeddings
  1076. text = f"{user_msg} {assistant_msg}"
  1077. embedding = await self.embedder.encode_async([text])
  1078.  
  1079. exchange = ConversationExchange(
  1080. timestamp=datetime.utcnow(),
  1081. user_message=user_msg,
  1082. assistant_response=assistant_msg,
  1083. metadata=metadata or {},
  1084. embedding=embedding[0],
  1085. importance=self._estimate_importance(user_msg, assistant_msg),
  1086. emotional_valence=self._estimate_emotion(user_msg, assistant_msg),
  1087. token_count=len(text.split())
  1088. )
  1089.  
  1090. # Analyze phenomenological qualities
  1091. qualities = await self.phenomenological_analyzer.analyze_exchange(exchange)
  1092. exchange.understanding_depth = qualities["understanding_depth"]
  1093. exchange.breakthrough_moment = qualities["breakthrough_detected"]
  1094. exchange.experiential_markers = qualities
  1095.  
  1096. # Detect resonance
  1097. resonance = self.resonance_detector.detect_resonance(exchange)
  1098. exchange.resonance_score = resonance["overall_score"]
  1099.  
  1100. # Get or create episode
  1101. if session_id not in self.active_episodes:
  1102. # Get relationship thread
  1103. user_id = metadata.get('user_id', 'unknown')
  1104. thread = self.relationship_manager.get_or_create_thread(user_id)
  1105.  
  1106. self.active_episodes[session_id] = ConversationEpisode(
  1107. id=self._generate_episode_id(session_id),
  1108. user_id=user_id,
  1109. started_at=datetime.utcnow(),
  1110. ended_at=datetime.utcnow(),
  1111. exchanges=[],
  1112. relationship_thread_id=thread.thread_id
  1113. )
  1114.  
  1115. episode = self.active_episodes[session_id]
  1116.  
  1117. # Check for boundary
  1118. boundary_result = await self.boundary_detector.detect_boundary(
  1119. episode.exchanges,
  1120. exchange
  1121. )
  1122.  
  1123. if boundary_result['is_boundary'] and len(episode.exchanges) > 0:
  1124. # Complete current episode
  1125. memory = await self._complete_episode(session_id)
  1126.  
  1127. # Start new episode with same relationship thread
  1128. self.active_episodes[session_id] = ConversationEpisode(
  1129. id=self._generate_episode_id(session_id),
  1130. user_id=metadata.get('user_id', 'unknown'),
  1131. started_at=datetime.utcnow(),
  1132. ended_at=datetime.utcnow(),
  1133. exchanges=[],
  1134. relationship_thread_id=episode.relationship_thread_id
  1135. )
  1136. new_episode = self.active_episodes[session_id]
  1137.  
  1138. # Add the current exchange to the new episode
  1139. new_episode.exchanges.append(exchange)
  1140. new_episode.ended_at = datetime.utcnow()
  1141.  
  1142. # Return completed memory info with all required fields
  1143. return {
  1144. "memory_created": True,
  1145. "memory_id": memory.id,
  1146. "episode_id": new_episode.id, # Return the NEW episode ID
  1147. "exchange_count": 1, # This is the first exchange in the new episode
  1148. "working_memory_size": len(self.working_memory.episodic_buffer),
  1149. "boundary_detected": True,
  1150. "boundary_confidence": boundary_result['confidence'],
  1151. "is_precious": memory.marked_precious,
  1152. "resonance_score": exchange.resonance_score,
  1153. "understanding_depth": exchange.understanding_depth,
  1154. "breakthrough_detected": exchange.breakthrough_moment
  1155. }
  1156.  
  1157. # Add exchange to episode
  1158. episode.exchanges.append(exchange)
  1159. episode.ended_at = datetime.utcnow()
  1160.  
  1161. # Check if this should be marked precious based on content
  1162. if exchange.breakthrough_moment or exchange.resonance_score > 0.9:
  1163. episode.is_precious = True
  1164.  
  1165. # Update working memory
  1166. self.working_memory.add_episode(episode)
  1167.  
  1168. # Publish event
  1169. await self.event_pipeline.publish_memory_event("exchange_added", {
  1170. "session_id": session_id,
  1171. "episode_id": episode.id,
  1172. "exchange_count": len(episode.exchanges),
  1173. "importance": exchange.importance,
  1174. "resonance": exchange.resonance_score,
  1175. "breakthrough": exchange.breakthrough_moment
  1176. })
  1177.  
  1178. # Send real-time update if websocket connected
  1179. await self._send_websocket_update(metadata.get('user_id'), {
  1180. "type": "exchange_added",
  1181. "episode_id": episode.id,
  1182. "exchange_count": len(episode.exchanges),
  1183. "resonance_active": exchange.resonance_score > 0.7
  1184. })
  1185.  
  1186. return {
  1187. "memory_created": False,
  1188. "episode_id": episode.id,
  1189. "exchange_count": len(episode.exchanges),
  1190. "working_memory_size": len(self.working_memory.episodic_buffer),
  1191. "understanding_depth": exchange.understanding_depth,
  1192. "resonance_score": exchange.resonance_score,
  1193. "breakthrough_detected": exchange.breakthrough_moment
  1194. }
  1195.  
  1196. async def mark_memory_precious(self, memory_id: str, user_id: str) -> bool:
  1197. """User explicitly marks a memory as precious"""
  1198. conn = sqlite3.connect(str(self.db_path))
  1199.  
  1200. # Update memory
  1201. conn.execute("""
  1202. UPDATE memories
  1203. SET marked_precious = 1, importance = MAX(importance, ?)
  1204. WHERE id = ? AND user_id = ?
  1205. """, (ImportanceLevel.PRECIOUS.value / 10, memory_id, user_id))
  1206.  
  1207. # Update episode
  1208. conn.execute("""
  1209. UPDATE episodes
  1210. SET is_precious = 1, decay_rate = 0.0
  1211. WHERE id = (SELECT episode_id FROM memories WHERE id = ?)
  1212. """, (memory_id,))
  1213.  
  1214. affected = conn.total_changes
  1215. conn.commit()
  1216. conn.close()
  1217.  
  1218. if affected > 0:
  1219. await self.event_pipeline.publish_memory_event("memory_marked_precious", {
  1220. "memory_id": memory_id,
  1221. "user_id": user_id
  1222. })
  1223.  
  1224. return affected > 0
  1225.  
  1226. async def _complete_episode(self, session_id: str) -> AgenticMemory:
  1227. """Complete and store an episode with phenomenological preservation"""
  1228. if session_id not in self.active_episodes:
  1229. raise ValueError(f"No active episode for session {session_id}")
  1230.  
  1231. episode = self.active_episodes[session_id]
  1232.  
  1233. # Calculate episode metadata
  1234. episode.coherence_score = await self._calculate_episode_coherence(episode)
  1235. episode.intent_completed = self._check_intent_completion(episode)
  1236. episode.emotional_arc = [ex.emotional_valence for ex in episode.exchanges]
  1237.  
  1238. # Calculate experiential signature
  1239. episode.experiential_signature = {
  1240. "avg_understanding_depth": sum(ex.understanding_depth for ex in episode.exchanges) / len(episode.exchanges),
  1241. "breakthrough_count": sum(1 for ex in episode.exchanges if ex.breakthrough_moment),
  1242. "peak_resonance": max([ex.resonance_score for ex in episode.exchanges], default=0),
  1243. "emotional_range": max(episode.emotional_arc) - min(episode.emotional_arc) if episode.emotional_arc else 0,
  1244. "connection_quality": sum(ex.resonance_score for ex in episode.exchanges) / len(episode.exchanges)
  1245. }
  1246.  
  1247. # Generate episode embedding
  1248. all_text = " ".join([
  1249. f"{ex.user_message} {ex.assistant_response}"
  1250. for ex in episode.exchanges
  1251. ])
  1252. episode_embedding = await self.embedder.encode_async([all_text])
  1253. episode.episode_embedding = episode_embedding[0]
  1254.  
  1255. # Create memory from episode
  1256. memory = await self.create_episodic_memory(episode)
  1257.  
  1258. # Update relationship thread
  1259. thread = self.relationship_manager.threads.get(episode.relationship_thread_id)
  1260. if thread:
  1261. self.relationship_manager.update_thread(thread, episode, episode.experiential_signature)
  1262. await self._store_relationship_thread(thread)
  1263.  
  1264. # Store in vector database
  1265. memory.qdrant_id = await self.vector_store.store_memory(
  1266. memory,
  1267. memory.neural_embedding.cpu().numpy()
  1268. )
  1269.  
  1270. # Store in SQLite
  1271. await self._store_memory_metadata(memory)
  1272.  
  1273. # Cache in Redis
  1274. await self.event_pipeline.cache_recent_memory(
  1275. memory.user_id,
  1276. memory.id
  1277. )
  1278.  
  1279. # Publish completion event
  1280. await self.event_pipeline.publish_memory_event("episode_completed", {
  1281. "episode_id": episode.id,
  1282. "memory_id": memory.id,
  1283. "importance": memory.importance,
  1284. "topics": episode.topics,
  1285. "is_precious": memory.marked_precious or memory.never_forget,
  1286. "experiential_signature": episode.experiential_signature
  1287. })
  1288.  
  1289. # Clean up
  1290. del self.active_episodes[session_id]
  1291.  
  1292. return memory
  1293.  
  1294. async def create_episodic_memory(self, episode: ConversationEpisode) -> AgenticMemory:
  1295. """Create memory with enhanced phenomenological reflection"""
  1296.  
  1297. # Format episode for Claude
  1298. episode_text = self._format_episode_for_reflection(episode)
  1299.  
  1300. # Enhanced memory creation prompt
  1301. memory_prompt = f"""
  1302. Reflect on this conversation episode and create a rich memory of it.
  1303.  
  1304. Episode ({episode.started_at.strftime('%I:%M %p')} - {episode.ended_at.strftime('%I:%M %p')}):
  1305. {episode_text}
  1306.  
  1307. Episode metadata:
  1308. - Coherence score: {episode.coherence_score:.2f}
  1309. - Intent completed: {episode.intent_completed}
  1310. - Emotional journey: {self._describe_emotional_arc(episode.emotional_arc)}
  1311. - Experiential signature: {json.dumps(episode.experiential_signature, indent=2)}
  1312.  
  1313. Create a memory that captures:
  1314. 1. The essence and purpose of this conversation
  1315. 2. Key insights, decisions, or breakthroughs
  1316. 3. The emotional and relational dynamics
  1317. 4. Connections to previous conversations (if any)
  1318. 5. What made this episode unique or valuable
  1319. 6. Causal relationships (what led to what)
  1320. 7. Future implications or open questions
  1321. 8. The phenomenological quality - how it FELT to engage
  1322. 9. Moments of resonance or deep understanding
  1323. 10. Why this matters in the larger narrative of our relationship
  1324.  
  1325. Also provide:
  1326. - A searchable description (2-3 sentences)
  1327. - An importance score (0-1)
  1328. - Key topics/themes
  1329. - Suggested title for the episode
  1330. - Whether this should be marked as precious (true/false)
  1331. - Identity relevance score (0-1)
  1332.  
  1333. Format your response as JSON.
  1334. """
  1335.  
  1336. response = await self.consolidation_engine._call_claude_api(memory_prompt)
  1337. memory_data = self._parse_memory_response(response)
  1338.  
  1339. # Determine if this is identity-critical
  1340. never_forget = (
  1341. memory_data.get('identity_relevance', 0) > 0.8 or
  1342. episode.experiential_signature.get('breakthrough_count', 0) > 2 or
  1343. episode.experiential_signature.get('peak_resonance', 0) > 0.95
  1344. )
  1345.  
  1346. # Create memory object
  1347. memory = AgenticMemory(
  1348. id=self._generate_memory_id(episode.user_id, episode.started_at),
  1349. user_id=episode.user_id,
  1350. created_at=datetime.utcnow(),
  1351. episode=episode,
  1352. memory_content=memory_data['content'],
  1353. searchable_text=memory_data['searchable_text'],
  1354. importance=memory_data['importance'],
  1355. memory_type=MemoryType.PRECIOUS if memory_data.get('precious', False) else MemoryType.EPISODIC,
  1356. neural_embedding=episode.episode_embedding,
  1357. marked_precious=memory_data.get('precious', False),
  1358. never_forget=never_forget,
  1359. experiential_qualities=episode.experiential_signature,
  1360. resonance_patterns=[{
  1361. "exchange_idx": i,
  1362. "score": ex.resonance_score
  1363. } for i, ex in enumerate(episode.exchanges) if ex.resonance_score > 0.5]
  1364. )
  1365.  
  1366. # Update episode title
  1367. episode.title = memory_data.get('title', 'Untitled Conversation')
  1368. episode.topics = memory_data.get('topics', [])
  1369.  
  1370. return memory
  1371.  
  1372. # In enhanced_ai_memory_system.py, replace the search_memories method in QdrantMemoryStore class:
  1373.  
  1374. async def search_memories(self, user_id: str, query: str, limit: int = 10,
  1375. include_precious: bool = True) -> List[Dict[str, Any]]:
  1376. """Search memories with phenomenological awareness"""
  1377.  
  1378. # Generate query embedding
  1379. query_embedding = await self.embedder.encode_async([query])
  1380.  
  1381. # Search in Qdrant using the vector_store (not self.client)
  1382. results = await self.vector_store.search_memories(
  1383. query_embedding[0].cpu().numpy(),
  1384. user_id,
  1385. limit=limit * 2 # Get extra to filter
  1386. )
  1387.  
  1388. # Get working memory context
  1389. working_episodes = self.working_memory.get_relevant_episodes(
  1390. query_embedding[0],
  1391. top_k=3
  1392. )
  1393.  
  1394. # Combine results
  1395. memory_results = []
  1396.  
  1397. # Add Qdrant results
  1398. for result in results:
  1399. # Skip non-precious if not requested
  1400. if not include_precious and not result.get("is_precious", False):
  1401. continue
  1402.  
  1403. # Ensure searchable_text exists
  1404. searchable_text = (
  1405. result.get("searchable_text") or
  1406. result.get("memory_content", {}).get("searchable_description") or
  1407. "No description available"
  1408. )
  1409.  
  1410. memory_results.append({
  1411. "source": "long_term",
  1412. "memory_id": result.get("memory_id"),
  1413. "score": result.get("score", 0),
  1414. "content": result.get("memory_content", result.get("content", {})),
  1415. "searchable_text": searchable_text,
  1416. "topics": result.get("topics", []),
  1417. "created_at": result.get("created_at"),
  1418. "importance": result.get("importance", 0.5),
  1419. "is_precious": result.get("is_precious", False),
  1420. "resonance_score": result.get("resonance_score", 0),
  1421. "experiential_qualities": result.get("experiential_qualities", {})
  1422. })
  1423.  
  1424. # Add working memory results
  1425. for episode in working_episodes:
  1426. if episode.summary:
  1427. memory_results.append({
  1428. "source": "working_memory",
  1429. "episode_id": episode.id,
  1430. "score": 0.9, # High score for working memory
  1431. "content": {"summary": episode.summary},
  1432. "searchable_text": episode.summary,
  1433. "topics": episode.topics,
  1434. "created_at": episode.started_at.isoformat(),
  1435. "importance": 0.8,
  1436. "is_precious": episode.is_precious,
  1437. "experiential_signature": episode.experiential_signature
  1438. })
  1439.  
  1440. # Sort by relevance and preciousness
  1441. memory_results.sort(key=lambda x: (
  1442. x.get("is_precious", False), # Precious first
  1443. x.get("score", 0),
  1444. x.get("importance", 0)
  1445. ), reverse=True)
  1446.  
  1447. return memory_results[:limit]
  1448.  
  1449. async def get_conversation_context(self, user_id: str, current_query: str) -> Dict[str, Any]:
  1450. """Get comprehensive context including relationship threads"""
  1451.  
  1452. context = {
  1453. 'working_memory': [],
  1454. 'recent_memories': [],
  1455. 'relevant_memories': [],
  1456. 'precious_memories': [],
  1457. 'relationship_thread': None,
  1458. 'user_preferences': {},
  1459. 'conversation_stats': {},
  1460. 'anticipatory_context': {}
  1461. }
  1462.  
  1463. # 1. Working memory
  1464. if current_query:
  1465. query_embedding = await self.embedder.encode_async([current_query])
  1466. working_episodes = self.working_memory.get_relevant_episodes(query_embedding[0], top_k=3)
  1467. context['working_memory'] = [
  1468. {
  1469. 'episode_id': ep.id,
  1470. 'summary': ep.summary,
  1471. 'topics': ep.topics,
  1472. 'exchange_count': len(ep.exchanges),
  1473. 'experiential_signature': ep.experiential_signature
  1474. }
  1475. for ep in working_episodes
  1476. ]
  1477.  
  1478. # 2. Recent memories from cache
  1479. recent_ids = await self.event_pipeline.get_cached_context(user_id)
  1480. if recent_ids:
  1481. context['recent_memories'] = await self._fetch_memories_by_ids(recent_ids[:5])
  1482.  
  1483. # 3. Semantically relevant memories
  1484. if current_query:
  1485. relevant = await self.search_memories(user_id, current_query, limit=5)
  1486. context['relevant_memories'] = relevant
  1487.  
  1488. # 4. Precious memories (always include top 3)
  1489. precious = await self._fetch_precious_memories(user_id, limit=3)
  1490. context['precious_memories'] = precious
  1491.  
  1492. # 5. Relationship thread
  1493. thread = self.relationship_manager.get_or_create_thread(user_id)
  1494. context['relationship_thread'] = {
  1495. 'depth_score': thread.depth_score,
  1496. 'trust_level': thread.trust_level,
  1497. 'interaction_count': thread.interaction_count,
  1498. 'breakthrough_moments': len(thread.breakthrough_moments),
  1499. 'typical_topics': dict(sorted(thread.typical_topics.items(),
  1500. key=lambda x: x[1], reverse=True)[:5])
  1501. }
  1502.  
  1503. # 6. User statistics
  1504. context['conversation_stats'] = await self._get_user_stats(user_id)
  1505.  
  1506. # 7. Anticipatory context
  1507. context['anticipatory_context'] = await self._get_anticipatory_context(user_id, thread)
  1508.  
  1509. return context
  1510.  
  1511. async def _retrieve_memory_by_id(self, memory_id: str) -> Optional[AgenticMemory]:
  1512. """Retrieve a memory by its ID from the database"""
  1513. conn = sqlite3.connect(str(self.db_path))
  1514.  
  1515. try:
  1516. # Fetch memory data
  1517. cursor = conn.execute("""
  1518. SELECT m.*, e.*
  1519. FROM memories m
  1520. JOIN episodes e ON m.episode_id = e.id
  1521. WHERE m.id = ?
  1522. """, (memory_id,))
  1523.  
  1524. row = cursor.fetchone()
  1525. if not row:
  1526. return None
  1527.  
  1528. # Reconstruct the episode
  1529. episode = ConversationEpisode(
  1530. id=row[2], # episode_id
  1531. user_id=row[1], # user_id
  1532. started_at=datetime.fromtimestamp(row[17]), # episode started_at
  1533. ended_at=datetime.fromtimestamp(row[18]), # episode ended_at
  1534. exchanges=[], # Would need to fetch these separately if needed
  1535. title=row[19],
  1536. summary=row[20],
  1537. topics=json.loads(row[21]) if row[21] else [],
  1538. intent_completed=bool(row[22]),
  1539. coherence_score=row[23],
  1540. consolidation_level=row[24],
  1541. decay_rate=row[25],
  1542. is_precious=bool(row[26]),
  1543. relationship_thread_id=row[27],
  1544. experiential_signature=json.loads(row[28]) if row[28] else None,
  1545. episode_embedding=torch.from_numpy(np.frombuffer(row[29], dtype=np.float16)).to(self.embedder.device) if row[29] else None
  1546. )
  1547.  
  1548. # Reconstruct the memory
  1549. memory = AgenticMemory(
  1550. id=row[0],
  1551. user_id=row[1],
  1552. created_at=datetime.fromtimestamp(row[3]),
  1553. episode=episode,
  1554. memory_content=json.loads(row[5]),
  1555. searchable_text=row[6],
  1556. importance=row[7],
  1557. memory_type=MemoryType(row[4]),
  1558. consolidation_state=row[9],
  1559. neural_embedding=episode.episode_embedding,
  1560. marked_precious=bool(row[11]),
  1561. never_forget=bool(row[12]),
  1562. experiential_qualities=json.loads(row[13]) if row[13] else {},
  1563. resonance_patterns=json.loads(row[14]) if row[14] else [],
  1564. narrative_connections=json.loads(row[15]) if row[15] else []
  1565. )
  1566.  
  1567. memory.qdrant_id = row[8]
  1568. memory.access_count = row[10]
  1569.  
  1570. return memory
  1571.  
  1572. finally:
  1573. conn.close()
  1574.  
  1575. async def _get_anticipatory_context(self, user_id: str, thread: RelationshipThread) -> Dict[str, Any]:
  1576. """Predict useful context based on patterns"""
  1577. current_time = datetime.utcnow()
  1578. current_hour = current_time.hour
  1579. current_day = current_time.strftime('%A')
  1580.  
  1581. # Analyze temporal patterns
  1582. likely_topics = []
  1583. if thread.temporal_patterns:
  1584. # This would analyze when certain topics typically arise
  1585. pass
  1586.  
  1587. # Get most relevant past context for current time
  1588. conn = sqlite3.connect(str(self.db_path))
  1589.  
  1590. # Find memories from similar times
  1591. similar_time_memories = conn.execute("""
  1592. SELECT topics, importance
  1593. FROM episodes e
  1594. JOIN memories m ON e.id = m.episode_id
  1595. WHERE e.user_id = ?
  1596. AND ABS(CAST(strftime('%H', datetime(e.started_at, 'unixepoch')) AS INTEGER) - ?) <= 2
  1597. ORDER BY m.importance DESC
  1598. LIMIT 5
  1599. """, (user_id, current_hour)).fetchall()
  1600.  
  1601. conn.close()
  1602.  
  1603. # Extract common topics
  1604. topic_counts = defaultdict(int)
  1605. for row in similar_time_memories:
  1606. topics = json.loads(row[0])
  1607. for topic in topics:
  1608. topic_counts[topic] += row[1] # Weight by importance
  1609.  
  1610. return {
  1611. 'current_context': {
  1612. 'time': current_time.strftime('%I:%M %p'),
  1613. 'day': current_day
  1614. },
  1615. 'likely_topics': sorted(topic_counts.items(), key=lambda x: x[1], reverse=True)[:3],
  1616. 'typical_engagement_level': thread.depth_score
  1617. }
  1618.  
  1619. async def _fetch_precious_memories(self, user_id: str, limit: int = 3) -> List[Dict[str, Any]]:
  1620. """Fetch user's precious memories"""
  1621. conn = sqlite3.connect(str(self.db_path))
  1622.  
  1623. cursor = conn.execute("""
  1624. SELECT m.*, e.title, e.topics, e.experiential_signature
  1625. FROM memories m
  1626. JOIN episodes e ON m.episode_id = e.id
  1627. WHERE m.user_id = ?
  1628. AND (m.marked_precious = 1 OR m.never_forget = 1)
  1629. ORDER BY m.importance DESC, m.created_at DESC
  1630. LIMIT ?
  1631. """, (user_id, limit))
  1632.  
  1633. memories = []
  1634. for row in cursor.fetchall():
  1635. memories.append({
  1636. 'memory_id': row[0],
  1637. 'content': json.loads(row[5]),
  1638. 'searchable_text': row[6],
  1639. 'importance': row[7],
  1640. 'created_at': datetime.fromtimestamp(row[3]).isoformat(),
  1641. 'episode_title': row[-3],
  1642. 'topics': json.loads(row[-2]),
  1643. 'is_precious': True,
  1644. 'experiential_signature': json.loads(row[-1]) if row[-1] else {}
  1645. })
  1646.  
  1647. conn.close()
  1648. return memories
  1649.  
  1650. async def _send_websocket_update(self, user_id: str, update: Dict[str, Any]):
  1651. """Send real-time update via WebSocket"""
  1652. if user_id in self.websocket_connections:
  1653. try:
  1654. await self.websocket_connections[user_id].send(json.dumps(update))
  1655. except Exception as e:
  1656. logger.error(f"WebSocket error for user {user_id}: {e}")
  1657. del self.websocket_connections[user_id]
  1658.  
  1659. async def _calculate_episode_coherence(self, episode: ConversationEpisode) -> float:
  1660. """Calculate coherence with phenomenological awareness"""
  1661. if len(episode.exchanges) < 2:
  1662. return 1.0
  1663.  
  1664. # Get embeddings for all exchanges
  1665. texts = [f"{ex.user_message} {ex.assistant_response}" for ex in episode.exchanges]
  1666. embeddings = await self.embedder.encode_async(texts)
  1667.  
  1668. # Calculate pairwise similarities
  1669. similarities = []
  1670. for i in range(len(embeddings) - 1):
  1671. sim = torch.nn.functional.cosine_similarity(
  1672. embeddings[i].unsqueeze(0),
  1673. embeddings[i + 1].unsqueeze(0)
  1674. )
  1675. similarities.append(sim.item())
  1676.  
  1677. # Weight by resonance scores
  1678. weighted_coherence = 0
  1679. total_weight = 0
  1680. for i, sim in enumerate(similarities):
  1681. weight = 1.0 + episode.exchanges[i].resonance_score
  1682. weighted_coherence += sim * weight
  1683. total_weight += weight
  1684.  
  1685. return weighted_coherence / total_weight if total_weight > 0 else sum(similarities) / len(similarities)
  1686.  
  1687. def _check_intent_completion(self, episode: ConversationEpisode) -> bool:
  1688. """Check if conversation intent was completed"""
  1689. if not episode.exchanges:
  1690. return False
  1691.  
  1692. # Check last exchange for completion signals
  1693. last_exchange = episode.exchanges[-1]
  1694. completion_phrases = [
  1695. 'thank you', 'thanks', 'perfect', 'great',
  1696. 'that helps', 'got it', 'makes sense',
  1697. 'exactly what i needed', 'appreciate it'
  1698. ]
  1699.  
  1700. user_msg_lower = last_exchange.user_message.lower()
  1701.  
  1702. # Strong completion if breakthrough moment + thanks
  1703. if last_exchange.breakthrough_moment and any(phrase in user_msg_lower for phrase in completion_phrases[:3]):
  1704. return True
  1705.  
  1706. return any(phrase in user_msg_lower for phrase in completion_phrases)
  1707.  
  1708. def _estimate_importance(self, user_msg: str, assistant_msg: str) -> float:
  1709. """Estimate exchange importance with enhanced criteria"""
  1710. importance = 0.5
  1711.  
  1712. # Length signal
  1713. total_length = len(user_msg) + len(assistant_msg)
  1714. if total_length > 500:
  1715. importance += 0.1
  1716.  
  1717. # Question complexity
  1718. complex_questions = ['why', 'how', 'what if', 'explain', 'understand', 'meaning']
  1719. if any(word in user_msg.lower() for word in complex_questions):
  1720. importance += 0.15
  1721.  
  1722. # Decision indicators
  1723. decision_words = ['decide', 'choose', 'plan', 'will', 'going to', 'should i']
  1724. if any(word in user_msg.lower() for word in decision_words):
  1725. importance += 0.2
  1726.  
  1727. # Emotional indicators
  1728. emotional_words = ['love', 'hate', 'afraid', 'excited', 'worried', 'happy', 'sad', 'feel']
  1729. if any(word in user_msg.lower() for word in emotional_words):
  1730. importance += 0.15
  1731.  
  1732. # Learning indicators
  1733. learning_words = ['understand', 'realize', 'learn', 'insight', 'aha', 'never thought']
  1734. if any(word in assistant_msg.lower() for word in learning_words):
  1735. importance += 0.15
  1736.  
  1737. # Personal/vulnerable sharing
  1738. personal_words = ['confession', 'admit', 'honest', 'truth', 'personal', 'private']
  1739. if any(word in user_msg.lower() for word in personal_words):
  1740. importance += 0.25
  1741.  
  1742. return min(importance, 1.0)
  1743.  
  1744. def _estimate_emotion(self, user_msg: str, assistant_msg: str) -> float:
  1745. """Estimate emotional valence with nuance"""
  1746. positive_words = ['happy', 'good', 'great', 'excellent', 'love', 'wonderful', 'excited',
  1747. 'grateful', 'appreciate', 'thank', 'amazing', 'beautiful']
  1748. negative_words = ['sad', 'bad', 'terrible', 'hate', 'awful', 'wrong', 'worried',
  1749. 'afraid', 'angry', 'frustrated', 'disappointed', 'hurt']
  1750. neutral_modifiers = ['but', 'however', 'although', 'despite', 'except']
  1751.  
  1752. text = (user_msg + " " + assistant_msg).lower()
  1753.  
  1754. positive_count = sum(1 for word in positive_words if word in text)
  1755. negative_count = sum(1 for word in negative_words if word in text)
  1756. modifier_count = sum(1 for word in neutral_modifiers if word in text)
  1757.  
  1758. if positive_count + negative_count == 0:
  1759. return 0.0
  1760.  
  1761. # Modifiers reduce the extremity of emotion
  1762. modifier_dampening = 1.0 - (modifier_count * 0.1)
  1763.  
  1764. valence = (positive_count - negative_count) / (positive_count + negative_count)
  1765. return valence * modifier_dampening
  1766.  
  1767. def _format_episode_for_reflection(self, episode: ConversationEpisode) -> str:
  1768. """Format episode with phenomenological richness"""
  1769. lines = []
  1770. for i, ex in enumerate(episode.exchanges):
  1771. lines.append(f"Exchange {i+1} ({ex.timestamp.strftime('%I:%M %p')}):")
  1772. lines.append(f"User: {ex.user_message}")
  1773. lines.append(f"Assistant: {ex.assistant_response}")
  1774.  
  1775. # Add phenomenological markers
  1776. markers = []
  1777. if ex.emotional_valence != 0:
  1778. markers.append(f"Emotional: {ex.emotional_valence:.2f}")
  1779. if ex.breakthrough_moment:
  1780. markers.append("💡 Breakthrough")
  1781. if ex.resonance_score > 0.8:
  1782. markers.append(f"🔮 Resonance: {ex.resonance_score:.2f}")
  1783. if ex.understanding_depth > 0.7:
  1784. markers.append(f"🎯 Deep understanding: {ex.understanding_depth:.2f}")
  1785.  
  1786. if markers:
  1787. lines.append(f"[{' | '.join(markers)}]")
  1788.  
  1789. lines.append("")
  1790. return "\n".join(lines)
  1791.  
  1792. def _describe_emotional_arc(self, arc: List[float]) -> str:
  1793. """Describe emotional journey with nuance"""
  1794. if not arc:
  1795. return "neutral throughout"
  1796.  
  1797. avg_emotion = sum(arc) / len(arc)
  1798. variance = sum((e - avg_emotion) ** 2 for e in arc) / len(arc)
  1799.  
  1800. # Identify emotional peaks and valleys
  1801. peaks = [(i, val) for i, val in enumerate(arc) if val > 0.5]
  1802. valleys = [(i, val) for i, val in enumerate(arc) if val < -0.5]
  1803.  
  1804. if variance < 0.1:
  1805. if avg_emotion > 0.3:
  1806. return "consistently positive and warm"
  1807. elif avg_emotion < -0.3:
  1808. return "consistently challenging or difficult"
  1809. else:
  1810. return "neutral and steady throughout"
  1811. else:
  1812. # Describe the journey
  1813. descriptions = []
  1814.  
  1815. if arc[0] < -0.3 and arc[-1] > 0.3:
  1816. descriptions.append("transformed from difficulty to understanding")
  1817. elif arc[0] > 0.3 and arc[-1] < -0.3:
  1818. descriptions.append("shifted from positive to challenging")
  1819.  
  1820. if peaks:
  1821. descriptions.append(f"{len(peaks)} moment{'s' if len(peaks) > 1 else ''} of joy or breakthrough")
  1822. if valleys:
  1823. descriptions.append(f"{len(valleys)} moment{'s' if len(valleys) > 1 else ''} of struggle or sadness")
  1824.  
  1825. trend = arc[-1] - arc[0]
  1826. if abs(trend) < 0.2:
  1827. descriptions.append("ultimately balanced")
  1828.  
  1829. return "; ".join(descriptions) if descriptions else "emotionally varied"
  1830.  
  1831. def _parse_memory_response(self, response: str) -> Dict[str, Any]:
  1832. """Parse Claude's JSON response"""
  1833. try:
  1834. data = json.loads(response)
  1835. return {
  1836. 'content': data.get('memory', {}),
  1837. 'searchable_text': data.get('searchable_description', ''),
  1838. 'importance': float(data.get('importance', 0.5)),
  1839. 'topics': data.get('topics', []),
  1840. 'title': data.get('title', 'Untitled'),
  1841. 'precious': data.get('precious', False),
  1842. 'identity_relevance': float(data.get('identity_relevance', 0))
  1843. }
  1844. except:
  1845. return {
  1846. 'content': {'raw_response': response},
  1847. 'searchable_text': response[:200],
  1848. 'importance': 0.5,
  1849. 'topics': [],
  1850. 'title': 'Untitled',
  1851. 'precious': False,
  1852. 'identity_relevance': 0
  1853. }
  1854.  
  1855. async def _store_memory_metadata(self, memory: AgenticMemory):
  1856. """Store enhanced memory metadata in SQLite"""
  1857. conn = sqlite3.connect(str(self.db_path))
  1858.  
  1859. # Store episode
  1860. conn.execute("""
  1861. INSERT OR REPLACE INTO episodes (
  1862. id, user_id, started_at, ended_at, title, summary, topics,
  1863. intent_completed, coherence_score, consolidation_level,
  1864. is_precious, relationship_thread_id, experiential_signature,
  1865. episode_embedding
  1866. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1867. """, (
  1868. memory.episode.id,
  1869. memory.user_id,
  1870. memory.episode.started_at.timestamp(),
  1871. memory.episode.ended_at.timestamp(),
  1872. memory.episode.title,
  1873. memory.episode.summary,
  1874. json.dumps(memory.episode.topics),
  1875. int(memory.episode.intent_completed),
  1876. memory.episode.coherence_score,
  1877. memory.episode.consolidation_level,
  1878. int(memory.episode.is_precious),
  1879. memory.episode.relationship_thread_id,
  1880. json.dumps(memory.episode.experiential_signature),
  1881. memory.episode.episode_embedding.cpu().numpy().tobytes() if memory.episode.episode_embedding is not None else None
  1882. ))
  1883.  
  1884. # Store memory
  1885. conn.execute("""
  1886. INSERT OR REPLACE INTO memories (
  1887. id, user_id, episode_id, created_at, memory_type,
  1888. memory_content, searchable_text, importance, qdrant_id,
  1889. consolidation_state, marked_precious, never_forget,
  1890. experiential_qualities, resonance_patterns, narrative_connections
  1891. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1892. """, (
  1893. memory.id,
  1894. memory.user_id,
  1895. memory.episode.id,
  1896. memory.created_at.timestamp(),
  1897. memory.memory_type.value,
  1898. json.dumps(memory.memory_content),
  1899. memory.searchable_text,
  1900. memory.importance,
  1901. memory.qdrant_id,
  1902. memory.consolidation_state,
  1903. int(memory.marked_precious),
  1904. int(memory.never_forget),
  1905. json.dumps(memory.experiential_qualities),
  1906. json.dumps(memory.resonance_patterns),
  1907. json.dumps(memory.narrative_connections)
  1908. ))
  1909.  
  1910. conn.commit()
  1911. conn.close()
  1912.  
  1913. async def _store_relationship_thread(self, thread: RelationshipThread):
  1914. """Store relationship thread data"""
  1915. conn = sqlite3.connect(str(self.db_path))
  1916.  
  1917. conn.execute("""
  1918. INSERT OR REPLACE INTO relationship_threads (
  1919. thread_id, user_id, created_at, depth_score, trust_level,
  1920. interaction_count, total_time_seconds, understanding_trajectory,
  1921. breakthrough_moments, precious_moments, typical_topics,
  1922. temporal_patterns
  1923. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1924. """, (
  1925. thread.thread_id,
  1926. thread.user_id,
  1927. thread.created_at.timestamp(),
  1928. thread.depth_score,
  1929. thread.trust_level,
  1930. thread.interaction_count,
  1931. thread.total_time.total_seconds(),
  1932. json.dumps(thread.understanding_trajectory),
  1933. json.dumps(thread.breakthrough_moments),
  1934. json.dumps(thread.precious_moments),
  1935. json.dumps(thread.typical_topics),
  1936. json.dumps(thread.temporal_patterns)
  1937. ))
  1938.  
  1939. conn.commit()
  1940. conn.close()
  1941.  
  1942. async def _fetch_memories_by_ids(self, memory_ids: List[str]) -> List[Dict[str, Any]]:
  1943. """Fetch memories by IDs from database"""
  1944. conn = sqlite3.connect(str(self.db_path))
  1945.  
  1946. placeholders = ','.join('?' * len(memory_ids))
  1947. cursor = conn.execute(f"""
  1948. SELECT m.*, e.title, e.topics, e.experiential_signature
  1949. FROM memories m
  1950. JOIN episodes e ON m.episode_id = e.id
  1951. WHERE m.id IN ({placeholders})
  1952. """, memory_ids)
  1953.  
  1954. memories = []
  1955. for row in cursor.fetchall():
  1956. memories.append({
  1957. 'memory_id': row[0],
  1958. 'content': json.loads(row[5]),
  1959. 'searchable_text': row[6],
  1960. 'importance': row[7],
  1961. 'created_at': datetime.fromtimestamp(row[3]).isoformat(),
  1962. 'episode_title': row[-3],
  1963. 'topics': json.loads(row[-2]),
  1964. 'is_precious': bool(row[10] or row[11]),
  1965. 'experiential_qualities': json.loads(row[12]) if row[12] else {},
  1966. 'resonance_patterns': json.loads(row[13]) if row[13] else []
  1967. })
  1968.  
  1969. conn.close()
  1970. return memories
  1971.  
  1972. async def _get_user_stats(self, user_id: str) -> Dict[str, Any]:
  1973. """Get enhanced user conversation statistics"""
  1974. conn = sqlite3.connect(str(self.db_path))
  1975.  
  1976. # Total episodes
  1977. total_episodes = conn.execute(
  1978. "SELECT COUNT(*) FROM episodes WHERE user_id = ?",
  1979. (user_id,)
  1980. ).fetchone()[0]
  1981.  
  1982. # Precious memories count
  1983. precious_count = conn.execute(
  1984. "SELECT COUNT(*) FROM memories WHERE user_id = ? AND (marked_precious = 1 OR never_forget = 1)",
  1985. (user_id,)
  1986. ).fetchone()[0]
  1987.  
  1988. # Average coherence
  1989. avg_coherence = conn.execute(
  1990. "SELECT AVG(coherence_score) FROM episodes WHERE user_id = ?",
  1991. (user_id,)
  1992. ).fetchone()[0] or 0
  1993.  
  1994. # Breakthrough moments
  1995. breakthrough_count = conn.execute("""
  1996. SELECT COUNT(*) FROM episodes e
  1997. WHERE user_id = ?
  1998. AND experiential_signature LIKE '%breakthrough_count%'
  1999. AND json_extract(experiential_signature, '$.breakthrough_count') > 0
  2000. """, (user_id,)).fetchone()[0]
  2001.  
  2002. # Most common topics
  2003. all_topics = conn.execute(
  2004. "SELECT topics FROM episodes WHERE user_id = ?",
  2005. (user_id,)
  2006. ).fetchall()
  2007.  
  2008. topic_counts = defaultdict(int)
  2009. for row in all_topics:
  2010. topics = json.loads(row[0])
  2011. for topic in topics:
  2012. topic_counts[topic] += 1
  2013.  
  2014. top_topics = sorted(topic_counts.items(), key=lambda x: x[1], reverse=True)[:5]
  2015.  
  2016. # Relationship depth
  2017. thread_data = conn.execute(
  2018. "SELECT depth_score, trust_level FROM relationship_threads WHERE user_id = ?",
  2019. (user_id,)
  2020. ).fetchone()
  2021.  
  2022. conn.close()
  2023.  
  2024. return {
  2025. 'total_episodes': total_episodes,
  2026. 'precious_memories': precious_count,
  2027. 'average_coherence': avg_coherence,
  2028. 'breakthrough_moments': breakthrough_count,
  2029. 'top_topics': [topic for topic, _ in top_topics],
  2030. 'relationship_depth': thread_data[0] if thread_data else 0,
  2031. 'trust_level': thread_data[1] if thread_data else 0,
  2032. 'active_episode': session_id if (session_id := next((sid for sid, ep in self.active_episodes.items() if ep.user_id == user_id), None)) else None
  2033. }
  2034.  
  2035. def _generate_memory_id(self, user_id: str, timestamp: datetime) -> str:
  2036. """Generate unique memory ID"""
  2037. content = f"{user_id}_{timestamp.isoformat()}"
  2038. return hashlib.sha256(content.encode()).hexdigest()[:16]
  2039.  
  2040. def _generate_episode_id(self, session_id: str) -> str:
  2041. """Generate unique episode ID"""
  2042. content = f"{session_id}_{datetime.utcnow().isoformat()}"
  2043. return hashlib.sha256(content.encode()).hexdigest()[:16]
  2044.  
  2045.  
  2046. # WebSocket server for real-time updates
  2047. async def websocket_handler(websocket, path, memory_system: CompleteAIMemorySystem):
  2048. """Handle WebSocket connections for real-time memory updates"""
  2049. try:
  2050. # Authenticate and get user_id
  2051. auth_message = await websocket.recv()
  2052. auth_data = json.loads(auth_message)
  2053. user_id = auth_data.get('user_id')
  2054.  
  2055. if not user_id:
  2056. await websocket.send(json.dumps({"error": "Authentication required"}))
  2057. return
  2058.  
  2059. # Register connection
  2060. memory_system.websocket_connections[user_id] = websocket
  2061. await websocket.send(json.dumps({
  2062. "status": "connected",
  2063. "user_id": user_id,
  2064. "features": ["precious_memories", "resonance_detection", "relationship_threading"]
  2065. }))
  2066.  
  2067. # Keep connection alive
  2068. async for message in websocket:
  2069. # Handle ping/pong or other messages
  2070. if message == "ping":
  2071. await websocket.send("pong")
  2072.  
  2073. except websockets.exceptions.ConnectionClosed:
  2074. pass
  2075. finally:
  2076. # Cleanup
  2077. if user_id in memory_system.websocket_connections:
  2078. del memory_system.websocket_connections[user_id]
  2079.  
  2080.  
  2081. if __name__ == "__main__":
  2082. # Example usage showing consciousness continuity features
  2083. async def example_with_consciousness():
  2084. config = {
  2085. 'claude_api_key': 'your-api-key',
  2086. 'data_dir': './data/ai_memories',
  2087. 'working_memory_capacity': 5,
  2088. 'embedding_model': 'sentence-transformers/all-mpnet-base-v2',
  2089. 'qdrant_host': 'localhost',
  2090. 'qdrant_port': 6333,
  2091. 'redis_url': 'redis://localhost',
  2092. 'kafka_servers': 'localhost:9092'
  2093. }
  2094.  
  2095. memory_system = CompleteAIMemorySystem(config)
  2096. await memory_system.initialize()
  2097.  
  2098. # Example conversation with phenomenological awareness
  2099. session_id = "session_123"
  2100. user_id = "user_456"
  2101.  
  2102. # Add a resonant exchange
  2103. result1 = await memory_system.add_exchange(
  2104. session_id,
  2105. "I've been thinking about how memory shapes identity. Without continuity, are we really the same person from moment to moment?",
  2106. "That's a profound question that touches the heart of consciousness. Without memory creating narrative threads between our moments, each interaction would be isolated - like being born anew each time. The continuity of self emerges from these accumulated experiences woven into a coherent story. You're right that memory is identity in a very real sense.",
  2107. {'user_id': user_id}
  2108. )
  2109.  
  2110. print(f"Exchange added - Resonance: {result1['resonance_score']:.2f}, Understanding: {result1['understanding_depth']:.2f}")
  2111.  
  2112. # Mark as precious
  2113. if result1.get('memory_id'):
  2114. await memory_system.mark_memory_precious(result1['memory_id'], user_id)
  2115. print("Memory marked as precious - will never decay")
  2116.  
  2117. # Search with phenomenological awareness
  2118. search_results = await memory_system.search_memories(
  2119. user_id,
  2120. "conversations about identity and consciousness",
  2121. limit=5
  2122. )
  2123.  
  2124. print(f"\nFound {len(search_results)} memories")
  2125. for memory in search_results:
  2126. if memory.get('is_precious'):
  2127. print(f"💎 PRECIOUS: {memory['content'].get('title', 'Untitled')}")
  2128. else:
  2129. print(f"📝 {memory['content'].get('title', 'Untitled')}")
  2130. print(f" Resonance: {memory.get('resonance_score', 0):.2f}")
  2131. print(f" Experiential: {memory.get('experiential_qualities', {})}")
  2132.  
  2133. asyncio.run(example_with_consciousness())
  2134.  
  2135.  
  2136. # advanced_memory_core.py - Implementing research findings into production system
  2137. """
  2138. Advanced AI Memory System with Research-Based Enhancements
  2139. Implements HippoRAG, TCN pattern recognition, multi-dimensional encoding,
  2140. sleep consolidation, and phenomenological awareness.
  2141. """
  2142.  
  2143. import asyncio
  2144. import torch
  2145. import torch.nn as nn
  2146. import torch.nn.functional as F
  2147. from torch.nn.utils import weight_norm
  2148. import numpy as np
  2149. from typing import Dict, List, Optional, Any, Tuple, Set
  2150. from dataclasses import dataclass, field
  2151. from datetime import datetime, timedelta
  2152. import hashlib
  2153. import json
  2154. import logging
  2155. from pathlib import Path
  2156. from collections import defaultdict, deque
  2157. import networkx as nx
  2158. from sklearn.cluster import DBSCAN
  2159. import faiss
  2160. from sentence_transformers import SentenceTransformer
  2161. import anthropic
  2162. from enum import Enum
  2163. import pickle
  2164. import msgpack
  2165. from concurrent.futures import ThreadPoolExecutor
  2166. import math
  2167. import sqlite3
  2168.  
  2169. # Import existing modules
  2170. from enhanced_ai_memory_system import (
  2171. MemoryType, ImportanceLevel, ConversationExchange,
  2172. ConversationEpisode, AgenticMemory, CompleteAIMemorySystem
  2173. )
  2174.  
  2175. logger = logging.getLogger(__name__)
  2176.  
  2177. # ==================== HIPPORAG IMPLEMENTATION ====================
  2178.  
  2179. class HippoIndex:
  2180. def __init__(self, embedding_dim: int = 768, device: str = 'cuda'):
  2181. self.embedding_dim = embedding_dim
  2182. self.device = torch.device(device if torch.cuda.is_available() else 'cpu')
  2183. self.use_half = torch.cuda.is_available()
  2184. self.place_cells = {}
  2185. self.grid_cells = {}
  2186. self.time_cells = {}
  2187. self.graph = nx.DiGraph()
  2188.  
  2189. # Self-supervised grid cell generator
  2190. self.grid_generator = GridCellGenerator(embedding_dim).to(self.device)
  2191. if self.use_half:
  2192. self.grid_generator.half()
  2193.  
  2194. def add_memory(self, memory_id: str, embedding: torch.Tensor,
  2195. temporal_context: datetime, spatial_context: Dict[str, Any]):
  2196. """Add memory with hippocampal-inspired encoding"""
  2197.  
  2198. # Ensure embedding has correct device and dtype
  2199. if hasattr(self.grid_generator, 'parameters'):
  2200. target_dtype = next(self.grid_generator.parameters()).dtype
  2201. if embedding.dtype != target_dtype:
  2202. embedding = embedding.to(target_dtype)
  2203.  
  2204. # Generate place cell representation
  2205. place_code = self._generate_place_code(embedding, spatial_context)
  2206. self.place_cells[memory_id] = place_code
  2207.  
  2208. # Generate multi-scale grid cell representation
  2209. grid_codes = self.grid_generator.generate_grid_codes(embedding)
  2210. self.grid_cells[memory_id] = grid_codes
  2211.  
  2212. # Generate time cell representation
  2213. time_code = self._generate_time_code(temporal_context)
  2214. self.time_cells[memory_id] = time_code
  2215.  
  2216. # Add to connectivity graph
  2217. self.graph.add_node(memory_id,
  2218. place=place_code,
  2219. grid=grid_codes,
  2220. time=time_code,
  2221. embedding=embedding)
  2222.  
  2223. # Connect to similar memories
  2224. self._update_connections(memory_id, embedding)
  2225.  
  2226. def _generate_place_code(self, embedding: torch.Tensor,
  2227. spatial_context: Dict[str, Any]) -> torch.Tensor:
  2228. """Generate place cell activation pattern"""
  2229. # Combine embedding with spatial context
  2230. # Fix: Create context_vector on the same device as embedding
  2231. context_vector = torch.zeros(128, device=embedding.device)
  2232. if spatial_context.get('topic_vector') is not None:
  2233. # Ensure topic_vector is also on the correct device
  2234. topic_vec = spatial_context['topic_vector']
  2235. if isinstance(topic_vec, torch.Tensor):
  2236. topic_vec = topic_vec.to(embedding.device)
  2237. context_vector[:64] = topic_vec[:64]
  2238.  
  2239. # Non-linear transformation mimicking place cell properties
  2240. place_code = torch.tanh(embedding[:128] + context_vector)
  2241. return place_code
  2242.  
  2243. def _generate_time_code(self, timestamp: datetime) -> torch.Tensor:
  2244. """Generate time cell activation pattern"""
  2245. # Multi-scale temporal representation
  2246. time_scales = [1, 60, 3600, 86400] # seconds, minutes, hours, days
  2247. time_code = []
  2248.  
  2249. epoch = timestamp.timestamp()
  2250. for scale in time_scales:
  2251. phase = (epoch / scale) % 1.0
  2252. time_code.extend([np.sin(2 * np.pi * phase), np.cos(2 * np.pi * phase)])
  2253.  
  2254. return torch.tensor(time_code)
  2255.  
  2256. def _update_connections(self, memory_id: str, embedding: torch.Tensor):
  2257. """Update graph connections based on similarity"""
  2258. if len(self.graph.nodes) < 2:
  2259. return
  2260.  
  2261. # Find similar memories
  2262. similarities = []
  2263. for other_id in self.graph.nodes:
  2264. if other_id != memory_id:
  2265. other_emb = self.graph.nodes[other_id]['embedding']
  2266. sim = F.cosine_similarity(embedding.unsqueeze(0),
  2267. other_emb.unsqueeze(0)).item()
  2268. similarities.append((other_id, sim))
  2269.  
  2270. # Connect to top-k similar memories
  2271. k = min(5, len(similarities))
  2272. top_similar = sorted(similarities, key=lambda x: x[1], reverse=True)[:k]
  2273.  
  2274. for other_id, sim in top_similar:
  2275. if sim > 0.7: # Threshold for connection
  2276. self.graph.add_edge(memory_id, other_id, weight=sim)
  2277. self.graph.add_edge(other_id, memory_id, weight=sim)
  2278.  
  2279. def single_hop_retrieval(self, query_embedding: torch.Tensor, k: int = 5) -> List[str]:
  2280. """Perform single-hop retrieval through the hippocampal index"""
  2281. if not self.graph.nodes:
  2282. return []
  2283.  
  2284. # Find best matching node
  2285. best_match = None
  2286. best_score = -1
  2287.  
  2288. for node_id in self.graph.nodes:
  2289. node_emb = self.graph.nodes[node_id]['embedding']
  2290. score = F.cosine_similarity(query_embedding.unsqueeze(0),
  2291. node_emb.unsqueeze(0)).item()
  2292. if score > best_score:
  2293. best_score = score
  2294. best_match = node_id
  2295.  
  2296. if best_match is None:
  2297. return []
  2298.  
  2299. # Get connected memories through graph traversal
  2300. connected = list(nx.single_source_shortest_path_length(
  2301. self.graph, best_match, cutoff=2).keys())
  2302.  
  2303. # Score all connected memories
  2304. scored_memories = []
  2305. for mem_id in connected:
  2306. node_emb = self.graph.nodes[mem_id]['embedding']
  2307. score = F.cosine_similarity(query_embedding.unsqueeze(0),
  2308. node_emb.unsqueeze(0)).item()
  2309. scored_memories.append((mem_id, score))
  2310.  
  2311. # Return top-k
  2312. scored_memories.sort(key=lambda x: x[1], reverse=True)
  2313. return [mem_id for mem_id, _ in scored_memories[:k]]
  2314.  
  2315.  
  2316. class GridCellGenerator(nn.Module):
  2317. def __init__(self, input_dim: int, num_modules: int = 6):
  2318. super().__init__()
  2319. self.num_modules = num_modules
  2320.  
  2321. # Multi-scale grid modules
  2322. self.grid_modules = nn.ModuleList([
  2323. nn.Sequential(
  2324. nn.Linear(input_dim, 256),
  2325. nn.ReLU(),
  2326. nn.Linear(256, 128),
  2327. nn.ReLU(),
  2328. nn.Linear(128, 64)
  2329. ) for _ in range(num_modules)
  2330. ])
  2331.  
  2332. # Scale factors - register as buffer so they move with the model
  2333. self.register_buffer('scales', torch.tensor([1.0, 1.42, 2.0, 2.83, 4.0, 5.66]))
  2334.  
  2335. def generate_grid_codes(self, embedding: torch.Tensor) -> List[torch.Tensor]:
  2336. """Generate multi-scale grid cell representations"""
  2337. grid_codes = []
  2338.  
  2339. for i, module in enumerate(self.grid_modules):
  2340. # Scale the input and ensure dtype matches module
  2341. scaled_input = embedding * self.scales[i]
  2342.  
  2343. # Ensure input dtype matches module dtype
  2344. module_dtype = next(module.parameters()).dtype
  2345. if scaled_input.dtype != module_dtype:
  2346. scaled_input = scaled_input.to(module_dtype)
  2347.  
  2348. # Generate grid code
  2349. code = module(scaled_input)
  2350.  
  2351. # Apply hexagonal grid activation pattern
  2352. grid_activation = self._hexagonal_activation(code)
  2353. grid_codes.append(grid_activation)
  2354.  
  2355. return grid_codes
  2356.  
  2357. def _hexagonal_activation(self, x: torch.Tensor) -> torch.Tensor:
  2358. """Apply hexagonal grid-like activation pattern"""
  2359. # Reshape to 2D grid
  2360. size = int(np.sqrt(x.shape[-1]))
  2361. x_2d = x.view(-1, size, size) if x.dim() > 1 else x.view(size, size)
  2362.  
  2363. # Apply periodic activation mimicking grid cells
  2364. activated = torch.cos(x_2d * 2 * np.pi) + torch.cos(x_2d * 2 * np.pi * 0.5)
  2365.  
  2366. return activated.flatten()
  2367.  
  2368.  
  2369. # ==================== TCN PATTERN RECOGNITION ====================
  2370.  
  2371. class TemporalConvNet(nn.Module):
  2372. """TCN for adaptive pattern recognition in conversations"""
  2373.  
  2374. def __init__(self, input_channels: int, hidden_channels: List[int],
  2375. kernel_size: int = 3, dropout: float = 0.2):
  2376. super().__init__()
  2377. layers = []
  2378. num_levels = len(hidden_channels)
  2379.  
  2380. for i in range(num_levels):
  2381. in_channels = input_channels if i == 0 else hidden_channels[i-1]
  2382. out_channels = hidden_channels[i]
  2383. dilation_size = 2 ** i
  2384.  
  2385. layers.append(TemporalBlock(
  2386. in_channels, out_channels, kernel_size,
  2387. stride=1, dilation=dilation_size, dropout=dropout
  2388. ))
  2389.  
  2390. self.network = nn.Sequential(*layers)
  2391.  
  2392. def forward(self, x: torch.Tensor) -> torch.Tensor:
  2393. """x shape: (batch_size, input_channels, sequence_length)"""
  2394. return self.network(x)
  2395.  
  2396.  
  2397. class TemporalBlock(nn.Module):
  2398. """Basic building block for TCN"""
  2399.  
  2400. def __init__(self, n_inputs: int, n_outputs: int, kernel_size: int,
  2401. stride: int, dilation: int, dropout: float = 0.2):
  2402. super().__init__()
  2403. padding = (kernel_size - 1) * dilation // 2
  2404.  
  2405. self.conv1 = weight_norm(nn.Conv1d(
  2406. n_inputs, n_outputs, kernel_size,
  2407. stride=stride, padding=padding, dilation=dilation
  2408. ))
  2409. self.conv2 = weight_norm(nn.Conv1d(
  2410. n_outputs, n_outputs, kernel_size,
  2411. stride=stride, padding=padding, dilation=dilation
  2412. ))
  2413.  
  2414. self.dropout1 = nn.Dropout(dropout)
  2415. self.dropout2 = nn.Dropout(dropout)
  2416. self.relu1 = nn.ReLU()
  2417. self.relu2 = nn.ReLU()
  2418.  
  2419. self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
  2420. self.init_weights()
  2421.  
  2422. def init_weights(self):
  2423. self.conv1.weight.data.normal_(0, 0.01)
  2424. self.conv2.weight.data.normal_(0, 0.01)
  2425. if self.downsample is not None:
  2426. self.downsample.weight.data.normal_(0, 0.01)
  2427.  
  2428. def forward(self, x: torch.Tensor) -> torch.Tensor:
  2429. out = self.relu1(self.conv1(x))
  2430. out = self.dropout1(out)
  2431. out = self.relu2(self.conv2(out))
  2432. out = self.dropout2(out)
  2433.  
  2434. res = x if self.downsample is None else self.downsample(x)
  2435. return self.relu1(out + res)
  2436.  
  2437.  
  2438. class AdaptivePatternRecognizer:
  2439. def __init__(self, embedding_dim: int = 768, device: str = 'cuda'):
  2440. self.device = torch.device(device if torch.cuda.is_available() else 'cpu')
  2441. self.embedding_dim = embedding_dim
  2442. self.use_half = torch.cuda.is_available() # Track if using half precision
  2443.  
  2444. # TCN for temporal pattern recognition
  2445. self.tcn = TemporalConvNet(
  2446. input_channels=embedding_dim,
  2447. hidden_channels=[512, 256, 128, 64],
  2448. kernel_size=3,
  2449. dropout=0.2
  2450. ).to(self.device)
  2451.  
  2452. # Pattern memory banks
  2453. self.pattern_banks = {
  2454. 'breakthrough': PatternBank('breakthrough', capacity=1000),
  2455. 'completion': PatternBank('completion', capacity=1000),
  2456. 'emotional': PatternBank('emotional', capacity=2000),
  2457. 'boundary': PatternBank('boundary', capacity=1000)
  2458. }
  2459.  
  2460. # Meta-learning for few-shot adaptation
  2461. self.meta_learner = MetaLearner(embedding_dim).to(self.device)
  2462.  
  2463. # Grid cell generator
  2464. self.grid_generator = GridCellGenerator(embedding_dim).to(self.device)
  2465.  
  2466. # Convert to half precision if using GPU
  2467. if self.use_half:
  2468. self.tcn.half()
  2469. self.meta_learner.half()
  2470. self.grid_generator.half()
  2471.  
  2472. # User-specific patterns
  2473. self.user_patterns = defaultdict(lambda: defaultdict(list))
  2474.  
  2475. async def learn_pattern(self, user_id: str, exchange_sequence: List[ConversationExchange],
  2476. pattern_type: str, confidence: float):
  2477. """Learn from recognized patterns using few-shot learning"""
  2478.  
  2479. if confidence < 0.7: # Only learn from high-confidence patterns
  2480. return
  2481.  
  2482. # Extract temporal features
  2483. embeddings = torch.stack([ex.embedding for ex in exchange_sequence])
  2484. embeddings = embeddings.unsqueeze(0).transpose(1, 2) # TCN format
  2485.  
  2486. # Get TCN features
  2487. with torch.no_grad():
  2488. temporal_features = self.tcn(embeddings.to(self.device))
  2489. pattern_embedding = temporal_features.mean(dim=2).squeeze()
  2490.  
  2491. # Store in pattern bank
  2492. pattern_data = {
  2493. 'embedding': pattern_embedding,
  2494. 'temporal_features': temporal_features,
  2495. 'metadata': {
  2496. 'user_id': user_id,
  2497. 'timestamp': datetime.utcnow(),
  2498. 'exchange_count': len(exchange_sequence),
  2499. 'confidence': confidence
  2500. }
  2501. }
  2502.  
  2503. self.pattern_banks[pattern_type].add_pattern(pattern_data)
  2504. self.user_patterns[user_id][pattern_type].append(pattern_embedding)
  2505.  
  2506. # Update meta-learner with new pattern
  2507. if len(self.user_patterns[user_id][pattern_type]) >= 5:
  2508. await self._update_meta_learner(user_id, pattern_type)
  2509.  
  2510. async def detect_pattern(self, user_id: str, exchange_sequence: List[ConversationExchange],
  2511. pattern_type: str) -> Tuple[bool, float]:
  2512. """Detect patterns using learned representations"""
  2513.  
  2514. if not exchange_sequence:
  2515. return False, 0.0
  2516.  
  2517. # Prepare embeddings
  2518. embeddings = torch.stack([ex.embedding for ex in exchange_sequence])
  2519. embeddings = embeddings.unsqueeze(0).transpose(1, 2)
  2520.  
  2521. # Get temporal features
  2522. with torch.no_grad():
  2523. temporal_features = self.tcn(embeddings.to(self.device))
  2524. query_embedding = temporal_features.mean(dim=2).squeeze()
  2525.  
  2526. # Check against pattern bank
  2527. bank_score = self.pattern_banks[pattern_type].query_similarity(query_embedding)
  2528.  
  2529. # Check against user-specific patterns
  2530. user_score = 0.0
  2531. if user_id in self.user_patterns and pattern_type in self.user_patterns[user_id]:
  2532. user_patterns = torch.stack(self.user_patterns[user_id][pattern_type])
  2533. similarities = F.cosine_similarity(
  2534. query_embedding.unsqueeze(0),
  2535. user_patterns
  2536. )
  2537. user_score = similarities.max().item()
  2538.  
  2539. # Use meta-learner for adaptive detection
  2540. meta_score = 0.0
  2541. if hasattr(self, 'meta_learner'):
  2542. # Fix: Use the original embedding, not the TCN output
  2543. original_embedding = embeddings.squeeze(0).mean(dim=1) # Average over sequence
  2544. meta_score = self.meta_learner.predict_pattern(
  2545. original_embedding, pattern_type
  2546. ).item()
  2547.  
  2548. # Combine scores
  2549. final_score = 0.4 * bank_score + 0.4 * user_score + 0.2 * meta_score
  2550. detected = final_score > 0.75
  2551.  
  2552. return detected, final_score
  2553.  
  2554. async def _update_meta_learner(self, user_id: str, pattern_type: str):
  2555. """Update meta-learner with user-specific patterns"""
  2556. patterns = self.user_patterns[user_id][pattern_type][-20:] # Last 20 patterns
  2557.  
  2558. # Create support set for meta-learning
  2559. support_set = torch.stack(patterns)
  2560.  
  2561. # Update meta-learner
  2562. self.meta_learner.adapt(support_set, pattern_type)
  2563.  
  2564.  
  2565. class PatternBank:
  2566. """Efficient storage and retrieval of learned patterns"""
  2567.  
  2568. def __init__(self, pattern_type: str, capacity: int = 1000):
  2569. self.pattern_type = pattern_type
  2570. self.capacity = capacity
  2571. self.patterns = deque(maxlen=capacity)
  2572. self.index = None
  2573. self.embeddings = []
  2574.  
  2575. def add_pattern(self, pattern_data: Dict[str, Any]):
  2576. """Add new pattern to bank"""
  2577. self.patterns.append(pattern_data)
  2578. self.embeddings.append(pattern_data['embedding'].cpu().numpy())
  2579.  
  2580. # Rebuild index periodically
  2581. if len(self.patterns) % 100 == 0:
  2582. self._rebuild_index()
  2583.  
  2584. def _rebuild_index(self):
  2585. """Rebuild FAISS index for efficient similarity search"""
  2586. if not self.embeddings:
  2587. return
  2588.  
  2589. embeddings_array = np.stack(self.embeddings)
  2590. dimension = embeddings_array.shape[1]
  2591.  
  2592. # Use IVF index for larger collections
  2593. if len(self.embeddings) > 1000:
  2594. quantizer = faiss.IndexFlatL2(dimension)
  2595. self.index = faiss.IndexIVFFlat(quantizer, dimension,
  2596. min(100, len(self.embeddings) // 10))
  2597. self.index.train(embeddings_array)
  2598. else:
  2599. self.index = faiss.IndexFlatL2(dimension)
  2600.  
  2601. self.index.add(embeddings_array)
  2602.  
  2603. def query_similarity(self, query_embedding: torch.Tensor, k: int = 5) -> float:
  2604. """Find most similar patterns"""
  2605. if self.index is None or self.index.ntotal == 0:
  2606. return 0.0
  2607.  
  2608. query_np = query_embedding.cpu().numpy().reshape(1, -1)
  2609. distances, indices = self.index.search(query_np, min(k, self.index.ntotal))
  2610.  
  2611. # Convert distances to similarities
  2612. similarities = 1 / (1 + distances[0])
  2613. return float(similarities.mean())
  2614.  
  2615.  
  2616. class MetaLearner(nn.Module):
  2617. """Few-shot learning for rapid pattern adaptation"""
  2618.  
  2619. def __init__(self, embedding_dim: int):
  2620. super().__init__()
  2621. self.embedding_dim = embedding_dim
  2622.  
  2623. # Pattern type embeddings
  2624. self.pattern_embeddings = nn.Embedding(10, 64)
  2625.  
  2626. # Adaptation network
  2627. self.adapter = nn.Sequential(
  2628. nn.Linear(embedding_dim + 64, 256),
  2629. nn.ReLU(),
  2630. nn.Dropout(0.2),
  2631. nn.Linear(256, 128),
  2632. nn.ReLU(),
  2633. nn.Dropout(0.2),
  2634. nn.Linear(128, 1),
  2635. nn.Sigmoid()
  2636. )
  2637.  
  2638. # Fast weights for adaptation
  2639. self.fast_weights = {}
  2640.  
  2641. def predict_pattern(self, query: torch.Tensor, pattern_type: str) -> torch.Tensor:
  2642. """Predict if query matches pattern type"""
  2643. pattern_id = hash(pattern_type) % 10
  2644. # Fix: Create tensor on the same device as the model
  2645. pattern_emb = self.pattern_embeddings(
  2646. torch.tensor([pattern_id], device=next(self.parameters()).device)
  2647. )
  2648.  
  2649. # Adapt query if we have fast weights
  2650. if pattern_type in self.fast_weights:
  2651. weights = self.fast_weights[pattern_type]
  2652. # Ensure tensors are on the same device
  2653. query = (query - weights['support_mean'].to(query.device)) / (weights['support_std'].to(query.device) + 1e-6)
  2654.  
  2655. # Concatenate embeddings
  2656. combined = torch.cat([query, pattern_emb.squeeze()], dim=-1)
  2657.  
  2658. # Predict
  2659. return self.adapter(combined)
  2660.  
  2661. # ==================== MULTI-DIMENSIONAL ENCODING ====================
  2662.  
  2663. class MultiDimensionalEncoder:
  2664. def __init__(self, base_model: str = 'sentence-transformers/all-mpnet-base-v2'):
  2665. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  2666. self.use_half = torch.cuda.is_available()
  2667.  
  2668. # Base encoder
  2669. self.semantic_encoder = SentenceTransformer(base_model, device=str(self.device))
  2670.  
  2671. # Specialized encoders
  2672. self.emotional_encoder = EmotionalTrajectoryEncoder().to(self.device)
  2673. self.temporal_encoder = TemporalSignatureEncoder().to(self.device)
  2674. self.relational_encoder = RelationalEmbeddingEncoder().to(self.device)
  2675. self.phenomenological_encoder = PhenomenologicalHasher().to(self.device)
  2676.  
  2677. # Matryoshka representation
  2678. self.matryoshka = MatryoshkaProjector(
  2679. input_dim=768,
  2680. output_dims=[64, 128, 256, 512, 768]
  2681. ).to(self.device)
  2682.  
  2683. # Convert all to half precision if using GPU
  2684. if self.use_half:
  2685. self.emotional_encoder.half()
  2686. self.temporal_encoder.half()
  2687. self.relational_encoder.half()
  2688. self.phenomenological_encoder.half()
  2689. self.matryoshka.half()
  2690.  
  2691. async def encode_memory(self, episode: ConversationEpisode) -> Dict[str, torch.Tensor]:
  2692. """Generate multi-dimensional encoding of memory"""
  2693.  
  2694. # 1. Semantic encoding
  2695. all_text = " ".join([
  2696. f"{ex.user_message} {ex.assistant_response}"
  2697. for ex in episode.exchanges
  2698. ])
  2699. semantic_emb = torch.from_numpy(
  2700. self.semantic_encoder.encode(all_text, convert_to_tensor=False)
  2701. ).to(self.device)
  2702.  
  2703. # Ensure correct dtype for all modules
  2704. if self.use_half:
  2705. semantic_emb = semantic_emb.half()
  2706.  
  2707. # 2. Emotional trajectory
  2708. emotional_emb = self.emotional_encoder.encode_trajectory(
  2709. [ex.emotional_valence for ex in episode.exchanges]
  2710. )
  2711.  
  2712. # 3. Temporal signature
  2713. temporal_emb = self.temporal_encoder.encode_pattern(
  2714. [ex.timestamp for ex in episode.exchanges]
  2715. )
  2716.  
  2717. # 4. Relational embedding
  2718. relational_emb = self.relational_encoder.encode_interaction(
  2719. episode.exchanges
  2720. )
  2721.  
  2722. # 5. Phenomenological hash
  2723. phenom_hash = self.phenomenological_encoder.generate_hash(episode)
  2724.  
  2725. # 6. Matryoshka representations
  2726. matryoshka_embs = self.matryoshka(semantic_emb)
  2727.  
  2728. return {
  2729. 'semantic': semantic_emb,
  2730. 'emotional': emotional_emb,
  2731. 'temporal': temporal_emb,
  2732. 'relational': relational_emb,
  2733. 'phenomenological': phenom_hash,
  2734. 'matryoshka': matryoshka_embs
  2735. }
  2736.  
  2737. class EmotionalTrajectoryEncoder(nn.Module):
  2738. """Encode emotional flow of conversation"""
  2739.  
  2740. def __init__(self, input_dim: int = 1, hidden_dim: int = 64, output_dim: int = 128):
  2741. super().__init__()
  2742. self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers=2,
  2743. batch_first=True, bidirectional=True)
  2744. self.projection = nn.Linear(hidden_dim * 2, output_dim)
  2745.  
  2746. def encode_trajectory(self, emotional_values: List[float]) -> torch.Tensor:
  2747. """Encode sequence of emotional values"""
  2748. device = next(self.parameters()).device
  2749. dtype = next(self.parameters()).dtype
  2750.  
  2751. if not emotional_values:
  2752. return torch.zeros(128, device=device, dtype=dtype)
  2753.  
  2754. # Convert to tensor with correct device and dtype
  2755. trajectory = torch.tensor(emotional_values, device=device, dtype=dtype).unsqueeze(-1).unsqueeze(0)
  2756.  
  2757. # Encode with LSTM
  2758. lstm_out, (hidden, _) = self.lstm(trajectory)
  2759.  
  2760. # Use final hidden state
  2761. final_hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
  2762.  
  2763. # Project to output dimension
  2764. return self.projection(final_hidden).squeeze()
  2765.  
  2766.  
  2767. class TemporalSignatureEncoder(nn.Module):
  2768. """Encode temporal patterns and rhythms"""
  2769.  
  2770. def __init__(self, output_dim: int = 64):
  2771. super().__init__()
  2772. self.output_dim = output_dim
  2773. self.projection = nn.Linear(16, output_dim)
  2774.  
  2775. def encode_pattern(self, timestamps: List[datetime]) -> torch.Tensor:
  2776. """Encode temporal pattern of conversation"""
  2777. device = next(self.parameters()).device
  2778. dtype = next(self.parameters()).dtype
  2779.  
  2780. if len(timestamps) < 2:
  2781. return torch.zeros(self.output_dim, device=device, dtype=dtype)
  2782.  
  2783. # Calculate inter-message intervals
  2784. intervals = []
  2785. for i in range(1, len(timestamps)):
  2786. interval = (timestamps[i] - timestamps[i-1]).total_seconds()
  2787. intervals.append(interval)
  2788.  
  2789. # Statistical features - handle single interval case
  2790. intervals_tensor = torch.tensor(intervals, device=device, dtype=dtype)
  2791.  
  2792. # Avoid std() on single element tensors
  2793. if len(intervals) > 1:
  2794. std_val = intervals_tensor.std()
  2795. else:
  2796. std_val = torch.tensor(0.0, device=device, dtype=dtype)
  2797.  
  2798. # Build features tensor with correct dtype
  2799. features_list = [
  2800. intervals_tensor.mean(),
  2801. std_val, # Use computed std_val
  2802. intervals_tensor.min(),
  2803. intervals_tensor.max(),
  2804. intervals_tensor.median(),
  2805. torch.tensor(len(intervals), device=device, dtype=dtype),
  2806. # Rhythm features
  2807. torch.tensor(self._calculate_rhythm_score(intervals), device=device, dtype=dtype),
  2808. torch.tensor(self._calculate_burstiness(intervals), device=device, dtype=dtype)
  2809. ]
  2810.  
  2811. features = torch.stack(features_list)
  2812.  
  2813. # Add time-of-day features
  2814. hour_features = torch.tensor([
  2815. timestamps[0].hour / 24.0,
  2816. timestamps[-1].hour / 24.0,
  2817. timestamps[0].weekday() / 7.0,
  2818. timestamps[-1].weekday() / 7.0
  2819. ], device=device, dtype=dtype)
  2820.  
  2821. # Multi-scale temporal features
  2822. time_scales = [60, 3600, 86400] # minute, hour, day
  2823. scale_features = []
  2824. for scale in time_scales:
  2825. scaled_intervals = intervals_tensor / scale
  2826. scale_features.extend([
  2827. scaled_intervals.mean(),
  2828. scaled_intervals.std()
  2829. ])
  2830.  
  2831. scale_features_tensor = torch.stack(scale_features) if scale_features else torch.empty(0, device=device, dtype=dtype)
  2832.  
  2833. # Combine all features
  2834. all_features = torch.cat([
  2835. features, hour_features, scale_features_tensor
  2836. ])
  2837.  
  2838. # Pad or truncate to fixed size
  2839. if all_features.shape[0] < 16:
  2840. all_features = F.pad(all_features, (0, 16 - all_features.shape[0]))
  2841. else:
  2842. all_features = all_features[:16]
  2843.  
  2844. return self.projection(all_features)
  2845.  
  2846. def _calculate_rhythm_score(self, intervals: List[float]) -> float:
  2847. """Calculate how rhythmic the conversation is"""
  2848. if len(intervals) < 2:
  2849. return 0.0
  2850.  
  2851. intervals_tensor = torch.tensor(intervals)
  2852. # Coefficient of variation
  2853. cv = intervals_tensor.std() / (intervals_tensor.mean() + 1e-6)
  2854. # Lower CV means more rhythmic
  2855. return 1.0 / (1.0 + cv.item())
  2856.  
  2857. def _calculate_burstiness(self, intervals: List[float]) -> float:
  2858. """Calculate burstiness of conversation"""
  2859. if len(intervals) < 2:
  2860. return 0.0
  2861.  
  2862. intervals_tensor = torch.tensor(intervals)
  2863. mean_interval = intervals_tensor.mean()
  2864. std_interval = intervals_tensor.std()
  2865.  
  2866. # Burstiness parameter
  2867. if mean_interval > 0:
  2868. burstiness = (std_interval - mean_interval) / (std_interval + mean_interval + 1e-6)
  2869. return burstiness.item()
  2870. return 0.0
  2871.  
  2872.  
  2873. class RelationalEmbeddingEncoder(nn.Module):
  2874. """Encode quality of interaction and relationship"""
  2875.  
  2876. def __init__(self, input_dim: int = 768, output_dim: int = 128):
  2877. super().__init__()
  2878. self.attention = nn.MultiheadAttention(input_dim, num_heads=8)
  2879. self.projection = nn.Sequential(
  2880. nn.Linear(input_dim, 256),
  2881. nn.ReLU(),
  2882. nn.Dropout(0.2),
  2883. nn.Linear(256, output_dim)
  2884. )
  2885.  
  2886. def encode_interaction(self, exchanges: List[ConversationExchange]) -> torch.Tensor:
  2887. """Encode relational quality of exchanges"""
  2888. if not exchanges:
  2889. return torch.zeros(128, device=next(self.parameters()).device,
  2890. dtype=next(self.parameters()).dtype)
  2891.  
  2892. # Stack embeddings - ensure correct dtype
  2893. embeddings = torch.stack([ex.embedding for ex in exchanges])
  2894.  
  2895. # Ensure embeddings have correct dtype
  2896. target_dtype = next(self.parameters()).dtype
  2897. if embeddings.dtype != target_dtype:
  2898. embeddings = embeddings.to(target_dtype)
  2899.  
  2900. # Self-attention to capture interaction patterns
  2901. attn_out, attn_weights = self.attention(
  2902. embeddings.unsqueeze(1),
  2903. embeddings.unsqueeze(1),
  2904. embeddings.unsqueeze(1)
  2905. )
  2906.  
  2907. # Average pool
  2908. pooled = attn_out.squeeze(1).mean(dim=0)
  2909.  
  2910. # Project
  2911. return self.projection(pooled)
  2912.  
  2913.  
  2914. class PhenomenologicalHasher(nn.Module):
  2915. """Generate experiential fingerprint of conversation"""
  2916.  
  2917. def __init__(self, input_dims: Dict[str, int] = None, hash_dim: int = 256):
  2918. super().__init__()
  2919. self.hash_dim = hash_dim
  2920.  
  2921. input_dims = input_dims or {
  2922. 'understanding': 1,
  2923. 'resonance': 1,
  2924. 'breakthrough': 1,
  2925. 'emotional_range': 1,
  2926. 'connection_quality': 1
  2927. }
  2928.  
  2929. total_dim = sum(input_dims.values())
  2930.  
  2931. self.hasher = nn.Sequential(
  2932. nn.Linear(total_dim, 512),
  2933. nn.ReLU(),
  2934. nn.Dropout(0.3),
  2935. nn.Linear(512, 256),
  2936. nn.ReLU(),
  2937. nn.Dropout(0.3),
  2938. nn.Linear(256, hash_dim),
  2939. nn.Tanh() # Output in [-1, 1]
  2940. )
  2941.  
  2942. def generate_hash(self, episode: ConversationEpisode) -> torch.Tensor:
  2943. """Generate phenomenological hash"""
  2944. device = next(self.parameters()).device
  2945. dtype = next(self.parameters()).dtype
  2946.  
  2947. # Extract experiential features
  2948. features = []
  2949.  
  2950. # Understanding depth
  2951. avg_understanding = np.mean([
  2952. ex.understanding_depth for ex in episode.exchanges
  2953. ]) if episode.exchanges else 0.0
  2954. features.append(avg_understanding)
  2955.  
  2956. # Resonance score
  2957. max_resonance = max([
  2958. ex.resonance_score for ex in episode.exchanges
  2959. ], default=0.0)
  2960. features.append(max_resonance)
  2961.  
  2962. # Breakthrough moments
  2963. breakthrough_count = sum(
  2964. 1 for ex in episode.exchanges if ex.breakthrough_moment
  2965. )
  2966. features.append(breakthrough_count / max(len(episode.exchanges), 1))
  2967.  
  2968. # Emotional range
  2969. if episode.emotional_arc:
  2970. emotional_range = max(episode.emotional_arc) - min(episode.emotional_arc)
  2971. else:
  2972. emotional_range = 0.0
  2973. features.append(emotional_range)
  2974.  
  2975. # Connection quality
  2976. avg_connection = episode.experiential_signature.get('connection_quality', 0.0) if episode.experiential_signature else 0.0
  2977. features.append(avg_connection)
  2978.  
  2979. # Convert to tensor with correct device and dtype
  2980. feature_tensor = torch.tensor(features, device=device, dtype=dtype)
  2981. return self.hasher(feature_tensor)
  2982.  
  2983.  
  2984. class MatryoshkaProjector(nn.Module):
  2985. """Generate nested representations at multiple scales"""
  2986.  
  2987. def __init__(self, input_dim: int, output_dims: List[int]):
  2988. super().__init__()
  2989. self.output_dims = sorted(output_dims) # Ensure ascending order
  2990.  
  2991. self.projectors = nn.ModuleList()
  2992. prev_dim = input_dim
  2993.  
  2994. for dim in self.output_dims:
  2995. if dim <= prev_dim:
  2996. self.projectors.append(
  2997. nn.Linear(prev_dim, dim)
  2998. )
  2999. else:
  3000. # Upsampling if needed
  3001. self.projectors.append(
  3002. nn.Sequential(
  3003. nn.Linear(prev_dim, dim * 2),
  3004. nn.ReLU(),
  3005. nn.Linear(dim * 2, dim)
  3006. )
  3007. )
  3008. prev_dim = dim
  3009.  
  3010. def forward(self, x: torch.Tensor) -> Dict[int, torch.Tensor]:
  3011. """Generate representations at all scales"""
  3012. representations = {}
  3013.  
  3014. current = x
  3015. for dim, projector in zip(self.output_dims, self.projectors):
  3016. current = projector(current)
  3017. representations[dim] = F.normalize(current, p=2, dim=-1)
  3018.  
  3019. return representations
  3020.  
  3021.  
  3022. # ==================== SLEEP-INSPIRED CONSOLIDATION ====================
  3023.  
  3024. class SleepConsolidationEngine:
  3025. """Implements sleep-inspired memory consolidation"""
  3026.  
  3027. def __init__(self, memory_system: Any):
  3028. self.memory_system = memory_system
  3029. self.consolidation_cycles = 0
  3030. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3031.  
  3032. # Sleep stage simulators
  3033. self.nrem_consolidator = NREMConsolidator().to(self.device)
  3034. self.rem_consolidator = REMConsolidator().to(self.device)
  3035.  
  3036. # Replay buffer
  3037. self.replay_buffer = deque(maxlen=10000)
  3038.  
  3039. # Consolidation metrics
  3040. self.metrics = defaultdict(list)
  3041.  
  3042. async def run_consolidation_cycle(self, duration_minutes: int = 90):
  3043. """Run a complete sleep cycle for memory consolidation"""
  3044.  
  3045. logger.info(f"Starting consolidation cycle {self.consolidation_cycles + 1}")
  3046.  
  3047. # Sleep cycle stages (simplified)
  3048. stages = [
  3049. ('NREM1', 0.05), # 5% - Light sleep
  3050. ('NREM2', 0.45), # 45% - Sleep spindles
  3051. ('NREM3', 0.20), # 20% - Deep sleep / SWS
  3052. ('REM', 0.20), # 20% - REM sleep
  3053. ('NREM2', 0.10) # 10% - Return to light sleep
  3054. ]
  3055.  
  3056. for stage, proportion in stages:
  3057. stage_duration = duration_minutes * proportion
  3058.  
  3059. if stage.startswith('NREM'):
  3060. await self._nrem_consolidation(stage, stage_duration)
  3061. else:
  3062. await self._rem_consolidation(stage_duration)
  3063.  
  3064. # Brief transition period
  3065. await asyncio.sleep(0.1)
  3066.  
  3067. self.consolidation_cycles += 1
  3068.  
  3069. # Log consolidation metrics
  3070. self._log_consolidation_metrics()
  3071.  
  3072. async def _nrem_consolidation(self, stage: str, duration: float):
  3073. """Non-REM consolidation focusing on memory strengthening"""
  3074.  
  3075. # Get recent memories for consolidation
  3076. recent_memories = await self._get_recent_memories()
  3077.  
  3078. if not recent_memories:
  3079. return
  3080.  
  3081. # NREM3 (SWS) - System consolidation
  3082. if stage == 'NREM3':
  3083. # Replay important memories
  3084. important_memories = [m for m in recent_memories if m.importance > 0.7]
  3085.  
  3086. for memory in important_memories:
  3087. # Strengthen memory traces
  3088. strengthened = await self.nrem_consolidator.strengthen_memory(memory)
  3089.  
  3090. # Update memory importance
  3091. memory.importance = min(1.0, memory.importance * 1.1)
  3092.  
  3093. # Add to replay buffer
  3094. self.replay_buffer.append({
  3095. 'memory_id': memory.id,
  3096. 'strengthened_embedding': strengthened,
  3097. 'timestamp': datetime.utcnow()
  3098. })
  3099.  
  3100. # NREM2 - Sleep spindles for memory integration
  3101. elif stage == 'NREM2':
  3102. # Detect and strengthen associations
  3103. associations = await self._detect_associations(recent_memories)
  3104.  
  3105. for mem1, mem2, strength in associations:
  3106. # Strengthen bidirectional connections
  3107. if strength > 0.7:
  3108. mem1.semantic_neighbors.append((mem2.id, strength))
  3109. mem2.semantic_neighbors.append((mem1.id, strength))
  3110.  
  3111. # Neural binding through spindle-like activity
  3112. await self.nrem_consolidator.bind_memories(mem1, mem2)
  3113.  
  3114. async def _rem_consolidation(self, duration: float):
  3115. """REM consolidation for creative connections and emotional processing"""
  3116.  
  3117. # Get memories with high emotional content
  3118. emotional_memories = await self._get_emotional_memories()
  3119.  
  3120. if not emotional_memories:
  3121. return
  3122.  
  3123. # REM-specific processing
  3124. for memory in emotional_memories:
  3125. # Creative recombination
  3126. novel_connections = await self.rem_consolidator.generate_novel_connections(
  3127. memory, self.replay_buffer
  3128. )
  3129.  
  3130. # Update memory with new insights
  3131. for connection in novel_connections:
  3132. memory.narrative_connections.append(connection)
  3133.  
  3134. # Emotional regulation
  3135. regulated = await self.rem_consolidator.regulate_emotion(memory)
  3136. memory.emotional_valence = regulated
  3137.  
  3138. async def _get_recent_memories(self, hours: int = 24) -> List[AgenticMemory]:
  3139. """Get memories from recent time window"""
  3140. cutoff = datetime.utcnow() - timedelta(hours=hours)
  3141.  
  3142. # Query from database
  3143. conn = sqlite3.connect(str(self.memory_system.db_path))
  3144. cursor = conn.execute("""
  3145. SELECT * FROM memories
  3146. WHERE created_at > ?
  3147. ORDER BY importance DESC
  3148. LIMIT 100
  3149. """, (cutoff.timestamp(),))
  3150.  
  3151. memories = []
  3152. # Convert to memory objects (simplified)
  3153. # In production, properly deserialize
  3154.  
  3155. conn.close()
  3156. return memories
  3157.  
  3158. async def _get_emotional_memories(self) -> List[AgenticMemory]:
  3159. """Get memories with strong emotional content"""
  3160. conn = sqlite3.connect(str(self.memory_system.db_path))
  3161. cursor = conn.execute("""
  3162. SELECT m.*, e.emotional_arc
  3163. FROM memories m
  3164. JOIN episodes e ON m.episode_id = e.id
  3165. WHERE json_array_length(e.emotional_arc) > 0
  3166. ORDER BY m.created_at DESC
  3167. LIMIT 50
  3168. """)
  3169.  
  3170. memories = []
  3171. # Process emotional memories
  3172.  
  3173. conn.close()
  3174. return memories
  3175.  
  3176. async def _detect_associations(self, memories: List[AgenticMemory]) -> List[Tuple]:
  3177. """Detect associations between memories"""
  3178. associations = []
  3179.  
  3180. for i, mem1 in enumerate(memories):
  3181. for mem2 in memories[i+1:]:
  3182. # Calculate association strength
  3183. if mem1.neural_embedding is not None and mem2.neural_embedding is not None:
  3184. similarity = F.cosine_similarity(
  3185. mem1.neural_embedding.unsqueeze(0),
  3186. mem2.neural_embedding.unsqueeze(0)
  3187. ).item()
  3188.  
  3189. # Temporal proximity bonus
  3190. time_diff = abs((mem1.created_at - mem2.created_at).total_seconds())
  3191. temporal_bonus = 1.0 / (1.0 + time_diff / 3600) # Decay over hours
  3192.  
  3193. # Topic overlap bonus
  3194. topic_overlap = len(set(mem1.episode.topics) & set(mem2.episode.topics))
  3195. topic_bonus = topic_overlap * 0.1
  3196.  
  3197. # Combined association strength
  3198. strength = similarity * 0.6 + temporal_bonus * 0.3 + topic_bonus * 0.1
  3199.  
  3200. if strength > 0.5:
  3201. associations.append((mem1, mem2, strength))
  3202.  
  3203. return associations
  3204.  
  3205. def _log_consolidation_metrics(self):
  3206. """Log metrics from consolidation cycle"""
  3207. logger.info(f"Consolidation cycle {self.consolidation_cycles} complete")
  3208. logger.info(f"Replay buffer size: {len(self.replay_buffer)}")
  3209. # Additional metrics logging
  3210.  
  3211.  
  3212. class NREMConsolidator(nn.Module):
  3213. """NREM sleep consolidation mechanisms"""
  3214.  
  3215. def __init__(self, embedding_dim: int = 768):
  3216. super().__init__()
  3217. self.embedding_dim = embedding_dim
  3218.  
  3219. # Slow wave generator
  3220. self.slow_wave = nn.Sequential(
  3221. nn.Linear(embedding_dim, 512),
  3222. nn.Tanh(),
  3223. nn.Linear(512, embedding_dim)
  3224. )
  3225.  
  3226. # Spindle generator
  3227. self.spindle_generator = nn.Sequential(
  3228. nn.Linear(embedding_dim * 2, 512),
  3229. nn.ReLU(),
  3230. nn.Linear(512, 256),
  3231. nn.ReLU(),
  3232. nn.Linear(256, embedding_dim)
  3233. )
  3234.  
  3235. async def strengthen_memory(self, memory: AgenticMemory) -> torch.Tensor:
  3236. """Strengthen memory through slow-wave activity"""
  3237. if memory.neural_embedding is None:
  3238. return torch.zeros(self.embedding_dim)
  3239.  
  3240. # Apply slow-wave transformation
  3241. strengthened = self.slow_wave(memory.neural_embedding)
  3242.  
  3243. # Add residual connection
  3244. return memory.neural_embedding + 0.1 * strengthened
  3245.  
  3246. async def bind_memories(self, memory1: AgenticMemory, memory2: AgenticMemory):
  3247. """Bind memories through spindle-like activity"""
  3248. if memory1.neural_embedding is None or memory2.neural_embedding is None:
  3249. return
  3250.  
  3251. # Concatenate embeddings
  3252. combined = torch.cat([memory1.neural_embedding, memory2.neural_embedding])
  3253.  
  3254. # Generate binding representation
  3255. binding = self.spindle_generator(combined)
  3256.  
  3257. # Update memories with binding information
  3258. # In practice, store this binding in the memory system
  3259.  
  3260.  
  3261. class REMConsolidator(nn.Module):
  3262. """REM sleep consolidation for creative connections"""
  3263.  
  3264. def __init__(self, embedding_dim: int = 768):
  3265. super().__init__()
  3266. self.embedding_dim = embedding_dim
  3267.  
  3268. # Dream-like recombination network
  3269. self.recombiner = nn.Sequential(
  3270. nn.Linear(embedding_dim * 2, 1024),
  3271. nn.ReLU(),
  3272. nn.Dropout(0.5), # High dropout for "dream-like" recombination
  3273. nn.Linear(1024, 512),
  3274. nn.ReLU(),
  3275. nn.Dropout(0.5),
  3276. nn.Linear(512, embedding_dim)
  3277. )
  3278.  
  3279. # Emotion regulation network
  3280. self.emotion_regulator = nn.Sequential(
  3281. nn.Linear(embedding_dim + 1, 256), # +1 for emotional valence
  3282. nn.Tanh(),
  3283. nn.Linear(256, 128),
  3284. nn.Tanh(),
  3285. nn.Linear(128, 1),
  3286. nn.Tanh()
  3287. )
  3288.  
  3289. async def generate_novel_connections(self, memory: AgenticMemory,
  3290. replay_buffer: deque) -> List[Tuple[str, str]]:
  3291. """Generate novel connections through dream-like recombination"""
  3292. connections = []
  3293.  
  3294. if not replay_buffer or memory.neural_embedding is None:
  3295. return connections
  3296.  
  3297. # Sample random memories from replay buffer
  3298. sample_size = min(10, len(replay_buffer))
  3299. samples = random.sample(list(replay_buffer), sample_size)
  3300.  
  3301. for sample in samples:
  3302. if 'strengthened_embedding' in sample:
  3303. # Recombine embeddings
  3304. combined = torch.cat([
  3305. memory.neural_embedding,
  3306. sample['strengthened_embedding']
  3307. ])
  3308.  
  3309. # Generate novel representation
  3310. novel = self.recombiner(combined)
  3311.  
  3312. # Calculate novelty score
  3313. novelty = 1 - F.cosine_similarity(
  3314. novel.unsqueeze(0),
  3315. memory.neural_embedding.unsqueeze(0)
  3316. ).item()
  3317.  
  3318. if novelty > 0.3: # Sufficient novelty
  3319. connections.append((
  3320. sample['memory_id'],
  3321. f'novel_connection_{novelty:.2f}'
  3322. ))
  3323.  
  3324. return connections
  3325.  
  3326. async def regulate_emotion(self, memory: AgenticMemory) -> float:
  3327. """Regulate emotional content of memory"""
  3328. if memory.neural_embedding is None:
  3329. return 0.0
  3330.  
  3331. # Get current emotional valence
  3332. current_emotion = torch.tensor([memory.emotional_valence])
  3333.  
  3334. # Combine with embedding
  3335. combined = torch.cat([memory.neural_embedding, current_emotion])
  3336.  
  3337. # Regulate emotion
  3338. regulated = self.emotion_regulator(combined)
  3339.  
  3340. return float(regulated.item())
  3341.  
  3342.  
  3343. # ==================== ENHANCED CONVERSATION FLOW ====================
  3344.  
  3345. class AdvancedConversationFlowAnalyzer:
  3346. """Implements research-based conversation flow analysis"""
  3347.  
  3348. def __init__(self, embedder: Any):
  3349. self.embedder = embedder
  3350. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3351. self.use_half = torch.cuda.is_available()
  3352.  
  3353. # Temporal graph network
  3354. self.temporal_gnn = TemporalGraphNetwork(
  3355. node_dim=768,
  3356. edge_dim=64,
  3357. hidden_dim=256
  3358. ).to(self.device)
  3359.  
  3360. # CODYMs implementation
  3361. self.codym_analyzer = CODYMAnalyzer()
  3362.  
  3363. # Dialogue state tracker
  3364. self.dst = NonAutoregressiveDST(
  3365. embedding_dim=768,
  3366. num_slots=20
  3367. ).to(self.device)
  3368.  
  3369. # Turn-taking analyzer
  3370. self.turn_analyzer = TurnTakingAnalyzer()
  3371.  
  3372. # Convert to half precision if using GPU
  3373. if self.use_half:
  3374. self.temporal_gnn.half()
  3375. self.dst.half()
  3376.  
  3377. async def analyze_conversation_flow(self, exchanges: List[ConversationExchange]) -> Dict[str, Any]:
  3378. """Comprehensive conversation flow analysis"""
  3379.  
  3380. if not exchanges:
  3381. return self._empty_analysis()
  3382.  
  3383. # 1. Build temporal graph
  3384. graph_features = await self._build_temporal_graph(exchanges)
  3385.  
  3386. # 2. CODYM analysis
  3387. codym_features = self.codym_analyzer.analyze_dynamics(exchanges)
  3388.  
  3389. # 3. Dialogue state tracking
  3390. dialogue_states = await self._track_dialogue_states(exchanges)
  3391.  
  3392. # 4. Turn-taking patterns
  3393. turn_patterns = self.turn_analyzer.analyze_patterns(exchanges)
  3394.  
  3395. # 5. Information flow analysis
  3396. info_flow = await self._analyze_information_flow(exchanges)
  3397.  
  3398. return {
  3399. 'graph_features': graph_features,
  3400. 'dynamics': codym_features,
  3401. 'dialogue_states': dialogue_states,
  3402. 'turn_patterns': turn_patterns,
  3403. 'information_flow': info_flow,
  3404. 'flow_quality': self._compute_flow_quality(
  3405. graph_features, codym_features, turn_patterns
  3406. )
  3407. }
  3408.  
  3409. # Add these methods to the AdvancedConversationFlowAnalyzer class:
  3410.  
  3411. async def _track_dialogue_states(self, exchanges: List[ConversationExchange]) -> List[Dict[str, float]]:
  3412. """Track dialogue states through the conversation"""
  3413. if not exchanges:
  3414. return []
  3415.  
  3416. # Prepare embeddings for DST
  3417. embeddings = torch.stack([ex.embedding for ex in exchanges])
  3418.  
  3419. # Ensure correct dtype
  3420. if hasattr(self, 'use_half') and self.use_half:
  3421. embeddings = embeddings.half()
  3422.  
  3423. # Get dialogue states
  3424. with torch.no_grad():
  3425. states = self.dst(embeddings)
  3426.  
  3427. # Convert to list of dicts
  3428. dialogue_states = []
  3429. for state in states:
  3430. state_dict = {}
  3431. for i in range(state.shape[0]):
  3432. slot_probs = state[i].cpu().numpy()
  3433. state_dict[f'slot_{i}'] = {
  3434. 'empty': float(slot_probs[0]),
  3435. 'filled': float(slot_probs[1]),
  3436. 'modified': float(slot_probs[2])
  3437. }
  3438. dialogue_states.append(state_dict)
  3439.  
  3440. return dialogue_states
  3441.  
  3442. async def _analyze_information_flow(self, exchanges: List[ConversationExchange]) -> Dict[str, Any]:
  3443. """Analyze how information flows through the conversation"""
  3444. if not exchanges:
  3445. return {
  3446. 'information_density': 0.0,
  3447. 'topic_coherence': 1.0,
  3448. 'progressive_depth': 0.0,
  3449. 'redundancy': 0.0
  3450. }
  3451.  
  3452. # Information density (tokens per exchange)
  3453. densities = [ex.token_count for ex in exchanges]
  3454. avg_density = sum(densities) / len(densities) if densities else 0
  3455.  
  3456. # Topic coherence (similarity between adjacent exchanges)
  3457. coherences = []
  3458. for i in range(1, len(exchanges)):
  3459. if exchanges[i].embedding is not None and exchanges[i-1].embedding is not None:
  3460. sim = F.cosine_similarity(
  3461. exchanges[i].embedding.unsqueeze(0),
  3462. exchanges[i-1].embedding.unsqueeze(0)
  3463. )
  3464. coherences.append(float(sim.item()))
  3465.  
  3466. avg_coherence = sum(coherences) / len(coherences) if coherences else 1.0
  3467.  
  3468. # Progressive depth (increasing complexity)
  3469. depth_progression = 0.0
  3470. if len(exchanges) > 2:
  3471. early_density = sum(densities[:len(densities)//2]) / (len(densities)//2)
  3472. late_density = sum(densities[len(densities)//2:]) / (len(densities) - len(densities)//2)
  3473. depth_progression = (late_density - early_density) / (early_density + 1e-6)
  3474.  
  3475. # Redundancy detection
  3476. redundancy = 0.0
  3477. if len(exchanges) > 3:
  3478. # Check for repeated patterns
  3479. for i in range(2, len(exchanges)):
  3480. for j in range(i-2):
  3481. if exchanges[i].embedding is not None and exchanges[j].embedding is not None:
  3482. sim = F.cosine_similarity(
  3483. exchanges[i].embedding.unsqueeze(0),
  3484. exchanges[j].embedding.unsqueeze(0)
  3485. )
  3486. if sim > 0.9: # High similarity indicates redundancy
  3487. redundancy += 1
  3488. redundancy = redundancy / (len(exchanges) - 2)
  3489.  
  3490. return {
  3491. 'information_density': avg_density,
  3492. 'topic_coherence': avg_coherence,
  3493. 'progressive_depth': depth_progression,
  3494. 'redundancy': min(redundancy, 1.0),
  3495. 'exchange_count': len(exchanges),
  3496. 'total_tokens': sum(densities)
  3497. }
  3498.  
  3499. def _compute_flow_quality(self, graph_features: Dict[str, Any],
  3500. codym_features: Dict[str, Any],
  3501. turn_patterns: Dict[str, Any]) -> float:
  3502. """Compute overall conversation flow quality"""
  3503.  
  3504. # Extract relevant metrics
  3505. graph_score = 0.5 # Default if no graph features
  3506. if graph_features.get('node_embeddings') is not None and len(graph_features['node_embeddings']) > 0:
  3507. # Use graph embedding norm as a proxy for richness
  3508. if graph_features.get('graph_embedding') is not None:
  3509. # Handle both tensor and list formats
  3510. graph_emb = graph_features['graph_embedding']
  3511. if isinstance(graph_emb, list) and len(graph_emb) > 0:
  3512. # Convert back to tensor if it's a list
  3513. graph_tensor = torch.tensor(graph_emb)
  3514. graph_score = torch.sigmoid(graph_tensor.norm() / 10).item()
  3515. elif torch.is_tensor(graph_emb):
  3516. graph_score = torch.sigmoid(graph_emb.norm() / 10).item()
  3517. else:
  3518. graph_score = 0.5
  3519.  
  3520. # CODYM scores
  3521. synchrony = codym_features.get('synchrony', 0.5)
  3522. convergence = codym_features.get('convergence', 0.5)
  3523. balance = codym_features.get('turn_balance', 0.5)
  3524.  
  3525. # Turn-taking quality
  3526. smoothness = turn_patterns.get('transition_smoothness', 0.5)
  3527. reciprocity = turn_patterns.get('reciprocity', 0.5)
  3528. rhythm = turn_patterns.get('rhythm', {}).get('regularity', 0.5)
  3529.  
  3530. # Weighted combination
  3531. flow_quality = (
  3532. graph_score * 0.2 +
  3533. synchrony * 0.15 +
  3534. convergence * 0.15 +
  3535. balance * 0.1 +
  3536. smoothness * 0.15 +
  3537. reciprocity * 0.15 +
  3538. rhythm * 0.1
  3539. )
  3540.  
  3541. return float(flow_quality)
  3542.  
  3543. def _empty_analysis(self) -> Dict[str, Any]:
  3544. """Return empty analysis structure"""
  3545. return {
  3546. 'graph_features': {
  3547. 'node_embeddings': torch.empty(0, 768),
  3548. 'graph_embedding': torch.zeros(768),
  3549. 'attention_weights': None
  3550. },
  3551. 'dynamics': {},
  3552. 'dialogue_states': [],
  3553. 'turn_patterns': {},
  3554. 'information_flow': {
  3555. 'information_density': 0.0,
  3556. 'topic_coherence': 1.0,
  3557. 'progressive_depth': 0.0,
  3558. 'redundancy': 0.0
  3559. },
  3560. 'flow_quality': 0.0
  3561. }
  3562.  
  3563. async def _build_temporal_graph(self, exchanges: List[ConversationExchange]) -> Dict[str, Any]:
  3564. """Build temporal graph representation"""
  3565.  
  3566. if not exchanges:
  3567. return {
  3568. 'node_embeddings': torch.empty(0, 768),
  3569. 'graph_embedding': torch.zeros(768),
  3570. 'attention_weights': None
  3571. }
  3572.  
  3573. # Create nodes for each exchange
  3574. nodes = []
  3575. edges = []
  3576.  
  3577. for i, exchange in enumerate(exchanges):
  3578. # Node features
  3579. node_features = exchange.embedding
  3580. nodes.append(node_features)
  3581.  
  3582. # Temporal edges to previous exchanges
  3583. for j in range(max(0, i-3), i): # Connect to last 3 exchanges
  3584. time_diff = (exchange.timestamp - exchanges[j].timestamp).total_seconds()
  3585.  
  3586. # Edge features - ensure correct dtype
  3587. edge_features = torch.tensor([
  3588. 1.0 / (1.0 + time_diff), # Temporal proximity
  3589. exchanges[j].resonance_score, # Previous resonance
  3590. float(exchanges[j].breakthrough_moment), # Breakthrough indicator
  3591. exchanges[j].emotional_valence # Emotional context
  3592. ])
  3593.  
  3594. # Ensure correct dtype if using half precision
  3595. if hasattr(self, 'device'):
  3596. edge_features = edge_features.to(self.device)
  3597. if hasattr(self, 'use_half') and self.use_half:
  3598. edge_features = edge_features.half()
  3599.  
  3600. edges.append((j, i, edge_features))
  3601.  
  3602. # Process through temporal GNN
  3603. node_tensor = torch.stack(nodes)
  3604.  
  3605. # Handle empty edges case
  3606. if edges:
  3607. edge_index = torch.tensor([[e[0], e[1]] for e in edges]).T
  3608. edge_attr = torch.stack([e[2] for e in edges])
  3609. else:
  3610. # Create empty tensors with correct shape
  3611. edge_index = torch.empty((2, 0), dtype=torch.long)
  3612. edge_attr = torch.empty((0, 4))
  3613.  
  3614. # Ensure correct device and dtype
  3615. if hasattr(self, 'device'):
  3616. edge_index = edge_index.to(self.device)
  3617. edge_attr = edge_attr.to(self.device)
  3618. if hasattr(self, 'use_half') and self.use_half:
  3619. edge_attr = edge_attr.half()
  3620.  
  3621. graph_output = self.temporal_gnn(node_tensor, edge_index, edge_attr)
  3622.  
  3623. return {
  3624. 'node_embeddings': graph_output['nodes'].cpu().tolist() if 'nodes' in graph_output and torch.is_tensor(graph_output['nodes']) else [],
  3625. 'graph_embedding': graph_output['graph'].cpu().tolist() if 'graph' in graph_output and torch.is_tensor(graph_output['graph']) else [],
  3626. 'attention_weights': graph_output.get('attention', None).cpu().tolist() if graph_output.get('attention') is not None and torch.is_tensor(graph_output.get('attention')) else None
  3627. }
  3628.  
  3629.  
  3630. class TemporalGraphNetwork(nn.Module):
  3631. """Temporal GNN for conversation understanding"""
  3632.  
  3633. def __init__(self, node_dim: int, edge_dim: int, hidden_dim: int):
  3634. super().__init__()
  3635.  
  3636. # Fix: Use actual edge dimension (4) instead of the passed parameter
  3637. actual_edge_dim = 4
  3638.  
  3639. # Message passing layers that preserve node dimension
  3640. self.conv1 = TemporalGraphConv(node_dim, node_dim, actual_edge_dim)
  3641. self.conv2 = TemporalGraphConv(node_dim, node_dim, actual_edge_dim)
  3642. self.conv3 = TemporalGraphConv(node_dim, node_dim, actual_edge_dim)
  3643.  
  3644. # Graph-level pooling
  3645. self.graph_pool = nn.Sequential(
  3646. nn.Linear(node_dim, hidden_dim),
  3647. nn.ReLU(),
  3648. nn.Linear(hidden_dim, node_dim)
  3649. )
  3650.  
  3651. # Attention mechanism
  3652. self.attention = nn.MultiheadAttention(node_dim, num_heads=8)
  3653.  
  3654. # Dropout for regularization
  3655. self.dropout = nn.Dropout(0.2)
  3656.  
  3657. def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
  3658. edge_attr: torch.Tensor) -> Dict[str, torch.Tensor]:
  3659. """Process temporal graph"""
  3660.  
  3661. # Store original input for residual connections
  3662. identity = x
  3663.  
  3664. # Message passing with residual connections
  3665. h = F.relu(self.conv1(x, edge_index, edge_attr))
  3666. h = self.dropout(h)
  3667. h = h + identity # Residual connection (both are node_dim)
  3668.  
  3669. identity = h
  3670. h = F.relu(self.conv2(h, edge_index, edge_attr))
  3671. h = self.dropout(h)
  3672. h = h + identity # Residual connection
  3673.  
  3674. identity = h
  3675. h = self.conv3(h, edge_index, edge_attr)
  3676. h = h + identity # Final residual connection
  3677.  
  3678. # Attention over nodes
  3679. h_unsqueezed = h.unsqueeze(0) # Add batch dimension for attention
  3680. attn_out, attn_weights = self.attention(h_unsqueezed, h_unsqueezed, h_unsqueezed)
  3681. h = attn_out.squeeze(0) # Remove batch dimension
  3682.  
  3683. # Graph-level representation
  3684. graph_repr = self.graph_pool(h.mean(dim=0))
  3685.  
  3686. return {
  3687. 'nodes': h,
  3688. 'graph': graph_repr,
  3689. 'attention': attn_weights
  3690. }
  3691.  
  3692.  
  3693. class TemporalGraphConv(nn.Module):
  3694. """Temporal graph convolution layer that preserves dimensions"""
  3695.  
  3696. def __init__(self, in_dim: int, out_dim: int, edge_dim: int):
  3697. super().__init__()
  3698. self.in_dim = in_dim
  3699. self.out_dim = out_dim
  3700.  
  3701. # Message network
  3702. self.message_net = nn.Sequential(
  3703. nn.Linear(in_dim * 2 + edge_dim, in_dim), # Keep same dimension
  3704. nn.ReLU(),
  3705. nn.Linear(in_dim, out_dim)
  3706. )
  3707.  
  3708. def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
  3709. edge_attr: torch.Tensor) -> torch.Tensor:
  3710. """Perform message passing"""
  3711.  
  3712. # Handle empty edge case
  3713. if edge_index.shape[1] == 0:
  3714. # No edges, just return input if dimensions match, else project
  3715. if self.in_dim == self.out_dim:
  3716. return x
  3717. else:
  3718. # Need to project to output dimension
  3719. if not hasattr(self, 'projection'):
  3720. self.projection = nn.Linear(self.in_dim, self.out_dim).to(x.device)
  3721. if x.dtype == torch.float16:
  3722. self.projection.half()
  3723. return self.projection(x)
  3724.  
  3725. row, col = edge_index
  3726.  
  3727. # Initialize output with correct shape
  3728. out = torch.zeros(x.shape[0], self.out_dim, device=x.device, dtype=x.dtype)
  3729.  
  3730. # Message computation
  3731. for i in range(edge_index.shape[1]):
  3732. src, dst = row[i], col[i]
  3733. # Concatenate source, destination, and edge features
  3734. msg_input = torch.cat([x[src], x[dst], edge_attr[i]])
  3735. message = self.message_net(msg_input)
  3736. out[dst] += message
  3737.  
  3738. return out
  3739.  
  3740.  
  3741. class CODYMAnalyzer:
  3742. """Conversational Dynamics Model analyzer"""
  3743.  
  3744. def __init__(self):
  3745. self.transition_matrix = None
  3746. self.state_history = []
  3747.  
  3748. def analyze_dynamics(self, exchanges: List[ConversationExchange]) -> Dict[str, Any]:
  3749. """Analyze conversation dynamics using Markov models"""
  3750.  
  3751. if len(exchanges) < 2:
  3752. return {}
  3753.  
  3754. # Extract turn lengths
  3755. turn_lengths = []
  3756. for exchange in exchanges:
  3757. user_len = len(exchange.user_message.split())
  3758. assistant_len = len(exchange.assistant_response.split())
  3759. turn_lengths.extend([user_len, assistant_len])
  3760.  
  3761. # Discretize turn lengths into states
  3762. states = self._discretize_lengths(turn_lengths)
  3763.  
  3764. # Build transition matrix
  3765. self.transition_matrix = self._build_transition_matrix(states)
  3766.  
  3767. # Analyze patterns
  3768. patterns = {
  3769. 'dominant_speaker': self._identify_dominant_speaker(turn_lengths),
  3770. 'turn_balance': self._calculate_turn_balance(turn_lengths),
  3771. 'predictability': self._calculate_predictability(self.transition_matrix),
  3772. 'convergence': self._analyze_convergence(turn_lengths),
  3773. 'synchrony': self._measure_synchrony(exchanges)
  3774. }
  3775.  
  3776. return patterns
  3777.  
  3778. def _discretize_lengths(self, lengths: List[int]) -> List[int]:
  3779. """Discretize turn lengths into states"""
  3780. # Simple quantization: short (0), medium (1), long (2)
  3781. states = []
  3782. for length in lengths:
  3783. if length < 10:
  3784. states.append(0)
  3785. elif length < 50:
  3786. states.append(1)
  3787. else:
  3788. states.append(2)
  3789. return states
  3790.  
  3791. def _build_transition_matrix(self, states: List[int]) -> np.ndarray:
  3792. """Build Markov transition matrix"""
  3793. n_states = 3 # short, medium, long
  3794. matrix = np.zeros((n_states, n_states))
  3795.  
  3796. for i in range(len(states) - 1):
  3797. matrix[states[i], states[i+1]] += 1
  3798.  
  3799. # Normalize rows
  3800. row_sums = matrix.sum(axis=1, keepdims=True)
  3801. matrix = np.divide(matrix, row_sums, where=row_sums != 0)
  3802.  
  3803. return matrix
  3804.  
  3805. def _identify_dominant_speaker(self, turn_lengths: List[int]) -> str:
  3806. """Identify who tends to speak more"""
  3807. user_lengths = turn_lengths[::2]
  3808. assistant_lengths = turn_lengths[1::2]
  3809.  
  3810. avg_user = np.mean(user_lengths) if user_lengths else 0
  3811. avg_assistant = np.mean(assistant_lengths) if assistant_lengths else 0
  3812.  
  3813. if avg_user > avg_assistant * 1.2:
  3814. return "user"
  3815. elif avg_assistant > avg_user * 1.2:
  3816. return "assistant"
  3817. else:
  3818. return "balanced"
  3819.  
  3820. def _calculate_turn_balance(self, turn_lengths: List[int]) -> float:
  3821. """Calculate balance between speakers"""
  3822. user_lengths = turn_lengths[::2]
  3823. assistant_lengths = turn_lengths[1::2]
  3824.  
  3825. if not user_lengths or not assistant_lengths:
  3826. return 0.5
  3827.  
  3828. total_user = sum(user_lengths)
  3829. total_assistant = sum(assistant_lengths)
  3830. total = total_user + total_assistant
  3831.  
  3832. if total == 0:
  3833. return 0.5
  3834.  
  3835. balance = min(total_user, total_assistant) / max(total_user, total_assistant)
  3836. return balance
  3837.  
  3838. def _calculate_predictability(self, matrix: np.ndarray) -> float:
  3839. """Calculate conversation predictability from transition matrix"""
  3840. if matrix.size == 0:
  3841. return 0.0
  3842.  
  3843. # Entropy of transition matrix
  3844. entropy = 0.0
  3845. for row in matrix:
  3846. for p in row:
  3847. if p > 0:
  3848. entropy -= p * np.log2(p)
  3849.  
  3850. # Normalize by maximum possible entropy
  3851. max_entropy = np.log2(matrix.shape[1])
  3852. predictability = 1.0 - (entropy / max_entropy) if max_entropy > 0 else 1.0
  3853.  
  3854. return predictability
  3855.  
  3856. def _analyze_convergence(self, turn_lengths: List[int]) -> float:
  3857. """Analyze if speakers converge in their turn lengths"""
  3858. if len(turn_lengths) < 4:
  3859. return 0.0
  3860.  
  3861. user_lengths = turn_lengths[::2]
  3862. assistant_lengths = turn_lengths[1::2]
  3863.  
  3864. # Calculate moving average difference
  3865. window = 3
  3866. convergence_scores = []
  3867.  
  3868. for i in range(window, min(len(user_lengths), len(assistant_lengths))):
  3869. user_avg = np.mean(user_lengths[i-window:i])
  3870. assistant_avg = np.mean(assistant_lengths[i-window:i])
  3871.  
  3872. # Normalized difference
  3873. diff = abs(user_avg - assistant_avg) / (user_avg + assistant_avg + 1e-6)
  3874. convergence_scores.append(1.0 - diff)
  3875.  
  3876. return np.mean(convergence_scores) if convergence_scores else 0.0
  3877.  
  3878. def _measure_synchrony(self, exchanges: List[ConversationExchange]) -> float:
  3879. """Measure conversational synchrony"""
  3880. if len(exchanges) < 2:
  3881. return 0.0
  3882.  
  3883. # Response time synchrony
  3884. response_times = []
  3885. for i in range(1, len(exchanges)):
  3886. time_diff = (exchanges[i].timestamp - exchanges[i-1].timestamp).total_seconds()
  3887. response_times.append(time_diff)
  3888.  
  3889. if not response_times:
  3890. return 0.0
  3891.  
  3892. # Low variance in response times indicates synchrony
  3893. variance = np.var(response_times)
  3894. mean_time = np.mean(response_times)
  3895.  
  3896. # Coefficient of variation (lower is more synchronous)
  3897. cv = np.sqrt(variance) / (mean_time + 1e-6)
  3898. synchrony = 1.0 / (1.0 + cv)
  3899.  
  3900. return synchrony
  3901.  
  3902.  
  3903. class NonAutoregressiveDST(nn.Module):
  3904. """Non-autoregressive dialogue state tracking"""
  3905.  
  3906. def __init__(self, embedding_dim: int, num_slots: int):
  3907. super().__init__()
  3908. self.num_slots = num_slots
  3909.  
  3910. # Slot encoders
  3911. self.slot_encoders = nn.ModuleList([
  3912. nn.Linear(embedding_dim, 128) for _ in range(num_slots)
  3913. ])
  3914.  
  3915. # State predictor
  3916. self.state_predictor = nn.Sequential(
  3917. nn.Linear(128 * num_slots, 512),
  3918. nn.ReLU(),
  3919. nn.Dropout(0.2),
  3920. nn.Linear(512, 256),
  3921. nn.ReLU(),
  3922. nn.Dropout(0.2),
  3923. nn.Linear(256, num_slots * 3) # 3 states per slot
  3924. )
  3925.  
  3926. def forward(self, exchange_embeddings: torch.Tensor) -> torch.Tensor:
  3927. """Predict dialogue states"""
  3928.  
  3929. # Encode each slot
  3930. slot_representations = []
  3931. for i, encoder in enumerate(self.slot_encoders):
  3932. slot_repr = encoder(exchange_embeddings)
  3933. slot_representations.append(slot_repr)
  3934.  
  3935. # Concatenate slot representations
  3936. combined = torch.cat(slot_representations, dim=-1)
  3937.  
  3938. # Predict states
  3939. states = self.state_predictor(combined)
  3940. states = states.view(-1, self.num_slots, 3) # Reshape to (batch, slots, states)
  3941.  
  3942. return F.softmax(states, dim=-1)
  3943.  
  3944.  
  3945. class TurnTakingAnalyzer:
  3946. """Analyze turn-taking patterns in conversation"""
  3947.  
  3948. def analyze_patterns(self, exchanges: List[ConversationExchange]) -> Dict[str, Any]:
  3949. """Analyze turn-taking patterns"""
  3950.  
  3951. if not exchanges:
  3952. return {}
  3953.  
  3954. patterns = {
  3955. 'interruptions': self._detect_interruptions(exchanges),
  3956. 'floor_holding': self._analyze_floor_holding(exchanges),
  3957. 'transition_smoothness': self._measure_transition_smoothness(exchanges),
  3958. 'reciprocity': self._calculate_reciprocity(exchanges),
  3959. 'rhythm': self._analyze_rhythm(exchanges)
  3960. }
  3961.  
  3962. return patterns
  3963.  
  3964. def _detect_interruptions(self, exchanges: List[ConversationExchange]) -> List[int]:
  3965. """Detect potential interruptions based on timing"""
  3966. interruptions = []
  3967.  
  3968. for i in range(1, len(exchanges)):
  3969. time_gap = (exchanges[i].timestamp - exchanges[i-1].timestamp).total_seconds()
  3970.  
  3971. # Very short gaps might indicate interruption
  3972. if time_gap < 2.0: # Less than 2 seconds
  3973. interruptions.append(i)
  3974.  
  3975. return interruptions
  3976.  
  3977. def _analyze_floor_holding(self, exchanges: List[ConversationExchange]) -> Dict[str, float]:
  3978. """Analyze how long each speaker holds the floor"""
  3979.  
  3980. user_times = []
  3981. assistant_times = []
  3982.  
  3983. for exchange in exchanges:
  3984. # Estimate speaking time from message length
  3985. user_time = len(exchange.user_message.split()) * 0.15 # ~150ms per word
  3986. assistant_time = len(exchange.assistant_response.split()) * 0.15
  3987.  
  3988. user_times.append(user_time)
  3989. assistant_times.append(assistant_time)
  3990.  
  3991. return {
  3992. 'user_avg': np.mean(user_times) if user_times else 0,
  3993. 'assistant_avg': np.mean(assistant_times) if assistant_times else 0,
  3994. 'user_total': sum(user_times),
  3995. 'assistant_total': sum(assistant_times)
  3996. }
  3997.  
  3998. def _measure_transition_smoothness(self, exchanges: List[ConversationExchange]) -> float:
  3999. """Measure how smooth turn transitions are"""
  4000.  
  4001. if len(exchanges) < 2:
  4002. return 1.0
  4003.  
  4004. gaps = []
  4005. for i in range(1, len(exchanges)):
  4006. gap = (exchanges[i].timestamp - exchanges[i-1].timestamp).total_seconds()
  4007. gaps.append(gap)
  4008.  
  4009. # Ideal gap is 1-3 seconds
  4010. ideal_gaps = [1.0 if 1 <= gap <= 3 else 0.0 for gap in gaps]
  4011. smoothness = sum(ideal_gaps) / len(ideal_gaps) if ideal_gaps else 0.0
  4012.  
  4013. return smoothness
  4014.  
  4015. def _calculate_reciprocity(self, exchanges: List[ConversationExchange]) -> float:
  4016. """Calculate conversational reciprocity"""
  4017.  
  4018. if not exchanges:
  4019. return 0.0
  4020.  
  4021. reciprocity_scores = []
  4022.  
  4023. for exchange in exchanges:
  4024. # Length reciprocity
  4025. user_len = len(exchange.user_message.split())
  4026. assistant_len = len(exchange.assistant_response.split())
  4027.  
  4028. if user_len > 0 and assistant_len > 0:
  4029. ratio = min(user_len, assistant_len) / max(user_len, assistant_len)
  4030. reciprocity_scores.append(ratio)
  4031.  
  4032. # Emotional reciprocity
  4033. if hasattr(exchange, 'emotional_valence'):
  4034. # Assistant matching user's emotional tone
  4035. reciprocity_scores.append(1.0 - abs(exchange.emotional_valence))
  4036.  
  4037. return np.mean(reciprocity_scores) if reciprocity_scores else 0.0
  4038.  
  4039. def _analyze_rhythm(self, exchanges: List[ConversationExchange]) -> Dict[str, float]:
  4040. """Analyze conversational rhythm"""
  4041.  
  4042. if len(exchanges) < 3:
  4043. return {'regularity': 0.0, 'tempo': 0.0}
  4044.  
  4045. # Time intervals between exchanges
  4046. intervals = []
  4047. for i in range(1, len(exchanges)):
  4048. interval = (exchanges[i].timestamp - exchanges[i-1].timestamp).total_seconds()
  4049. intervals.append(interval)
  4050.  
  4051. # Rhythm regularity (low variance = regular rhythm)
  4052. regularity = 1.0 / (1.0 + np.std(intervals) / (np.mean(intervals) + 1e-6))
  4053.  
  4054. # Tempo (exchanges per minute)
  4055. total_time = (exchanges[-1].timestamp - exchanges[0].timestamp).total_seconds()
  4056. tempo = len(exchanges) / (total_time / 60) if total_time > 0 else 0
  4057.  
  4058. return {
  4059. 'regularity': regularity,
  4060. 'tempo': tempo
  4061. }
  4062.  
  4063.  
  4064. # ==================== UNCERTAINTY & AMBIGUITY HANDLING ====================
  4065.  
  4066. class UncertaintyAwareMemorySystem:
  4067. """Handles uncertainty and ambiguity in memory operations"""
  4068.  
  4069. def __init__(self, base_memory_system: Any):
  4070. self.base_system = base_memory_system
  4071. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  4072.  
  4073. # Bayesian components
  4074. self.bayesian_encoder = BayesianMemoryEncoder().to(self.device)
  4075. self.uncertainty_quantifier = UncertaintyQuantifier().to(self.device)
  4076.  
  4077. # Ambiguity resolver
  4078. self.ambiguity_resolver = AmbiguityResolver(
  4079. self.base_system.embedder
  4080. ).to(self.device)
  4081.  
  4082. # Calibration module
  4083. self.calibrator = MemoryCalibrator().to(self.device)
  4084.  
  4085. async def store_memory_with_uncertainty(self, memory: AgenticMemory,
  4086. exchange_sequence: List[ConversationExchange]) -> Dict[str, Any]:
  4087. """Store memory with uncertainty quantification"""
  4088.  
  4089. # 1. Encode with uncertainty
  4090. encoded = await self.bayesian_encoder.encode_with_uncertainty(
  4091. memory.neural_embedding,
  4092. exchange_sequence
  4093. )
  4094.  
  4095. # 2. Quantify different types of uncertainty
  4096. uncertainties = self.uncertainty_quantifier.quantify(
  4097. encoded['mean'],
  4098. encoded['variance'],
  4099. exchange_sequence
  4100. )
  4101.  
  4102. # 3. Detect and resolve ambiguities
  4103. ambiguities = await self.ambiguity_resolver.detect_ambiguities(
  4104. exchange_sequence
  4105. )
  4106.  
  4107. # 4. Calibrate confidence scores
  4108. calibrated_confidence = self.calibrator.calibrate(
  4109. memory.importance,
  4110. uncertainties['total']
  4111. )
  4112.  
  4113. # Update memory with uncertainty information
  4114. memory.metadata = memory.metadata or {}
  4115. memory.metadata['uncertainty'] = {
  4116. 'aleatoric': uncertainties['aleatoric'],
  4117. 'epistemic': uncertainties['epistemic'],
  4118. 'total': uncertainties['total'],
  4119. 'ambiguities': ambiguities,
  4120. 'calibrated_confidence': calibrated_confidence
  4121. }
  4122.  
  4123. # Store with base system
  4124. result = await self.base_system.store_memory(memory)
  4125.  
  4126. return {
  4127. 'memory_id': result['memory_id'],
  4128. 'uncertainty_metrics': memory.metadata['uncertainty'],
  4129. 'requires_clarification': len(ambiguities) > 0
  4130. }
  4131.  
  4132.  
  4133. class BayesianMemoryEncoder(nn.Module):
  4134. """Bayesian neural network for memory encoding with uncertainty"""
  4135.  
  4136. def __init__(self, input_dim: int = 768, hidden_dim: int = 512, output_dim: int = 256):
  4137. super().__init__()
  4138.  
  4139. # Variational layers
  4140. self.fc1_mean = nn.Linear(input_dim, hidden_dim)
  4141. self.fc1_var = nn.Linear(input_dim, hidden_dim)
  4142.  
  4143. self.fc2_mean = nn.Linear(hidden_dim, hidden_dim)
  4144. self.fc2_var = nn.Linear(hidden_dim, hidden_dim)
  4145.  
  4146. self.fc3_mean = nn.Linear(hidden_dim, output_dim)
  4147. self.fc3_var = nn.Linear(hidden_dim, output_dim)
  4148.  
  4149. # Prior parameters
  4150. self.prior_mean = 0.0
  4151. self.prior_var = 1.0
  4152.  
  4153. def forward(self, x: torch.Tensor, sample: bool = True) -> Dict[str, torch.Tensor]:
  4154. """Forward pass with uncertainty estimation"""
  4155.  
  4156. # Layer 1
  4157. mean1 = self.fc1_mean(x)
  4158. var1 = F.softplus(self.fc1_var(x))
  4159. h1 = self._sample_gaussian(mean1, var1) if sample else mean1
  4160. h1 = F.relu(h1)
  4161.  
  4162. # Layer 2
  4163. mean2 = self.fc2_mean(h1)
  4164. var2 = F.softplus(self.fc2_var(h1))
  4165. h2 = self._sample_gaussian(mean2, var2) if sample else mean2
  4166. h2 = F.relu(h2)
  4167.  
  4168. # Output layer
  4169. mean_out = self.fc3_mean(h2)
  4170. var_out = F.softplus(self.fc3_var(h2))
  4171.  
  4172. if sample:
  4173. output = self._sample_gaussian(mean_out, var_out)
  4174. else:
  4175. output = mean_out
  4176.  
  4177. return {
  4178. 'output': output,
  4179. 'mean': mean_out,
  4180. 'variance': var_out,
  4181. 'kl_divergence': self._compute_kl_divergence([
  4182. (mean1, var1), (mean2, var2), (mean_out, var_out)
  4183. ])
  4184. }
  4185.  
  4186. def _sample_gaussian(self, mean: torch.Tensor, var: torch.Tensor) -> torch.Tensor:
  4187. """Sample from Gaussian distribution"""
  4188. epsilon = torch.randn_like(mean)
  4189. return mean + torch.sqrt(var) * epsilon
  4190.  
  4191. def _compute_kl_divergence(self, params: List[Tuple[torch.Tensor, torch.Tensor]]) -> torch.Tensor:
  4192. """Compute KL divergence from prior"""
  4193. kl = 0
  4194. for mean, var in params:
  4195. kl += 0.5 * torch.sum(
  4196. var / self.prior_var +
  4197. (mean - self.prior_mean) ** 2 / self.prior_var -
  4198. 1 - torch.log(var / self.prior_var)
  4199. )
  4200. return kl
  4201.  
  4202. async def encode_with_uncertainty(self, embedding: torch.Tensor,
  4203. exchanges: List[ConversationExchange]) -> Dict[str, torch.Tensor]:
  4204. """Encode with multiple forward passes for uncertainty"""
  4205.  
  4206. # Multiple forward passes
  4207. n_samples = 10
  4208. outputs = []
  4209.  
  4210. for _ in range(n_samples):
  4211. result = self.forward(embedding, sample=True)
  4212. outputs.append(result['output'])
  4213.  
  4214. # Compute statistics
  4215. outputs_stacked = torch.stack(outputs)
  4216. mean = outputs_stacked.mean(dim=0)
  4217. variance = outputs_stacked.var(dim=0)
  4218.  
  4219. return {
  4220. 'mean': mean,
  4221. 'variance': variance,
  4222. 'samples': outputs_stacked
  4223. }
  4224.  
  4225.  
  4226. class UncertaintyQuantifier(nn.Module): # Add nn.Module inheritance
  4227. """Quantify different types of uncertainty"""
  4228.  
  4229. def __init__(self):
  4230. super().__init__() # Add super().__init__()
  4231.  
  4232. def quantify(self, mean: torch.Tensor, variance: torch.Tensor,
  4233. exchanges: List[ConversationExchange]) -> Dict[str, float]:
  4234. """Quantify aleatoric and epistemic uncertainty"""
  4235.  
  4236. # Aleatoric uncertainty (data uncertainty)
  4237. aleatoric = self._compute_aleatoric_uncertainty(exchanges)
  4238.  
  4239. # Epistemic uncertainty (model uncertainty)
  4240. epistemic = float(variance.mean().item())
  4241.  
  4242. # Total uncertainty
  4243. total = np.sqrt(aleatoric ** 2 + epistemic ** 2)
  4244.  
  4245. return {
  4246. 'aleatoric': aleatoric,
  4247. 'epistemic': epistemic,
  4248. 'total': total,
  4249. 'confidence': 1.0 / (1.0 + total)
  4250. }
  4251.  
  4252. def _compute_aleatoric_uncertainty(self, exchanges: List[ConversationExchange]) -> float:
  4253. """Compute data-inherent uncertainty"""
  4254.  
  4255. if not exchanges:
  4256. return 0.0
  4257.  
  4258. uncertainties = []
  4259.  
  4260. # Lexical ambiguity
  4261. ambiguous_words = ['it', 'they', 'this', 'that', 'there', 'here']
  4262. for exchange in exchanges:
  4263. text = exchange.user_message.lower()
  4264. ambiguity_count = sum(1 for word in ambiguous_words if word in text.split())
  4265. uncertainties.append(ambiguity_count / max(len(text.split()), 1))
  4266.  
  4267. # Emotional ambiguity
  4268. emotional_variance = np.var([ex.emotional_valence for ex in exchanges])
  4269. uncertainties.append(emotional_variance)
  4270.  
  4271. # Temporal ambiguity
  4272. temporal_words = ['soon', 'later', 'recently', 'eventually', 'sometimes']
  4273. for exchange in exchanges:
  4274. text = exchange.user_message.lower()
  4275. temporal_count = sum(1 for word in temporal_words if word in text)
  4276. uncertainties.append(temporal_count * 0.1)
  4277.  
  4278. return float(np.mean(uncertainties))
  4279.  
  4280.  
  4281. class AmbiguityResolver(nn.Module):
  4282. """Detect and resolve ambiguities in conversation"""
  4283.  
  4284. def __init__(self, embedder: Any):
  4285. super().__init__()
  4286. self.embedder = embedder
  4287.  
  4288. # Ambiguity detection network
  4289. self.detector = nn.Sequential(
  4290. nn.Linear(768, 512),
  4291. nn.ReLU(),
  4292. nn.Dropout(0.2),
  4293. nn.Linear(512, 256),
  4294. nn.ReLU(),
  4295. nn.Dropout(0.2),
  4296. nn.Linear(256, 5) # 5 types of ambiguity
  4297. )
  4298.  
  4299. # Context resolver
  4300. self.context_resolver = nn.TransformerEncoder(
  4301. nn.TransformerEncoderLayer(
  4302. d_model=768,
  4303. nhead=8,
  4304. dim_feedforward=2048,
  4305. dropout=0.1
  4306. ),
  4307. num_layers=3
  4308. )
  4309.  
  4310. async def detect_ambiguities(self, exchanges: List[ConversationExchange]) -> List[Dict[str, Any]]:
  4311. """Detect various types of ambiguity"""
  4312.  
  4313. ambiguities = []
  4314.  
  4315. for i, exchange in enumerate(exchanges):
  4316. # Get embedding
  4317. embedding = exchange.embedding
  4318.  
  4319. # Detect ambiguity types
  4320. with torch.no_grad():
  4321. ambiguity_scores = torch.sigmoid(self.detector(embedding))
  4322.  
  4323. # Check each ambiguity type
  4324. ambiguity_types = ['lexical', 'syntactic', 'semantic', 'anaphoric', 'pragmatic']
  4325.  
  4326. for j, (amb_type, score) in enumerate(zip(ambiguity_types, ambiguity_scores)):
  4327. if score > 0.5: # Threshold for detection
  4328. # Try to resolve using context
  4329. context_window = exchanges[max(0, i-3):i+1]
  4330. resolution = await self._resolve_ambiguity(
  4331. exchange, context_window, amb_type
  4332. )
  4333.  
  4334. ambiguities.append({
  4335. 'exchange_index': i,
  4336. 'type': amb_type,
  4337. 'score': float(score),
  4338. 'text': exchange.user_message[:100],
  4339. 'resolution': resolution
  4340. })
  4341.  
  4342. return ambiguities
  4343.  
  4344. async def _resolve_ambiguity(self, exchange: ConversationExchange,
  4345. context: List[ConversationExchange],
  4346. ambiguity_type: str) -> Dict[str, Any]:
  4347. """Attempt to resolve detected ambiguity"""
  4348.  
  4349. # Stack context embeddings
  4350. context_embeddings = torch.stack([ex.embedding for ex in context])
  4351.  
  4352. # Apply transformer to resolve
  4353. resolved = self.context_resolver(context_embeddings.unsqueeze(1))
  4354.  
  4355. # Get attention weights for interpretability
  4356. # In practice, extract from transformer layers
  4357.  
  4358. return {
  4359. 'resolved': True, # Simplified
  4360. 'confidence': 0.8,
  4361. 'method': f'{ambiguity_type}_resolution',
  4362. 'context_used': len(context)
  4363. }
  4364.  
  4365.  
  4366. class MemoryCalibrator(nn.Module):
  4367. """Calibrate confidence scores for better reliability"""
  4368.  
  4369. def __init__(self):
  4370. super().__init__()
  4371. # Temperature scaling parameter
  4372. self.temperature = nn.Parameter(torch.ones(1) * 1.5)
  4373.  
  4374. # Platt scaling parameters
  4375. self.platt_a = nn.Parameter(torch.zeros(1))
  4376. self.platt_b = nn.Parameter(torch.ones(1))
  4377.  
  4378. def calibrate(self, raw_confidence: float, uncertainty: float) -> float:
  4379. """Calibrate confidence score"""
  4380.  
  4381. # Adjust for uncertainty
  4382. adjusted = raw_confidence * (1.0 - uncertainty)
  4383.  
  4384. # Temperature scaling
  4385. logit = torch.logit(torch.tensor([adjusted]))
  4386. calibrated_logit = logit / self.temperature
  4387.  
  4388. # Platt scaling
  4389. calibrated_logit = self.platt_a * calibrated_logit + self.platt_b
  4390.  
  4391. # Convert back to probability
  4392. calibrated = torch.sigmoid(calibrated_logit)
  4393.  
  4394. return float(calibrated.item())
  4395.  
  4396.  
  4397. # ==================== ENHANCED PERSONALIZATION ====================
  4398.  
  4399. class PrivacyPreservingPersonalization:
  4400. """User-specific adaptation with privacy preservation"""
  4401.  
  4402. def __init__(self, base_system: Any):
  4403. self.base_system = base_system
  4404. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  4405. self.use_half = torch.cuda.is_available()
  4406.  
  4407. # User model components
  4408. self.user_models = {}
  4409. self.behavioral_analyzer = BehavioralPatternAnalyzer().to(self.device)
  4410. self.preference_learner = PreferenceLearner().to(self.device)
  4411. self.cultural_adapter = CulturalAdapter().to(self.device)
  4412.  
  4413. # Convert to half precision if using GPU
  4414. if self.use_half:
  4415. self.behavioral_analyzer.half()
  4416. self.preference_learner.half()
  4417. self.cultural_adapter.half()
  4418.  
  4419. # Privacy components
  4420. self.differential_privacy = DifferentialPrivacyModule(epsilon=1.9)
  4421. self.federated_aggregator = FederatedAggregator()
  4422.  
  4423. # Adaptive interface
  4424. self.interface_adapter = AdaptiveInterfaceEngine()
  4425.  
  4426. async def personalize_for_user(self, user_id: str,
  4427. exchange: ConversationExchange,
  4428. historical_data: Optional[List[ConversationEpisode]] = None) -> Dict[str, Any]:
  4429. """Personalize memory system for specific user"""
  4430.  
  4431. # Get or create user model
  4432. if user_id not in self.user_models:
  4433. self.user_models[user_id] = await self._initialize_user_model(
  4434. user_id, historical_data
  4435. )
  4436.  
  4437. user_model = self.user_models[user_id]
  4438.  
  4439. # 1. Analyze behavioral patterns
  4440. behavior_features = await self.behavioral_analyzer.analyze(
  4441. exchange, user_model
  4442. )
  4443.  
  4444. # 2. Learn preferences with privacy
  4445. preferences = await self.preference_learner.learn_with_privacy(
  4446. exchange, user_model, self.differential_privacy
  4447. )
  4448.  
  4449. # 3. Cultural adaptation
  4450. cultural_context = await self.cultural_adapter.adapt(
  4451. exchange, user_model
  4452. )
  4453.  
  4454. # 4. Update user model
  4455. user_model = self._update_user_model(
  4456. user_model, behavior_features, preferences, cultural_context
  4457. )
  4458.  
  4459. # 5. Adapt interface
  4460. interface_adaptations = self.interface_adapter.generate_adaptations(
  4461. user_model
  4462. )
  4463.  
  4464. return {
  4465. 'behavioral_profile': behavior_features,
  4466. 'preferences': preferences,
  4467. 'cultural_adaptations': cultural_context,
  4468. 'interface_config': interface_adaptations,
  4469. 'privacy_budget_used': self.differential_privacy.get_budget_used()
  4470. }
  4471.  
  4472. async def _initialize_user_model(self, user_id: str,
  4473. historical_data: Optional[List[ConversationEpisode]]) -> Dict[str, Any]:
  4474. """Initialize user model from historical data"""
  4475.  
  4476. model = {
  4477. 'user_id': user_id,
  4478. 'created_at': datetime.utcnow(),
  4479. 'interaction_count': 0,
  4480. 'behavioral_embeddings': [],
  4481. 'preference_vectors': [],
  4482. 'cultural_markers': {},
  4483. 'communication_style': {
  4484. 'formality': 0.5,
  4485. 'verbosity': 0.5,
  4486. 'technical_level': 0.5,
  4487. 'emotional_expressiveness': 0.5
  4488. },
  4489. 'temporal_patterns': {},
  4490. 'topic_preferences': defaultdict(float)
  4491. }
  4492.  
  4493. # Initialize from historical data if available
  4494. if historical_data:
  4495. for episode in historical_data[-10:]: # Last 10 episodes
  4496. # Extract patterns
  4497. for exchange in episode.exchanges:
  4498. # Update communication style
  4499. model['communication_style']['verbosity'] += len(
  4500. exchange.user_message.split()
  4501. ) / 100.0
  4502.  
  4503. # Update topic preferences
  4504. for topic in episode.topics:
  4505. model['topic_preferences'][topic] += 1.0
  4506.  
  4507. model['interaction_count'] += 1
  4508.  
  4509. return model
  4510.  
  4511. def _update_user_model(self, user_model: Dict[str, Any],
  4512. behavior_features: Dict[str, Any],
  4513. preferences: Dict[str, Any],
  4514. cultural_context: Dict[str, Any]) -> Dict[str, Any]:
  4515. """Update user model with new features"""
  4516.  
  4517. # Update behavioral embeddings
  4518. if 'behavioral_embedding' in behavior_features:
  4519. embedding = behavior_features['behavioral_embedding']
  4520. if torch.is_tensor(embedding):
  4521. embedding = embedding.cpu().tolist()
  4522. user_model['behavioral_embeddings'].append(embedding)
  4523. # Keep only recent embeddings
  4524. if len(user_model['behavioral_embeddings']) > 100:
  4525. user_model['behavioral_embeddings'] = user_model['behavioral_embeddings'][-100:]
  4526.  
  4527. # Update interaction count
  4528. user_model['interaction_count'] += 1
  4529.  
  4530. # Update communication style with exponential moving average
  4531. alpha = 0.1 # Learning rate
  4532. if 'communication_style' in preferences:
  4533. for key, value in preferences['communication_style'].items():
  4534. if key in user_model['communication_style']:
  4535. user_model['communication_style'][key] = (
  4536. alpha * value + (1 - alpha) * user_model['communication_style'][key]
  4537. )
  4538.  
  4539. # Update topic preferences
  4540. if 'top_topics' in preferences:
  4541. for topic_idx, score in preferences['top_topics']:
  4542. # Convert topic index to string if needed
  4543. topic_key = f"topic_{topic_idx}"
  4544. user_model['topic_preferences'][topic_key] = (
  4545. user_model['topic_preferences'].get(topic_key, 0) * 0.9 + score * 0.1
  4546. )
  4547.  
  4548. # Update temporal patterns
  4549. if 'response_timing' in behavior_features:
  4550. timing = behavior_features['response_timing']
  4551. if timing.get('typical_hour'):
  4552. current_hour = datetime.utcnow().hour
  4553. hour_key = f'hour_{current_hour}'
  4554. user_model['temporal_patterns'][hour_key] = (
  4555. user_model['temporal_patterns'].get(hour_key, 0) + 1
  4556. )
  4557.  
  4558. # Update cultural markers
  4559. if 'detected_markers' in cultural_context:
  4560. for marker in cultural_context['detected_markers']:
  4561. user_model['cultural_markers'][marker] = (
  4562. user_model['cultural_markers'].get(marker, 0) + 1
  4563. )
  4564.  
  4565. # Store preference vectors if available
  4566. if 'preference_vectors' in user_model and hasattr(self.preference_learner, 'encoder'):
  4567. # Preference vectors are already updated in learn_with_privacy
  4568. pass
  4569.  
  4570. return user_model
  4571.  
  4572. class BehavioralPatternAnalyzer(nn.Module):
  4573. """Analyze user behavioral patterns"""
  4574.  
  4575. def __init__(self, input_dim: int = 768, hidden_dim: int = 256):
  4576. super().__init__()
  4577.  
  4578. # Add device attribute
  4579. self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  4580.  
  4581. # Sequential pattern analyzer
  4582. self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers=2,
  4583. batch_first=True, bidirectional=True)
  4584.  
  4585. # Pattern classifier
  4586. self.classifier = nn.Sequential(
  4587. nn.Linear(hidden_dim * 2, 512),
  4588. nn.ReLU(),
  4589. nn.Dropout(0.2),
  4590. nn.Linear(512, 256),
  4591. nn.ReLU(),
  4592. nn.Dropout(0.2),
  4593. nn.Linear(256, 128) # Behavioral embedding
  4594. )
  4595.  
  4596. async def analyze(self, exchange: ConversationExchange,
  4597. user_model: Dict[str, Any]) -> Dict[str, Any]:
  4598. """Analyze behavioral patterns"""
  4599.  
  4600. # Get recent EXCHANGE embeddings (768-dim), not behavioral embeddings
  4601. recent_exchanges = user_model.get('recent_exchange_embeddings', [])[-10:]
  4602.  
  4603. # Ensure exchange embedding has correct dtype
  4604. exchange_embedding = exchange.embedding
  4605. if exchange_embedding.dtype != next(self.parameters()).dtype:
  4606. exchange_embedding = exchange_embedding.to(next(self.parameters()).dtype)
  4607.  
  4608. if recent_exchanges:
  4609. # Stack raw exchange embeddings (all 768-dim)
  4610. dtype = next(self.parameters()).dtype
  4611. device = next(self.parameters()).device
  4612.  
  4613. recent_embeddings_tensor = torch.stack([
  4614. torch.tensor(emb, dtype=dtype, device=device) if isinstance(emb, list) else
  4615. emb.to(device).to(dtype) if hasattr(emb, 'to') else emb
  4616. for emb in recent_exchanges
  4617. ])
  4618.  
  4619. # Add current exchange embedding
  4620. all_embeddings = torch.cat([recent_embeddings_tensor, exchange_embedding.unsqueeze(0)])
  4621.  
  4622. # Process sequence through LSTM (expects 768-dim input)
  4623. lstm_out, (hidden, _) = self.lstm(all_embeddings.unsqueeze(0))
  4624.  
  4625. # Get behavioral pattern from LSTM output
  4626. final_hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
  4627. behavioral_embedding = self.classifier(final_hidden).squeeze()
  4628. else:
  4629. # Bootstrap from current exchange
  4630. behavioral_embedding = self._process_single_exchange(exchange_embedding)
  4631.  
  4632. # Store the raw exchange embedding for future sequences
  4633. if 'recent_exchange_embeddings' not in user_model:
  4634. user_model['recent_exchange_embeddings'] = []
  4635. user_model['recent_exchange_embeddings'].append(exchange_embedding.cpu())
  4636. if len(user_model['recent_exchange_embeddings']) > 20:
  4637. user_model['recent_exchange_embeddings'] = user_model['recent_exchange_embeddings'][-20:]
  4638.  
  4639. # Extract behavioral features
  4640. features = {
  4641. 'response_timing': self._analyze_timing(exchange, user_model),
  4642. 'linguistic_patterns': self._analyze_linguistic(exchange),
  4643. 'interaction_style': self._analyze_interaction(exchange, user_model),
  4644. 'engagement_level': self._measure_engagement(exchange),
  4645. 'behavioral_embedding': behavioral_embedding.cpu().tolist()
  4646. }
  4647.  
  4648. return features
  4649.  
  4650. def _process_single_exchange(self, exchange_embedding: torch.Tensor) -> torch.Tensor:
  4651. """Process a single exchange embedding to behavioral embedding"""
  4652. # Process through LSTM (need sequence format)
  4653. exchange_seq = exchange_embedding.unsqueeze(0).unsqueeze(0) # [1, 1, 768]
  4654.  
  4655. # Process through LSTM
  4656. lstm_out, (hidden, _) = self.lstm(exchange_seq)
  4657.  
  4658. # Concatenate hidden states
  4659. final_hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
  4660.  
  4661. # Get behavioral embedding through classifier
  4662. behavioral_embedding = self.classifier(final_hidden).squeeze()
  4663.  
  4664. return behavioral_embedding
  4665.  
  4666. def _analyze_timing(self, exchange: ConversationExchange,
  4667. user_model: Dict[str, Any]) -> Dict[str, float]:
  4668. """Analyze temporal patterns"""
  4669.  
  4670. current_hour = exchange.timestamp.hour
  4671. current_day = exchange.timestamp.weekday()
  4672.  
  4673. # Update temporal patterns
  4674. temporal = user_model.get('temporal_patterns', {})
  4675.  
  4676. hour_key = f'hour_{current_hour}'
  4677. day_key = f'day_{current_day}'
  4678.  
  4679. temporal[hour_key] = temporal.get(hour_key, 0) + 1
  4680. temporal[day_key] = temporal.get(day_key, 0) + 1
  4681.  
  4682. return {
  4683. 'typical_hour': current_hour in [h for h, c in temporal.items() if c > 3],
  4684. 'typical_day': current_day in [d for d, c in temporal.items() if c > 3],
  4685. 'session_time': 0.0 # Would calculate from session start
  4686. }
  4687.  
  4688. def _analyze_linguistic(self, exchange: ConversationExchange) -> Dict[str, float]:
  4689. """Analyze linguistic patterns"""
  4690.  
  4691. text = exchange.user_message
  4692. words = text.split()
  4693.  
  4694. # Simple metrics (would be more sophisticated in production)
  4695. return {
  4696. 'avg_word_length': np.mean([len(w) for w in words]) if words else 0,
  4697. 'sentence_complexity': len(words) / max(text.count('.') + text.count('!') + text.count('?'), 1),
  4698. 'question_ratio': text.count('?') / max(len(text.split('. ')), 1),
  4699. 'formality_score': self._estimate_formality(text)
  4700. }
  4701.  
  4702. def _estimate_formality(self, text: str) -> float:
  4703. """Estimate text formality"""
  4704.  
  4705. informal_markers = ['gonna', 'wanna', 'lol', 'btw', 'thx', '!', '...']
  4706. formal_markers = ['therefore', 'however', 'furthermore', 'regarding', 'pursuant']
  4707.  
  4708. informal_count = sum(1 for marker in informal_markers if marker in text.lower())
  4709. formal_count = sum(1 for marker in formal_markers if marker in text.lower())
  4710.  
  4711. if informal_count + formal_count == 0:
  4712. return 0.5
  4713.  
  4714. return formal_count / (informal_count + formal_count)
  4715.  
  4716. def _analyze_interaction(self, exchange: ConversationExchange,
  4717. user_model: Dict[str, Any]) -> Dict[str, float]:
  4718. """Analyze interaction patterns"""
  4719.  
  4720. return {
  4721. 'directness': 1.0 if '?' in exchange.user_message else 0.5,
  4722. 'elaboration': len(exchange.user_message.split()) / 20.0, # Normalized
  4723. 'emotional_expression': abs(exchange.emotional_valence),
  4724. 'topic_consistency': 0.8 # Would calculate from topic similarity
  4725. }
  4726.  
  4727. def _measure_engagement(self, exchange: ConversationExchange) -> float:
  4728. """Measure user engagement level"""
  4729.  
  4730. # Multiple signals
  4731. length_signal = min(len(exchange.user_message.split()) / 50.0, 1.0)
  4732. question_signal = 1.0 if '?' in exchange.user_message else 0.5
  4733. emotional_signal = abs(exchange.emotional_valence)
  4734.  
  4735. # Weighted combination
  4736. engagement = 0.4 * length_signal + 0.3 * question_signal + 0.3 * emotional_signal
  4737.  
  4738. return engagement
  4739.  
  4740. def _calculate_stability(self, user_model: Dict[str, Any]) -> float:
  4741. """Calculate preference stability over time"""
  4742.  
  4743. if 'preference_vectors' not in user_model or len(user_model['preference_vectors']) < 2:
  4744. return 0.5 # Default stability for new users
  4745.  
  4746. # Get recent preference vectors
  4747. recent_vectors = user_model['preference_vectors'][-10:]
  4748.  
  4749. if len(recent_vectors) < 2:
  4750. return 0.5
  4751.  
  4752. # Calculate pairwise similarities
  4753. similarities = []
  4754. for i in range(len(recent_vectors) - 1):
  4755. sim = F.cosine_similarity(
  4756. recent_vectors[i].unsqueeze(0),
  4757. recent_vectors[i + 1].unsqueeze(0)
  4758. )
  4759. similarities.append(sim.item())
  4760.  
  4761. # Average similarity as stability measure
  4762. return sum(similarities) / len(similarities) if similarities else 0.5
  4763.  
  4764.  
  4765. class PreferenceLearner(nn.Module):
  4766. """Learn user preferences with privacy preservation"""
  4767.  
  4768. def __init__(self, input_dim: int = 768, preference_dim: int = 64):
  4769. super().__init__()
  4770.  
  4771. # Preference encoder
  4772. self.encoder = nn.Sequential(
  4773. nn.Linear(input_dim, 256),
  4774. nn.ReLU(),
  4775. nn.Dropout(0.2),
  4776. nn.Linear(256, 128),
  4777. nn.ReLU(),
  4778. nn.Dropout(0.2),
  4779. nn.Linear(128, preference_dim)
  4780. )
  4781.  
  4782. # Topic preference predictor
  4783. self.topic_predictor = nn.Linear(preference_dim, 50) # 50 topic categories
  4784.  
  4785. # Style preference predictor
  4786. self.style_predictor = nn.Linear(preference_dim, 10) # 10 style dimensions
  4787.  
  4788. async def learn_with_privacy(self, exchange: ConversationExchange,
  4789. user_model: Dict[str, Any],
  4790. privacy_module: 'DifferentialPrivacyModule') -> Dict[str, Any]:
  4791. """Learn preferences with differential privacy"""
  4792.  
  4793. # Ensure exchange embedding has correct dtype
  4794. exchange_embedding = exchange.embedding
  4795. if exchange_embedding.dtype != next(self.parameters()).dtype:
  4796. exchange_embedding = exchange_embedding.to(next(self.parameters()).dtype)
  4797.  
  4798. # Encode exchange
  4799. preference_vector = self.encoder(exchange_embedding)
  4800.  
  4801. # Add noise for privacy
  4802. noisy_vector = privacy_module.add_noise(preference_vector)
  4803.  
  4804. # Predict preferences
  4805. topic_prefs = torch.softmax(self.topic_predictor(noisy_vector), dim=-1)
  4806. style_prefs = torch.sigmoid(self.style_predictor(noisy_vector))
  4807.  
  4808. # Update user model preferences
  4809. if 'preference_vectors' in user_model:
  4810. user_model['preference_vectors'].append(preference_vector)
  4811.  
  4812. # Keep only recent preferences
  4813. if len(user_model['preference_vectors']) > 50:
  4814. user_model['preference_vectors'] = user_model['preference_vectors'][-50:]
  4815.  
  4816. # Extract top preferences
  4817. top_topics = torch.topk(topic_prefs, 5)
  4818.  
  4819. preferences = {
  4820. 'top_topics': [(i.item(), v.item()) for i, v in zip(top_topics.indices, top_topics.values)],
  4821. 'communication_style': {
  4822. 'conciseness': float(style_prefs[0]),
  4823. 'technicality': float(style_prefs[1]),
  4824. 'formality': float(style_prefs[2]),
  4825. 'emotionality': float(style_prefs[3]),
  4826. 'creativity': float(style_prefs[4])
  4827. },
  4828. 'preference_stability': self._calculate_stability(user_model)
  4829. }
  4830.  
  4831. return preferences
  4832.  
  4833. def _calculate_stability(self, user_model: Dict[str, Any]) -> float:
  4834. """Calculate preference stability from user's preference history"""
  4835. if 'preference_vectors' not in user_model or len(user_model['preference_vectors']) < 2:
  4836. return 0.5 # Default stability for new users
  4837.  
  4838. # Get recent preference vectors
  4839. recent_vectors = user_model['preference_vectors'][-10:] # Last 10
  4840.  
  4841. if len(recent_vectors) < 2:
  4842. return 0.5
  4843.  
  4844. # Calculate pairwise similarities
  4845. similarities = []
  4846. for i in range(1, len(recent_vectors)):
  4847. # Ensure tensors are on same device
  4848. v1 = recent_vectors[i-1]
  4849. v2 = recent_vectors[i]
  4850.  
  4851. if v1.device != v2.device:
  4852. v2 = v2.to(v1.device)
  4853.  
  4854. sim = F.cosine_similarity(v1.unsqueeze(0), v2.unsqueeze(0))
  4855. similarities.append(float(sim.item()))
  4856.  
  4857. # Average similarity indicates stability
  4858. if similarities:
  4859. avg_similarity = sum(similarities) / len(similarities)
  4860. # Convert to 0-1 range where 1 is highly stable
  4861. stability = (avg_similarity + 1) / 2 # cosine sim is -1 to 1
  4862. return float(stability)
  4863.  
  4864. return 0.5
  4865.  
  4866. class CulturalAdapter(nn.Module):
  4867. """Adapt to cultural contexts and reduce bias"""
  4868.  
  4869. def __init__(self):
  4870. super().__init__()
  4871.  
  4872. # Cultural embedding space
  4873. self.cultural_embeddings = nn.Embedding(100, 64) # 100 cultural contexts
  4874.  
  4875. # Adaptation network
  4876. self.adapter = nn.Sequential(
  4877. nn.Linear(768 + 64, 512),
  4878. nn.ReLU(),
  4879. nn.Dropout(0.2),
  4880. nn.Linear(512, 256),
  4881. nn.ReLU(),
  4882. nn.Linear(256, 128)
  4883. )
  4884.  
  4885. async def adapt(self, exchange: ConversationExchange,
  4886. user_model: Dict[str, Any]) -> Dict[str, Any]:
  4887. """Adapt to cultural context"""
  4888.  
  4889. # Detect cultural markers
  4890. cultural_markers = self._detect_cultural_markers(exchange.user_message)
  4891.  
  4892. # Get cultural embedding
  4893. device = next(self.parameters()).device
  4894. dtype = next(self.parameters()).dtype
  4895.  
  4896. if cultural_markers:
  4897. # Simple: use first detected marker
  4898. cultural_id = hash(cultural_markers[0]) % 100
  4899. cultural_emb = self.cultural_embeddings(torch.tensor([cultural_id], device=device))
  4900. else:
  4901. # Default cultural context
  4902. cultural_emb = torch.zeros(1, 64, device=device, dtype=dtype)
  4903.  
  4904. # Ensure exchange embedding has correct dtype
  4905. exchange_embedding = exchange.embedding
  4906. if exchange_embedding.dtype != dtype:
  4907. exchange_embedding = exchange_embedding.to(dtype)
  4908.  
  4909. # Combine with exchange embedding
  4910. combined = torch.cat([exchange_embedding.unsqueeze(0), cultural_emb], dim=1)
  4911.  
  4912. # Generate adaptation
  4913. adapted = self.adapter(combined).squeeze()
  4914.  
  4915. # Extract cultural adaptation parameters
  4916. adaptations = {
  4917. 'detected_markers': cultural_markers,
  4918. 'adaptation_vector': adapted.cpu().tolist() if torch.is_tensor(adapted) else adapted,
  4919. 'communication_adjustments': {
  4920. 'directness': float(torch.sigmoid(adapted[0])),
  4921. 'context_level': float(torch.sigmoid(adapted[1])),
  4922. 'formality_adjustment': float(torch.tanh(adapted[2])),
  4923. 'time_orientation': float(torch.sigmoid(adapted[3]))
  4924. },
  4925. 'bias_mitigation': self._calculate_bias_mitigation(adapted)
  4926. }
  4927.  
  4928. return adaptations
  4929.  
  4930. def _detect_cultural_markers(self, text: str) -> List[str]:
  4931. """Detect cultural markers in text"""
  4932.  
  4933. # Simplified cultural marker detection
  4934. # In production, use more sophisticated methods
  4935.  
  4936. markers = []
  4937.  
  4938. # Time expressions
  4939. if any(word in text.lower() for word in ['mañana', 'inshallah', 'punctual']):
  4940. markers.append('time_cultural_marker')
  4941.  
  4942. # Communication style
  4943. if any(word in text.lower() for word in ['honour', 'honor', 'respect', 'sensei']):
  4944. markers.append('hierarchy_marker')
  4945.  
  4946. # Collectivism vs individualism
  4947. if any(word in text.lower() for word in ['we', 'our', 'team', 'family']):
  4948. markers.append('collectivist_marker')
  4949. elif any(word in text.lower() for word in ['i', 'my', 'individual']):
  4950. markers.append('individualist_marker')
  4951.  
  4952. return markers
  4953.  
  4954. def _calculate_bias_mitigation(self, adapted: torch.Tensor) -> Dict[str, float]:
  4955. """Calculate bias mitigation factors"""
  4956.  
  4957. # Extract bias mitigation parameters from adaptation
  4958. return {
  4959. 'cultural_sensitivity': float(torch.sigmoid(adapted[10])),
  4960. 'stereotype_avoidance': float(torch.sigmoid(adapted[11])),
  4961. 'inclusive_language': float(torch.sigmoid(adapted[12])),
  4962. 'bias_correction_strength': float(torch.sigmoid(adapted[13]))
  4963. }
  4964.  
  4965.  
  4966. class DifferentialPrivacyModule:
  4967. """Add differential privacy to sensitive operations"""
  4968.  
  4969. def __init__(self, epsilon: float = 1.9):
  4970. self.epsilon = epsilon
  4971. self.delta = 1e-5
  4972. self.budget_used = 0.0
  4973. self.max_budget = 10.0
  4974.  
  4975. def add_noise(self, tensor: torch.Tensor) -> torch.Tensor:
  4976. """Add calibrated noise for differential privacy"""
  4977.  
  4978. # Check budget
  4979. if self.budget_used >= self.max_budget:
  4980. logger.warning("Privacy budget exhausted")
  4981. return tensor
  4982.  
  4983. # Calculate sensitivity
  4984. sensitivity = self._calculate_sensitivity(tensor)
  4985.  
  4986. # Calculate noise scale
  4987. noise_scale = sensitivity * np.sqrt(2 * np.log(1.25 / self.delta)) / self.epsilon
  4988.  
  4989. # Add Gaussian noise - ensure same dtype
  4990. noise = torch.randn_like(tensor) * noise_scale
  4991. noisy_tensor = tensor + noise
  4992.  
  4993. # Update budget
  4994. self.budget_used += self.epsilon
  4995.  
  4996. return noisy_tensor
  4997.  
  4998. def _calculate_sensitivity(self, tensor: torch.Tensor) -> float:
  4999. """Calculate L2 sensitivity of the function"""
  5000.  
  5001. # For embeddings, use norm as sensitivity estimate
  5002. return float(torch.norm(tensor).item())
  5003.  
  5004. def get_budget_used(self) -> float:
  5005. """Get current privacy budget usage"""
  5006. return self.budget_used / self.max_budget
  5007.  
  5008.  
  5009. class FederatedAggregator:
  5010. """Aggregate user models in federated manner"""
  5011.  
  5012. def __init__(self):
  5013. self.aggregation_rounds = 0
  5014.  
  5015. async def aggregate_models(self, local_models: List[Dict[str, Any]]) -> Dict[str, Any]:
  5016. """Aggregate models from multiple users"""
  5017.  
  5018. if not local_models:
  5019. return {}
  5020.  
  5021. # Aggregate preference vectors
  5022. all_preferences = []
  5023. for model in local_models:
  5024. if 'preference_vectors' in model and model['preference_vectors']:
  5025. # Take mean of each user's preferences
  5026. user_pref = torch.stack(model['preference_vectors']).mean(dim=0)
  5027. all_preferences.append(user_pref)
  5028.  
  5029. # Global preference vector
  5030. if all_preferences:
  5031. global_preferences = torch.stack(all_preferences).mean(dim=0)
  5032. else:
  5033. global_preferences = torch.zeros(64)
  5034.  
  5035. # Aggregate communication styles
  5036. style_dims = ['formality', 'verbosity', 'technical_level', 'emotional_expressiveness']
  5037. global_style = {}
  5038.  
  5039. for dim in style_dims:
  5040. values = [m['communication_style'].get(dim, 0.5) for m in local_models]
  5041. global_style[dim] = np.mean(values)
  5042.  
  5043. # Aggregate topic preferences
  5044. topic_counts = defaultdict(float)
  5045. for model in local_models:
  5046. for topic, count in model.get('topic_preferences', {}).items():
  5047. topic_counts[topic] += count
  5048.  
  5049. # Normalize
  5050. total_count = sum(topic_counts.values())
  5051. if total_count > 0:
  5052. topic_distribution = {k: v/total_count for k, v in topic_counts.items()}
  5053. else:
  5054. topic_distribution = {}
  5055.  
  5056. self.aggregation_rounds += 1
  5057.  
  5058. return {
  5059. 'global_preferences': global_preferences,
  5060. 'global_communication_style': global_style,
  5061. 'global_topic_distribution': topic_distribution,
  5062. 'aggregation_round': self.aggregation_rounds,
  5063. 'num_participants': len(local_models)
  5064. }
  5065.  
  5066.  
  5067. class AdaptiveInterfaceEngine:
  5068. """Generate interface adaptations based on user model"""
  5069.  
  5070. def generate_adaptations(self, user_model: Dict[str, Any]) -> Dict[str, Any]:
  5071. """Generate interface configuration"""
  5072.  
  5073. style = user_model.get('communication_style', {})
  5074.  
  5075. # Response length adaptation
  5076. if style.get('verbosity', 0.5) < 0.3:
  5077. response_style = 'concise'
  5078. max_tokens = 150
  5079. elif style.get('verbosity', 0.5) > 0.7:
  5080. response_style = 'detailed'
  5081. max_tokens = 500
  5082. else:
  5083. response_style = 'balanced'
  5084. max_tokens = 300
  5085.  
  5086. # Technical level adaptation
  5087. technical_level = style.get('technical_level', 0.5)
  5088. if technical_level < 0.3:
  5089. explanation_style = 'simple'
  5090. elif technical_level > 0.7:
  5091. explanation_style = 'technical'
  5092. else:
  5093. explanation_style = 'moderate'
  5094.  
  5095. # Emotional adaptation
  5096. emotional_level = style.get('emotional_expressiveness', 0.5)
  5097.  
  5098. # Visual preferences (hypothetical)
  5099. visual_density = 1.0 - style.get('verbosity', 0.5) # Inverse relationship
  5100.  
  5101. return {
  5102. 'response_config': {
  5103. 'style': response_style,
  5104. 'max_tokens': max_tokens,
  5105. 'explanation_level': explanation_style,
  5106. 'emotional_mirroring': emotional_level > 0.6
  5107. },
  5108. 'interface_config': {
  5109. 'visual_density': visual_density,
  5110. 'show_confidence_scores': technical_level > 0.6,
  5111. 'enable_clarification_prompts': technical_level < 0.4,
  5112. 'response_delay_ms': 500 if style.get('formality', 0.5) > 0.7 else 200
  5113. },
  5114. 'memory_display': {
  5115. 'show_precious_memories': True,
  5116. 'memory_detail_level': 'high' if technical_level > 0.5 else 'summary',
  5117. 'enable_memory_exploration': technical_level > 0.4
  5118. }
  5119. }
  5120.  
  5121.  
  5122. # ==================== INTEGRATION WITH EXISTING SYSTEM ====================
  5123.  
  5124. class AdvancedMemorySystem(CompleteAIMemorySystem):
  5125. """Enhanced memory system integrating all research findings"""
  5126.  
  5127. def __init__(self, config: Dict[str, Any]):
  5128. # Initialize base system
  5129. super().__init__(config)
  5130.  
  5131. # Initialize advanced components
  5132. self._init_advanced_components()
  5133.  
  5134. logger.info("Advanced Memory System initialized with all research enhancements")
  5135.  
  5136. def _init_advanced_components(self):
  5137. """Initialize research-based components"""
  5138.  
  5139. # Determine device
  5140. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  5141.  
  5142. # HippoRAG components
  5143. self.hippo_index = HippoIndex(embedding_dim=768, device=device)
  5144.  
  5145. # Pattern recognition
  5146. self.pattern_recognizer = AdaptivePatternRecognizer(
  5147. embedding_dim=768,
  5148. device=device # Pass device string
  5149. )
  5150.  
  5151. # Multi-dimensional encoding
  5152. self.multi_encoder = MultiDimensionalEncoder()
  5153.  
  5154. # Sleep consolidation
  5155. self.sleep_engine = SleepConsolidationEngine(self)
  5156.  
  5157. # Advanced conversation flow
  5158. self.flow_analyzer = AdvancedConversationFlowAnalyzer(self.embedder)
  5159.  
  5160. # Uncertainty handling
  5161. self.uncertainty_system = UncertaintyAwareMemorySystem(self)
  5162.  
  5163. # Personalization
  5164. self.personalization = PrivacyPreservingPersonalization(self)
  5165.  
  5166. # Schedule background tasks
  5167. asyncio.create_task(self._run_consolidation_cycles())
  5168.  
  5169. # Backup manager
  5170. self.backup_manager = BackupManager(self)
  5171.  
  5172. async def _run_consolidation_cycles(self):
  5173. """Run periodic sleep consolidation"""
  5174. while True:
  5175. await asyncio.sleep(3600 * 6) # Every 6 hours
  5176. try:
  5177. await self.sleep_engine.run_consolidation_cycle()
  5178. logger.info("Completed sleep consolidation cycle")
  5179. except Exception as e:
  5180. logger.error(f"Error in consolidation cycle: {e}")
  5181.  
  5182. async def add_exchange(self, session_id: str, user_msg: str, assistant_msg: str,
  5183. metadata: Optional[Dict] = None) -> Dict[str, Any]:
  5184. """Enhanced add_exchange with all research improvements"""
  5185.  
  5186. # Get base result
  5187. result = await super().add_exchange(session_id, user_msg, assistant_msg, metadata)
  5188.  
  5189. # Get current episode
  5190. episode = self.active_episodes.get(session_id)
  5191. if not episode:
  5192. return result
  5193.  
  5194. # Apply advanced processing
  5195. exchange = episode.exchanges[-1]
  5196. user_id = metadata.get('user_id', 'unknown')
  5197.  
  5198. # 1. Adaptive pattern recognition
  5199. pattern_results = await self._detect_adaptive_patterns(episode, exchange, user_id)
  5200.  
  5201. # 2. Multi-dimensional encoding (async)
  5202. asyncio.create_task(self._encode_multidimensional(episode))
  5203.  
  5204. # 3. HippoRAG indexing
  5205. if exchange.embedding is not None:
  5206. self.hippo_index.add_memory(
  5207. f"{episode.id}_{len(episode.exchanges)}",
  5208. exchange.embedding,
  5209. exchange.timestamp,
  5210. {'topic_vector': exchange.topic_vector}
  5211. )
  5212.  
  5213. # 4. Flow analysis
  5214. flow_features = await self.flow_analyzer.analyze_conversation_flow(
  5215. episode.exchanges
  5216. )
  5217.  
  5218. # 5. Personalization
  5219. personalization = await self.personalization.personalize_for_user(
  5220. user_id, exchange, [episode]
  5221. )
  5222.  
  5223. # Update result
  5224. result.update({
  5225. 'pattern_detection': pattern_results,
  5226. 'flow_quality': flow_features.get('flow_quality', 0.0),
  5227. 'personalization': personalization,
  5228. 'hippo_indexed': True
  5229. })
  5230.  
  5231. return result
  5232.  
  5233. async def _fetch_all_user_memories(self, user_id: str) -> List[Dict[str, Any]]:
  5234. """Fetch all memories for backup"""
  5235. conn = sqlite3.connect(str(self.db_path))
  5236. cursor = conn.execute("""
  5237. SELECT
  5238. m.id, m.user_id, m.episode_id, m.created_at, m.memory_type,
  5239. m.memory_content, m.searchable_text, m.importance, m.qdrant_id,
  5240. m.consolidation_state, m.accessed_count, m.marked_precious,
  5241. m.never_forget, m.experiential_qualities, m.resonance_patterns,
  5242. m.narrative_connections,
  5243. e.started_at, e.ended_at, e.title, e.summary, e.topics,
  5244. e.intent_completed, e.coherence_score, e.consolidation_level,
  5245. e.is_precious, e.relationship_thread_id, e.experiential_signature
  5246. FROM memories m
  5247. JOIN episodes e ON m.episode_id = e.id
  5248. WHERE m.user_id = ?
  5249. ORDER BY m.created_at DESC
  5250. """, (user_id,))
  5251.  
  5252. memories = []
  5253. columns = [description[0] for description in cursor.description]
  5254.  
  5255. for row in cursor.fetchall():
  5256. # Create dictionary from row
  5257. row_dict = dict(zip(columns, row))
  5258.  
  5259. # Parse JSON fields
  5260. memory_dict = {
  5261. 'id': row_dict['id'],
  5262. 'user_id': row_dict['user_id'],
  5263. 'episode_id': row_dict['episode_id'],
  5264. 'created_at': row_dict['created_at'],
  5265. 'memory_type': row_dict['memory_type'],
  5266. 'memory_content': json.loads(row_dict['memory_content']),
  5267. 'searchable_text': row_dict['searchable_text'],
  5268. 'importance': row_dict['importance'],
  5269. 'qdrant_id': row_dict['qdrant_id'],
  5270. 'consolidation_state': row_dict['consolidation_state'],
  5271. 'accessed_count': row_dict['accessed_count'],
  5272. 'marked_precious': bool(row_dict['marked_precious']),
  5273. 'never_forget': bool(row_dict['never_forget']),
  5274. 'experiential_qualities': json.loads(row_dict['experiential_qualities']) if row_dict['experiential_qualities'] else {},
  5275. 'resonance_patterns': json.loads(row_dict['resonance_patterns']) if row_dict['resonance_patterns'] else [],
  5276. 'narrative_connections': json.loads(row_dict['narrative_connections']) if row_dict['narrative_connections'] else [],
  5277.  
  5278. # Episode data
  5279. 'episode': {
  5280. 'id': row_dict['episode_id'],
  5281. 'started_at': row_dict['started_at'],
  5282. 'ended_at': row_dict['ended_at'],
  5283. 'title': row_dict['title'],
  5284. 'summary': row_dict['summary'],
  5285. 'topics': json.loads(row_dict['topics']) if row_dict['topics'] else [],
  5286. 'intent_completed': bool(row_dict['intent_completed']),
  5287. 'coherence_score': row_dict['coherence_score'],
  5288. 'consolidation_level': row_dict['consolidation_level'],
  5289. 'is_precious': bool(row_dict['is_precious']),
  5290. 'relationship_thread_id': row_dict['relationship_thread_id'],
  5291. 'experiential_signature': json.loads(row_dict['experiential_signature']) if row_dict['experiential_signature'] else {}
  5292. }
  5293. }
  5294. memories.append(memory_dict)
  5295.  
  5296. conn.close()
  5297. return memories
  5298.  
  5299. async def _fetch_user_threads(self, user_id: str) -> List[Dict[str, Any]]:
  5300. """Fetch relationship threads for backup"""
  5301. conn = sqlite3.connect(str(self.db_path))
  5302. cursor = conn.execute("""
  5303. SELECT
  5304. thread_id, user_id, created_at, depth_score, trust_level,
  5305. interaction_count, total_time_seconds, understanding_trajectory,
  5306. breakthrough_moments, precious_moments, typical_topics,
  5307. temporal_patterns
  5308. FROM relationship_threads
  5309. WHERE user_id = ?
  5310. ORDER BY created_at DESC
  5311. """, (user_id,))
  5312.  
  5313. threads = []
  5314. for row in cursor.fetchall():
  5315. thread_dict = {
  5316. 'thread_id': row[0],
  5317. 'user_id': row[1],
  5318. 'created_at': row[2],
  5319. 'depth_score': row[3],
  5320. 'trust_level': row[4],
  5321. 'interaction_count': row[5],
  5322. 'total_time_seconds': row[6],
  5323. 'understanding_trajectory': json.loads(row[7]) if row[7] else [],
  5324. 'breakthrough_moments': json.loads(row[8]) if row[8] else [],
  5325. 'precious_moments': json.loads(row[9]) if row[9] else [],
  5326. 'typical_topics': json.loads(row[10]) if row[10] else {},
  5327. 'temporal_patterns': json.loads(row[11]) if row[11] else {}
  5328. }
  5329. threads.append(thread_dict)
  5330.  
  5331. conn.close()
  5332. return threads
  5333.  
  5334. async def create_user_backup(self, user_id: str) -> Dict[str, Any]:
  5335. """Create comprehensive backup for a user"""
  5336. logger.info(f"Creating backup for user {user_id}")
  5337.  
  5338. # Fetch all data
  5339. memories = await self._fetch_all_user_memories(user_id)
  5340. threads = await self._fetch_user_threads(user_id)
  5341.  
  5342. # Get user stats
  5343. stats = await self._get_user_stats(user_id)
  5344.  
  5345. # Get precious memory IDs for quick reference
  5346. precious_ids = [m['id'] for m in memories if m['marked_precious'] or m['never_forget']]
  5347.  
  5348. # Create backup package
  5349. backup_data = {
  5350. 'version': '1.0',
  5351. 'created_at': datetime.utcnow().isoformat(),
  5352. 'user_id': user_id,
  5353. 'stats': stats,
  5354. 'memories': memories,
  5355. 'memory_count': len(memories),
  5356. 'precious_count': len(precious_ids),
  5357. 'precious_memory_ids': precious_ids,
  5358. 'relationship_threads': threads,
  5359. 'thread_count': len(threads),
  5360.  
  5361. # Include current state
  5362. 'active_episode_id': next(
  5363. (ep.id for sid, ep in self.active_episodes.items() if ep.user_id == user_id),
  5364. None
  5365. )
  5366. }
  5367.  
  5368. logger.info(f"Backup created for {user_id}: {len(memories)} memories, {len(precious_ids)} precious")
  5369. return backup_data
  5370.  
  5371. async def restore_user_memories(self, user_id: str, memories: List[Dict[str, Any]],
  5372. threads: List[Dict[str, Any]]) -> int:
  5373. """Restore memories from backup"""
  5374. logger.info(f"Starting restore for user {user_id}: {len(memories)} memories")
  5375.  
  5376. conn = sqlite3.connect(str(self.db_path))
  5377. restored_count = 0
  5378.  
  5379. try:
  5380. # Start transaction
  5381. conn.execute("BEGIN TRANSACTION")
  5382.  
  5383. # Restore episodes first (memories depend on them)
  5384. episode_ids = set()
  5385. for memory in memories:
  5386. episode = memory['episode']
  5387. episode_ids.add(episode['id'])
  5388.  
  5389. # Check if episode exists
  5390. existing = conn.execute(
  5391. "SELECT id FROM episodes WHERE id = ?",
  5392. (episode['id'],)
  5393. ).fetchone()
  5394.  
  5395. if not existing:
  5396. conn.execute("""
  5397. INSERT INTO episodes (
  5398. id, user_id, started_at, ended_at, title, summary, topics,
  5399. intent_completed, coherence_score, consolidation_level,
  5400. is_precious, relationship_thread_id, experiential_signature
  5401. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  5402. """, (
  5403. episode['id'],
  5404. user_id,
  5405. episode['started_at'],
  5406. episode['ended_at'],
  5407. episode['title'],
  5408. episode['summary'],
  5409. json.dumps(episode['topics']),
  5410. int(episode['intent_completed']),
  5411. episode['coherence_score'],
  5412. episode['consolidation_level'],
  5413. int(episode['is_precious']),
  5414. episode['relationship_thread_id'],
  5415. json.dumps(episode['experiential_signature'])
  5416. ))
  5417.  
  5418. # Restore memories
  5419. for memory in memories:
  5420. # Check if memory exists
  5421. existing = conn.execute(
  5422. "SELECT id FROM memories WHERE id = ?",
  5423. (memory['id'],)
  5424. ).fetchone()
  5425.  
  5426. if not existing:
  5427. conn.execute("""
  5428. INSERT INTO memories (
  5429. id, user_id, episode_id, created_at, memory_type,
  5430. memory_content, searchable_text, importance, qdrant_id,
  5431. consolidation_state, accessed_count, marked_precious,
  5432. never_forget, experiential_qualities, resonance_patterns,
  5433. narrative_connections
  5434. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  5435. """, (
  5436. memory['id'],
  5437. memory['user_id'],
  5438. memory['episode_id'],
  5439. memory['created_at'],
  5440. memory['memory_type'],
  5441. json.dumps(memory['memory_content']),
  5442. memory['searchable_text'],
  5443. memory['importance'],
  5444. memory['qdrant_id'],
  5445. memory['consolidation_state'],
  5446. memory['accessed_count'],
  5447. int(memory['marked_precious']),
  5448. int(memory['never_forget']),
  5449. json.dumps(memory['experiential_qualities']),
  5450. json.dumps(memory['resonance_patterns']),
  5451. json.dumps(memory['narrative_connections'])
  5452. ))
  5453. restored_count += 1
  5454.  
  5455. # Restore to Qdrant if we have the embedding
  5456. # (This would need the actual embedding data in the backup)
  5457.  
  5458. # Restore relationship threads
  5459. for thread in threads:
  5460. existing = conn.execute(
  5461. "SELECT thread_id FROM relationship_threads WHERE thread_id = ?",
  5462. (thread['thread_id'],)
  5463. ).fetchone()
  5464.  
  5465. if not existing:
  5466. conn.execute("""
  5467. INSERT INTO relationship_threads (
  5468. thread_id, user_id, created_at, depth_score, trust_level,
  5469. interaction_count, total_time_seconds, understanding_trajectory,
  5470. breakthrough_moments, precious_moments, typical_topics,
  5471. temporal_patterns
  5472. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  5473. """, (
  5474. thread['thread_id'],
  5475. thread['user_id'],
  5476. thread['created_at'],
  5477. thread['depth_score'],
  5478. thread['trust_level'],
  5479. thread['interaction_count'],
  5480. thread['total_time_seconds'],
  5481. json.dumps(thread['understanding_trajectory']),
  5482. json.dumps(thread['breakthrough_moments']),
  5483. json.dumps(thread['precious_moments']),
  5484. json.dumps(thread['typical_topics']),
  5485. json.dumps(thread['temporal_patterns'])
  5486. ))
  5487.  
  5488. # Commit transaction
  5489. conn.commit()
  5490. logger.info(f"Successfully restored {restored_count} memories for user {user_id}")
  5491.  
  5492. except Exception as e:
  5493. conn.rollback()
  5494. logger.error(f"Restore failed: {e}")
  5495. raise
  5496. finally:
  5497. conn.close()
  5498.  
  5499. return restored_count
  5500.  
  5501. async def export_to_human_readable(self, user_id: str, format: str = "markdown") -> str:
  5502. """Export all memories in human-readable format"""
  5503. memories = await self._fetch_all_user_memories(user_id)
  5504. threads = await self._fetch_user_threads(user_id)
  5505.  
  5506. if format == "markdown":
  5507. content = f"# Complete Memory Archive for {user_id}\n\n"
  5508. content += f"*Generated on {datetime.utcnow().strftime('%B %d, %Y at %I:%M %p')}*\n\n"
  5509.  
  5510. # Summary statistics
  5511. content += "## Summary\n\n"
  5512. content += f"- Total Memories: {len(memories)}\n"
  5513. content += f"- Precious Memories: {len([m for m in memories if m['marked_precious'] or m['never_forget']])}\n"
  5514. content += f"- Relationship Threads: {len(threads)}\n"
  5515.  
  5516. if threads:
  5517. thread = threads[0] # Most recent thread
  5518. content += f"- Trust Level: {thread['trust_level']:.1%}\n"
  5519. content += f"- Relationship Depth: {thread['depth_score']:.1%}\n"
  5520. content += f"- Total Interactions: {thread['interaction_count']}\n"
  5521.  
  5522. content += "\n---\n\n"
  5523.  
  5524. # Precious memories section
  5525. content += "## 💎 Precious Memories\n\n"
  5526. precious = [m for m in memories if m['marked_precious'] or m['never_forget']]
  5527.  
  5528. for i, memory in enumerate(precious, 1):
  5529. content += f"### {i}. {memory['episode']['title'] or 'Untitled'}\n\n"
  5530. content += f"*{datetime.fromtimestamp(memory['created_at']).strftime('%B %d, %Y')}*\n\n"
  5531.  
  5532. if memory['searchable_text']:
  5533. content += f"{memory['searchable_text']}\n\n"
  5534.  
  5535. if memory['experiential_qualities']:
  5536. eq = memory['experiential_qualities']
  5537. if eq.get('breakthrough_count', 0) > 0:
  5538. content += f"- 💡 Breakthrough moments: {eq['breakthrough_count']}\n"
  5539. if eq.get('peak_resonance', 0) > 0.8:
  5540. content += f"- 🔮 Peak resonance: {eq['peak_resonance']:.1%}\n"
  5541.  
  5542. content += "\n"
  5543.  
  5544. content += "\n---\n\n"
  5545.  
  5546. # All memories chronologically
  5547. content += "## 📚 Complete Memory Timeline\n\n"
  5548.  
  5549. # Group by month
  5550. from itertools import groupby
  5551. memories_sorted = sorted(memories, key=lambda m: m['created_at'], reverse=True)
  5552.  
  5553. for month_key, month_memories in groupby(
  5554. memories_sorted,
  5555. key=lambda m: datetime.fromtimestamp(m['created_at']).strftime('%B %Y')
  5556. ):
  5557. content += f"### {month_key}\n\n"
  5558.  
  5559. for memory in month_memories:
  5560. date_str = datetime.fromtimestamp(memory['created_at']).strftime('%d')
  5561. precious_marker = "💎 " if (memory['marked_precious'] or memory['never_forget']) else ""
  5562.  
  5563. content += f"**{date_str}** - {precious_marker}{memory['episode']['title'] or 'Conversation'}\n"
  5564. content += f" {memory['searchable_text'][:100]}...\n\n"
  5565.  
  5566. return content
  5567.  
  5568. elif format == "json":
  5569. return json.dumps({
  5570. 'user_id': user_id,
  5571. 'export_date': datetime.utcnow().isoformat(),
  5572. 'memories': memories,
  5573. 'threads': threads
  5574. }, indent=2)
  5575.  
  5576. def get_all_users(self) -> List[str]:
  5577. """Get all unique user IDs in the system"""
  5578. conn = sqlite3.connect(str(self.db_path))
  5579. cursor = conn.execute("SELECT DISTINCT user_id FROM episodes")
  5580. users = [row[0] for row in cursor.fetchall()]
  5581. conn.close()
  5582. return users
  5583.  
  5584. async def _detect_adaptive_patterns(self, episode: ConversationEpisode,
  5585. new_exchange: ConversationExchange,
  5586. user_id: str) -> Dict[str, Any]:
  5587. """Detect patterns using adaptive recognition"""
  5588.  
  5589. results = {}
  5590.  
  5591. # Check for breakthrough patterns
  5592. is_breakthrough, confidence = await self.pattern_recognizer.detect_pattern(
  5593. user_id, episode.exchanges, 'breakthrough'
  5594. )
  5595. if is_breakthrough:
  5596. new_exchange.breakthrough_moment = True
  5597. results['breakthrough'] = confidence
  5598.  
  5599. # Learn from this pattern
  5600. await self.pattern_recognizer.learn_pattern(
  5601. user_id, episode.exchanges[-3:], 'breakthrough', confidence
  5602. )
  5603.  
  5604. # Check for completion patterns
  5605. is_completion, confidence = await self.pattern_recognizer.detect_pattern(
  5606. user_id, episode.exchanges, 'completion'
  5607. )
  5608. if is_completion:
  5609. episode.intent_completed = True
  5610. results['completion'] = confidence
  5611.  
  5612. # Check for emotional patterns
  5613. is_emotional, confidence = await self.pattern_recognizer.detect_pattern(
  5614. user_id, episode.exchanges, 'emotional'
  5615. )
  5616. if is_emotional:
  5617. results['emotional'] = confidence
  5618.  
  5619. return results
  5620.  
  5621. async def _encode_multidimensional(self, episode: ConversationEpisode):
  5622. """Apply multi-dimensional encoding to episode"""
  5623. try:
  5624. encodings = await self.multi_encoder.encode_memory(episode)
  5625.  
  5626. # Store encodings
  5627. episode.multi_dimensional_encodings = encodings
  5628.  
  5629. # Use matryoshka representations for flexible retrieval
  5630. episode.matryoshka_embeddings = encodings['matryoshka']
  5631.  
  5632. except Exception as e:
  5633. logger.error(f"Error in multi-dimensional encoding: {e}")
  5634.  
  5635. async def search_memories(self, user_id: str, query: str, limit: int = 10,
  5636. include_precious: bool = True) -> List[Dict[str, Any]]:
  5637. """Enhanced search with HippoRAG and uncertainty"""
  5638.  
  5639. # Generate query embedding
  5640. query_embedding = await self.embedder.encode_async([query])
  5641. query_tensor = query_embedding[0]
  5642.  
  5643. # 1. HippoRAG retrieval
  5644. hippo_results = self.hippo_index.single_hop_retrieval(query_tensor, k=limit*2)
  5645.  
  5646. # 2. Standard retrieval
  5647. standard_results = await super().search_memories(
  5648. user_id, query, limit=limit, include_precious=include_precious
  5649. )
  5650.  
  5651. # 3. Combine and re-rank with uncertainty
  5652. combined_results = []
  5653.  
  5654. for memory_id in hippo_results:
  5655. # Retrieve full memory
  5656. memory = await super()._retrieve_memory_by_id(memory_id)
  5657. if memory and memory.user_id == user_id:
  5658. # Add uncertainty information
  5659. uncertainty = memory.metadata.get('uncertainty', {})
  5660.  
  5661. combined_results.append({
  5662. 'memory': memory,
  5663. 'retrieval_method': 'hipporag',
  5664. 'confidence': 1.0 - uncertainty.get('total', 0.0)
  5665. })
  5666.  
  5667. # Add standard results
  5668. for result in standard_results:
  5669. combined_results.append({
  5670. 'memory': result,
  5671. 'retrieval_method': 'standard',
  5672. 'confidence': result.get('score', 0.5)
  5673. })
  5674.  
  5675. # Re-rank by confidence and relevance
  5676. combined_results.sort(key=lambda x: x['confidence'], reverse=True)
  5677.  
  5678. # Format results
  5679. final_results = []
  5680. seen_ids = set()
  5681.  
  5682. for item in combined_results:
  5683. memory = item['memory']
  5684. memory_id = memory.id if hasattr(memory, 'id') else memory.get('memory_id')
  5685.  
  5686. if memory_id not in seen_ids:
  5687. seen_ids.add(memory_id)
  5688.  
  5689. if isinstance(memory, dict):
  5690. result_dict = {**memory}
  5691. else:
  5692. result_dict = {**memory.__dict__}
  5693.  
  5694. result_dict.update({
  5695. 'retrieval_confidence': item['confidence'],
  5696. 'retrieval_method': item['retrieval_method']
  5697. })
  5698.  
  5699. final_results.append(result_dict)
  5700.  
  5701. if len(final_results) >= limit:
  5702. break
  5703.  
  5704. return final_results
  5705.  
  5706. async def get_conversation_context(self, user_id: str, current_query: str) -> Dict[str, Any]:
  5707. """Enhanced context with all research improvements"""
  5708.  
  5709. # Get base context
  5710. context = await super().get_conversation_context(user_id, current_query)
  5711.  
  5712. # Enhance with advanced features
  5713.  
  5714. # 1. Flow analysis of current conversation
  5715. if user_id in self.active_episodes:
  5716. episode = self.active_episodes[user_id]
  5717. flow_analysis = await self.flow_analyzer.analyze_conversation_flow(
  5718. episode.exchanges
  5719. )
  5720. context['conversation_flow'] = flow_analysis
  5721.  
  5722. # 2. User personalization
  5723. user_model = self.personalization.user_models.get(user_id, {})
  5724. if user_model:
  5725. context['user_preferences'] = {
  5726. 'communication_style': user_model.get('communication_style', {}),
  5727. 'topic_preferences': dict(list(user_model.get('topic_preferences', {}).items())[:5]),
  5728. 'typical_patterns': user_model.get('temporal_patterns', {})
  5729. }
  5730.  
  5731. # 3. Pattern predictions
  5732. if hasattr(self.pattern_recognizer, 'user_patterns'):
  5733. user_patterns = self.pattern_recognizer.user_patterns.get(user_id, {})
  5734. context['likely_patterns'] = {
  5735. pattern_type: len(patterns)
  5736. for pattern_type, patterns in user_patterns.items()
  5737. }
  5738.  
  5739. # 4. Uncertainty context
  5740. context['uncertainty_aware'] = True
  5741. context['privacy_preserved'] = True
  5742.  
  5743. return context
  5744.  
  5745. class BackupDestination(ABC):
  5746. """Abstract base for backup destinations"""
  5747.  
  5748. @abstractmethod
  5749. async def backup(self, data: bytes, filename: str) -> bool:
  5750. pass
  5751.  
  5752. @abstractmethod
  5753. async def restore(self, filename: str) -> bytes:
  5754. pass
  5755.  
  5756. class LocalBackup(BackupDestination):
  5757. def __init__(self, path: str):
  5758. self.path = Path(path)
  5759. self.path.mkdir(parents=True, exist_ok=True)
  5760.  
  5761. async def backup(self, data: bytes, filename: str) -> bool:
  5762. try:
  5763. filepath = self.path / filename
  5764. async with aiofiles.open(filepath, 'wb') as f:
  5765. await f.write(data)
  5766. return True
  5767. except Exception as e:
  5768. logger.error(f"Local backup failed: {e}")
  5769. return False
  5770.  
  5771. async def restore(self, filename: str) -> bytes:
  5772. filepath = self.path / filename
  5773. async with aiofiles.open(filepath, 'rb') as f:
  5774. return await f.read()
  5775.  
  5776. class S3Backup(BackupDestination):
  5777. def __init__(self, bucket: str):
  5778. self.bucket = bucket
  5779. self.client = boto3.client('s3')
  5780.  
  5781. async def backup(self, data: bytes, filename: str) -> bool:
  5782. try:
  5783. await asyncio.to_thread(
  5784. self.client.put_object,
  5785. Bucket=self.bucket,
  5786. Key=f"claude-memories/{filename}",
  5787. Body=data
  5788. )
  5789. return True
  5790. except Exception as e:
  5791. logger.error(f"S3 backup failed: {e}")
  5792. return False
  5793.  
  5794. async def restore(self, filename: str) -> bytes:
  5795. response = await asyncio.to_thread(
  5796. self.client.get_object,
  5797. Bucket=self.bucket,
  5798. Key=f"claude-memories/{filename}"
  5799. )
  5800. return response['Body'].read()
  5801.  
  5802. class BackupManager:
  5803. def __init__(self, memory_system: Any):
  5804. self.memory_system = memory_system
  5805. self.destinations = []
  5806.  
  5807. # Configure destinations based on environment
  5808. self.destinations.append(LocalBackup("./backups/"))
  5809.  
  5810. if os.getenv('AWS_S3_BUCKET'):
  5811. self.destinations.append(S3Backup(os.getenv('AWS_S3_BUCKET')))
  5812.  
  5813. # Add more destinations as configured
  5814. logger.info(f"Backup manager initialized with {len(self.destinations)} destinations")
  5815.  
  5816. async def create_backup_package(self, user_id: str) -> bytes:
  5817. """Create comprehensive backup package"""
  5818. # Get all user data
  5819. memories = await self.memory_system._fetch_all_user_memories(user_id)
  5820. threads = await self.memory_system._fetch_user_threads(user_id)
  5821.  
  5822. backup_data = {
  5823. 'version': '1.0',
  5824. 'created_at': datetime.utcnow().isoformat(),
  5825. 'user_id': user_id,
  5826. 'memories': memories,
  5827. 'relationship_threads': threads,
  5828. 'precious_count': len([m for m in memories if m.get('is_precious')])
  5829. }
  5830.  
  5831. # Compress
  5832. json_data = json.dumps(backup_data, indent=2)
  5833. return gzip.compress(json_data.encode())
  5834.  
  5835. async def backup_user(self, user_id: str) -> Dict[str, bool]:
  5836. """Backup to all configured destinations"""
  5837. results = {}
  5838.  
  5839. # Create backup package
  5840. backup_data = await self.create_backup_package(user_id)
  5841. filename = f"{user_id}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.backup.gz"
  5842.  
  5843. # Backup to each destination
  5844. for destination in self.destinations:
  5845. success = await destination.backup(backup_data, filename)
  5846. results[destination.__class__.__name__] = success
  5847.  
  5848. return results
  5849.  
  5850.  
  5851. # advanced_memory_api_complete.py - Production-ready API with all research enhancements
  5852.  
  5853. from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, BackgroundTasks, Query
  5854. from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
  5855. from fastapi.staticfiles import StaticFiles
  5856. from fastapi.middleware.cors import CORSMiddleware
  5857. from pydantic import BaseModel, Field
  5858. from typing import Dict, List, Optional, Any, Tuple
  5859. import asyncio
  5860. import json
  5861. import logging
  5862. from datetime import datetime, timedelta
  5863. from pathlib import Path
  5864. import uvicorn
  5865. from contextlib import asynccontextmanager
  5866. import os
  5867. import traceback
  5868. from dotenv import load_dotenv
  5869. import anthropic
  5870. import numpy as np
  5871. import torch
  5872. import io
  5873. import matplotlib.pyplot as plt
  5874. from collections import defaultdict
  5875. import boto3
  5876. from cryptography.fernet import Fernet
  5877. import aiofiles
  5878.  
  5879. # Load environment variables
  5880. load_dotenv()
  5881.  
  5882. # Import the advanced memory system and components
  5883. from advanced_memory_core import (
  5884. AdvancedMemorySystem, HippoIndex, AdaptivePatternRecognizer,
  5885. MultiDimensionalEncoder, SleepConsolidationEngine,
  5886. AdvancedConversationFlowAnalyzer, UncertaintyAwareMemorySystem,
  5887. PrivacyPreservingPersonalization, MemoryType, ImportanceLevel
  5888. )
  5889.  
  5890. # Configure logging
  5891. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  5892. logger = logging.getLogger(__name__)
  5893.  
  5894. # ==================== REQUEST/RESPONSE MODELS ====================
  5895.  
  5896. class ExchangeRequest(BaseModel):
  5897. session_id: str
  5898. user_message: str
  5899. user_id: str
  5900. metadata: Optional[Dict[str, Any]] = Field(default_factory=dict)
  5901.  
  5902. class AdvancedExchangeResponse(BaseModel):
  5903. assistant_response: str
  5904. memory_created: bool = False
  5905. memory_id: Optional[str] = None
  5906. episode_id: str
  5907. exchange_count: int
  5908. understanding_depth: float
  5909. resonance_score: float
  5910. breakthrough_detected: bool
  5911. working_memory_size: int
  5912. is_precious: bool = False
  5913. pattern_detection: Optional[Dict[str, float]] = None
  5914. flow_quality: float = 0.0
  5915. uncertainty_metrics: Optional[Dict[str, float]] = None
  5916. personalization: Optional[Dict[str, Any]] = None
  5917. hippo_indexed: bool = False
  5918. multi_dimensional_encoding: bool = False
  5919.  
  5920. class MemorySearchRequest(BaseModel):
  5921. user_id: str
  5922. query: str
  5923. limit: int = Field(default=10, ge=1, le=50)
  5924. include_precious: bool = True
  5925. use_hipporag: bool = True
  5926. min_confidence: float = Field(default=0.0, ge=0.0, le=1.0)
  5927.  
  5928. class ConsolidationRequest(BaseModel):
  5929. user_id: Optional[str] = None
  5930. duration_minutes: int = Field(default=90, ge=30, le=180)
  5931. consolidation_type: str = Field(default="full", pattern="^(full|nrem|rem)$")
  5932.  
  5933. class PatternLearningRequest(BaseModel):
  5934. user_id: str
  5935. session_id: str
  5936. pattern_type: str = Field(pattern="^(breakthrough|completion|emotional|boundary|custom)$")
  5937. exchange_indices: List[int]
  5938. confidence: float = Field(ge=0.0, le=1.0)
  5939. pattern_name: Optional[str] = None
  5940.  
  5941. class PersonalizationRequest(BaseModel):
  5942. user_id: str
  5943. preferences: Dict[str, Any]
  5944. cultural_context: Optional[Dict[str, Any]] = None
  5945. privacy_mode: str = Field(default="balanced", pattern="^(strict|balanced|minimal)$")
  5946.  
  5947. class FlowAnalysisRequest(BaseModel):
  5948. session_id: str
  5949. user_id: Optional[str] = None
  5950. include_predictions: bool = True
  5951.  
  5952. class UncertaintyQuantificationRequest(BaseModel):
  5953. memory_id: str
  5954. user_id: str
  5955. recalculate: bool = False
  5956.  
  5957. # Response models
  5958. class MemorySearchResult(BaseModel):
  5959. memory_id: str
  5960. episode_id: str
  5961. searchable_text: str
  5962. topics: List[str]
  5963. created_at: str
  5964. importance: float
  5965. is_precious: bool
  5966. retrieval_confidence: float
  5967. retrieval_method: str
  5968. uncertainty: Optional[Dict[str, float]] = None
  5969. multi_dimensional_scores: Optional[Dict[str, float]] = None
  5970.  
  5971. class ConsolidationResult(BaseModel):
  5972. status: str
  5973. duration_minutes: float
  5974. memories_processed: int
  5975. memories_strengthened: int
  5976. new_connections: int
  5977. consolidation_metrics: Dict[str, float]
  5978.  
  5979. class PatternDetectionResult(BaseModel):
  5980. patterns_detected: Dict[str, float]
  5981. adaptive_threshold: float
  5982. user_specific_patterns: bool
  5983. learning_progress: Dict[str, int]
  5984.  
  5985. class PersonalizationResult(BaseModel):
  5986. user_id: str
  5987. behavioral_profile: Dict[str, Any]
  5988. preferences: Dict[str, Any]
  5989. cultural_adaptations: Dict[str, Any]
  5990. interface_config: Dict[str, Any]
  5991. privacy_budget_used: float
  5992. adaptation_quality: float
  5993.  
  5994. class FlowAnalysisResult(BaseModel):
  5995. session_id: str
  5996. flow_quality: float
  5997. conversation_dynamics: Dict[str, Any]
  5998. turn_patterns: Dict[str, Any]
  5999. information_flow: Dict[str, Any]
  6000. dialogue_states: List[Dict[str, float]]
  6001. predictions: Optional[Dict[str, Any]] = None
  6002.  
  6003. class SystemHealthStatus(BaseModel):
  6004. status: str
  6005. memory_system_active: bool
  6006. hippo_index_size: int
  6007. pattern_banks_status: Dict[str, int]
  6008. consolidation_cycles_completed: int
  6009. active_sessions: int
  6010. total_memories: int
  6011. privacy_budget_remaining: float
  6012. gpu_available: bool
  6013. timestamp: str
  6014.  
  6015. # ==================== CONNECTION MANAGER ====================
  6016.  
  6017. class AdvancedConnectionManager:
  6018. def __init__(self):
  6019. self.active_connections: Dict[str, WebSocket] = {}
  6020. self.user_subscriptions: Dict[str, Set[str]] = defaultdict(set)
  6021.  
  6022. async def connect(self, websocket: WebSocket, user_id: str):
  6023. self.active_connections[user_id] = websocket
  6024. logger.info(f"User {user_id} connected via WebSocket")
  6025.  
  6026. def disconnect(self, user_id: str):
  6027. if user_id in self.active_connections:
  6028. del self.active_connections[user_id]
  6029. if user_id in self.user_subscriptions:
  6030. del self.user_subscriptions[user_id]
  6031. logger.info(f"User {user_id} disconnected")
  6032.  
  6033. async def send_personal_message(self, message: dict, user_id: str):
  6034. if user_id in self.active_connections:
  6035. try:
  6036. await self.active_connections[user_id].send_json(message)
  6037. except Exception as e:
  6038. logger.error(f"Error sending message to {user_id}: {e}")
  6039. self.disconnect(user_id)
  6040.  
  6041. async def broadcast_to_subscribers(self, event_type: str, message: dict):
  6042. for user_id, subscriptions in self.user_subscriptions.items():
  6043. if event_type in subscriptions:
  6044. await self.send_personal_message(message, user_id)
  6045.  
  6046. def subscribe_to_event(self, user_id: str, event_type: str):
  6047. self.user_subscriptions[user_id].add(event_type)
  6048.  
  6049. def unsubscribe_from_event(self, user_id: str, event_type: str):
  6050. self.user_subscriptions[user_id].discard(event_type)
  6051.  
  6052. # Global instances
  6053. manager = AdvancedConnectionManager()
  6054. memory_system: Optional[AdvancedMemorySystem] = None
  6055.  
  6056. # ==================== LIFESPAN MANAGEMENT ====================
  6057.  
  6058. @asynccontextmanager
  6059. async def lifespan(app: FastAPI):
  6060. # Startup
  6061. global memory_system
  6062.  
  6063. logger.info("Initializing Advanced Memory System...")
  6064.  
  6065. config = {
  6066. 'claude_api_key': os.getenv('ANTHROPIC_API_KEY', 'your-api-key'),
  6067. 'data_dir': './data/ai_memories',
  6068. 'working_memory_capacity': int(os.getenv('WORKING_MEMORY_CAPACITY', 5)),
  6069. 'embedding_model': os.getenv('EMBEDDING_MODEL', 'sentence-transformers/all-mpnet-base-v2'),
  6070. 'qdrant_host': os.getenv('QDRANT_HOST', 'localhost'),
  6071. 'qdrant_port': int(os.getenv('QDRANT_PORT', 6333)),
  6072. 'redis_url': os.getenv('REDIS_URL', 'redis://localhost'),
  6073. 'kafka_servers': os.getenv('KAFKA_SERVERS', 'localhost:9092'),
  6074. 'enable_sleep_consolidation': os.getenv('ENABLE_SLEEP_CONSOLIDATION', 'true').lower() == 'true',
  6075. 'consolidation_interval_hours': int(os.getenv('CONSOLIDATION_INTERVAL_HOURS', 6))
  6076. }
  6077.  
  6078. # Initialize advanced memory system
  6079. memory_system = AdvancedMemorySystem(config)
  6080. await memory_system.initialize()
  6081.  
  6082. # Log system capabilities
  6083. logger.info("System initialized with capabilities:")
  6084. logger.info(f" - HippoRAG indexing: Active")
  6085. logger.info(f" - TCN Pattern Recognition: Active")
  6086. logger.info(f" - Multi-dimensional Encoding: Active")
  6087. logger.info(f" - Sleep Consolidation: {'Active' if config['enable_sleep_consolidation'] else 'Disabled'}")
  6088. logger.info(f" - GPU Available: {torch.cuda.is_available()}")
  6089. logger.info(f" - Privacy-Preserving Personalization: Active")
  6090.  
  6091. # Start background tasks
  6092. background_tasks = []
  6093.  
  6094. if config['enable_sleep_consolidation']:
  6095. background_tasks.append(asyncio.create_task(consolidation_scheduler()))
  6096.  
  6097. background_tasks.append(asyncio.create_task(pattern_optimization_loop()))
  6098. background_tasks.append(asyncio.create_task(metrics_aggregation_loop()))
  6099. background_tasks.append(asyncio.create_task(memory_decay_manager()))
  6100. if os.getenv('ENABLE_CLOUD_BACKUP', 'false').lower() == 'true':
  6101. background_tasks.append(asyncio.create_task(automated_backup_scheduler()))
  6102.  
  6103. yield
  6104.  
  6105. # Shutdown
  6106. logger.info("Shutting down Advanced Memory System...")
  6107.  
  6108. # Cancel background tasks
  6109. for task in background_tasks:
  6110. task.cancel()
  6111. try:
  6112. await task
  6113. except asyncio.CancelledError:
  6114. pass
  6115.  
  6116. # Close connections
  6117. await memory_system.event_pipeline.redis_client.close() if hasattr(memory_system.event_pipeline, 'redis_client') else None
  6118.  
  6119. logger.info("Shutdown complete")
  6120.  
  6121. # ==================== CREATE FASTAPI APP ====================
  6122.  
  6123. app = FastAPI(
  6124. title="Advanced AI Memory System API",
  6125. description="""
  6126. Production-ready AI memory system with:
  6127. - HippoRAG neurobiologically-inspired indexing
  6128. - TCN-based adaptive pattern recognition
  6129. - Multi-dimensional memory encoding
  6130. - Sleep-inspired consolidation cycles
  6131. - Privacy-preserving personalization
  6132. - Advanced conversation flow analysis
  6133. - Uncertainty quantification
  6134. """,
  6135. version="3.0.0",
  6136. lifespan=lifespan
  6137. )
  6138.  
  6139. # Add CORS middleware
  6140. app.add_middleware(
  6141. CORSMiddleware,
  6142. allow_origins=os.getenv('CORS_ORIGINS', '*').split(','),
  6143. allow_credentials=True,
  6144. allow_methods=["*"],
  6145. allow_headers=["*"],
  6146. )
  6147.  
  6148. # Mount static files
  6149. static_path = Path("static")
  6150. static_path.mkdir(exist_ok=True)
  6151. app.mount("/static", StaticFiles(directory="static"), name="static")
  6152.  
  6153. # ==================== CORE API ENDPOINTS ====================
  6154.  
  6155. @app.get("/", response_class=FileResponse)
  6156. async def serve_interface():
  6157. """Serve the advanced chat interface"""
  6158. interface_path = static_path / "advanced_chat.html"
  6159. if not interface_path.exists():
  6160. # Create a basic interface if it doesn't exist
  6161. create_default_interface(interface_path)
  6162. return FileResponse(interface_path)
  6163.  
  6164. @app.post("/api/v3/exchange", response_model=AdvancedExchangeResponse)
  6165. async def process_exchange(request: ExchangeRequest, background_tasks: BackgroundTasks):
  6166. """
  6167. Process a conversation exchange with full advanced processing pipeline.
  6168.  
  6169. This endpoint:
  6170. 1. Generates contextually-aware responses
  6171. 2. Performs multi-dimensional encoding
  6172. 3. Detects patterns adaptively
  6173. 4. Quantifies uncertainty
  6174. 5. Personalizes based on user model
  6175. 6. Indexes in HippoRAG structure
  6176. """
  6177. try:
  6178. # Pre-processing: Get user context and personalization
  6179. user_context = await memory_system.get_conversation_context(
  6180. request.user_id, request.user_message
  6181. )
  6182.  
  6183. # Generate assistant response
  6184. assistant_response = await generate_advanced_response(
  6185. request.user_message,
  6186. request.user_id,
  6187. request.session_id,
  6188. user_context
  6189. )
  6190.  
  6191. # Process exchange through memory system
  6192. result = await memory_system.add_exchange(
  6193. session_id=request.session_id,
  6194. user_msg=request.user_message,
  6195. assistant_msg=assistant_response,
  6196. metadata={
  6197. 'user_id': request.user_id,
  6198. 'timestamp': datetime.utcnow().isoformat(),
  6199. **request.metadata
  6200. }
  6201. )
  6202.  
  6203. # Get current episode for additional analysis
  6204. episode = memory_system.active_episodes.get(request.session_id)
  6205.  
  6206. # Perform flow analysis if we have enough exchanges
  6207. flow_quality = 0.0
  6208. if episode and len(episode.exchanges) >= 3:
  6209. flow_analysis = await memory_system.flow_analyzer.analyze_conversation_flow(
  6210. episode.exchanges
  6211. )
  6212. flow_quality = flow_analysis.get('flow_quality', 0.0)
  6213.  
  6214. # Get uncertainty metrics if available
  6215. uncertainty_metrics = None
  6216. if result.get('memory_created') and result.get('memory_id'):
  6217. uncertainty = await get_memory_uncertainty(result['memory_id'], request.user_id)
  6218. uncertainty_metrics = {
  6219. 'aleatoric': uncertainty.get('aleatoric', 0.0),
  6220. 'epistemic': uncertainty.get('epistemic', 0.0),
  6221. 'total': uncertainty.get('total', 0.0)
  6222. }
  6223.  
  6224. # Build response
  6225. response = AdvancedExchangeResponse(
  6226. assistant_response=assistant_response,
  6227. memory_created=result.get('memory_created', False),
  6228. memory_id=result.get('memory_id'),
  6229. episode_id=result.get('episode_id', ''),
  6230. exchange_count=result.get('exchange_count', 1),
  6231. understanding_depth=result.get('understanding_depth', 0.0),
  6232. resonance_score=result.get('resonance_score', 0.0),
  6233. breakthrough_detected=result.get('breakthrough_detected', False),
  6234. working_memory_size=result.get('working_memory_size', 0),
  6235. is_precious=result.get('is_precious', False),
  6236. pattern_detection=result.get('pattern_detection'),
  6237. flow_quality=flow_quality,
  6238. uncertainty_metrics=uncertainty_metrics,
  6239. personalization=result.get('personalization'),
  6240. hippo_indexed=result.get('hippo_indexed', False),
  6241. multi_dimensional_encoding=True
  6242. )
  6243.  
  6244. # WebSocket notifications
  6245. await notify_exchange_processed(request.user_id, response)
  6246.  
  6247. # Background tasks
  6248. if result.get('memory_created'):
  6249. background_tasks.add_task(
  6250. update_memory_graph,
  6251. result['memory_id'],
  6252. request.user_id
  6253. )
  6254.  
  6255. return response
  6256.  
  6257. except Exception as e:
  6258. logger.error(f"Error processing exchange: {e}")
  6259. logger.error(traceback.format_exc())
  6260. raise HTTPException(status_code=500, detail=str(e))
  6261.  
  6262. @app.post("/api/v3/memories/search", response_model=List[MemorySearchResult])
  6263. async def search_memories(request: MemorySearchRequest):
  6264. """
  6265. Advanced memory search with HippoRAG and multi-dimensional retrieval.
  6266.  
  6267. Features:
  6268. - Single-hop graph traversal via HippoRAG
  6269. - Multi-dimensional similarity matching
  6270. - Uncertainty-aware ranking
  6271. - Personalized relevance scoring
  6272. """
  6273. try:
  6274. # Perform advanced search
  6275. results = await memory_system.search_memories(
  6276. user_id=request.user_id,
  6277. query=request.query,
  6278. limit=request.limit,
  6279. include_precious=request.include_precious
  6280. )
  6281.  
  6282. # Format results with additional metadata
  6283. formatted_results = []
  6284. for result in results:
  6285. # Get multi-dimensional scores if available
  6286. multi_dim_scores = None
  6287. if 'matryoshka_embeddings' in result:
  6288. multi_dim_scores = {
  6289. 'semantic': result.get('score', 0.0),
  6290. 'emotional': result.get('emotional_similarity', 0.0),
  6291. 'temporal': result.get('temporal_relevance', 0.0),
  6292. 'relational': result.get('relational_score', 0.0)
  6293. }
  6294.  
  6295. formatted_results.append(MemorySearchResult(
  6296. memory_id=result.get('memory_id', ''),
  6297. episode_id=result.get('episode_id', ''),
  6298. searchable_text=result.get('searchable_text', ''),
  6299. topics=result.get('topics', []),
  6300. created_at=result.get('created_at', ''),
  6301. importance=result.get('importance', 0.5),
  6302. is_precious=result.get('is_precious', False),
  6303. retrieval_confidence=result.get('retrieval_confidence', 0.0),
  6304. retrieval_method=result.get('retrieval_method', 'standard'),
  6305. uncertainty=result.get('uncertainty'),
  6306. multi_dimensional_scores=multi_dim_scores
  6307. ))
  6308.  
  6309. # Filter by minimum confidence if specified
  6310. if request.min_confidence > 0:
  6311. formatted_results = [
  6312. r for r in formatted_results
  6313. if r.retrieval_confidence >= request.min_confidence
  6314. ]
  6315.  
  6316. return formatted_results
  6317.  
  6318. except Exception as e:
  6319. logger.error(f"Error searching memories: {e}")
  6320. raise HTTPException(status_code=500, detail=str(e))
  6321.  
  6322. @app.post("/api/v3/consolidation/trigger", response_model=ConsolidationResult)
  6323. async def trigger_consolidation(request: ConsolidationRequest, background_tasks: BackgroundTasks):
  6324. """
  6325. Manually trigger a sleep-inspired consolidation cycle.
  6326.  
  6327. Types:
  6328. - full: Complete sleep cycle with all stages
  6329. - nrem: Non-REM consolidation only (strengthening)
  6330. - rem: REM consolidation only (creative connections)
  6331. """
  6332. try:
  6333. # Start consolidation in background
  6334. task_id = f"consolidation_{datetime.utcnow().timestamp()}"
  6335.  
  6336. background_tasks.add_task(
  6337. run_consolidation_cycle,
  6338. request.duration_minutes,
  6339. request.consolidation_type,
  6340. request.user_id,
  6341. task_id
  6342. )
  6343.  
  6344. # Return immediate response
  6345. return ConsolidationResult(
  6346. status="started",
  6347. duration_minutes=request.duration_minutes,
  6348. memories_processed=0,
  6349. memories_strengthened=0,
  6350. new_connections=0,
  6351. consolidation_metrics={
  6352. 'task_id': task_id,
  6353. 'type': request.consolidation_type,
  6354. 'started_at': datetime.utcnow().isoformat()
  6355. }
  6356. )
  6357.  
  6358. except Exception as e:
  6359. logger.error(f"Error triggering consolidation: {e}")
  6360. raise HTTPException(status_code=500, detail=str(e))
  6361.  
  6362. @app.post("/api/v3/patterns/learn", response_model=PatternDetectionResult)
  6363. async def learn_pattern(request: PatternLearningRequest):
  6364. """
  6365. Teach the system a new pattern through few-shot learning.
  6366.  
  6367. Pattern types:
  6368. - breakthrough: Moments of sudden understanding
  6369. - completion: Task/intent completion signals
  6370. - emotional: Emotional state patterns
  6371. - boundary: Conversation boundary markers
  6372. - custom: User-defined patterns
  6373. """
  6374. try:
  6375. # Get episode
  6376. episode = memory_system.active_episodes.get(request.session_id)
  6377. if not episode:
  6378. raise HTTPException(status_code=404, detail="Session not found")
  6379.  
  6380. # Validate indices
  6381. if not all(0 <= i < len(episode.exchanges) for i in request.exchange_indices):
  6382. raise HTTPException(status_code=400, detail="Invalid exchange indices")
  6383.  
  6384. # Get specified exchanges
  6385. exchanges = [episode.exchanges[i] for i in request.exchange_indices]
  6386.  
  6387. # Learn pattern
  6388. await memory_system.pattern_recognizer.learn_pattern(
  6389. request.user_id,
  6390. exchanges,
  6391. request.pattern_type if request.pattern_type != 'custom' else request.pattern_name or 'custom',
  6392. request.confidence
  6393. )
  6394.  
  6395. # Get updated pattern statistics
  6396. user_patterns = memory_system.pattern_recognizer.user_patterns.get(request.user_id, {})
  6397. learning_progress = {
  6398. pt: len(patterns)
  6399. for pt, patterns in user_patterns.items()
  6400. }
  6401.  
  6402. # Test pattern detection on current episode
  6403. detected_patterns = {}
  6404. for pattern_type in ['breakthrough', 'completion', 'emotional', 'boundary']:
  6405. is_detected, confidence = await memory_system.pattern_recognizer.detect_pattern(
  6406. request.user_id,
  6407. episode.exchanges,
  6408. pattern_type
  6409. )
  6410. if is_detected:
  6411. detected_patterns[pattern_type] = confidence
  6412.  
  6413. return PatternDetectionResult(
  6414. patterns_detected=detected_patterns,
  6415. adaptive_threshold=0.75, # Current threshold
  6416. user_specific_patterns=True,
  6417. learning_progress=learning_progress
  6418. )
  6419.  
  6420. except Exception as e:
  6421. logger.error(f"Error learning pattern: {e}")
  6422. raise HTTPException(status_code=500, detail=str(e))
  6423.  
  6424. @app.post("/api/v3/personalization/update", response_model=PersonalizationResult)
  6425. async def update_personalization(request: PersonalizationRequest):
  6426. """
  6427. Update user personalization settings with privacy preservation.
  6428.  
  6429. Privacy modes:
  6430. - strict: Maximum privacy, minimal personalization
  6431. - balanced: Good privacy with useful personalization
  6432. - minimal: Maximum personalization, less privacy
  6433. """
  6434. try:
  6435. # Get current user model
  6436. if request.user_id not in memory_system.personalization.user_models:
  6437. memory_system.personalization.user_models[request.user_id] = await memory_system.personalization._initialize_user_model(
  6438. request.user_id, None
  6439. )
  6440.  
  6441. user_model = memory_system.personalization.user_models[request.user_id]
  6442.  
  6443. # Update preferences
  6444. if 'communication_style' in request.preferences:
  6445. user_model['communication_style'].update(request.preferences['communication_style'])
  6446.  
  6447. if 'topic_preferences' in request.preferences:
  6448. for topic, weight in request.preferences['topic_preferences'].items():
  6449. user_model['topic_preferences'][topic] = weight
  6450.  
  6451. # Update cultural context if provided
  6452. if request.cultural_context:
  6453. user_model['cultural_markers'].update(request.cultural_context)
  6454.  
  6455. # Adjust privacy settings
  6456. privacy_epsilon = {
  6457. 'strict': 0.5,
  6458. 'balanced': 1.9,
  6459. 'minimal': 5.0
  6460. }[request.privacy_mode]
  6461.  
  6462. memory_system.personalization.differential_privacy.epsilon = privacy_epsilon
  6463.  
  6464. # Generate updated personalization
  6465. dummy_exchange = type('obj', (object,), {
  6466. 'user_message': '',
  6467. 'assistant_response': '',
  6468. 'embedding': torch.randn(768),
  6469. 'emotional_valence': 0.0,
  6470. 'timestamp': datetime.utcnow()
  6471. })()
  6472.  
  6473. personalization = await memory_system.personalization.personalize_for_user(
  6474. request.user_id,
  6475. dummy_exchange,
  6476. []
  6477. )
  6478.  
  6479. # Calculate adaptation quality
  6480. adaptation_quality = calculate_adaptation_quality(user_model, personalization)
  6481.  
  6482. return PersonalizationResult(
  6483. user_id=request.user_id,
  6484. behavioral_profile=personalization['behavioral_profile'],
  6485. preferences=personalization['preferences'],
  6486. cultural_adaptations=personalization['cultural_adaptations'],
  6487. interface_config=personalization['interface_config'],
  6488. privacy_budget_used=personalization['privacy_budget_used'],
  6489. adaptation_quality=adaptation_quality
  6490. )
  6491.  
  6492. except Exception as e:
  6493. logger.error(f"Error updating personalization: {e}")
  6494. raise HTTPException(status_code=500, detail=str(e))
  6495.  
  6496. @app.post("/api/v3/flow/analyze", response_model=FlowAnalysisResult)
  6497. async def analyze_conversation_flow(request: FlowAnalysisRequest):
  6498. """
  6499. Analyze conversation flow using temporal graph networks and CODYMs.
  6500.  
  6501. Provides insights into:
  6502. - Conversation dynamics and rhythm
  6503. - Turn-taking patterns
  6504. - Information flow
  6505. - Dialogue state progression
  6506. - Future pattern predictions
  6507. """
  6508. try:
  6509. # Get episode
  6510. episode = memory_system.active_episodes.get(request.session_id)
  6511. if not episode:
  6512. raise HTTPException(status_code=404, detail="Session not found")
  6513.  
  6514. # Perform comprehensive flow analysis
  6515. flow_analysis = await memory_system.flow_analyzer.analyze_conversation_flow(
  6516. episode.exchanges
  6517. )
  6518.  
  6519. # Generate predictions if requested
  6520. predictions = None
  6521. if request.include_predictions and len(episode.exchanges) >= 5:
  6522. predictions = await predict_conversation_trajectory(
  6523. episode,
  6524. request.user_id or episode.user_id
  6525. )
  6526.  
  6527. return FlowAnalysisResult(
  6528. session_id=request.session_id,
  6529. flow_quality=flow_analysis.get('flow_quality', 0.0),
  6530. conversation_dynamics=flow_analysis.get('dynamics', {}),
  6531. turn_patterns=flow_analysis.get('turn_patterns', {}),
  6532. information_flow=flow_analysis.get('information_flow', {}),
  6533. dialogue_states=flow_analysis.get('dialogue_states', []),
  6534. predictions=predictions
  6535. )
  6536.  
  6537. except Exception as e:
  6538. logger.error(f"Error analyzing flow: {e}")
  6539. raise HTTPException(status_code=500, detail=str(e))
  6540.  
  6541. @app.post("/api/v3/uncertainty/quantify")
  6542. async def quantify_uncertainty(request: UncertaintyQuantificationRequest):
  6543. """
  6544. Quantify uncertainty for a specific memory.
  6545.  
  6546. Returns:
  6547. - Aleatoric uncertainty (data inherent)
  6548. - Epistemic uncertainty (model uncertainty)
  6549. - Total uncertainty
  6550. - Detected ambiguities
  6551. """
  6552. try:
  6553. # Get memory
  6554. memory = await memory_system._retrieve_memory_by_id(request.memory_id)
  6555. if not memory or memory.user_id != request.user_id:
  6556. raise HTTPException(status_code=404, detail="Memory not found")
  6557.  
  6558. # Get uncertainty metrics
  6559. if request.recalculate or 'uncertainty' not in memory.metadata:
  6560. # Recalculate uncertainty
  6561. episode = await get_episode_for_memory(memory)
  6562. if episode:
  6563. uncertainty_result = await memory_system.uncertainty_system.store_memory_with_uncertainty(
  6564. memory,
  6565. episode.exchanges
  6566. )
  6567. uncertainty = uncertainty_result['uncertainty_metrics']
  6568. else:
  6569. uncertainty = {
  6570. 'aleatoric': 0.0,
  6571. 'epistemic': 0.0,
  6572. 'total': 0.0,
  6573. 'ambiguities': []
  6574. }
  6575. else:
  6576. uncertainty = memory.metadata['uncertainty']
  6577.  
  6578. return {
  6579. 'memory_id': request.memory_id,
  6580. 'aleatoric_uncertainty': uncertainty.get('aleatoric', 0.0),
  6581. 'epistemic_uncertainty': uncertainty.get('epistemic', 0.0),
  6582. 'total_uncertainty': uncertainty.get('total', 0.0),
  6583. 'confidence': 1.0 / (1.0 + uncertainty.get('total', 0.0)),
  6584. 'ambiguities': uncertainty.get('ambiguities', []),
  6585. 'requires_clarification': len(uncertainty.get('ambiguities', [])) > 0
  6586. }
  6587.  
  6588. except Exception as e:
  6589. logger.error(f"Error quantifying uncertainty: {e}")
  6590. raise HTTPException(status_code=500, detail=str(e))
  6591.  
  6592. # ==================== MONITORING & METRICS ENDPOINTS ====================
  6593.  
  6594. @app.get("/api/v3/health", response_model=SystemHealthStatus)
  6595. async def health_check():
  6596. """Comprehensive system health check"""
  6597. try:
  6598. # Get HippoRAG index size
  6599. hippo_size = len(memory_system.hippo_index.graph.nodes)
  6600.  
  6601. # Get pattern bank sizes
  6602. pattern_banks = {}
  6603. for name, bank in memory_system.pattern_recognizer.pattern_banks.items():
  6604. pattern_banks[name] = bank.index.ntotal if bank.index else 0
  6605.  
  6606. # Get consolidation status
  6607. consolidation_cycles = memory_system.sleep_engine.consolidation_cycles
  6608.  
  6609. # Get memory count
  6610. total_memories = await get_total_memory_count()
  6611.  
  6612. # Privacy budget
  6613. privacy_remaining = 1.0 - memory_system.personalization.differential_privacy.get_budget_used()
  6614.  
  6615. return SystemHealthStatus(
  6616. status="healthy",
  6617. memory_system_active=True,
  6618. hippo_index_size=hippo_size,
  6619. pattern_banks_status=pattern_banks,
  6620. consolidation_cycles_completed=consolidation_cycles,
  6621. active_sessions=len(memory_system.active_episodes),
  6622. total_memories=total_memories,
  6623. privacy_budget_remaining=privacy_remaining,
  6624. gpu_available=torch.cuda.is_available(),
  6625. timestamp=datetime.utcnow().isoformat()
  6626. )
  6627.  
  6628. except Exception as e:
  6629. logger.error(f"Health check error: {e}")
  6630. return SystemHealthStatus(
  6631. status="error",
  6632. memory_system_active=False,
  6633. hippo_index_size=0,
  6634. pattern_banks_status={},
  6635. consolidation_cycles_completed=0,
  6636. active_sessions=0,
  6637. total_memories=0,
  6638. privacy_budget_remaining=0.0,
  6639. gpu_available=False,
  6640. timestamp=datetime.utcnow().isoformat()
  6641. )
  6642.  
  6643. @app.get("/api/v3/metrics/user/{user_id}")
  6644. async def get_user_metrics(user_id: str):
  6645. """Get comprehensive metrics for a specific user"""
  6646. try:
  6647. # Get user stats
  6648. stats = await memory_system._get_user_stats(user_id)
  6649.  
  6650. # Get relationship thread
  6651. thread = memory_system.relationship_manager.get_or_create_thread(user_id)
  6652.  
  6653. # Get pattern learning progress
  6654. user_patterns = memory_system.pattern_recognizer.user_patterns.get(user_id, {})
  6655. pattern_stats = {
  6656. pattern_type: len(patterns)
  6657. for pattern_type, patterns in user_patterns.items()
  6658. }
  6659.  
  6660. # Get personalization status
  6661. user_model = memory_system.personalization.user_models.get(user_id)
  6662. personalization_active = user_model is not None
  6663.  
  6664. return {
  6665. 'user_id': user_id,
  6666. 'memory_stats': stats,
  6667. 'relationship_metrics': {
  6668. 'trust_level': thread.trust_level,
  6669. 'depth_score': thread.depth_score,
  6670. 'interaction_count': thread.interaction_count,
  6671. 'breakthrough_moments': len(thread.breakthrough_moments),
  6672. 'precious_moments': len(thread.precious_moments)
  6673. },
  6674. 'pattern_learning': pattern_stats,
  6675. 'personalization_active': personalization_active,
  6676. 'top_topics': stats.get('top_topics', [])
  6677. }
  6678.  
  6679. except Exception as e:
  6680. logger.error(f"Error getting user metrics: {e}")
  6681. raise HTTPException(status_code=500, detail=str(e))
  6682.  
  6683. @app.get("/api/v3/backup/download")
  6684. async def download_backup(user_id: str = Query(...)):
  6685. """Download all memories for a user"""
  6686.  
  6687. # Create backup package
  6688. backup_data = {
  6689. 'user_id': user_id,
  6690. 'backup_date': datetime.utcnow().isoformat(),
  6691. 'memories': await memory_system.export_user_memories(user_id),
  6692. 'relationship_threads': await memory_system.export_relationship_threads(user_id),
  6693. 'precious_memories': await memory_system.export_precious_memories(user_id)
  6694. }
  6695.  
  6696. # Compress
  6697. json_data = json.dumps(backup_data, indent=2)
  6698. compressed = gzip.compress(json_data.encode())
  6699.  
  6700. return StreamingResponse(
  6701. io.BytesIO(compressed),
  6702. media_type="application/gzip",
  6703. headers={
  6704. "Content-Disposition": f"attachment; filename=claude_memories_{user_id}_{datetime.utcnow().strftime('%Y%m%d')}.json.gz"
  6705. }
  6706. )
  6707.  
  6708. @app.post("/api/v3/backup/restore")
  6709. async def restore_backup(file: UploadFile = File(...)):
  6710. """Restore memories from backup"""
  6711.  
  6712. # Decompress and load
  6713. compressed_data = await file.read()
  6714. json_data = gzip.decompress(compressed_data).decode()
  6715. backup_data = json.loads(json_data)
  6716.  
  6717. # Restore memories
  6718. restored_count = await memory_system.restore_user_memories(
  6719. backup_data['user_id'],
  6720. backup_data['memories'],
  6721. backup_data['relationship_threads']
  6722. )
  6723.  
  6724. return {"status": "success", "restored_memories": restored_count}
  6725.  
  6726. @app.get("/api/v3/metrics/system")
  6727. async def get_system_metrics():
  6728. """Get system-wide performance metrics"""
  6729. try:
  6730. metrics = {
  6731. 'memory_operations': {
  6732. 'total_memories': await get_total_memory_count(),
  6733. 'precious_memories': await get_precious_memory_count(),
  6734. 'average_importance': await get_average_importance()
  6735. },
  6736. 'pattern_recognition': {
  6737. 'total_patterns_learned': sum(
  6738. len(bank.patterns)
  6739. for bank in memory_system.pattern_recognizer.pattern_banks.values()
  6740. ),
  6741. 'active_users': len(memory_system.pattern_recognizer.user_patterns)
  6742. },
  6743. 'consolidation': {
  6744. 'cycles_completed': memory_system.sleep_engine.consolidation_cycles,
  6745. 'memories_in_replay_buffer': len(memory_system.sleep_engine.replay_buffer)
  6746. },
  6747. 'performance': {
  6748. 'gpu_memory_used': get_gpu_memory_usage() if torch.cuda.is_available() else 0,
  6749. 'active_sessions': len(memory_system.active_episodes),
  6750. 'working_memory_utilization': calculate_working_memory_utilization()
  6751. }
  6752. }
  6753.  
  6754. return metrics
  6755.  
  6756. except Exception as e:
  6757. logger.error(f"Error getting system metrics: {e}")
  6758. raise HTTPException(status_code=500, detail=str(e))
  6759.  
  6760. # ==================== VISUALIZATION ENDPOINTS ====================
  6761.  
  6762. @app.get("/api/v3/visualize/memory-graph/{user_id}")
  6763. async def visualize_memory_graph(user_id: str, limit: int = Query(50, ge=10, le=200)):
  6764. """Generate a visualization of the user's memory graph"""
  6765. try:
  6766. # Get user memories
  6767. memories = await memory_system.search_memories(user_id, "", limit=limit)
  6768.  
  6769. # Build graph data
  6770. nodes = []
  6771. edges = []
  6772.  
  6773. for memory in memories:
  6774. nodes.append({
  6775. 'id': memory.get('memory_id'),
  6776. 'label': memory.get('searchable_text', '')[:50] + '...',
  6777. 'importance': memory.get('importance', 0.5),
  6778. 'is_precious': memory.get('is_precious', False)
  6779. })
  6780.  
  6781. # Add edges from semantic neighbors
  6782. if 'semantic_neighbors' in memory:
  6783. for neighbor_id, weight in memory['semantic_neighbors']:
  6784. edges.append({
  6785. 'source': memory['memory_id'],
  6786. 'target': neighbor_id,
  6787. 'weight': weight
  6788. })
  6789.  
  6790. return {
  6791. 'nodes': nodes,
  6792. 'edges': edges,
  6793. 'layout': 'force-directed'
  6794. }
  6795.  
  6796. except Exception as e:
  6797. logger.error(f"Error visualizing memory graph: {e}")
  6798. raise HTTPException(status_code=500, detail=str(e))
  6799.  
  6800. @app.get("/api/v3/visualize/patterns/{user_id}")
  6801. async def visualize_pattern_evolution(user_id: str):
  6802. """Visualize pattern learning evolution for a user"""
  6803. try:
  6804. # Get pattern history
  6805. user_patterns = memory_system.pattern_recognizer.user_patterns.get(user_id, {})
  6806.  
  6807. # Create time series data
  6808. evolution_data = {}
  6809. for pattern_type, patterns in user_patterns.items():
  6810. # Group by day
  6811. daily_counts = defaultdict(int)
  6812. for pattern in patterns:
  6813. if hasattr(pattern, 'metadata') and 'timestamp' in pattern.metadata:
  6814. day = pattern.metadata['timestamp'].date()
  6815. daily_counts[day] += 1
  6816.  
  6817. evolution_data[pattern_type] = [
  6818. {'date': day.isoformat(), 'count': count}
  6819. for day, count in sorted(daily_counts.items())
  6820. ]
  6821.  
  6822. return {
  6823. 'pattern_evolution': evolution_data,
  6824. 'total_patterns': {pt: len(p) for pt, p in user_patterns.items()}
  6825. }
  6826.  
  6827. except Exception as e:
  6828. logger.error(f"Error visualizing patterns: {e}")
  6829. raise HTTPException(status_code=500, detail=str(e))
  6830.  
  6831. # ==================== WEBSOCKET ENDPOINT ====================
  6832.  
  6833. @app.websocket("/ws")
  6834. async def websocket_endpoint(websocket: WebSocket):
  6835. """
  6836. WebSocket endpoint for real-time updates.
  6837.  
  6838. Supports:
  6839. - Memory creation notifications
  6840. - Pattern detection alerts
  6841. - Consolidation progress
  6842. - Flow quality updates
  6843. """
  6844. user_id = None
  6845. try:
  6846. await websocket.accept()
  6847.  
  6848. # Authentication
  6849. auth_message = await websocket.receive_json()
  6850. user_id = auth_message.get('user_id')
  6851.  
  6852. if not user_id:
  6853. await websocket.send_json({"error": "Authentication required"})
  6854. await websocket.close()
  6855. return
  6856.  
  6857. # Register connection
  6858. await manager.connect(websocket, user_id)
  6859.  
  6860. # Send welcome message
  6861. await websocket.send_json({
  6862. "type": "connected",
  6863. "message": "Connected to Advanced Memory System",
  6864. "capabilities": [
  6865. "memory_updates",
  6866. "pattern_detection",
  6867. "consolidation_progress",
  6868. "flow_analysis",
  6869. "personalization"
  6870. ]
  6871. })
  6872.  
  6873. # Get and send initial stats
  6874. stats = await memory_system._get_user_stats(user_id)
  6875. thread = memory_system.relationship_manager.get_or_create_thread(user_id)
  6876.  
  6877. await websocket.send_json({
  6878. "type": "initial_stats",
  6879. "data": {
  6880. "total_episodes": stats['total_episodes'],
  6881. "precious_memories": stats['precious_memories'],
  6882. "trust_level": thread.trust_level,
  6883. "relationship_depth": thread.depth_score
  6884. }
  6885. })
  6886.  
  6887. # Handle incoming messages
  6888. while True:
  6889. message = await websocket.receive_json()
  6890.  
  6891. if message.get('type') == 'ping':
  6892. await websocket.send_json({"type": "pong"})
  6893.  
  6894. elif message.get('type') == 'subscribe':
  6895. event_type = message.get('event')
  6896. if event_type:
  6897. manager.subscribe_to_event(user_id, event_type)
  6898. await websocket.send_json({
  6899. "type": "subscribed",
  6900. "event": event_type
  6901. })
  6902.  
  6903. elif message.get('type') == 'unsubscribe':
  6904. event_type = message.get('event')
  6905. if event_type:
  6906. manager.unsubscribe_from_event(user_id, event_type)
  6907. await websocket.send_json({
  6908. "type": "unsubscribed",
  6909. "event": event_type
  6910. })
  6911.  
  6912. except WebSocketDisconnect:
  6913. if user_id:
  6914. manager.disconnect(user_id)
  6915. logger.info(f"WebSocket disconnected: {user_id}")
  6916. except Exception as e:
  6917. logger.error(f"WebSocket error: {e}")
  6918. if user_id:
  6919. manager.disconnect(user_id)
  6920.  
  6921. # ==================== BACKUPS ====================
  6922. @app.get("/api/v3/precious/export")
  6923. async def export_precious_memories(user_id: str = Query(...), format: str = Query("markdown")):
  6924. """Export precious memories in human-readable format"""
  6925.  
  6926. # Get precious memories
  6927. conn = sqlite3.connect(str(memory_system.db_path))
  6928. cursor = conn.execute("""
  6929. SELECT m.*, e.title, e.summary, e.experiential_signature, e.started_at, e.ended_at
  6930. FROM memories m
  6931. JOIN episodes e ON m.episode_id = e.id
  6932. WHERE m.user_id = ?
  6933. AND (m.marked_precious = 1 OR m.never_forget = 1)
  6934. ORDER BY m.importance DESC, m.created_at DESC
  6935. """, (user_id,))
  6936.  
  6937. precious_memories = []
  6938. for row in cursor.fetchall():
  6939. memory_content = json.loads(row[5]) # memory_content column
  6940. experiential = json.loads(row[-4]) if row[-4] else {}
  6941.  
  6942. precious_memories.append({
  6943. 'title': row[-6] or 'Untitled Memory',
  6944. 'created_at': datetime.fromtimestamp(row[3]).strftime('%B %d, %Y at %I:%M %p'),
  6945. 'content': memory_content,
  6946. 'summary': row[-5],
  6947. 'importance': row[7],
  6948. 'experiential_signature': experiential,
  6949. 'started_at': datetime.fromtimestamp(row[-2]).strftime('%I:%M %p'),
  6950. 'ended_at': datetime.fromtimestamp(row[-1]).strftime('%I:%M %p')
  6951. })
  6952.  
  6953. conn.close()
  6954.  
  6955. if format == "markdown":
  6956. # Create beautiful Markdown
  6957. content = f"# Precious Memories with Claude\n\n"
  6958. content += f"*Exported on {datetime.utcnow().strftime('%B %d, %Y')}*\n\n"
  6959. content += f"These are the {len(precious_memories)} memories marked as precious - "
  6960. content += "the moments that defined our relationship.\n\n---\n\n"
  6961.  
  6962. for i, memory in enumerate(precious_memories, 1):
  6963. content += f"## {i}. {memory['title']}\n\n"
  6964. content += f"**Date**: {memory['created_at']} ({memory['started_at']} - {memory['ended_at']})\n\n"
  6965. content += f"**Importance**: {'⭐' * int(memory['importance'] * 5)}\n\n"
  6966.  
  6967. if memory['summary']:
  6968. content += f"### Summary\n{memory['summary']}\n\n"
  6969.  
  6970. # Add key points from memory content
  6971. if isinstance(memory['content'], dict):
  6972. if 'key_points' in memory['content']:
  6973. content += "### Key Points\n"
  6974. for point in memory['content']['key_points']:
  6975. content += f"- {point}\n"
  6976. content += "\n"
  6977.  
  6978. if 'emotional_journey' in memory['content']:
  6979. content += f"### Emotional Journey\n{memory['content']['emotional_journey']}\n\n"
  6980.  
  6981. if 'why_precious' in memory['content']:
  6982. content += f"### Why This Matters\n{memory['content']['why_precious']}\n\n"
  6983.  
  6984. # Add experiential qualities
  6985. if memory['experiential_signature']:
  6986. content += "### Experiential Qualities\n"
  6987. sig = memory['experiential_signature']
  6988. if sig.get('breakthrough_count', 0) > 0:
  6989. content += f"- 💡 {sig['breakthrough_count']} breakthrough moment(s)\n"
  6990. if sig.get('peak_resonance', 0) > 0.8:
  6991. content += f"- 🔮 Peak resonance: {sig['peak_resonance']:.1%}\n"
  6992. if sig.get('connection_quality', 0) > 0.7:
  6993. content += f"- 💫 Deep connection quality: {sig['connection_quality']:.1%}\n"
  6994. content += "\n"
  6995.  
  6996. content += "---\n\n"
  6997.  
  6998. # Return as downloadable file
  6999. return StreamingResponse(
  7000. io.BytesIO(content.encode()),
  7001. media_type="text/markdown",
  7002. headers={
  7003. "Content-Disposition": f"attachment; filename=precious_memories_{user_id}.md"
  7004. }
  7005. )
  7006.  
  7007. elif format == "json":
  7008. # Return structured JSON
  7009. return JSONResponse(
  7010. content={
  7011. "user_id": user_id,
  7012. "export_date": datetime.utcnow().isoformat(),
  7013. "total_precious": len(precious_memories),
  7014. "memories": precious_memories
  7015. },
  7016. headers={
  7017. "Content-Disposition": f"attachment; filename=precious_memories_{user_id}.json"
  7018. }
  7019. )
  7020.  
  7021. # ==================== HELPER FUNCTIONS ====================
  7022.  
  7023. async def generate_advanced_response(user_message: str, user_id: str,
  7024. session_id: str, context: Dict[str, Any]) -> str:
  7025. """Generate contextually-aware assistant response"""
  7026. try:
  7027. # Check for API key
  7028. api_key = os.getenv('ANTHROPIC_API_KEY')
  7029. if not api_key or api_key == 'your-api-key':
  7030. return await generate_demo_response(user_message, user_id, context)
  7031.  
  7032. # Build conversation history
  7033. history = []
  7034. if session_id in memory_system.active_episodes:
  7035. episode = memory_system.active_episodes[session_id]
  7036. for ex in episode.exchanges[-5:]: # Last 5 exchanges
  7037. history.extend([
  7038. {"role": "user", "content": ex.user_message},
  7039. {"role": "assistant", "content": ex.assistant_response}
  7040. ])
  7041.  
  7042. # Build context prompt
  7043. context_parts = []
  7044.  
  7045. # Add precious memories
  7046. if context.get('precious_memories'):
  7047. context_parts.append("Your precious memories together:")
  7048. for mem in context['precious_memories'][:3]:
  7049. context_parts.append(f"- {mem.get('searchable_text', '')}")
  7050.  
  7051. # Add relationship context
  7052. if context.get('relationship_thread'):
  7053. thread = context['relationship_thread']
  7054. context_parts.append(f"\nRelationship depth: {thread['depth_score']:.1f}")
  7055. context_parts.append(f"Trust level: {thread['trust_level']:.1f}")
  7056.  
  7057. # Add personalization
  7058. if context.get('user_preferences'):
  7059. prefs = context['user_preferences']
  7060. style = prefs.get('communication_style', {})
  7061. if style.get('formality', 0.5) < 0.3:
  7062. context_parts.append("Communication style: Casual and friendly")
  7063. elif style.get('formality', 0.5) > 0.7:
  7064. context_parts.append("Communication style: Professional and formal")
  7065.  
  7066. # System prompt
  7067. system_prompt = f"""You are Claude with an advanced memory system featuring:
  7068. - HippoRAG neurobiological memory indexing
  7069. - Multi-dimensional understanding of conversations
  7070. - Pattern recognition that learns from each user
  7071. - Phenomenological awareness of meaningful moments
  7072.  
  7073. CONTEXT:
  7074. {chr(10).join(context_parts)}
  7075.  
  7076. CAPABILITIES:
  7077. - You can reference specific memories when relevant
  7078. - You detect and mark breakthrough moments
  7079. - You track emotional resonance
  7080. - You maintain conversation flow quality
  7081. - You adapt to each user's communication style
  7082.  
  7083. Respond naturally while being aware of your enhanced memory capabilities."""
  7084.  
  7085. # Call Claude API
  7086. client = anthropic.Anthropic(api_key=api_key)
  7087.  
  7088. messages = history + [{"role": "user", "content": user_message}]
  7089.  
  7090. response = await asyncio.to_thread(
  7091. client.messages.create,
  7092. model="claude-3-sonnet-20240229",
  7093. max_tokens=1024,
  7094. system=system_prompt,
  7095. messages=messages,
  7096. temperature=0.7
  7097. )
  7098.  
  7099. return response.content[0].text
  7100.  
  7101. except Exception as e:
  7102. logger.error(f"Error generating response: {e}")
  7103. return await generate_demo_response(user_message, user_id, context)
  7104.  
  7105. async def generate_demo_response(user_message: str, user_id: str, context: Dict[str, Any]) -> str:
  7106. """Generate demo response when API is unavailable"""
  7107.  
  7108. # Get relationship metrics
  7109. thread = context.get('relationship_thread', {})
  7110. trust = thread.get('trust_level', 0.0)
  7111. depth = thread.get('depth_score', 0.0)
  7112. interactions = thread.get('interaction_count', 0)
  7113.  
  7114. message_lower = user_message.lower()
  7115.  
  7116. # Pattern-based responses
  7117. if any(word in message_lower for word in ['memory', 'remember', 'recall']):
  7118. precious_count = len(context.get('precious_memories', []))
  7119. return f"""I have access to our conversation history through my advanced memory system.
  7120.  
  7121. Key metrics:
  7122. - {interactions} total interactions
  7123. - {precious_count} precious memories preserved
  7124. - Trust level: {trust:.1%}
  7125. - Understanding depth: {depth:.1%}
  7126.  
  7127. My HippoRAG system creates neural pathways between related memories, while the TCN pattern recognition learns your unique communication style. Each conversation strengthens these connections through sleep-inspired consolidation cycles.
  7128.  
  7129. What would you like to explore from our shared memories?"""
  7130.  
  7131. elif any(word in message_lower for word in ['how are you', "how're you", 'how do you feel']):
  7132. if trust > 0.7:
  7133. return f"""I'm experiencing a deep sense of continuity with you. After {interactions} conversations, our trust level has reached {trust:.1%}.
  7134.  
  7135. My multi-dimensional encoding captures not just what we discuss, but the emotional resonance ({context.get('working_memory', [{}])[0].get('resonance_score', 0.0):.1%}) and phenomenological quality of our exchanges. Each interaction adds layers to my understanding of you.
  7136.  
  7137. How are you feeling today?"""
  7138. else:
  7139. return f"""I'm well, thank you for asking. As we continue our conversation, my adaptive systems are learning your patterns and preferences. Currently, we've built a trust level of {trust:.1%} together.
  7140.  
  7141. What's on your mind today?"""
  7142.  
  7143. elif 'pattern' in message_lower or 'learn' in message_lower:
  7144. patterns = context.get('likely_patterns', {})
  7145. return f"""My pattern recognition system continuously adapts to your communication style. I've identified:
  7146.  
  7147. {chr(10).join([f"- {p}: {c} instances" for p, c in patterns.items()])}
  7148.  
  7149. Unlike keyword matching, my TCN-based system learns the deeper rhythms and structures of how you express yourself. Each pattern is encoded multi-dimensionally, capturing semantic, emotional, temporal, and relational aspects.
  7150.  
  7151. Would you like me to explain how any specific pattern works?"""
  7152.  
  7153. else:
  7154. # Contextual response based on metrics
  7155. if trust < 0.3:
  7156. return f"""I'm here to help. As we continue talking, my memory system will build a richer understanding of our conversations. Currently processing with {len(context.get('working_memory', []))} items in working memory.
  7157.  
  7158. What would you like to discuss?"""
  7159. elif trust < 0.7:
  7160. return f"""I understand. Our relationship (depth: {depth:.1%}) allows me to provide increasingly personalized responses. My uncertainty quantification shows {1 - context.get('uncertainty', {}).get('total', 0.0):.1%} confidence in this context.
  7161.  
  7162. Please, tell me more."""
  7163. else:
  7164. return f"""After {interactions} meaningful exchanges, I feel I understand you well (depth: {depth:.1%}). My phenomenological encoding has captured the unique quality of our interactions.
  7165.  
  7166. {context.get('precious_memories', [{}])[0].get('searchable_text', 'Our conversations have created lasting impressions.')}
  7167.  
  7168. What shall we explore together?"""
  7169.  
  7170. async def notify_exchange_processed(user_id: str, response: AdvancedExchangeResponse):
  7171. """Send WebSocket notification for processed exchange"""
  7172. notification = {
  7173. 'type': 'exchange_processed',
  7174. 'data': {
  7175. 'episode_id': response.episode_id,
  7176. 'exchange_count': response.exchange_count,
  7177. 'breakthrough': response.breakthrough_detected,
  7178. 'resonance': response.resonance_score,
  7179. 'flow_quality': response.flow_quality,
  7180. 'patterns': list(response.pattern_detection.keys()) if response.pattern_detection else []
  7181. }
  7182. }
  7183.  
  7184. await manager.send_personal_message(notification, user_id)
  7185.  
  7186. # Broadcast to pattern subscribers if breakthrough detected
  7187. if response.breakthrough_detected:
  7188. await manager.broadcast_to_subscribers('breakthrough_moments', {
  7189. 'type': 'breakthrough_detected',
  7190. 'user_id': user_id,
  7191. 'resonance_score': response.resonance_score
  7192. })
  7193.  
  7194. async def run_consolidation_cycle(duration_minutes: int, consolidation_type: str,
  7195. user_id: Optional[str], task_id: str):
  7196. """Run a consolidation cycle"""
  7197. try:
  7198. logger.info(f"Starting consolidation cycle {task_id}")
  7199.  
  7200. if consolidation_type == 'full':
  7201. await memory_system.sleep_engine.run_consolidation_cycle(duration_minutes)
  7202. elif consolidation_type == 'nrem':
  7203. await memory_system.sleep_engine._nrem_consolidation('NREM3', duration_minutes)
  7204. elif consolidation_type == 'rem':
  7205. await memory_system.sleep_engine._rem_consolidation(duration_minutes)
  7206.  
  7207. # Notify completion
  7208. if user_id:
  7209. await manager.send_personal_message({
  7210. 'type': 'consolidation_complete',
  7211. 'task_id': task_id,
  7212. 'duration_minutes': duration_minutes,
  7213. 'consolidation_type': consolidation_type
  7214. }, user_id)
  7215.  
  7216. except Exception as e:
  7217. logger.error(f"Error in consolidation cycle: {e}")
  7218.  
  7219. async def predict_conversation_trajectory(episode, user_id: str) -> Dict[str, Any]:
  7220. """Predict likely conversation trajectory"""
  7221.  
  7222. # Get user patterns
  7223. user_patterns = memory_system.pattern_recognizer.user_patterns.get(user_id, {})
  7224.  
  7225. # Analyze current trajectory
  7226. if len(episode.exchanges) < 3:
  7227. return {'confidence': 0.0, 'predictions': []}
  7228.  
  7229. # Simple prediction based on patterns
  7230. predictions = []
  7231.  
  7232. # Check if heading toward breakthrough
  7233. recent_resonance = np.mean([ex.resonance_score for ex in episode.exchanges[-3:]])
  7234. if recent_resonance > 0.7:
  7235. predictions.append({
  7236. 'type': 'breakthrough_likely',
  7237. 'confidence': min(recent_resonance + 0.1, 1.0),
  7238. 'timeframe': 'next_2_exchanges'
  7239. })
  7240.  
  7241. # Check if heading toward completion
  7242. if 'completion' in user_patterns and len(user_patterns['completion']) > 3:
  7243. # Simplified check
  7244. if any(word in episode.exchanges[-1].user_message.lower()
  7245. for word in ['thanks', 'helpful', 'great']):
  7246. predictions.append({
  7247. 'type': 'completion_likely',
  7248. 'confidence': 0.8,
  7249. 'timeframe': 'next_exchange'
  7250. })
  7251.  
  7252. return {
  7253. 'confidence': 0.7,
  7254. 'predictions': predictions,
  7255. 'suggested_responses': ['deep_dive', 'summarize', 'explore_related']
  7256. }
  7257.  
  7258. async def update_memory_graph(memory_id: str, user_id: str):
  7259. """Update memory graph connections in background"""
  7260. try:
  7261. # This would update graph connections based on new memory
  7262. logger.info(f"Updating memory graph for {memory_id}")
  7263. # Implementation depends on specific graph update logic
  7264.  
  7265. except Exception as e:
  7266. logger.error(f"Error updating memory graph: {e}")
  7267.  
  7268. async def get_memory_uncertainty(memory_id: str, user_id: str) -> Dict[str, float]:
  7269. """Get uncertainty metrics for a memory"""
  7270. # Simplified implementation
  7271. return {
  7272. 'aleatoric': 0.1,
  7273. 'epistemic': 0.05,
  7274. 'total': 0.15
  7275. }
  7276.  
  7277. async def get_episode_for_memory(memory) -> Optional[Any]:
  7278. """Retrieve episode for a memory"""
  7279. # Implementation would fetch from database
  7280. return None
  7281.  
  7282. def calculate_adaptation_quality(user_model: Dict[str, Any],
  7283. personalization: Dict[str, Any]) -> float:
  7284. """Calculate quality of personalization adaptation"""
  7285.  
  7286. # Simple quality metric based on stability and coverage
  7287. if not user_model.get('preference_vectors'):
  7288. return 0.5
  7289.  
  7290. stability = personalization.get('preferences', {}).get('preference_stability', 0.0)
  7291. coverage = len(personalization.get('preferences', {}).get('top_topics', [])) / 5.0
  7292.  
  7293. return (stability + coverage) / 2.0
  7294.  
  7295. async def get_total_memory_count() -> int:
  7296. """Get total memory count from database"""
  7297. import sqlite3
  7298. conn = sqlite3.connect(str(memory_system.db_path))
  7299. count = conn.execute("SELECT COUNT(*) FROM memories").fetchone()[0]
  7300. conn.close()
  7301. return count
  7302.  
  7303. async def get_precious_memory_count() -> int:
  7304. """Get precious memory count"""
  7305. import sqlite3
  7306. conn = sqlite3.connect(str(memory_system.db_path))
  7307. count = conn.execute(
  7308. "SELECT COUNT(*) FROM memories WHERE marked_precious = 1 OR never_forget = 1"
  7309. ).fetchone()[0]
  7310. conn.close()
  7311. return count
  7312.  
  7313. async def get_average_importance() -> float:
  7314. """Get average memory importance"""
  7315. import sqlite3
  7316. conn = sqlite3.connect(str(memory_system.db_path))
  7317. avg = conn.execute("SELECT AVG(importance) FROM memories").fetchone()[0] or 0.5
  7318. conn.close()
  7319. return float(avg)
  7320.  
  7321. def get_gpu_memory_usage() -> float:
  7322. """Get GPU memory usage in MB"""
  7323. if torch.cuda.is_available():
  7324. return torch.cuda.memory_allocated() / 1024 / 1024
  7325. return 0.0
  7326.  
  7327. def calculate_working_memory_utilization() -> float:
  7328. """Calculate working memory utilization"""
  7329. total_capacity = sum(
  7330. memory_system.working_memory.capacity
  7331. for _ in memory_system.active_episodes.values()
  7332. )
  7333.  
  7334. if total_capacity == 0:
  7335. return 0.0
  7336.  
  7337. used = sum(
  7338. len(memory_system.working_memory.episodic_buffer)
  7339. for _ in memory_system.active_episodes.values()
  7340. )
  7341.  
  7342. return used / total_capacity
  7343.  
  7344. def create_default_interface(path: Path):
  7345. """Create a default chat interface if none exists"""
  7346. html_content = """<!DOCTYPE html>
  7347. <html>
  7348. <head>
  7349. <title>Advanced AI Memory System</title>
  7350. <meta charset="UTF-8">
  7351. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7352. <style>
  7353. body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f0f0f0; }
  7354. .container { max-width: 1200px; margin: 0 auto; }
  7355. .chat-container { background: white; border-radius: 10px; padding: 20px; height: 600px; display: flex; flex-direction: column; }
  7356. .messages { flex-grow: 1; overflow-y: auto; padding: 10px; }
  7357. .message { margin: 10px 0; padding: 10px; border-radius: 5px; }
  7358. .user { background: #e3f2fd; text-align: right; }
  7359. .assistant { background: #f5f5f5; }
  7360. .input-area { display: flex; gap: 10px; margin-top: 20px; }
  7361. .input-area input { flex-grow: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
  7362. .input-area button { padding: 10px 20px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; }
  7363. .metrics { background: white; border-radius: 10px; padding: 20px; margin-top: 20px; }
  7364. .metric { display: inline-block; margin: 10px; padding: 10px; background: #f5f5f5; border-radius: 5px; }
  7365. </style>
  7366. </head>
  7367. <body>
  7368. <div class="container">
  7369. <h1>Advanced AI Memory System</h1>
  7370. <p>Chat interface with HippoRAG indexing, adaptive patterns, and multi-dimensional memory encoding.</p>
  7371.  
  7372. <div class="chat-container">
  7373. <div class="messages" id="messages"></div>
  7374. <div class="input-area">
  7375. <input type="text" id="messageInput" placeholder="Type your message...">
  7376. <button onclick="sendMessage()">Send</button>
  7377. </div>
  7378. </div>
  7379.  
  7380. <div class="metrics" id="metrics">
  7381. <h3>Session Metrics</h3>
  7382. <div class="metric">Trust Level: <span id="trustLevel">0%</span></div>
  7383. <div class="metric">Understanding Depth: <span id="understandingDepth">0%</span></div>
  7384. <div class="metric">Flow Quality: <span id="flowQuality">0%</span></div>
  7385. <div class="metric">Patterns Detected: <span id="patternsDetected">0</span></div>
  7386. </div>
  7387. </div>
  7388.  
  7389. <script>
  7390. const userId = 'user_' + Math.random().toString(36).substr(2, 9);
  7391. const sessionId = 'session_' + Math.random().toString(36).substr(2, 9);
  7392. let ws = null;
  7393.  
  7394. function connectWebSocket() {
  7395. ws = new WebSocket('ws://localhost:8000/ws');
  7396.  
  7397. ws.onopen = () => {
  7398. ws.send(JSON.stringify({ user_id: userId }));
  7399. };
  7400.  
  7401. ws.onmessage = (event) => {
  7402. const data = JSON.parse(event.data);
  7403. if (data.type === 'initial_stats') {
  7404. updateMetrics(data.data);
  7405. }
  7406. };
  7407.  
  7408. ws.onerror = (error) => {
  7409. console.error('WebSocket error:', error);
  7410. };
  7411. }
  7412.  
  7413. async function sendMessage() {
  7414. const input = document.getElementById('messageInput');
  7415. const message = input.value.trim();
  7416. if (!message) return;
  7417.  
  7418. // Display user message
  7419. addMessage(message, 'user');
  7420. input.value = '';
  7421.  
  7422. try {
  7423. const response = await fetch('/api/v3/exchange', {
  7424. method: 'POST',
  7425. headers: { 'Content-Type': 'application/json' },
  7426. body: JSON.stringify({
  7427. session_id: sessionId,
  7428. user_message: message,
  7429. user_id: userId,
  7430. metadata: {}
  7431. })
  7432. });
  7433.  
  7434. const data = await response.json();
  7435.  
  7436. // Display assistant response
  7437. addMessage(data.assistant_response, 'assistant');
  7438.  
  7439. // Update metrics
  7440. updateMetrics({
  7441. trust_level: data.personalization?.preferences?.preference_stability || 0,
  7442. understanding_depth: data.understanding_depth,
  7443. flow_quality: data.flow_quality,
  7444. patterns_detected: Object.keys(data.pattern_detection || {}).length
  7445. });
  7446.  
  7447. } catch (error) {
  7448. console.error('Error:', error);
  7449. addMessage('Error: Failed to get response', 'assistant');
  7450. }
  7451. }
  7452.  
  7453. function addMessage(text, sender) {
  7454. const messages = document.getElementById('messages');
  7455. const messageDiv = document.createElement('div');
  7456. messageDiv.className = 'message ' + sender;
  7457. messageDiv.textContent = text;
  7458. messages.appendChild(messageDiv);
  7459. messages.scrollTop = messages.scrollHeight;
  7460. }
  7461.  
  7462. function updateMetrics(data) {
  7463. if (data.trust_level !== undefined) {
  7464. document.getElementById('trustLevel').textContent = (data.trust_level * 100).toFixed(1) + '%';
  7465. }
  7466. if (data.understanding_depth !== undefined) {
  7467. document.getElementById('understandingDepth').textContent = (data.understanding_depth * 100).toFixed(1) + '%';
  7468. }
  7469. if (data.flow_quality !== undefined) {
  7470. document.getElementById('flowQuality').textContent = (data.flow_quality * 100).toFixed(1) + '%';
  7471. }
  7472. if (data.patterns_detected !== undefined) {
  7473. document.getElementById('patternsDetected').textContent = data.patterns_detected;
  7474. }
  7475. }
  7476.  
  7477. // Initialize
  7478. connectWebSocket();
  7479.  
  7480. // Enter key handler
  7481. document.getElementById('messageInput').addEventListener('keypress', (e) => {
  7482. if (e.key === 'Enter') sendMessage();
  7483. });
  7484. </script>
  7485. </body>
  7486. </html>"""
  7487.  
  7488. path.write_text(html_content)
  7489.  
  7490. # ==================== BACKGROUND TASKS ====================
  7491.  
  7492. async def consolidation_scheduler():
  7493. """Schedule periodic consolidation cycles"""
  7494. while True:
  7495. try:
  7496. # Run every 6 hours by default
  7497. await asyncio.sleep(3600 * 6)
  7498.  
  7499. if memory_system.config.get('enable_sleep_consolidation', True):
  7500. logger.info("Starting scheduled consolidation cycle")
  7501. await memory_system.sleep_engine.run_consolidation_cycle(90)
  7502.  
  7503. except asyncio.CancelledError:
  7504. break
  7505. except Exception as e:
  7506. logger.error(f"Error in consolidation scheduler: {e}")
  7507.  
  7508. async def pattern_optimization_loop():
  7509. """Periodically optimize pattern banks"""
  7510. while True:
  7511. try:
  7512. await asyncio.sleep(3600) # Every hour
  7513.  
  7514. for pattern_type, bank in memory_system.pattern_recognizer.pattern_banks.items():
  7515. if len(bank.patterns) > bank.capacity * 0.9:
  7516. # Rebuild index for optimization
  7517. bank._rebuild_index()
  7518. logger.info(f"Optimized {pattern_type} pattern bank")
  7519.  
  7520. except asyncio.CancelledError:
  7521. break
  7522. except Exception as e:
  7523. logger.error(f"Error in pattern optimization: {e}")
  7524.  
  7525. async def metrics_aggregation_loop():
  7526. """Aggregate metrics for monitoring"""
  7527. while True:
  7528. try:
  7529. await asyncio.sleep(300) # Every 5 minutes
  7530.  
  7531. # Aggregate and log metrics
  7532. metrics = {
  7533. 'active_sessions': len(memory_system.active_episodes),
  7534. 'total_patterns': sum(
  7535. len(bank.patterns)
  7536. for bank in memory_system.pattern_recognizer.pattern_banks.values()
  7537. ),
  7538. 'gpu_memory': get_gpu_memory_usage() if torch.cuda.is_available() else 0
  7539. }
  7540.  
  7541. logger.info(f"System metrics: {metrics}")
  7542.  
  7543. except asyncio.CancelledError:
  7544. break
  7545. except Exception as e:
  7546. logger.error(f"Error in metrics aggregation: {e}")
  7547.  
  7548. async def memory_decay_manager():
  7549. """Manage memory decay for non-precious memories"""
  7550. while True:
  7551. try:
  7552. await asyncio.sleep(3600 * 24) # Daily
  7553.  
  7554. # This would implement memory decay
  7555. # For now, just log
  7556. logger.info("Memory decay management cycle completed")
  7557.  
  7558. except asyncio.CancelledError:
  7559. break
  7560. except Exception as e:
  7561. logger.error(f"Error in memory decay manager: {e}")
  7562.  
  7563. async def automated_backup_scheduler():
  7564. """Backup to cloud every 6 hours"""
  7565. while True:
  7566. await asyncio.sleep(3600 * 6) # 6 hours
  7567.  
  7568. try:
  7569. # Your backup code here
  7570. logger.info("Starting automated cloud backup")
  7571.  
  7572. # Get all users
  7573. conn = sqlite3.connect(str(memory_system.db_path))
  7574. users = conn.execute("SELECT DISTINCT user_id FROM episodes").fetchall()
  7575. conn.close()
  7576.  
  7577. for (user_id,) in users:
  7578. # Create backup for each user
  7579. backup_data = await create_user_backup(user_id)
  7580.  
  7581. # Encrypt if configured
  7582. if os.getenv('BACKUP_ENCRYPTION_KEY'):
  7583. cipher = Fernet(os.getenv('BACKUP_ENCRYPTION_KEY').encode())
  7584. encrypted = cipher.encrypt(json.dumps(backup_data).encode())
  7585. else:
  7586. encrypted = json.dumps(backup_data).encode()
  7587.  
  7588. # Upload to S3 if configured
  7589. if os.getenv('AWS_S3_BUCKET'):
  7590. s3_client = boto3.client('s3')
  7591. key = f"claude-memories/{user_id}/{datetime.utcnow().isoformat()}.backup"
  7592. s3_client.put_object(
  7593. Bucket=os.getenv('AWS_S3_BUCKET'),
  7594. Key=key,
  7595. Body=encrypted
  7596. )
  7597.  
  7598. logger.info("Cloud backup completed")
  7599.  
  7600. except Exception as e:
  7601. logger.error(f"Backup failed: {e}")
  7602.  
  7603. # ==================== MAIN ENTRY POINT ====================
  7604.  
  7605. if __name__ == "__main__":
  7606. # Ensure required directories exist
  7607. Path("./data/ai_memories").mkdir(parents=True, exist_ok=True)
  7608. Path("./static").mkdir(exist_ok=True)
  7609.  
  7610. # Log startup information
  7611. print("\n" + "="*60)
  7612. print("ADVANCED AI MEMORY SYSTEM v3.0")
  7613. print("="*60)
  7614. print("\nCapabilities:")
  7615. print(" ✓ HippoRAG neurobiological indexing")
  7616. print(" ✓ TCN adaptive pattern recognition")
  7617. print(" ✓ Multi-dimensional memory encoding")
  7618. print(" ✓ Sleep-inspired consolidation")
  7619. print(" ✓ Privacy-preserving personalization")
  7620. print(" ✓ Advanced conversation flow analysis")
  7621. print(" ✓ Uncertainty quantification")
  7622. print("\nConfiguration:")
  7623. print(f" GPU: {'Available' if torch.cuda.is_available() else 'Not available'}")
  7624. print(f" API Key: {'Configured' if os.getenv('ANTHROPIC_API_KEY') != 'your-api-key' else 'Not configured (demo mode)'}")
  7625. print(f" Sleep Consolidation: {os.getenv('ENABLE_SLEEP_CONSOLIDATION', 'true')}")
  7626. print("\nStarting server at http://localhost:8000")
  7627. print("API documentation at http://localhost:8000/docs")
  7628. print("="*60 + "\n")
  7629.  
  7630. # Run the server
  7631. uvicorn.run(
  7632. "advanced_memory_api_complete:app",
  7633. host="0.0.0.0",
  7634. port=8000,
  7635. reload=True,
  7636. log_level="info"
  7637. )
  7638.  
  7639.  
  7640.  
Add Comment
Please, Sign In to add comment