nvidia-nat-redis 1.2.0rc5__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.
- aiq/meta/pypi.md +23 -0
- aiq/plugins/redis/__init__.py +0 -0
- aiq/plugins/redis/memory.py +57 -0
- aiq/plugins/redis/redis_editor.py +233 -0
- aiq/plugins/redis/register.py +22 -0
- aiq/plugins/redis/schema.py +135 -0
- nvidia_nat_redis-1.2.0rc5.dist-info/METADATA +34 -0
- nvidia_nat_redis-1.2.0rc5.dist-info/RECORD +11 -0
- nvidia_nat_redis-1.2.0rc5.dist-info/WHEEL +5 -0
- nvidia_nat_redis-1.2.0rc5.dist-info/entry_points.txt +2 -0
- nvidia_nat_redis-1.2.0rc5.dist-info/top_level.txt +1 -0
aiq/meta/pypi.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
<!--
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
-->
|
17
|
+
|
18
|
+

|
19
|
+
|
20
|
+
# NVIDIA NeMo Agent Toolkit Subpackage
|
21
|
+
This is a subpackage for Redis memory integration in NeMo Agent toolkit.
|
22
|
+
|
23
|
+
For more information about NeMo Agent toolkit, please visit the [NeMo Agent toolkit package](https://pypi.org/project/aiqtoolkit/).
|
File without changes
|
@@ -0,0 +1,57 @@
|
|
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 redis.asyncio as redis
|
17
|
+
from pydantic import Field
|
18
|
+
|
19
|
+
from aiq.builder.builder import Builder
|
20
|
+
from aiq.builder.framework_enum import LLMFrameworkEnum
|
21
|
+
from aiq.cli.register_workflow import register_memory
|
22
|
+
from aiq.data_models.component_ref import EmbedderRef
|
23
|
+
from aiq.data_models.memory import MemoryBaseConfig
|
24
|
+
|
25
|
+
|
26
|
+
class RedisMemoryClientConfig(MemoryBaseConfig, name="redis_memory"):
|
27
|
+
host: str | None = Field(default="localhost", description="Redis server host")
|
28
|
+
db: str | None = Field(default="0", description="Redis DB")
|
29
|
+
port: str | None = Field(default="6379", description="Redis server port")
|
30
|
+
key_prefix: str | None = Field(default="aiq", description="Key prefix to use for redis keys")
|
31
|
+
embedder: EmbedderRef = Field(description=("Instance name of the memory client instance from the workflow "
|
32
|
+
"configuration object."))
|
33
|
+
|
34
|
+
|
35
|
+
@register_memory(config_type=RedisMemoryClientConfig)
|
36
|
+
async def redis_memory_client(config: RedisMemoryClientConfig, builder: Builder):
|
37
|
+
|
38
|
+
from aiq.plugins.redis.redis_editor import RedisEditor
|
39
|
+
|
40
|
+
from .schema import ensure_index_exists
|
41
|
+
|
42
|
+
redis_client = redis.Redis(host=config.host,
|
43
|
+
port=config.port,
|
44
|
+
db=config.db,
|
45
|
+
decode_responses=True,
|
46
|
+
socket_timeout=5.0,
|
47
|
+
socket_connect_timeout=5.0)
|
48
|
+
|
49
|
+
embedder = await builder.get_embedder(config.embedder, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
|
50
|
+
|
51
|
+
test_embedding = await embedder.aembed_query("test")
|
52
|
+
embedding_dim = len(test_embedding)
|
53
|
+
await ensure_index_exists(client=redis_client, key_prefix=config.key_prefix, embedding_dim=embedding_dim)
|
54
|
+
|
55
|
+
memory_editor = RedisEditor(redis_client=redis_client, key_prefix=config.key_prefix, embedder=embedder)
|
56
|
+
|
57
|
+
yield memory_editor
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-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
|
+
import secrets
|
18
|
+
|
19
|
+
import numpy as np
|
20
|
+
import redis.asyncio as redis
|
21
|
+
import redis.exceptions as redis_exceptions
|
22
|
+
from langchain_core.embeddings import Embeddings
|
23
|
+
from redis.commands.search.query import Query
|
24
|
+
|
25
|
+
from aiq.memory.interfaces import MemoryEditor
|
26
|
+
from aiq.memory.models import MemoryItem
|
27
|
+
|
28
|
+
logger = logging.getLogger(__name__)
|
29
|
+
|
30
|
+
INDEX_NAME = "memory_idx"
|
31
|
+
|
32
|
+
|
33
|
+
class RedisEditor(MemoryEditor):
|
34
|
+
"""
|
35
|
+
Wrapper class that implements AIQ Toolkit Interfaces for Redis memory storage.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, redis_client: redis.Redis, key_prefix: str, embedder: Embeddings):
|
39
|
+
"""
|
40
|
+
Initialize Redis client for memory storage.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
redis_client: (redis.Redis) Redis client
|
44
|
+
key_prefix: (str) Redis key prefix
|
45
|
+
embedder: (Embeddings) Embedder for semantic search functionality
|
46
|
+
"""
|
47
|
+
|
48
|
+
self._client: redis.Redis = redis_client
|
49
|
+
self._key_prefix: str = key_prefix
|
50
|
+
self._embedder: Embeddings = embedder
|
51
|
+
|
52
|
+
async def add_items(self, items: list[MemoryItem]) -> None:
|
53
|
+
"""
|
54
|
+
Insert Multiple MemoryItems into Redis.
|
55
|
+
Each MemoryItem is stored with its metadata and tags.
|
56
|
+
"""
|
57
|
+
logger.debug(f"Attempting to add {len(items)} items to Redis")
|
58
|
+
|
59
|
+
for memory_item in items:
|
60
|
+
item_meta = memory_item.metadata
|
61
|
+
conversation = memory_item.conversation
|
62
|
+
user_id = memory_item.user_id
|
63
|
+
tags = memory_item.tags
|
64
|
+
memory_id = secrets.token_hex(4) # e.g. 02ba3fe9
|
65
|
+
|
66
|
+
# Create a unique key for this memory item
|
67
|
+
memory_key = f"{self._key_prefix}:memory:{memory_id}"
|
68
|
+
logger.debug(f"Generated memory key: {memory_key}")
|
69
|
+
|
70
|
+
# Prepare memory data
|
71
|
+
memory_data = {
|
72
|
+
"conversation": conversation,
|
73
|
+
"user_id": user_id,
|
74
|
+
"tags": tags,
|
75
|
+
"metadata": item_meta,
|
76
|
+
"memory": memory_item.memory or ""
|
77
|
+
}
|
78
|
+
logger.debug(f"Prepared memory data for key {memory_key}")
|
79
|
+
|
80
|
+
# If we have memory, compute and store the embedding
|
81
|
+
if memory_item.memory:
|
82
|
+
logger.debug("Computing embedding for memory text")
|
83
|
+
search_vector = await self._embedder.aembed_query(memory_item.memory)
|
84
|
+
logger.debug(f"Generated embedding vector of length: {len(search_vector)}")
|
85
|
+
memory_data["embedding"] = search_vector
|
86
|
+
|
87
|
+
try:
|
88
|
+
# Store as JSON in Redis
|
89
|
+
logger.debug(f"Attempting to store memory data in Redis for key: {memory_key}")
|
90
|
+
await self._client.json().set(memory_key, "$", memory_data)
|
91
|
+
logger.debug(f"Successfully stored memory data for key: {memory_key}")
|
92
|
+
|
93
|
+
# Verify the data was stored
|
94
|
+
stored_data = await self._client.json().get(memory_key)
|
95
|
+
logger.debug(f"Verified data storage for key {memory_key}: {bool(stored_data)}")
|
96
|
+
|
97
|
+
except redis_exceptions.ResponseError as e:
|
98
|
+
logger.error(f"Failed to store memory item: {str(e)}")
|
99
|
+
raise
|
100
|
+
except redis_exceptions.ConnectionError as e:
|
101
|
+
logger.error(f"Redis connection error while storing memory item: {str(e)}")
|
102
|
+
raise
|
103
|
+
|
104
|
+
async def search(self, query: str, top_k: int = 5, **kwargs) -> list[MemoryItem]:
|
105
|
+
"""
|
106
|
+
Retrieve items relevant to the given query.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
query (str): The query string to match.
|
110
|
+
top_k (int): Maximum number of items to return.
|
111
|
+
kwargs (dict): Keyword arguments to pass to the search method.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
list[MemoryItem]: The most relevant MemoryItems for the given query.
|
115
|
+
"""
|
116
|
+
logger.debug(f"Search called with query: {query}, top_k: {top_k}, kwargs: {kwargs}")
|
117
|
+
|
118
|
+
user_id = kwargs.get("user_id", "redis") # TODO: remove this fallback username
|
119
|
+
logger.debug(f"Using user_id: {user_id}")
|
120
|
+
|
121
|
+
# Perform vector search using Redis search
|
122
|
+
logger.debug("Using embedder for vector search")
|
123
|
+
try:
|
124
|
+
logger.debug(f"Generating embedding for query: '{query}'")
|
125
|
+
query_vector = await self._embedder.aembed_query(query)
|
126
|
+
logger.debug(f"Generated embedding vector of length: {len(query_vector)}")
|
127
|
+
except Exception as e:
|
128
|
+
logger.error(f"Failed to generate embedding: {str(e)}")
|
129
|
+
raise
|
130
|
+
|
131
|
+
# Create vector search query
|
132
|
+
search_query = (
|
133
|
+
Query(f"(@user_id:{user_id})=>[KNN {top_k} @embedding $vec AS score]").sort_by("score").return_fields(
|
134
|
+
"conversation", "user_id", "tags", "metadata", "memory", "score").dialect(2))
|
135
|
+
logger.debug(f"Created search query: {search_query}")
|
136
|
+
logger.debug(f"Query string: {search_query.query_string()}")
|
137
|
+
|
138
|
+
# Convert query vector to bytes
|
139
|
+
try:
|
140
|
+
logger.debug("Converting query vector to bytes")
|
141
|
+
query_vector_bytes = np.array(query_vector, dtype=np.float32).tobytes()
|
142
|
+
logger.debug(f"Converted vector to bytes of length: {len(query_vector_bytes)}")
|
143
|
+
except Exception as e:
|
144
|
+
logger.error(f"Failed to convert vector to bytes: {str(e)}")
|
145
|
+
raise
|
146
|
+
|
147
|
+
try:
|
148
|
+
# Execute search with vector parameters
|
149
|
+
logger.debug("Executing Redis search with vector parameters")
|
150
|
+
logger.debug(f"Search query parameters: vec length={len(query_vector_bytes)}")
|
151
|
+
|
152
|
+
# Log the actual query being executed
|
153
|
+
logger.debug(f"Full search query: {search_query.query_string()}")
|
154
|
+
|
155
|
+
# Check if there are any documents in the index
|
156
|
+
try:
|
157
|
+
total_docs = await self._client.ft(INDEX_NAME).info()
|
158
|
+
logger.debug(f"Total documents in index: {total_docs.get('num_docs', 0)}")
|
159
|
+
except Exception as e:
|
160
|
+
logger.error(f"Failed to get index info: {str(e)}")
|
161
|
+
|
162
|
+
# Execute the search
|
163
|
+
results = await self._client.ft(INDEX_NAME).search(search_query, query_params={"vec": query_vector_bytes})
|
164
|
+
|
165
|
+
# Log detailed results information
|
166
|
+
logger.debug(f"Search returned {len(results.docs)} results")
|
167
|
+
logger.debug(f"Total results found: {results.total}")
|
168
|
+
|
169
|
+
# Convert results to MemoryItems
|
170
|
+
memories = []
|
171
|
+
for i, doc in enumerate(results.docs):
|
172
|
+
try:
|
173
|
+
logger.debug(f"Processing result {i+1}/{len(results.docs)}")
|
174
|
+
# Get the document data from the correct attribute
|
175
|
+
memory_data = {
|
176
|
+
"conversation": getattr(doc, 'conversation', []),
|
177
|
+
"user_id": getattr(doc, 'user_id', user_id),
|
178
|
+
"tags": getattr(doc, 'tags', []),
|
179
|
+
"metadata": getattr(doc, 'metadata', {}),
|
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)
|
185
|
+
memories.append(memory_item)
|
186
|
+
logger.debug(f"Successfully created MemoryItem for result {i+1}")
|
187
|
+
except Exception as e:
|
188
|
+
logger.error(f"Failed to process result {i+1}: {str(e)}")
|
189
|
+
raise
|
190
|
+
|
191
|
+
logger.debug(f"Successfully processed all {len(memories)} results")
|
192
|
+
return memories
|
193
|
+
except redis_exceptions.ResponseError as e:
|
194
|
+
logger.error(f"Search failed with ResponseError: {str(e)}")
|
195
|
+
raise
|
196
|
+
except redis_exceptions.ConnectionError as e:
|
197
|
+
logger.error(f"Search failed with ConnectionError: {str(e)}")
|
198
|
+
raise
|
199
|
+
except Exception as e:
|
200
|
+
logger.error(f"Unexpected error during search: {str(e)}")
|
201
|
+
raise
|
202
|
+
|
203
|
+
def _create_memory_item(self, memory_data: dict, user_id: str) -> MemoryItem:
|
204
|
+
"""Helper method to create a MemoryItem from Redis data."""
|
205
|
+
# Ensure tags is always a list
|
206
|
+
tags = memory_data.get("tags", [])
|
207
|
+
# Not sure why but sometimes the tags are retrieved as a string
|
208
|
+
if isinstance(tags, str):
|
209
|
+
tags = [tags]
|
210
|
+
elif not isinstance(tags, list):
|
211
|
+
tags = []
|
212
|
+
|
213
|
+
return MemoryItem(conversation=memory_data.get("conversation", []),
|
214
|
+
user_id=user_id,
|
215
|
+
memory=memory_data.get("memory", ""),
|
216
|
+
tags=tags,
|
217
|
+
metadata=memory_data.get("metadata", {}))
|
218
|
+
|
219
|
+
async def remove_items(self, **kwargs):
|
220
|
+
"""
|
221
|
+
Remove memory items based on provided criteria.
|
222
|
+
"""
|
223
|
+
try:
|
224
|
+
pattern = f"{self._key_prefix}:memory:*"
|
225
|
+
keys = await self._client.keys(pattern)
|
226
|
+
if keys:
|
227
|
+
await self._client.delete(*keys)
|
228
|
+
except redis_exceptions.ResponseError as e:
|
229
|
+
logger.error(f"Failed to remove items: {str(e)}")
|
230
|
+
raise
|
231
|
+
except redis_exceptions.ConnectionError as e:
|
232
|
+
logger.error(f"Redis connection error while removing items: {str(e)}")
|
233
|
+
raise
|
@@ -0,0 +1,22 @@
|
|
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
|
+
# pylint: disable=unused-import
|
17
|
+
# flake8: noqa
|
18
|
+
# isort:skip_file
|
19
|
+
|
20
|
+
# Import any providers which need to be automatically registered here
|
21
|
+
|
22
|
+
from . import memory
|
@@ -0,0 +1,135 @@
|
|
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
|
+
import redis.exceptions as redis_exceptions
|
20
|
+
from redis.commands.search.field import TagField
|
21
|
+
from redis.commands.search.field import TextField
|
22
|
+
from redis.commands.search.field import VectorField
|
23
|
+
from redis.commands.search.indexDefinition import IndexDefinition
|
24
|
+
from redis.commands.search.indexDefinition import IndexType
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
INDEX_NAME = "memory_idx"
|
29
|
+
DEFAULT_DIM = 384 # Default embedding dimension
|
30
|
+
|
31
|
+
|
32
|
+
def create_schema(embedding_dim: int = DEFAULT_DIM):
|
33
|
+
"""
|
34
|
+
Create the Redis search schema for redis_memory.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
embedding_dim (int): Dimension of the embedding vectors
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
tuple: Schema definition for Redis search
|
41
|
+
"""
|
42
|
+
logger.info(f"Creating schema with embedding dimension: {embedding_dim}")
|
43
|
+
|
44
|
+
embedding_field = VectorField("$.embedding",
|
45
|
+
"HNSW",
|
46
|
+
{
|
47
|
+
"TYPE": "FLOAT32",
|
48
|
+
"DIM": embedding_dim,
|
49
|
+
"DISTANCE_METRIC": "L2",
|
50
|
+
"INITIAL_CAP": 100,
|
51
|
+
"M": 16,
|
52
|
+
"EF_CONSTRUCTION": 200,
|
53
|
+
"EF_RUNTIME": 10
|
54
|
+
},
|
55
|
+
as_name="embedding")
|
56
|
+
logger.info(f"Created embedding field with dimension {embedding_dim}")
|
57
|
+
|
58
|
+
schema = (
|
59
|
+
TextField("$.user_id", as_name="user_id"),
|
60
|
+
TagField("$.tags[*]", as_name="tags"),
|
61
|
+
TextField("$.memory", as_name="memory"),
|
62
|
+
# TextField("$.conversations[*]", as_name="conversations"), # TODO: figure out if/how this should be done
|
63
|
+
embedding_field)
|
64
|
+
|
65
|
+
# Log the schema details
|
66
|
+
logger.info("Schema fields:")
|
67
|
+
for field in schema:
|
68
|
+
logger.info(f" - {field.name}: {type(field).__name__}")
|
69
|
+
|
70
|
+
return schema
|
71
|
+
|
72
|
+
|
73
|
+
async def ensure_index_exists(client: redis.Redis, key_prefix: str, embedding_dim: int | None) -> None:
|
74
|
+
"""
|
75
|
+
Ensure the Redis search index exists, creating it if necessary.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
client (redis.Redis): Redis client instance
|
79
|
+
key_prefix (str): Prefix for keys to be indexed
|
80
|
+
embedding_dim (Optional[int]): Dimension of embedding vectors. If None, uses default.
|
81
|
+
"""
|
82
|
+
try:
|
83
|
+
# Check if index exists
|
84
|
+
logger.info(f"Checking if index '{INDEX_NAME}' exists...")
|
85
|
+
info = await client.ft(INDEX_NAME).info()
|
86
|
+
logger.info(f"Redis search index '{INDEX_NAME}' exists.")
|
87
|
+
|
88
|
+
# Verify the schema
|
89
|
+
schema = info.get('attributes', [])
|
90
|
+
|
91
|
+
return
|
92
|
+
except redis_exceptions.ResponseError as e:
|
93
|
+
error_msg = str(e)
|
94
|
+
if "no such index" not in error_msg.lower() and "Index needs recreation" not in error_msg:
|
95
|
+
logger.error(f"Unexpected Redis error: {error_msg}")
|
96
|
+
raise
|
97
|
+
|
98
|
+
# Index doesn't exist or needs recreation
|
99
|
+
logger.info(f"Creating Redis search index '{INDEX_NAME}' with prefix '{key_prefix}'")
|
100
|
+
|
101
|
+
# Drop any existing index
|
102
|
+
try:
|
103
|
+
logger.info(f"Attempting to drop existing index '{INDEX_NAME}' if it exists")
|
104
|
+
await client.ft(INDEX_NAME).dropindex()
|
105
|
+
logger.info(f"Successfully dropped existing index '{INDEX_NAME}'")
|
106
|
+
except redis_exceptions.ResponseError as e:
|
107
|
+
if "no such index" not in str(e).lower():
|
108
|
+
logger.warning(f"Error while dropping index: {str(e)}")
|
109
|
+
|
110
|
+
# Create new schema and index
|
111
|
+
schema = create_schema(embedding_dim or DEFAULT_DIM)
|
112
|
+
logger.info(f"Created schema with embedding dimension: {embedding_dim or DEFAULT_DIM}")
|
113
|
+
|
114
|
+
try:
|
115
|
+
# Create the index
|
116
|
+
logger.info(f"Creating new index '{INDEX_NAME}' with schema")
|
117
|
+
await client.ft(INDEX_NAME).create_index(schema,
|
118
|
+
definition=IndexDefinition(prefix=[f"{key_prefix}:"],
|
119
|
+
index_type=IndexType.JSON))
|
120
|
+
|
121
|
+
# Verify index was created
|
122
|
+
info = await client.ft(INDEX_NAME).info()
|
123
|
+
logger.info(f"Successfully created Redis search index '{INDEX_NAME}'")
|
124
|
+
logger.debug(f"Redis search index info: {info}")
|
125
|
+
|
126
|
+
# Verify the schema
|
127
|
+
schema = info.get('attributes', [])
|
128
|
+
logger.debug(f"New index schema: {schema}")
|
129
|
+
|
130
|
+
except redis_exceptions.ResponseError as e:
|
131
|
+
logger.error(f"Failed to create index: {str(e)}")
|
132
|
+
raise
|
133
|
+
except redis_exceptions.ConnectionError as e:
|
134
|
+
logger.error(f"Redis connection error while creating index: {str(e)}")
|
135
|
+
raise
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: nvidia-nat-redis
|
3
|
+
Version: 1.2.0rc5
|
4
|
+
Summary: Subpackage for Redis memory integration in AIQtoolkit
|
5
|
+
Keywords: ai,agents,memory
|
6
|
+
Classifier: Programming Language :: Python
|
7
|
+
Requires-Python: <3.13,>=3.11
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
Requires-Dist: nvidia-nat~=1.2
|
10
|
+
Requires-Dist: redis~=4.3.4
|
11
|
+
|
12
|
+
<!--
|
13
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
14
|
+
SPDX-License-Identifier: Apache-2.0
|
15
|
+
|
16
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
17
|
+
you may not use this file except in compliance with the License.
|
18
|
+
You may obtain a copy of the License at
|
19
|
+
|
20
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
21
|
+
|
22
|
+
Unless required by applicable law or agreed to in writing, software
|
23
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
24
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
25
|
+
See the License for the specific language governing permissions and
|
26
|
+
limitations under the License.
|
27
|
+
-->
|
28
|
+
|
29
|
+

|
30
|
+
|
31
|
+
# NVIDIA NeMo Agent Toolkit Subpackage
|
32
|
+
This is a subpackage for Redis memory integration in NeMo Agent toolkit.
|
33
|
+
|
34
|
+
For more information about NeMo Agent toolkit, please visit the [NeMo Agent toolkit package](https://pypi.org/project/aiqtoolkit/).
|
@@ -0,0 +1,11 @@
|
|
1
|
+
aiq/meta/pypi.md,sha256=tfhFKDhfJtxBVT6J5pnvrIdaBN_Uy5Dy6in6CCZpYpU,1101
|
2
|
+
aiq/plugins/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
aiq/plugins/redis/memory.py,sha256=rz-cbcSOe1knd795783leY4vm0n1C4UtUJtZOHmINcU,2569
|
4
|
+
aiq/plugins/redis/redis_editor.py,sha256=2hb5BGSTjWId5sb6dNAA3MLzO6WSVAU-Px-iv7Fzy2Q,10088
|
5
|
+
aiq/plugins/redis/register.py,sha256=_ffKNKnMfkB2HzX4Nk_9EW0pwebg3GuzAE-iB-CoC3E,839
|
6
|
+
aiq/plugins/redis/schema.py,sha256=lcazZKzWEoJEJmGEt6rk135zJ6kGqw6v2b1B5l1xg7o,5494
|
7
|
+
nvidia_nat_redis-1.2.0rc5.dist-info/METADATA,sha256=PrUfA8u5_hdV53lCkhonCJtaZILr-LCmAsxw7ywx5i4,1427
|
8
|
+
nvidia_nat_redis-1.2.0rc5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
9
|
+
nvidia_nat_redis-1.2.0rc5.dist-info/entry_points.txt,sha256=JyY7zDx7SZTvETPEEZuTgDVhe3EN23s1Glyp8RHIoWY,56
|
10
|
+
nvidia_nat_redis-1.2.0rc5.dist-info/top_level.txt,sha256=fo7AzYcNhZ_tRWrhGumtxwnxMew4xrT1iwouDy_f0Kc,4
|
11
|
+
nvidia_nat_redis-1.2.0rc5.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
aiq
|