Skip to content

Commit 73954f8

Browse files
author
Nitin Kanukolanu
committed
Updates: svs-vamana version capability validation
1 parent 6ad0439 commit 73954f8

File tree

4 files changed

+229
-1
lines changed

4 files changed

+229
-1
lines changed

redisvl/exceptions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,29 @@ class QueryValidationError(RedisVLError):
3131
"""Error when validating a query."""
3232

3333
pass
34+
35+
36+
class RedisModuleVersionError(RedisVLError):
37+
"""Error when Redis or module versions are incompatible with requested features."""
38+
39+
@classmethod
40+
def for_svs_vamana(cls, capabilities, min_redis_version: str):
41+
"""Create error for unsupported SVS-VAMANA.
42+
43+
Args:
44+
capabilities: VectorSupport instance with version info
45+
min_redis_version: Minimum required Redis version
46+
47+
Returns:
48+
RedisModuleVersionError with formatted message
49+
"""
50+
message = (
51+
f"SVS-VAMANA requires Redis >= {min_redis_version} with RediSearch >= 2.8.10. "
52+
f"Current: Redis {capabilities.redis_version}, "
53+
f"RediSearch {capabilities.search_version_str}, "
54+
f"SearchLight {capabilities.searchlight_version_str}. "
55+
f"Options: 1) Upgrade Redis Stack, "
56+
f"2) Use algorithm='hnsw' or 'flat', "
57+
f"3) Remove compression parameters"
58+
)
59+
return cls(message)

redisvl/index/index.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767

6868
from redisvl.exceptions import (
6969
QueryValidationError,
70+
RedisModuleVersionError,
7071
RedisSearchError,
7172
RedisVLError,
7273
SchemaValidationError,
@@ -82,11 +83,15 @@
8283
from redisvl.query.filter import FilterExpression
8384
from redisvl.redis.connection import (
8485
RedisConnectionFactory,
86+
check_vector_capabilities,
87+
check_vector_capabilities_async,
8588
convert_index_info_to_schema,
8689
)
90+
from redisvl.redis.constants import SVS_MIN_REDIS_VERSION
8791
from redisvl.schema import IndexSchema, StorageType
8892
from redisvl.schema.fields import (
8993
VECTOR_NORM_MAP,
94+
SVSVectorField,
9095
VectorDistanceMetric,
9196
VectorIndexAlgorithm,
9297
)
@@ -228,6 +233,12 @@ def _storage(self) -> BaseStorage:
228233
index_schema=self.schema
229234
)
230235

236+
def _uses_svs_vamana(self) -> bool:
237+
"""Check if schema contains any SVS-VAMANA vector fields."""
238+
return any(
239+
isinstance(field, SVSVectorField) for field in self.schema.fields.values()
240+
)
241+
231242
def _validate_query(self, query: BaseQuery) -> None:
232243
"""Validate a query."""
233244
if isinstance(query, VectorQuery):
@@ -535,6 +546,17 @@ def set_client(self, redis_client: SyncRedisClient, **kwargs):
535546
self.__redis_client = redis_client
536547
return self
537548

549+
def _check_svs_support(self) -> None:
550+
"""Validate SVS-VAMANA support.
551+
552+
Raises:
553+
RedisModuleVersionError: If SVS-VAMANA requirements are not met.
554+
"""
555+
caps = check_vector_capabilities(self._redis_client)
556+
557+
if not caps.svs_vamana_supported:
558+
raise RedisModuleVersionError.for_svs_vamana(caps, SVS_MIN_REDIS_VERSION)
559+
538560
def create(self, overwrite: bool = False, drop: bool = False) -> None:
539561
"""Create an index in Redis with the current schema and properties.
540562
@@ -566,6 +588,10 @@ def create(self, overwrite: bool = False, drop: bool = False) -> None:
566588
if not isinstance(overwrite, bool):
567589
raise TypeError("overwrite must be of type bool")
568590

591+
# Check if schema uses SVS-VAMANA and validate Redis capabilities
592+
if self._uses_svs_vamana():
593+
self._check_svs_support()
594+
569595
if self.exists():
570596
if not overwrite:
571597
logger.info("Index already exists, not overwriting.")
@@ -1302,6 +1328,18 @@ async def _info(name: str, redis_client: AsyncRedisClient) -> Dict[str, Any]:
13021328
f"Error while fetching {name} index info: {str(e)}"
13031329
) from e
13041330

1331+
async def _check_svs_support_async(self) -> None:
1332+
"""Validate SVS-VAMANA support.
1333+
1334+
Raises:
1335+
RedisModuleVersionError: If SVS-VAMANA requirements are not met.
1336+
"""
1337+
client = await self._get_client()
1338+
caps = await check_vector_capabilities_async(client)
1339+
1340+
if not caps.svs_vamana_supported:
1341+
raise RedisModuleVersionError.for_svs_vamana(caps, SVS_MIN_REDIS_VERSION)
1342+
13051343
async def create(self, overwrite: bool = False, drop: bool = False) -> None:
13061344
"""Asynchronously create an index in Redis with the current schema
13071345
and properties.
@@ -1335,6 +1373,10 @@ async def create(self, overwrite: bool = False, drop: bool = False) -> None:
13351373
if not isinstance(overwrite, bool):
13361374
raise TypeError("overwrite must be of type bool")
13371375

1376+
# Check if schema uses SVS-VAMANA and validate Redis capabilities
1377+
if self._uses_svs_vamana():
1378+
await self._check_svs_support_async()
1379+
13381380
if await self.exists():
13391381
if not overwrite:
13401382
logger.info("Index already exists, not overwriting.")

redisvl/redis/connection.py

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from dataclasses import dataclass
23
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload
34
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
45
from warnings import warn
@@ -15,7 +16,11 @@
1516
from redis.sentinel import Sentinel
1617

1718
from redisvl import __version__
18-
from redisvl.redis.constants import REDIS_URL_ENV_VAR
19+
from redisvl.redis.constants import (
20+
REDIS_URL_ENV_VAR,
21+
SVS_MIN_REDIS_VERSION,
22+
SVS_REQUIRED_MODULES,
23+
)
1924
from redisvl.redis.utils import convert_bytes, is_cluster_url
2025
from redisvl.types import AsyncRedisClient, RedisClient, SyncRedisClient
2126
from redisvl.utils.utils import deprecated_function
@@ -101,6 +106,152 @@ def unpack_redis_modules(module_list: List[Dict[str, Any]]) -> Dict[str, Any]:
101106
return {module["name"]: module["ver"] for module in module_list}
102107

103108

109+
@dataclass
110+
class VectorSupport:
111+
"""Redis server capabilities for vector operations."""
112+
113+
redis_version: str
114+
search_version: int
115+
searchlight_version: int
116+
svs_vamana_supported: bool
117+
118+
@property
119+
def search_version_str(self) -> str:
120+
"""Format search module version as string."""
121+
return format_module_version(self.search_version)
122+
123+
@property
124+
def searchlight_version_str(self) -> str:
125+
"""Format searchlight module version as string."""
126+
return format_module_version(self.searchlight_version)
127+
128+
129+
def format_module_version(version: int) -> str:
130+
"""Format module version from integer (20810) to string (2.8.10)."""
131+
if version == 0:
132+
return "not installed"
133+
major = version // 10000
134+
minor = (version % 10000) // 100
135+
patch = version % 100
136+
return f"{major}.{minor}.{patch}"
137+
138+
139+
def check_vector_capabilities(client: SyncRedisClient) -> VectorSupport:
140+
"""Check Redis server capabilities for vector features.
141+
142+
Args:
143+
client: Sync Redis client instance
144+
145+
Returns:
146+
VectorSupport with version info and supported features
147+
"""
148+
info = client.info("server")
149+
redis_version = info.get("redis_version", "0.0.0")
150+
151+
modules = RedisConnectionFactory.get_modules(client)
152+
search_ver = modules.get("search", 0)
153+
searchlight_ver = modules.get("searchlight", 0)
154+
155+
# Check if SVS-VAMANA requirements are met
156+
redis_ok = compare_versions(redis_version, SVS_MIN_REDIS_VERSION)
157+
modules_ok = search_ver >= 20810 or searchlight_ver >= 20810
158+
159+
return VectorSupport(
160+
redis_version=redis_version,
161+
search_version=search_ver,
162+
searchlight_version=searchlight_ver,
163+
svs_vamana_supported=redis_ok and modules_ok,
164+
)
165+
166+
167+
async def check_vector_capabilities_async(client: AsyncRedisClient) -> VectorSupport:
168+
"""Async version of check_vector_capabilities.
169+
170+
Args:
171+
client: Async Redis client instance
172+
173+
Returns:
174+
VectorSupport with version info and supported features
175+
"""
176+
info = await client.info("server")
177+
redis_version = info.get("redis_version", "0.0.0")
178+
179+
modules = await RedisConnectionFactory.get_modules_async(client)
180+
search_ver = modules.get("search", 0)
181+
searchlight_ver = modules.get("searchlight", 0)
182+
183+
# Check if SVS-VAMANA requirements are met
184+
redis_ok = compare_versions(redis_version, SVS_MIN_REDIS_VERSION)
185+
modules_ok = search_ver >= 20810 or searchlight_ver >= 20810
186+
187+
return VectorSupport(
188+
redis_version=redis_version,
189+
search_version=search_ver,
190+
searchlight_version=searchlight_ver,
191+
svs_vamana_supported=redis_ok and modules_ok,
192+
)
193+
194+
195+
def supports_svs_vamana(client: SyncRedisClient) -> bool:
196+
"""Check if Redis server supports SVS-VAMANA algorithm.
197+
198+
SVS-Vamana requires:
199+
- Redis version >= 8.2.0
200+
- RediSearch version >= 2.8.10 (20810)
201+
202+
Args:
203+
client: Sync Redis client instance
204+
205+
Returns:
206+
bool: True if SVS-VAMANA is supported, False otherwise
207+
"""
208+
info = client.info()
209+
redis_version = info.get("redis_version", "0.0.0")
210+
if not compare_versions(redis_version, SVS_MIN_REDIS_VERSION):
211+
return False
212+
213+
# Check module versions
214+
modules = unpack_redis_modules(convert_bytes(client.module_list()))
215+
for module in SVS_REQUIRED_MODULES:
216+
module_name = module["name"]
217+
required_version = module["ver"]
218+
if module_name not in modules:
219+
return False
220+
if modules[module_name] < required_version:
221+
return False
222+
return True
223+
224+
225+
async def async_supports_svs_vamana(client: AsyncRedisClient) -> bool:
226+
"""Check if Redis server supports SVS-VAMANA algorithm asynchronously.
227+
228+
SVS-Vamana requires:
229+
- Redis version >= 8.2.0
230+
- RediSearch version >= 2.8.10 (20810)
231+
232+
Args:
233+
client: Sync Redis client instance
234+
235+
Returns:
236+
bool: True if SVS-VAMANA is supported, False otherwise
237+
"""
238+
info = await client.info()
239+
redis_version = info.get("redis_version", "0.0.0")
240+
if not compare_versions(redis_version, SVS_MIN_REDIS_VERSION):
241+
return False
242+
243+
# Check module versions
244+
modules = unpack_redis_modules(convert_bytes(await client.module_list()))
245+
for module in SVS_REQUIRED_MODULES:
246+
module_name = module["name"]
247+
required_version = module["ver"]
248+
if module_name not in modules:
249+
return False
250+
if modules[module_name] < required_version:
251+
return False
252+
return True
253+
254+
104255
def get_address_from_env() -> str:
105256
"""Get Redis URL from environment variable."""
106257
redis_url = os.getenv(REDIS_URL_ENV_VAR)

redisvl/redis/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
{"name": "searchlight", "ver": 20600},
55
]
66

7+
# SVS-VAMANA requires Redis 8.2+ with RediSearch 2.8.10+
8+
SVS_REQUIRED_MODULES = [
9+
{"name": "search", "ver": 20810}, # RediSearch 2.8.10+
10+
{"name": "searchlight", "ver": 20810},
11+
]
12+
13+
# Minimum Redis version for SVS-VAMANA
14+
SVS_MIN_REDIS_VERSION = "8.2.0"
15+
716
# default tag separator
817
REDIS_TAG_SEPARATOR = ","
918

0 commit comments

Comments
 (0)