Skip to content

terminusdb-org/vectorlink

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

166 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tdb-search

A semantic search engine built primarily for TerminusDB, built in Rust on LanceDB. It is designed to work standalone.

tdb-search is an independent reimplementation of TerminusDB's VectorLink semantic indexer. It preserves VectorLink's search-family HTTP shapes and its TerminusDB integration, but replaces the bespoke HNSW index and hand-rolled vector store with LanceDB — gaining full-text search, hybrid (vector + keyword) search, and a maintained, versioned, branchable columnar vector store. Embeddings come from a configurable provider, defaulting to a local CPU model (served by an Ollama sidecar) so the whole stack runs offline.


Why

The original terminusdb-semantic-indexer (the source of the terminusdb/vectorlink image) hardcodes OpenAI embeddings and uses a custom HNSW engine that requires maintenance by the original team. Additionally, it was never completed and proven in production for actual workloads.

tdb-search:

  • Preserves the search interface — the same /search, /similar, /duplicates, /check, /statistics shapes, so existing search callers keep working (search-family parity; additive search modes layered on).
  • Inverts indexing to a push model — instead of the indexer pulling content, TerminusDB pushes rendered text deltas to it (GET /last-indexedPOST /push). The indexer never calls back into TerminusDB; it owns the embedding model and is a complete, standalone HTTP search service.
  • Uses LanceDB at the core — versioned, branchable storage with full-text and hybrid search out of the box (selectable using a mode parameter).
  • Configurable embeddings — local CPU model via an Ollama sidecar (default), direct OpenAI, or any OpenAI-compatible / generic HTTP endpoint.
  • Reflects TerminusDB history faithfully — each commit is a versioned snapshot; branch-out shares the parent commit's vector blocks rather than copying them.

How history maps to LanceDB

TerminusDB history is linear per branch. tdb-search mirrors this:

TerminusDB tdb-search (LanceDB / Lance)
domain org/db a Lance dataset
commit a dataset version, bound by a Lance tag (commit:<id>)
index commit C from parent P append only the changed documents on top of P's version
branch-out at P a Lance branch forked from P's version, sharing its data blocks
reassign commit pointer a tag pointing at an existing version (no recompute)

Vector blocks from the parent commit are reused, never duplicated — the same property the original indexer achieved by sharing an append-only vector store across commits. A global commit→layer index (keyed by commit id, per domain, backed by Lance tags) resolves any commit's layer, so a branch forked at commit P finds P's vectors regardless of which branch first indexed it.

Search

A single endpoint serves three modes:

  • hybrid (default) — vector + full-text fused with reciprocal-rank fusion; best out-of-the-box relevance.
  • vector — semantic nearest-neighbour only; reproduces the original VectorLink behaviour exactly.
  • fts — keyword/full-text over the rendered text (filterable by doc_type).

The same raw query text drives both sides in hybrid mode. Distances use the reference normalised cosine scale in [0, 1] (0 = identical, 0.5 = unrelated, 1 = opposite).

Embeddings

The embedding provider is configurable:

  • Local CPU (default)nomic-ai/nomic-embed-text-v2-moe (768-d, multilingual), served by a local Ollama sidecar (from Nomic's official GGUF, Q8_0) exposing an OpenAI-compatible API. arm64-native, runs on CPU, no external network after the one-time model pull.
  • Direct OpenAIapi.openai.com for parity with the original.
  • Generic / OpenAI-compatible HTTP — any embeddings endpoint, configurable base URL, model, and dimension (e.g. TEI or vLLM if you prefer a different sidecar).

The default model requires task prefixes (search_document: for indexed text, search_query: for queries); tdb-search injects these automatically from a hard-coded, model-keyed table, so they cannot be misconfigured per deployment.

An in-process (no-sidecar) embedding mode via fastembed is a deferred future option — it does not yet build on aarch64, so the Ollama sidecar is the canonical local runtime.

Access

The indexer is a trusted component behind a single shared admin secret (HTTP Basic, default admin:root), checked on every request (401 on miss) — authentication only, no RBAC. TerminusDB fronts search and authorises the caller against its own capability system before calling the indexer. Deploy the indexer on a private network with TerminusDB as the front door.

Quickstart (planned)

docker compose up

Brings up three services on CPU, no external calls (after the one-time model pull):

  • tdb-search — this server (HTTP API on :8080)
  • embeddings — Ollama serving nomic-embed-text-v2-moe (GGUF, Q8_0)
  • terminusdb — a TerminusDB server for an end-to-end example

Drive indexing by push, then search (all requests carry the admin secret):

# 1. ask where the indexer is up to (TerminusDB uses this to compute the delta)
curl -u admin:root 'localhost:8080/last-indexed?domain=admin/star_wars&branch=main'

# 2. push the rendered NDJSON delta for a commit (one operation per line)
curl -u admin:root -X POST \
  'localhost:8080/push?domain=admin/star_wars&branch=main&target_commit=<C>&parent_commit=<P>' \
  -H 'Content-Type: application/x-ndjson' \
  --data-binary @delta.ndjson

# 3. semantic search (raw text body; hybrid by default)
curl -u admin:root 'localhost:8080/search?domain=admin/star_wars&commit=<C>' -d 'Wise old man'

In normal operation TerminusDB performs steps 1–2 automatically; the curl calls above are how you drive the indexer standalone.

HTTP API

  • Indexing (push): GET /last-indexed, POST /push (NDJSON operation stream, incremental), GET /check (poll a push task), POST /assign (point a commit at an existing index — a mutation, so POST, not the reference's GET).
  • Search: POST /search?mode=vector|fts|hybrid (hybrid default; doc_type/doc_id filters), GET /similar, GET /duplicates.
  • Ops: GET /statistics.

Indexing is driven by push (/last-indexed + /push), not a pull trigger. The full contract is the OpenAPI document: openapi.yaml — render it with make docs.

Deployment & startup

The engine targets fast cold-start for KEDA-style scale-from-zero: a fresh pod binds and answers its liveness probe near-instantly, defers all heavy work (datasets opened on first touch, layer index resolved per request, no boot-time scans), and the embedding tier is scaled separately so an indexer pod never pays model-load cost on cold start.

Documentation

License

Most of tdb-search is licensed under the Apache License, Version 2.0 (see LICENSE). Enterprise Edition modules are licensed under the Business Source License 1.1 (see LICENSE-EE) and convert to Apache-2.0 on the Change Date.

All source files carry SPDX-License-Identifier headers indicating their license. Files without an explicit EE marker are Apache-2.0.

About

TerminusDB Semantic Indexer

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages