eval

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package eval provides an offline retrieval-quality harness: it runs a golden dataset (query → expected relevant sources) through a Retriever and reports the standard ranking metrics — Recall@k, Mean Reciprocal Rank (MRR) and nDCG@k — so that changes to the retrieval stack (fusion, reranking, grounding) can be validated objectively rather than by feel.

The metric functions are pure and dependency-free (this file), so they run in the short unit suite. The end-to-end evaluation of a real Codex against a dataset lives in the integration tests, gated behind an env var like the other Ollama-backed tests.

Index

Constants

This section is empty.

Variables

View Source
var DefaultKs = []int{1, 3, 5, 10}

DefaultKs are the cut-offs reported by default (Recall@1/3/5/10, nDCG@1/3/5/10).

Functions

func AnswerExactMatch

func AnswerExactMatch(pred string, golds []string) float64

AnswerExactMatch is 1.0 when the prediction, after normalisation, equals any of the gold answers, else 0.0. With no gold answers it returns 0.

func AnswerF1

func AnswerF1(pred string, golds []string) float64

AnswerF1 is the maximum SQuAD token-overlap F1 between the prediction and any gold answer: with p = |common|/|pred| and r = |common|/|gold| over the multiset of normalised tokens, F1 = 2pr/(p+r). It credits partial answers that a strict Exact Match misses. Two empty (after normalisation) strings count as a perfect match — matching the SQuAD convention for yes/no and no-answer cases — while an empty prediction against a non-empty gold (or vice-versa) scores 0.

func FirstRelevantRank

func FirstRelevantRank(retrieved, relevant []string) int

FirstRelevantRank returns the 1-indexed rank of the first relevant document in the retrieved list, or -1 when none of them is relevant.

func NDCGAtK

func NDCGAtK(retrieved, relevant []string, k int) float64

NDCGAtK is the normalised Discounted Cumulative Gain at cut-off k for binary relevance: DCG@k / IDCG@k, where each relevant document contributes a gain of 1 discounted by log2(position+1). It rewards placing relevant documents higher and, unlike Recall@k, is sensitive to their exact rank. It returns 0 when there are no relevant documents.

func NormalizeAnswer

func NormalizeAnswer(s string) string

NormalizeAnswer applies the canonical SQuAD answer normalisation before comparison: lower-case, drop punctuation, drop the articles a/an/the, and collapse runs of whitespace. This makes "The Eiffel Tower." and "eiffel tower" compare equal, so EM/F1 measure answer content rather than surface formatting.

func PrecisionAtK

func PrecisionAtK(retrieved, relevant []string, k int) float64

PrecisionAtK is the fraction of the top-k retrieved results that are relevant: |relevant ∩ top-k| / k'. It is the counterpart of Recall@k on the precision axis — where recall asks "did we find the gold documents", precision asks "of what we returned, how much is gold". It matters for pipelines that deliberately return a small, high-purity evidence set (e.g. grounded iterative retrieval), whose value recall@k understates. The denominator k' is min(k, len(retrieved)), so a run that returns fewer than k results is not penalised for the empty slots. It returns 0 when nothing was retrieved or there are no relevant documents.

func RecallAtK

func RecallAtK(retrieved, relevant []string, k int) float64

RecallAtK is the fraction of the relevant documents that appear within the top-k retrieved results: |relevant ∩ top-k| / |relevant|. With a single relevant document it degrades to the binary "is the target in the top-k" measure. It returns 0 when there are no relevant documents (nothing to find) and clamps k to a non-negative value.

func ReciprocalRank

func ReciprocalRank(retrieved, relevant []string) float64

ReciprocalRank is 1/rank of the first relevant document, or 0 when none of the relevant documents was retrieved. It is the per-query term averaged into the Mean Reciprocal Rank.

func Subsample

func Subsample(corpus *Corpus, dataset *Dataset, maxQueries, maxDocs int) (*Corpus, *Dataset)

Subsample builds a smaller, self-consistent corpus + query set for cheap evaluation on a machine that cannot embed a full BEIR corpus: it keeps the first maxQueries queries, ALL of their relevant documents (so every kept query stays answerable), then fills the corpus with further documents as distractors up to maxDocs. Unlike Corpus.Truncate (which keeps the first N documents and would drop the scattered gold documents of a BEIR dataset), it is gold-aware. A non-positive bound disables that limit; gold documents are always included even if they exceed maxDocs. Selection order is deterministic.

Types

type Corpus

type Corpus struct {
	Name      string     `json:"name,omitempty"`
	Documents []Document `json:"documents"`
}

Corpus is the set of documents an evaluation indexes before scoring queries.

func LoadCorpus

func LoadCorpus(path string) (*Corpus, error)

LoadCorpus reads a JSON corpus from the given file path.

func ReadCorpus

func ReadCorpus(r io.Reader) (*Corpus, error)

ReadCorpus decodes a JSON corpus from r.

func (*Corpus) Merge

func (c *Corpus) Merge(others ...*Corpus)

Merge appends the documents of others into c, de-duplicating by Source (the first occurrence wins). It is used to combine per-language corpora into a single index.

func (*Corpus) Sources

func (c *Corpus) Sources() map[string]struct{}

Sources returns the set of document source identifiers in the corpus.

func (*Corpus) Truncate

func (c *Corpus) Truncate(n int)

Truncate keeps at most the first n documents (insertion order). A non-positive n leaves the corpus unchanged. Pair it with Dataset.KeepAnswerable to drop queries whose passage was removed.

type Dataset

type Dataset struct {
	Name    string  `json:"name,omitempty"`
	Queries []Query `json:"queries"`
}

Dataset is a named collection of golden queries.

func LoadDataset

func LoadDataset(path string) (*Dataset, error)

LoadDataset reads a JSON dataset from the given file path.

func LoadDatasetFS

func LoadDatasetFS(fsys fs.FS, path string) (*Dataset, error)

LoadDatasetFS reads a JSON dataset from a file system (e.g. an embed.FS), so fixtures can be embedded into a binary or test.

func ReadDataset

func ReadDataset(r io.Reader) (*Dataset, error)

ReadDataset decodes a JSON dataset from r.

func (*Dataset) KeepAnswerable

func (ds *Dataset) KeepAnswerable(sources map[string]struct{}) *Dataset

KeepAnswerable returns a copy of the dataset keeping only the queries whose every relevant source is present in the given set — i.e. questions that are actually answerable against the (possibly truncated) corpus. Queries with no relevant sources are dropped.

func (*Dataset) Truncate

func (ds *Dataset) Truncate(n int)

Truncate keeps at most the first n queries. A non-positive n leaves the dataset unchanged.

type Document

type Document struct {
	Source  string `json:"source"`
	Title   string `json:"title,omitempty"`
	Content string `json:"content"`
	Lang    string `json:"lang,omitempty"`
}

Document is one indexable unit of an evaluation corpus: the passage/document that queries are expected to retrieve. Source is the identifier that a Query's RelevantSources refer to (and that a Retriever returns), so the two must agree.

type Query

type Query struct {
	// ID is a stable identifier for the query, used to label per-query results
	// and to segment metrics. Optional but recommended.
	ID string `json:"id,omitempty"`
	// Query is the search string submitted to the retriever.
	Query string `json:"query"`
	// Lang is the ISO language code of the query (e.g. "fr", "en", "es"), used
	// to segment metrics by language.
	Lang string `json:"lang,omitempty"`
	// RelevantSources lists the source identifiers expected in the results.
	RelevantSources []string `json:"relevant_sources"`
	// Answers lists the acceptable gold answer strings for the query, used only by
	// the optional generation (reader) evaluation to score answer EM/F1. Empty for
	// pure retrieval datasets; a query may have several equally-correct answers
	// (SQuAD) or a single one (HotpotQA).
	Answers []string `json:"answers,omitempty"`
	// Tags optionally categorise the query (intent, complexity, domain) for
	// segmented reporting.
	Tags []string `json:"tags,omitempty"`
}

Query is one golden evaluation case: a natural-language query and the set of document source identifiers that are considered relevant answers to it. The identifiers must match whatever the Retriever returns (for a Codex-backed retriever, the document Source URL as a string).

type QueryReport

type QueryReport struct {
	QueryID string
	Query   string
	Lang    string
	Tags    []string
	// TargetRank is the 1-indexed rank of the first relevant document, or -1 if
	// none was retrieved.
	TargetRank     int
	ReciprocalRank float64
	RecallAtK      map[int]float64
	NDCGAtK        map[int]float64
	PrecisionAtK   map[int]float64
}

QueryReport holds the metrics computed for a single query.

type Report

type Report struct {
	Dataset    string
	NumQueries int
	Ks         []int
	// MRR is the mean reciprocal rank across all queries.
	MRR float64
	// RecallAtK, NDCGAtK and PrecisionAtK are the dataset-averaged metrics per
	// cut-off.
	RecallAtK    map[int]float64
	NDCGAtK      map[int]float64
	PrecisionAtK map[int]float64
	PerQuery     []QueryReport
}

Report aggregates the metrics over a whole dataset run.

func Evaluate

func Evaluate(ctx context.Context, ds *Dataset, r Retriever, ks ...int) (*Report, error)

Evaluate runs every query in the dataset through the retriever and returns the aggregated report. It fetches the largest requested cut-off worth of results per query. ks defaults to DefaultKs when empty. A retriever error aborts the run (a query with no results is not an error — it simply scores 0).

func (*Report) ByLang

func (r *Report) ByLang() map[string]*Report

ByLang segments the report by query language.

func (*Report) BySegment

func (r *Report) BySegment(key func(QueryReport) string) map[string]*Report

BySegment partitions the report's queries by the given key function and re-aggregates each partition into its own sub-report. Segments with an empty key are grouped under "" and can be ignored by the caller. It is the basis for query-type analysis (by language, intent, complexity, ...).

func (*Report) String

func (r *Report) String() string

String renders the aggregate report as a compact, human-readable table, suitable for logging in a manual evaluation run.

type Retriever

type Retriever interface {
	Retrieve(ctx context.Context, query string, k int) ([]string, error)
}

Retriever is the minimal contract the harness evaluates: given a query and a cut-off k, return up to k document source identifiers ranked best-first. It is deliberately decoupled from *Codex so the harness can score any retrieval implementation (a Codex, a single index, a baseline BM25, a mock).

func FromSearchResults

func FromSearchResults(search func(ctx context.Context, query string, k int) ([]*index.SearchResult, error)) Retriever

FromSearchResults adapts a search function returning ranked index.SearchResult (such as Codex.Search) into a Retriever, using each result's Source as the identifier. Results with a nil Source are skipped.

type RetrieverFunc

type RetrieverFunc func(ctx context.Context, query string, k int) ([]string, error)

RetrieverFunc adapts a function to the Retriever interface.

func (RetrieverFunc) Retrieve

func (fn RetrieverFunc) Retrieve(ctx context.Context, query string, k int) ([]string, error)

Retrieve implements Retriever.

Directories

Path Synopsis
Package beir loads datasets in the BEIR format (the de-facto information retrieval benchmark) into an amoxtli evaluation corpus + query set.
Package beir loads datasets in the BEIR format (the de-facto information retrieval benchmark) into an amoxtli evaluation corpus + query set.
Package hfqa loads Hugging Face extractive-QA datasets in the SQuAD JSON format into an amoxtli evaluation corpus + query set.
Package hfqa loads Hugging Face extractive-QA datasets in the SQuAD JSON format into an amoxtli evaluation corpus + query set.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL