Spaces:
Sleeping
Sleeping
| """Gestion mémoire long terme — port Mark-XLVIII.""" | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from datetime import datetime | |
| from pathlib import Path | |
| from threading import Lock | |
| _DESKTOP_DIR = Path(__file__).resolve().parent.parent | |
| MEMORY_PATH = _DESKTOP_DIR / "memory" / "long_term.json" | |
| _lock = Lock() | |
| MAX_VALUE_LENGTH = 380 | |
| MEMORY_MAX_CHARS = 2200 | |
| def _empty_memory() -> dict: | |
| return { | |
| "identity": {}, | |
| "preferences": {}, | |
| "projects": {}, | |
| "relationships": {}, | |
| "wishes": {}, | |
| "notes": {}, | |
| } | |
| def load_memory() -> dict: | |
| if not MEMORY_PATH.exists(): | |
| return _empty_memory() | |
| with _lock: | |
| try: | |
| data = json.loads(MEMORY_PATH.read_text(encoding="utf-8")) | |
| if isinstance(data, dict): | |
| base = _empty_memory() | |
| for key in base: | |
| if key not in data: | |
| data[key] = {} | |
| return data | |
| return _empty_memory() | |
| except Exception as e: | |
| print(f"[Memory] Load error: {e}") | |
| return _empty_memory() | |
| def _all_entries(memory: dict) -> list[tuple]: | |
| entries = [] | |
| for cat, items in memory.items(): | |
| if not isinstance(items, dict): | |
| continue | |
| for key, entry in items.items(): | |
| if isinstance(entry, dict) and "value" in entry: | |
| entries.append((cat, key, entry)) | |
| return entries | |
| def _trim_to_limit(memory: dict) -> dict: | |
| if len(json.dumps(memory, ensure_ascii=False)) <= MEMORY_MAX_CHARS: | |
| return memory | |
| entries = _all_entries(memory) | |
| entries.sort(key=lambda t: t[2].get("updated", "0000-00-00")) | |
| for cat, key, _ in entries: | |
| if len(json.dumps(memory, ensure_ascii=False)) <= MEMORY_MAX_CHARS: | |
| break | |
| del memory[cat][key] | |
| print(f"[Memory] Trimmed {cat}/{key}") | |
| return memory | |
| def save_memory(memory: dict) -> None: | |
| if not isinstance(memory, dict): | |
| return | |
| memory = _trim_to_limit(memory) | |
| MEMORY_PATH.parent.mkdir(parents=True, exist_ok=True) | |
| with _lock: | |
| MEMORY_PATH.write_text( | |
| json.dumps(memory, indent=2, ensure_ascii=False), | |
| encoding="utf-8", | |
| ) | |
| def _truncate_value(val: str) -> str: | |
| if isinstance(val, str) and len(val) > MAX_VALUE_LENGTH: | |
| return val[:MAX_VALUE_LENGTH].rstrip() + "…" | |
| return val | |
| def _recursive_update(target: dict, updates: dict) -> bool: | |
| changed = False | |
| for key, value in updates.items(): | |
| if value is None: | |
| continue | |
| if isinstance(value, str) and not value.strip(): | |
| continue | |
| if isinstance(value, dict) and "value" not in value: | |
| if key not in target or not isinstance(target[key], dict): | |
| target[key] = {} | |
| changed = True | |
| if _recursive_update(target[key], value): | |
| changed = True | |
| else: | |
| new_val = _truncate_value(str(value["value"] if isinstance(value, dict) else value)) | |
| entry = {"value": new_val, "updated": datetime.now().strftime("%Y-%m-%d")} | |
| existing = target.get(key, {}) | |
| if not isinstance(existing, dict) or existing.get("value") != new_val: | |
| target[key] = entry | |
| changed = True | |
| return changed | |
| def update_memory(memory_update: dict) -> dict: | |
| if not isinstance(memory_update, dict) or not memory_update: | |
| return load_memory() | |
| memory = load_memory() | |
| if _recursive_update(memory, memory_update): | |
| save_memory(memory) | |
| print(f"[Memory] Saved: {list(memory_update.keys())}") | |
| return memory | |
| def format_memory_for_prompt(memory: dict | None) -> str: | |
| if not memory: | |
| return "" | |
| lines = [] | |
| identity = memory.get("identity", {}) | |
| id_fields = ["name", "age", "birthday", "city", "job", "language", "school", "nationality"] | |
| for field in id_fields: | |
| entry = identity.get(field) | |
| if entry: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f"{field.title()}: {val}") | |
| for key, entry in identity.items(): | |
| if key in id_fields: | |
| continue | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f"{key.replace('_', ' ').title()}: {val}") | |
| prefs = memory.get("preferences", {}) | |
| if prefs: | |
| lines.append("") | |
| lines.append("Preferences:") | |
| for key, entry in list(prefs.items())[:15]: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f" - {key.replace('_', ' ').title()}: {val}") | |
| projects = memory.get("projects", {}) | |
| if projects: | |
| lines.append("") | |
| lines.append("Active Projects / Goals:") | |
| for key, entry in list(projects.items())[:8]: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f" - {key.replace('_', ' ').title()}: {val}") | |
| rels = memory.get("relationships", {}) | |
| if rels: | |
| lines.append("") | |
| lines.append("People in their life:") | |
| for key, entry in list(rels.items())[:10]: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f" - {key.replace('_', ' ').title()}: {val}") | |
| wishes = memory.get("wishes", {}) | |
| if wishes: | |
| lines.append("") | |
| lines.append("Wishes / Plans / Wants:") | |
| for key, entry in list(wishes.items())[:8]: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f" - {key.replace('_', ' ').title()}: {val}") | |
| notes = memory.get("notes", {}) | |
| if notes: | |
| lines.append("") | |
| lines.append("Other notes:") | |
| for key, entry in list(notes.items())[:8]: | |
| val = entry.get("value") if isinstance(entry, dict) else entry | |
| if val: | |
| lines.append(f" - {key}: {val}") | |
| if not lines: | |
| return "" | |
| header = "[WHAT YOU KNOW ABOUT THIS PERSON — use naturally, never recite like a list]\n" | |
| result = header + "\n".join(lines) | |
| if len(result) > 2000: | |
| result = result[:1997] + "…" | |
| return result + "\n" | |
| def remember(key: str, value: str, category: str = "notes") -> str: | |
| valid = {"identity", "preferences", "projects", "relationships", "wishes", "notes"} | |
| if category not in valid: | |
| category = "notes" | |
| update_memory({category: {key: {"value": value}}}) | |
| return f"Remembered: {category}/{key} = {value}" | |
| def forget(key: str, category: str = "notes") -> str: | |
| memory = load_memory() | |
| cat = memory.get(category, {}) | |
| if key in cat: | |
| del cat[key] | |
| memory[category] = cat | |
| save_memory(memory) | |
| return f"Forgotten: {category}/{key}" | |
| return f"Not found: {category}/{key}" | |
| forget_memory = forget | |