phase2-risk-loop-mempalace
CI / gate (push) Has been cancelled

This commit is contained in:
QihuAI Design committed 2026-07-28 09:18:16 +08:00
1 parent 88b9a232ce
commit a9d8642db5
4 files changed
+331

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
"""
Phase 2 #2B: MemPalace Hierarchical Memory
Wing (User) → Room (Conversation) → Drawer (Topic)
Three-level memory index with priority retrieval.
"""
from typing import List, Dict, Optional
from datetime import datetime
class MemPalace:
"""Hierarchical memory system with Wing/Room/Drawer indexing."""
LEVELS = {"wing": 0, "room": 1, "drawer": 2}
def __init__(self, user_id: str, conv_id: Optional[str] = None):
self.user_id = user_id
self.conv_id = conv_id
async def remember(self, content: str, level: str = "room",
drawer_topic: str = "", ttl_days: int = 30) -> dict:
"""Store a memory at the specified level."""
try:
from app.database import SessionLocal
from sqlalchemy import text
import uuid
db = SessionLocal()
mem_id = str(uuid.uuid4())
db.execute(text(
"INSERT INTO memories (id, user_id, conv_id, content, level, drawer_topic, "
"ttl_days, created_at) VALUES (:id, :uid, :cid, :c, :l, :dt, :ttl, NOW())"
), {"id": mem_id, "uid": self.user_id, "cid": self.conv_id,
"c": content[:2000], "l": level, "dt": drawer_topic, "ttl": ttl_days})
db.commit()
db.close()
return {"id": mem_id, "status": "stored", "level": level}
except Exception as e:
return {"status": "error", "error": str(e)}
async def recall(self, query: str, max_results: int = 5,
level_filter: str = None) -> List[dict]:
"""Retrieve memories by relevance, respecting hierarchy."""
try:
from app.database import SessionLocal
from sqlalchemy import text
db = SessionLocal()
# Priority: wing(user) > room(conv) > drawer(topic)
params = {"uid": self.user_id, "cid": self.conv_id or ""}
where = "user_id = :uid AND (conv_id = :cid OR conv_id IS NULL)"
if level_filter:
where += f" AND level = :lv"
params["lv"] = level_filter
# Simulated relevance via LIKE (in production, use pgvector)
search = f"%{query}%"
rows = db.execute(text(
f"SELECT id, content, level, drawer_topic, created_at FROM memories "
f"WHERE {where} AND (content ILIKE :q OR drawer_topic ILIKE :q) "
f"ORDER BY CASE level WHEN 'drawer' THEN 0 WHEN 'room' THEN 1 WHEN 'wing' THEN 2 END, "
f"created_at DESC LIMIT :lim"
), {**params, "q": search, "lim": max_results}).fetchall()
db.close()
return [{"id": r[0], "content": r[1], "level": r[2],
"topic": r[3], "created": str(r[4])} for r in rows]
except Exception:
return []
async def forget(self, level: str = None, older_than_days: int = None) -> int:
"""Purge old or level-specific memories."""
try:
from app.database import SessionLocal
from sqlalchemy import text
db = SessionLocal()
where = "user_id = :uid"
params = {"uid": self.user_id}
if level:
where += " AND level = :lv"
params["lv"] = level
if older_than_days:
where += " AND created_at < NOW() - make_interval(days => :days)"
params["days"] = older_than_days
result = db.execute(text(f"DELETE FROM memories WHERE {where}"), params)
db.commit()
db.close()
return result.rowcount
except Exception:
return 0
async def get_context(self, max_tokens: int = 2000) -> str:
"""Get compact memory context for LLM prompt injection."""
memories = await self.recall("", max_results=10)
if not memories:
return ""
lines = ["[Memory Context]"]
for m in memories:
lines.append(f"[{m['level']}] {m['content'][:200]}")
return "\n".join(lines)[:max_tokens]