Spaces:
Running
Running
File size: 11,191 Bytes
588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 3f76fc7 588592f 70a3640 588592f 70a3640 588592f 3f76fc7 588592f 3f76fc7 588592f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | """
Memory Manager - Graphiti Knowledge Graph Interface.
Provides unified memory operations using the same Graphiti instance
configured for Claude Code's Graphiti MCP server.
Configuration (must match Graphiti MCP):
- FALKORDB_URI: redis://localhost:6379 (default)
- FALKORDB_DATABASE: graphiti (default)
- MISTRAL_API_KEY: Required for entity extraction
- GRAPHITI_GROUP_ID: main (default)
"""
from __future__ import annotations
import os
from datetime import datetime, timezone
from typing import Annotated, Literal, Optional
import gradio as gr
from ._docstrings import autodoc
# Graphiti configuration - matches Graphiti MCP server
FALKORDB_URI = os.getenv("FALKORDB_URI", "redis://localhost:6379")
FALKORDB_DATABASE = os.getenv("FALKORDB_DATABASE", "graphiti")
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY", "")
GRAPHITI_GROUP_ID = os.getenv("GRAPHITI_GROUP_ID", "main")
# Check if Graphiti is available
GRAPHITI_AVAILABLE = bool(MISTRAL_API_KEY)
# Lazy-loaded Graphiti client
_graphiti_client = None
def _get_graphiti_client():
"""Get or create the Graphiti client (lazy load to avoid import errors)."""
global _graphiti_client
if _graphiti_client is None and GRAPHITI_AVAILABLE:
try:
from graphiti_core import Graphiti
from graphiti_core.llm_client import OpenAIClient
from graphiti_core.driver.falkordb_driver import FalkorDriver
# Create FalkorDB driver
driver = FalkorDriver(
uri=FALKORDB_URI,
database=FALKORDB_DATABASE,
)
# Create Mistral LLM client (OpenAI-compatible API)
llm_client = OpenAIClient(
api_key=MISTRAL_API_KEY,
base_url="https://api.mistral.ai/v1",
model="mistral-large-2411",
)
# Create Graphiti client
_graphiti_client = Graphiti(
uri=FALKORDB_URI,
driver=driver,
llm_client=llm_client,
)
except ImportError as e:
print(f"[Memory_Manager] Graphiti not available: {e}")
return None
except Exception as e:
print(f"[Memory_Manager] Failed to initialize Graphiti: {e}")
return None
return _graphiti_client
def _format_timestamp() -> str:
"""Return current UTC timestamp in ISO format."""
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
# ============================================================================
# Graphiti Memory Operations
# ============================================================================
def _graphiti_save(text: str, tags: str) -> str:
"""Save memory to Graphiti knowledge graph."""
if not GRAPHITI_AVAILABLE:
return "Error: MISTRAL_API_KEY not set. Cannot save to Graphiti."
client = _get_graphiti_client()
if not client:
return "Error: Failed to initialize Graphiti client."
try:
# Build episode body with tags
episode_body = text.strip()
if tags and tags.strip():
episode_body = f"{text.strip()}\n\nTags: {tags.strip()}"
# Add episode to Graphiti
import asyncio
async def _save():
return await client.add_episode(
name=f"Memory {_format_timestamp()}",
episode_body=episode_body,
source_description="Memory_Manager tool",
group_id=GRAPHITI_GROUP_ID,
)
result = asyncio.run(_save())
return f"Memory saved to Graphiti knowledge graph (group: {GRAPHITI_GROUP_ID})"
except Exception as e:
return f"Error saving to Graphiti: {e}"
def _graphiti_list(limit: int, include_tags: bool) -> str:
"""List recent memories from Graphiti."""
if not GRAPHITI_AVAILABLE:
return "Error: MISTRAL_API_KEY not set. Cannot access Graphiti."
client = _get_graphiti_client()
if not client:
return "Error: Failed to initialize Graphiti client."
try:
import asyncio
async def _list():
# Get episodes from Graphiti
return await client.get_episodes(
group_ids=[GRAPHITI_GROUP_ID],
limit=limit,
)
episodes = asyncio.run(_list())
if not episodes:
return f"No memories found in Graphiti (group: {GRAPHITI_GROUP_ID})"
lines = [f"Graphiti Memories (group: {GRAPHITI_GROUP_ID})", "-" * 50]
for ep in episodes:
name = ep.name if hasattr(ep, "name") else "?"
created = ep.created_at if hasattr(ep, "created_at") else "?"
content = ep.content if hasattr(ep, "content") else str(ep)
# Extract tags from content if present
tags_str = ""
if include_tags and "Tags:" in content:
parts = content.split("Tags:")
if len(parts) > 1:
tags_str = f" | tags: {parts[1].strip()}"
content = parts[0].strip()
lines.append(f"[{created}] {content[:100]}{'...' if len(content) > 100 else ''}{tags_str}")
return "\n".join(lines)
except Exception as e:
return f"Error listing from Graphiti: {e}"
def _graphiti_search(query: str, limit: int) -> str:
"""Search memories in Graphiti knowledge graph."""
if not GRAPHITI_AVAILABLE:
return "Error: MISTRAL_API_KEY not set. Cannot search Graphiti."
client = _get_graphiti_client()
if not client:
return "Error: Failed to initialize Graphiti client."
try:
import asyncio
async def _search():
# Use Graphiti's hybrid search
return await client.search(
query=query,
group_ids=[GRAPHITI_GROUP_ID],
num_results=limit,
)
results = asyncio.run(_search())
if not results:
return f"No matches found for: {query}"
lines = [f"Graphiti Search Results for: {query}", "-" * 50]
for i, result in enumerate(results, 1):
if hasattr(result, "fact"):
# Edge/fact result
source = getattr(result, "source_node", "?")
target = getattr(result, "target_node", "?")
fact = result.fact
lines.append(f"{i}. {source} -> {target}: {fact}")
elif hasattr(result, "name"):
# Node result
name = result.name
summary = getattr(result, "summary", "")
lines.append(f"{i}. [{name}] {summary[:150]}{'...' if len(summary) > 150 else ''}")
else:
lines.append(f"{i}. {str(result)[:150]}")
return "\n".join(lines)
except Exception as e:
return f"Error searching Graphiti: {e}"
def _graphiti_delete(memory_id: str) -> str:
"""Delete memory from Graphiti (requires episode UUID)."""
if not GRAPHITI_AVAILABLE:
return "Error: MISTRAL_API_KEY not set. Cannot access Graphiti."
# Note: Graphiti deletion requires the full episode UUID
# This is a simplified implementation
return f"Note: To delete from Graphiti, use the Graphiti MCP directly with the episode UUID. Memory deletion is not fully implemented in this interface."
# ============================================================================
# Status Check
# ============================================================================
def _get_status() -> str:
"""Get Graphiti connection status."""
if not GRAPHITI_AVAILABLE:
return "Status: MISTRAL_API_KEY not configured"
client = _get_graphiti_client()
if client:
return f"Status: Connected to Graphiti\nDatabase: {FALKORDB_DATABASE}\nGroup: {GRAPHITI_GROUP_ID}"
return "Status: Failed to initialize Graphiti client"
# ============================================================================
# Main Tool Function
# ============================================================================
TOOL_SUMMARY = (
"Manage memories in Graphiti knowledge graph (save, list, search, status). "
"Connects to the same Graphiti instance as the Graphiti MCP server. "
"Requires MISTRAL_API_KEY for entity extraction and knowledge graph operations."
)
@autodoc(summary=TOOL_SUMMARY)
def Memory_Manager(
action: Annotated[Literal["save", "list", "search", "status"], "Action: save | list | search | status"] = "list",
text: Annotated[Optional[str], "Memory text (Save only)"] = None,
tags: Annotated[Optional[str], "Comma-separated tags (Save only)"] = None,
query: Annotated[Optional[str], "Search query (Search only)"] = None,
limit: Annotated[int, "Max results (List/Search only)"] = 20,
include_tags: Annotated[bool, "Include tags in output"] = True,
) -> str:
"""
Memory Manager - Graphiti Knowledge Graph Interface.
Connects to the same Graphiti instance used by Claude Code's Graphiti MCP.
All memories are stored in the knowledge graph with automatic entity extraction
and relationship detection.
"""
act = (action or "list").lower().strip()
if act == "status":
return _get_status()
if act == "save":
text = (text or "").strip()
if not text:
return "Error: 'text' is required when action=save."
return _graphiti_save(text=text, tags=tags or "")
if act == "list":
return _graphiti_list(limit=max(1, min(200, limit)), include_tags=include_tags)
if act == "search":
query = (query or "").strip()
if not query:
return "Error: 'query' is required when action=search."
return _graphiti_search(query=query, limit=max(1, min(200, limit)))
return "Error: invalid action (use save|list|search|status)."
def build_interface() -> gr.Interface:
"""Build Gradio interface for Memory Manager."""
status_info = _get_status()
return gr.Interface(
fn=Memory_Manager,
inputs=[
gr.Radio(
label="Action",
choices=["save", "list", "search", "status"],
value="status",
info="Action to perform",
),
gr.Textbox(label="Text", lines=3, info="Memory text (Save only)"),
gr.Textbox(label="Tags", placeholder="tag1, tag2", max_lines=1, info="Comma-separated tags (Save only)"),
gr.Textbox(label="Query", placeholder="search terms...", max_lines=1, info="Search query (Search only)"),
gr.Slider(1, 200, value=20, step=1, label="Limit", info="Max results (List/Search only)"),
gr.Checkbox(value=True, label="Include Tags", info="Include tags in output"),
],
outputs=gr.Textbox(label="Result", lines=14),
title="Memory Manager - Graphiti",
description=f"<div style='text-align:center'><strong>{status_info}</strong><br/>Knowledge graph memory with entity extraction</div>",
api_description=TOOL_SUMMARY,
flagging_mode="never",
)
__all__ = [
"Memory_Manager",
"build_interface",
"GRAPHITI_AVAILABLE",
"GRAPHITI_GROUP_ID",
] |