Prerequisites
- Go 1.26 or later, a
quivrcheckout in$QUIVR_REPO, and thequivrcommand on yourPATH(Build your first plugin shows how).
How a search runs
A search runs in rounds, three at most:- Quivr sends the query, the Corpora in scope, the profile and the vector spaces those Corpora carry.
- The plugin answers either candidate requests or the final ranking.
- For each request, Quivr queries the index, drops what the caller may not read or what was withdrawn, and serves the rest. It then calls the plugin again with everything served so far.
bm25 (keywords), near_vector (one space’s vectors; Quivr encodes query_text with the space’s owner), hybrid (both in one query), or profile (another plugin’s ranking, with its scores and explanations). A ranking may hold only candidates Quivr served, and each hit can carry an explanation that the API returns with it.
Steps
1
Start from the sample
2
Declare your profiles
A profile is a named strategy with its budgets. Clients choose one with
profile in POST /v0/search; the deployment’s default alias answers when they name none. A plugin may serve only deep. The sample declares two:quivr-plugin.yaml
max_latency_ms is the profile’s latency objective, a p95 target: a search that takes longer still answers, the API logs a warning that names the slowest phase, and GET /v0/admin/stats/searches counts it in the profile’s over_objective. Quivr cancels a slow search at the profile’s hard limit, four times max_latency_ms, at least 2 seconds and at most 9 seconds. Past it, Quivr answers 504 search_deadline_exceeded, or a retryable 503 when a dependency could not serve candidates: model_unavailable for query encoding, search_unavailable for other dependencies. Dependency failures can return these 503 codes before the hard limit. max_cost_cents is what one search may report spending on paid calls, such as a re-ranking model. Change the id to your own before you pin it.3
Rank
main.go implements one method, Search, called once per round. In round 1 it asks for candidates:main.go (excerpt)
req.Served, which holds every earlier request with its candidates and their text:main.go (excerpt)
req.ServedSpace() names the vector space that answers search by default. The answer must be deterministic: the same search ranks the same way every time. Return quivrplugin.TerminalSearchError for a query you can never answer.4
Certify it
fixtures/ (a query and a catalogue of candidates), drives each search under every profile, and judges every round as the engine does. It also checks deadlines and budgets, replays each search, and sends invalid requests. Set a fixture’s expect.top to pin the first hits of your ranking.5
Pin it
The api requires at least one retrieval plugin. You can use yours alone with the configuration below, or pin it beside
core.retrieve and set Search profile aliases to choose which plugin answers each search:QUIVR_CONFIG
Check it worked
retrieval_profile, reports the rounds, the elapsed time and the reported spend in usage, and carries your explanation on each hit.
Troubleshooting
Start from another plugin’s ranking
Search profiles shows how a chain runs and shares its budget. To build on an installed retrieval plugin, use the profile-candidates feature in the Plugin API history. Declare its plugin id, version range and local profiles in the manifest’srequires field. Ask for
primitive: profile with profile: {name: "core.retrieve/default", limit: 10}.
Use the full name declared in requires; an optional query replaces your
search’s query text. With the profile-candidate-mode feature, optional mode
selects lexical, semantic or hybrid; omitting it inherits the outer mode.
The retrieval contract
lists the request fields; the manifest reference
lists dependency fields.
Quivr runs the inner rounds on your search’s plan and scope. Reorder the
returned candidates from req.Served as usual. Declare budgets for the whole
chain, and check req.Budget before paid calls; report only your plugin’s own
usage. Quivr adds inner usage to the total and includes each level in
usage.profiles. Chains have at most two profile levels; invalid dependencies
are refused at installation and activation.