memorisdk 1.0.2__py3-none-any.whl → 2.0.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 (46) hide show
  1. memori/__init__.py +24 -8
  2. memori/agents/conscious_agent.py +252 -414
  3. memori/agents/memory_agent.py +487 -224
  4. memori/agents/retrieval_agent.py +416 -60
  5. memori/config/memory_manager.py +323 -0
  6. memori/core/conversation.py +393 -0
  7. memori/core/database.py +386 -371
  8. memori/core/memory.py +1638 -531
  9. memori/core/providers.py +217 -0
  10. memori/database/adapters/__init__.py +10 -0
  11. memori/database/adapters/mysql_adapter.py +331 -0
  12. memori/database/adapters/postgresql_adapter.py +291 -0
  13. memori/database/adapters/sqlite_adapter.py +229 -0
  14. memori/database/auto_creator.py +320 -0
  15. memori/database/connection_utils.py +207 -0
  16. memori/database/connectors/base_connector.py +283 -0
  17. memori/database/connectors/mysql_connector.py +240 -18
  18. memori/database/connectors/postgres_connector.py +277 -4
  19. memori/database/connectors/sqlite_connector.py +178 -3
  20. memori/database/models.py +400 -0
  21. memori/database/queries/base_queries.py +1 -1
  22. memori/database/queries/memory_queries.py +91 -2
  23. memori/database/query_translator.py +222 -0
  24. memori/database/schema_generators/__init__.py +7 -0
  25. memori/database/schema_generators/mysql_schema_generator.py +215 -0
  26. memori/database/search/__init__.py +8 -0
  27. memori/database/search/mysql_search_adapter.py +255 -0
  28. memori/database/search/sqlite_search_adapter.py +180 -0
  29. memori/database/search_service.py +548 -0
  30. memori/database/sqlalchemy_manager.py +839 -0
  31. memori/integrations/__init__.py +36 -11
  32. memori/integrations/litellm_integration.py +340 -6
  33. memori/integrations/openai_integration.py +506 -240
  34. memori/utils/input_validator.py +395 -0
  35. memori/utils/pydantic_models.py +138 -36
  36. memori/utils/query_builder.py +530 -0
  37. memori/utils/security_audit.py +594 -0
  38. memori/utils/security_integration.py +339 -0
  39. memori/utils/transaction_manager.py +547 -0
  40. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/METADATA +44 -17
  41. memorisdk-2.0.0.dist-info/RECORD +67 -0
  42. memorisdk-1.0.2.dist-info/RECORD +0 -44
  43. memorisdk-1.0.2.dist-info/entry_points.txt +0 -2
  44. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/WHEEL +0 -0
  45. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/licenses/LICENSE +0 -0
  46. {memorisdk-1.0.2.dist-info → memorisdk-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,291 @@
1
+ """
2
+ PostgreSQL-specific search adapter with tsvector support and proper security
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from loguru import logger
8
+
9
+ from ...utils.exceptions import ValidationError
10
+ from ...utils.input_validator import DatabaseInputValidator
11
+ from ...utils.query_builder import DatabaseDialect, DatabaseQueryExecutor
12
+ from ..connectors.base_connector import BaseDatabaseConnector, BaseSearchAdapter
13
+
14
+
15
+ class PostgreSQLSearchAdapter(BaseSearchAdapter):
16
+ """PostgreSQL-specific search implementation with tsvector and security measures"""
17
+
18
+ def __init__(self, connector: BaseDatabaseConnector):
19
+ super().__init__(connector)
20
+ self.query_executor = DatabaseQueryExecutor(
21
+ connector, DatabaseDialect.POSTGRESQL
22
+ )
23
+ self._fts_available = None
24
+
25
+ def execute_fulltext_search(
26
+ self,
27
+ query: str,
28
+ namespace: str = "default",
29
+ category_filter: Optional[List[str]] = None,
30
+ limit: int = 10,
31
+ ) -> List[Dict[str, Any]]:
32
+ """Execute PostgreSQL full-text search with tsvector"""
33
+ try:
34
+ # Validate all parameters
35
+ validated = DatabaseInputValidator.validate_search_params(
36
+ query, namespace, category_filter, limit
37
+ )
38
+
39
+ # Check if FTS is available
40
+ if not self._check_fts_available():
41
+ logger.debug(
42
+ "PostgreSQL FTS not available, falling back to LIKE search"
43
+ )
44
+ return self.execute_fallback_search(
45
+ validated["query"],
46
+ validated["namespace"],
47
+ validated["category_filter"],
48
+ validated["limit"],
49
+ )
50
+
51
+ # Execute PostgreSQL tsvector search
52
+ return self.query_executor.execute_search(
53
+ validated["query"],
54
+ validated["namespace"],
55
+ validated["category_filter"],
56
+ validated["limit"],
57
+ use_fts=True,
58
+ )
59
+
60
+ except ValidationError as e:
61
+ logger.error(f"Invalid search parameters: {e}")
62
+ return []
63
+ except Exception as e:
64
+ logger.error(f"PostgreSQL FTS search failed: {e}")
65
+ return self.execute_fallback_search(
66
+ query, namespace, category_filter, limit
67
+ )
68
+
69
+ def create_search_indexes(self) -> List[str]:
70
+ """Create PostgreSQL-specific search indexes"""
71
+ indexes = []
72
+
73
+ try:
74
+ with self.connector.get_connection() as conn:
75
+ cursor = conn.cursor()
76
+
77
+ # Create GIN indexes for full-text search
78
+ fts_indexes = [
79
+ """
80
+ CREATE INDEX IF NOT EXISTS idx_short_term_fts_gin
81
+ ON short_term_memory
82
+ USING gin(to_tsvector('english', searchable_content || ' ' || summary))
83
+ """,
84
+ """
85
+ CREATE INDEX IF NOT EXISTS idx_long_term_fts_gin
86
+ ON long_term_memory
87
+ USING gin(to_tsvector('english', searchable_content || ' ' || summary))
88
+ """,
89
+ ]
90
+
91
+ for index_sql in fts_indexes:
92
+ try:
93
+ cursor.execute(index_sql)
94
+ indexes.append("gin_fts_index")
95
+ except Exception as e:
96
+ logger.warning(f"Failed to create FTS index: {e}")
97
+
98
+ # Create standard B-tree indexes for fallback search
99
+ standard_indexes = [
100
+ "CREATE INDEX IF NOT EXISTS idx_st_search_pg ON short_term_memory(namespace, category_primary, importance_score)",
101
+ "CREATE INDEX IF NOT EXISTS idx_lt_search_pg ON long_term_memory(namespace, category_primary, importance_score)",
102
+ "CREATE INDEX IF NOT EXISTS idx_st_content_pg ON short_term_memory USING gin(searchable_content gin_trgm_ops)",
103
+ "CREATE INDEX IF NOT EXISTS idx_lt_content_pg ON long_term_memory USING gin(searchable_content gin_trgm_ops)",
104
+ ]
105
+
106
+ # Enable trigram extension for better LIKE performance
107
+ try:
108
+ cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
109
+ logger.info("Enabled PostgreSQL pg_trgm extension")
110
+ except Exception as e:
111
+ logger.warning(f"Could not enable pg_trgm extension: {e}")
112
+
113
+ for index_sql in standard_indexes:
114
+ try:
115
+ cursor.execute(index_sql)
116
+ indexes.append(index_sql.split()[-1])
117
+ except Exception as e:
118
+ logger.warning(f"Failed to create index: {e}")
119
+
120
+ conn.commit()
121
+ logger.info(f"Created {len(indexes)} PostgreSQL search indexes")
122
+
123
+ except Exception as e:
124
+ logger.error(f"Failed to create PostgreSQL search indexes: {e}")
125
+
126
+ return indexes
127
+
128
+ def translate_search_query(self, query: str) -> str:
129
+ """Translate search query to PostgreSQL tsquery syntax"""
130
+ if not query or not query.strip():
131
+ return ""
132
+
133
+ # Sanitize input for tsquery
134
+ sanitized = query.strip()
135
+
136
+ # Remove potentially dangerous operators
137
+ dangerous_chars = ["!", "&", "|", "(", ")", "<", ">"]
138
+ for char in dangerous_chars:
139
+ sanitized = sanitized.replace(char, " ")
140
+
141
+ # Split into words and clean
142
+ words = [word.strip() for word in sanitized.split() if word.strip()]
143
+
144
+ if not words:
145
+ return ""
146
+
147
+ # Join words with AND operator (safer than allowing user operators)
148
+ return " & ".join(words)
149
+
150
+ def _check_fts_available(self) -> bool:
151
+ """Check if PostgreSQL full-text search is available"""
152
+ if self._fts_available is not None:
153
+ return self._fts_available
154
+
155
+ try:
156
+ with self.connector.get_connection() as conn:
157
+ cursor = conn.cursor()
158
+ # Test tsvector functionality
159
+ cursor.execute(
160
+ "SELECT to_tsvector('english', 'test') @@ plainto_tsquery('english', 'test')"
161
+ )
162
+ result = cursor.fetchone()
163
+ self._fts_available = result[0] if result else False
164
+ except Exception:
165
+ self._fts_available = False
166
+
167
+ return self._fts_available
168
+
169
+ def execute_fallback_search(
170
+ self,
171
+ query: str,
172
+ namespace: str = "default",
173
+ category_filter: Optional[List[str]] = None,
174
+ limit: int = 10,
175
+ ) -> List[Dict[str, Any]]:
176
+ """Execute LIKE-based fallback search for PostgreSQL"""
177
+ try:
178
+ return self.query_executor.execute_search(
179
+ query, namespace, category_filter, limit, use_fts=False
180
+ )
181
+ except Exception as e:
182
+ logger.error(f"PostgreSQL fallback search failed: {e}")
183
+ return []
184
+
185
+ def optimize_database(self):
186
+ """Perform PostgreSQL-specific optimizations"""
187
+ try:
188
+ with self.connector.get_connection() as conn:
189
+ cursor = conn.cursor()
190
+
191
+ # Update table statistics
192
+ cursor.execute("ANALYZE short_term_memory")
193
+ cursor.execute("ANALYZE long_term_memory")
194
+
195
+ # Reindex GIN indexes for FTS performance
196
+ try:
197
+ cursor.execute("REINDEX INDEX CONCURRENTLY idx_short_term_fts_gin")
198
+ cursor.execute("REINDEX INDEX CONCURRENTLY idx_long_term_fts_gin")
199
+ except Exception as e:
200
+ logger.debug(
201
+ f"Concurrent reindex failed, trying regular reindex: {e}"
202
+ )
203
+ try:
204
+ cursor.execute("REINDEX INDEX idx_short_term_fts_gin")
205
+ cursor.execute("REINDEX INDEX idx_long_term_fts_gin")
206
+ except Exception as e2:
207
+ logger.warning(f"Reindex failed: {e2}")
208
+
209
+ conn.commit()
210
+ logger.info("PostgreSQL database optimization completed")
211
+
212
+ except Exception as e:
213
+ logger.warning(f"PostgreSQL optimization failed: {e}")
214
+
215
+ def create_materialized_views(self):
216
+ """Create materialized views for better search performance"""
217
+ try:
218
+ with self.connector.get_connection() as conn:
219
+ cursor = conn.cursor()
220
+
221
+ # Create materialized view for search optimization
222
+ mv_sql = """
223
+ CREATE MATERIALIZED VIEW IF NOT EXISTS memory_search_mv AS
224
+ SELECT
225
+ memory_id,
226
+ 'short_term' as memory_type,
227
+ namespace,
228
+ category_primary,
229
+ searchable_content,
230
+ summary,
231
+ importance_score,
232
+ created_at,
233
+ to_tsvector('english', searchable_content || ' ' || summary) as search_vector
234
+ FROM short_term_memory
235
+ WHERE expires_at IS NULL OR expires_at > NOW()
236
+
237
+ UNION ALL
238
+
239
+ SELECT
240
+ memory_id,
241
+ 'long_term' as memory_type,
242
+ namespace,
243
+ category_primary,
244
+ searchable_content,
245
+ summary,
246
+ importance_score,
247
+ created_at,
248
+ to_tsvector('english', searchable_content || ' ' || summary) as search_vector
249
+ FROM long_term_memory
250
+
251
+ ORDER BY importance_score DESC, created_at DESC
252
+ """
253
+
254
+ cursor.execute(mv_sql)
255
+
256
+ # Create index on materialized view
257
+ cursor.execute(
258
+ """
259
+ CREATE INDEX IF NOT EXISTS idx_memory_search_mv_fts
260
+ ON memory_search_mv
261
+ USING gin(search_vector)
262
+ """
263
+ )
264
+
265
+ cursor.execute(
266
+ """
267
+ CREATE INDEX IF NOT EXISTS idx_memory_search_mv_filter
268
+ ON memory_search_mv (namespace, category_primary, memory_type)
269
+ """
270
+ )
271
+
272
+ conn.commit()
273
+ logger.info(
274
+ "Created PostgreSQL materialized view for search optimization"
275
+ )
276
+
277
+ except Exception as e:
278
+ logger.warning(f"Failed to create materialized view: {e}")
279
+
280
+ def refresh_materialized_views(self):
281
+ """Refresh materialized views for up-to-date search results"""
282
+ try:
283
+ with self.connector.get_connection() as conn:
284
+ cursor = conn.cursor()
285
+ cursor.execute(
286
+ "REFRESH MATERIALIZED VIEW CONCURRENTLY memory_search_mv"
287
+ )
288
+ conn.commit()
289
+ logger.debug("Refreshed PostgreSQL materialized view")
290
+ except Exception as e:
291
+ logger.warning(f"Failed to refresh materialized view: {e}")
@@ -0,0 +1,229 @@
1
+ """
2
+ SQLite-specific search adapter with FTS5 support and proper security
3
+ """
4
+
5
+ import sqlite3
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from loguru import logger
9
+
10
+ from ...utils.exceptions import ValidationError
11
+ from ...utils.input_validator import DatabaseInputValidator
12
+ from ...utils.query_builder import DatabaseDialect, DatabaseQueryExecutor
13
+ from ..connectors.base_connector import BaseDatabaseConnector, BaseSearchAdapter
14
+
15
+
16
+ class SQLiteSearchAdapter(BaseSearchAdapter):
17
+ """SQLite-specific search implementation with FTS5 and security measures"""
18
+
19
+ def __init__(self, connector: BaseDatabaseConnector):
20
+ super().__init__(connector)
21
+ self.query_executor = DatabaseQueryExecutor(connector, DatabaseDialect.SQLITE)
22
+ self._fts_available = None
23
+
24
+ def execute_fulltext_search(
25
+ self,
26
+ query: str,
27
+ namespace: str = "default",
28
+ category_filter: Optional[List[str]] = None,
29
+ limit: int = 10,
30
+ ) -> List[Dict[str, Any]]:
31
+ """Execute SQLite FTS5 search with proper validation"""
32
+ try:
33
+ # Validate all parameters
34
+ validated = DatabaseInputValidator.validate_search_params(
35
+ query, namespace, category_filter, limit
36
+ )
37
+
38
+ # Check if FTS is available
39
+ if not self._check_fts_available():
40
+ logger.debug("FTS not available, falling back to LIKE search")
41
+ return self.execute_fallback_search(
42
+ validated["query"],
43
+ validated["namespace"],
44
+ validated["category_filter"],
45
+ validated["limit"],
46
+ )
47
+
48
+ # Execute FTS search
49
+ return self.query_executor.execute_search(
50
+ validated["query"],
51
+ validated["namespace"],
52
+ validated["category_filter"],
53
+ validated["limit"],
54
+ use_fts=True,
55
+ )
56
+
57
+ except ValidationError as e:
58
+ logger.error(f"Invalid search parameters: {e}")
59
+ return []
60
+ except Exception as e:
61
+ logger.error(f"SQLite FTS search failed: {e}")
62
+ # Fallback to LIKE search on error
63
+ return self.execute_fallback_search(
64
+ query, namespace, category_filter, limit
65
+ )
66
+
67
+ def create_search_indexes(self) -> List[str]:
68
+ """Create SQLite-specific search indexes"""
69
+ indexes = []
70
+
71
+ try:
72
+ with self.connector.get_connection() as conn:
73
+ cursor = conn.cursor()
74
+
75
+ # Create FTS5 virtual table if not exists
76
+ fts_sql = """
77
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_search_fts USING fts5(
78
+ memory_id,
79
+ memory_type,
80
+ namespace,
81
+ searchable_content,
82
+ summary,
83
+ category_primary,
84
+ content='',
85
+ contentless_delete=1
86
+ )
87
+ """
88
+ cursor.execute(fts_sql)
89
+ indexes.append("memory_search_fts")
90
+
91
+ # Create standard indexes for fallback search
92
+ standard_indexes = [
93
+ "CREATE INDEX IF NOT EXISTS idx_st_search ON short_term_memory(namespace, category_primary, importance_score)",
94
+ "CREATE INDEX IF NOT EXISTS idx_lt_search ON long_term_memory(namespace, category_primary, importance_score)",
95
+ "CREATE INDEX IF NOT EXISTS idx_st_content ON short_term_memory(searchable_content)",
96
+ "CREATE INDEX IF NOT EXISTS idx_lt_content ON long_term_memory(searchable_content)",
97
+ ]
98
+
99
+ for index_sql in standard_indexes:
100
+ try:
101
+ cursor.execute(index_sql)
102
+ indexes.append(index_sql.split()[-1]) # Extract index name
103
+ except sqlite3.Error as e:
104
+ logger.warning(f"Failed to create index: {e}")
105
+
106
+ # Create triggers to maintain FTS index
107
+ triggers = [
108
+ """
109
+ CREATE TRIGGER IF NOT EXISTS short_term_memory_fts_insert
110
+ AFTER INSERT ON short_term_memory
111
+ BEGIN
112
+ INSERT INTO memory_search_fts(memory_id, memory_type, namespace, searchable_content, summary, category_primary)
113
+ VALUES (NEW.memory_id, 'short_term', NEW.namespace, NEW.searchable_content, NEW.summary, NEW.category_primary);
114
+ END
115
+ """,
116
+ """
117
+ CREATE TRIGGER IF NOT EXISTS long_term_memory_fts_insert
118
+ AFTER INSERT ON long_term_memory
119
+ BEGIN
120
+ INSERT INTO memory_search_fts(memory_id, memory_type, namespace, searchable_content, summary, category_primary)
121
+ VALUES (NEW.memory_id, 'long_term', NEW.namespace, NEW.searchable_content, NEW.summary, NEW.category_primary);
122
+ END
123
+ """,
124
+ """
125
+ CREATE TRIGGER IF NOT EXISTS short_term_memory_fts_delete
126
+ AFTER DELETE ON short_term_memory
127
+ BEGIN
128
+ DELETE FROM memory_search_fts WHERE memory_id = OLD.memory_id AND memory_type = 'short_term';
129
+ END
130
+ """,
131
+ """
132
+ CREATE TRIGGER IF NOT EXISTS long_term_memory_fts_delete
133
+ AFTER DELETE ON long_term_memory
134
+ BEGIN
135
+ DELETE FROM memory_search_fts WHERE memory_id = OLD.memory_id AND memory_type = 'long_term';
136
+ END
137
+ """,
138
+ ]
139
+
140
+ for trigger_sql in triggers:
141
+ try:
142
+ cursor.execute(trigger_sql)
143
+ except sqlite3.Error as e:
144
+ logger.warning(f"Failed to create trigger: {e}")
145
+
146
+ conn.commit()
147
+ logger.info(f"Created {len(indexes)} SQLite search indexes")
148
+
149
+ except Exception as e:
150
+ logger.error(f"Failed to create SQLite search indexes: {e}")
151
+
152
+ return indexes
153
+
154
+ def translate_search_query(self, query: str) -> str:
155
+ """Translate search query to SQLite FTS5 syntax"""
156
+ if not query or not query.strip():
157
+ return "*"
158
+
159
+ # Escape FTS5 special characters
160
+ sanitized = query.replace('"', '""') # Escape quotes
161
+
162
+ # Remove potentially dangerous FTS operators
163
+ dangerous_operators = ["AND", "OR", "NOT", "NEAR", "^"]
164
+ for op in dangerous_operators:
165
+ sanitized = sanitized.replace(op, "")
166
+
167
+ # Wrap in quotes for phrase search (safer than operators)
168
+ return f'"{sanitized.strip()}"'
169
+
170
+ def _check_fts_available(self) -> bool:
171
+ """Check if FTS5 is available in SQLite"""
172
+ if self._fts_available is not None:
173
+ return self._fts_available
174
+
175
+ try:
176
+ with self.connector.get_connection() as conn:
177
+ cursor = conn.cursor()
178
+ cursor.execute("CREATE VIRTUAL TABLE fts_test USING fts5(content)")
179
+ cursor.execute("DROP TABLE fts_test")
180
+ self._fts_available = True
181
+ except sqlite3.OperationalError:
182
+ self._fts_available = False
183
+ except Exception:
184
+ self._fts_available = False
185
+
186
+ return self._fts_available
187
+
188
+ def execute_fallback_search(
189
+ self,
190
+ query: str,
191
+ namespace: str = "default",
192
+ category_filter: Optional[List[str]] = None,
193
+ limit: int = 10,
194
+ ) -> List[Dict[str, Any]]:
195
+ """Execute LIKE-based fallback search for SQLite"""
196
+ try:
197
+ return self.query_executor.execute_search(
198
+ query, namespace, category_filter, limit, use_fts=False
199
+ )
200
+ except Exception as e:
201
+ logger.error(f"SQLite fallback search failed: {e}")
202
+ return []
203
+
204
+ def optimize_database(self):
205
+ """Perform SQLite-specific optimizations"""
206
+ try:
207
+ with self.connector.get_connection() as conn:
208
+ cursor = conn.cursor()
209
+
210
+ # Analyze tables for better query planning
211
+ cursor.execute("ANALYZE")
212
+
213
+ # Optimize FTS index if available
214
+ if self._check_fts_available():
215
+ try:
216
+ cursor.execute(
217
+ "INSERT INTO memory_search_fts(memory_search_fts) VALUES('optimize')"
218
+ )
219
+ except sqlite3.Error as e:
220
+ logger.debug(f"FTS optimization skipped: {e}")
221
+
222
+ # Update table statistics
223
+ cursor.execute("PRAGMA optimize")
224
+
225
+ conn.commit()
226
+ logger.info("SQLite database optimization completed")
227
+
228
+ except Exception as e:
229
+ logger.warning(f"SQLite optimization failed: {e}")