AthelaPerk commited on
Commit
fbf45fc
Β·
verified Β·
1 Parent(s): a620165

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +235 -42
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Mnemo v2 - Interactive Demo (CPU Optimized)
3
- Enhanced memory system with real embeddings, HNSW index, and temporal decay.
4
  """
5
 
6
  import gradio as gr
@@ -9,8 +9,7 @@ from datetime import datetime
9
  from typing import List
10
  import numpy as np
11
 
12
- # Import core components
13
- from mnemo_core import MnemoV2, compute_embedding, compute_embeddings_batch
14
 
15
  # Global persistent state
16
  MNEMO = MnemoV2()
@@ -20,18 +19,23 @@ def format_time(timestamp: float) -> str:
20
 
21
  def get_stats_text(user_id: str = "default") -> str:
22
  stats = MNEMO.get_stats(user_id=user_id or "default")
 
23
  return f"""**System Stats**
24
  - Total memories: {stats['total_memories']}
25
  - User memories: {stats['user_memory_count']}
26
- - Total users: {stats['total_users']}
27
- - Adds: {stats['total_adds']} | Updates: {stats['total_updates']} | Deletes: {stats['total_deletes']}
28
- - Searches: {stats['total_searches']}
29
- - Decay half-life: {stats['decay_half_life_days']:.1f} days"""
 
 
 
 
30
 
31
 
32
  def add_memory(content: str, importance: float, tags: str, user_id: str):
33
  if not content.strip():
34
- return "❌ Please enter content", get_stats_text(user_id)
35
 
36
  tags_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else []
37
  embedding = compute_embedding(content)
@@ -46,39 +50,65 @@ def add_memory(content: str, importance: float, tags: str, user_id: str):
46
 
47
  op = result['operation']
48
  icon = {"ADD": "βœ…", "UPDATE": "πŸ”„", "NOOP": "⚠️"}.get(op, "❓")
49
- return f"{icon} {result['message']} (ID: {result['id']})", get_stats_text(user_id)
 
 
 
 
 
50
 
51
 
52
- def search_memories(query: str, k: int, min_score: float, user_id: str):
 
 
 
 
 
 
 
 
 
 
53
  if not query.strip():
54
  return "❌ Please enter a search query"
55
 
56
  start = time.time()
57
  query_embedding = compute_embedding(query)
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  results = MNEMO.search_with_embedding(
60
  query_embedding=query_embedding,
61
  user_id=user_id or "default",
62
  k=k,
63
- min_score=min_score
 
64
  )
65
 
66
  latency = (time.time() - start) * 1000
 
67
 
68
  if not results:
69
- return f"No results found (searched in {latency:.1f}ms)"
70
 
71
- output = f"**Found {len(results)} results in {latency:.1f}ms**\n\n"
72
 
73
  for i, r in enumerate(results, 1):
74
  output += f"### {i}. [{r['id']}]\n"
75
  output += f"**Content:** {r['content']}\n\n"
76
  output += f"- Relevance: `{r['relevance_score']:.3f}`\n"
77
  output += f"- Similarity: `{r['similarity']:.3f}`\n"
78
- output += f"- Decay: `{r['decay_score']:.3f}`\n"
79
  output += f"- Importance: `{r['importance']:.2f}`\n"
80
  output += f"- Accesses: `{r['access_count']}`\n"
81
- output += f"- Last accessed: `{format_time(r['last_accessed'])}`\n"
82
  if r['tags']:
83
  output += f"- Tags: `{', '.join(r['tags'])}`\n"
84
  output += "\n---\n\n"
@@ -86,6 +116,42 @@ def search_memories(query: str, k: int, min_score: float, user_id: str):
86
  return output
87
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def list_memories_ui(user_id: str, limit: int):
90
  memories = MNEMO.list_memories(user_id=user_id or "default", limit=limit)
91
 
@@ -95,9 +161,8 @@ def list_memories_ui(user_id: str, limit: int):
95
  output = f"**{len(memories)} memories (sorted by last accessed)**\n\n"
96
 
97
  for m in memories:
98
- output += f"**{m['id']}** (importance: {m['importance']:.2f}, accesses: {m['access_count']})\n"
99
  output += f"> {m['content'][:100]}{'...' if len(m['content']) > 100 else ''}\n"
100
- output += f"- Created: {format_time(m['created_at'])} | Last: {format_time(m['last_accessed'])}\n"
101
  if m['tags']:
102
  output += f"- Tags: {', '.join(m['tags'])}\n"
103
  output += "\n"
@@ -123,13 +188,13 @@ def clear_memories_ui(user_id: str):
123
 
124
  def load_examples(user_id: str):
125
  examples = [
126
- ("User prefers dark mode and VS Code for development", 1.0, ["preferences", "development"]),
127
- ("Project deadline is March 15th 2026 for the Q1 release", 0.9, ["project", "deadline"]),
128
- ("Favorite programming language is Python, also uses TypeScript", 0.8, ["preferences", "languages"]),
129
- ("Weekly standup meetings are every Monday at 10am", 0.7, ["meetings", "schedule"]),
130
- ("User is allergic to peanuts - important health info", 1.0, ["health", "critical"]),
131
- ("Prefers cappuccino with oat milk for coffee orders", 0.5, ["preferences", "food"]),
132
- ("Working on a machine learning project for recommendation systems", 0.8, ["project", "ml"]),
133
  ("Lives in San Francisco, timezone is PST", 0.6, ["personal", "location"]),
134
  ]
135
 
@@ -148,14 +213,19 @@ def load_examples(user_id: str):
148
  if result['operation'] in ('ADD', 'UPDATE'):
149
  added += 1
150
 
151
- return f"βœ… Loaded {added} example memories", get_stats_text(user_id)
 
 
 
 
152
 
153
 
 
154
  with gr.Blocks(title="Mnemo v2", theme=gr.themes.Soft()) as demo:
155
  gr.Markdown("""
156
- # 🧠 Mnemo v2 - Enhanced Memory for LLMs
157
 
158
- **Features:** πŸ” Semantic embeddings | ⚑ HNSW search | πŸ“‰ Temporal decay | πŸ“Š Relevance scoring | πŸ”„ Auto-dedup | πŸ‘₯ Multi-user
159
  """)
160
 
161
  with gr.Row():
@@ -165,47 +235,170 @@ with gr.Blocks(title="Mnemo v2", theme=gr.themes.Soft()) as demo:
165
  stats_display = gr.Markdown(get_stats_text())
166
 
167
  with gr.Tabs():
 
168
  with gr.Tab("πŸ” Search"):
169
- search_query = gr.Textbox(label="Search Query", placeholder="e.g., What are the user's preferences?")
170
  with gr.Row():
171
- search_k = gr.Slider(1, 20, value=5, step=1, label="Max Results")
172
- search_min_score = gr.Slider(0, 1, value=0, step=0.05, label="Min Score")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  search_btn = gr.Button("Search", variant="primary")
174
  search_output = gr.Markdown()
175
- search_btn.click(search_memories, [search_query, search_k, search_min_score, user_id_input], search_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
 
177
  with gr.Tab("βž• Add Memory"):
178
- add_content = gr.Textbox(label="Memory Content", placeholder="e.g., User prefers dark mode", lines=3)
 
 
 
 
179
  with gr.Row():
180
  add_importance = gr.Slider(0, 1, value=1.0, step=0.1, label="Importance")
181
- add_tags = gr.Textbox(label="Tags (comma-separated)", placeholder="preferences, settings")
 
 
 
182
  add_btn = gr.Button("Add Memory", variant="primary")
183
  add_output = gr.Textbox(label="Result")
184
- add_btn.click(add_memory, [add_content, add_importance, add_tags, user_id_input], [add_output, stats_display])
 
 
 
 
 
185
 
 
186
  with gr.Tab("πŸ“‹ List"):
187
  list_limit = gr.Slider(10, 100, value=50, step=10, label="Limit")
188
  list_btn = gr.Button("List All Memories")
189
  list_output = gr.Markdown()
190
- list_btn.click(list_memories_ui, [user_id_input, list_limit], list_output)
 
 
 
 
 
191
 
 
192
  with gr.Tab("βš™οΈ Manage"):
193
  delete_id = gr.Textbox(label="Memory ID to Delete")
194
  delete_btn = gr.Button("Delete", variant="stop")
195
  delete_output = gr.Textbox(label="Result")
196
- delete_btn.click(delete_memory_ui, [delete_id, user_id_input], [delete_output, stats_display])
 
 
 
 
 
197
 
198
  clear_btn = gr.Button("πŸ—‘οΈ Clear All", variant="stop")
199
  clear_output = gr.Textbox(label="Result")
200
- clear_btn.click(clear_memories_ui, [user_id_input], [clear_output, stats_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- load_btn.click(load_examples, [user_id_input], [stats_display, stats_display])
 
 
 
 
 
 
 
 
 
 
203
 
204
  gr.Markdown("""
205
  ---
206
- | Embeddings | Vector Index | Decay | Scoring |
207
- |------------|--------------|-------|---------|
208
- | all-MiniLM-L6-v2 (384d) | FAISS HNSW | 7-day half-life | 40% sim + 30% rec + 30% freq |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  """)
210
 
211
  if __name__ == "__main__":
 
1
  """
2
+ Mnemo v2 - Interactive Demo with Metadata Filtering
3
+ Enhanced memory system with filtering by tags, importance, and dates.
4
  """
5
 
6
  import gradio as gr
 
9
  from typing import List
10
  import numpy as np
11
 
12
+ from mnemo_core import MnemoV2, MemoryFilter, compute_embedding, compute_embeddings_batch
 
13
 
14
  # Global persistent state
15
  MNEMO = MnemoV2()
 
19
 
20
  def get_stats_text(user_id: str = "default") -> str:
21
  stats = MNEMO.get_stats(user_id=user_id or "default")
22
+ tags_str = ", ".join(stats['tags'][:10]) if stats['tags'] else "none"
23
  return f"""**System Stats**
24
  - Total memories: {stats['total_memories']}
25
  - User memories: {stats['user_memory_count']}
26
+ - Unique tags: {stats['unique_tags']}
27
+ - Tags: {tags_str}
28
+ - Searches: {stats['total_searches']} (filtered: {stats['filtered_searches']})"""
29
+
30
+
31
+ def get_available_tags(user_id: str) -> List[str]:
32
+ """Get available tags for dropdown."""
33
+ return MNEMO.get_all_tags(user_id=user_id or "default")
34
 
35
 
36
  def add_memory(content: str, importance: float, tags: str, user_id: str):
37
  if not content.strip():
38
+ return "❌ Please enter content", get_stats_text(user_id), gr.update(choices=get_available_tags(user_id))
39
 
40
  tags_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else []
41
  embedding = compute_embedding(content)
 
50
 
51
  op = result['operation']
52
  icon = {"ADD": "βœ…", "UPDATE": "πŸ”„", "NOOP": "⚠️"}.get(op, "❓")
53
+
54
+ return (
55
+ f"{icon} {result['message']} (ID: {result['id']})",
56
+ get_stats_text(user_id),
57
+ gr.update(choices=get_available_tags(user_id))
58
+ )
59
 
60
 
61
+ def search_memories(
62
+ query: str,
63
+ k: int,
64
+ min_score: float,
65
+ user_id: str,
66
+ filter_tags_include: List[str],
67
+ filter_tags_exclude: List[str],
68
+ filter_min_importance: float,
69
+ filter_content_contains: str
70
+ ):
71
+ """Search memories with optional filters."""
72
  if not query.strip():
73
  return "❌ Please enter a search query"
74
 
75
  start = time.time()
76
  query_embedding = compute_embedding(query)
77
 
78
+ # Build filter
79
+ mem_filter = None
80
+ if any([filter_tags_include, filter_tags_exclude,
81
+ filter_min_importance > 0, filter_content_contains]):
82
+ mem_filter = MemoryFilter(
83
+ tags_include_any=filter_tags_include if filter_tags_include else None,
84
+ tags_exclude=filter_tags_exclude if filter_tags_exclude else None,
85
+ min_importance=filter_min_importance if filter_min_importance > 0 else None,
86
+ content_contains=filter_content_contains if filter_content_contains else None
87
+ )
88
+
89
  results = MNEMO.search_with_embedding(
90
  query_embedding=query_embedding,
91
  user_id=user_id or "default",
92
  k=k,
93
+ min_score=min_score,
94
+ filter=mem_filter
95
  )
96
 
97
  latency = (time.time() - start) * 1000
98
+ filter_str = " (filtered)" if mem_filter else ""
99
 
100
  if not results:
101
+ return f"No results found{filter_str} (searched in {latency:.1f}ms)"
102
 
103
+ output = f"**Found {len(results)} results{filter_str} in {latency:.1f}ms**\n\n"
104
 
105
  for i, r in enumerate(results, 1):
106
  output += f"### {i}. [{r['id']}]\n"
107
  output += f"**Content:** {r['content']}\n\n"
108
  output += f"- Relevance: `{r['relevance_score']:.3f}`\n"
109
  output += f"- Similarity: `{r['similarity']:.3f}`\n"
 
110
  output += f"- Importance: `{r['importance']:.2f}`\n"
111
  output += f"- Accesses: `{r['access_count']}`\n"
 
112
  if r['tags']:
113
  output += f"- Tags: `{', '.join(r['tags'])}`\n"
114
  output += "\n---\n\n"
 
116
  return output
117
 
118
 
119
+ def search_by_tags_only(
120
+ tags: List[str],
121
+ match_all: bool,
122
+ k: int,
123
+ user_id: str
124
+ ):
125
+ """Search by tags without semantic query."""
126
+ if not tags:
127
+ return "❌ Please select at least one tag"
128
+
129
+ start = time.time()
130
+ results = MNEMO.search_by_tags(
131
+ tags=tags,
132
+ user_id=user_id or "default",
133
+ match_all=match_all,
134
+ k=k
135
+ )
136
+ latency = (time.time() - start) * 1000
137
+
138
+ match_str = "ALL" if match_all else "ANY"
139
+
140
+ if not results:
141
+ return f"No memories found with {match_str} tags: {', '.join(tags)}"
142
+
143
+ output = f"**Found {len(results)} memories with {match_str} tags in {latency:.1f}ms**\n\n"
144
+
145
+ for i, r in enumerate(results, 1):
146
+ output += f"### {i}. [{r['id']}]\n"
147
+ output += f"**Content:** {r['content']}\n\n"
148
+ output += f"- Importance: `{r['importance']:.2f}`\n"
149
+ output += f"- Tags: `{', '.join(r['tags'])}`\n"
150
+ output += "\n---\n\n"
151
+
152
+ return output
153
+
154
+
155
  def list_memories_ui(user_id: str, limit: int):
156
  memories = MNEMO.list_memories(user_id=user_id or "default", limit=limit)
157
 
 
161
  output = f"**{len(memories)} memories (sorted by last accessed)**\n\n"
162
 
163
  for m in memories:
164
+ output += f"**{m['id']}** (importance: {m['importance']:.2f})\n"
165
  output += f"> {m['content'][:100]}{'...' if len(m['content']) > 100 else ''}\n"
 
166
  if m['tags']:
167
  output += f"- Tags: {', '.join(m['tags'])}\n"
168
  output += "\n"
 
188
 
189
  def load_examples(user_id: str):
190
  examples = [
191
+ ("User prefers dark mode and VS Code for development", 1.0, ["preferences", "development", "tools"]),
192
+ ("Project deadline is March 15th 2026 for the Q1 release", 0.9, ["project", "deadline", "work"]),
193
+ ("Favorite programming language is Python, also uses TypeScript", 0.8, ["preferences", "languages", "development"]),
194
+ ("Weekly standup meetings are every Monday at 10am", 0.7, ["meetings", "schedule", "work"]),
195
+ ("User is allergic to peanuts - important health info", 1.0, ["health", "critical", "personal"]),
196
+ ("Prefers cappuccino with oat milk for coffee orders", 0.5, ["preferences", "food", "personal"]),
197
+ ("Working on a machine learning project for recommendation systems", 0.8, ["project", "ml", "work"]),
198
  ("Lives in San Francisco, timezone is PST", 0.6, ["personal", "location"]),
199
  ]
200
 
 
213
  if result['operation'] in ('ADD', 'UPDATE'):
214
  added += 1
215
 
216
+ return (
217
+ f"βœ… Loaded {added} example memories",
218
+ get_stats_text(user_id),
219
+ gr.update(choices=get_available_tags(user_id))
220
+ )
221
 
222
 
223
+ # Build the Gradio interface
224
  with gr.Blocks(title="Mnemo v2", theme=gr.themes.Soft()) as demo:
225
  gr.Markdown("""
226
+ # 🧠 Mnemo v2 - Enhanced Memory with Filtering
227
 
228
+ **Features:** πŸ” Semantic search | 🏷️ Tag filtering | ⚑ HNSW index | πŸ“‰ Temporal decay | πŸ”„ Auto-dedup
229
  """)
230
 
231
  with gr.Row():
 
235
  stats_display = gr.Markdown(get_stats_text())
236
 
237
  with gr.Tabs():
238
+ # SEARCH TAB
239
  with gr.Tab("πŸ” Search"):
 
240
  with gr.Row():
241
+ with gr.Column(scale=2):
242
+ search_query = gr.Textbox(
243
+ label="Search Query",
244
+ placeholder="e.g., What are the user's preferences?"
245
+ )
246
+ with gr.Row():
247
+ search_k = gr.Slider(1, 20, value=5, step=1, label="Max Results")
248
+ search_min_score = gr.Slider(0, 1, value=0, step=0.05, label="Min Score")
249
+
250
+ with gr.Column(scale=1):
251
+ gr.Markdown("**Filters (optional)**")
252
+ filter_tags_include = gr.Dropdown(
253
+ label="Include tags (any)",
254
+ choices=[],
255
+ multiselect=True
256
+ )
257
+ filter_tags_exclude = gr.Dropdown(
258
+ label="Exclude tags",
259
+ choices=[],
260
+ multiselect=True
261
+ )
262
+ filter_min_importance = gr.Slider(
263
+ 0, 1, value=0, step=0.1,
264
+ label="Min importance"
265
+ )
266
+ filter_content = gr.Textbox(
267
+ label="Content contains",
268
+ placeholder="substring to match"
269
+ )
270
+
271
  search_btn = gr.Button("Search", variant="primary")
272
  search_output = gr.Markdown()
273
+
274
+ search_btn.click(
275
+ search_memories,
276
+ inputs=[
277
+ search_query, search_k, search_min_score, user_id_input,
278
+ filter_tags_include, filter_tags_exclude,
279
+ filter_min_importance, filter_content
280
+ ],
281
+ outputs=search_output
282
+ )
283
+
284
+ # TAG SEARCH TAB
285
+ with gr.Tab("🏷️ Search by Tags"):
286
+ gr.Markdown("Search memories by tags only (no semantic query needed)")
287
+
288
+ tag_search_tags = gr.Dropdown(
289
+ label="Select tags",
290
+ choices=[],
291
+ multiselect=True
292
+ )
293
+ with gr.Row():
294
+ tag_match_all = gr.Checkbox(label="Match ALL tags", value=False)
295
+ tag_search_k = gr.Slider(1, 50, value=10, step=1, label="Max Results")
296
+
297
+ tag_search_btn = gr.Button("Search by Tags", variant="primary")
298
+ tag_search_output = gr.Markdown()
299
+
300
+ tag_search_btn.click(
301
+ search_by_tags_only,
302
+ inputs=[tag_search_tags, tag_match_all, tag_search_k, user_id_input],
303
+ outputs=tag_search_output
304
+ )
305
 
306
+ # ADD MEMORY TAB
307
  with gr.Tab("βž• Add Memory"):
308
+ add_content = gr.Textbox(
309
+ label="Memory Content",
310
+ placeholder="e.g., User prefers dark mode",
311
+ lines=3
312
+ )
313
  with gr.Row():
314
  add_importance = gr.Slider(0, 1, value=1.0, step=0.1, label="Importance")
315
+ add_tags = gr.Textbox(
316
+ label="Tags (comma-separated)",
317
+ placeholder="e.g., preferences, settings, personal"
318
+ )
319
  add_btn = gr.Button("Add Memory", variant="primary")
320
  add_output = gr.Textbox(label="Result")
321
+
322
+ add_btn.click(
323
+ add_memory,
324
+ inputs=[add_content, add_importance, add_tags, user_id_input],
325
+ outputs=[add_output, stats_display, filter_tags_include]
326
+ )
327
 
328
+ # LIST TAB
329
  with gr.Tab("πŸ“‹ List"):
330
  list_limit = gr.Slider(10, 100, value=50, step=10, label="Limit")
331
  list_btn = gr.Button("List All Memories")
332
  list_output = gr.Markdown()
333
+
334
+ list_btn.click(
335
+ list_memories_ui,
336
+ inputs=[user_id_input, list_limit],
337
+ outputs=list_output
338
+ )
339
 
340
+ # MANAGE TAB
341
  with gr.Tab("βš™οΈ Manage"):
342
  delete_id = gr.Textbox(label="Memory ID to Delete")
343
  delete_btn = gr.Button("Delete", variant="stop")
344
  delete_output = gr.Textbox(label="Result")
345
+
346
+ delete_btn.click(
347
+ delete_memory_ui,
348
+ inputs=[delete_id, user_id_input],
349
+ outputs=[delete_output, stats_display]
350
+ )
351
 
352
  clear_btn = gr.Button("πŸ—‘οΈ Clear All", variant="stop")
353
  clear_output = gr.Textbox(label="Result")
354
+
355
+ clear_btn.click(
356
+ clear_memories_ui,
357
+ inputs=[user_id_input],
358
+ outputs=[clear_output, stats_display]
359
+ )
360
+
361
+ # Update tag dropdowns when examples are loaded
362
+ def update_tags_and_stats(user_id):
363
+ tags = get_available_tags(user_id)
364
+ return (
365
+ get_stats_text(user_id),
366
+ gr.update(choices=tags),
367
+ gr.update(choices=tags),
368
+ gr.update(choices=tags)
369
+ )
370
 
371
+ load_btn.click(
372
+ load_examples,
373
+ inputs=[user_id_input],
374
+ outputs=[stats_display, stats_display, filter_tags_include]
375
+ ).then(
376
+ lambda uid: (gr.update(choices=get_available_tags(uid)),
377
+ gr.update(choices=get_available_tags(uid)),
378
+ gr.update(choices=get_available_tags(uid))),
379
+ inputs=[user_id_input],
380
+ outputs=[filter_tags_include, filter_tags_exclude, tag_search_tags]
381
+ )
382
 
383
  gr.Markdown("""
384
  ---
385
+ ### Filter Options
386
+
387
+ | Filter | Description |
388
+ |--------|-------------|
389
+ | Include tags (any) | Results must have at least one of these tags |
390
+ | Exclude tags | Results must NOT have any of these tags |
391
+ | Min importance | Only memories with importance >= this value |
392
+ | Content contains | Substring match in memory content |
393
+
394
+ ### Architecture
395
+
396
+ | Component | Implementation |
397
+ |-----------|----------------|
398
+ | Embeddings | `sentence-transformers/all-MiniLM-L6-v2` (384d) |
399
+ | Vector Index | FAISS IndexFlatIP (cosine similarity) |
400
+ | Tag Index | Inverted index for O(1) tag lookup |
401
+ | Filtering | Post-search filtering with expanded candidate set |
402
  """)
403
 
404
  if __name__ == "__main__":