Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Local semantic search trial

examples/semantic_search.plenty is a native Plenty CLI for a separate SQLite search database. It uses sqlite-vec for exact cosine-distance search and built-in FTS5 for keyword search, combining their top 30 candidates with reciprocal rank fusion and returning up to ten chunks. All parsing, validation, hashing, database operations, transactions and ranking are driven by Plenty. Only model inference runs in Python, through scripts/search_embed.py and FastEmbed. Plenty owns the CLI, chunking and document lifecycle.

For the repository corpus, use the setup and synchronization wrapper, which scans documentation, source docs and the local issue register and removes deleted documents. This page covers the underlying interface and individual document operations. No generated database or model is committed.

Setup

Use the compiler built from this checkout and the pinned sqlite-vec 0.1.9 shared library for x86-64 Linux. One way to obtain the extension is its Python wheel. Install FastEmbed in the same environment:

uv venv /tmp/plenty-search-env
uv pip install --python /tmp/plenty-search-env/bin/python sqlite-vec==0.1.9 fastembed==0.9.0
export PLENTY_SQLITE_VEC="$(/tmp/plenty-search-env/bin/python -c 'import sqlite_vec; print(sqlite_vec.loadable_path())')"
cargo run --locked --bin plenty -- --compile examples/semantic_search.plenty -o /tmp/plenty-search

The executable loads that library through Connection.load_extension. Use only a trusted extension. SQLite’s SQL loading function remains disabled. Storage-only commands do not need a Python process.

Search document text

Run these commands from the checkout root. Setup downloads the pinned BAAI/bge-small-en-v1.5 quantized ONNX model (about 67 MB); inference thereafter uses only the local cache and CPU. The helper pins the model artifact revision, uses 384-dimensional vectors, and refuses input beyond its 512-token limit.

export SEARCH_PYTHON=/tmp/plenty-search-env/bin/python
export SEARCH_CACHE=/tmp/plenty-search-model-cache
"$SEARCH_PYTHON" scripts/search_embed.py --setup "$SEARCH_CACHE"
export SEARCH_MODEL="$("$SEARCH_PYTHON" scripts/search_embed.py --identity)"
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search.db "$SEARCH_MODEL" init 384
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search.db "$SEARCH_MODEL" index docs:sqlite SQLite book/src/design/stdlib/sqlite.md "$SEARCH_PYTHON" scripts/search_embed.py "$SEARCH_CACHE"
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search.db "$SEARCH_MODEL" find "How do I undo changes when a transaction fails?" "$SEARCH_PYTHON" scripts/search_embed.py "$SEARCH_CACHE"

index ID TITLE TEXT_FILE PYTHON SCRIPT CACHE reads one UTF-8 text document. Plenty hashes its title and text before inference; unchanged input returns immediately. Changed documents are split into 400-character windows with 64-character overlap, then embedded in one subprocess invocation. This simple trial chunker can split sentences and Markdown structures; corpus-aware chunking remains follow-on work. Empty files keep a document with zero chunks. All replacement happens in one transaction after successful inference; a failed subprocess, invalid response or wrong vector dimension leaves the old document.

find QUERY PYTHON SCRIPT CACHE embeds the query and runs hybrid search. The process receives JSON text and returns JSON vectors; it never opens a database or reads source files. There is no shell interpolation of paths or queries. The helper reports errors on stderr, which Plenty preserves in its error. Each indexing/query invocation loads the model anew, trading latency for a simple setup without a background service.

Use show ID, status and delete ID with the same extension, database and model arguments. Reindex after editing a file; explicitly delete its stable ID after deleting it. Nothing modifies the original source or issues.db.

Vector input and document operations

Every invocation starts with EXTENSION DB MODEL. Use a new database path, separate from the authoritative issues.db. MODEL identifies the exact model, revision, normalization and chunking configuration; changing it requires a new database. The CLI rejects a different identity or initialization dimension.

The following example uses synthetic two-dimensional vectors to test storage; they are not embeddings of the example text.

/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 init 2

Save this JSON as /tmp/search-chunks.json:

[
  {"text": "Owned resources are released on scope exit.", "embedding": [1.0, 0.0]},
  {"text": "Borrows cannot outlive their owners.", "embedding": [0.9, 0.1]}
]
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 put docs:ownership "Ownership" /tmp/search-chunks.json
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 show docs:ownership
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 status

put replaces all chunks belonging to that document in one transaction. Its hash covers the title, chunk text and supplied vectors. Identical input prints unchanged. Parsing happens before the transaction; if insertion fails (for example, a vector has the wrong dimension), the previous document remains. An empty chunk array keeps the document with no searchable chunks.

Save [1.0, 0.0] as /tmp/search-query.json, then query:

/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 query /tmp/search-query.json "resource cleanup"
/tmp/plenty-search "$PLENTY_SQLITE_VEC" /tmp/search-trial.db fixture-v1 delete docs:ownership

Query words are quoted individually and joined with OR for FTS5. Empty words give vector-only results. Results show document ID/title, zero-based chunk number, fused score and text. Vectors must come from the same model and preprocessing as the stored documents; the CLI cannot infer this from numbers. Supply finite, nonzero vectors suitable for cosine similarity.

delete removes the document, its chunks and embedded vectors through a foreign key cascade; triggers remove the associated FTS5 entries. It never deletes a source file. The trial uses ordinary SQLite BLOB columns and sqlite-vec distance functions, so vector ownership follows normal relational deletion.

Validation

The deterministic integration test uses the actual extension and fake inference and checks insertion, unchanged input, search, rollback on malformed dimensions, replacement, deletion, Unicode chunking, skipped inference and subprocess failure preserving the previous document:

PLENTY_SQLITE_VEC="$PLENTY_SQLITE_VEC" cargo test --locked --test integration test_std_sqlite::sqlite_vec_search_trial -- --ignored

The test is explicitly ignored in the default suite because the extension is an optional external dependency. The ordinary SQLite tests exercise extension loading with a locally compiled fixture and check FTS5 without any download.