Scalable text search over Qdrant: lexical-gated dense + server-side sparse scoring - #125
Conversation
…parse scoring, mode selector, text-index command
…ion path; bm25 source umbrella; synthesize lexical init; doctor live-readiness; async/set_payload sparse limits documented; strict empty gate
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc2e8ac071
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…nse vectors under sparse mode; CLI launchers forward text_search; deterministic gate tokens; tenant-aware qdrant_bm25
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a97efdb343
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ctor too); native ids from qdrant bm25 corpus; synthesize needs no embeddings for payload-only modes; sparse gets the same preflight
Scalable text search over Qdrant
The lexical arm of hybrid recall no longer has to live in client memory. The in-process BM25 is honest about its ceiling (<100K documents, corpus in RAM) — this PR moves lexical matching into Qdrant itself, next to the vectors, at any collection size, selectable by one config switch:
The two new arms
lexical— lexical-gated dense (QdrantTextRetriever): the query's salient tokens (exact technical tokens first — IDs, IPs, versions — then content words) become a server-side full-textMatchTextgate, dense similarity ranks within the lexically-matching subset. Works on a pre-existing collection without reindexing; the one server prerequisite is a full-text payload index (mnemostack text-index, idempotent/non-destructive). The gate filters, it does not score.sparse— server-side lexical scoring (QdrantSparseRetriever): writes maintain a dependency-free sparse tf encoding of each chunk (SparseTextEncoder, CRC-32 token hashing) in a named sparse space with Qdrant's IDF modifier — queries rank by a tf·idf-like score computed on the server, no client-side corpus. New collections indexed under this mode get the space automatically; populated collections migrate viamnemostack sparse-backfill(scroll + update_vectors: nothing re-embedded, idempotent) — enabling sparse on a populated collection without the encodings refuses loudly rather than silently serving partial recall.Selector semantics
auto(default) keeps historical behavior byte-for-byte: file-corpus BM25 whenbm25_pathsis set, else no lexical arm.qdrant_bm25wires the existing payload-scrolled BM25 corpus as a first-class option for sub-100K collections.--source bm25selector is an umbrella for the configured lexical arm (whatever mode is active answers to it) — on the CLI, insynthesize(), and in result filtering.synthesize);doctorlive-checks the configured arm's readiness (the sparse space / the full-text index on the actual collection), not just the config string.Boundaries stated plainly
text_key/timestamp_key/timestamp_format), including schema declaration for the Recaller's per-field derivation.VectorStoreonly (AsyncVectorStoredoes not write sparse encodings — documented);set_payloadtext edits leave the sparse encoding stale until re-upsert/backfill (documented).VectorStore.searchgainstext_any=(nested should-gate composed with tenant/validity filters; an explicitly empty gate raises rather than degrade to match-all) andsparse_search().qdrant-clientis now>=1.10(sparse models + IDF modifier).Tests / docs
tests/test_text_search.py(23 tests): encoder shapes, sparse roundtrip/ranking/tenant boundary, lexical gate composition, both retrievers incl. foreign schema + gate-token quality, mode resolution/env/doctor, CLI wiring for all three Qdrant modes, indexing-store sparse flag, dense→sparse migration (refusal + backfill coverage),--source bm25umbrella throughsynthesize().docs/deployment.mdgains a lexical-arm scaling table + migration notes;docs/api-stability.mddocuments the mode contract.Full suite: 1415 passed / 9 skipped; ruff + mypy clean.