ADDING MEMORY MANAGER TOOL - CREATE, DELETE, LIST, SEARCH MEMORIES (intended for local use)
Browse files
app.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
-
# Purpose: One Space that offers
|
2 |
# 1) Fetch — convert webpages to clean Markdown format
|
3 |
# 2) DuckDuckGo Search — compact JSONL search output (short keys to minimize tokens)
|
4 |
# 3) Python Code Executor — run Python code and capture stdout/errors
|
5 |
# 4) Kokoro TTS — synthesize speech from text using Kokoro-82M with 54 voice options
|
6 |
-
# 5)
|
7 |
-
# 6)
|
|
|
8 |
|
9 |
from __future__ import annotations
|
10 |
|
@@ -14,7 +15,7 @@ import sys
|
|
14 |
import os
|
15 |
import random
|
16 |
from io import StringIO
|
17 |
-
from typing import List, Dict, Tuple, Annotated
|
18 |
|
19 |
import gradio as gr
|
20 |
import requests
|
@@ -805,31 +806,32 @@ def _save_memories(memories: List[Dict[str, str]]) -> None:
|
|
805 |
os.replace(tmp_path, MEMORY_FILE)
|
806 |
|
807 |
|
808 |
-
def
|
809 |
text: Annotated[str, "Raw textual content to remember (will be stored verbatim)."],
|
810 |
tags: Annotated[str, "Optional comma-separated tags for lightweight categorization (e.g. 'user, preference')."] = "",
|
811 |
) -> str:
|
812 |
-
"""
|
813 |
|
814 |
-
|
815 |
-
|
816 |
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
|
823 |
-
|
824 |
-
|
|
|
|
|
|
|
825 |
|
826 |
-
|
827 |
-
|
828 |
|
829 |
-
|
830 |
-
|
831 |
-
* Concurrency protection is process‑local (thread lock). For multi‑process
|
832 |
-
usage you would need an OS file lock (out of scope here).
|
833 |
"""
|
834 |
text_clean = (text or "").strip()
|
835 |
if not text_clean:
|
@@ -856,22 +858,22 @@ def Save_Memory( # <-- MCP tool (Save memory)
|
|
856 |
return f"Memory saved: {mem_id}"
|
857 |
|
858 |
|
859 |
-
def
|
860 |
limit: Annotated[int, "Maximum number of most recent memories to return (1–200)."] = 20,
|
861 |
include_tags: Annotated[bool, "If true, include tags column in output."] = True,
|
862 |
) -> str:
|
863 |
-
"""
|
864 |
|
865 |
-
|
866 |
-
|
867 |
-
|
868 |
|
869 |
-
Format:
|
870 |
-
|
|
|
871 |
|
872 |
-
|
873 |
-
|
874 |
-
* UUID prefix is first 8 chars to keep responses compact (full id retained on disk).
|
875 |
"""
|
876 |
limit = max(1, min(200, limit))
|
877 |
with _MEMORY_LOCK:
|
@@ -889,24 +891,23 @@ def List_Memories( # <-- MCP tool (List memories)
|
|
889 |
return "\n".join(lines)
|
890 |
|
891 |
|
892 |
-
def
|
893 |
query: Annotated[str, "Case-insensitive substring search; space-separated terms are ANDed."],
|
894 |
limit: Annotated[int, "Maximum number of matches (1–200)."] = 20,
|
895 |
) -> str:
|
896 |
-
"""
|
897 |
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
* Results are sorted by timestamp descending (newest first).
|
903 |
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
|
908 |
Returns:
|
909 |
-
|
910 |
"""
|
911 |
q = (query or "").strip()
|
912 |
if not q:
|
@@ -931,19 +932,19 @@ def Search_Memories( # <-- MCP tool (Search memories)
|
|
931 |
return "\n".join(lines)
|
932 |
|
933 |
|
934 |
-
def
|
935 |
memory_id: Annotated[str, "Full UUID or a unique prefix (>=4 chars) of the memory id to delete."],
|
936 |
) -> str:
|
937 |
-
"""Delete
|
938 |
|
939 |
-
|
940 |
-
|
941 |
-
prefix matches multiple entries, no deletion occurs (safety).
|
942 |
|
943 |
Returns:
|
944 |
-
|
945 |
-
|
946 |
-
|
|
|
947 |
"""
|
948 |
key = (memory_id or "").strip().lower()
|
949 |
if len(key) < 4:
|
@@ -1046,7 +1047,7 @@ CSS_STYLES = """
|
|
1046 |
/* Place bold tools list on line 2, normal auth note on line 3 (below title) */
|
1047 |
.gradio-container h1::before {
|
1048 |
grid-row: 2;
|
1049 |
-
content: "Fetch Webpage | Search DuckDuckGo |
|
1050 |
display: block;
|
1051 |
font-size: 1rem;
|
1052 |
font-weight: 700;
|
@@ -1056,7 +1057,7 @@ CSS_STYLES = """
|
|
1056 |
}
|
1057 |
.gradio-container h1::after {
|
1058 |
grid-row: 3;
|
1059 |
-
content: "Authentication is optional but Image/Video Generation require a `HF_READ_TOKEN` in env secrets. They are hidden otherwise.";
|
1060 |
display: block;
|
1061 |
font-size: 1rem;
|
1062 |
font-weight: 400;
|
@@ -1101,42 +1102,95 @@ kokoro_interface = gr.Interface(
|
|
1101 |
flagging_mode="never",
|
1102 |
)
|
1103 |
|
1104 |
-
|
1105 |
-
|
1106 |
-
|
1107 |
-
|
1108 |
-
|
1109 |
-
|
1110 |
-
|
1111 |
-
|
1112 |
-
|
1113 |
-
|
1114 |
-
|
1115 |
-
|
1116 |
-
|
1117 |
-
|
1118 |
-
|
1119 |
-
|
1120 |
-
|
1121 |
-
|
1122 |
-
|
1123 |
-
|
1124 |
-
|
1125 |
-
|
1126 |
-
|
1127 |
-
|
1128 |
-
|
1129 |
-
|
1130 |
-
|
1131 |
-
|
1132 |
-
|
1133 |
-
|
1134 |
-
|
1135 |
-
|
1136 |
-
|
1137 |
-
|
1138 |
-
|
1139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1140 |
|
1141 |
# ==========================
|
1142 |
# Image Generation (Serverless)
|
@@ -1460,17 +1514,23 @@ _interfaces = [
|
|
1460 |
fetch_interface,
|
1461 |
concise_interface,
|
1462 |
code_interface,
|
1463 |
-
memory_interface,
|
1464 |
kokoro_interface,
|
1465 |
]
|
1466 |
_tab_names = [
|
1467 |
"Fetch Webpage",
|
1468 |
"DuckDuckGo Search",
|
1469 |
"Python Code Executor",
|
1470 |
-
"Memory Manager",
|
1471 |
"Kokoro TTS",
|
1472 |
]
|
1473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1474 |
if HAS_HF_TOKEN:
|
1475 |
_interfaces.extend([image_generation_interface, video_generation_interface])
|
1476 |
_tab_names.extend(["Image Generation", "Video Generation"])
|
|
|
1 |
+
# Purpose: One Space that offers up to seven tools/tabs (all exposed as MCP tools):
|
2 |
# 1) Fetch — convert webpages to clean Markdown format
|
3 |
# 2) DuckDuckGo Search — compact JSONL search output (short keys to minimize tokens)
|
4 |
# 3) Python Code Executor — run Python code and capture stdout/errors
|
5 |
# 4) Kokoro TTS — synthesize speech from text using Kokoro-82M with 54 voice options
|
6 |
+
# 5) Memory Manager — lightweight JSON-based local memory store (requires HF_READ_TOKEN)
|
7 |
+
# 6) Image Generation - HF serverless inference providers (requires HF_READ_TOKEN)
|
8 |
+
# 7) Video Generation - HF serverless inference providers (requires HF_READ_TOKEN)
|
9 |
|
10 |
from __future__ import annotations
|
11 |
|
|
|
15 |
import os
|
16 |
import random
|
17 |
from io import StringIO
|
18 |
+
from typing import List, Dict, Tuple, Annotated, Literal
|
19 |
|
20 |
import gradio as gr
|
21 |
import requests
|
|
|
806 |
os.replace(tmp_path, MEMORY_FILE)
|
807 |
|
808 |
|
809 |
+
def _mem_save(
|
810 |
text: Annotated[str, "Raw textual content to remember (will be stored verbatim)."],
|
811 |
tags: Annotated[str, "Optional comma-separated tags for lightweight categorization (e.g. 'user, preference')."] = "",
|
812 |
) -> str:
|
813 |
+
"""(Internal) Persist a new memory record.
|
814 |
|
815 |
+
Summary:
|
816 |
+
Adds a memory object to the local JSON store (no external database).
|
817 |
|
818 |
+
Stored Fields:
|
819 |
+
- id (str, UUID4)
|
820 |
+
- text (str, verbatim user content)
|
821 |
+
- timestamp (UTC "YYYY-MM-DD HH:MM:SS")
|
822 |
+
- tags (str, original comma-separated tag string)
|
823 |
|
824 |
+
Behavior / Rules:
|
825 |
+
1. Whitespace is trimmed; empty text is rejected.
|
826 |
+
2. If the most recent existing memory has identical text, the new one is skipped (light dedupe heuristic).
|
827 |
+
3. When total entries exceed _MAX_MEMORIES, oldest entries are pruned (soft cap).
|
828 |
+
4. Operation is protected by an in‑process reentrant lock only (no cross‑process locking).
|
829 |
|
830 |
+
Returns:
|
831 |
+
str: Human readable confirmation containing the new memory UUID (full or prefix).
|
832 |
|
833 |
+
Security / Privacy:
|
834 |
+
Data is plaintext JSON on local disk; do NOT store secrets or regulated data.
|
|
|
|
|
835 |
"""
|
836 |
text_clean = (text or "").strip()
|
837 |
if not text_clean:
|
|
|
858 |
return f"Memory saved: {mem_id}"
|
859 |
|
860 |
|
861 |
+
def _mem_list(
|
862 |
limit: Annotated[int, "Maximum number of most recent memories to return (1–200)."] = 20,
|
863 |
include_tags: Annotated[bool, "If true, include tags column in output."] = True,
|
864 |
) -> str:
|
865 |
+
"""(Internal) List most recent memories.
|
866 |
|
867 |
+
Parameters:
|
868 |
+
limit (int): Max rows to return; clamped to [1, 200].
|
869 |
+
include_tags (bool): Include tags section when True.
|
870 |
|
871 |
+
Output Format (one per line):
|
872 |
+
<uuid_prefix> [YYYY-MM-DD HH:MM:SS] <text> | tags: <tag list>
|
873 |
+
(Tag column omitted if empty or include_tags=False.)
|
874 |
|
875 |
+
Returns:
|
876 |
+
str: Joined newline string or a friendly "No memories stored." message.
|
|
|
877 |
"""
|
878 |
limit = max(1, min(200, limit))
|
879 |
with _MEMORY_LOCK:
|
|
|
891 |
return "\n".join(lines)
|
892 |
|
893 |
|
894 |
+
def _mem_search(
|
895 |
query: Annotated[str, "Case-insensitive substring search; space-separated terms are ANDed."],
|
896 |
limit: Annotated[int, "Maximum number of matches (1–200)."] = 20,
|
897 |
) -> str:
|
898 |
+
"""(Internal) Full-text style AND search across text and tags.
|
899 |
|
900 |
+
Search Semantics:
|
901 |
+
- Split query on whitespace into individual terms.
|
902 |
+
- A memory matches only if EVERY term appears (case-insensitive) in the text OR tags field.
|
903 |
+
- Results are ordered newest-first (descending timestamp).
|
|
|
904 |
|
905 |
+
Parameters:
|
906 |
+
query (str): Raw user query string; must contain at least one non-space character.
|
907 |
+
limit (int): Max rows to return; clamped to [1, 200].
|
908 |
|
909 |
Returns:
|
910 |
+
str: Formatted lines identical to _mem_list output or "No matches".
|
911 |
"""
|
912 |
q = (query or "").strip()
|
913 |
if not q:
|
|
|
932 |
return "\n".join(lines)
|
933 |
|
934 |
|
935 |
+
def _mem_delete(
|
936 |
memory_id: Annotated[str, "Full UUID or a unique prefix (>=4 chars) of the memory id to delete."],
|
937 |
) -> str:
|
938 |
+
"""(Internal) Delete one memory by UUID or unique prefix.
|
939 |
|
940 |
+
Parameters:
|
941 |
+
memory_id (str): Full UUID4 (preferred) OR a unique prefix (>=4 chars). If prefix is ambiguous, no deletion occurs.
|
|
|
942 |
|
943 |
Returns:
|
944 |
+
str: One of: success message, ambiguity notice, or not-found message.
|
945 |
+
|
946 |
+
Safety:
|
947 |
+
Ambiguous prefixes are rejected to prevent accidental mass deletion.
|
948 |
"""
|
949 |
key = (memory_id or "").strip().lower()
|
950 |
if len(key) < 4:
|
|
|
1047 |
/* Place bold tools list on line 2, normal auth note on line 3 (below title) */
|
1048 |
.gradio-container h1::before {
|
1049 |
grid-row: 2;
|
1050 |
+
content: "Fetch Webpage | Search DuckDuckGo | Python Interpreter | Memory Manager | Kokoro TTS | Image Generation | Video Generation";
|
1051 |
display: block;
|
1052 |
font-size: 1rem;
|
1053 |
font-weight: 700;
|
|
|
1057 |
}
|
1058 |
.gradio-container h1::after {
|
1059 |
grid-row: 3;
|
1060 |
+
content: "Authentication is optional but Image/Video Generation require a `HF_READ_TOKEN` in env secrets. They are hidden otherwise. Same with Memory (intended for local)";
|
1061 |
display: block;
|
1062 |
font-size: 1rem;
|
1063 |
font-weight: 400;
|
|
|
1102 |
flagging_mode="never",
|
1103 |
)
|
1104 |
|
1105 |
+
def Memory_Tool(
|
1106 |
+
action: Annotated[Literal["save","list","search","delete"], "Action to perform: save | list | search | delete"],
|
1107 |
+
text: Annotated[str, "Text content (used when action=save)"] = "",
|
1108 |
+
tags: Annotated[str, "Comma-separated tags (used when action=save)"] = "",
|
1109 |
+
query: Annotated[str, "Search query terms (used when action=search)"] = "",
|
1110 |
+
limit: Annotated[int, "Max results for list/search (1–200)"] = 20,
|
1111 |
+
memory_id: Annotated[str, "Full UUID or unique prefix (used when action=delete)"] = "",
|
1112 |
+
include_tags: Annotated[bool, "Include tags in list/search output"] = True,
|
1113 |
+
) -> str:
|
1114 |
+
"""Manage lightweight local JSON “memories” (save | list | search | delete) in one MCP tool.
|
1115 |
+
|
1116 |
+
Overview:
|
1117 |
+
This tool provides simple, local, append‑only style persistence for short text memories
|
1118 |
+
with optional tags. Data is stored in a plaintext JSON file ("memories.json") beside the
|
1119 |
+
application; no external database or network access is required.
|
1120 |
+
|
1121 |
+
Supported Actions:
|
1122 |
+
- save : Store a new memory (requires 'text'; optional 'tags').
|
1123 |
+
- list : Return the most recent memories (respects 'limit' + 'include_tags').
|
1124 |
+
- search : AND match space‑separated terms across text and tags (uses 'query', 'limit').
|
1125 |
+
- delete : Remove one memory by full UUID or unique prefix (uses 'memory_id').
|
1126 |
+
|
1127 |
+
Parameter Usage by Action:
|
1128 |
+
action=save -> text (required), tags (optional)
|
1129 |
+
action=list -> limit, include_tags
|
1130 |
+
action=search -> query (required), limit, include_tags
|
1131 |
+
action=delete -> memory_id (required)
|
1132 |
+
|
1133 |
+
Parameters:
|
1134 |
+
action (Literal[save|list|search|delete]): Operation selector (case-insensitive).
|
1135 |
+
text (str): Raw memory content; leading/trailing whitespace trimmed (save only).
|
1136 |
+
tags (str): Optional comma-separated tags; stored verbatim (save only).
|
1137 |
+
query (str): Space-separated terms (AND logic, case-insensitive) across text+tags (search only).
|
1138 |
+
limit (int): Maximum rows for list/search (clamped internally to 1–200).
|
1139 |
+
memory_id (str): Full UUID or unique prefix (>=4 chars) (delete only).
|
1140 |
+
include_tags (bool): When True, show tag column in list/search output.
|
1141 |
+
|
1142 |
+
Storage Format (per entry):
|
1143 |
+
{"id": "<uuid4>", "text": "<original text>", "timestamp": "YYYY-MM-DD HH:MM:SS", "tags": "tag1, tag2"}
|
1144 |
+
|
1145 |
+
Lifecycle & Constraints:
|
1146 |
+
- A soft cap of {_MAX_MEMORIES} entries is enforced by pruning oldest records on save.
|
1147 |
+
- A light duplicate guard skips saving if the newest existing entry has identical text.
|
1148 |
+
- All operations are protected by a thread‑local reentrant lock (NOT multi‑process safe).
|
1149 |
+
|
1150 |
+
Returns:
|
1151 |
+
str: Human‑readable status / result lines (never raw JSON) suitable for direct model consumption.
|
1152 |
+
|
1153 |
+
Error Modes:
|
1154 |
+
- Invalid action -> error string.
|
1155 |
+
- Missing required field for the chosen action -> explanatory message.
|
1156 |
+
- Ambiguous or unknown memory_id on delete -> clarification message.
|
1157 |
+
|
1158 |
+
Security & Privacy:
|
1159 |
+
Plaintext JSON; do not store secrets, credentials, or regulated personal data.
|
1160 |
+
"""
|
1161 |
+
act = (action or "").lower().strip()
|
1162 |
+
if act == "save":
|
1163 |
+
return _mem_save(text=text, tags=tags)
|
1164 |
+
if act == "list":
|
1165 |
+
return _mem_list(limit=limit, include_tags=include_tags)
|
1166 |
+
if act == "search":
|
1167 |
+
return _mem_search(query=query, limit=limit)
|
1168 |
+
if act == "delete":
|
1169 |
+
return _mem_delete(memory_id=memory_id)
|
1170 |
+
return "Error: invalid action (use save|list|search|delete)."
|
1171 |
+
|
1172 |
+
memory_interface = gr.Interface(
|
1173 |
+
fn=Memory_Tool,
|
1174 |
+
inputs=[
|
1175 |
+
gr.Dropdown(label="Action", choices=["save","list","search","delete"], value="list"),
|
1176 |
+
gr.Textbox(label="Text", lines=3, placeholder="Memory text (save)"),
|
1177 |
+
gr.Textbox(label="Tags", placeholder="tag1, tag2"),
|
1178 |
+
gr.Textbox(label="Query", placeholder="Search terms (search)"),
|
1179 |
+
gr.Slider(1, 200, value=20, step=1, label="Limit"),
|
1180 |
+
gr.Textbox(label="Memory ID / Prefix", placeholder="UUID or prefix (delete)"),
|
1181 |
+
gr.Checkbox(value=True, label="Include Tags"),
|
1182 |
+
],
|
1183 |
+
outputs=gr.Textbox(label="Result", lines=14),
|
1184 |
+
title="Memory Manager",
|
1185 |
+
description=(
|
1186 |
+
"Lightweight local JSON memory store (no external DB). Choose an Action, fill only the relevant fields, and run."
|
1187 |
+
),
|
1188 |
+
api_description=(
|
1189 |
+
"Manage short text memories with optional tags. Actions: save(text,tags), list(limit,include_tags), "
|
1190 |
+
"search(query,limit,include_tags), delete(memory_id). Plaintext JSON; avoid secrets. Returns human-readable lines."
|
1191 |
+
),
|
1192 |
+
flagging_mode="never",
|
1193 |
+
)
|
1194 |
|
1195 |
# ==========================
|
1196 |
# Image Generation (Serverless)
|
|
|
1514 |
fetch_interface,
|
1515 |
concise_interface,
|
1516 |
code_interface,
|
|
|
1517 |
kokoro_interface,
|
1518 |
]
|
1519 |
_tab_names = [
|
1520 |
"Fetch Webpage",
|
1521 |
"DuckDuckGo Search",
|
1522 |
"Python Code Executor",
|
|
|
1523 |
"Kokoro TTS",
|
1524 |
]
|
1525 |
|
1526 |
+
# Only expose memory manager if an HF_READ_TOKEN is present (parity with Image/Video gating)
|
1527 |
+
HAS_HF_READ = bool(HF_API_TOKEN)
|
1528 |
+
if HAS_HF_READ:
|
1529 |
+
# Insert before Kokoro TTS for previous ordering
|
1530 |
+
insert_index = 3 if len(_interfaces) >= 3 else len(_interfaces)
|
1531 |
+
_interfaces.insert(insert_index, memory_interface)
|
1532 |
+
_tab_names.insert(insert_index, "Memory Manager")
|
1533 |
+
|
1534 |
if HAS_HF_TOKEN:
|
1535 |
_interfaces.extend([image_generation_interface, video_generation_interface])
|
1536 |
_tab_names.extend(["Image Generation", "Video Generation"])
|