foley.index.indexes#
Concrete vector + keyword index backends (the swappable retrieval engines).
Each backend satisfies BOTH VectorIndex and
KeywordIndex, so one object serves as both
vindex and kindex in SoundLibrary. Three
tiers, same protocol (report 04 §6.2 — “small protocols, swapped stores”):
MemoryIndex— purenumpycosine + a compact stdlib BM25. Zero persistence, zero optional-extra (just numpy). The always-available default for tests and small ephemeral libraries; it also exercises foley’s own RRF.
LanceIndex— LanceDB: one table with a vector column + native full-text index (notantivyneeded). Local dir ors3://unchanged — the recommended persistent default (foley[index]).
SqliteVecIndex— sqlite-vec (vec0) + stdlib FTS5 in one file (foley[index-sqlite]). Minimalist single-file option; **requires an interpreter whosesqlite3allows loadable extensions** (seesqlite_vec_loadable()).
All three lazy-import their heavy dependency inside methods, so importing this
module costs only the stdlib. Fusion is NOT done in-engine: knn() and
bm25() return raw ranked lists and foley.index.search fuses them via
one shared RRF, so ranking is identical across backends.
Functions
|
Build the best available persistent index for a library. |
True if |
|
True if |
Classes
|
LanceDB-backed index: one table with a vector column + a native FTS index. |
|
In-memory vector + keyword index (numpy cosine + compact BM25). |
|
Single-file index: sqlite-vec |
- class foley.index.indexes.LanceIndex(*, uri, dim, table_name='sounds')[source]#
Bases:
objectLanceDB-backed index: one table with a vector column + a native FTS index.
Writes are staged per id and flushed on the next read (or explicit
commit()), so a sound’s vector (upsert()) and text (index()) — which arrive as two separate protocol calls — are merged into one row and written as an efficient batch. Vector search is exact cosine (no ANN index is built at this tier; adding one is a scale-time optimization). Fusion is done byfoley.index.search, not by LanceDB’s native hybrid reranker, so ranking matches every other backend.One-table constraint: the vector column is mandatory, so **every indexed row needs a vector**.
SoundLibrary.addenforces this (it raises without an embedding source), so the façade path is safe; a bareindex()with no matchingupsert()stages a text-only row that stays unflushed (never keyword-searchable). For a keyword-only library with no embeddings, useMemoryIndexorSqliteVecIndex(independent vector/text tables).- property db#
The lazily-connected LanceDB database handle.
- get_vector(id)[source]#
Return the stored vector for
id(staged or persisted), elseNone.- Return type:
Optional[ndarray]
- index(id, text, meta)[source]#
Stage the searchable text for
id(flushed on the next read/commit).- Return type:
- property table#
The lazily-opened (or created-empty) LanceDB table.
- class foley.index.indexes.MemoryIndex(*, dim=None)[source]#
Bases:
objectIn-memory vector + keyword index (numpy cosine + compact BM25).
Not persistent — everything lives in dicts, lost on process exit. It exists so the full hybrid + façade path is testable and usable with only
numpy(no LanceDB, no torch), and as a genuine zero-config tier for small or ephemeral libraries. The vector and text stores are independent dicts, soupsert()andindex()never contend.- bm25(query, k, *, where=None)[source]#
Return the top-
kBM25 matches forquery(best first).A compact Okapi BM25 (
k1=1.5,b=0.75) recomputed per query — O(N) in the corpus size, which is fine for the in-memory tier’s scale.
- index(id, text, meta)[source]#
Insert or replace the searchable text (and light metadata) for
id.- Return type:
- class foley.index.indexes.SqliteVecIndex(*, path, dim)[source]#
Bases:
objectSingle-file index: sqlite-vec
vec0KNN + stdlib FTS5 keyword search.The whole index is one SQLite file behind two virtual tables (independent, so
upsert()andindex()never contend). **Requires an interpreter whosesqlite3permits loadable extensions** — probe withsqlite_vec_loadable()before constructing; the constructor raises a clear error otherwise.- bm25(query, k, *, where=None)[source]#
FTS5 BM25 search; returns
[(id, score), ...]best-first.FTS5’s
rankis more-negative-is-better; it is negated so the returned score is larger-is-better (consistent with the other backends).
- index(id, text, meta)[source]#
Insert or replace the searchable text for
idin the FTS5 table.- Return type:
- foley.index.indexes.default_index(*, data_dir, dim)[source]#
Build the best available persistent index for a library.
Degradation ladder: LanceDB (
foley[index]) → sqlite-vec (foley[index-sqlite], if loadable) → an informative error. The non-persistentMemoryIndexis never chosen automatically (a library must survive restarts); inject it explicitly for tests/ephemeral use.- Parameters:
data_dir – The library data root (a
pathlib.Path-like).dim (
int) – The embedding dimensionality from the active embedder.
- Returns:
A ready index object (both
VectorIndexandKeywordIndex).- Raises:
RuntimeError – If no persistent backend is installed/usable.
- foley.index.indexes.lancedb_available()[source]#
True if
lancedbis importable (thefoley[index]extra is present).- Return type:
- foley.index.indexes.sqlite_vec_loadable()[source]#
True if
sqlite_vecis installed AND this interpreter can load it.The macOS system / pyenv CPython builds frequently ship a
sqlite3without loadable-extension support (noenable_load_extension); on those, sqlite-vec cannot be used even when ``pip install``ed. This probes both.- Return type: