Skip to main content
An ingestion plugin decides how Quivr cuts each article into segments and turns them into vectors, and encodes search queries the same way. The core keeps what must stay safe and uniform: the index, the vector space registry, rebuilds, authorization and withdrawal. The contract is the ingestion Contribution; this page takes a plugin from its manifest to searchable Records.

Declare its spaces

A vector space is one embedding model with its dimensions and distance. The plugin owns every space it declares, so each space id is the plugin id or starts with it:
Bump a space’s version whenever its vectors change: a new model, new weights or a new input template. Quivr treats acme.embedder.base@2 as a new space, never mixing its vectors with those of version 1.

Implement it

With the Go SDK, one type answers both operations:
  • SegmentAndEmbed returns the segments of the Version’s text Parts, in reading order, each with Unicode code point offsets into its Part and one vector per requested space. It may add a LexicalText (folded or stemmed words, indexed in a separate keyword field while excerpts keep the source text) and a Provenance map.
  • EmbedQuery returns the vector of one query in one space.
Both must be deterministic: rebuilds reuse the stored segments and vectors, and a different answer for the same Version is refused. Return a retryable error when a backend is down, a terminal one for content you can never handle. sdks/go/examples/hash-embedder is a complete example with two spaces.

Certify it

The Contract Runner sends the normative article and your own ingestion fixtures (fixtures/*.json with a top-level ingestion), checks every segment and vector as the engine will, replays each fixture, encodes the fixture queries in every space, and checks that invalid requests are refused and that no declared secret appears in an answer or your plugin’s output. Set the fixture’s expect.segments to pin your segmentation.

Pin it

Add it to plugins in the QUIVR_CONFIG file of every api, worker and migrate process, and say which space answers search:
  • Served and evaluation. Exactly one space is served. Evaluation spaces are embedded on the same segments and indexed, so you can compare them, but search never uses them. With one declared space, spaces may be omitted.
  • One plugin. A deployment pins one ingestion plugin (ingestion_conflict); compare embedders as several spaces of it.
  • Startup. The spaces are registered with their owner. Startup refuses a space another plugin already registered (space_owner_conflict) and a registered space whose model, dimensions or metric changed without a new version (space_changed). An unreachable plugin does not prevent startup.

Move a Corpus onto the plugin

Each Corpus keeps the spaces it was built with. A Corpus created before the pin stays on its current space, searchable as before, until you rebuild it (POST /v0/corpora/{corpus_id}/rebuilds, see the HTTP API reference). The rebuild asks the plugin for every Version that has no stored vectors in the new spaces, then switches search over at once. From then on new Records go through the plugin, and search encodes queries with it. GET /v0/corpora/{corpus_id}/vector-spaces shows the Corpus’s spaces, their owner and role, and how many segments hold a vector in each.

When something fails