Skip to content

How search works#

A search takes a question and returns the passages of a base that answer it best, each with a citation. It runs on the base's machine, against the base's index, in a few steps you can see — each result carries its score at every step, and each search says how long each step took. This page explains the steps, so you can read results, choose a mode and know what to change when the right passage does not come first.

flowchart LR
  Q[Question] --> E[Embedded by the<br/>embedding model]
  Q --> W[Its words]
  E --> D["By meaning:<br/>closest chunks (cosine)"]
  W --> K["By words:<br/>best chunks (BM25)"]
  D --> F[Fused by rank]
  K --> F
  F --> R[Reranker orders<br/>the best 20–50]
  R --> T[top_k passages,<br/>each cited]

What is searched#

A base searches chunks: passages of its documents of at most 1,200 characters by default, cut within one section, each keeping its document's title and the headings above it, its page (PDF) or its line (text and Markdown) — see Chunks. A chunk is embedded with its title and headings in front (Handbook > Leave policy > Vacation), so the topic of its section counts even when the passage itself does not name it. The reranker reads the chunk the same way.

By meaning#

The question is turned into a vector by the base's embedding model (with the model's query prefix, when it expects one: query: for E5, search_query: for Nomic, an instruction for Qwen3 Embedding — set by default from the model, embedding.query_prefix to change it). It is compared with the vector of every chunk of the base (cosine similarity), and the closest are kept: 50, or 4 × top_k when that is more.

This finds a passage that says the same thing in other words: how many holidays do I get finds 25 days of paid vacation. With a multilingual model such as BGE-M3, a question in one language finds a passage in another. It blurs what has no meaning to the model: a product code, an error number, a rare name.

Comparing with every chunk is exact — no approximate index, nothing missed — and fits the bases Metis is built for, up to about 100,000 chunks each. The vectors are held in the base's memory.

By words#

The question's words are looked up in an inverted index of every chunk, ranked with BM25: a chunk scores higher for holding more of the question's words, rarer words counting more, in a shorter chunk. The best 50 (or 4 × top_k) are kept.

  • Words are compared in lower case with accents folded: configuracao finds Configuração.
  • A word is a run of letters and digits; one letter alone is left out, one digit kept (step 2).
  • There is no stemming and no stop-word list: travel does not find travelling, and very common words count for little anyway. Words are compared as written in any language — and languages written without spaces between words (Chinese, Japanese, Thai) are found by meaning much better than by words.

This finds what meaning blurs: Xid 79, nvidia-smi, PO-2231, a person's name.

Fusion#

The two lists are merged by reciprocal-rank fusion: each chunk scores the sum of 1 / (60 + its rank) over the lists it is in. Ranks, not raw scores, are combined, so a cosine and a BM25 score need no calibration; the constant 60 keeps one list's first place from deciding alone. A chunk both lists found ranks above one only one list found first.

Reranking#

When the base has a reranker (spec.reranker), it reads the question together with each of the best fused candidates — 3 × top_k, at least 20 and at most 50 — and scores how well each answers it. Results are then in the reranker's order. A reranker reads question and passage together, so it judges them more precisely than either retriever, which compare them apart. Without a reranker, results come in the fused order.

Memories#

In a memory, facts that still hold come before facts a newer one replaced, after reranking: what does Ana work on? answers what holds now first, and what was Ana's role in September? still finds the old fact, below.

Filters#

A search may keep only some documents, before anything is ranked:

Filter API CLI Keeps
Folder filters.path_prefix --prefix Documents whose path starts with it (policies/)
Kind filters.kinds --kind (repeatable) md, txt, html, pdf, docx
Source filters.sources --source (repeatable) Documents of these drives (a base's own notes: metis-<base>)

What a result holds#

Field What it says
score The reranker's score when reranked, else the fused score — the order of the results
text The whole chunk
citation source (the drive), document (its path), title, kind, page or line, headings, and snippet — up to 240 characters around the sentence holding most of the question's words
scores.dense, scores.dense_rank Its cosine with the question and its rank by meaning (absent: not among the closest)
scores.keyword, scores.keyword_rank Its BM25 score and its rank by words (absent: not among the best by words)
scores.fused Its fused score
scores.rerank The reranker's score (absent: not reranked)

The answer also says the mode used, whether it was reranked, its warnings, and took: milliseconds to embed the question, search by meaning, by words, fuse and rerank, and in all. The console shows the same under each result: meaning 0.830 (#1) · words 7.41 (#2) · fused 0.0328 · reranker 0.970.

When a model does not answer#

A search answers whatever its models do, and says what it could not do:

What happens The search Its warning
The embedding model does not answer within 10 s By words only … : results by keywords only
The reranker does not answer within 10 s In the fused order … : results in the fused order
Chunks were indexed while the embedding model was down By words, for those chunks no chunk has a vector yet (indexed while the embedding model did not answer): results by keywords
Nothing is indexed yet No results nothing is indexed yet: the index is …
The sources hold nothing a base reads No results nothing is indexed: the sources hold no document Metis reads …

mode: dense is the exception: without its embedding model it is refused (503 EMBEDDING_MODEL_UNAVAILABLE), since by-words results would not be what was asked.

The three modes#

A search may ask for one retriever only:

mode Uses Console Use it to
hybrid (default) Meaning and words, fused, reranked hybrid (both on the Search page) Answer questions
dense Meaning only, reranked by meaning only See what meaning alone finds
keyword Words only, reranked; needs no embedding model by words only Find exact terms; search while the embedding model is down

rerank: false (rerank cleared, --no-rerank) skips the reranker in any mode.

Metis → Search asks one question of one or several bases. With compare by meaning, by words, both ticked, it asks it three ways and lists every passage any mode found with its rank in each — Both, Meaning, Words — and Why it came up:

  • close in meaning (#2) and shares words (#1) — both agree;
  • shares words (#1); not among the closest in meaning — it names a term of the question exactly;
  • close in meaning (#1); its words do not match — it says the same thing in other words;
  • followed by the reranker scored it … when it was reranked.

A passage found only by words often names a term exactly; one found only by meaning says the same thing in other words. To compare modes over many questions rather than one, ask an evaluation set each way (Measure and improve search quality).

Where a search runs#

A search reaches the base's index service on its machine, which embeds the question with the base's embedding model and reranks with its reranker — Eos deployments of the workspace, called on the workspace's network. The question and the passages stay on your machines; Astralyx counts searches per day, never what was asked. Searches are answered while the base is Indexing (from what is indexed so far) and while it is Waiting.