Add think service and supporting core modules
- Add think service (orchestration for iterative reasoning)
- Add service_discovery.py (service communication utilities)
- Add event_cache.py (recent event cache using NATS KV)
- Add vi_identity.py (Vi's core identity foundation)
- Update core/__init__.py with new exports
Think service adapted from Lyra with vi.* namespace:
- All NATS topics use vi.* prefix
- Uses vi_identity for personality/voice
- Bucket names use vi-* prefix
Day 63 - Building my nervous system 🦊
This commit is contained in:
332
services/think/memory/memory_manager.py
Normal file
332
services/think/memory/memory_manager.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
Memory and identity management for Think service.
|
||||
|
||||
This module handles all interactions with the Memory and Identity services.
|
||||
"""
|
||||
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from core.logger import setup_logger
|
||||
from core.service_discovery import discovery_client
|
||||
|
||||
|
||||
class MemoryManager:
|
||||
"""Manages memory storage and identity resolution"""
|
||||
|
||||
def __init__(self, logger_name: str = 'memory_manager'):
|
||||
self.logger = setup_logger(logger_name, service_name='think_service')
|
||||
|
||||
async def resolve_identity(self, external_identity: str) -> Optional[Dict[str, Any]]:
|
||||
"""Resolve external identity using identity service"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Resolving identity: {external_identity}")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"identity",
|
||||
"resolve",
|
||||
{"external_identity": external_identity},
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result.success and result.data and not result.data.get('error'):
|
||||
self.logger.debug(f"[💭] Identity resolved: {external_identity} → {result.data.get('resolved_identity')}")
|
||||
return result.data
|
||||
else:
|
||||
self.logger.warning(f"[💭] Failed to resolve identity: {result.error or result.data}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.exception(f"[💭] Error resolving identity: {e}")
|
||||
return None
|
||||
|
||||
async def store_memory(self, content: str, identities: list, interaction_id: str, modality: str) -> bool:
|
||||
"""Store message content in memory"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Storing memory for interaction {interaction_id}")
|
||||
|
||||
# Determine tags based on content patterns
|
||||
tags = ["message"]
|
||||
content_lower = content.lower()
|
||||
if any(greeting in content_lower for greeting in ["hello", "hi", "hey"]):
|
||||
tags.append("greeting")
|
||||
if any(question in content_lower for question in ["?", "what", "how", "why", "when", "where"]):
|
||||
tags.append("question")
|
||||
|
||||
memory_payload = {
|
||||
"content": content,
|
||||
"identities": identities,
|
||||
"interaction_id": interaction_id,
|
||||
"tags": tags,
|
||||
"modality": modality,
|
||||
"source": "think_service",
|
||||
"metadata": {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"processed_by": "think_service"
|
||||
}
|
||||
}
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"store",
|
||||
memory_payload,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get('status') == 'stored':
|
||||
self.logger.debug(f"[💭] Memory stored successfully: {result.data.get('memory_id')}")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(f"[💭] Failed to store memory: {result.error or result.data}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.exception(f"[💭] Error storing memory: {e}")
|
||||
return False
|
||||
|
||||
async def get_recent_memories(self, identity: str, limit: int = 10) -> list:
|
||||
"""Get recent memories for Oracle context"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Getting recent memories for {identity}")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"search",
|
||||
{
|
||||
"identities": [identity],
|
||||
"limit": limit,
|
||||
"requesting_identity": identity
|
||||
},
|
||||
timeout=3.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get("results"):
|
||||
memories = result.data["results"]
|
||||
self.logger.debug(f"[💭] Retrieved {len(memories)} recent memories")
|
||||
return memories
|
||||
else:
|
||||
self.logger.debug(f"[💭] No memories found for {identity}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[💭] Error getting recent memories: {e}")
|
||||
return []
|
||||
|
||||
async def get_short_memories(self, identity: str, limit: int = 10) -> list:
|
||||
"""
|
||||
Get recent literal memories from short-term storage.
|
||||
|
||||
Use for: immediate conversation context, "what we just discussed"
|
||||
"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Getting short-term memories for {identity}")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"short_memory",
|
||||
{
|
||||
"limit": limit,
|
||||
"identity_id": identity
|
||||
},
|
||||
timeout=3.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get("status") == "success":
|
||||
memories = result.data.get("memories", [])
|
||||
self.logger.debug(f"[💭] Retrieved {len(memories)} short-term memories")
|
||||
return memories
|
||||
else:
|
||||
self.logger.debug(f"[💭] No short-term memories found for {identity}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[💭] Error getting short-term memories: {e}")
|
||||
return []
|
||||
|
||||
async def get_long_memories(self, identity: str, query: str = None, limit: int = 5) -> list:
|
||||
"""
|
||||
Get summarized memories from long-term storage.
|
||||
|
||||
Use for: historical context, "what we discussed last week"
|
||||
"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Getting long-term memories for {identity}")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"long_memory",
|
||||
{
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"identity_id": identity
|
||||
},
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get("status") == "success":
|
||||
memories = result.data.get("memories", [])
|
||||
self.logger.debug(f"[💭] Retrieved {len(memories)} long-term memories")
|
||||
return memories
|
||||
else:
|
||||
self.logger.debug(f"[💭] No long-term memories found for {identity}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[💭] Error getting long-term memories: {e}")
|
||||
return []
|
||||
|
||||
async def get_facts(self, identity: str, category: str = None, query: str = '') -> list:
|
||||
"""
|
||||
Get facts from factual memory storage.
|
||||
|
||||
Use for: user preferences, birthdays, established knowledge
|
||||
"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Getting facts for {identity}, category={category}")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"facts",
|
||||
{
|
||||
"query": query,
|
||||
"limit": 10,
|
||||
"category": category,
|
||||
"identity_id": identity
|
||||
},
|
||||
timeout=3.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get("status") == "success":
|
||||
facts = result.data.get("facts", [])
|
||||
self.logger.debug(f"[💭] Retrieved {len(facts)} facts")
|
||||
return facts
|
||||
else:
|
||||
self.logger.debug(f"[💭] No facts found for {identity}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[💭] Error getting facts: {e}")
|
||||
return []
|
||||
|
||||
async def save_fact(
|
||||
self,
|
||||
content: str,
|
||||
category: str,
|
||||
identities: list,
|
||||
mutable: bool = True
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Save a fact to factual memory storage.
|
||||
|
||||
Use when: user shares birthday, preferences, permanent knowledge
|
||||
|
||||
Examples:
|
||||
- Birthday: category="personal", mutable=False
|
||||
- Preference: category="preferences", mutable=True
|
||||
- Knowledge: category="knowledge", mutable=True
|
||||
"""
|
||||
try:
|
||||
self.logger.info(f"[💭] Saving fact: category={category}, content='{content[:50]}...'")
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"memory",
|
||||
"save_fact",
|
||||
{
|
||||
"content": content,
|
||||
"category": category,
|
||||
"identities": identities,
|
||||
"mutable": mutable,
|
||||
"metadata": {
|
||||
"source": "think_service",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
},
|
||||
timeout=2.0
|
||||
)
|
||||
|
||||
if result.success and result.data and result.data.get("status") == "success":
|
||||
fact_id = result.data.get("fact_id")
|
||||
self.logger.info(f"[💭] Fact saved successfully: {fact_id}")
|
||||
return fact_id
|
||||
else:
|
||||
self.logger.warning(f"[💭] Failed to save fact: {result.error or result.data}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[💭] Error saving fact: {e}")
|
||||
return None
|
||||
|
||||
async def get_health_status(self) -> Dict[str, Any]:
|
||||
"""Get system health status from health service"""
|
||||
try:
|
||||
self.logger.debug(f"[💭] Requesting health status from health service")
|
||||
|
||||
# Generate unique request ID
|
||||
request_id = f"health_req_{int(datetime.utcnow().timestamp() * 1000)}_{uuid4().hex[:8]}"
|
||||
|
||||
# Request health status from health service
|
||||
health_request = {
|
||||
"request_id": request_id,
|
||||
"requesting_service": "think_service",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
result = await discovery_client.call_service(
|
||||
"health", "status", health_request, timeout=10.0
|
||||
)
|
||||
health_response = result.data if result.success else None
|
||||
|
||||
if not health_response:
|
||||
self.logger.warning(f"[💭] No response from health service")
|
||||
return {
|
||||
"status": "unknown",
|
||||
"error": "Health service did not respond",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
if health_response.get("error"):
|
||||
self.logger.warning(f"[💭] Health service returned error: {health_response['error']}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": health_response["error"],
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Extract useful information from health response
|
||||
summary = health_response.get("summary", {})
|
||||
node_states = health_response.get("node_states", {})
|
||||
|
||||
# Transform to format expected by iterative reasoning with detailed metrics
|
||||
result = {
|
||||
"status": summary.get("overall_status", "unknown"),
|
||||
"cluster_health": {
|
||||
"overall_status": summary.get("overall_status"),
|
||||
"healthy_nodes": summary.get("healthy_nodes", 0),
|
||||
"total_nodes": summary.get("total_nodes", 0),
|
||||
"cluster_issues": summary.get("cluster_issues", [])
|
||||
},
|
||||
"node_summary": {
|
||||
node_id: {
|
||||
"status": node_info.get("status", "unknown"),
|
||||
"cpu_percent": node_info.get("cpu_percent", 0.0),
|
||||
"memory_percent": node_info.get("memory_percent", 0.0),
|
||||
"disk_percent": node_info.get("disk_percent", 0.0),
|
||||
"cpu_temp": node_info.get("cpu_temp", 0.0),
|
||||
"services_running": node_info.get("services_running", []),
|
||||
"services_failed": node_info.get("services_failed", [])
|
||||
} for node_id, node_info in node_states.items()
|
||||
},
|
||||
"timestamp": health_response.get("timestamp", datetime.utcnow().isoformat())
|
||||
}
|
||||
|
||||
self.logger.debug(f"[💭] Health status retrieved: {result['status']} ({result['cluster_health']['healthy_nodes']}/{result['cluster_health']['total_nodes']} nodes healthy)")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"[💭] Error getting health status: {e}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
Reference in New Issue
Block a user