Skip to content

Commit db9896f

Browse files
committed
fix: keep vector fields in from_existing on RESP2 unified FT.INFO replies
With redis-py 8, protocol=2 and legacy_responses=False, FT.INFO packs vector params into the field's flags list. parse_vector_attrs ignored flags, found no dims, and the caller dropped the field without a trace, so from_existing() + create() rebuilt the index without its vectors. Unpack key/value pairs from flags (skipping valueless modifiers such as INDEXMISSING), and log a warning when a vector field is skipped. Fixes redis#712
1 parent 6cb34e2 commit db9896f

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎redisvl/redis/connection.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ def normalize_index_definition(index_info: dict[str, Any]) -> dict[str, Any]:
318318
return _normalize_index_info_mapping(index_info.get("index_definition"))
319319

320320

321+
# FT.INFO field modifiers that appear in "flags" without a value.
322+
_VECTOR_VALUELESS_FLAGS = {"INDEXMISSING", "INDEXEMPTY", "SORTABLE", "NOINDEX", "UNF"}
323+
324+
321325
def normalize_index_fields(index_info: dict[str, Any]) -> list[dict[str, Any]]:
322326
"""Return FT.INFO field entries in mapping form."""
323327
return [
@@ -373,6 +377,18 @@ def parse_vector_attrs(attrs):
373377
for key, value in attrs.items()
374378
if key not in {"identifier", "attribute", "type", "flags"}
375379
}
380+
# redis-py 8 on RESP2 with legacy_responses=False puts the vector
381+
# params in "flags" as key/value pairs, possibly followed by
382+
# valueless modifiers: ["algorithm", "FLAT", "dim", 4, "INDEXMISSING"]
383+
flags = attrs.get("flags") or []
384+
i = 0
385+
while i < len(flags):
386+
key = str(flags[i])
387+
if key.upper() in _VECTOR_VALUELESS_FLAGS or i + 1 >= len(flags):
388+
i += 1
389+
continue
390+
vector_attrs.setdefault(key.lower(), flags[i + 1])
391+
i += 2
376392
else:
377393
# Check if we have any attributes beyond the type declaration
378394
if len(attrs) <= 6:
@@ -604,6 +620,12 @@ def parse_attrs(attrs, field_type=None):
604620
if attrs is None:
605621
# Vector field attributes cannot be parsed on this Redis version
606622
# Skip this field - it cannot be properly reconstructed
623+
logger.warning(
624+
"Skipping vector field %r of index %r: could not parse its "
625+
"attributes from FT.INFO",
626+
name,
627+
index_name,
628+
)
607629
continue
608630
field["attrs"] = attrs
609631
else:

‎tests/unit/test_convert_index_info.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,67 @@ def test_convert_index_info_default_stopwords():
241241

242242
assert result["index"]["name"] == "test_default_stopwords"
243243
assert "stopwords" not in result["index"] # Should not be present
244+
245+
246+
def test_convert_index_info_resp2_unified_vector_flags():
247+
"""Vector params packed into ``flags`` (redis-py 8, RESP2, legacy_responses=False)."""
248+
index_info = {
249+
"index_name": "test_index",
250+
"index_definition": {"key_type": "HASH", "prefixes": ["doc"]},
251+
"attributes": [
252+
{
253+
"identifier": "emb",
254+
"attribute": "emb",
255+
"type": "VECTOR",
256+
"flags": [
257+
"algorithm",
258+
"HNSW",
259+
"data_type",
260+
"FLOAT32",
261+
"dim",
262+
8,
263+
"distance_metric",
264+
"L2",
265+
"M",
266+
24,
267+
"ef_construction",
268+
300,
269+
"INDEXMISSING",
270+
],
271+
}
272+
],
273+
}
274+
275+
result = convert_index_info_to_schema(index_info)
276+
277+
assert result["fields"] == [
278+
{
279+
"name": "emb",
280+
"type": "vector",
281+
"attrs": {
282+
"dims": 8,
283+
"distance_metric": "l2",
284+
"algorithm": "hnsw",
285+
"datatype": "float32",
286+
"m": 24,
287+
"ef_construction": 300,
288+
},
289+
}
290+
]
291+
292+
293+
def test_convert_index_info_warns_on_unparseable_vector_field(caplog):
294+
"""A vector field without params is skipped with a warning, not silently."""
295+
index_info = {
296+
"index_name": "test_index",
297+
"index_definition": {"key_type": "HASH", "prefixes": ["doc"]},
298+
"attributes": [
299+
{"identifier": "emb", "attribute": "emb", "type": "VECTOR", "flags": []}
300+
],
301+
}
302+
303+
with caplog.at_level("WARNING"):
304+
result = convert_index_info_to_schema(index_info)
305+
306+
assert result["fields"] == []
307+
assert "'emb'" in caplog.text and "'test_index'" in caplog.text

0 commit comments

Comments
 (0)