memorisdk 2.0.0__py3-none-any.whl → 2.1.0__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.

Files changed (63) hide show
  1. memori/__init__.py +3 -3
  2. memori/agents/conscious_agent.py +289 -77
  3. memori/agents/memory_agent.py +19 -9
  4. memori/agents/retrieval_agent.py +138 -63
  5. memori/config/manager.py +7 -7
  6. memori/config/memory_manager.py +25 -25
  7. memori/config/settings.py +13 -6
  8. memori/core/conversation.py +15 -15
  9. memori/core/database.py +14 -13
  10. memori/core/memory.py +438 -123
  11. memori/core/providers.py +25 -25
  12. memori/database/__init__.py +11 -0
  13. memori/database/adapters/__init__.py +11 -0
  14. memori/database/adapters/mongodb_adapter.py +739 -0
  15. memori/database/adapters/mysql_adapter.py +8 -8
  16. memori/database/adapters/postgresql_adapter.py +6 -6
  17. memori/database/adapters/sqlite_adapter.py +6 -6
  18. memori/database/auto_creator.py +8 -9
  19. memori/database/connection_utils.py +5 -5
  20. memori/database/connectors/__init__.py +11 -0
  21. memori/database/connectors/base_connector.py +18 -19
  22. memori/database/connectors/mongodb_connector.py +527 -0
  23. memori/database/connectors/mysql_connector.py +13 -15
  24. memori/database/connectors/postgres_connector.py +12 -12
  25. memori/database/connectors/sqlite_connector.py +11 -11
  26. memori/database/models.py +2 -2
  27. memori/database/mongodb_manager.py +1402 -0
  28. memori/database/queries/base_queries.py +3 -4
  29. memori/database/queries/chat_queries.py +3 -5
  30. memori/database/queries/entity_queries.py +3 -5
  31. memori/database/queries/memory_queries.py +3 -5
  32. memori/database/query_translator.py +11 -11
  33. memori/database/schema_generators/__init__.py +11 -0
  34. memori/database/schema_generators/mongodb_schema_generator.py +666 -0
  35. memori/database/schema_generators/mysql_schema_generator.py +2 -4
  36. memori/database/search/__init__.py +11 -0
  37. memori/database/search/mongodb_search_adapter.py +653 -0
  38. memori/database/search/mysql_search_adapter.py +8 -8
  39. memori/database/search/sqlite_search_adapter.py +6 -6
  40. memori/database/search_service.py +218 -66
  41. memori/database/sqlalchemy_manager.py +72 -25
  42. memori/integrations/__init__.py +1 -1
  43. memori/integrations/anthropic_integration.py +1 -3
  44. memori/integrations/litellm_integration.py +23 -6
  45. memori/integrations/openai_integration.py +31 -3
  46. memori/tools/memory_tool.py +104 -13
  47. memori/utils/exceptions.py +58 -58
  48. memori/utils/helpers.py +11 -12
  49. memori/utils/input_validator.py +10 -12
  50. memori/utils/logging.py +4 -4
  51. memori/utils/pydantic_models.py +57 -57
  52. memori/utils/query_builder.py +20 -20
  53. memori/utils/security_audit.py +28 -28
  54. memori/utils/security_integration.py +9 -9
  55. memori/utils/transaction_manager.py +20 -19
  56. memori/utils/validators.py +6 -6
  57. {memorisdk-2.0.0.dist-info → memorisdk-2.1.0.dist-info}/METADATA +36 -20
  58. memorisdk-2.1.0.dist-info/RECORD +71 -0
  59. memori/scripts/llm_text.py +0 -50
  60. memorisdk-2.0.0.dist-info/RECORD +0 -67
  61. {memorisdk-2.0.0.dist-info → memorisdk-2.1.0.dist-info}/WHEEL +0 -0
  62. {memorisdk-2.0.0.dist-info → memorisdk-2.1.0.dist-info}/licenses/LICENSE +0 -0
  63. {memorisdk-2.0.0.dist-info → memorisdk-2.1.0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ MySQL FULLTEXT search adapter for Memori
3
3
  Implements MySQL-specific search functionality
4
4
  """
5
5
 
6
- from typing import Any, Dict, List, Optional
6
+ from typing import Any
7
7
 
8
8
  from loguru import logger
9
9
 
@@ -49,9 +49,9 @@ class MySQLSearchAdapter(BaseSearchAdapter):
49
49
  self,
50
50
  query: str,
51
51
  namespace: str = "default",
52
- category_filter: Optional[List[str]] = None,
52
+ category_filter: list[str] | None = None,
53
53
  limit: int = 10,
54
- ) -> List[Dict[str, Any]]:
54
+ ) -> list[dict[str, Any]]:
55
55
  """Execute MySQL FULLTEXT search or fallback to LIKE search"""
56
56
  if self.fulltext_available and query and query.strip():
57
57
  try:
@@ -70,9 +70,9 @@ class MySQLSearchAdapter(BaseSearchAdapter):
70
70
  self,
71
71
  query: str,
72
72
  namespace: str,
73
- category_filter: Optional[List[str]],
73
+ category_filter: list[str] | None,
74
74
  limit: int,
75
- ) -> List[Dict[str, Any]]:
75
+ ) -> list[dict[str, Any]]:
76
76
  """Execute MySQL FULLTEXT search"""
77
77
  results = []
78
78
  translated_query = self.translate_search_query(query)
@@ -160,7 +160,7 @@ class MySQLSearchAdapter(BaseSearchAdapter):
160
160
  except:
161
161
  return 0.0
162
162
 
163
- def create_search_indexes(self) -> List[str]:
163
+ def create_search_indexes(self) -> list[str]:
164
164
  """Create MySQL FULLTEXT indexes"""
165
165
  commands = [
166
166
  # Create FULLTEXT index on short_term_memory
@@ -206,9 +206,9 @@ class MySQLSearchAdapter(BaseSearchAdapter):
206
206
  self,
207
207
  query: str,
208
208
  namespace: str = "default",
209
- category_filter: Optional[List[str]] = None,
209
+ category_filter: list[str] | None = None,
210
210
  limit: int = 10,
211
- ) -> List[Dict[str, Any]]:
211
+ ) -> list[dict[str, Any]]:
212
212
  """Execute fallback LIKE-based search with MySQL syntax"""
213
213
  results = []
214
214
 
@@ -4,7 +4,7 @@ Maintains existing FTS5 functionality
4
4
  """
5
5
 
6
6
  import sqlite3
7
- from typing import Any, Dict, List, Optional
7
+ from typing import Any
8
8
 
9
9
  from loguru import logger
10
10
 
@@ -34,9 +34,9 @@ class SQLiteSearchAdapter(BaseSearchAdapter):
34
34
  self,
35
35
  query: str,
36
36
  namespace: str = "default",
37
- category_filter: Optional[List[str]] = None,
37
+ category_filter: list[str] | None = None,
38
38
  limit: int = 10,
39
- ) -> List[Dict[str, Any]]:
39
+ ) -> list[dict[str, Any]]:
40
40
  """Execute FTS5 search or fallback to LIKE search"""
41
41
  if self.fts5_available:
42
42
  try:
@@ -53,9 +53,9 @@ class SQLiteSearchAdapter(BaseSearchAdapter):
53
53
  self,
54
54
  query: str,
55
55
  namespace: str,
56
- category_filter: Optional[List[str]],
56
+ category_filter: list[str] | None,
57
57
  limit: int,
58
- ) -> List[Dict[str, Any]]:
58
+ ) -> list[dict[str, Any]]:
59
59
  """Execute FTS5 search (adapted from original implementation)"""
60
60
  # Build FTS query with category filter - handle empty queries
61
61
  if query and query.strip():
@@ -119,7 +119,7 @@ class SQLiteSearchAdapter(BaseSearchAdapter):
119
119
 
120
120
  return self.connector.execute_query(sql_query, params)
121
121
 
122
- def create_search_indexes(self) -> List[str]:
122
+ def create_search_indexes(self) -> list[str]:
123
123
  """Create FTS5 virtual table and triggers"""
124
124
  if not self.fts5_available:
125
125
  logger.warning("FTS5 not available, skipping FTS index creation")
@@ -4,7 +4,7 @@ Provides cross-database full-text search capabilities
4
4
  """
5
5
 
6
6
  from datetime import datetime
7
- from typing import Any, Dict, List, Optional
7
+ from typing import Any
8
8
 
9
9
  from loguru import logger
10
10
  from sqlalchemy import and_, desc, or_, text
@@ -24,10 +24,10 @@ class SearchService:
24
24
  self,
25
25
  query: str,
26
26
  namespace: str = "default",
27
- category_filter: Optional[List[str]] = None,
27
+ category_filter: list[str] | None = None,
28
28
  limit: int = 10,
29
- memory_types: Optional[List[str]] = None,
30
- ) -> List[Dict[str, Any]]:
29
+ memory_types: list[str] | None = None,
30
+ ) -> list[dict[str, Any]]:
31
31
  """
32
32
  Search memories across different database backends
33
33
 
@@ -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,31 +111,69 @@ 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,
111
152
  query: str,
112
153
  namespace: str,
113
- category_filter: Optional[List[str]],
154
+ category_filter: list[str] | None,
114
155
  limit: int,
115
156
  search_short_term: bool,
116
157
  search_long_term: bool,
117
- ) -> List[Dict[str, Any]]:
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 []
@@ -176,15 +259,23 @@ class SearchService:
176
259
  self,
177
260
  query: str,
178
261
  namespace: str,
179
- category_filter: Optional[List[str]],
262
+ category_filter: list[str] | None,
180
263
  limit: int,
181
264
  search_short_term: bool,
182
265
  search_long_term: bool,
183
- ) -> List[Dict[str, Any]]:
266
+ ) -> list[dict[str, Any]]:
184
267
  """Search using MySQL FULLTEXT"""
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 []
@@ -259,15 +356,23 @@ class SearchService:
259
356
  self,
260
357
  query: str,
261
358
  namespace: str,
262
- category_filter: Optional[List[str]],
359
+ category_filter: list[str] | None,
263
360
  limit: int,
264
361
  search_short_term: bool,
265
362
  search_long_term: bool,
266
- ) -> List[Dict[str, Any]]:
363
+ ) -> list[dict[str, Any]]:
267
364
  """Search using PostgreSQL tsvector"""
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 []
@@ -346,24 +461,47 @@ class SearchService:
346
461
  self,
347
462
  query: str,
348
463
  namespace: str,
349
- category_filter: Optional[List[str]],
464
+ category_filter: list[str] | None,
350
465
  limit: int,
351
466
  search_short_term: bool,
352
467
  search_long_term: bool,
353
- ) -> List[Dict[str, Any]]:
354
- """Fallback LIKE-based search"""
468
+ ) -> list[dict[str, Any]]:
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,15 +584,18 @@ 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(
441
593
  self,
442
594
  namespace: str,
443
- category_filter: Optional[List[str]],
595
+ category_filter: list[str] | None,
444
596
  limit: int,
445
- memory_types: Optional[List[str]],
446
- ) -> List[Dict[str, Any]]:
597
+ memory_types: list[str] | None,
598
+ ) -> list[dict[str, Any]]:
447
599
  """Get recent memories when no search query is provided"""
448
600
  results = []
449
601
 
@@ -515,8 +667,8 @@ class SearchService:
515
667
  return results
516
668
 
517
669
  def _rank_and_limit_results(
518
- self, results: List[Dict[str, Any]], limit: int
519
- ) -> List[Dict[str, Any]]:
670
+ self, results: list[dict[str, Any]], limit: int
671
+ ) -> list[dict[str, Any]]:
520
672
  """Rank and limit search results"""
521
673
  # Calculate composite score
522
674
  for result in results: