needtoknow needtoknow needtoknow
  • Get started
  • Guide
    • Concepts Subjects, authorizers, filters, strategies
    • Combining and tenants Layered rules, multi-tenancy
    • Hybrid search Keyword and vector, fused
    • Security model What is guaranteed and what is assumed
    • Operations Caching, audit, tracing, failure modes
    • Production checklist Launch checks, failure modes
    • Benchmarks Recall, latency and throughput, measured
  • Integrations
    • Overview Every backend and authorizer
    • PostgreSQL + pgvector Filter inside SQL
    • Qdrant Filter as a query_filter
    • Milvus Parameterized filters
    • MongoDB Atlas Vector Search
    • Weaviate Filtered vector search
    • Redis Query Engine KNN
    • Elasticsearch, OpenSearch Filtered kNN
    • OpenFGA Relationship-based access
    • Open Policy Agent Policy as code
    • LangChain Retriever and tools
    • LlamaIndex Retriever and postprocessor
    • Haystack Retriever component
  • API
/

Guide

  • Getting started
  • Concepts
  • Combining authorizers and multi-tenancy
  • Hybrid search
  • Your own vectors
  • Reranking
  • Security model
  • Threat model
  • Revocation and consistency
  • Operations
  • Incident runbook
  • Production checklist
  • Compliance mapping
  • For security reviewers

Integrations

  • Integrations
  • PostgreSQL + pgvector
  • Chroma
  • Elasticsearch and OpenSearch
  • Qdrant
  • Milvus
  • MongoDB Atlas Vector Search
  • Weaviate
  • Redis vector search
  • LanceDB
  • Open Policy Agent
  • OpenFGA
  • SpiceDB
  • Cerbos
  • Cedar
  • Google Drive
  • LangChain
  • LlamaIndex
  • Haystack
  • MCP
  • Agent frameworks
  • FastAPI

Reference

  • API reference
  • Extending needtoknow
  • Supported configurations
  • Benchmarks
  • Changelog
  • Contributing

On this page

  • How permissions are indexed
  • Hashes written by other tools
  • Keyword search and hybrid search
Edit this page
  1. needtoknow /
  2. Redis vector search
View as Markdown Open in ChatGPT Open in Claude

Redis vector search#

pip install "needtoknow[redis]"

The backend runs a KNN query on the Redis Query Engine with the permission filter as part of the same query, so the k nearest chunks the subject may read come back however few of the index’s chunks that is. It needs Redis 8 or later, where the Query Engine is built in, or Redis Stack. CI runs needtoknow’s conformance kit against Redis 8.10.2. The same needtoknow[redis] extra serves the shared permission cache.

import redis

from needtoknow.backends.redis import RedisBackend, create_index

client = redis.Redis.from_url(REDIS_URL)
create_index(client, "chunks", dim=768)  # hashes under "needtoknow:", HNSW, cosine
backend = RedisBackend(client, "chunks", embed=embed)  # embed: str -> list[float]
backend.add(documents)

Each chunk is a hash under the prefix (needtoknow: plus the document id) with the fields needtoknow_id, content, metadata (JSON), needtoknow_terms and embedding (float32). A write is searchable as soon as it returns; replacing a chunk replaces the whole hash. ef_runtime sets HNSW’s search breadth. Scores are cosine similarity, 1 minus Redis’s cosine distance. For a native aretrieve, pass async_client=redis.asyncio.Redis(...); otherwise async calls run in a thread.

HNSW is approximate. Redis indexes a hash again whenever any of its fields changes, so set_metadata removes and re-adds the chunk’s vector in the graph, and a small graph that changes often can miss a chunk; it never returns one the filter excludes. create_index(..., algorithm="FLAT") compares the query with every vector instead: exact, and fast enough up to some tens of thousands of chunks. CI checks both, exactly on FLAT.

How permissions are indexed#

The backend uses the same type-tagged terms as Elasticsearch, OpenSearch and Milvus, so 1 and "1" stay apart and booleans, floats, nested lists and objects never match. Redis TAG queries have their own syntax (braces, |, @, *), and TAG matching ignores case by default, so each term is stored hex-encoded:

from needtoknow import In
from needtoknow.backends.redis import filter_to_query

filter_to_query(In("allowed_groups", ["hr"]))
# '@needtoknow_terms:{616c6c6f7765645f67726f7570731f733a6872}'

A hex string holds only 0-9 and a-f, so no group name or user id can add query syntax, and "HR" and "hr" stay different values. CI checks both on a real server. And becomes an intersection and Or a union of TAG conditions, and an In with no values matches nothing without a call to Redis. The metadata is stored as JSON and returned exactly as written, so the retriever’s re-check sees the true permissions.

Hashes written by other tools#

A hash without needtoknow_terms is invisible to a pre-filtered search, though post-filtering still finds it and checks its metadata. Fill the field in your ingestion pipeline:

from needtoknow.backends.redis import permission_terms

client.hset(key, mapping={"needtoknow_terms": permission_terms(metadata)})

Keyword search and hybrid search#

mode="keyword" searches the chunks’ text with BM25 under the same permission filter. The index must hold the text: create it with content_field=, which keeps every word (the index has no stop words). One index then serves both halves of hybrid search:

from needtoknow import RankFusion

create_index(client, "chunks", dim=768, content_field="content")
vector = RedisBackend(client, "chunks", embed=embed)
keyword = RedisBackend(client, "chunks", mode="keyword")  # needs no embed
retriever = PermissionedRetriever(RankFusion(vector, keyword), authorizer)

The query is split into words (letters, digits and underscores), and each word is sent as a query parameter rather than written into the query, so no query text can add syntax: the permission condition always applies. A query keeps its first 64 distinct words, and any of them may match. Scores are Redis’s BM25 (BM25STD), higher for better matches. A keyword backend without embed writes chunks with no vector, which keyword search finds and vector search does not.

An existing index can gain the text field with FT.ALTER chunks SCHEMA ADD content TEXT, and Redis indexes the stored hashes in the background. An index created by an earlier needtoknow keeps Redis’s default stop words, so common English words in a query match nothing there. CI runs the conformance kit against Redis 8.10.2 in keyword mode and fused with vector search.

Previous
Weaviate
Next
LanceDB

2026, Syed Muzakkir Hussain

Made with Sphinx and Shibuya theme.