cognee-community-vector-adapter-redis 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cognee_community_vector_adapter_redis/__init__.py +0 -0
- cognee_community_vector_adapter_redis/redis_adapter.py +484 -0
- cognee_community_vector_adapter_redis/register.py +5 -0
- cognee_community_vector_adapter_redis-0.0.1.dist-info/METADATA +199 -0
- cognee_community_vector_adapter_redis-0.0.1.dist-info/RECORD +6 -0
- cognee_community_vector_adapter_redis-0.0.1.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Dict, List, Optional, Any
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from redisvl.index import AsyncSearchIndex
|
|
7
|
+
from redisvl.schema import IndexSchema
|
|
8
|
+
from redisvl.query import VectorQuery
|
|
9
|
+
|
|
10
|
+
from cognee.exceptions import InvalidValueError
|
|
11
|
+
from cognee.shared.logging_utils import get_logger
|
|
12
|
+
|
|
13
|
+
from cognee.infrastructure.engine import DataPoint
|
|
14
|
+
from cognee.infrastructure.engine.utils import parse_id
|
|
15
|
+
from cognee.infrastructure.databases.vector import VectorDBInterface
|
|
16
|
+
from cognee.infrastructure.databases.vector.models.ScoredResult import ScoredResult
|
|
17
|
+
from cognee.infrastructure.databases.vector.embeddings.EmbeddingEngine import EmbeddingEngine
|
|
18
|
+
|
|
19
|
+
logger = get_logger("RedisAdapter")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VectorEngineInitializationError(Exception):
|
|
23
|
+
"""Exception raised when vector engine initialization fails."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CollectionNotFoundError(Exception):
|
|
28
|
+
"""Exception raised when a collection is not found."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def serialize_for_json(obj: Any) -> Any:
|
|
33
|
+
"""Convert objects to JSON-serializable format.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
obj: Object to serialize (UUID, dict, list, or any other type).
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
JSON-serializable representation of the object.
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(obj, UUID):
|
|
42
|
+
return str(obj)
|
|
43
|
+
elif isinstance(obj, dict):
|
|
44
|
+
return {k: serialize_for_json(v) for k, v in obj.items()}
|
|
45
|
+
elif isinstance(obj, list):
|
|
46
|
+
return [serialize_for_json(item) for item in obj]
|
|
47
|
+
else:
|
|
48
|
+
return obj
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RedisDataPoint(DataPoint):
|
|
52
|
+
"""Redis data point schema for vector index entries.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
text: The text content to be indexed.
|
|
56
|
+
metadata: Metadata containing index field configuration.
|
|
57
|
+
"""
|
|
58
|
+
text: str
|
|
59
|
+
metadata: dict = {"index_fields": ["text"]}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RedisAdapter(VectorDBInterface):
|
|
63
|
+
"""Redis vector database adapter using RedisVL for vector similarity search.
|
|
64
|
+
|
|
65
|
+
This adapter provides an interface to Redis vector search capabilities,
|
|
66
|
+
supporting embedding generation, vector indexing, and similarity search.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
name = "Redis"
|
|
70
|
+
url: Optional[str]
|
|
71
|
+
api_key: Optional[str] = None
|
|
72
|
+
embedding_engine: Optional[EmbeddingEngine] = None
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
url: str,
|
|
77
|
+
api_key: Optional[str] = None,
|
|
78
|
+
embedding_engine: Optional[EmbeddingEngine] = None
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Initialize the Redis adapter.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
url (str): Connection string for your Redis instance like redis://localhost:6379.
|
|
84
|
+
embedding_engine: Engine for generating embeddings.
|
|
85
|
+
api_key: Optional API key. Ignored for Redis.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
VectorEngineInitializationError: If required parameters are missing.
|
|
89
|
+
"""
|
|
90
|
+
if not url:
|
|
91
|
+
raise VectorEngineInitializationError("Redis connnection URL!")
|
|
92
|
+
if not embedding_engine:
|
|
93
|
+
raise VectorEngineInitializationError("Embedding engine is required!")
|
|
94
|
+
|
|
95
|
+
self.url = url
|
|
96
|
+
self.embedding_engine = embedding_engine
|
|
97
|
+
self._indices = {}
|
|
98
|
+
|
|
99
|
+
async def embed_data(self, data: List[str]) -> List[List[float]]:
|
|
100
|
+
"""Embed text data using the embedding engine.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
data: List of text strings to embed.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of embedding vectors as lists of floats.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
Exception: If embedding generation fails.
|
|
110
|
+
"""
|
|
111
|
+
return await self.embedding_engine.embed_text(data)
|
|
112
|
+
|
|
113
|
+
def _create_schema(self, collection_name: str) -> IndexSchema:
|
|
114
|
+
"""Create a RedisVL IndexSchema for a collection.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
collection_name: Name of the collection to create an index schema for.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Redis IndexSchema configured for vector search.
|
|
121
|
+
"""
|
|
122
|
+
schema_dict = {
|
|
123
|
+
"index": {
|
|
124
|
+
"name": collection_name,
|
|
125
|
+
"prefix": f"{collection_name}",
|
|
126
|
+
"storage_type": "json"
|
|
127
|
+
},
|
|
128
|
+
"fields": [
|
|
129
|
+
{"name": "id", "type": "tag", "attrs": {"sortable": True}},
|
|
130
|
+
{"name": "text", "type": "text", "attrs": {"sortable": True}},
|
|
131
|
+
{
|
|
132
|
+
"name": "vector",
|
|
133
|
+
"type": "vector",
|
|
134
|
+
"attrs": {
|
|
135
|
+
"algorithm": "hnsw",
|
|
136
|
+
"m": 32,
|
|
137
|
+
"dims": self.embedding_engine.get_vector_size(),
|
|
138
|
+
"distance_metric": "cosine",
|
|
139
|
+
"datatype": "float32"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{"name": "payload", "type": "text", "attrs": {"sortable": True}}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
return IndexSchema.from_dict(schema_dict)
|
|
146
|
+
|
|
147
|
+
def _get_index(self, collection_name: str) -> AsyncSearchIndex:
|
|
148
|
+
"""Get or create an AsyncSearchIndex for a collection.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
collection_name: Name of the collection.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
AsyncSearchIndex instance for the collection.
|
|
155
|
+
"""
|
|
156
|
+
if collection_name not in self._indices:
|
|
157
|
+
schema = self._create_schema(collection_name)
|
|
158
|
+
self._indices[collection_name] = AsyncSearchIndex(
|
|
159
|
+
schema=schema,
|
|
160
|
+
redis_url=self.url,
|
|
161
|
+
validate_on_load=True
|
|
162
|
+
)
|
|
163
|
+
return self._indices[collection_name]
|
|
164
|
+
|
|
165
|
+
async def has_collection(self, collection_name: str) -> bool:
|
|
166
|
+
"""Check if a collection (index) exists.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
collection_name: Name of the collection to check.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
True if collection exists, False otherwise.
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
index = self._get_index(collection_name)
|
|
176
|
+
return await index.exists()
|
|
177
|
+
except Exception:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
async def create_collection(
|
|
181
|
+
self,
|
|
182
|
+
collection_name: str,
|
|
183
|
+
payload_schema: Optional[Any] = None,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Create a new collection (Redis index) with vector search capabilities.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
collection_name: Name of the collection to create.
|
|
189
|
+
payload_schema: Schema for payload data (not used).
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
Exception: If collection creation fails.
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
if await self.has_collection(collection_name):
|
|
196
|
+
logger.info(f"Collection {collection_name} already exists")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
index = self._get_index(collection_name)
|
|
200
|
+
await index.create(overwrite=False)
|
|
201
|
+
|
|
202
|
+
logger.info(f"Created collection {collection_name}")
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.error(f"Error creating collection {collection_name}: {str(e)}")
|
|
206
|
+
raise e
|
|
207
|
+
|
|
208
|
+
async def create_data_points(self, collection_name: str, data_points: List[DataPoint]) -> None:
|
|
209
|
+
"""Create data points in the collection.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
collection_name: Name of the target collection.
|
|
213
|
+
data_points: List of DataPoint objects to insert.
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
CollectionNotFoundError: If the collection doesn't exist.
|
|
217
|
+
Exception: If data point creation fails.
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
if not await self.has_collection(collection_name):
|
|
221
|
+
raise CollectionNotFoundError(f"Collection {collection_name} not found!")
|
|
222
|
+
|
|
223
|
+
# Embed the data points
|
|
224
|
+
data_vectors = await self.embed_data(
|
|
225
|
+
[DataPoint.get_embeddable_data(data_point) for data_point in data_points]
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Prepare documents for RedisVL
|
|
229
|
+
documents = []
|
|
230
|
+
for data_point, embedding in zip(data_points, data_vectors):
|
|
231
|
+
# Serialize the payload to handle UUIDs and other non-JSON types
|
|
232
|
+
payload = serialize_for_json(data_point.model_dump())
|
|
233
|
+
|
|
234
|
+
doc_data = {
|
|
235
|
+
"id": str(data_point.id),
|
|
236
|
+
"text": getattr(data_point, data_point.metadata.get("index_fields", ["text"])[0], ""),
|
|
237
|
+
"vector": embedding,
|
|
238
|
+
"payload": json.dumps(payload) # Store as JSON string
|
|
239
|
+
}
|
|
240
|
+
documents.append(doc_data)
|
|
241
|
+
|
|
242
|
+
# Load using RedisVL
|
|
243
|
+
index = self._get_index(collection_name)
|
|
244
|
+
await index.load(documents, id_field="id")
|
|
245
|
+
|
|
246
|
+
logger.info(f"Created {len(data_points)} data points in collection {collection_name}")
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error(f"Error creating data points: {str(e)}")
|
|
250
|
+
raise e
|
|
251
|
+
|
|
252
|
+
async def create_vector_index(self, index_name: str, index_property_name: str) -> None:
|
|
253
|
+
"""Create a vector index for a specific property.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
index_name: Base name for the index.
|
|
257
|
+
index_property_name: Property name to index.
|
|
258
|
+
"""
|
|
259
|
+
await self.create_collection(f"{index_name}_{index_property_name}")
|
|
260
|
+
|
|
261
|
+
async def index_data_points(
|
|
262
|
+
self, index_name: str, index_property_name: str, data_points: list[DataPoint]
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Index data points for a specific property.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
index_name: Base name for the index.
|
|
268
|
+
index_property_name: Property name to index.
|
|
269
|
+
data_points: List of DataPoint objects to index.
|
|
270
|
+
"""
|
|
271
|
+
await self.create_data_points(
|
|
272
|
+
f"{index_name}_{index_property_name}",
|
|
273
|
+
[
|
|
274
|
+
RedisDataPoint(
|
|
275
|
+
id=data_point.id,
|
|
276
|
+
text=getattr(data_point, data_point.metadata.get("index_fields", ["text"])[0]),
|
|
277
|
+
)
|
|
278
|
+
for data_point in data_points
|
|
279
|
+
],
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
async def retrieve(self, collection_name: str, data_point_ids: List[str]) -> List[Dict[str, Any]]:
|
|
283
|
+
"""Retrieve data points by their IDs.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
collection_name: Name of the collection to retrieve from.
|
|
287
|
+
data_point_ids: List of data point IDs to retrieve.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
List of retrieved data point payloads.
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
index = self._get_index(collection_name)
|
|
294
|
+
results = []
|
|
295
|
+
|
|
296
|
+
for data_id in data_point_ids:
|
|
297
|
+
doc = await index.fetch(data_id)
|
|
298
|
+
if doc:
|
|
299
|
+
# Parse the stored payload JSON
|
|
300
|
+
payload_str = doc.get("payload", "{}")
|
|
301
|
+
try:
|
|
302
|
+
payload = json.loads(payload_str)
|
|
303
|
+
results.append(payload)
|
|
304
|
+
except json.JSONDecodeError:
|
|
305
|
+
# Fallback to the document itself if payload parsing fails
|
|
306
|
+
results.append(doc)
|
|
307
|
+
|
|
308
|
+
return results
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.error(f"Error retrieving data points: {str(e)}")
|
|
312
|
+
return []
|
|
313
|
+
|
|
314
|
+
async def search(
|
|
315
|
+
self,
|
|
316
|
+
collection_name: str,
|
|
317
|
+
query_text: Optional[str] = None,
|
|
318
|
+
query_vector: Optional[List[float]] = None,
|
|
319
|
+
limit: int = 15,
|
|
320
|
+
with_vector: bool = False,
|
|
321
|
+
) -> List[ScoredResult]:
|
|
322
|
+
"""Search for similar vectors in the collection.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
collection_name: Name of the collection to search.
|
|
326
|
+
query_text: Text query to search for (will be embedded).
|
|
327
|
+
query_vector: Pre-computed query vector.
|
|
328
|
+
limit: Maximum number of results to return.
|
|
329
|
+
with_vector: Whether to include vectors in results.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of ScoredResult objects sorted by similarity.
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
InvalidValueError: If neither query_text nor query_vector is provided.
|
|
336
|
+
Exception: If search execution fails.
|
|
337
|
+
"""
|
|
338
|
+
if query_text is None and query_vector is None:
|
|
339
|
+
raise InvalidValueError("One of query_text or query_vector must be provided!")
|
|
340
|
+
|
|
341
|
+
if limit <= 0:
|
|
342
|
+
return []
|
|
343
|
+
|
|
344
|
+
if not await self.has_collection(collection_name):
|
|
345
|
+
logger.warning(f"Collection '{collection_name}' not found in RedisAdapter.search; returning [].")
|
|
346
|
+
return []
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Get the query vector
|
|
350
|
+
if query_vector is None:
|
|
351
|
+
query_vector = (await self.embed_data([query_text]))[0]
|
|
352
|
+
|
|
353
|
+
# Create the vector query
|
|
354
|
+
vector_query = VectorQuery(
|
|
355
|
+
vector=query_vector,
|
|
356
|
+
vector_field_name="vector",
|
|
357
|
+
num_results=limit,
|
|
358
|
+
return_score=True,
|
|
359
|
+
normalize_vector_distance=True
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Set return fields
|
|
363
|
+
return_fields = ["id", "text", "payload"]
|
|
364
|
+
if with_vector:
|
|
365
|
+
return_fields.append("vector")
|
|
366
|
+
vector_query = vector_query.return_fields(*return_fields)
|
|
367
|
+
|
|
368
|
+
# Execute the search
|
|
369
|
+
index = self._get_index(collection_name)
|
|
370
|
+
results = await index.query(vector_query)
|
|
371
|
+
|
|
372
|
+
# Convert results to ScoredResult objects
|
|
373
|
+
scored_results = []
|
|
374
|
+
for doc in results:
|
|
375
|
+
# Parse the stored payload - it's stored as JSON string
|
|
376
|
+
payload_str = doc.get("payload", "{}")
|
|
377
|
+
try:
|
|
378
|
+
payload = json.loads(payload_str)
|
|
379
|
+
except json.JSONDecodeError:
|
|
380
|
+
payload = doc
|
|
381
|
+
|
|
382
|
+
scored_results.append(
|
|
383
|
+
ScoredResult(
|
|
384
|
+
id=parse_id(doc["id"]),
|
|
385
|
+
payload=payload,
|
|
386
|
+
score=float(doc.get("vector_distance", 0.0)) # RedisVL returns distance
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
return scored_results
|
|
390
|
+
|
|
391
|
+
except Exception as e:
|
|
392
|
+
logger.error(f"Error during search: {str(e)}")
|
|
393
|
+
raise e
|
|
394
|
+
|
|
395
|
+
async def batch_search(
|
|
396
|
+
self,
|
|
397
|
+
collection_name: str,
|
|
398
|
+
query_texts: List[str],
|
|
399
|
+
limit: Optional[int] = None,
|
|
400
|
+
with_vectors: bool = False,
|
|
401
|
+
) -> List[List[ScoredResult]]:
|
|
402
|
+
"""Perform batch search for multiple queries.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
collection_name: Name of the collection to search.
|
|
406
|
+
query_texts: List of text queries to search for.
|
|
407
|
+
limit: Maximum number of results per query.
|
|
408
|
+
with_vectors: Whether to include vectors in results.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
List of search results for each query, filtered by score threshold.
|
|
412
|
+
"""
|
|
413
|
+
# Embed all queries at once
|
|
414
|
+
vectors = await self.embed_data(query_texts)
|
|
415
|
+
|
|
416
|
+
# Execute searches in parallel
|
|
417
|
+
# TODO: replace with index.batch_query() in the future
|
|
418
|
+
search_tasks = [
|
|
419
|
+
self.search(
|
|
420
|
+
collection_name=collection_name,
|
|
421
|
+
query_vector=vector,
|
|
422
|
+
limit=limit,
|
|
423
|
+
with_vector=with_vectors
|
|
424
|
+
)
|
|
425
|
+
for vector in vectors
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
results = await asyncio.gather(*search_tasks)
|
|
429
|
+
|
|
430
|
+
# Filter results by score threshold (Redis uses distance, so lower is better)
|
|
431
|
+
return [
|
|
432
|
+
[result for result in result_group if result.score < 0.1]
|
|
433
|
+
for result_group in results
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
async def delete_data_points(self, collection_name: str, data_point_ids: List[str]) -> Dict[str, int]:
|
|
437
|
+
"""Delete data points by their IDs.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
collection_name: Name of the collection to delete from.
|
|
441
|
+
data_point_ids: List of data point IDs to delete.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Dictionary containing the number of deleted documents.
|
|
445
|
+
|
|
446
|
+
Raises:
|
|
447
|
+
Exception: If deletion fails.
|
|
448
|
+
"""
|
|
449
|
+
try:
|
|
450
|
+
index = self._get_index(collection_name)
|
|
451
|
+
deleted_count = await index.drop_documents(data_point_ids)
|
|
452
|
+
logger.info(f"Deleted {deleted_count} data points from collection {collection_name}")
|
|
453
|
+
return {"deleted": deleted_count}
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.error(f"Error deleting data points: {str(e)}")
|
|
456
|
+
raise e
|
|
457
|
+
|
|
458
|
+
async def prune(self) -> None:
|
|
459
|
+
"""Remove all collections and data from Redis.
|
|
460
|
+
|
|
461
|
+
This method drops all existing indices and clears the internal cache.
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
Exception: If pruning fails.
|
|
465
|
+
"""
|
|
466
|
+
try:
|
|
467
|
+
# Get all existing indices and delete them
|
|
468
|
+
for collection_name, index in self._indices.items():
|
|
469
|
+
try:
|
|
470
|
+
if await index.exists():
|
|
471
|
+
await index.delete(drop=True)
|
|
472
|
+
logger.info(f"Dropped index {collection_name}")
|
|
473
|
+
await index.disconnect()
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.warning(f"Failed to drop index {collection_name}: {str(e)}")
|
|
476
|
+
|
|
477
|
+
# Clear the indices cache
|
|
478
|
+
self._indices.clear()
|
|
479
|
+
|
|
480
|
+
logger.info("Pruned all Redis vector collections")
|
|
481
|
+
|
|
482
|
+
except Exception as e:
|
|
483
|
+
logger.error(f"Error during prune: {str(e)}")
|
|
484
|
+
raise e
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cognee-community-vector-adapter-redis
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Redis vector database adapter for cognee
|
|
5
|
+
Requires-Python: <=3.13,>=3.11
|
|
6
|
+
Requires-Dist: cognee>=0.2.0.dev0
|
|
7
|
+
Requires-Dist: redisvl<=1.0.0,>=0.6.0
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
<div align="center" dir="auto">
|
|
11
|
+
<img width="250" src="https://raw.githubusercontent.com/redis/redis-vl-python/main/docs/_static/Redis_Logo_Red_RGB.svg" style="max-width: 100%" alt="Redis">
|
|
12
|
+
<h1>🧠 Cognee Redis Vector Adapter</h1>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div align="center" style="margin-top: 20px;">
|
|
16
|
+
<span style="display: block; margin-bottom: 10px;">Blazing fast vector similarity search for Cognee using Redis</span>
|
|
17
|
+
<br />
|
|
18
|
+
|
|
19
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
[](https://github.com/redis/redis-vl-python)
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
<div display="inline-block">
|
|
28
|
+
<a href="https://github.com/topoteretes/cognee"><b>Cognee</b></a>
|
|
29
|
+
<a href="https://docs.redisvl.com"><b>RedisVL Docs</b></a>
|
|
30
|
+
<a href="#examples"><b>Examples</b></a>
|
|
31
|
+
<a href="#troubleshooting"><b>Support</b></a>
|
|
32
|
+
</div>
|
|
33
|
+
<br />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Full support for vector embeddings storage and retrieval
|
|
40
|
+
- Batch / pipeline operations for efficient processing
|
|
41
|
+
- Automatic embedding generation via configurable embedding engines
|
|
42
|
+
- JSON payload serialization with UUID support
|
|
43
|
+
- Comprehensive error handling
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install cognee-community-vector-adapter-redis
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Prerequisites
|
|
52
|
+
|
|
53
|
+
You need a Redis instance with the Redis Search module enabled. You can use:
|
|
54
|
+
|
|
55
|
+
1. **Redis**:
|
|
56
|
+
```bash
|
|
57
|
+
docker run -d --name redis -p 6379:6379 redis:8.0.2
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. **Redis Cloud** with the search module enabled: [Redis Cloud](https://redis.io/try-free)
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
Checkout the `examples/` folder!
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv run examples/example.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
>You will need an OpenAI API key to run the example script.
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from cognee.infrastructure.databases.vector.embeddings.EmbeddingEngine import EmbeddingEngine
|
|
75
|
+
from cognee_community_vector_adapter_redis import RedisAdapter
|
|
76
|
+
|
|
77
|
+
# Initialize your embedding engine
|
|
78
|
+
embedding_engine = EmbeddingEngine(
|
|
79
|
+
model="your-model",
|
|
80
|
+
# ... other config
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Create Redis adapter
|
|
84
|
+
redis_adapter = RedisAdapter(
|
|
85
|
+
url="redis://localhost:6379", # Redis connection URL
|
|
86
|
+
embedding_engine=embedding_engine,
|
|
87
|
+
api_key=None # Optional, not used for Redis but part of interface
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Create a collection
|
|
91
|
+
await redis_adapter.create_collection("my_collection")
|
|
92
|
+
|
|
93
|
+
# Add data points
|
|
94
|
+
from cognee.infrastructure.engine import DataPoint
|
|
95
|
+
|
|
96
|
+
data_points = [
|
|
97
|
+
DataPoint(id="1", text="Hello world", metadata={"index_fields": ["text"]}),
|
|
98
|
+
DataPoint(id="2", text="Redis vector search", metadata={"index_fields": ["text"]})
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
await redis_adapter.create_data_points("my_collection", data_points)
|
|
102
|
+
|
|
103
|
+
# Search for similar vectors
|
|
104
|
+
results = await redis_adapter.search(
|
|
105
|
+
collection_name="my_collection",
|
|
106
|
+
query_text="Hello Redis",
|
|
107
|
+
limit=10
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Search with pre-computed vector
|
|
111
|
+
query_vector = await redis_adapter.embed_data(["Hello Redis"])
|
|
112
|
+
results = await redis_adapter.search(
|
|
113
|
+
collection_name="my_collection",
|
|
114
|
+
query_vector=query_vector[0],
|
|
115
|
+
limit=10,
|
|
116
|
+
with_vector=True # Include vectors in results
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Batch search
|
|
120
|
+
results = await redis_adapter.batch_search(
|
|
121
|
+
collection_name="my_collection",
|
|
122
|
+
query_texts=["query1", "query2"],
|
|
123
|
+
limit=5
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Retrieve specific data points
|
|
127
|
+
retrieved = await redis_adapter.retrieve(
|
|
128
|
+
collection_name="my_collection",
|
|
129
|
+
data_point_ids=["1", "2"]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Delete data points
|
|
133
|
+
await redis_adapter.delete_data_points(
|
|
134
|
+
collection_name="my_collection",
|
|
135
|
+
data_point_ids=["1"]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Check if collection exists
|
|
139
|
+
exists = await redis_adapter.has_collection("my_collection")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
The Redis adapter supports the following configuration options:
|
|
145
|
+
|
|
146
|
+
- `url`: Redis connection URL (e.g., "redis://localhost:6379", "redis://user:pass@host:port")
|
|
147
|
+
- `embedding_engine`: The `EmbeddingEngine` to use for text vectorization (required)
|
|
148
|
+
- `api_key`: Optional API key parameter (not used for Redis but part of the interface)
|
|
149
|
+
|
|
150
|
+
### Connection URL Examples
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Local Redis
|
|
154
|
+
redis_adapter = RedisAdapter(url="redis://localhost:6379", embedding_engine=engine)
|
|
155
|
+
|
|
156
|
+
# Redis with authentication
|
|
157
|
+
redis_adapter = RedisAdapter(url="redis://user:password@localhost:6379", embedding_engine=engine)
|
|
158
|
+
|
|
159
|
+
# Redis with SSL
|
|
160
|
+
redis_adapter = RedisAdapter(url="rediss://localhost:6380", embedding_engine=engine)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## Error Handling
|
|
165
|
+
|
|
166
|
+
The adapter includes comprehensive error handling:
|
|
167
|
+
|
|
168
|
+
- `VectorEngineInitializationError`: Raised when required parameters are missing
|
|
169
|
+
- `CollectionNotFoundError`: Raised when attempting operations on non-existent collections
|
|
170
|
+
- `InvalidValueError`: Raised for invalid query parameters
|
|
171
|
+
- Graceful handling of connection failures and embedding errors
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## Troubleshooting
|
|
175
|
+
|
|
176
|
+
### Common Issues
|
|
177
|
+
|
|
178
|
+
1. **Connection Errors**: Ensure Redis is running and accessible at the specified URL
|
|
179
|
+
2. **Search Module Missing**: Make sure Redis has the Search module enabled
|
|
180
|
+
3. **Embedding Dimension Mismatch**: Verify embedding engine dimensions match index configuration
|
|
181
|
+
4. **Collection Not Found**: Always create collections before adding data points
|
|
182
|
+
|
|
183
|
+
### Debug Logging
|
|
184
|
+
|
|
185
|
+
The adapter uses Cognee's logging system. Enable debug logging to see detailed operation logs:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import logging
|
|
189
|
+
logging.getLogger("RedisAdapter").setLevel(logging.DEBUG)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Development
|
|
193
|
+
|
|
194
|
+
To contribute or modify the adapter:
|
|
195
|
+
|
|
196
|
+
1. Clone the repository and `cd` into the `redis` folder
|
|
197
|
+
2. Install dependencies: `uv sync --all-extras`
|
|
198
|
+
3. Make sure a Redis instance is running (see above)
|
|
199
|
+
5. Make your changes, test, and submit a PR
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
cognee_community_vector_adapter_redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cognee_community_vector_adapter_redis/redis_adapter.py,sha256=c4D2GLkiZ11nIJ7rk30OW0kSN78rep7T05mJT0wSN0o,17576
|
|
3
|
+
cognee_community_vector_adapter_redis/register.py,sha256=0LdEifYQuIu9OkXNV8PxOPOg2gKCL-9Lq4FL0nmUCxo,154
|
|
4
|
+
cognee_community_vector_adapter_redis-0.0.1.dist-info/METADATA,sha256=g55O7nqOsjqVPdBp649zGoa39fy1gZZDBjCH_ULDvqI,5966
|
|
5
|
+
cognee_community_vector_adapter_redis-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
cognee_community_vector_adapter_redis-0.0.1.dist-info/RECORD,,
|