Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Vectoria
Embedded hybrid search engine for ecommerce. Single binary, no external services required.
Vectoria combines BM25 full-text search, vector similarity, and behavioral signals (clicks, purchases, views) into a single ranking formula. Its main advantages over BM25-only search (SQLite FTS5, Meilisearch basic tier):
- Zero-result elimination — semantic mode returns results for long-tail queries that share no keywords with any product. Hybrid mode keeps BM25 precision while removing zero results entirely.
- Query-level CTR feedback — products previously clicked for a given query rank higher for future searches of the same query. The feedback loop activates immediately after the first click event with a
queryfield.
The embedding model runs locally via ONNX; no external API calls required unless you configure an OpenAI-compatible provider.
Designed for catalogs up to ~500K products on a single node.
Requirements
- Rust 1.80+ (for building from source)
- ~40 MB disk space for the default embedding model (downloaded on first run)
Getting started
Start the server:
On first run, it downloads the multilingual-e5-small embedding model (~40 MB) and prints an API key:
INFO vectoria v0.1.5
INFO api_key: a1b2c3d4e5f6...
INFO listening on http://0.0.0.0:7700
Index a product:
Search:
Configuration
Place a vectoria.toml in the working directory. All fields are optional.
[]
= "0.0.0.0"
= 7700
= "your-key" # auto-generated if absent
= false # skip model download prompt on first run
[]
= "./vectoria.db" # path for persistent index files
[]
= "local" # "local" | "openai-compatible"
= "multilingual-e5-small"
[]
= "edgestore-hnsw" # see below
= false # cross-encoder reranking (slower, higher quality)
= 300 # how often behavioral signals fold into ranking
= 60
= 1000
= 10000
[]
= 0.7
= 0.3
= 0.2
= 0.15 # boost products previously clicked for this exact query
= 0.05
= 0.05
vector_backend options:
edgestore-hnsw— persistent HNSW index (activated afterPOST /admin/reindex), recommended for productionsqlite— SQLite metadata + EdgeStore flat vector indexmemory— everything in-memory, lost on restart (development only)
Environment variable overrides (take precedence over vectoria.toml):
VECTORIA_HOST
VECTORIA_PORT
VECTORIA_API_KEY
VECTORIA_STORAGE_PATH
VECTORIA_EMBEDDING_PROVIDER
VECTORIA_EMBEDDING_BASE_URL
VECTORIA_EMBEDDING_MODEL
VECTORIA_CONFIG # path to config file, default: vectoria.toml
VECTORIA_SKIP_CONSENT=1 # maps to server.skip_consent
VECTORIA_ENABLE_RERANKER=1 # maps to index.enable_reranker
Behavioral ranking
Vectoria uses two behavioral signals derived from POST /events:
| Signal | Source | Effect |
|---|---|---|
| Global popularity | click_count / view_count (all queries) | Products with high overall CTR rank slightly higher everywhere |
| Query CTR | click + purchase count per (query, product) | Products clicked for this exact query rank higher for future searches |
Always include query in click/purchase events to activate query-level CTR:
Events without query still contribute to global popularity but not per-query ranking. Background aggregation runs every aggregation_interval_secs (default 300s). The query_ctr weight defaults to 0.15 and is configurable in [ranking].
Use "explain": true in search requests to see per-factor scores:
CLI
The vectoria CLI handles bulk operations against a running server.
# Bulk import from NDJSON, CSV, or Parquet
# Re-embed all products after a model change
# Benchmark search quality (Recall@K, NDCG@K, MRR) across all modes
Embedded usage (Rust)
Add vectoria-core to your Cargo.toml:
= "0.1.5"
Async (with Tokio):
use ;
let engine = new
.query_cache
.build
.await?;
engine.index.await?;
let results = engine.search.await?;
Sync (no Tokio required in caller):
use ;
let engine = new?;
let results = engine.search?;
SearchEngineBuilder accepts optional overrides for storage backend, vector index, embedding provider, ranking weights, query cache TTL/size, and cross-encoder reranking. All default to in-memory storage and the local multilingual-e5-small ONNX model.
Preloading an existing database — pass a persistent backend pointing to an existing file, then call reindex_all() to rebuild the BM25 index and spell corrector from stored products:
use ;
use ;
let engine = new
.storage
.build
.await?;
engine.reindex_all.await?; // rebuild BM25 + spell corrector
Bulk indexing — call engine.index() in a loop. If products already have vectors, set product.vector to skip the embedding step. Call reindex_all() once after bulk loading to flush the HNSW graph.
Publish target: make publish (requires cargo login or CARGO_REGISTRY_TOKEN). See crates.io/crates/vectoria-core.
Demo webstore
The fastest way to try Vectoria with real data is the Make-based demo. It uses the Amazon ESCI product catalog — a separate license agreement is required: https://github.com/amazon-science/esci-data
To benchmark after importing:
Run make help for all targets and overridable variables. See
docs/quickstart.md for a full walkthrough.
Benchmark
Amazon ESCI dataset, 5000 US products, multilingual-e5-small embedding. Results by label hardness:
| Label set | Queries | BM25 MRR | Hybrid MRR | Semantic MRR | Coverage (all modes) |
|---|---|---|---|---|---|
| E (exact) | 107 | 0.5842 | 0.5882 | 0.4922 | 100% |
| E+S | 117 | 0.6595 | 0.6576 | 0.5690 | 100% |
| E+S+C | 119 | 0.6835 | 0.6803 | 0.5797 | 100% |
ESCI label meanings: E = exact product name match (BM25-optimal), S = substitute/concept (keyword overlap low), C = complement (e.g. query "camera" → relevant product "camera bag").
Key takeaways:
- 100% coverage across all modes and label sets — zero-result queries handled by spell-correction fallback (compound split + typo correction applied only when BM25 returns no results)
- Hybrid ≈ BM25 on exact queries, with coverage maintained by semantic fallback
- Semantic covers zero-keyword queries that BM25 would miss entirely (S and C labels)
- Semantic p50 latency: 2ms (cached embeddings); BM25/hybrid: sub-ms to ~3ms
Reproduce with custom label sets:
# Or choose label set:
Next benchmark target: WANDS (Wayfair) — 42K furniture/home goods products, complex descriptive concept queries ("mid century modern floor lamp"). Expected to show larger hybrid advantage since Wayfair queries are more concept-driven than ESCI exact matches.
API reference
See docs/api.md.
Docker
Quickest start — Docker Compose (builds image, mounts volumes, sets API key):
VECTORIA_API_KEY=my-secret-key
Or build and run manually:
# Full image — ONNX model downloaded on first start (~400 MB image, ~40 MB model cache)
# Slim image — requires OpenAI-compatible embedding provider (~50 MB)
Both images include the vectoria CLI. Run it against the container:
Volumes:
/data— persistent index and SQLite storage/root/.cache/fastembed— ONNX model cache (full image only; mount to avoid re-downloading)
Building from source