memorisdk 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of memorisdk might be problematic. Click here for more details.

memori/__init__.py CHANGED
@@ -5,7 +5,7 @@ Professional-grade memory layer with comprehensive error handling, configuration
5
5
  management, and modular architecture for production AI systems.
6
6
  """
7
7
 
8
- __version__ = "2.0.0"
8
+ __version__ = "2.0.1"
9
9
  __author__ = "Harshal More"
10
10
  __email__ = "harshalmore2468@gmail.com"
11
11
 
@@ -411,32 +411,96 @@ Be strategic and comprehensive in your search planning."""
411
411
 
412
412
  # This would need to be implemented in the database manager
413
413
  # For now, get all memories and filter by category
414
+ logger.debug(
415
+ f"Searching memories by categories: {categories} in namespace: {namespace}"
416
+ )
414
417
  all_results = db_manager.search_memories(
415
418
  query="", namespace=namespace, limit=limit * 3
416
419
  )
417
420
 
421
+ logger.debug(
422
+ f"Retrieved {len(all_results)} total results for category filtering"
423
+ )
424
+
418
425
  filtered_results = []
419
- for result in all_results:
426
+ for i, result in enumerate(all_results):
427
+ logger.debug(f"Processing result {i+1}/{len(all_results)}: {type(result)}")
428
+
420
429
  # Extract category from processed_data if it's stored as JSON
421
430
  try:
422
- if "processed_data" in result:
431
+ memory_category = None
432
+
433
+ # Check processed_data field first
434
+ if "processed_data" in result and result["processed_data"]:
423
435
  processed_data = result["processed_data"]
436
+ logger.debug(
437
+ f"Found processed_data: {type(processed_data)} - {str(processed_data)[:100]}..."
438
+ )
439
+
424
440
  # Handle both dict and JSON string formats
425
441
  if isinstance(processed_data, str):
426
- processed_data = json.loads(processed_data)
427
- elif not isinstance(processed_data, dict):
428
- continue # Skip if neither dict nor string
442
+ try:
443
+ processed_data = json.loads(processed_data)
444
+ except json.JSONDecodeError as je:
445
+ logger.debug(f"JSON decode error for processed_data: {je}")
446
+ continue
447
+
448
+ if isinstance(processed_data, dict):
449
+ # Try multiple possible category locations
450
+ category_paths = [
451
+ ["category", "primary_category"],
452
+ ["category"],
453
+ ["primary_category"],
454
+ ["metadata", "category"],
455
+ ["classification", "category"],
456
+ ]
457
+
458
+ for path in category_paths:
459
+ temp_data = processed_data
460
+ try:
461
+ for key in path:
462
+ temp_data = temp_data.get(key, {})
463
+ if isinstance(temp_data, str) and temp_data:
464
+ memory_category = temp_data
465
+ logger.debug(
466
+ f"Found category via path {path}: {memory_category}"
467
+ )
468
+ break
469
+ except (AttributeError, TypeError):
470
+ continue
471
+ else:
472
+ logger.debug(
473
+ f"processed_data is not a dict after parsing: {type(processed_data)}"
474
+ )
475
+ continue
429
476
 
430
- memory_category = processed_data.get("category", {}).get(
431
- "primary_category", ""
477
+ # Fallback: check direct category field
478
+ if not memory_category and "category" in result and result["category"]:
479
+ memory_category = result["category"]
480
+ logger.debug(f"Found category via direct field: {memory_category}")
481
+
482
+ # Check if the found category matches any of our target categories
483
+ if memory_category:
484
+ logger.debug(
485
+ f"Comparing memory category '{memory_category}' against target categories {categories}"
432
486
  )
433
487
  if memory_category in categories:
434
488
  filtered_results.append(result)
435
- elif result.get("category") in categories:
436
- filtered_results.append(result)
437
- except (json.JSONDecodeError, KeyError, AttributeError):
489
+ logger.debug(f" Category match found: {memory_category}")
490
+ else:
491
+ logger.debug(
492
+ f"✗ Category mismatch: {memory_category} not in {categories}"
493
+ )
494
+ else:
495
+ logger.debug("No category found in result")
496
+
497
+ except Exception as e:
498
+ logger.debug(f"Error processing result {i+1}: {e}")
438
499
  continue
439
500
 
501
+ logger.debug(
502
+ f"Category filtering complete: {len(filtered_results)} results match categories {categories}"
503
+ )
440
504
  return filtered_results[:limit]
441
505
 
442
506
  def _detect_structured_output_support(self) -> bool:
@@ -821,8 +885,11 @@ Be strategic and comprehensive in your search planning."""
821
885
  """
822
886
  # This is a compatibility method that uses the database manager directly
823
887
  # We'll need the database manager to be injected or passed
824
- # For now, return empty list and log the issue
825
- logger.warning(f"search_memories called without database manager: {query}")
888
+ # For now, return empty list and log the issue with parameters
889
+ logger.warning(
890
+ f"search_memories called without database manager: query='{query}', "
891
+ f"max_results={max_results}, namespace='{namespace}'"
892
+ )
826
893
  return []
827
894
 
828
895
 
memori/core/memory.py CHANGED
@@ -1036,15 +1036,38 @@ class Memori:
1036
1036
  self._in_context_retrieval = True
1037
1037
 
1038
1038
  logger.debug(
1039
- f"Auto-ingest: Starting context retrieval for query: '{user_input[:50]}...'"
1039
+ f"Auto-ingest: Starting context retrieval for query: '{user_input[:50]}...' in namespace: '{self.namespace}'"
1040
1040
  )
1041
1041
 
1042
1042
  # Always try direct database search first as it's more reliable
1043
1043
  logger.debug("Auto-ingest: Using direct database search (primary method)")
1044
- results = self.db_manager.search_memories(
1045
- query=user_input, namespace=self.namespace, limit=5
1044
+ logger.debug(
1045
+ f"Auto-ingest: Database manager type: {type(self.db_manager).__name__}"
1046
1046
  )
1047
1047
 
1048
+ try:
1049
+ results = self.db_manager.search_memories(
1050
+ query=user_input, namespace=self.namespace, limit=5
1051
+ )
1052
+ logger.debug(
1053
+ f"Auto-ingest: Database search returned {len(results) if results else 0} results"
1054
+ )
1055
+
1056
+ if results:
1057
+ for i, result in enumerate(
1058
+ results[:3]
1059
+ ): # Log first 3 results for debugging
1060
+ logger.debug(
1061
+ f"Auto-ingest: Result {i+1}: {type(result)} with keys: {list(result.keys()) if isinstance(result, dict) else 'N/A'}"
1062
+ )
1063
+ except Exception as db_search_e:
1064
+ logger.error(f"Auto-ingest: Database search failed: {db_search_e}")
1065
+ logger.debug(
1066
+ f"Auto-ingest: Database search error details: {type(db_search_e).__name__}: {str(db_search_e)}",
1067
+ exc_info=True,
1068
+ )
1069
+ results = []
1070
+
1048
1071
  if results:
1049
1072
  logger.debug(
1050
1073
  f"Auto-ingest: Direct database search returned {len(results)} results"
@@ -1085,8 +1108,12 @@ class Memori:
1085
1108
  )
1086
1109
 
1087
1110
  except Exception as search_error:
1088
- logger.warning(
1089
- f"Auto-ingest: Search engine failed ({search_error})"
1111
+ logger.error(
1112
+ f"Auto-ingest: Search engine failed for query '{user_input[:50]}...': {search_error}"
1113
+ )
1114
+ logger.debug(
1115
+ f"Auto-ingest: Search engine error details: {type(search_error).__name__}: {str(search_error)}",
1116
+ exc_info=True,
1090
1117
  )
1091
1118
  else:
1092
1119
  logger.debug("Auto-ingest: No search engine available")
@@ -1095,22 +1122,39 @@ class Memori:
1095
1122
  logger.debug(
1096
1123
  "Auto-ingest: All search methods returned 0 results, using recent memories fallback"
1097
1124
  )
1098
- fallback_results = self.db_manager.search_memories(
1099
- query="", # Empty query to get recent memories
1100
- namespace=self.namespace,
1101
- limit=3,
1125
+ logger.debug(
1126
+ f"Auto-ingest: Attempting fallback search in namespace '{self.namespace}'"
1102
1127
  )
1103
1128
 
1104
- if fallback_results:
1129
+ try:
1130
+ fallback_results = self.db_manager.search_memories(
1131
+ query="", # Empty query to get recent memories
1132
+ namespace=self.namespace,
1133
+ limit=3,
1134
+ )
1105
1135
  logger.debug(
1106
- f"Auto-ingest: Fallback returned {len(fallback_results)} recent memories"
1136
+ f"Auto-ingest: Fallback search returned {len(fallback_results) if fallback_results else 0} results"
1137
+ )
1138
+
1139
+ if fallback_results:
1140
+ logger.debug(
1141
+ f"Auto-ingest: Fallback returned {len(fallback_results)} recent memories"
1142
+ )
1143
+ # Add search metadata to fallback results
1144
+ for result in fallback_results:
1145
+ if isinstance(result, dict):
1146
+ result["retrieval_method"] = "recent_memories_fallback"
1147
+ result["retrieval_query"] = user_input
1148
+ return fallback_results
1149
+ else:
1150
+ logger.debug("Auto-ingest: Fallback search returned no results")
1151
+
1152
+ except Exception as fallback_e:
1153
+ logger.error(f"Auto-ingest: Fallback search failed: {fallback_e}")
1154
+ logger.debug(
1155
+ f"Auto-ingest: Fallback error details: {type(fallback_e).__name__}: {str(fallback_e)}",
1156
+ exc_info=True,
1107
1157
  )
1108
- # Add search metadata to fallback results
1109
- for result in fallback_results:
1110
- if isinstance(result, dict):
1111
- result["retrieval_method"] = "recent_memories_fallback"
1112
- result["retrieval_query"] = user_input
1113
- return fallback_results
1114
1158
 
1115
1159
  logger.debug(
1116
1160
  "Auto-ingest: All retrieval methods failed, returning empty context"
@@ -41,7 +41,12 @@ class SearchService:
41
41
  Returns:
42
42
  List of memory dictionaries with search metadata
43
43
  """
44
+ logger.debug(
45
+ f"SearchService.search_memories called - query: '{query}', namespace: '{namespace}', database: {self.database_type}, limit: {limit}"
46
+ )
47
+
44
48
  if not query or not query.strip():
49
+ logger.debug("Empty query provided, returning recent memories")
45
50
  return self._get_recent_memories(
46
51
  namespace, category_filter, limit, memory_types
47
52
  )
@@ -52,9 +57,14 @@ class SearchService:
52
57
  search_short_term = not memory_types or "short_term" in memory_types
53
58
  search_long_term = not memory_types or "long_term" in memory_types
54
59
 
60
+ logger.debug(
61
+ f"Memory types to search - short_term: {search_short_term}, long_term: {search_long_term}, categories: {category_filter}"
62
+ )
63
+
55
64
  try:
56
65
  # Try database-specific full-text search first
57
66
  if self.database_type == "sqlite":
67
+ logger.debug("Using SQLite FTS5 search strategy")
58
68
  results = self._search_sqlite_fts(
59
69
  query,
60
70
  namespace,
@@ -64,6 +74,7 @@ class SearchService:
64
74
  search_long_term,
65
75
  )
66
76
  elif self.database_type == "mysql":
77
+ logger.debug("Using MySQL FULLTEXT search strategy")
67
78
  results = self._search_mysql_fulltext(
68
79
  query,
69
80
  namespace,
@@ -73,6 +84,7 @@ class SearchService:
73
84
  search_long_term,
74
85
  )
75
86
  elif self.database_type == "postgresql":
87
+ logger.debug("Using PostgreSQL FTS search strategy")
76
88
  results = self._search_postgresql_fts(
77
89
  query,
78
90
  namespace,
@@ -82,8 +94,13 @@ class SearchService:
82
94
  search_long_term,
83
95
  )
84
96
 
97
+ logger.debug(f"Primary search strategy returned {len(results)} results")
98
+
85
99
  # If no results or full-text search failed, fall back to LIKE search
86
100
  if not results:
101
+ logger.debug(
102
+ "Primary search returned no results, falling back to LIKE search"
103
+ )
87
104
  results = self._search_like_fallback(
88
105
  query,
89
106
  namespace,
@@ -94,17 +111,41 @@ class SearchService:
94
111
  )
95
112
 
96
113
  except Exception as e:
97
- logger.warning(f"Full-text search failed: {e}, falling back to LIKE search")
98
- results = self._search_like_fallback(
99
- query,
100
- namespace,
101
- category_filter,
102
- limit,
103
- search_short_term,
104
- search_long_term,
114
+ logger.error(
115
+ f"Full-text search failed for query '{query}' in namespace '{namespace}': {e}"
116
+ )
117
+ logger.debug(
118
+ f"Full-text search error details: {type(e).__name__}: {str(e)}",
119
+ exc_info=True,
120
+ )
121
+ logger.warning(f"Falling back to LIKE search for query '{query}'")
122
+ try:
123
+ results = self._search_like_fallback(
124
+ query,
125
+ namespace,
126
+ category_filter,
127
+ limit,
128
+ search_short_term,
129
+ search_long_term,
130
+ )
131
+ logger.debug(f"LIKE fallback search returned {len(results)} results")
132
+ except Exception as fallback_e:
133
+ logger.error(
134
+ f"LIKE fallback search also failed for query '{query}': {fallback_e}"
135
+ )
136
+ results = []
137
+
138
+ final_results = self._rank_and_limit_results(results, limit)
139
+ logger.debug(
140
+ f"SearchService completed - returning {len(final_results)} final results after ranking and limiting"
141
+ )
142
+
143
+ if final_results:
144
+ logger.debug(
145
+ f"Top result: memory_id={final_results[0].get('memory_id')}, score={final_results[0].get('composite_score', 0):.3f}, strategy={final_results[0].get('search_strategy')}"
105
146
  )
106
147
 
107
- return self._rank_and_limit_results(results, limit)
148
+ return final_results
108
149
 
109
150
  def _search_sqlite_fts(
110
151
  self,
@@ -117,8 +158,22 @@ class SearchService:
117
158
  ) -> List[Dict[str, Any]]:
118
159
  """Search using SQLite FTS5"""
119
160
  try:
161
+ logger.debug(
162
+ f"SQLite FTS search starting for query: '{query}' in namespace: '{namespace}'"
163
+ )
164
+
165
+ # Use parameters to validate search scope
166
+ if not search_short_term and not search_long_term:
167
+ logger.debug("No memory types specified for search, defaulting to both")
168
+ search_short_term = search_long_term = True
169
+
170
+ logger.debug(
171
+ f"Search scope - short_term: {search_short_term}, long_term: {search_long_term}"
172
+ )
173
+
120
174
  # Build FTS query
121
175
  fts_query = f'"{query.strip()}"'
176
+ logger.debug(f"FTS query built: {fts_query}")
122
177
 
123
178
  # Build category filter
124
179
  category_clause = ""
@@ -133,41 +188,69 @@ class SearchService:
133
188
  )
134
189
  for i, cat in enumerate(category_filter):
135
190
  params[f"cat_{i}"] = cat
191
+ logger.debug(f"Category filter applied: {category_filter}")
136
192
 
137
- # SQLite FTS5 search query
193
+ # SQLite FTS5 search query with COALESCE to handle NULL values
138
194
  sql_query = f"""
139
195
  SELECT
140
- fts.memory_id, fts.memory_type, fts.category_primary,
141
- CASE
142
- WHEN fts.memory_type = 'short_term' THEN st.processed_data
143
- WHEN fts.memory_type = 'long_term' THEN lt.processed_data
144
- END as processed_data,
145
- CASE
146
- WHEN fts.memory_type = 'short_term' THEN st.importance_score
147
- WHEN fts.memory_type = 'long_term' THEN lt.importance_score
148
- ELSE 0.5
149
- END as importance_score,
150
- CASE
151
- WHEN fts.memory_type = 'short_term' THEN st.created_at
152
- WHEN fts.memory_type = 'long_term' THEN lt.created_at
153
- END as created_at,
154
- fts.summary,
155
- rank as search_score,
196
+ fts.memory_id,
197
+ fts.memory_type,
198
+ fts.category_primary,
199
+ COALESCE(
200
+ CASE
201
+ WHEN fts.memory_type = 'short_term' THEN st.processed_data
202
+ WHEN fts.memory_type = 'long_term' THEN lt.processed_data
203
+ END,
204
+ '{{}}'
205
+ ) as processed_data,
206
+ COALESCE(
207
+ CASE
208
+ WHEN fts.memory_type = 'short_term' THEN st.importance_score
209
+ WHEN fts.memory_type = 'long_term' THEN lt.importance_score
210
+ ELSE 0.5
211
+ END,
212
+ 0.5
213
+ ) as importance_score,
214
+ COALESCE(
215
+ CASE
216
+ WHEN fts.memory_type = 'short_term' THEN st.created_at
217
+ WHEN fts.memory_type = 'long_term' THEN lt.created_at
218
+ END,
219
+ datetime('now')
220
+ ) as created_at,
221
+ COALESCE(fts.summary, '') as summary,
222
+ COALESCE(rank, 0.0) as search_score,
156
223
  'sqlite_fts5' as search_strategy
157
224
  FROM memory_search_fts fts
158
225
  LEFT JOIN short_term_memory st ON fts.memory_id = st.memory_id AND fts.memory_type = 'short_term'
159
226
  LEFT JOIN long_term_memory lt ON fts.memory_id = lt.memory_id AND fts.memory_type = 'long_term'
160
227
  WHERE memory_search_fts MATCH :fts_query AND fts.namespace = :namespace
161
228
  {category_clause}
162
- ORDER BY rank, importance_score DESC
229
+ ORDER BY search_score, importance_score DESC
163
230
  LIMIT {limit}
164
231
  """
165
232
 
233
+ logger.debug(f"Executing SQLite FTS query with params: {params}")
166
234
  result = self.session.execute(text(sql_query), params)
167
- return [dict(row) for row in result]
235
+ rows = [dict(row) for row in result]
236
+ logger.debug(f"SQLite FTS search returned {len(rows)} results")
237
+
238
+ # Log details of first result for debugging
239
+ if rows:
240
+ logger.debug(
241
+ f"Sample result: memory_id={rows[0].get('memory_id')}, type={rows[0].get('memory_type')}, score={rows[0].get('search_score')}"
242
+ )
243
+
244
+ return rows
168
245
 
169
246
  except Exception as e:
170
- logger.debug(f"SQLite FTS5 search failed: {e}")
247
+ logger.error(
248
+ f"SQLite FTS5 search failed for query '{query}' in namespace '{namespace}': {e}"
249
+ )
250
+ logger.debug(
251
+ f"SQLite FTS5 error details: {type(e).__name__}: {str(e)}",
252
+ exc_info=True,
253
+ )
171
254
  # Roll back the transaction to recover from error state
172
255
  self.session.rollback()
173
256
  return []
@@ -185,6 +268,14 @@ class SearchService:
185
268
  results = []
186
269
 
187
270
  try:
271
+ # Apply limit proportionally between memory types
272
+ short_limit = (
273
+ limit // 2 if search_short_term and search_long_term else limit
274
+ )
275
+ long_limit = (
276
+ limit - short_limit if search_short_term and search_long_term else limit
277
+ )
278
+
188
279
  # Search short-term memory if requested
189
280
  if search_short_term:
190
281
  short_query = self.session.query(ShortTermMemory).filter(
@@ -203,7 +294,7 @@ class SearchService:
203
294
  ShortTermMemory.category_primary.in_(category_filter)
204
295
  )
205
296
 
206
- # Add relevance score
297
+ # Add relevance score and limit
207
298
  short_results = self.session.execute(
208
299
  short_query.statement.add_columns(
209
300
  text(
@@ -211,7 +302,7 @@ class SearchService:
211
302
  ).params(query=query),
212
303
  text("'short_term' as memory_type"),
213
304
  text("'mysql_fulltext' as search_strategy"),
214
- )
305
+ ).limit(short_limit)
215
306
  ).fetchall()
216
307
 
217
308
  results.extend([dict(row) for row in short_results])
@@ -234,7 +325,7 @@ class SearchService:
234
325
  LongTermMemory.category_primary.in_(category_filter)
235
326
  )
236
327
 
237
- # Add relevance score
328
+ # Add relevance score and limit
238
329
  long_results = self.session.execute(
239
330
  long_query.statement.add_columns(
240
331
  text(
@@ -242,7 +333,7 @@ class SearchService:
242
333
  ).params(query=query),
243
334
  text("'long_term' as memory_type"),
244
335
  text("'mysql_fulltext' as search_strategy"),
245
- )
336
+ ).limit(long_limit)
246
337
  ).fetchall()
247
338
 
248
339
  results.extend([dict(row) for row in long_results])
@@ -250,7 +341,13 @@ class SearchService:
250
341
  return results
251
342
 
252
343
  except Exception as e:
253
- logger.debug(f"MySQL FULLTEXT search failed: {e}")
344
+ logger.error(
345
+ f"MySQL FULLTEXT search failed for query '{query}' in namespace '{namespace}': {e}"
346
+ )
347
+ logger.debug(
348
+ f"MySQL FULLTEXT error details: {type(e).__name__}: {str(e)}",
349
+ exc_info=True,
350
+ )
254
351
  # Roll back the transaction to recover from error state
255
352
  self.session.rollback()
256
353
  return []
@@ -268,6 +365,14 @@ class SearchService:
268
365
  results = []
269
366
 
270
367
  try:
368
+ # Apply limit proportionally between memory types
369
+ short_limit = (
370
+ limit // 2 if search_short_term and search_long_term else limit
371
+ )
372
+ long_limit = (
373
+ limit - short_limit if search_short_term and search_long_term else limit
374
+ )
375
+
271
376
  # Prepare query for tsquery - handle spaces and special characters
272
377
  # Convert simple query to tsquery format (join words with &)
273
378
  tsquery_text = " & ".join(query.split())
@@ -290,7 +395,7 @@ class SearchService:
290
395
  ShortTermMemory.category_primary.in_(category_filter)
291
396
  )
292
397
 
293
- # Add relevance score
398
+ # Add relevance score and limit
294
399
  short_results = self.session.execute(
295
400
  short_query.statement.add_columns(
296
401
  text(
@@ -298,7 +403,9 @@ class SearchService:
298
403
  ).params(query=tsquery_text),
299
404
  text("'short_term' as memory_type"),
300
405
  text("'postgresql_fts' as search_strategy"),
301
- ).order_by(text("search_score DESC"))
406
+ )
407
+ .order_by(text("search_score DESC"))
408
+ .limit(short_limit)
302
409
  ).fetchall()
303
410
 
304
411
  results.extend([dict(row) for row in short_results])
@@ -321,7 +428,7 @@ class SearchService:
321
428
  LongTermMemory.category_primary.in_(category_filter)
322
429
  )
323
430
 
324
- # Add relevance score
431
+ # Add relevance score and limit
325
432
  long_results = self.session.execute(
326
433
  long_query.statement.add_columns(
327
434
  text(
@@ -329,7 +436,9 @@ class SearchService:
329
436
  ).params(query=tsquery_text),
330
437
  text("'long_term' as memory_type"),
331
438
  text("'postgresql_fts' as search_strategy"),
332
- ).order_by(text("search_score DESC"))
439
+ )
440
+ .order_by(text("search_score DESC"))
441
+ .limit(long_limit)
333
442
  ).fetchall()
334
443
 
335
444
  results.extend([dict(row) for row in long_results])
@@ -337,7 +446,13 @@ class SearchService:
337
446
  return results
338
447
 
339
448
  except Exception as e:
340
- logger.debug(f"PostgreSQL FTS search failed: {e}")
449
+ logger.error(
450
+ f"PostgreSQL FTS search failed for query '{query}' in namespace '{namespace}': {e}"
451
+ )
452
+ logger.debug(
453
+ f"PostgreSQL FTS error details: {type(e).__name__}: {str(e)}",
454
+ exc_info=True,
455
+ )
341
456
  # Roll back the transaction to recover from error state
342
457
  self.session.rollback()
343
458
  return []
@@ -351,19 +466,42 @@ class SearchService:
351
466
  search_short_term: bool,
352
467
  search_long_term: bool,
353
468
  ) -> List[Dict[str, Any]]:
354
- """Fallback LIKE-based search"""
469
+ """Fallback LIKE-based search with improved flexibility"""
470
+ logger.debug(
471
+ f"Starting LIKE fallback search for query: '{query}' in namespace: '{namespace}'"
472
+ )
355
473
  results = []
356
- search_pattern = f"%{query}%"
474
+
475
+ # Create multiple search patterns for better matching
476
+ search_patterns = [
477
+ f"%{query}%", # Original full query
478
+ ]
479
+
480
+ # Add individual word patterns for better matching
481
+ words = query.strip().split()
482
+ if len(words) > 1:
483
+ for word in words:
484
+ if len(word) > 2: # Skip very short words
485
+ search_patterns.append(f"%{word}%")
486
+
487
+ logger.debug(f"LIKE search patterns: {search_patterns}")
357
488
 
358
489
  # Search short-term memory
359
490
  if search_short_term:
491
+ # Build OR conditions for all search patterns
492
+ search_conditions = []
493
+ for pattern in search_patterns:
494
+ search_conditions.extend(
495
+ [
496
+ ShortTermMemory.searchable_content.like(pattern),
497
+ ShortTermMemory.summary.like(pattern),
498
+ ]
499
+ )
500
+
360
501
  short_query = self.session.query(ShortTermMemory).filter(
361
502
  and_(
362
503
  ShortTermMemory.namespace == namespace,
363
- or_(
364
- ShortTermMemory.searchable_content.like(search_pattern),
365
- ShortTermMemory.summary.like(search_pattern),
366
- ),
504
+ or_(*search_conditions),
367
505
  )
368
506
  )
369
507
 
@@ -381,6 +519,8 @@ class SearchService:
381
519
  .all()
382
520
  )
383
521
 
522
+ logger.debug(f"LIKE fallback found {len(short_results)} short-term results")
523
+
384
524
  for result in short_results:
385
525
  memory_dict = {
386
526
  "memory_id": result.memory_id,
@@ -397,13 +537,20 @@ class SearchService:
397
537
 
398
538
  # Search long-term memory
399
539
  if search_long_term:
540
+ # Build OR conditions for all search patterns
541
+ search_conditions = []
542
+ for pattern in search_patterns:
543
+ search_conditions.extend(
544
+ [
545
+ LongTermMemory.searchable_content.like(pattern),
546
+ LongTermMemory.summary.like(pattern),
547
+ ]
548
+ )
549
+
400
550
  long_query = self.session.query(LongTermMemory).filter(
401
551
  and_(
402
552
  LongTermMemory.namespace == namespace,
403
- or_(
404
- LongTermMemory.searchable_content.like(search_pattern),
405
- LongTermMemory.summary.like(search_pattern),
406
- ),
553
+ or_(*search_conditions),
407
554
  )
408
555
  )
409
556
 
@@ -421,6 +568,8 @@ class SearchService:
421
568
  .all()
422
569
  )
423
570
 
571
+ logger.debug(f"LIKE fallback found {len(long_results)} long-term results")
572
+
424
573
  for result in long_results:
425
574
  memory_dict = {
426
575
  "memory_id": result.memory_id,
@@ -435,6 +584,9 @@ class SearchService:
435
584
  }
436
585
  results.append(memory_dict)
437
586
 
587
+ logger.debug(
588
+ f"LIKE fallback search completed, returning {len(results)} total results"
589
+ )
438
590
  return results
439
591
 
440
592
  def _get_recent_memories(
@@ -425,10 +425,31 @@ class SQLAlchemyDatabaseManager:
425
425
  logger.warning(f"PostgreSQL FTS setup failed: {e}")
426
426
 
427
427
  def _get_search_service(self) -> SearchService:
428
- """Get search service instance with fresh session"""
429
- # Always create a new session to avoid stale connections
430
- session = self.SessionLocal()
431
- return SearchService(session, self.database_type)
428
+ """Get search service instance with fresh session and proper error handling"""
429
+ try:
430
+ if not self.SessionLocal:
431
+ logger.error("SessionLocal not available for search service")
432
+ return None
433
+
434
+ # Always create a new session to avoid stale connections
435
+ session = self.SessionLocal()
436
+ if not session:
437
+ logger.error("Failed to create database session")
438
+ return None
439
+
440
+ search_service = SearchService(session, self.database_type)
441
+ logger.debug(
442
+ f"Created new search service instance for database type: {self.database_type}"
443
+ )
444
+ return search_service
445
+
446
+ except Exception as e:
447
+ logger.error(f"Failed to create search service: {e}")
448
+ logger.debug(
449
+ f"Search service creation error: {type(e).__name__}: {str(e)}",
450
+ exc_info=True,
451
+ )
452
+ return None
432
453
 
433
454
  def store_chat_history(
434
455
  self,
@@ -564,23 +585,51 @@ class SQLAlchemyDatabaseManager:
564
585
  limit: int = 10,
565
586
  ) -> List[Dict[str, Any]]:
566
587
  """Search memories using the cross-database search service"""
588
+ search_service = None
567
589
  try:
590
+ logger.debug(
591
+ f"Starting memory search for query '{query}' in namespace '{namespace}' with category_filter={category_filter}"
592
+ )
568
593
  search_service = self._get_search_service()
569
- try:
570
- results = search_service.search_memories(
571
- query, namespace, category_filter, limit
594
+
595
+ if not search_service:
596
+ logger.error("Failed to create search service instance")
597
+ return []
598
+
599
+ results = search_service.search_memories(
600
+ query, namespace, category_filter, limit
601
+ )
602
+ logger.debug(f"Search for '{query}' returned {len(results)} results")
603
+
604
+ # Validate results structure
605
+ if not isinstance(results, list):
606
+ logger.warning(
607
+ f"Search service returned unexpected type: {type(results)}, converting to list"
572
608
  )
573
- logger.debug(f"Search for '{query}' returned {len(results)} results")
574
- return results
575
- finally:
576
- # Ensure session is properly closed
577
- search_service.session.close()
609
+ results = list(results) if results else []
610
+
611
+ return results
578
612
 
579
613
  except Exception as e:
580
- logger.error(f"Memory search failed for query '{query}': {e}")
614
+ logger.error(
615
+ f"Memory search failed for query '{query}' in namespace '{namespace}': {e}"
616
+ )
617
+ logger.debug(
618
+ f"Search error details: {type(e).__name__}: {str(e)}", exc_info=True
619
+ )
581
620
  # Return empty list instead of raising exception to avoid breaking auto_ingest
582
621
  return []
583
622
 
623
+ finally:
624
+ # Ensure session is properly closed, even if an exception occurred
625
+ if search_service and hasattr(search_service, "session"):
626
+ try:
627
+ if search_service.session:
628
+ logger.debug("Closing search service session")
629
+ search_service.session.close()
630
+ except Exception as session_e:
631
+ logger.warning(f"Error closing search service session: {session_e}")
632
+
584
633
  def get_memory_stats(self, namespace: str = "default") -> Dict[str, Any]:
585
634
  """Get comprehensive memory statistics"""
586
635
  with self.SessionLocal() as session:
@@ -73,11 +73,24 @@ class MemoryTool:
73
73
 
74
74
  # Use retrieval agent for intelligent search
75
75
  try:
76
+ logger.debug(
77
+ f"Attempting to import MemorySearchEngine for query: '{query}'"
78
+ )
76
79
  from ..agents.retrieval_agent import MemorySearchEngine
77
80
 
81
+ logger.debug("Successfully imported MemorySearchEngine")
82
+
78
83
  # Create search engine if not already initialized
79
84
  if not hasattr(self, "_search_engine"):
80
- self._search_engine = MemorySearchEngine()
85
+ if (
86
+ hasattr(self.memori, "provider_config")
87
+ and self.memori.provider_config
88
+ ):
89
+ self._search_engine = MemorySearchEngine(
90
+ provider_config=self.memori.provider_config
91
+ )
92
+ else:
93
+ self._search_engine = MemorySearchEngine()
81
94
 
82
95
  # Execute search using retrieval agent
83
96
  results = self._search_engine.execute_search(
@@ -88,18 +101,62 @@ class MemoryTool:
88
101
  )
89
102
 
90
103
  if not results:
104
+ logger.debug(
105
+ f"Primary search returned no results for query: '{query}', trying fallback search"
106
+ )
107
+ # Try fallback direct database search
108
+ try:
109
+ fallback_results = self.memori.db_manager.search_memories(
110
+ query=query, namespace=self.memori.namespace, limit=5
111
+ )
112
+
113
+ if fallback_results:
114
+ logger.debug(
115
+ f"Fallback search found {len(fallback_results)} results"
116
+ )
117
+ results = fallback_results
118
+ else:
119
+ logger.warning(
120
+ f"Both primary and fallback search returned no results for query: '{query}'"
121
+ )
122
+ return f"No relevant memories found for query: '{query}'"
123
+
124
+ except Exception as fallback_e:
125
+ logger.error(
126
+ f"Fallback search also failed for query '{query}': {fallback_e}"
127
+ )
128
+ return f"No relevant memories found for query: '{query}'"
129
+
130
+ # Ensure we have results to format
131
+ if not results:
132
+ logger.warning(
133
+ f"No results available for formatting for query: '{query}'"
134
+ )
91
135
  return f"No relevant memories found for query: '{query}'"
92
136
 
93
137
  # Format results as a readable string
138
+ logger.debug(
139
+ f"Starting to format {len(results)} results for query: '{query}'"
140
+ )
94
141
  formatted_output = f"🔍 Memory Search Results for: '{query}'\n\n"
95
142
 
96
143
  for i, result in enumerate(results, 1):
97
144
  try:
145
+ logger.debug(
146
+ f"Formatting result {i}: type={type(result)}, keys={list(result.keys()) if isinstance(result, dict) else 'not-dict'}"
147
+ )
148
+
98
149
  # Try to parse processed data for better formatting
99
150
  if "processed_data" in result:
100
151
  import json
101
152
 
102
- processed_data = json.loads(result["processed_data"])
153
+ if isinstance(result["processed_data"], dict):
154
+ processed_data = result["processed_data"]
155
+ elif isinstance(result["processed_data"], str):
156
+ processed_data = json.loads(result["processed_data"])
157
+ else:
158
+ raise ValueError("Error, wrong 'processed_data' format")
159
+
103
160
  summary = processed_data.get("summary", "")
104
161
  category = processed_data.get("category", {}).get(
105
162
  "primary_category", ""
@@ -124,34 +181,63 @@ class MemoryTool:
124
181
 
125
182
  formatted_output += "\n"
126
183
 
127
- except Exception:
184
+ except Exception as format_e:
185
+ logger.warning(f"Error formatting result {i}: {format_e}")
128
186
  # Fallback formatting
129
187
  content = result.get(
130
188
  "searchable_content", "Memory content available"
131
189
  )[:100]
132
190
  formatted_output += f"{i}. {content}...\n\n"
133
191
 
192
+ logger.debug(
193
+ f"Successfully formatted results, output length: {len(formatted_output)}"
194
+ )
134
195
  return formatted_output.strip()
135
196
 
136
- except ImportError:
197
+ except ImportError as import_e:
198
+ logger.warning(
199
+ f"Failed to import MemorySearchEngine for query '{query}': {import_e}"
200
+ )
137
201
  # Fallback to original search methods if retrieval agent is not available
202
+ logger.debug(
203
+ f"Using ImportError fallback search methods for query: '{query}'"
204
+ )
205
+
138
206
  # Try different search strategies based on query content
139
207
  if any(word in query.lower() for word in ["name", "who am i", "about me"]):
208
+ logger.debug(
209
+ f"Trying essential conversations for personal query: '{query}'"
210
+ )
140
211
  # Personal information query - try essential conversations first
141
212
  essential_result = self._get_essential_conversations()
142
213
  if essential_result.get("count", 0) > 0:
214
+ logger.debug(
215
+ f"Essential conversations found {essential_result.get('count', 0)} results"
216
+ )
143
217
  return self._format_dict_to_string(essential_result)
144
218
 
145
219
  # General search
220
+ logger.debug(f"Trying general search for query: '{query}'")
146
221
  search_result = self._search_memories(query=query, limit=10)
222
+ logger.debug(
223
+ f"General search returned results_count: {search_result.get('results_count', 0)}"
224
+ )
147
225
  if search_result.get("results_count", 0) > 0:
148
226
  return self._format_dict_to_string(search_result)
149
227
 
150
228
  # Fallback to context retrieval
229
+ logger.debug(f"Trying context retrieval fallback for query: '{query}'")
151
230
  context_result = self._retrieve_context(query=query, limit=5)
231
+ logger.debug(
232
+ f"Context retrieval returned context_count: {context_result.get('context_count', 0)}"
233
+ )
152
234
  return self._format_dict_to_string(context_result)
153
235
 
154
236
  except Exception as e:
237
+ logger.error(
238
+ f"Unexpected error in memory tool execute for query '{query}': {e}",
239
+ exc_info=True,
240
+ )
155
241
  return f"Error searching memories: {str(e)}"
156
242
 
157
243
  def _format_dict_to_string(self, result_dict: Dict[str, Any]) -> str:
@@ -293,6 +379,8 @@ class MemoryTool:
293
379
 
294
380
  def _get_stats(self, **kwargs) -> Dict[str, Any]:
295
381
  """Get memory and integration statistics"""
382
+ # kwargs can be used for future filtering options
383
+ _ = kwargs # Mark as intentionally unused
296
384
  try:
297
385
  memory_stats = self.memori.get_memory_stats()
298
386
  integration_stats = self.memori.get_integration_stats()
@@ -348,6 +436,8 @@ class MemoryTool:
348
436
 
349
437
  def _trigger_analysis(self, **kwargs) -> Dict[str, Any]:
350
438
  """Trigger conscious agent analysis"""
439
+ # kwargs can be used for future analysis options
440
+ _ = kwargs # Mark as intentionally unused
351
441
  try:
352
442
  if hasattr(self.memori, "trigger_conscious_analysis"):
353
443
  self.memori.trigger_conscious_analysis()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorisdk
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: The Open-Source Memory Layer for AI Agents & Multi-Agent Systems
5
5
  Author-email: GibsonAI Team <noc@gibsonai.com>
6
6
  License: Apache-2.0
@@ -458,19 +458,25 @@ memori/
458
458
  - **[Memory Retrieval](./memory_retrival_example.py)** - Function calling with memory tools
459
459
  - **[Advanced Config](./examples/advanced_config.py)** - Production configuration
460
460
  - **[Interactive Demo](./memori_example.py)** - Live conscious ingestion showcase
461
+ - **[Simple Multi-User](./examples/multiple-users/simple_multiuser.py)** - Basic demonstration of user memory isolation with namespaces
462
+ - **[FastAPI Multi-User App](./examples/multiple-users/fastapi_multiuser_app.py)** - Full-featured REST API with Swagger UI for testing multi-user functionality
461
463
 
462
464
  ## Framework Integrations
463
465
 
464
466
  Memori works seamlessly with popular AI frameworks:
465
467
 
466
- | Framework | Description | Example | Features |
467
- |-----------|-------------|---------|----------|
468
- | 🤖 [Agno](./examples/integrations/agno_example.py) | Memory-enhanced agent framework integration with persistent conversations | Simple chat agent with memory search | Memory tools, conversation persistence, contextual responses |
469
- | 👥 [CrewAI](./examples/integrations/crewai_example.py) | Multi-agent system with shared memory across agent interactions | Collaborative agents with memory | Agent coordination, shared memory, task-based workflows |
470
- | 🌊 [Digital Ocean AI](./examples/integrations/digital_ocean_example.py) | Memory-enhanced customer support using Digital Ocean's AI platform | Customer support assistant with conversation history | Context injection, session continuity, support analytics |
471
- | 🔗 [LangChain](./examples/integrations/langchain_example.py) | Enterprise-grade agent framework with advanced memory integration | AI assistant with LangChain tools and memory | Custom tools, agent executors, memory persistence, error handling |
472
- | [OpenAI Agent](./examples/integrations/openai_agent_example.py) | Memory-enhanced OpenAI Agent with function calling and user preference tracking | Interactive assistant with memory search and user info storage | Function calling tools, memory search, preference tracking, async conversations |
473
- | �🚀 [Swarms](./examples/integrations/swarms_example.py) | Multi-agent system framework with persistent memory capabilities | Memory-enhanced Swarms agents with auto/conscious ingestion | Agent memory persistence, multi-agent coordination, contextual awareness |
468
+ | Framework | Description | Example |
469
+ |-----------|-------------|---------|
470
+ | [AgentOps](./examples/integrations/agentops_example.py) | Track and monitor Memori memory operations with comprehensive observability | Memory operation tracking with AgentOps analytics |
471
+ | [Agno](./examples/integrations/agno_example.py) | Memory-enhanced agent framework integration with persistent conversations | Simple chat agent with memory search |
472
+ | [AWS Strands](./examples/integrations/aws_strands_example.py) | Professional development coach with Strands SDK and persistent memory | Career coaching agent with goal tracking |
473
+ | [Azure AI Foundry](./examples/integrations/azure_ai_foundry_example.py) | Azure AI Foundry agents with persistent memory across conversations | Enterprise AI agents with Azure integration |
474
+ | [CamelAI](./examples/integrations/camelai_example.py) | Multi-agent communication framework with automatic memory recording and retrieval | Memory-enhanced chat agents with conversation continuity |
475
+ | [CrewAI](./examples/integrations/crewai_example.py) | Multi-agent system with shared memory across agent interactions | Collaborative agents with memory |
476
+ | [Digital Ocean AI](./examples/integrations/digital_ocean_example.py) | Memory-enhanced customer support using Digital Ocean's AI platform | Customer support assistant with conversation history |
477
+ | [LangChain](./examples/integrations/langchain_example.py) | Enterprise-grade agent framework with advanced memory integration | AI assistant with LangChain tools and memory |
478
+ | [OpenAI Agent](./examples/integrations/openai_agent_example.py) | Memory-enhanced OpenAI Agent with function calling and user preference tracking | Interactive assistant with memory search and user info storage |
479
+ | [Swarms](./examples/integrations/swarms_example.py) | Multi-agent system framework with persistent memory capabilities | Memory-enhanced Swarms agents with auto/conscious ingestion |
474
480
 
475
481
  ## Interactive Demos
476
482
 
@@ -1,8 +1,8 @@
1
- memori/__init__.py,sha256=emYkTqCGsQfc-qY2vSTm4-w9DVJdziC9RTfSrktUmG0,3676
1
+ memori/__init__.py,sha256=1PEpzV75fNiOmS6aIsDAQOpkPzdW2xq64beYADfaG70,3676
2
2
  memori/agents/__init__.py,sha256=9M3IG5R10FfVgT8tUzBZ2pZ0SypSpYkFfhtyvMyeTpE,261
3
3
  memori/agents/conscious_agent.py,sha256=NQ-dEHRXutrkwo2ssPZ0ct7uHXU2gDa-PQ0d74mUFvk,13459
4
4
  memori/agents/memory_agent.py,sha256=tBwkNMtlVDlQ88jATjuL96vQHPfU_HOz_XXOB3vd710,23552
5
- memori/agents/retrieval_agent.py,sha256=w1_6j_OJytzAF4IQi5nRWUxNIyIyHK8nHdklYYwsCxU,37145
5
+ memori/agents/retrieval_agent.py,sha256=FvGApAzljhubFvyMTM0rCs-JpROg34i0aOFeYh3Ru6s,40222
6
6
  memori/config/__init__.py,sha256=tQAxopgOsea02u9iId-ocOY86nWWNGC3rvt3AOFcLn8,295
7
7
  memori/config/manager.py,sha256=xi8d8xW3obyV6v9UHDG6idSAymge8yWxGW11e2mI0nQ,10388
8
8
  memori/config/memory_manager.py,sha256=hxwkMpUaOkOcnnvgpHxR4PT6iHNj8yNda-o9OpM0Y-g,11020
@@ -10,15 +10,15 @@ memori/config/settings.py,sha256=nrrWD4hwdbtYlIPtJFHgGyMudGP-hz9sA-KBW_7ZZbE,972
10
10
  memori/core/__init__.py,sha256=jvhHn-KL3bzRHs11-4B0BCKH6gkAf6Gf_G59If8fD0M,157
11
11
  memori/core/conversation.py,sha256=l1SRLyIrKrcSzqEdqnqLwSqYW6rXtGeH1Fhs7YBI0XU,15825
12
12
  memori/core/database.py,sha256=RkBelmgDm_6WoTKISPTz_WYVsPvnVB7NKJuRFWOLbZM,40044
13
- memori/core/memory.py,sha256=sORgSkIdQ00iTuJYG_zjmNN6sUnf8DQ-2vk_A03-X6Y,103388
13
+ memori/core/memory.py,sha256=Wg1K8XUxGHwgkVi5nRDY7PhfVSRhAGMGN8xd8KnuQVg,105518
14
14
  memori/core/providers.py,sha256=Ix8CEb_kw0Qur1E3_mxydh3RvUpyNy5qGAKK4wuGK6Y,7032
15
15
  memori/database/__init__.py,sha256=kMLxwfRfTVvw0oV1kl9v-Dkyqm6ggcsMV6hltqdrN3k,189
16
16
  memori/database/auto_creator.py,sha256=pQUKV9hO-wM-vocr-_2I-1kwCofd3z8-KpkHAxLREaM,12686
17
17
  memori/database/connection_utils.py,sha256=iAxVvwP-_0UZwmc_y_GOs3-26YlPTmot6_bY8crIPxQ,6741
18
18
  memori/database/models.py,sha256=jeC5JiVXjxzk3ABt7BeXfkAYOpyjcQ3OqnuLkfIIRyc,14832
19
19
  memori/database/query_translator.py,sha256=oe_BCPamtT-LBBUKChbM0jK4rjI4ZDZtVp7ZUGnpDqs,7026
20
- memori/database/search_service.py,sha256=xh1MtXbov48HAcJpeMYHh9JUTEWrqRBqn0kahriGJqU,20584
21
- memori/database/sqlalchemy_manager.py,sha256=hC0bC-XJm81bCWKYtuMowFTkVdQayTWb8893O6MbhN8,33049
20
+ memori/database/search_service.py,sha256=9J6rqCY9R0cxJRssbii73s49VvZbMgdAFFJgasoaj9A,26902
21
+ memori/database/sqlalchemy_manager.py,sha256=KvVv3TcBxB-FTiL1YWolRlklDca-YCfdxCWaDhAHILw,34974
22
22
  memori/database/adapters/__init__.py,sha256=lSvRPZXJoKTlj4iI8kW5I45OXjKZh8oFb6vqDpJ69sQ,366
23
23
  memori/database/adapters/mysql_adapter.py,sha256=WOe-huYmrt-aeDn0YACnH3F5bhSTjG0SePjl4hThd-s,12944
24
24
  memori/database/adapters/postgresql_adapter.py,sha256=tGBGoBo340bLluhb9DytflDdVnhkjbgaCY2SvvoUpfA,11358
@@ -45,9 +45,8 @@ memori/integrations/__init__.py,sha256=jlTI3TRBpclXXiqbigSUVqpfN9t64Q9B17EhlN543
45
45
  memori/integrations/anthropic_integration.py,sha256=IJtqPYTcMaYsXKWUabR4XDMpCabg9qMK7d-8T9A1CcY,8169
46
46
  memori/integrations/litellm_integration.py,sha256=UWC5huyUMasfZDBzH7MxAVbTXoo70qvyGcrYZI7Gx_Y,12407
47
47
  memori/integrations/openai_integration.py,sha256=iQKitY1JY_i-YldcAeePnVdYc6Ns0Gmk_zIoMV302RA,19977
48
- memori/scripts/llm_text.py,sha256=HDkuGMb527yupSbA3syBFA9WHBPnP2lVqEKW6trIZtU,1603
49
48
  memori/tools/__init__.py,sha256=0KPbWAFYmvEleacrby4RzhJGW5GPdFiXN6RWwFrbqf4,200
50
- memori/tools/memory_tool.py,sha256=j57j2-MbhC9oTQJxBOnEBssB536OAb7igFA-XnxtHy4,20917
49
+ memori/tools/memory_tool.py,sha256=g4H774npQoYWB8vnTY5xdLrLbEq5i1lK8FDm8MfFtVU,25077
51
50
  memori/utils/__init__.py,sha256=e3AN4KfomQBQDsr53HwfvOeTtI3QZMzGQMYpRp8l6ow,1757
52
51
  memori/utils/exceptions.py,sha256=JGLo2S8ElG3gBjD4aJeVPWNsNB9OLPYAYzCdKfiEW74,12136
53
52
  memori/utils/helpers.py,sha256=_lpGSsI2UkMyYUY6X9k_VEpACvyxwY51TzgYVPZTeBk,13059
@@ -60,8 +59,8 @@ memori/utils/security_audit.py,sha256=BB9IwDdAiUgmuwjm8rEyHbthfTHr1sipcNlPdFpBs_
60
59
  memori/utils/security_integration.py,sha256=TORuhOPEAQ1PRkt7xcBHhI1UbCd4LVLowdiv2UItwCY,13078
61
60
  memori/utils/transaction_manager.py,sha256=ITSF1Gba4cEBJXW1woDyomhOsdfL8vwfGckqbYqAGok,18755
62
61
  memori/utils/validators.py,sha256=2btILpEh2yBS_6rytp2B2epFxMT4876SibS0yNj6bKI,11287
63
- memorisdk-2.0.0.dist-info/licenses/LICENSE,sha256=gyrDaYsSODngoYE1l68l_UfjppS-oYDrf1MvY1JGhgE,10430
64
- memorisdk-2.0.0.dist-info/METADATA,sha256=GtHOV0QPtwUELPHMuj8GD6lMo8AGbrYy031xY4nKPWI,18201
65
- memorisdk-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
66
- memorisdk-2.0.0.dist-info/top_level.txt,sha256=Nm3ad0isbJYBzTEce-O_gmkAEiTbAbyilgAhRt8IoGA,7
67
- memorisdk-2.0.0.dist-info/RECORD,,
62
+ memorisdk-2.0.1.dist-info/licenses/LICENSE,sha256=gyrDaYsSODngoYE1l68l_UfjppS-oYDrf1MvY1JGhgE,10430
63
+ memorisdk-2.0.1.dist-info/METADATA,sha256=sqUK-ZDWt9bYhI5VtLlTLCabZuNdem9ZPHO-JM6S308,18803
64
+ memorisdk-2.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
65
+ memorisdk-2.0.1.dist-info/top_level.txt,sha256=Nm3ad0isbJYBzTEce-O_gmkAEiTbAbyilgAhRt8IoGA,7
66
+ memorisdk-2.0.1.dist-info/RECORD,,
@@ -1,50 +0,0 @@
1
- import os
2
-
3
-
4
- def is_text_file(file_path):
5
- try:
6
- with open(file_path, encoding="utf-8") as f:
7
- f.read()
8
- return True
9
- except (UnicodeDecodeError, OSError):
10
- return False
11
-
12
-
13
- def ingest_folder_to_txt(
14
- input_path, output_file="ingested_data.txt", exclude_dirs=None
15
- ):
16
- if exclude_dirs is None:
17
- exclude_dirs = [".git", "node_modules", "__pycache__"]
18
-
19
- with open(output_file, "w", encoding="utf-8") as out_f:
20
- for root, dirs, files in os.walk(input_path):
21
- # Filter out excluded directories
22
- dirs[:] = [d for d in dirs if d not in exclude_dirs]
23
-
24
- for file in files:
25
- file_path = os.path.join(root, file)
26
-
27
- # Skip binary files or the output file itself
28
- if not is_text_file(file_path) or os.path.abspath(
29
- file_path
30
- ) == os.path.abspath(output_file):
31
- continue
32
-
33
- try:
34
- with open(file_path, encoding="utf-8") as in_f:
35
- content = in_f.read()
36
-
37
- relative_path = os.path.relpath(file_path, input_path)
38
- out_f.write(f"\n### FILE: {relative_path} ###\n")
39
- out_f.write(content + "\n")
40
-
41
- except Exception as e:
42
- print(f"Skipping {file_path}: {e}")
43
-
44
- print(f"\n✅ Ingested data written to: {output_file}")
45
-
46
-
47
- # ---- Run from CLI or script ----
48
- if __name__ == "__main__":
49
- folder_path = input("Enter the path to the folder: ").strip()
50
- ingest_folder_to_txt(folder_path)