| """ |
| Project Jarvis — Memory Manager |
| Handles long-term cognitive consolidation and automated memory extraction. |
| """ |
|
|
| import logging |
| import json |
| from datetime import datetime |
| from typing import Optional, List, Dict |
|
|
| from app.services import llm |
| from app.core import context |
|
|
| logger = logging.getLogger("friday.memory") |
|
|
| SUMMARIZATION_PROMPT = """ |
| Summarize the following conversation history between FRIDAY and the user. |
| Extract: |
| 1. Key facts about the user (preferences, names mentioned). |
| 2. Status of ongoing tasks. |
| 3. Summary of the most important context. |
| |
| Output format: JSON with "summary", "facts", and "tasks". |
| """ |
|
|
| def consolidate_session(session_id: str): |
| """ |
| Summarizes the session and saves key facts to the persistent memory. |
| This should be called periodically or at the end of a long interaction. |
| """ |
| logger.info(f"Starting cognitive consolidation for session: {session_id}") |
| |
| turns = context.get_conversation_messages(session_id) |
| if not turns or len(turns) < 4: |
| logger.info("Session too short for consolidation.") |
| return |
|
|
| |
| history_str = "\n".join([f"{t['role']}: {t['content']}" for t in turns]) |
| prompt = f"{SUMMARIZATION_PROMPT}\n\n[HISTORY]\n{history_str}" |
| |
| try: |
| raw_result = llm.chat( |
| messages=[{"role": "user", "content": prompt}], |
| system_prompt="You are a data extraction engine. Respond ONLY with valid JSON.", |
| use_tools=False |
| ) |
| |
| |
| result = _parse_json_flexible(raw_result) |
| |
| |
| if result.get("facts"): |
| for fact in result["facts"]: |
| |
| |
| entities = context.get_last_entities(session_id) |
| entities.update(fact if isinstance(fact, dict) else {"extracted_fact": fact}) |
| context.set_metadata(session_id, "entities", entities) |
| logger.info(f"✓ Extracted and saved fact: {fact}") |
|
|
| logger.info(f"✓ Consolidation complete. Summary: {result.get('summary', 'No summary generated.')}") |
|
|
| except Exception as e: |
| logger.error(f"Memory consolidation failed: {e}") |
|
|
| def _parse_json_flexible(text: str) -> dict: |
| """Safely parse JSON from LLM output even if wrapped in markdown blocks.""" |
| text = text.strip() |
| if text.startswith("```"): |
| lines = text.split("\n") |
| if lines[0].startswith("```"): |
| lines = lines[1:] |
| if lines[-1].strip() == "```": |
| lines = lines[:-1] |
| text = "\n".join(lines).strip() |
| |
| try: |
| return json.loads(text) |
| except json.JSONDecodeError: |
| logger.warning("Failed to parse JSON exactly, attempting fallback.") |
| return {"summary": text, "facts": [], "tasks": []} |
|
|