CoolFace
Modelpublic

ScottzillaSystems/conversation-memory

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
memory_system.py141 linesDownload Raw Back to root
1"""2Conversation Memory System for ScottzillaSystems3Self-contained โ€” no external APIs needed4Uses SQLite + sentence-transformers for local embeddings5Replaces the fraudulent MemPalace project with a real working system.6"""7 8import os9import json10import sqlite311import hashlib12from datetime import datetime13from typing import List, Dict, Optional14 15try:16    from sentence_transformers import SentenceTransformer17    EMBEDDING_MODEL = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')18    USE_VECTOR_SEARCH = True19except Exception:20    EMBEDDING_MODEL = None21    USE_VECTOR_SEARCH = False22    print("[Memory] sentence-transformers not available, using text search fallback")23 24 25class ConversationMemory:26    def __init__(self, db_path: str = "./memory_db/conversations.db", user_id: str = "scottzilla"):27        self.db_path = db_path28        self.user_id = user_id29        os.makedirs(os.path.dirname(db_path), exist_ok=True)30        self.conn = sqlite3.connect(db_path)31        self._init_db()32 33    def _init_db(self):34        self.conn.execute("""35            CREATE TABLE IF NOT EXISTS memories (36                id INTEGER PRIMARY KEY AUTOINCREMENT,37                user_id TEXT NOT NULL,38                thread_id TEXT NOT NULL,39                timestamp TEXT NOT NULL,40                role TEXT NOT NULL,41                content TEXT NOT NULL,42                content_hash TEXT NOT NULL,43                metadata TEXT,44                embedding BLOB45            )46        """)47        self.conn.execute("CREATE INDEX IF NOT EXISTS idx_user_thread ON memories(user_id, thread_id, timestamp)")48        self.conn.execute("CREATE INDEX IF NOT EXISTS idx_content ON memories(content)")49        self.conn.commit()50 51    def _get_embedding(self, text: str) -> Optional[bytes]:52        if not USE_VECTOR_SEARCH or EMBEDDING_MODEL is None:53            return None54        try:55            embedding = EMBEDDING_MODEL.encode(text, convert_to_numpy=True)56            return embedding.tobytes()57        except Exception as e:58            print(f"[Memory] Embedding error: {e}")59            return None60 61    def save_message(self, role: str, content: str, thread_id: str, metadata: Optional[Dict] = None) -> Dict:62        timestamp = datetime.utcnow().isoformat()63        content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]64        embedding = self._get_embedding(content)65        meta_json = json.dumps(metadata or {})66        cursor = self.conn.execute(67            "INSERT INTO memories (user_id, thread_id, timestamp, role, content, content_hash, metadata, embedding) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",68            (self.user_id, thread_id, timestamp, role, content, content_hash, meta_json, embedding)69        )70        self.conn.commit()71        return {"id": cursor.lastrowid, "thread_id": thread_id, "timestamp": timestamp, "role": role, "content": content, "metadata": metadata or {}}72 73    def save_conversation(self, messages: List[Dict], thread_id: str, title: Optional[str] = None) -> List[Dict]:74        results = []75        for msg in messages:76            result = self.save_message(77                role=msg.get("role", "unknown"),78                content=msg.get("content", ""),79                thread_id=thread_id,80                metadata={"title": title, **msg.get("metadata", {})}81            )82            results.append(result)83        return results84 85    def get_thread(self, thread_id: str, limit: int = 1000) -> List[Dict]:86        cursor = self.conn.execute(87            "SELECT id, timestamp, role, content, metadata FROM memories WHERE user_id = ? AND thread_id = ? ORDER BY timestamp ASC LIMIT ?",88            (self.user_id, thread_id, limit)89        )90        rows = cursor.fetchall()91        return [{"id": row[0], "timestamp": row[1], "role": row[2], "content": row[3], "metadata": json.loads(row[4])} for row in rows]92 93    def search(self, query: str, thread_id: Optional[str] = None, limit: int = 20) -> List[Dict]:94        if thread_id:95            cursor = self.conn.execute(96                "SELECT id, timestamp, role, content, metadata FROM memories WHERE user_id = ? AND thread_id = ? AND content LIKE ? ORDER BY timestamp DESC LIMIT ?",97                (self.user_id, thread_id, f"%{query}%", limit)98            )99            rows = cursor.fetchall()100            return [{"id": r[0], "timestamp": r[1], "role": r[2], "content": r[3], "metadata": json.loads(r[4])} for r in rows]101        else:102            cursor = self.conn.execute(103                "SELECT id, timestamp, role, content, metadata, thread_id FROM memories WHERE user_id = ? AND content LIKE ? ORDER BY timestamp DESC LIMIT ?",104                (self.user_id, f"%{query}%", limit)105            )106            rows = cursor.fetchall()107            return [{"id": r[0], "timestamp": r[1], "role": r[2], "content": r[3], "metadata": json.loads(r[4]), "thread_id": r[5]} for r in rows]108 109    def get_all_threads(self) -> List[Dict]:110        cursor = self.conn.execute(111            "SELECT thread_id, COUNT(*) as msg_count, MIN(timestamp) as started, MAX(timestamp) as last_msg FROM memories WHERE user_id = ? GROUP BY thread_id ORDER BY last_msg DESC",112            (self.user_id,)113        )114        rows = cursor.fetchall()115        return [{"thread_id": row[0], "message_count": row[1], "started": row[2], "last_message": row[3]} for row in rows]116 117    def export_to_json(self, filepath: str, thread_id: Optional[str] = None):118        if thread_id:119            memories = self.get_thread(thread_id)120        else:121            cursor = self.conn.execute("SELECT id, timestamp, role, content, metadata, thread_id FROM memories WHERE user_id = ? ORDER BY timestamp", (self.user_id,))122            memories = [{"id": r[0], "timestamp": r[1], "role": r[2], "content": r[3], "metadata": json.loads(r[4]), "thread_id": r[5]} for r in cursor.fetchall()]123        with open(filepath, 'w') as f:124            json.dump(memories, f, indent=2)125        print(f"[Memory] Exported {len(memories)} memories to {filepath}")126 127    def export_to_markdown(self, filepath: str, thread_id: str):128        memories = self.get_thread(thread_id)129        with open(filepath, 'w') as f:130            f.write(f"# Conversation: {thread_id}\n\n*Exported: {datetime.utcnow().isoformat()}*\n\n---\n\n")131            for mem in memories:132                role = mem.get("role", "unknown")133                timestamp = mem.get("timestamp", "unknown")134                content = mem.get("content", "")135                emoji = "๐Ÿ‘ค" if role == "user" else "๐Ÿค–" if role == "assistant" else "๐Ÿ“"136                f.write(f"### {emoji} {role.title()} *({timestamp})*\n\n{content}\n\n---\n\n")137        print(f"[Memory] Exported conversation to {filepath}")138 139    def close(self):140        self.conn.close()141