feat: Add Memory system with Weaviate integration and MCP tools

MEMORY SYSTEM ARCHITECTURE:
- Weaviate-based memory storage (Thought, Message, Conversation collections)
- GPU embeddings with BAAI/bge-m3 (1024-dim, RTX 4070)
- 9 MCP tools for Claude Desktop integration

CORE MODULES (memory/):
- core/embedding_service.py: GPU embedder singleton with PyTorch
- schemas/memory_schemas.py: Weaviate schema definitions
- mcp/thought_tools.py: add_thought, search_thoughts, get_thought
- mcp/message_tools.py: add_message, get_messages, search_messages
- mcp/conversation_tools.py: get_conversation, search_conversations, list_conversations

FLASK TEMPLATES:
- conversation_view.html: Display single conversation with messages
- conversations.html: List all conversations with search
- memories.html: Browse and search thoughts

FEATURES:
- Semantic search across thoughts, messages, conversations
- Privacy levels (private, shared, public)
- Thought types (reflection, question, intuition, observation)
- Conversation categories with filtering
- Message ordering and role-based display

DATA (as of 2026-01-08):
- 102 Thoughts
- 377 Messages
- 12 Conversations

DOCUMENTATION:
- memory/README_MCP_TOOLS.md: Complete API reference and usage examples

All MCP tools tested and validated (see test_memory_mcp_tools.py in archive).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-08 18:08:13 +01:00
parent 187ba4854e
commit 2f34125ef6
13 changed files with 2145 additions and 0 deletions

203
memory/mcp/thought_tools.py Normal file
View File

@@ -0,0 +1,203 @@
"""
Thought MCP Tools - Handlers for thought-related operations.
Provides tools for adding, searching, and retrieving thoughts from Weaviate.
"""
import weaviate
from datetime import datetime, timezone
from typing import Any, Dict
from pydantic import BaseModel, Field
from memory.core import get_embedder
class AddThoughtInput(BaseModel):
"""Input for add_thought tool."""
content: str = Field(..., description="The thought content")
thought_type: str = Field(default="reflection", description="Type: reflection, question, intuition, observation, etc.")
trigger: str = Field(default="", description="What triggered this thought")
concepts: list[str] = Field(default_factory=list, description="Related concepts/tags")
privacy_level: str = Field(default="private", description="Privacy: private, shared, public")
class SearchThoughtsInput(BaseModel):
"""Input for search_thoughts tool."""
query: str = Field(..., description="Search query text")
limit: int = Field(default=10, ge=1, le=100, description="Maximum results")
thought_type_filter: str | None = Field(default=None, description="Filter by thought type")
async def add_thought_handler(input_data: AddThoughtInput) -> Dict[str, Any]:
"""
Add a new thought to Weaviate.
Args:
input_data: Thought data to add.
Returns:
Dictionary with success status and thought UUID.
"""
try:
# Connect to Weaviate
client = weaviate.connect_to_local()
try:
# Get embedder
embedder = get_embedder()
# Generate vector for thought content
vector = embedder.embed_batch([input_data.content])[0]
# Get collection
collection = client.collections.get("Thought")
# Insert thought
uuid = collection.data.insert(
properties={
"content": input_data.content,
"thought_type": input_data.thought_type,
"timestamp": datetime.now(timezone.utc).isoformat(),
"trigger": input_data.trigger,
"concepts": input_data.concepts,
"privacy_level": input_data.privacy_level,
"emotional_state": "",
"context": "",
},
vector=vector.tolist()
)
return {
"success": True,
"uuid": str(uuid),
"content": input_data.content[:100] + "..." if len(input_data.content) > 100 else input_data.content,
"thought_type": input_data.thought_type,
}
finally:
client.close()
except Exception as e:
return {
"success": False,
"error": str(e),
}
async def search_thoughts_handler(input_data: SearchThoughtsInput) -> Dict[str, Any]:
"""
Search thoughts using semantic similarity.
Args:
input_data: Search parameters.
Returns:
Dictionary with search results.
"""
try:
# Connect to Weaviate
client = weaviate.connect_to_local()
try:
# Get embedder
embedder = get_embedder()
# Generate query vector
query_vector = embedder.embed_batch([input_data.query])[0]
# Get collection
collection = client.collections.get("Thought")
# Build query
query = collection.query.near_vector(
near_vector=query_vector.tolist(),
limit=input_data.limit,
)
# Apply thought_type filter if provided
if input_data.thought_type_filter:
query = query.where({
"path": ["thought_type"],
"operator": "Equal",
"valueText": input_data.thought_type_filter,
})
# Execute search
results = query.objects
# Format results
thoughts = []
for obj in results:
thoughts.append({
"uuid": str(obj.uuid),
"content": obj.properties['content'],
"thought_type": obj.properties['thought_type'],
"timestamp": obj.properties['timestamp'],
"trigger": obj.properties.get('trigger', ''),
"concepts": obj.properties.get('concepts', []),
})
return {
"success": True,
"query": input_data.query,
"results": thoughts,
"count": len(thoughts),
}
finally:
client.close()
except Exception as e:
return {
"success": False,
"error": str(e),
}
async def get_thought_handler(uuid: str) -> Dict[str, Any]:
"""
Get a specific thought by UUID.
Args:
uuid: Thought UUID.
Returns:
Dictionary with thought data.
"""
try:
# Connect to Weaviate
client = weaviate.connect_to_local()
try:
# Get collection
collection = client.collections.get("Thought")
# Fetch by UUID
obj = collection.query.fetch_object_by_id(uuid)
if not obj:
return {
"success": False,
"error": f"Thought {uuid} not found",
}
return {
"success": True,
"uuid": str(obj.uuid),
"content": obj.properties['content'],
"thought_type": obj.properties['thought_type'],
"timestamp": obj.properties['timestamp'],
"trigger": obj.properties.get('trigger', ''),
"concepts": obj.properties.get('concepts', []),
"privacy_level": obj.properties.get('privacy_level', 'private'),
"emotional_state": obj.properties.get('emotional_state', ''),
"context": obj.properties.get('context', ''),
}
finally:
client.close()
except Exception as e:
return {
"success": False,
"error": str(e),
}