Skip to main content
Run a second embedding model (a model that turns text into numeric vectors) beside the current one. Fill it for existing documents, compare searches, then switch with a way back and update alerts that match by meaning.

Prerequisites

  • A running Quivr with an ingestion plugin pinned. This guide keeps core.ingest serving and adds hosted.embed, the hosted text embedding plugin.
  • curl, jq, Go and the quivr CLI (curl --version, jq --version, go version, command -v quivr). Run package commands from a Quivr checkout.
  • Your API address in QUIVR_API_URL and the id of a collection of documents (Corpus) in CORPUS_ID.
  • API keys with the following permissions and grants for the Corpus:
All three keys for a Corpus must belong to its Organization. Plugin, plan and promotion requests affect the whole deployment; backfills and coverage reads see only the key’s Organization and grants. You also need a reachable model endpoint, its model name and dimensions, and its credential in the plugin process environment. The plugin reads AZURE_FOUNDRY_KEY for every provider, despite that variable’s name. The examples use a hosted Cohere v2 endpoint and 1024 dimensions; choose a model that supports them.
Hosted embedding sends document text and search queries to your provider and can incur charges, including during certification and evaluation. Keep provider keys out of configuration files and API requests.
The package generation runs offline. The API sequence is checked with a local fake embedding provider; the provider configuration below is an example, not a live provider run. Replace placeholders with your installation’s values. This procedure changes the plugin that produces segments and vectors (the ingestion owner). If your new model is another space of the same plugin, follow Fill a new vector space, then use this page’s comparison and alert-migration checks. That path promotes a space; this example activates a registration. Keep the former model reachable and filling new documents throughout the trial and switch, so rollback remains possible.

Steps

1

Prepare the second model

Build the plugin, then write its configuration. Replace the URL and model placeholders before generating the manifest:
For Cohere’s own API, use "base_url":"https://api.cohere.com/v2" and "auth":"bearer" in both configuration copies instead.The generated manifest binds the model and settings to a distinct vector space, the coordinate system for its numeric vectors. Use that exact configuration when pinning or registering it. Copy the space name from contributions.ingestion.spaces; its API id is that name followed by @ and the declared space version.Certify with a fixture carrying the same configuration. Inject AZURE_FOUNDRY_KEY into the certification command’s environment so its plugin subprocess receives it. This step calls your provider:
Check that the report says CERTIFIED. Start the plugin in a separate terminal, with the provider key injected from your secret manager. Choose a free port reachable by Quivr’s API and workers:
Changing the model, dimensions, format, metric, input templates or model_revision creates another space. Set model_revision when the provider changes weights behind a stable model name. A changed package needs a new immutable registration: increment plugin_version, regenerate and certify it. To compare several hosted models at once, give each a distinct plugin_id before generation.
2

Install it for evaluation

Keep your current served routing, and add hosted.embed under ingestion.evaluation for each source format you want to compare. An ingestion owner is the plugin that produces a document’s segments and vectors. Evaluation adds another owner’s output without replacing the served owner’s output.For example, merge these settings into each API, worker and migrate configuration, preserving existing pins and routes:
Two settings use the same words. The spaces pin picks which of this plugin’s spaces it uses: hosted.embed has only one, so it is served. ingestion.evaluation decides whether its output reaches ordinary search: it does not until you activate it in step 5. Apply the pin; for a runtime registration, follow Upgrade or switch a plugin.Routing uses the original source media type: a PDF normalized to text still follows application/pdf. Both owners read the normalized document pieces (canonical Parts), but each keeps its own segment cuts and vectors.Ordinary search reads the served owner’s output; an explicit evaluation search reads the second owner’s output.In words: each model builds its own segments and vectors from the same document. Ordinary search uses the served model; evaluation search names the candidate and its space. An evaluation failure is recorded in document diagnostics and does not change served readiness.Find the active hosted.embed registration and save its registration_id as REGISTRATION_ID:
Extract SPACE_ID from the generated manifest, which is JSON-compatible YAML. Save the current plan for an exact rollback target:
3

Fill existing documents

Live ingestion fills a space once the Corpus’s search index carries it. Installing the plugin alone does not prepare every existing Corpus. A backfill can add the space and fill old documents. Run it for every Corpus in the deployment, in every Organization, including empty ones, without date filters.Use your deployment’s list of Organizations and each Organization’s keys. List its authorized Corpora:
Follow next_page_cursor by sending it as page_cursor until it is absent (displayed here as null). Quivr cannot list all Organizations’ Corpora with one key. Ensure the key grants access to every Corpus in its Organization.Make a dry run with an explicit registration and its primary space:
For two short documents, the dry run includes these fields (trimmed; your counts vary):
Read versions, segments, input_tokens, estimated_seconds and confirmation_required. Cost is unknown when estimated_cost_usd is absent. A plugin without its own segments yet is estimated using the current owner’s cuts; completion uses the new owner’s actual cuts.The start request must reuse the dry run’s key and scope, changing dry_run to false. Add confirm_cost:true only after accepting an estimate that requires confirmation:
Save the returned operation_id in OPERATION_ID. An Operation records this long-running task. Follow it until state is succeeded, failed or canceled. Continue only after succeeded; otherwise inspect the failure or rerun the fill:
See Backfill a vector space for pausing, resuming and failures. The same accepted key and body return the same Operation; a later fill needs a fresh key and its own dry run before starting. One unfinished backfill per Corpus is allowed.
4

Compare searches and coverage

Use the same query, Corpora, mode, search profile, filters and limit. These two requests differ only in selecting the evaluation path:
Both evaluation fields are required together. The space must belong to that plugin and be carried by every requested Corpus; invalid selections return 422 unsupported_search.Each hit is one segment (passage), so one document can appear several times. Compare relevant document ids and passage order across several queries, including paraphrases and other languages. Owners can cut documents differently, so segment offsets need not match. Raw similarity scores from different models are not comparable probabilities. Check hit vector_space_id to confirm which model answered.Read each Corpus’s coverage:
In the same two-document run, the candidate entry reads (trimmed):
For each space, coverage.segments counts vectors present, coverage.total_segments counts that owner’s current segments, and coverage.versions_covered counts current document revisions fully covered. Compare segment counts within that owner; the response’s top-level segments describes served segments and can differ.Before switching, require coverage.segments to equal coverage.total_segments and the backfill Operation to be succeeded for each Corpus. An empty Corpus reads 0/0, which is fine. These counts describe stored segments; skipped documents can still lack candidate output. The switch rechecks coverage across the deployment and refuses if anything is missing.
5

Switch the model

Fill and compare before switching ordinary search; migrate meaning alerts immediately afterward.In words: run both models, fill existing documents, compare, then switch and update alerts. If results worsen, return to the saved plan and update alerts again. Changing spaces within one owner uses space promotion; changing the ingestion owner uses registration activation.Different owner, as in this hosted.embed example: activate the already active evaluation registration:
For the example’s three source formats, expect this output (plan id replaced):
This switches every source format that names this plugin as an evaluation owner, moving its segments and vectors together. Its primary space must be carried by every Corpus’s index across every Organization, including empty Corpora. Every current document routed to the candidate must have all its candidate segments and primary vectors.Missing coverage returns 409 plugin_conflict and leaves the plan unchanged; an unreachable candidate returns 409 plugin_unreachable. Repeating activation returns the same plan. The previous owner becomes an evaluation owner for those formats and continues filling new documents asynchronously. If a configuration change leaves an existing Corpus serving another owner, new documents also fill the active plan’s default or source-route owner. Keep these plugins reachable and check coverage before returning to either model.Same owner, another space: follow Fill a new vector space, which ends with a space promotion. That API allows force as an explicit exception for incomplete coverage; documents without vectors then disappear from semantic results until filled. Registration activation has no force option.
6

Move vector meaning alerts

A Saved Query stores an alert definition; each immutable Saved Query Version fixes its query vectors. A Subscription pins one such Version and the evaluator that judges documents. Switching search does not re-encode those vectors or move those pins. Vectors from different spaces cannot be compared.Migrate immediately after the switch. Until you do, these alerts return not_ready because the document and query vectors are in different spaces. Quivr records that result as final: it does not automatically re-check those documents when you migrate. A new Subscription Version takes effect from its commit, so documents accepted between the switch and migration can miss alerts permanently. The same gap opens after rollback; migrate immediately then too.For every Saved Query using meaning_check: vectors in the affected Corpora, create a new Version after the served-model switch. Set SAVED_QUERY_ID to its id from your application’s alert inventory. Preserve its definition:
Save its returned version_id as QUERY_VERSION_ID. For each Subscription of this Saved Query, set SUBSCRIPTION_ID and create a new Version, retaining its evaluator settings and destination:
The query Version must still be that Saved Query’s current Version. Finish its Subscription migrations before creating another query Version. Replaying the same key and body returns the same Version; a changed body conflicts. Subscription edits preserve enabled state and affect future changes from their commit, without replaying past documents or rewriting existing Matches.Use your application’s inventory to include disabled Subscriptions before re-enabling them. The Subscription API requires an owner filter and lists only enabled, non-deleted items; follow all pages for each owner and list global Subscriptions separately with owner=none. Gather Saved Query ids from those lists or your inventory; the API cannot list Saved Queries. Recalibrate the evaluator’s similarity threshold against the new model. This is separate from upgrading an alert-rule plugin.

Check it worked

Repeat the current-model search without evaluation fields:
With matching documents in the switched formats, expect your candidate’s space id:
Read the active plan’s served routes:
API and worker processes poll for plan changes. If loading a plan fails, they keep the old plan and do not retry that plan id until another plan appears; inspect their logs and fix the reported error before applying another plan change. Inspect every Corpus’s coverage. Submit a new document and confirm the new model fills it. Read migrated Subscriptions and check current_version.saved_query_version_id matches the new query Version.

Roll back

Keep the previous plugin reachable with its expected build, and keep its evaluation coverage filling new documents. A missing vector or owner projection can block rollback; backfill the returning owner first. Do not stop it while work or alerts still pin it. For an owner switch, return to the saved plan, using a new key for this rollback attempt:
Use drain for sound output; use stop for bad output or hangs. The same key and body replay this rollback. Keep the explicit target: another untargeted rollback can return to the plan you just left. Unreachable builds return 409 plugin_unreachable; missing owner coverage returns 409 plugin_conflict. Its message names the owner, space and number of missing documents, and points to POST /v0/admin/backfills. It also reports missing index generations: even an empty Corpus must carry the returning space. Find the returning owner’s registration with GET /v0/admin/plugins and read each Corpus’s coverage to locate gaps. Use the backfill procedure for that registration and space in each affected Corpus, wait for complete coverage, then retry. Historical gaps and failed optional calls still need this recovery. For a same-owner space switch, promote the returned previous_space_id back with the space-promotion request. It needs complete coverage too. Immediately after either rollback, create another Saved Query Version using the restored served model and migrate its Subscriptions again with fresh keys; the earlier migrations do not reverse themselves. Compatible redeploys preserve activated serving and evaluation routes; see Redeploy a configured build for the compatibility conditions. Earlier plans and pinned work still require their original builds. If a redeploy makes the saved plan unreachable, restore that build or follow the linked procedure to activate the returning owner’s current evaluation registration, then migrate alerts again.

Clean up

Delete the local query-version.json and subscription-version.json files after migration. Retaining an evaluation owner is useful for rollback but continues provider calls. Once you no longer need that rollback option:
  1. Copy the active plan’s default, served routes and evaluation owners into ingestion.default, ingestion.routes and ingestion.evaluation in every process’s configuration. Apply this configuration first, so it records the routing you chose by activation or rollback.
  2. Remove the retiring owner from each ingestion.evaluation list and apply the configuration again. After rollback in this example, remove hosted.embed; if you keep the candidate serving, remove the former owner instead. This stops new evaluation calls for those formats.
  3. If that plugin no longer serves any format or other plugin role, remove its plugins pin too. Keep it reachable until pinned work and Subscriptions release it and its registration becomes inactive; see the plugin lifecycle. A plugin still serving another format or role must keep running.
Preserve the served routes and remaining owners’ pins throughout cleanup. Stored alert Versions and Matches remain readable.

Next