nvidia-nat-redis 1.3.0.dev2__py3-none-any.whl → 1.3.0rc2__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.
- nat/plugins/redis/memory.py +7 -6
- nat/plugins/redis/object_store.py +40 -0
- nat/plugins/redis/redis_editor.py +39 -44
- nat/plugins/redis/redis_object_store.py +126 -0
- nat/plugins/redis/register.py +1 -1
- nat/plugins/redis/schema.py +21 -20
- {nvidia_nat_redis-1.3.0.dev2.dist-info → nvidia_nat_redis-1.3.0rc2.dist-info}/METADATA +6 -3
- nvidia_nat_redis-1.3.0rc2.dist-info/RECORD +13 -0
- nvidia_nat_redis-1.3.0.dev2.dist-info/RECORD +0 -11
- {nvidia_nat_redis-1.3.0.dev2.dist-info → nvidia_nat_redis-1.3.0rc2.dist-info}/WHEEL +0 -0
- {nvidia_nat_redis-1.3.0.dev2.dist-info → nvidia_nat_redis-1.3.0rc2.dist-info}/entry_points.txt +0 -0
- {nvidia_nat_redis-1.3.0.dev2.dist-info → nvidia_nat_redis-1.3.0rc2.dist-info}/top_level.txt +0 -0
nat/plugins/redis/memory.py
CHANGED
@@ -13,21 +13,19 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
import redis.asyncio as redis
|
17
16
|
from pydantic import Field
|
18
17
|
|
19
18
|
from nat.builder.builder import Builder
|
20
|
-
from nat.builder.framework_enum import LLMFrameworkEnum
|
21
19
|
from nat.cli.register_workflow import register_memory
|
22
20
|
from nat.data_models.component_ref import EmbedderRef
|
23
21
|
from nat.data_models.memory import MemoryBaseConfig
|
24
22
|
|
25
23
|
|
26
24
|
class RedisMemoryClientConfig(MemoryBaseConfig, name="redis_memory"):
|
27
|
-
host: str
|
28
|
-
db:
|
29
|
-
port:
|
30
|
-
key_prefix: str
|
25
|
+
host: str = Field(default="localhost", description="Redis server host")
|
26
|
+
db: int = Field(default=0, description="Redis DB")
|
27
|
+
port: int = Field(default=6379, description="Redis server port")
|
28
|
+
key_prefix: str = Field(default="nat", description="Key prefix to use for redis keys")
|
31
29
|
embedder: EmbedderRef = Field(description=("Instance name of the memory client instance from the workflow "
|
32
30
|
"configuration object."))
|
33
31
|
|
@@ -35,6 +33,9 @@ class RedisMemoryClientConfig(MemoryBaseConfig, name="redis_memory"):
|
|
35
33
|
@register_memory(config_type=RedisMemoryClientConfig)
|
36
34
|
async def redis_memory_client(config: RedisMemoryClientConfig, builder: Builder):
|
37
35
|
|
36
|
+
import redis.asyncio as redis
|
37
|
+
|
38
|
+
from nat.builder.framework_enum import LLMFrameworkEnum
|
38
39
|
from nat.plugins.redis.redis_editor import RedisEditor
|
39
40
|
|
40
41
|
from .schema import ensure_index_exists
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
from pydantic import Field
|
17
|
+
|
18
|
+
from nat.builder.builder import Builder
|
19
|
+
from nat.cli.register_workflow import register_object_store
|
20
|
+
from nat.data_models.object_store import ObjectStoreBaseConfig
|
21
|
+
|
22
|
+
|
23
|
+
class RedisObjectStoreClientConfig(ObjectStoreBaseConfig, name="redis"):
|
24
|
+
"""
|
25
|
+
Object store that stores objects in a Redis database.
|
26
|
+
"""
|
27
|
+
|
28
|
+
host: str = Field(default="localhost", description="The host of the Redis server")
|
29
|
+
db: int = Field(default=0, description="The Redis logical database number")
|
30
|
+
port: int = Field(default=6379, description="The port of the Redis server")
|
31
|
+
bucket_name: str = Field(description="The name of the bucket to use for the object store")
|
32
|
+
|
33
|
+
|
34
|
+
@register_object_store(config_type=RedisObjectStoreClientConfig)
|
35
|
+
async def redis_object_store_client(config: RedisObjectStoreClientConfig, _builder: Builder):
|
36
|
+
|
37
|
+
from .redis_object_store import RedisObjectStore
|
38
|
+
|
39
|
+
async with RedisObjectStore(**config.model_dump(exclude={"type"})) as store:
|
40
|
+
yield store
|
@@ -54,7 +54,7 @@ class RedisEditor(MemoryEditor):
|
|
54
54
|
Insert Multiple MemoryItems into Redis.
|
55
55
|
Each MemoryItem is stored with its metadata and tags.
|
56
56
|
"""
|
57
|
-
logger.debug(
|
57
|
+
logger.debug("Attempting to add %d items to Redis", len(items))
|
58
58
|
|
59
59
|
for memory_item in items:
|
60
60
|
item_meta = memory_item.metadata
|
@@ -65,7 +65,7 @@ class RedisEditor(MemoryEditor):
|
|
65
65
|
|
66
66
|
# Create a unique key for this memory item
|
67
67
|
memory_key = f"{self._key_prefix}:memory:{memory_id}"
|
68
|
-
logger.debug(
|
68
|
+
logger.debug("Generated memory key: %s", memory_key)
|
69
69
|
|
70
70
|
# Prepare memory data
|
71
71
|
memory_data = {
|
@@ -75,30 +75,30 @@ class RedisEditor(MemoryEditor):
|
|
75
75
|
"metadata": item_meta,
|
76
76
|
"memory": memory_item.memory or ""
|
77
77
|
}
|
78
|
-
logger.debug(
|
78
|
+
logger.debug("Prepared memory data for key %s", memory_key)
|
79
79
|
|
80
80
|
# If we have memory, compute and store the embedding
|
81
81
|
if memory_item.memory:
|
82
82
|
logger.debug("Computing embedding for memory text")
|
83
83
|
search_vector = await self._embedder.aembed_query(memory_item.memory)
|
84
|
-
logger.debug(
|
84
|
+
logger.debug("Generated embedding vector of length: %d", len(search_vector))
|
85
85
|
memory_data["embedding"] = search_vector
|
86
86
|
|
87
87
|
try:
|
88
88
|
# Store as JSON in Redis
|
89
|
-
logger.debug(
|
89
|
+
logger.debug("Attempting to store memory data in Redis for key: %s", memory_key)
|
90
90
|
await self._client.json().set(memory_key, "$", memory_data)
|
91
|
-
logger.debug(
|
91
|
+
logger.debug("Successfully stored memory data for key: %s", memory_key)
|
92
92
|
|
93
93
|
# Verify the data was stored
|
94
94
|
stored_data = await self._client.json().get(memory_key)
|
95
|
-
logger.debug(
|
95
|
+
logger.debug("Verified data storage for key %s: %s", memory_key, bool(stored_data))
|
96
96
|
|
97
97
|
except redis_exceptions.ResponseError as e:
|
98
|
-
logger.error(
|
98
|
+
logger.error("Failed to store memory item: %s", e)
|
99
99
|
raise
|
100
100
|
except redis_exceptions.ConnectionError as e:
|
101
|
-
logger.error(
|
101
|
+
logger.error("Redis connection error while storing memory item: %s", e)
|
102
102
|
raise
|
103
103
|
|
104
104
|
async def search(self, query: str, top_k: int = 5, **kwargs) -> list[MemoryItem]:
|
@@ -113,91 +113,86 @@ class RedisEditor(MemoryEditor):
|
|
113
113
|
Returns:
|
114
114
|
list[MemoryItem]: The most relevant MemoryItems for the given query.
|
115
115
|
"""
|
116
|
-
logger.debug(
|
116
|
+
logger.debug("Search called with query: %s, top_k: %d, kwargs: %s", query, top_k, kwargs)
|
117
117
|
|
118
118
|
user_id = kwargs.get("user_id", "redis") # TODO: remove this fallback username
|
119
|
-
logger.debug(
|
119
|
+
logger.debug("Using user_id: %s", user_id)
|
120
120
|
|
121
121
|
# Perform vector search using Redis search
|
122
122
|
logger.debug("Using embedder for vector search")
|
123
123
|
try:
|
124
|
-
logger.debug(
|
124
|
+
logger.debug("Generating embedding for query: '%s'", query)
|
125
125
|
query_vector = await self._embedder.aembed_query(query)
|
126
|
-
logger.debug(
|
126
|
+
logger.debug("Generated embedding vector of length: %d", len(query_vector))
|
127
127
|
except Exception as e:
|
128
|
-
logger.error(
|
128
|
+
logger.error("Failed to generate embedding: %s", e)
|
129
129
|
raise
|
130
130
|
|
131
131
|
# Create vector search query
|
132
132
|
search_query = (
|
133
133
|
Query(f"(@user_id:{user_id})=>[KNN {top_k} @embedding $vec AS score]").sort_by("score").return_fields(
|
134
134
|
"conversation", "user_id", "tags", "metadata", "memory", "score").dialect(2))
|
135
|
-
logger.debug(
|
136
|
-
logger.debug(
|
135
|
+
logger.debug("Created search query: %s", search_query)
|
136
|
+
logger.debug("Query string: %s", search_query.query_string())
|
137
137
|
|
138
138
|
# Convert query vector to bytes
|
139
139
|
try:
|
140
140
|
logger.debug("Converting query vector to bytes")
|
141
141
|
query_vector_bytes = np.array(query_vector, dtype=np.float32).tobytes()
|
142
|
-
logger.debug(
|
142
|
+
logger.debug("Converted vector to bytes of length: %d", len(query_vector_bytes))
|
143
143
|
except Exception as e:
|
144
|
-
logger.error(
|
144
|
+
logger.error("Failed to convert vector to bytes: %s", e)
|
145
145
|
raise
|
146
146
|
|
147
147
|
try:
|
148
148
|
# Execute search with vector parameters
|
149
149
|
logger.debug("Executing Redis search with vector parameters")
|
150
|
-
logger.debug(
|
150
|
+
logger.debug("Search query parameters: vec length=%d", len(query_vector_bytes))
|
151
151
|
|
152
152
|
# Log the actual query being executed
|
153
|
-
logger.debug(
|
153
|
+
logger.debug("Full search query: %s", search_query.query_string())
|
154
154
|
|
155
155
|
# Check if there are any documents in the index
|
156
156
|
try:
|
157
157
|
total_docs = await self._client.ft(INDEX_NAME).info()
|
158
|
-
logger.debug(
|
158
|
+
logger.debug("Total documents in index: %d", total_docs.get('num_docs', 0))
|
159
159
|
except Exception as e:
|
160
|
-
logger.
|
160
|
+
logger.exception("Failed to get index info: %s", e)
|
161
161
|
|
162
162
|
# Execute the search
|
163
163
|
results = await self._client.ft(INDEX_NAME).search(search_query, query_params={"vec": query_vector_bytes})
|
164
164
|
|
165
165
|
# Log detailed results information
|
166
|
-
logger.debug(
|
167
|
-
logger.debug(
|
166
|
+
logger.debug("Search returned %d results", len(results.docs))
|
167
|
+
logger.debug("Total results found: %d", results.total)
|
168
168
|
|
169
169
|
# Convert results to MemoryItems
|
170
170
|
memories = []
|
171
171
|
for i, doc in enumerate(results.docs):
|
172
172
|
try:
|
173
|
-
logger.debug(
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
"memory": getattr(doc, 'memory', "")
|
181
|
-
}
|
182
|
-
logger.debug(f"Similarity score: {getattr(doc, 'score', 0)}")
|
183
|
-
logger.debug(f"Extracted data for result {i+1}: {memory_data}")
|
184
|
-
memory_item = self._create_memory_item(memory_data, user_id)
|
173
|
+
logger.debug("Processing result %d/%d", i + 1, len(results.docs))
|
174
|
+
logger.debug("Similarity score: %d", getattr(doc, 'score', 0))
|
175
|
+
|
176
|
+
# Get the full document data
|
177
|
+
full_doc = await self._client.json().get(doc.id)
|
178
|
+
logger.debug("Extracted data for result %d: %s", i + 1, full_doc)
|
179
|
+
memory_item = self._create_memory_item(dict(full_doc), user_id)
|
185
180
|
memories.append(memory_item)
|
186
|
-
logger.debug(
|
181
|
+
logger.debug("Successfully created MemoryItem for result %d", i + 1)
|
187
182
|
except Exception as e:
|
188
|
-
logger.error(
|
183
|
+
logger.error("Failed to process result %d: %s", i + 1, e)
|
189
184
|
raise
|
190
185
|
|
191
|
-
logger.debug(
|
186
|
+
logger.debug("Successfully processed all %d results", len(memories))
|
192
187
|
return memories
|
193
188
|
except redis_exceptions.ResponseError as e:
|
194
|
-
logger.error(
|
189
|
+
logger.error("Search failed with ResponseError: %s", e)
|
195
190
|
raise
|
196
191
|
except redis_exceptions.ConnectionError as e:
|
197
|
-
logger.error(
|
192
|
+
logger.error("Search failed with ConnectionError: %s", e)
|
198
193
|
raise
|
199
194
|
except Exception as e:
|
200
|
-
logger.error(
|
195
|
+
logger.error("Unexpected error during search: %s", e)
|
201
196
|
raise
|
202
197
|
|
203
198
|
def _create_memory_item(self, memory_data: dict, user_id: str) -> MemoryItem:
|
@@ -226,8 +221,8 @@ class RedisEditor(MemoryEditor):
|
|
226
221
|
if keys:
|
227
222
|
await self._client.delete(*keys)
|
228
223
|
except redis_exceptions.ResponseError as e:
|
229
|
-
logger.error(
|
224
|
+
logger.error("Failed to remove items: %s", e)
|
230
225
|
raise
|
231
226
|
except redis_exceptions.ConnectionError as e:
|
232
|
-
logger.error(
|
227
|
+
logger.error("Redis connection error while removing items: %s", e)
|
233
228
|
raise
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
import logging
|
17
|
+
|
18
|
+
import redis.asyncio as redis
|
19
|
+
|
20
|
+
from nat.data_models.object_store import KeyAlreadyExistsError
|
21
|
+
from nat.data_models.object_store import NoSuchKeyError
|
22
|
+
from nat.object_store.interfaces import ObjectStore
|
23
|
+
from nat.object_store.models import ObjectStoreItem
|
24
|
+
from nat.utils.type_utils import override
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
class RedisObjectStore(ObjectStore):
|
30
|
+
"""
|
31
|
+
Implementation of ObjectStore that stores objects in Redis.
|
32
|
+
|
33
|
+
Each object is stored as a single binary value at key "nat/object_store/{bucket_name}/{object_key}".
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self, *, bucket_name: str, host: str, port: int, db: int):
|
37
|
+
|
38
|
+
super().__init__()
|
39
|
+
|
40
|
+
self._bucket_name = bucket_name
|
41
|
+
self._host = host
|
42
|
+
self._port = port
|
43
|
+
self._db = db
|
44
|
+
self._client: redis.Redis | None = None
|
45
|
+
|
46
|
+
async def __aenter__(self) -> "RedisObjectStore":
|
47
|
+
|
48
|
+
if self._client is not None:
|
49
|
+
raise RuntimeError("Connection already established")
|
50
|
+
|
51
|
+
self._client = redis.Redis(
|
52
|
+
host=self._host,
|
53
|
+
port=self._port,
|
54
|
+
db=self._db,
|
55
|
+
socket_timeout=5.0,
|
56
|
+
socket_connect_timeout=5.0,
|
57
|
+
)
|
58
|
+
|
59
|
+
# Ping to ensure connectivity
|
60
|
+
res = await self._client.ping()
|
61
|
+
if not res:
|
62
|
+
raise RuntimeError("Failed to connect to Redis")
|
63
|
+
|
64
|
+
logger.info("Connected Redis client for %s at %s:%s/%s", self._bucket_name, self._host, self._port, self._db)
|
65
|
+
|
66
|
+
return self
|
67
|
+
|
68
|
+
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
|
69
|
+
|
70
|
+
if not self._client:
|
71
|
+
raise RuntimeError("Connection not established")
|
72
|
+
|
73
|
+
await self._client.close()
|
74
|
+
self._client = None
|
75
|
+
|
76
|
+
def _make_key(self, key: str) -> str:
|
77
|
+
return f"nat/object_store/{self._bucket_name}/{key}"
|
78
|
+
|
79
|
+
@override
|
80
|
+
async def put_object(self, key: str, item: ObjectStoreItem):
|
81
|
+
|
82
|
+
if not self._client:
|
83
|
+
raise RuntimeError("Connection not established")
|
84
|
+
|
85
|
+
full_key = self._make_key(key)
|
86
|
+
|
87
|
+
item_json = item.model_dump_json()
|
88
|
+
# Redis SET with NX ensures we do not overwrite existing keys
|
89
|
+
if not await self._client.set(full_key, item_json, nx=True):
|
90
|
+
raise KeyAlreadyExistsError(key=key,
|
91
|
+
additional_message=f"Redis bucket {self._bucket_name} already has key {key}")
|
92
|
+
|
93
|
+
@override
|
94
|
+
async def upsert_object(self, key: str, item: ObjectStoreItem):
|
95
|
+
|
96
|
+
if not self._client:
|
97
|
+
raise RuntimeError("Connection not established")
|
98
|
+
|
99
|
+
full_key = self._make_key(key)
|
100
|
+
item_json = item.model_dump_json()
|
101
|
+
await self._client.set(full_key, item_json)
|
102
|
+
|
103
|
+
@override
|
104
|
+
async def get_object(self, key: str) -> ObjectStoreItem:
|
105
|
+
|
106
|
+
if not self._client:
|
107
|
+
raise RuntimeError("Connection not established")
|
108
|
+
|
109
|
+
full_key = self._make_key(key)
|
110
|
+
data = await self._client.get(full_key)
|
111
|
+
if data is None:
|
112
|
+
raise NoSuchKeyError(key=key,
|
113
|
+
additional_message=f"Redis bucket {self._bucket_name} does not have key {key}")
|
114
|
+
return ObjectStoreItem.model_validate_json(data)
|
115
|
+
|
116
|
+
@override
|
117
|
+
async def delete_object(self, key: str):
|
118
|
+
|
119
|
+
if not self._client:
|
120
|
+
raise RuntimeError("Connection not established")
|
121
|
+
|
122
|
+
full_key = self._make_key(key)
|
123
|
+
deleted = await self._client.delete(full_key)
|
124
|
+
if deleted == 0:
|
125
|
+
raise NoSuchKeyError(key=key,
|
126
|
+
additional_message=f"Redis bucket {self._bucket_name} does not have key {key}")
|
nat/plugins/redis/register.py
CHANGED
@@ -13,10 +13,10 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
|
16
|
-
# pylint: disable=unused-import
|
17
16
|
# flake8: noqa
|
18
17
|
# isort:skip_file
|
19
18
|
|
20
19
|
# Import any providers which need to be automatically registered here
|
21
20
|
|
22
21
|
from . import memory
|
22
|
+
from . import object_store
|
nat/plugins/redis/schema.py
CHANGED
@@ -39,7 +39,7 @@ def create_schema(embedding_dim: int = DEFAULT_DIM):
|
|
39
39
|
Returns:
|
40
40
|
tuple: Schema definition for Redis search
|
41
41
|
"""
|
42
|
-
logger.info(
|
42
|
+
logger.info("Creating schema with embedding dimension: %d", embedding_dim)
|
43
43
|
|
44
44
|
embedding_field = VectorField("$.embedding",
|
45
45
|
"HNSW",
|
@@ -53,19 +53,20 @@ def create_schema(embedding_dim: int = DEFAULT_DIM):
|
|
53
53
|
"EF_RUNTIME": 10
|
54
54
|
},
|
55
55
|
as_name="embedding")
|
56
|
-
logger.info(
|
56
|
+
logger.info("Created embedding field with dimension %d", embedding_dim)
|
57
57
|
|
58
58
|
schema = (
|
59
|
+
# Redis search can't directly index complex objects (e.g. conversation and metadata) in return_fields
|
60
|
+
# They need to be retrieved via json().get() for full object access
|
59
61
|
TextField("$.user_id", as_name="user_id"),
|
60
62
|
TagField("$.tags[*]", as_name="tags"),
|
61
63
|
TextField("$.memory", as_name="memory"),
|
62
|
-
# TextField("$.conversations[*]", as_name="conversations"), # TODO: figure out if/how this should be done
|
63
64
|
embedding_field)
|
64
65
|
|
65
66
|
# Log the schema details
|
66
67
|
logger.info("Schema fields:")
|
67
68
|
for field in schema:
|
68
|
-
logger.info(
|
69
|
+
logger.info(" - %s: %s", field.name, type(field).__name__)
|
69
70
|
|
70
71
|
return schema
|
71
72
|
|
@@ -81,55 +82,55 @@ async def ensure_index_exists(client: redis.Redis, key_prefix: str, embedding_di
|
|
81
82
|
"""
|
82
83
|
try:
|
83
84
|
# Check if index exists
|
84
|
-
logger.info(
|
85
|
+
logger.info("Checking if index '%s' exists...", INDEX_NAME)
|
85
86
|
info = await client.ft(INDEX_NAME).info()
|
86
|
-
logger.info(
|
87
|
+
logger.info("Redis search index '%s' exists.", INDEX_NAME)
|
87
88
|
|
88
89
|
# Verify the schema
|
89
90
|
schema = info.get('attributes', [])
|
90
91
|
|
91
92
|
return
|
92
|
-
except redis_exceptions.ResponseError as
|
93
|
-
error_msg = str(
|
93
|
+
except redis_exceptions.ResponseError as ex:
|
94
|
+
error_msg = str(ex)
|
94
95
|
if "no such index" not in error_msg.lower() and "Index needs recreation" not in error_msg:
|
95
|
-
logger.error(
|
96
|
+
logger.error("Unexpected Redis error: %s", error_msg)
|
96
97
|
raise
|
97
98
|
|
98
99
|
# Index doesn't exist or needs recreation
|
99
|
-
logger.info(
|
100
|
+
logger.info("Creating Redis search index '%s' with prefix '%s'", INDEX_NAME, key_prefix)
|
100
101
|
|
101
102
|
# Drop any existing index
|
102
103
|
try:
|
103
|
-
logger.info(
|
104
|
+
logger.info("Attempting to drop existing index '%s' if it exists", INDEX_NAME)
|
104
105
|
await client.ft(INDEX_NAME).dropindex()
|
105
|
-
logger.info(
|
106
|
+
logger.info("Successfully dropped existing index '%s'", INDEX_NAME)
|
106
107
|
except redis_exceptions.ResponseError as e:
|
107
108
|
if "no such index" not in str(e).lower():
|
108
|
-
logger.warning(
|
109
|
+
logger.warning("Error while dropping index: %s", str(e))
|
109
110
|
|
110
111
|
# Create new schema and index
|
111
112
|
schema = create_schema(embedding_dim or DEFAULT_DIM)
|
112
|
-
logger.info(
|
113
|
+
logger.info("Created schema with embedding dimension: %d", embedding_dim or DEFAULT_DIM)
|
113
114
|
|
114
115
|
try:
|
115
116
|
# Create the index
|
116
|
-
logger.info(
|
117
|
+
logger.info("Creating new index '%s' with schema", INDEX_NAME)
|
117
118
|
await client.ft(INDEX_NAME).create_index(schema,
|
118
119
|
definition=IndexDefinition(prefix=[f"{key_prefix}:"],
|
119
120
|
index_type=IndexType.JSON))
|
120
121
|
|
121
122
|
# Verify index was created
|
122
123
|
info = await client.ft(INDEX_NAME).info()
|
123
|
-
logger.info(
|
124
|
-
logger.debug(
|
124
|
+
logger.info("Successfully created Redis search index '%s'", INDEX_NAME)
|
125
|
+
logger.debug("Redis search index info: %s", info)
|
125
126
|
|
126
127
|
# Verify the schema
|
127
128
|
schema = info.get('attributes', [])
|
128
|
-
logger.debug(
|
129
|
+
logger.debug("New index schema: %s", schema)
|
129
130
|
|
130
131
|
except redis_exceptions.ResponseError as e:
|
131
|
-
logger.error(
|
132
|
+
logger.error("Failed to create index: %s", str(e))
|
132
133
|
raise
|
133
134
|
except redis_exceptions.ConnectionError as e:
|
134
|
-
logger.error(
|
135
|
+
logger.error("Redis connection error while creating index: %s", str(e))
|
135
136
|
raise
|
@@ -1,12 +1,15 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nvidia-nat-redis
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.0rc2
|
4
4
|
Summary: Subpackage for Redis integration in NeMo Agent toolkit
|
5
5
|
Keywords: ai,agents,memory
|
6
6
|
Classifier: Programming Language :: Python
|
7
|
-
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
10
|
+
Requires-Python: <3.14,>=3.11
|
8
11
|
Description-Content-Type: text/markdown
|
9
|
-
Requires-Dist: nvidia-nat==v1.3.0-
|
12
|
+
Requires-Dist: nvidia-nat==v1.3.0-rc2
|
10
13
|
Requires-Dist: redis~=4.3.4
|
11
14
|
|
12
15
|
<!--
|
@@ -0,0 +1,13 @@
|
|
1
|
+
nat/meta/pypi.md,sha256=TpeNbVZJxzvEf0Gh3BGvLHPYsKnXjgM_KQVCayBPXso,1090
|
2
|
+
nat/plugins/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
nat/plugins/redis/memory.py,sha256=wqj_UYqnllc4kTCtFDz3eu-OZnyPWXxNZ3e6G992OGQ,2546
|
4
|
+
nat/plugins/redis/object_store.py,sha256=f_GtCTZ3KHfxE4f0lAzKaoAoInAZZki4Dfo7hrKCAHA,1681
|
5
|
+
nat/plugins/redis/redis_editor.py,sha256=nkSIWi1HUPald088fXTuF0rmZ0uS_3V65Vxy20vLSgk,9746
|
6
|
+
nat/plugins/redis/redis_object_store.py,sha256=DX46GEQl4H1Ivf2wrRaSimNcq6EfvRsm-xhyiECudoQ,4302
|
7
|
+
nat/plugins/redis/register.py,sha256=dJBKi-7W72ipkmZTOIo1E3ETffmJIlYhQTOlrkiFH3A,834
|
8
|
+
nat/plugins/redis/schema.py,sha256=Zcas3hIIqG7wuR94baYRFycmnccB3CuGmTs4p8Vv4mA,5589
|
9
|
+
nvidia_nat_redis-1.3.0rc2.dist-info/METADATA,sha256=9Tqk6EmOPRnnYz_0CVFzyrlXx4PkBrOVMUXZPAqjJN4,1577
|
10
|
+
nvidia_nat_redis-1.3.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
nvidia_nat_redis-1.3.0rc2.dist-info/entry_points.txt,sha256=nyS8t8L9CbRFIMlE70RQBtJXrflBP4Ltl5zAkIl44So,56
|
12
|
+
nvidia_nat_redis-1.3.0rc2.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
|
13
|
+
nvidia_nat_redis-1.3.0rc2.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
nat/meta/pypi.md,sha256=TpeNbVZJxzvEf0Gh3BGvLHPYsKnXjgM_KQVCayBPXso,1090
|
2
|
-
nat/plugins/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
nat/plugins/redis/memory.py,sha256=2AbG29r6tul4RVvGnSVrQnT96Hvm725i1X23zvDGWTg,2569
|
4
|
-
nat/plugins/redis/redis_editor.py,sha256=BskJN5R0h1xzz7w5Ic8IVvd4z-qdBp9ZBIpGYNiSwE8,10080
|
5
|
-
nat/plugins/redis/register.py,sha256=_ffKNKnMfkB2HzX4Nk_9EW0pwebg3GuzAE-iB-CoC3E,839
|
6
|
-
nat/plugins/redis/schema.py,sha256=lcazZKzWEoJEJmGEt6rk135zJ6kGqw6v2b1B5l1xg7o,5494
|
7
|
-
nvidia_nat_redis-1.3.0.dev2.dist-info/METADATA,sha256=D3jH06cYkjsumzqybsa-Oze-Fw25N0Hj1fRAN9KoQJo,1427
|
8
|
-
nvidia_nat_redis-1.3.0.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
-
nvidia_nat_redis-1.3.0.dev2.dist-info/entry_points.txt,sha256=nyS8t8L9CbRFIMlE70RQBtJXrflBP4Ltl5zAkIl44So,56
|
10
|
-
nvidia_nat_redis-1.3.0.dev2.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
|
11
|
-
nvidia_nat_redis-1.3.0.dev2.dist-info/RECORD,,
|
File without changes
|
{nvidia_nat_redis-1.3.0.dev2.dist-info → nvidia_nat_redis-1.3.0rc2.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|