Skip to content

fix(storage): pin Redis client to RESP2 so FT.SEARCH decodes correctly#3

Merged
gzileni merged 1 commit into
mainfrom
fix/redis-resp2
Jun 11, 2026
Merged

fix(storage): pin Redis client to RESP2 so FT.SEARCH decodes correctly#3
gzileni merged 1 commit into
mainfrom
fix/redis-resp2

Conversation

@gzileni

@gzileni gzileni commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

Every /query call against a freshly-ingested index returned zero sources even when keyword and vector matches existed in the underlying Redis Stack. redis-cli FT.SEARCH … from the host returned the expected docs; the Python path through RedisVectorStore returned 0.

Root cause

Redis Stack 7.2+ negotiates RESP3 by default. On RESP3 the FT.SEARCH reply is a dict:

```python
{b'total_results': N, b'results': [...], b'attributes': [...], ...}
```

The ft().search() helper in redis-py still parses the legacy RESP2 positional list shape ([count, doc_id, doc_data, ...]). Faced with a dict it silently produces total=0, docs=[] — looks like "no matches" instead of a decode failure.

Fix

Pass protocol=2 to aioredis.from_url in RedisVectorStore.__init__. Forces the connection to RESP2 so the server replies with the positional list the helper expects. One-line change.

Verified end-to-end (against a 32-chunk ingested namespace)

Before:
```
Q='mps' -> sources=0
Q='FTSE MIB' -> sources=0
Q='borsa italiana' -> sources=0
```

After:
```
Q='mps' -> sources=5
Q='FTSE MIB' -> sources=7
Q='borsa italiana' -> sources=6
```

Same data, same Redis, same query, only difference is the protocol flag.

Test plan

  • Live verification: rebuild kg-api, run a chat that previously failed → now returns grounded answers with cited sources.
  • Add an integration test that ingests one doc and asserts at least one keyword + one vector hit (deferred — kg-api currently has no integration test harness against a live Redis).

🤖 Generated with Claude Code

Symptom: every /query call against a freshly-ingested index returned
zero sources even when keyword and vector matches existed in the
underlying Redis. Direct ``redis-cli FT.SEARCH ...`` from the host
returned the expected docs; the kg-api Python path returned 0.

Root cause: Redis Stack 7.2+ negotiates RESP3 by default. On RESP3
the FT.SEARCH reply is a dict:

  {b'total_results': N, b'results': [...], b'attributes': [...], ...}

The ``ft().search()`` helper in redis-py still parses the legacy RESP2
positional list shape (``[count, doc_id, doc_data, ...]``). Faced
with a dict it silently produces ``total=0, docs=[]`` — looks like
"no matches" instead of a decode failure.

Fix: pass ``protocol=2`` to ``aioredis.from_url`` in
``RedisVectorStore.__init__``. Forces the connection to RESP2 so the
server replies with the positional list the helper expects.

Verified end-to-end on the kairos market-news namespace:

  Q='mps'              sources=5
  Q='FTSE MIB'         sources=7
  Q='borsa italiana'   sources=6

(Previously all three returned 0 with identical data in Redis.)
@gzileni gzileni merged commit 7b87823 into main Jun 11, 2026
6 checks passed
@gzileni gzileni deleted the fix/redis-resp2 branch June 11, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant