Result of a rerank request: documents reordered by relevance to a query.
{:ok, result} = ExAgent.rerank(provider, "who restarts processes?", chunks)
for %{index: index, score: score} <- result.results do
IO.puts("#{score} - #{Enum.at(chunks, index)}")
endWhy rerank at all
Embedding retrieval compares a query vector to document vectors computed independently, which is what makes it fast enough to run over millions of rows - and also what limits it. A cross-encoder reads the query and one document together, so it can weigh them against each other, at a cost that only makes sense over a shortlist.
The pairing is therefore: embeddings fetch the top ~100, a reranker orders the top ~10 that actually go in the prompt.
:index is the contract, not :document
Every entry carries the position of the document in the list you passed, so results map back to your own records without depending on the server echoing text back:
ranked = Enum.map(result.results, fn %{index: i} -> Enum.at(rows, i) end):document is populated only when the server returns the text, which is
optional and off by default - sending a corpus back over the wire to get it
re-quoted is waste.
Scores are model-scoped
Higher means more relevant, and that is the only guarantee. The range is the
model's own: jina-reranker-m0 emits roughly 0-1 while other cross-encoders
emit unbounded logits. So compare scores within one result set, never
across models, and be wary of absolute cutoffs - persist :model next to
anything you store, for the same reason ExAgent.Embeddings does.
Summary
Functions
Keeps only results scoring at or above threshold.
Reorders documents to match the ranking, dropping anything not ranked.
Types
@type result() :: %{ :index => non_neg_integer(), :score => float(), optional(:document) => String.t() | nil }
One reranked document.
:index is its position in the input list; :score is the model's relevance
score; :document is the text, present only when the provider returned it.
Functions
Keeps only results scoring at or above threshold.
Ranking always returns something, even when nothing is relevant - the best of a bad set still sorts first. A floor is how you decline to answer instead of feeding the model the least-irrelevant chunk.
The threshold is model-specific, so calibrate it against your own data rather than copying a number.
Examples
iex> result = %ExAgent.Reranking{
...> results: [%{index: 0, score: 0.9}, %{index: 1, score: 0.1}],
...> model: "m",
...> provider: Fake
...> }
iex> ExAgent.Reranking.above(result, 0.5).results
[%{index: 0, score: 0.9}]
Reorders documents to match the ranking, dropping anything not ranked.
A convenience for the common case where the ranked texts are all you need.
Use :index directly when you are ranking rows rather than strings.
Examples
iex> result = %ExAgent.Reranking{
...> results: [%{index: 2, score: 0.9}, %{index: 0, score: 0.4}],
...> model: "m",
...> provider: Fake
...> }
iex> ExAgent.Reranking.take(result, ["a", "b", "c"])
["c", "a"]