Skip to main content
A search profile names how Quivr finds and ranks results. Your deployment chooses which plugin handles names such as default and deep, and one profile can start from another profile’s results and re-rank them, within one time and cost budget.

Short names and full names

A retrieval plugin is the plugin that answers searches. Each profile it declares has a full name, the plugin’s id and the profile joined by a slash: core.retrieve/default. Clients usually send a short name, such as deep, and the deployment maps each short name to one full name. A search that names no profile uses default. Two plugins can each declare a profile called deep, and they are different profiles: core.retrieve ships with Quivr, and the make dev stack pins it: it lists it in Quivr’s startup configuration, which is how a deployment installs a plugin. Quivr’s API refuses to start without a pinned retrieval plugin. jev.rerank is optional. Pinning it beside core.retrieve makes a map of short names required, and deep means Jev only if that map says so. A short name goes through the deployment’s map; a full name goes straight to its profile. Example deployment mapping, with Jev installed: In words: Quivr looks up a short name in the deployment’s map. In this example, default reaches core.retrieve/default and deep reaches jev.rerank/deep. A full name skips the map, so core.retrieve/deep, which has no short name left, is reached by its full name.

How a deployment chooses

The map is retrieval.profiles in the startup configuration. This one makes deep mean Jev:
QUIVR_CONFIG (excerpt)
  • One retrieval plugin and no map: its own profile names are the short names. It must declare default.
  • Several retrieval plugins: the map is required and must contain default. Each target must be a profile of an installed retrieval plugin.
  • Full names always work, including for profiles that no short name maps to.
Quivr refuses to start when a required map is missing or a target does not resolve, and the error names the problem. Activating a new version of a plugin keeps the map, and Quivr refuses an activation that would leave the map unresolved. Search profiles in the configuration reference lists the rules, and Re-rank with Jev pins both plugins.

See and call the profiles

GET /v0/search/profiles lists every installed profile, the one default maps to first. It needs a key with search:query. The make dev stack pins only core.retrieve and no map, so there deep is core.retrieve/deep:
aliases holds the short names that select a profile, and is empty for a profile reachable only by its full name. Each item also has name, the plugin’s own name for the profile: with Jev mapped, two items are named deep, and only jev.rerank/deep has deep in its aliases. To call a profile, put either name in the search’s profile field, "profile": "deep" or "profile": "core.retrieve/deep". The response’s retrieval_profile.name echoes the name you sent, and usage.profiles lists the full name of each profile that ran. Choose a profile in the Search guide runs both. A name that maps to nothing is refused with 422 unsupported_profile.

One profile building on another

A profile can ask Quivr for another profile’s ranked results and re-rank them. jev.rerank/deep does this: it asks for core.retrieve/default’s hybrid results, which combine keyword and meaning matches, then sends each passage and the query to Jev, a paid model from TypeSafe that scores how well the passage answers. To find meaning matches, Quivr first turns the query into a vector with the ingestion plugin, the plugin that made the passages’ vectors. Quivr runs the inner profile itself, so neither plugin sees a passage the caller may not read: In words:
  1. Your app searches with profile: deep, which the map resolves to jev.rerank/deep.
  2. Quivr sends Jev the query and the time and cost left. Jev asks for the top results of core.retrieve/default in hybrid mode, whatever mode your search sent. Its candidate_count setting sets how many: 30 by default.
  3. Quivr runs core.retrieve/default with the same query, Corpora, filter and access. Quivr has the ingestion plugin encode the query, queries the index, drops passages the caller may not read or that were withdrawn, and reads the rest from storage.
  4. Quivr hands Jev that ranking with each passage’s text and score. Jev scores the passages with the Jev model and returns its ranking and what it spent, or the hybrid order when it cannot score.
  5. Quivr returns the results. usage.elapsed_ms reports the whole search’s elapsed time, and usage.profiles lists each profile’s rounds, paid calls and cost. Quivr does not report time per profile.
Write a retrieval plugin describes the rounds a plugin exchanges with Quivr.

Time and cost budgets

Each profile declares a latency objective, max_latency_ms, and the most a search may spend on paid calls, max_cost_cents. The profile list shows both.
  • The objective is a target, not a cutoff. A slower search still answers and is counted as over its objective. Quivr stops a search only at its hard limit, four times the objective, between 2 and 9 seconds. Past it, the search fails with 504 search_deadline_exceeded, or 503 search_unavailable when the time ran out while Quivr served candidates.
  • The outer profile’s limit covers the chain. A jev.rerank/deep search, inner profile included, has 9 seconds: four times 3 seconds, capped at 9. The inner profile gets the time left or its own hard limit, whichever is shorter: 2 seconds for core.retrieve/default.
  • Spending counts at every level. Each plugin reports only its own paid calls. Quivr adds them up in usage.paid_calls and usage.cost_cents, and lists each profile’s own share in usage.profiles, outer first. Inner spending counts against the inner profile’s allowance and the outer one’s. A plugin on Plugin API 0.12 or later receives the time and cost left each round, and a reported spend over an allowance fails the search with 502 retrieval_plugin_invalid.
Within that time, Jev gives its call to the Jev model at most 2 seconds, and less when less time is left. Before each attempt, it reserves the attempt’s maximum cost and checks that it fits the cost left.

When Jev returns hybrid results

If hybrid retrieval succeeds and the Jev plugin can respond, the Jev plugin handles errors from the Jev model itself and returns core.retrieve/default’s hybrid order instead of failing the search. This covers a missing TypeSafe key, provider errors, invalid answers, and a deadline or cost left too small for a call. Each hit’s explanation field then reads re-ranker unavailable: <reason> instead of Jev’s probability; without a key, the reason is API key not configured. Retrieval or plugin outages can still fail the search: a Jev plugin that is down or unreachable, or an ingestion plugin that cannot encode the query, returns 503 search_unavailable. Without a key, there is no paid call and no reported cost. With a key, a failed paid attempt can still report cost: an attempt that fails without usage from the provider reports a reserved upper-bound cost, not an invoice.

What composition does not do

  • No automatic stacking. A profile builds on another only when its plugin declares that dependency in its manifest and asks for it. Mapping deep to Jev does not change default or core.retrieve/deep.
  • Two levels at most. A profile that builds on another cannot itself be built on, and a chain cannot loop. Quivr refuses a missing or invalid dependency when the plugin is installed or activated.
  • No wider access. The inner profile runs with the caller’s Corpora, filter and access. The outer and inner retrieval profiles use the same snapshot of retrieval plugin versions, even if an activation happens during the search.

Next

Re-rank with Jev

Run the Jev plugin beside core.retrieve and map deep to it.

Write a retrieval plugin

Declare profiles and start from another plugin’s ranking.