scrydb
scrydb's purpose is making lexical, dense, and hybrid search possible with SQLite. Hardware requirements are kept low and everything is self-contained in a single file, i.e., the raw documents, their embeddings, and the lexical index are stored in a single SQLite file.
- Lexical search is made possible by the FTS5 extension for SQLite.
- Semantic search over the entire index is made possible by
sqlite-vec, a small, dependency-free vector search extension for SQLite. Every embedding can be searched at three precisions: binary (1 bit/dim, Hamming distance), int8 (1 byte/dim, cosine), and float (full precision, cosine) — trading index size and speed against ranking quality, and combinable as a two-stage rerank (e.g. binary-first >> float-precision rerank). - Hybrid search relies on Reciprocal Rank Fusion to fuse lexical and semantic search results.
The library is compatible with Sentence Transformers. However, it is also possible to store precomputed embeddings for both queries and documents.
[!NOTE]
The evaluation protocol, benchmark results, and examples are available atscrydb-eval, the corresponding data is shared on Hugging Face.
Usage examples
scrydb can be used interactively as follows:
from scrydb import Index, SentenceEmbedding
with Index.open("idx.db") as index:
index.add_model(SentenceEmbedding())
index.index_documents("corpus.jsonl", id_field="docid", text_field="text")
results = index.search("some query", mode="hybrid", rerank=True)
Batch search for Information Retrieval benchmarks with precomputed embeddings can be run as follows:
import scrydb
idx = scrydb.Index.open("./path/to/index.db")
idx.index_documents(
source="./path/to/corpus.jsonl",
id_field="docid",
text_field="text",
embedding_field="embedding",
store_int8_embeddings=True, # opt in to int8 storage alongside binary/float
)
idx.index_queries(
source="./path/to/queries.jsonl",
id_field="qid",
text_field="text",
embedding_field="embedding",
store_int8_embeddings=True,
)
idx.batch_search(mode="lexical").write_trec("./path/to/lexical/run")
idx.batch_search(mode="semantic", precision="binary").write_trec("./path/to/binary/run")
idx.batch_search(mode="semantic", precision="int8").write_trec("./path/to/int8/run")
idx.batch_search(mode="semantic", precision="float").write_trec("./path/to/float/run")
idx.batch_search(mode="semantic", precision="binary", rerank="float").write_trec("./path/to/binary-rerank-float/run")
idx.batch_search(mode="hybrid").write_trec("./path/to/hybrid/run")
Search modes, precision, and rerank
search()/batch_search() take three orthogonal knobs:
mode—"lexical"(BM25 over FTS5),"semantic"(vector search), or"hybrid"(Reciprocal Rank Fusion of both).precision— which vector representationmode="semantic"/the semantic side ofmode="hybrid"ranks with:"binary"(default),"int8", or"float".rerank—False(default), or a second-stage rerank over the top candidates frommode, at"binary","int8", or"float"precision (Trueis a synonym for"float").
idx.search("some query", mode="lexical") # BM25
idx.search("some query", mode="lexical", rerank="float") # BM25 >> Float/Cosine
idx.search("some query", mode="semantic", precision="binary") # Binary/Hamming
idx.search("some query", mode="semantic", precision="int8") # Int8/Cosine
idx.search("some query", mode="semantic", precision="binary", rerank="float") # Binary >> Float/Cosine
idx.search("some query", mode="hybrid", rerank=True) # Hybrid/RRF
Installing
pip install scrydb
Or with uv (faster, and manages the virtualenv for you):
uv venv && uv pip install scrydb
# or, inside a uv-managed project:
uv add scrydb
No C compiler or SQLite development headers required: vector search is
powered by sqlite-vec, a pure
pip dependency that ships prebuilt binaries, so pip install scrydb is a
plain, fast, wheel-only install.
Index.open() loads the sqlite-vec extension automatically. This
requires a Python build whose sqlite3 module supports
enable_load_extension() — true for Homebrew, pyenv, and
uv-managed builds on macOS, virtually all
Linux distro packages, and the official Windows builds, but not for
macOS Pythons that link against Apple's SQLite, which disables extension
loading. That includes macOS's system Python and the CPython that
actions/setup-python installs on GitHub Actions runners. If you hit a
RuntimeError mentioning enable_load_extension, switch to Python from
Homebrew (brew install python), uv (uv python install <version>), or
pyenv (PYTHON_CONFIGURE_OPTS='--enable-loadable-sqlite-extensions' pyenv install <version>).
If you'd rather not load the extension at all, you can still install and use scrydb for lexical (BM25) search only — just disable it explicitly:
Index.open("idx.db", vec_ext_path=None)
Try the CLI without installing (uvx)
uvx runs the scrydb command-line
tool (index/search/batch-search/auto — see the Docker
section below for the full reference) in a throwaway environment, no venv or
persistent install needed:
uvx scrydb index --documents corpus.jsonl --queries queries.jsonl --db idx.db
uvx scrydb search "some query" --db idx.db --mode hybrid --rerank float
uvx scrydb batch-search --db idx.db --mode hybrid --output run.trec
Since scrydb has no compiled artifacts of its own, uvx just downloads the
wheel and its dependencies (including sqlite-vec's prebuilt binary) into
its cache — no build step at all.
Docker
No local Python install needed: the Dockerfile builds a
Linux image with scrydb already installed, driven by a bundled scrydb
CLI. Everything it reads and writes -- input JSONL, the SQLite index, TREC
run files -- lives under /data, so bind-mount a host directory there.
docker build -t scrydb .
Drop documents.jsonl (and, optionally, queries.jsonl) into ./data and
run the image with no arguments: it indexes whatever's present into
./data/index.db, then either batch-searches the stored queries into
./data/run.trec or -- if there's nothing to index and no stored queries --
tells you what it's waiting for.
docker run --rm -v "$PWD/data":/data scrydb
If ./data/index.db already exists (say, you built it locally, or a
previous run produced it), the same command re-uses it: skips indexing
whatever source files aren't present and searches straight away. To query
an existing index ad hoc instead of running a full batch, override the
default command:
docker run --rm -v "$PWD/data":/data scrydb search "some query" --mode hybrid --rerank float
Every option is also settable as an SCRYDB_* environment variable (handy
for docker run -e), and the JSONL id-field names for documents/queries
default to docid/qid-style overrides when they differ from id:
docker run --rm -v "$PWD/data":/data \
-e SCRYDB_DOC_ID_FIELD=docid -e SCRYDB_QUERY_ID_FIELD=qid \
-e SCRYDB_MODEL=mixedbread-ai/mxbai-embed-large-v1 \
-e SCRYDB_MODE=hybrid -e SCRYDB_RERANK=float \
scrydb
Run docker run --rm scrydb --help (or ... <subcommand> --help) for the
full index/search/batch-search/auto reference, or see the
module docstring in src/scrydb/cli.py.
Notes:
- Dense/hybrid search (
sentence-transformers) isn't in the image by default -- build with--build-arg EXTRAS=all(ordense) to add it. This installs the CPU-onlytorchbuild so the image doesn't pull in multi-gigabyte CUDA packages it can't use. - Multi-platform: build once for both Intel/AMD and Apple
Silicon/ARM hosts (each running natively, no QEMU emulation) with
docker buildx build --platform linux/amd64,linux/arm64 -t scrydb . - Plain Python access:
docker run --rm -it -v "$PWD/data":/data --entrypoint python3 scrydbdrops into an interpreter withscrydbimportable.
How extension discovery works at runtime
Index.open()/Index() default to vec_ext_path="auto", which loads
sqlite_vec.loadable_path() — the copy of the extension bundled inside
the installed sqlite-vec pip package, prebuilt for the current
platform. Pass an explicit path to load a different build (e.g. a newer
vec0 release), or None to skip loading it entirely.
Development / editable installs
git clone <repo>
cd scrydb
python -m venv .venv && source .venv/bin/activate
pip install -e ".[all]"
pytest
Or with uv:
git clone <repo>
cd scrydb
uv venv
uv pip install -e ".[all]"
uv run pytest
uv run picks up .venv automatically, so there's no source .venv/bin/activate
step — any command after it (uv run pytest, uv run python -m scrydb.cli --help,
uv run python your_script.py) runs inside the project's venv.
Optional extras
pip install "scrydb[dense]"/uv pip install "scrydb[dense]"— dense/hybrid search viasentence-transformerspip install "scrydb[eval]"/uv pip install "scrydb[eval]"—Run.to_dataframe()viapandaspip install "scrydb[all]"/uv pip install "scrydb[all]"— both
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scrydb-0.2.0.tar.gz.
File metadata
- Download URL: scrydb-0.2.0.tar.gz
- Upload date:
- Size: 30.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1e999161e09f05fb47a2cc985e2e748c3276a1f3a92b67677abea34996f2e76
|
|
| MD5 |
98d45fef496eb58e3c1e7c79d6967a0d
|
|
| BLAKE2b-256 |
4897825807697eb717cea553d0267cf35cbc5182ff3fccbe036547b7c131e572
|
Provenance
The following attestation bundles were made for scrydb-0.2.0.tar.gz:
Publisher:
release.yml on breuert/scrydb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrydb-0.2.0.tar.gz -
Subject digest:
b1e999161e09f05fb47a2cc985e2e748c3276a1f3a92b67677abea34996f2e76 - Sigstore transparency entry: 2582696066
- Sigstore integration time:
-
Permalink:
breuert/scrydb@bb8949cd447cd8f406bc886ef56ffbaf1c3ee3e4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/breuert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bb8949cd447cd8f406bc886ef56ffbaf1c3ee3e4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file scrydb-0.2.0-py3-none-any.whl.
File metadata
- Download URL: scrydb-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9eb2c393fad056ee60ed0077d82b6e5727ecac1f5d56af1bd20fcb9e90faa63f
|
|
| MD5 |
da1a10da63ccc2760386b89b64934a90
|
|
| BLAKE2b-256 |
088ac9013dac9105faaf73a37509648801b6dea161214acb63042af3d345e0d8
|
Provenance
The following attestation bundles were made for scrydb-0.2.0-py3-none-any.whl:
Publisher:
release.yml on breuert/scrydb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrydb-0.2.0-py3-none-any.whl -
Subject digest:
9eb2c393fad056ee60ed0077d82b6e5727ecac1f5d56af1bd20fcb9e90faa63f - Sigstore transparency entry: 2582696071
- Sigstore integration time:
-
Permalink:
breuert/scrydb@bb8949cd447cd8f406bc886ef56ffbaf1c3ee3e4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/breuert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@bb8949cd447cd8f406bc886ef56ffbaf1c3ee3e4 -
Trigger Event:
push
-
Statement type: