genxai-framework 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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Vector store implementations for long-term memory."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from genxai.core.memory.base import Memory, MemoryType
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VectorStore(ABC):
|
|
14
|
+
"""Abstract base class for vector stores."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def store(
|
|
18
|
+
self,
|
|
19
|
+
memory: Memory,
|
|
20
|
+
embedding: List[float],
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Store a memory with its embedding.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
memory: Memory to store
|
|
26
|
+
embedding: Vector embedding of the memory
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
async def search(
|
|
32
|
+
self,
|
|
33
|
+
query_embedding: List[float],
|
|
34
|
+
limit: int = 10,
|
|
35
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
36
|
+
) -> List[Tuple[Memory, float]]:
|
|
37
|
+
"""Search for similar memories.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
query_embedding: Query vector
|
|
41
|
+
limit: Maximum number of results
|
|
42
|
+
filters: Optional metadata filters
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of (memory, similarity_score) tuples
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def delete(self, memory_id: str) -> bool:
|
|
51
|
+
"""Delete a memory by ID.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
memory_id: ID of memory to delete
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
True if deleted, False if not found
|
|
58
|
+
"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def clear(self) -> None:
|
|
63
|
+
"""Clear all memories."""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
68
|
+
"""Get vector store statistics."""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ChromaVectorStore(VectorStore):
|
|
73
|
+
"""ChromaDB vector store implementation."""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
collection_name: str = "genxai_memories",
|
|
78
|
+
persist_directory: Optional[str] = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Initialize ChromaDB vector store.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
collection_name: Name of the collection
|
|
84
|
+
persist_directory: Directory to persist data (None for in-memory)
|
|
85
|
+
"""
|
|
86
|
+
self.collection_name = collection_name
|
|
87
|
+
self.persist_directory = persist_directory
|
|
88
|
+
self._client = None
|
|
89
|
+
self._collection = None
|
|
90
|
+
self._initialized = False
|
|
91
|
+
|
|
92
|
+
async def _ensure_initialized(self) -> None:
|
|
93
|
+
"""Ensure ChromaDB client is initialized."""
|
|
94
|
+
if self._initialized:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
import chromadb
|
|
99
|
+
from chromadb.config import Settings
|
|
100
|
+
|
|
101
|
+
if self.persist_directory:
|
|
102
|
+
self._client = chromadb.Client(
|
|
103
|
+
Settings(
|
|
104
|
+
persist_directory=self.persist_directory,
|
|
105
|
+
anonymized_telemetry=False,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
self._client = chromadb.Client()
|
|
110
|
+
|
|
111
|
+
# Get or create collection
|
|
112
|
+
self._collection = self._client.get_or_create_collection(
|
|
113
|
+
name=self.collection_name,
|
|
114
|
+
metadata={"description": "GenXAI agent memories"},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
self._initialized = True
|
|
118
|
+
logger.info(f"Initialized ChromaDB collection: {self.collection_name}")
|
|
119
|
+
|
|
120
|
+
except ImportError:
|
|
121
|
+
logger.error(
|
|
122
|
+
"ChromaDB not installed. Install with: pip install chromadb"
|
|
123
|
+
)
|
|
124
|
+
raise RuntimeError("ChromaDB not available")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Failed to initialize ChromaDB: {e}")
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
async def store(
|
|
130
|
+
self,
|
|
131
|
+
memory: Memory,
|
|
132
|
+
embedding: List[float],
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Store a memory with its embedding."""
|
|
135
|
+
await self._ensure_initialized()
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
# Prepare metadata
|
|
139
|
+
metadata = {
|
|
140
|
+
"type": memory.type.value,
|
|
141
|
+
"importance": memory.importance,
|
|
142
|
+
"timestamp": memory.timestamp.isoformat(),
|
|
143
|
+
"access_count": memory.access_count,
|
|
144
|
+
"tags": ",".join(memory.tags) if memory.tags else "",
|
|
145
|
+
**memory.metadata,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Store in ChromaDB
|
|
149
|
+
self._collection.add(
|
|
150
|
+
ids=[memory.id],
|
|
151
|
+
embeddings=[embedding],
|
|
152
|
+
documents=[str(memory.content)],
|
|
153
|
+
metadatas=[metadata],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
logger.debug(f"Stored memory {memory.id} in ChromaDB")
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Failed to store memory in ChromaDB: {e}")
|
|
160
|
+
raise
|
|
161
|
+
|
|
162
|
+
async def search(
|
|
163
|
+
self,
|
|
164
|
+
query_embedding: List[float],
|
|
165
|
+
limit: int = 10,
|
|
166
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
167
|
+
) -> List[Tuple[Memory, float]]:
|
|
168
|
+
"""Search for similar memories."""
|
|
169
|
+
await self._ensure_initialized()
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# Build where clause for filters
|
|
173
|
+
where = None
|
|
174
|
+
if filters:
|
|
175
|
+
where = {}
|
|
176
|
+
for key, value in filters.items():
|
|
177
|
+
if key in ["type", "importance", "tags"]:
|
|
178
|
+
where[key] = value
|
|
179
|
+
|
|
180
|
+
# Query ChromaDB
|
|
181
|
+
results = self._collection.query(
|
|
182
|
+
query_embeddings=[query_embedding],
|
|
183
|
+
n_results=limit,
|
|
184
|
+
where=where,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Convert results to Memory objects
|
|
188
|
+
memories = []
|
|
189
|
+
if results["ids"] and results["ids"][0]:
|
|
190
|
+
for i, memory_id in enumerate(results["ids"][0]):
|
|
191
|
+
metadata = results["metadatas"][0][i]
|
|
192
|
+
content = results["documents"][0][i]
|
|
193
|
+
distance = results["distances"][0][i] if "distances" in results else 0.0
|
|
194
|
+
|
|
195
|
+
# Reconstruct Memory object
|
|
196
|
+
memory = Memory(
|
|
197
|
+
id=memory_id,
|
|
198
|
+
type=MemoryType(metadata["type"]),
|
|
199
|
+
content=content,
|
|
200
|
+
metadata={k: v for k, v in metadata.items() if k not in ["type", "importance", "timestamp", "access_count", "tags"]},
|
|
201
|
+
timestamp=datetime.fromisoformat(metadata["timestamp"]),
|
|
202
|
+
importance=metadata["importance"],
|
|
203
|
+
access_count=metadata["access_count"],
|
|
204
|
+
last_accessed=datetime.now(),
|
|
205
|
+
tags=metadata["tags"].split(",") if metadata.get("tags") else [],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Convert distance to similarity score (0-1)
|
|
209
|
+
similarity = 1.0 / (1.0 + distance)
|
|
210
|
+
memories.append((memory, similarity))
|
|
211
|
+
|
|
212
|
+
logger.debug(f"Found {len(memories)} similar memories in ChromaDB")
|
|
213
|
+
return memories
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.error(f"Failed to search ChromaDB: {e}")
|
|
217
|
+
return []
|
|
218
|
+
|
|
219
|
+
async def delete(self, memory_id: str) -> bool:
|
|
220
|
+
"""Delete a memory by ID."""
|
|
221
|
+
await self._ensure_initialized()
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
self._collection.delete(ids=[memory_id])
|
|
225
|
+
logger.debug(f"Deleted memory {memory_id} from ChromaDB")
|
|
226
|
+
return True
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Failed to delete memory from ChromaDB: {e}")
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
async def clear(self) -> None:
|
|
232
|
+
"""Clear all memories."""
|
|
233
|
+
await self._ensure_initialized()
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
# Delete and recreate collection
|
|
237
|
+
self._client.delete_collection(name=self.collection_name)
|
|
238
|
+
self._collection = self._client.create_collection(
|
|
239
|
+
name=self.collection_name,
|
|
240
|
+
metadata={"description": "GenXAI agent memories"},
|
|
241
|
+
)
|
|
242
|
+
logger.info(f"Cleared ChromaDB collection: {self.collection_name}")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(f"Failed to clear ChromaDB: {e}")
|
|
245
|
+
raise
|
|
246
|
+
|
|
247
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
248
|
+
"""Get vector store statistics."""
|
|
249
|
+
await self._ensure_initialized()
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
count = self._collection.count()
|
|
253
|
+
return {
|
|
254
|
+
"backend": "chromadb",
|
|
255
|
+
"collection": self.collection_name,
|
|
256
|
+
"count": count,
|
|
257
|
+
"persist_directory": self.persist_directory,
|
|
258
|
+
}
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Failed to get ChromaDB stats: {e}")
|
|
261
|
+
return {"backend": "chromadb", "error": str(e)}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class PineconeVectorStore(VectorStore):
|
|
265
|
+
"""Pinecone vector store implementation."""
|
|
266
|
+
|
|
267
|
+
def __init__(
|
|
268
|
+
self,
|
|
269
|
+
index_name: str = "genxai-memories",
|
|
270
|
+
api_key: Optional[str] = None,
|
|
271
|
+
environment: Optional[str] = None,
|
|
272
|
+
dimension: int = 1536,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Initialize Pinecone vector store.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
index_name: Name of the Pinecone index
|
|
278
|
+
api_key: Pinecone API key (or set PINECONE_API_KEY env var)
|
|
279
|
+
environment: Pinecone environment (or set PINECONE_ENVIRONMENT env var)
|
|
280
|
+
dimension: Vector dimension
|
|
281
|
+
"""
|
|
282
|
+
self.index_name = index_name
|
|
283
|
+
self.api_key = api_key
|
|
284
|
+
self.environment = environment
|
|
285
|
+
self.dimension = dimension
|
|
286
|
+
self._index = None
|
|
287
|
+
self._initialized = False
|
|
288
|
+
|
|
289
|
+
async def _ensure_initialized(self) -> None:
|
|
290
|
+
"""Ensure Pinecone client is initialized."""
|
|
291
|
+
if self._initialized:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
import pinecone
|
|
296
|
+
import os
|
|
297
|
+
|
|
298
|
+
# Get API key and environment
|
|
299
|
+
api_key = self.api_key or os.getenv("PINECONE_API_KEY")
|
|
300
|
+
environment = self.environment or os.getenv("PINECONE_ENVIRONMENT")
|
|
301
|
+
|
|
302
|
+
if not api_key or not environment:
|
|
303
|
+
raise ValueError(
|
|
304
|
+
"Pinecone API key and environment required. "
|
|
305
|
+
"Set PINECONE_API_KEY and PINECONE_ENVIRONMENT env vars."
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Initialize Pinecone
|
|
309
|
+
pinecone.init(api_key=api_key, environment=environment)
|
|
310
|
+
|
|
311
|
+
# Create index if it doesn't exist
|
|
312
|
+
if self.index_name not in pinecone.list_indexes():
|
|
313
|
+
pinecone.create_index(
|
|
314
|
+
name=self.index_name,
|
|
315
|
+
dimension=self.dimension,
|
|
316
|
+
metric="cosine",
|
|
317
|
+
)
|
|
318
|
+
logger.info(f"Created Pinecone index: {self.index_name}")
|
|
319
|
+
|
|
320
|
+
# Connect to index
|
|
321
|
+
self._index = pinecone.Index(self.index_name)
|
|
322
|
+
self._initialized = True
|
|
323
|
+
logger.info(f"Initialized Pinecone index: {self.index_name}")
|
|
324
|
+
|
|
325
|
+
except ImportError:
|
|
326
|
+
logger.error(
|
|
327
|
+
"Pinecone not installed. Install with: pip install pinecone-client"
|
|
328
|
+
)
|
|
329
|
+
raise RuntimeError("Pinecone not available")
|
|
330
|
+
except Exception as e:
|
|
331
|
+
logger.error(f"Failed to initialize Pinecone: {e}")
|
|
332
|
+
raise
|
|
333
|
+
|
|
334
|
+
async def store(
|
|
335
|
+
self,
|
|
336
|
+
memory: Memory,
|
|
337
|
+
embedding: List[float],
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Store a memory with its embedding."""
|
|
340
|
+
await self._ensure_initialized()
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
# Prepare metadata
|
|
344
|
+
metadata = {
|
|
345
|
+
"type": memory.type.value,
|
|
346
|
+
"content": str(memory.content),
|
|
347
|
+
"importance": memory.importance,
|
|
348
|
+
"timestamp": memory.timestamp.isoformat(),
|
|
349
|
+
"access_count": memory.access_count,
|
|
350
|
+
"tags": ",".join(memory.tags) if memory.tags else "",
|
|
351
|
+
**memory.metadata,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
# Store in Pinecone
|
|
355
|
+
self._index.upsert(
|
|
356
|
+
vectors=[(memory.id, embedding, metadata)]
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
logger.debug(f"Stored memory {memory.id} in Pinecone")
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
logger.error(f"Failed to store memory in Pinecone: {e}")
|
|
363
|
+
raise
|
|
364
|
+
|
|
365
|
+
async def search(
|
|
366
|
+
self,
|
|
367
|
+
query_embedding: List[float],
|
|
368
|
+
limit: int = 10,
|
|
369
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
370
|
+
) -> List[Tuple[Memory, float]]:
|
|
371
|
+
"""Search for similar memories."""
|
|
372
|
+
await self._ensure_initialized()
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
# Build filter
|
|
376
|
+
filter_dict = None
|
|
377
|
+
if filters:
|
|
378
|
+
filter_dict = {}
|
|
379
|
+
for key, value in filters.items():
|
|
380
|
+
if key in ["type", "importance", "tags"]:
|
|
381
|
+
filter_dict[key] = value
|
|
382
|
+
|
|
383
|
+
# Query Pinecone
|
|
384
|
+
results = self._index.query(
|
|
385
|
+
vector=query_embedding,
|
|
386
|
+
top_k=limit,
|
|
387
|
+
filter=filter_dict,
|
|
388
|
+
include_metadata=True,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Convert results to Memory objects
|
|
392
|
+
memories = []
|
|
393
|
+
for match in results.matches:
|
|
394
|
+
metadata = match.metadata
|
|
395
|
+
|
|
396
|
+
# Reconstruct Memory object
|
|
397
|
+
memory = Memory(
|
|
398
|
+
id=match.id,
|
|
399
|
+
type=MemoryType(metadata["type"]),
|
|
400
|
+
content=metadata["content"],
|
|
401
|
+
metadata={k: v for k, v in metadata.items() if k not in ["type", "content", "importance", "timestamp", "access_count", "tags"]},
|
|
402
|
+
timestamp=datetime.fromisoformat(metadata["timestamp"]),
|
|
403
|
+
importance=metadata["importance"],
|
|
404
|
+
access_count=metadata["access_count"],
|
|
405
|
+
last_accessed=datetime.now(),
|
|
406
|
+
tags=metadata["tags"].split(",") if metadata.get("tags") else [],
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
memories.append((memory, match.score))
|
|
410
|
+
|
|
411
|
+
logger.debug(f"Found {len(memories)} similar memories in Pinecone")
|
|
412
|
+
return memories
|
|
413
|
+
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.error(f"Failed to search Pinecone: {e}")
|
|
416
|
+
return []
|
|
417
|
+
|
|
418
|
+
async def delete(self, memory_id: str) -> bool:
|
|
419
|
+
"""Delete a memory by ID."""
|
|
420
|
+
await self._ensure_initialized()
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
self._index.delete(ids=[memory_id])
|
|
424
|
+
logger.debug(f"Deleted memory {memory_id} from Pinecone")
|
|
425
|
+
return True
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.error(f"Failed to delete memory from Pinecone: {e}")
|
|
428
|
+
return False
|
|
429
|
+
|
|
430
|
+
async def clear(self) -> None:
|
|
431
|
+
"""Clear all memories."""
|
|
432
|
+
await self._ensure_initialized()
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
self._index.delete(delete_all=True)
|
|
436
|
+
logger.info(f"Cleared Pinecone index: {self.index_name}")
|
|
437
|
+
except Exception as e:
|
|
438
|
+
logger.error(f"Failed to clear Pinecone: {e}")
|
|
439
|
+
raise
|
|
440
|
+
|
|
441
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
442
|
+
"""Get vector store statistics."""
|
|
443
|
+
await self._ensure_initialized()
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
stats = self._index.describe_index_stats()
|
|
447
|
+
return {
|
|
448
|
+
"backend": "pinecone",
|
|
449
|
+
"index": self.index_name,
|
|
450
|
+
"dimension": self.dimension,
|
|
451
|
+
"total_vector_count": stats.total_vector_count,
|
|
452
|
+
"namespaces": stats.namespaces,
|
|
453
|
+
}
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.error(f"Failed to get Pinecone stats: {e}")
|
|
456
|
+
return {"backend": "pinecone", "error": str(e)}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class VectorStoreFactory:
|
|
460
|
+
"""Factory for creating vector stores."""
|
|
461
|
+
|
|
462
|
+
_stores = {
|
|
463
|
+
"chromadb": ChromaVectorStore,
|
|
464
|
+
"pinecone": PineconeVectorStore,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@classmethod
|
|
468
|
+
def create(
|
|
469
|
+
cls,
|
|
470
|
+
backend: str,
|
|
471
|
+
**kwargs
|
|
472
|
+
) -> VectorStore:
|
|
473
|
+
"""Create a vector store instance.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
backend: Vector store backend ("chromadb", "pinecone")
|
|
477
|
+
**kwargs: Backend-specific arguments
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
VectorStore instance
|
|
481
|
+
|
|
482
|
+
Raises:
|
|
483
|
+
ValueError: If backend is not supported
|
|
484
|
+
"""
|
|
485
|
+
if backend not in cls._stores:
|
|
486
|
+
raise ValueError(
|
|
487
|
+
f"Unsupported vector store backend: {backend}. "
|
|
488
|
+
f"Supported: {list(cls._stores.keys())}"
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
store_class = cls._stores[backend]
|
|
492
|
+
return store_class(**kwargs)
|
|
493
|
+
|
|
494
|
+
@classmethod
|
|
495
|
+
def register(cls, name: str, store_class: type) -> None:
|
|
496
|
+
"""Register a custom vector store.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
name: Name of the vector store
|
|
500
|
+
store_class: Vector store class
|
|
501
|
+
"""
|
|
502
|
+
cls._stores[name] = store_class
|
|
503
|
+
logger.info(f"Registered vector store: {name}")
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def list_backends(cls) -> List[str]:
|
|
507
|
+
"""List available vector store backends."""
|
|
508
|
+
return list(cls._stores.keys())
|