powermem 0.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.
- powermem/__init__.py +103 -0
- powermem/agent/__init__.py +35 -0
- powermem/agent/abstract/__init__.py +22 -0
- powermem/agent/abstract/collaboration.py +259 -0
- powermem/agent/abstract/context.py +187 -0
- powermem/agent/abstract/manager.py +232 -0
- powermem/agent/abstract/permission.py +217 -0
- powermem/agent/abstract/privacy.py +267 -0
- powermem/agent/abstract/scope.py +199 -0
- powermem/agent/agent.py +791 -0
- powermem/agent/components/__init__.py +18 -0
- powermem/agent/components/collaboration_coordinator.py +645 -0
- powermem/agent/components/permission_controller.py +586 -0
- powermem/agent/components/privacy_protector.py +767 -0
- powermem/agent/components/scope_controller.py +685 -0
- powermem/agent/factories/__init__.py +16 -0
- powermem/agent/factories/agent_factory.py +266 -0
- powermem/agent/factories/config_factory.py +308 -0
- powermem/agent/factories/memory_factory.py +229 -0
- powermem/agent/implementations/__init__.py +16 -0
- powermem/agent/implementations/hybrid.py +728 -0
- powermem/agent/implementations/multi_agent.py +1040 -0
- powermem/agent/implementations/multi_user.py +1020 -0
- powermem/agent/types.py +53 -0
- powermem/agent/wrappers/__init__.py +14 -0
- powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
- powermem/agent/wrappers/compatibility_wrapper.py +520 -0
- powermem/config_loader.py +318 -0
- powermem/configs.py +249 -0
- powermem/core/__init__.py +19 -0
- powermem/core/async_memory.py +1493 -0
- powermem/core/audit.py +258 -0
- powermem/core/base.py +165 -0
- powermem/core/memory.py +1567 -0
- powermem/core/setup.py +162 -0
- powermem/core/telemetry.py +215 -0
- powermem/integrations/__init__.py +17 -0
- powermem/integrations/embeddings/__init__.py +13 -0
- powermem/integrations/embeddings/aws_bedrock.py +100 -0
- powermem/integrations/embeddings/azure_openai.py +55 -0
- powermem/integrations/embeddings/base.py +31 -0
- powermem/integrations/embeddings/config/base.py +132 -0
- powermem/integrations/embeddings/configs.py +31 -0
- powermem/integrations/embeddings/factory.py +48 -0
- powermem/integrations/embeddings/gemini.py +39 -0
- powermem/integrations/embeddings/huggingface.py +41 -0
- powermem/integrations/embeddings/langchain.py +35 -0
- powermem/integrations/embeddings/lmstudio.py +29 -0
- powermem/integrations/embeddings/mock.py +11 -0
- powermem/integrations/embeddings/ollama.py +53 -0
- powermem/integrations/embeddings/openai.py +49 -0
- powermem/integrations/embeddings/qwen.py +102 -0
- powermem/integrations/embeddings/together.py +31 -0
- powermem/integrations/embeddings/vertexai.py +54 -0
- powermem/integrations/llm/__init__.py +18 -0
- powermem/integrations/llm/anthropic.py +87 -0
- powermem/integrations/llm/base.py +132 -0
- powermem/integrations/llm/config/anthropic.py +56 -0
- powermem/integrations/llm/config/azure.py +56 -0
- powermem/integrations/llm/config/base.py +62 -0
- powermem/integrations/llm/config/deepseek.py +56 -0
- powermem/integrations/llm/config/ollama.py +56 -0
- powermem/integrations/llm/config/openai.py +79 -0
- powermem/integrations/llm/config/qwen.py +68 -0
- powermem/integrations/llm/config/qwen_asr.py +46 -0
- powermem/integrations/llm/config/vllm.py +56 -0
- powermem/integrations/llm/configs.py +26 -0
- powermem/integrations/llm/deepseek.py +106 -0
- powermem/integrations/llm/factory.py +118 -0
- powermem/integrations/llm/gemini.py +201 -0
- powermem/integrations/llm/langchain.py +65 -0
- powermem/integrations/llm/ollama.py +106 -0
- powermem/integrations/llm/openai.py +166 -0
- powermem/integrations/llm/openai_structured.py +80 -0
- powermem/integrations/llm/qwen.py +207 -0
- powermem/integrations/llm/qwen_asr.py +171 -0
- powermem/integrations/llm/vllm.py +106 -0
- powermem/integrations/rerank/__init__.py +20 -0
- powermem/integrations/rerank/base.py +43 -0
- powermem/integrations/rerank/config/__init__.py +7 -0
- powermem/integrations/rerank/config/base.py +27 -0
- powermem/integrations/rerank/configs.py +23 -0
- powermem/integrations/rerank/factory.py +68 -0
- powermem/integrations/rerank/qwen.py +159 -0
- powermem/intelligence/__init__.py +17 -0
- powermem/intelligence/ebbinghaus_algorithm.py +354 -0
- powermem/intelligence/importance_evaluator.py +361 -0
- powermem/intelligence/intelligent_memory_manager.py +284 -0
- powermem/intelligence/manager.py +148 -0
- powermem/intelligence/plugin.py +229 -0
- powermem/prompts/__init__.py +29 -0
- powermem/prompts/graph/graph_prompts.py +217 -0
- powermem/prompts/graph/graph_tools_prompts.py +469 -0
- powermem/prompts/importance_evaluation.py +246 -0
- powermem/prompts/intelligent_memory_prompts.py +163 -0
- powermem/prompts/templates.py +193 -0
- powermem/storage/__init__.py +14 -0
- powermem/storage/adapter.py +896 -0
- powermem/storage/base.py +109 -0
- powermem/storage/config/base.py +13 -0
- powermem/storage/config/oceanbase.py +58 -0
- powermem/storage/config/pgvector.py +52 -0
- powermem/storage/config/sqlite.py +27 -0
- powermem/storage/configs.py +159 -0
- powermem/storage/factory.py +59 -0
- powermem/storage/migration_manager.py +438 -0
- powermem/storage/oceanbase/__init__.py +8 -0
- powermem/storage/oceanbase/constants.py +162 -0
- powermem/storage/oceanbase/oceanbase.py +1384 -0
- powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
- powermem/storage/pgvector/__init__.py +7 -0
- powermem/storage/pgvector/pgvector.py +420 -0
- powermem/storage/sqlite/__init__.py +0 -0
- powermem/storage/sqlite/sqlite.py +218 -0
- powermem/storage/sqlite/sqlite_vector_store.py +311 -0
- powermem/utils/__init__.py +35 -0
- powermem/utils/utils.py +605 -0
- powermem/version.py +23 -0
- powermem-0.1.0.dist-info/METADATA +187 -0
- powermem-0.1.0.dist-info/RECORD +123 -0
- powermem-0.1.0.dist-info/WHEEL +5 -0
- powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
- powermem-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sub-store migration status management module.
|
|
3
|
+
|
|
4
|
+
This module provides a database-backed migration status manager for sub-stores.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import uuid
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Dict, List, Optional, Any
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MigrationStatus:
|
|
17
|
+
"""Migration status enum"""
|
|
18
|
+
PENDING = "pending" # Registered, not started
|
|
19
|
+
MIGRATING = "migrating" # Migration in progress
|
|
20
|
+
COMPLETED = "completed" # Completed, ready for routing
|
|
21
|
+
FAILED = "failed" # Migration failed
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SubStoreMigrationManager:
|
|
25
|
+
"""
|
|
26
|
+
Sub-store migration status manager (database-backed).
|
|
27
|
+
|
|
28
|
+
This class manages migration status for sub-stores using a database table.
|
|
29
|
+
All status information is persisted in the database, supporting multi-process
|
|
30
|
+
sharing and application restarts.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, vector_store, main_collection_name: str):
|
|
34
|
+
"""
|
|
35
|
+
Initialize migration manager.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
vector_store: Vector store instance (must support SQL operations)
|
|
39
|
+
main_collection_name: Main collection name
|
|
40
|
+
"""
|
|
41
|
+
self.vector_store = vector_store
|
|
42
|
+
self.main_collection_name = main_collection_name
|
|
43
|
+
|
|
44
|
+
# Table name for migration status
|
|
45
|
+
self.status_table = "sub_store_migration_status"
|
|
46
|
+
|
|
47
|
+
# Ensure table exists
|
|
48
|
+
self._init_status_table()
|
|
49
|
+
|
|
50
|
+
def _init_status_table(self):
|
|
51
|
+
"""Initialize migration status table"""
|
|
52
|
+
try:
|
|
53
|
+
# Check if vector store supports SQL operations
|
|
54
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
55
|
+
logger.warning("Vector store does not support SQL operations, migration status will not be persisted")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Create table if not exists
|
|
59
|
+
create_table_sql = f"""
|
|
60
|
+
CREATE TABLE IF NOT EXISTS {self.status_table} (
|
|
61
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
62
|
+
main_collection_name VARCHAR(128) NOT NULL,
|
|
63
|
+
sub_store_name VARCHAR(128) NOT NULL,
|
|
64
|
+
routing_filter TEXT,
|
|
65
|
+
status VARCHAR(32) DEFAULT 'pending',
|
|
66
|
+
migrated_count INT DEFAULT 0,
|
|
67
|
+
total_count INT DEFAULT 0,
|
|
68
|
+
error_message TEXT,
|
|
69
|
+
created_at VARCHAR(128),
|
|
70
|
+
updated_at VARCHAR(128),
|
|
71
|
+
started_at VARCHAR(128),
|
|
72
|
+
completed_at VARCHAR(128),
|
|
73
|
+
UNIQUE KEY unique_sub_store (main_collection_name, sub_store_name),
|
|
74
|
+
KEY idx_main_collection (main_collection_name),
|
|
75
|
+
KEY idx_status (status)
|
|
76
|
+
)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
self.vector_store.execute_sql(create_table_sql)
|
|
80
|
+
logger.info(f"Migration status table '{self.status_table}' initialized")
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.warning(f"Failed to initialize migration status table: {e}")
|
|
84
|
+
logger.warning("Migration status will not be persisted")
|
|
85
|
+
|
|
86
|
+
def register_sub_store(
|
|
87
|
+
self,
|
|
88
|
+
sub_store_name: str,
|
|
89
|
+
routing_filter: Dict
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Register a sub store with pending status.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
sub_store_name: Sub store name
|
|
96
|
+
routing_filter: Routing filter dict
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
100
|
+
logger.debug("Skipping registration: vector store does not support SQL")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
status_id = str(uuid.uuid4())
|
|
104
|
+
now = datetime.utcnow().isoformat()
|
|
105
|
+
|
|
106
|
+
# Convert routing filter to JSON string
|
|
107
|
+
routing_filter_json = json.dumps(routing_filter)
|
|
108
|
+
|
|
109
|
+
# Insert with ON DUPLICATE KEY UPDATE (for MySQL/OceanBase)
|
|
110
|
+
insert_sql = f"""
|
|
111
|
+
INSERT INTO {self.status_table}
|
|
112
|
+
(id, main_collection_name, sub_store_name, routing_filter, status, created_at, updated_at)
|
|
113
|
+
VALUES
|
|
114
|
+
('{status_id}', '{self.main_collection_name}', '{sub_store_name}',
|
|
115
|
+
'{routing_filter_json}', '{MigrationStatus.PENDING}', '{now}', '{now}')
|
|
116
|
+
ON DUPLICATE KEY UPDATE
|
|
117
|
+
routing_filter = '{routing_filter_json}',
|
|
118
|
+
updated_at = '{now}'
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
self.vector_store.execute_sql(insert_sql)
|
|
122
|
+
logger.info(f"Registered sub store '{sub_store_name}' with status: pending")
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Failed to register sub store: {e}")
|
|
126
|
+
|
|
127
|
+
def is_ready(self, sub_store_name: str) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Check if sub store is ready (migration completed).
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
sub_store_name: Sub store name
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if migration completed, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
status = self.get_status(sub_store_name)
|
|
139
|
+
if status:
|
|
140
|
+
return status.get("status") == MigrationStatus.COMPLETED
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Failed to check sub store readiness: {e}")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def mark_migrating(self, sub_store_name: str, total_count: int):
|
|
148
|
+
"""
|
|
149
|
+
Mark sub store as migrating.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
sub_store_name: Sub store name
|
|
153
|
+
total_count: Total number of records to migrate
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
now = datetime.utcnow().isoformat()
|
|
160
|
+
|
|
161
|
+
update_sql = f"""
|
|
162
|
+
UPDATE {self.status_table}
|
|
163
|
+
SET status = '{MigrationStatus.MIGRATING}',
|
|
164
|
+
total_count = {total_count},
|
|
165
|
+
started_at = '{now}',
|
|
166
|
+
updated_at = '{now}'
|
|
167
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
168
|
+
AND sub_store_name = '{sub_store_name}'
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
self.vector_store.execute_sql(update_sql)
|
|
172
|
+
logger.info(f"Marked sub store '{sub_store_name}' as migrating (total: {total_count})")
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Failed to mark sub store as migrating: {e}")
|
|
176
|
+
|
|
177
|
+
def update_progress(self, sub_store_name: str, migrated_count: int):
|
|
178
|
+
"""
|
|
179
|
+
Update migration progress.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
sub_store_name: Sub store name
|
|
183
|
+
migrated_count: Number of migrated records
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
now = datetime.utcnow().isoformat()
|
|
190
|
+
|
|
191
|
+
update_sql = f"""
|
|
192
|
+
UPDATE {self.status_table}
|
|
193
|
+
SET migrated_count = {migrated_count},
|
|
194
|
+
updated_at = '{now}'
|
|
195
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
196
|
+
AND sub_store_name = '{sub_store_name}'
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
self.vector_store.execute_sql(update_sql)
|
|
200
|
+
logger.debug(f"Updated progress for '{sub_store_name}': {migrated_count}")
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Failed to update progress: {e}")
|
|
204
|
+
|
|
205
|
+
def mark_completed(self, sub_store_name: str, migrated_count: int):
|
|
206
|
+
"""
|
|
207
|
+
Mark sub store migration as completed.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
sub_store_name: Sub store name
|
|
211
|
+
migrated_count: Final migrated count
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
now = datetime.utcnow().isoformat()
|
|
218
|
+
|
|
219
|
+
update_sql = f"""
|
|
220
|
+
UPDATE {self.status_table}
|
|
221
|
+
SET status = '{MigrationStatus.COMPLETED}',
|
|
222
|
+
migrated_count = {migrated_count},
|
|
223
|
+
completed_at = '{now}',
|
|
224
|
+
updated_at = '{now}',
|
|
225
|
+
error_message = NULL
|
|
226
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
227
|
+
AND sub_store_name = '{sub_store_name}'
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
self.vector_store.execute_sql(update_sql)
|
|
231
|
+
logger.info(f"Marked sub store '{sub_store_name}' as completed ({migrated_count} records)")
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f"Failed to mark sub store as completed: {e}")
|
|
235
|
+
|
|
236
|
+
def mark_failed(self, sub_store_name: str, error_message: str):
|
|
237
|
+
"""
|
|
238
|
+
Mark sub store migration as failed.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
sub_store_name: Sub store name
|
|
242
|
+
error_message: Error message
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
now = datetime.utcnow().isoformat()
|
|
249
|
+
|
|
250
|
+
# Escape single quotes in error message
|
|
251
|
+
error_message_escaped = error_message.replace("'", "''")
|
|
252
|
+
|
|
253
|
+
update_sql = f"""
|
|
254
|
+
UPDATE {self.status_table}
|
|
255
|
+
SET status = '{MigrationStatus.FAILED}',
|
|
256
|
+
error_message = '{error_message_escaped}',
|
|
257
|
+
updated_at = '{now}'
|
|
258
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
259
|
+
AND sub_store_name = '{sub_store_name}'
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
self.vector_store.execute_sql(update_sql)
|
|
263
|
+
logger.error(f"Marked sub store '{sub_store_name}' as failed: {error_message}")
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Failed to mark sub store as failed: {e}")
|
|
267
|
+
|
|
268
|
+
def get_status(self, sub_store_name: str) -> Optional[Dict[str, Any]]:
|
|
269
|
+
"""
|
|
270
|
+
Get detailed status for a sub store.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
sub_store_name: Sub store name
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Status dict or None if not found
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
query_sql = f"""
|
|
283
|
+
SELECT * FROM {self.status_table}
|
|
284
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
285
|
+
AND sub_store_name = '{sub_store_name}'
|
|
286
|
+
LIMIT 1
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
result = self.vector_store.execute_sql(query_sql)
|
|
290
|
+
|
|
291
|
+
if result and len(result) > 0:
|
|
292
|
+
row = result[0]
|
|
293
|
+
return {
|
|
294
|
+
"id": row.get("id"),
|
|
295
|
+
"main_collection_name": row.get("main_collection_name"),
|
|
296
|
+
"sub_store_name": row.get("sub_store_name"),
|
|
297
|
+
"routing_filter": json.loads(row.get("routing_filter", "{}")),
|
|
298
|
+
"status": row.get("status"),
|
|
299
|
+
"migrated_count": row.get("migrated_count", 0),
|
|
300
|
+
"total_count": row.get("total_count", 0),
|
|
301
|
+
"error_message": row.get("error_message"),
|
|
302
|
+
"created_at": row.get("created_at"),
|
|
303
|
+
"updated_at": row.get("updated_at"),
|
|
304
|
+
"started_at": row.get("started_at"),
|
|
305
|
+
"completed_at": row.get("completed_at"),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.error(f"Failed to get status: {e}")
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
def get_migration_progress(self, sub_store_name: str) -> Optional[Dict[str, Any]]:
|
|
315
|
+
"""
|
|
316
|
+
Get migration progress including percentage.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
sub_store_name: Sub store name
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Progress dict or None if not found
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
status = self.get_status(sub_store_name)
|
|
326
|
+
if not status:
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
total_count = status.get("total_count", 0)
|
|
330
|
+
migrated_count = status.get("migrated_count", 0)
|
|
331
|
+
|
|
332
|
+
# Calculate percentage
|
|
333
|
+
if total_count > 0:
|
|
334
|
+
progress_percentage = (migrated_count / total_count) * 100
|
|
335
|
+
else:
|
|
336
|
+
progress_percentage = 0.0
|
|
337
|
+
|
|
338
|
+
# Calculate elapsed time
|
|
339
|
+
started_at = status.get("started_at")
|
|
340
|
+
updated_at = status.get("updated_at")
|
|
341
|
+
elapsed_seconds = None
|
|
342
|
+
|
|
343
|
+
if started_at and updated_at:
|
|
344
|
+
try:
|
|
345
|
+
start_time = datetime.fromisoformat(started_at)
|
|
346
|
+
current_time = datetime.fromisoformat(updated_at)
|
|
347
|
+
elapsed_seconds = (current_time - start_time).total_seconds()
|
|
348
|
+
except Exception:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
"sub_store_name": sub_store_name,
|
|
353
|
+
"status": status.get("status"),
|
|
354
|
+
"progress_percentage": progress_percentage,
|
|
355
|
+
"migrated_count": migrated_count,
|
|
356
|
+
"total_count": total_count,
|
|
357
|
+
"elapsed_seconds": elapsed_seconds,
|
|
358
|
+
"error_message": status.get("error_message"),
|
|
359
|
+
"is_ready": status.get("status") == MigrationStatus.COMPLETED
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.error(f"Failed to get migration progress: {e}")
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
def list_all_status(self) -> List[Dict[str, Any]]:
|
|
367
|
+
"""
|
|
368
|
+
List status for all sub stores under this main collection.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
List of status dicts
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
375
|
+
return []
|
|
376
|
+
|
|
377
|
+
query_sql = f"""
|
|
378
|
+
SELECT * FROM {self.status_table}
|
|
379
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
380
|
+
ORDER BY created_at
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
results = self.vector_store.execute_sql(query_sql)
|
|
384
|
+
|
|
385
|
+
status_list = []
|
|
386
|
+
for row in results:
|
|
387
|
+
status_list.append({
|
|
388
|
+
"id": row.get("id"),
|
|
389
|
+
"sub_store_name": row.get("sub_store_name"),
|
|
390
|
+
"routing_filter": json.loads(row.get("routing_filter", "{}")),
|
|
391
|
+
"status": row.get("status"),
|
|
392
|
+
"migrated_count": row.get("migrated_count", 0),
|
|
393
|
+
"total_count": row.get("total_count", 0),
|
|
394
|
+
"error_message": row.get("error_message"),
|
|
395
|
+
"created_at": row.get("created_at"),
|
|
396
|
+
"updated_at": row.get("updated_at"),
|
|
397
|
+
"started_at": row.get("started_at"),
|
|
398
|
+
"completed_at": row.get("completed_at"),
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
return status_list
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
logger.error(f"Failed to list all status: {e}")
|
|
405
|
+
return []
|
|
406
|
+
|
|
407
|
+
def reset_status(self, sub_store_name: str):
|
|
408
|
+
"""
|
|
409
|
+
Reset sub store migration status to pending (for retry).
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
sub_store_name: Sub store name
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
if not hasattr(self.vector_store, 'execute_sql'):
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
now = datetime.utcnow().isoformat()
|
|
419
|
+
|
|
420
|
+
update_sql = f"""
|
|
421
|
+
UPDATE {self.status_table}
|
|
422
|
+
SET status = '{MigrationStatus.PENDING}',
|
|
423
|
+
migrated_count = 0,
|
|
424
|
+
total_count = 0,
|
|
425
|
+
error_message = NULL,
|
|
426
|
+
started_at = NULL,
|
|
427
|
+
completed_at = NULL,
|
|
428
|
+
updated_at = '{now}'
|
|
429
|
+
WHERE main_collection_name = '{self.main_collection_name}'
|
|
430
|
+
AND sub_store_name = '{sub_store_name}'
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
self.vector_store.execute_sql(update_sql)
|
|
434
|
+
logger.info(f"Reset migration status for sub store '{sub_store_name}'")
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
logger.error(f"Failed to reset status: {e}")
|
|
438
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OceanBase storage constants
|
|
3
|
+
|
|
4
|
+
This module contains all constants used across OceanBase storage implementations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from pyobvector import VecIndexType
|
|
9
|
+
except ImportError:
|
|
10
|
+
# Fallback for when pyobvector is not available
|
|
11
|
+
class VecIndexType:
|
|
12
|
+
HNSW = "HNSW"
|
|
13
|
+
HNSW_SQ = "HNSW_SQ"
|
|
14
|
+
IVFFLAT = "IVFFLAT"
|
|
15
|
+
IVFSQ = "IVFSQ"
|
|
16
|
+
IVFPQ = "IVFPQ"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Connection Defaults
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
DEFAULT_OCEANBASE_CONNECTION = {
|
|
24
|
+
"host": "localhost",
|
|
25
|
+
"port": "2881",
|
|
26
|
+
"user": "root@test",
|
|
27
|
+
"password": "",
|
|
28
|
+
"db_name": "test",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# Vector Index Configuration
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
# Index type groupings for easier management
|
|
37
|
+
INDEX_TYPE_HNSW = ("HNSW", "HNSW_SQ")
|
|
38
|
+
INDEX_TYPE_IVF = ("IVF", "IVF_FLAT", "IVF_SQ")
|
|
39
|
+
INDEX_TYPE_IVF_PQ = "IVF_PQ"
|
|
40
|
+
|
|
41
|
+
# Default vector metric type
|
|
42
|
+
DEFAULT_OCEANBASE_VECTOR_METRIC_TYPE = "l2"
|
|
43
|
+
DEFAULT_VIDX_NAME = "vidx"
|
|
44
|
+
DEFAULT_INDEX_TYPE = "HNSW"
|
|
45
|
+
|
|
46
|
+
# Default parameters for different index types
|
|
47
|
+
DEFAULT_OCEANBASE_HNSW_BUILD_PARAM = {"M": 16, "efConstruction": 200}
|
|
48
|
+
DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM = {"efSearch": 64}
|
|
49
|
+
DEFAULT_OCEANBASE_IVF_BUILD_PARAM = {"nlist": 128}
|
|
50
|
+
DEFAULT_OCEANBASE_IVF_SEARCH_PARAM = {}
|
|
51
|
+
DEFAULT_OCEANBASE_IVF_PQ_BUILD_PARAM = {"nlist": 128, "m": 3}
|
|
52
|
+
DEFAULT_OCEANBASE_FLAT_BUILD_PARAM = {}
|
|
53
|
+
DEFAULT_OCEANBASE_FLAT_SEARCH_PARAM = {}
|
|
54
|
+
|
|
55
|
+
# Supported vector index types mapping
|
|
56
|
+
OCEANBASE_SUPPORTED_VECTOR_INDEX_TYPES = {
|
|
57
|
+
"HNSW": VecIndexType.HNSW,
|
|
58
|
+
"HNSW_SQ": VecIndexType.HNSW_SQ,
|
|
59
|
+
"IVF": VecIndexType.IVFFLAT,
|
|
60
|
+
"IVF_FLAT": VecIndexType.IVFFLAT,
|
|
61
|
+
"IVF_SQ": VecIndexType.IVFSQ,
|
|
62
|
+
"IVF_PQ": VecIndexType.IVFPQ,
|
|
63
|
+
"FLAT": VecIndexType.IVFFLAT,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Index type to build parameters mapping
|
|
67
|
+
OCEANBASE_BUILD_PARAMS_MAPPING = {
|
|
68
|
+
"HNSW": DEFAULT_OCEANBASE_HNSW_BUILD_PARAM,
|
|
69
|
+
"HNSW_SQ": DEFAULT_OCEANBASE_HNSW_BUILD_PARAM,
|
|
70
|
+
"IVF": DEFAULT_OCEANBASE_IVF_BUILD_PARAM,
|
|
71
|
+
"IVF_FLAT": DEFAULT_OCEANBASE_IVF_BUILD_PARAM,
|
|
72
|
+
"IVF_SQ": DEFAULT_OCEANBASE_IVF_BUILD_PARAM,
|
|
73
|
+
"IVF_PQ": DEFAULT_OCEANBASE_IVF_PQ_BUILD_PARAM,
|
|
74
|
+
"FLAT": DEFAULT_OCEANBASE_FLAT_BUILD_PARAM,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Index type to search parameters mapping
|
|
78
|
+
OCEANBASE_SEARCH_PARAMS_MAPPING = {
|
|
79
|
+
"HNSW": DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM,
|
|
80
|
+
"HNSW_SQ": DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM,
|
|
81
|
+
"IVF": DEFAULT_OCEANBASE_IVF_SEARCH_PARAM,
|
|
82
|
+
"IVF_FLAT": DEFAULT_OCEANBASE_IVF_SEARCH_PARAM,
|
|
83
|
+
"IVF_SQ": DEFAULT_OCEANBASE_IVF_SEARCH_PARAM,
|
|
84
|
+
"IVF_PQ": DEFAULT_OCEANBASE_IVF_SEARCH_PARAM,
|
|
85
|
+
"FLAT": DEFAULT_OCEANBASE_FLAT_SEARCH_PARAM,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Field Names
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
DEFAULT_METADATA_FIELD = "metadata"
|
|
94
|
+
DEFAULT_PRIMARY_FIELD = "id"
|
|
95
|
+
DEFAULT_VECTOR_FIELD = "embedding"
|
|
96
|
+
DEFAULT_TEXT_FIELD = "document"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# =============================================================================
|
|
100
|
+
# Fulltext Search
|
|
101
|
+
# =============================================================================
|
|
102
|
+
|
|
103
|
+
# Supported fulltext parsers
|
|
104
|
+
OCEANBASE_SUPPORTED_FULLTEXT_PARSERS = ["ik", "ngram", "ngram2", "beng", "space"]
|
|
105
|
+
DEFAULT_FULLTEXT_PARSER = "ik"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =============================================================================
|
|
109
|
+
# Graph Storage Configuration
|
|
110
|
+
# =============================================================================
|
|
111
|
+
|
|
112
|
+
# Table names for graph storage
|
|
113
|
+
TABLE_ENTITIES = "graph_entities"
|
|
114
|
+
TABLE_RELATIONSHIPS = "graph_relationships"
|
|
115
|
+
|
|
116
|
+
# Graph search parameters
|
|
117
|
+
DEFAULT_SIMILARITY_THRESHOLD = 0.7
|
|
118
|
+
DEFAULT_PATH_STRING_LENGTH = 500
|
|
119
|
+
DEFAULT_SEARCH_LIMIT = 100
|
|
120
|
+
DEFAULT_BM25_TOP_N = 15
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# =============================================================================
|
|
124
|
+
# LLM Configuration
|
|
125
|
+
# =============================================================================
|
|
126
|
+
|
|
127
|
+
# Default LLM provider
|
|
128
|
+
DEFAULT_LLM_PROVIDER = "openai"
|
|
129
|
+
|
|
130
|
+
# Structured LLM providers
|
|
131
|
+
STRUCTURED_LLM_PROVIDERS = ["openai_structured"]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# Helper Functions
|
|
136
|
+
# =============================================================================
|
|
137
|
+
|
|
138
|
+
def get_default_build_params(index_type: str) -> dict:
|
|
139
|
+
"""Get default build parameters for the given index type."""
|
|
140
|
+
if index_type in INDEX_TYPE_HNSW:
|
|
141
|
+
return DEFAULT_OCEANBASE_HNSW_BUILD_PARAM.copy()
|
|
142
|
+
elif index_type in INDEX_TYPE_IVF:
|
|
143
|
+
return DEFAULT_OCEANBASE_IVF_BUILD_PARAM.copy()
|
|
144
|
+
elif index_type == INDEX_TYPE_IVF_PQ:
|
|
145
|
+
return DEFAULT_OCEANBASE_IVF_PQ_BUILD_PARAM.copy()
|
|
146
|
+
else:
|
|
147
|
+
return DEFAULT_OCEANBASE_FLAT_BUILD_PARAM.copy()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_default_search_params(index_type: str) -> dict:
|
|
151
|
+
"""Get default search parameters for the given index type."""
|
|
152
|
+
if index_type in INDEX_TYPE_HNSW:
|
|
153
|
+
return DEFAULT_OCEANBASE_HNSW_SEARCH_PARAM.copy()
|
|
154
|
+
elif index_type in INDEX_TYPE_IVF:
|
|
155
|
+
return DEFAULT_OCEANBASE_IVF_SEARCH_PARAM.copy()
|
|
156
|
+
else:
|
|
157
|
+
return DEFAULT_OCEANBASE_FLAT_SEARCH_PARAM.copy()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def is_structured_llm_provider(provider: str) -> bool:
|
|
161
|
+
"""Check if the given provider is a structured LLM provider."""
|
|
162
|
+
return provider in STRUCTURED_LLM_PROVIDERS
|