tovli is a local-first technical memory assistant for engineering documents.
It ingests a folder of notes, chunks the files, embeds the chunks, stores them
in an embedded RuVector index, and lets you search or ask questions from the
command line.
The project is designed as a practical, open-source learning implementation of
a retrieval-augmented generation pipeline. The default embedding path is local
ONNX/MiniLM, so no Docker, Postgres, or cloud API key is needed. A deterministic
mock embedder remains available with --mock or --no-default-features for
lightweight development and CI.
- Recursive local document ingestion.
- UTF-8 parsers for
.md,.txt,.json,.yaml, and.yml. - Markdown-aware chunking with heading paths and code-fence preservation.
- Embedded vector storage with
ruvector-coreandredb. - Local ONNX/MiniLM embeddings by default.
- Deterministic mock embeddings for repeatable local development and CI.
- Vector, keyword, and hybrid search with
--top-k,--project,--tag,--source, and--explain. - Local full-chunk keyword index with application-level RRF fusion for hybrid ranking.
- Retrieval evaluation with Hit@1, Hit@3, Hit@5, MRR, latency, and an optional CI threshold gate.
- RAG-style
askcommand with cited answers, citation validation, a no-answer policy, and JSONL answer logging.
The current repository is an early CLI and library implementation. The core local workflow is working:
tovli ingesttovli searchtovli evaltovli ask
Planned work includes feedback collection, a real LLM provider adapter, full-content answer context, an HTTP API, bot integrations, PDF support, and additional source connectors.
- Rust 1.77 or newer.
- The repository-pinned Rust toolchain in
rust-toolchain.toml. - On Windows, Visual Studio C++ Build Tools with the Windows SDK.
Install Rust with rustup:
winget install --id Rustlang.Rustup -e --source wingetIf cargo is not available in the current PowerShell session, open a new
terminal or refresh PATH:
$env:PATH = "$env:USERPROFILE\.cargo\bin;$env:PATH"
cargo --version
rustup showRun the local RuVector smoke test:
cargo run --bin tovli -- spikeIngest the project docs with the default local ONNX/MiniLM embedder:
cargo run --bin tovli -- ingest ./docsSearch the indexed chunks:
cargo run --bin tovli -- search "What do the ADRs say about score semantics?" --top-k 5 --explainAsk for a cited answer:
cargo run --bin tovli -- ask "What is the citation policy?" --show-contextEvaluate retrieval quality:
cargo run --bin tovli -- eval ./eval/questions.json --fail-below-hit-at-3 0.6Generated local state is written to .tovli/, which is ignored by Git. Delete
that directory to reset the local index and answer log.
tovli spike
tovli ingest <folder> [--dry-run] [--force] [--project <name>] [--tag <tag>] [--mock]
tovli search <query> [--top-k <n>] [--mode vector|keyword|hybrid] [--project <name>] [--tag <tag>] [--source <path>] [--explain] [--mock]
tovli eval <questions.json> [--top-k <n>] [--mode vector|keyword|hybrid] [--fail-below-hit-at-3 <fraction>] [--output <path>] [--mock]
tovli ask <query> [--top-k <n>] [--mode vector|keyword|hybrid] [--show-context] [--no-llm] [--mock]
Default mode is vector. keyword uses the local full-chunk keyword index.
hybrid fuses vector and keyword ranks with Reciprocal Rank Fusion.
The default build enables the onnx Cargo feature and uses OnnxEmbedder.
On first use, tovli looks for MiniLM in this order:
TOVLI_MINILM_DIR, when set, containingmodel.onnxandtokenizer.json.models/all-MiniLM-L6-v2/in the current working directory.- The OS user cache, where tovli downloads the files from Hugging Face if they are not already present.
The downloaded files are:
onnx/model.onnxtokenizer.json
Verify the local model files and ONNX runtime stack with:
cargo run --bin verify-onnxPass --mock to ingest, search, eval, or ask to force the deterministic
mock embedder. To avoid compiling ONNX dependencies entirely, build with
--no-default-features.
The sample evaluation dataset lives at eval/questions.json. Each question
declares the expected source files, and the evaluator reports:
- Hit@1
- Hit@3
- Hit@5
- MRR
- average latency
- empty-result count
- below-threshold count
Use --fail-below-hit-at-3 in CI to make retrieval regressions fail the run.
The codebase is organized around bounded contexts and hexagonal seams:
src/
ingestion/ parse, chunk, embed, and store documents
retrieval/ vector/keyword/hybrid search, filtering, ranking
evaluation/ retrieval quality datasets, metrics, and reports
answer_generation/ context assembly, prompt rendering, citations, answers
vector_store.rs small RuVector spike/store abstraction
The CLI in src/main.rs is intentionally thin. Domain and application logic
live under the context modules, while infrastructure adapters isolate RuVector,
redb, parsers, embedders, report writing, and the mock LLM.
For design background, see:
Run the test suite serially:
cargo test -- --test-threads=1Run Clippy:
cargo clippy --all-targetsThe serial test run is the documented path because the integration tests open multiple embedded vector stores.
Issues and pull requests are welcome. See CONTRIBUTING.md for setup, test commands, design principles, and the pull request checklist. Good starter areas include parser coverage, retrieval evaluation examples, provider adapters, documentation, and small CLI usability improvements.
Before opening a PR:
- Keep the default workflow offline and deterministic.
- Add or update focused tests for behavior changes.
- Run
cargo test -- --test-threads=1. - Run
cargo clippy --all-targets. - Update the relevant docs or ADRs when changing behavior or architecture.
This project is licensed under the MIT License.