Arcana.SearchResult (Arcana v3.0.0)

Copy Markdown View Source

Normalized search result returned by Arcana.search/2 for every mode.

All modes (:vector, :keyword, :hybrid, and graph-enhanced search) return the same struct, so integrators never need per-mode formatters.

Fields

  • :id - Chunk id (UUID string)
  • :text - Chunk text
  • :document_id - Owning document id (UUID string)
  • :chunk_index - Position of the chunk within its document
  • :score - Final relevance score for the mode that produced it
  • :vector_score - Vector similarity component. Only set by the single-query hybrid search on the pgvector backend, nil elsewhere.
  • :keyword_score - Keyword relevance component. Same availability as :vector_score.
  • :rerank_score - Score attached by rerankers that produce one (e.g. Arcana.Reranker.ColBERT), nil otherwise.
  • :metadata - The chunk's stored metadata with string keys.

The struct implements the Access behaviour for reads, so result[:text] keeps working for code that used the previous plain maps.

Serializing

Use to_map/1:

results |> Enum.map(&Arcana.SearchResult.to_map/1) |> JSON.encode!()

The struct is deliberately not derived for Jason.Encoder or JSON.Encoder. Deriving would make the field set part of the wire contract, so adding or renaming a field would break everyone serializing results. to_map/1 is the seam instead: it returns a shape this module commits to, and lets fields be added without exposing them.

Two things not to do. Map.from_struct/1 works today but makes the private field set your contract, so a field added later that isn't JSON-safe breaks you at encode time. And a defimpl JSON.Encoder, for: Arcana.SearchResult in your own application is an orphan implementation for a struct you don't own: it collides if Arcana ever ships one, and with any other library that did the same.

Summary

Functions

Builds a result from a vector-store backend map (%{id, score, metadata}).

The result as a plain map, for encoding, logging, or handing to a model.

Types

t()

@type t() :: %Arcana.SearchResult{
  chunk_index: non_neg_integer() | nil,
  document_id: String.t() | nil,
  id: String.t() | term(),
  keyword_score: number() | nil,
  metadata: map(),
  rerank_score: number() | nil,
  score: number(),
  text: String.t(),
  vector_score: number() | nil
}

Functions

from_store_result(result)

Builds a result from a vector-store backend map (%{id, score, metadata}).

Backend metadata mixes the chunk's stored metadata with well-known keys (:text, :chunk_index, :document_id, and hybrid score components); the well-known keys become struct fields and the rest is kept under :metadata with keys normalized to strings.

to_map(result)

@spec to_map(t()) :: map()

The result as a plain map, for encoding, logging, or handing to a model.

Fields are listed explicitly rather than taken from the struct, so this stays a contract: a field added to the struct later is not silently exposed to everything that serializes a result, and the keys here can outlive an internal rename.

iex> result = %Arcana.SearchResult{id: "abc", text: "hello", score: 0.9}
iex> map = Arcana.SearchResult.to_map(result)
iex> {map.id, map.text, map.score, map.metadata}
{"abc", "hello", 0.9, %{}}

:metadata is passed through as stored, with string keys.