bench

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package bench measures whether the distillation is actually worth doing.

This is the core deliverable, not an afterthought. The claim is that an agent given an artifact answers better, faster and far more cheaply than the same agent given the raw page, and a claim like that is worth exactly as much as the harness behind it.

What is measured, and what each number is allowed to mean

Token cost      Input tokens the API actually charged. A measurement, not
                an estimate: both conditions report usage from the same API.
Time to answer  Wall clock for the answering call.
Accuracy        Graded against hand-written ground truth, 0 to 1.
Coverage        Share of ground-truth facts present in the artifact at all.
                This is the *real* coverage number -- the one measured where
                the right answer is known. The per-artifact self-audit
                deliberately calls its figure "graph retention" instead,
                because that one compares output against what the capture
                observed and would cheerfully report 100% of a page it only
                half saw.
Fidelity        Share of artifact statements verifiable in the source. A
                distiller that invents content is worse than no distiller,
                so this one gates the release.
Stability       Distill twice, compare. Reported separately for the tier
                decision and for the content, because a tool that wavers
                between tiers and one that extracts inconsistently are
                different failures with different causes.

Index

Constants

View Source
const (
	TargetTokenReduction = 0.90
	TargetAccuracyGain   = 0.20
	TargetCoverage       = 0.90
	// A distiller that invents content is worse than no distiller, so this is
	// the highest bar of the four and the one that gates the release.
	TargetFidelity = 0.98
)

Targets are the success criteria for v1.

Variables

This section is empty.

Functions

This section is empty.

Types

type Answer

type Answer struct {
	QuestionID string    `json:"question_id"`
	Condition  Condition `json:"condition"`
	Text       string    `json:"text"`
	Usage      llm.Usage `json:"usage"`
	LatencyMS  int64     `json:"latency_ms"`
	Refused    bool      `json:"refused,omitempty"`
	Error      string    `json:"error,omitempty"`

	// Accuracy and FactsFound are filled by the grader.
	Accuracy    float64  `json:"accuracy"`
	FactsFound  []string `json:"facts_found,omitempty"`
	FactsMissed []string `json:"facts_missed,omitempty"`
	GraderNote  string   `json:"grader_note,omitempty"`

	// Graded records that a grade was actually reached.
	//
	// An answer that was produced but never graded -- the grader rate limited,
	// out of quota, or returning unparseable JSON -- has an Accuracy of zero
	// that means "not measured", not "wrong". The distinction is the same one
	// Error draws for the answering call, and it was missing here: a run that
	// answered every question and graded none reported an accuracy of 0.000
	// for both conditions, which reads as total failure of the extraction when
	// nothing had been measured at all.
	Graded bool `json:"graded"`
}

Answer is one question answered under one condition.

type Band

type Band string

Band groups questions by what they test.

const (
	// BandFactual: what is the founding year, what materials are listed.
	BandFactual Band = "factual"
	// BandStructural: what sections exist, what is the navigation hierarchy.
	BandStructural Band = "structural"
	// BandActionable: how does a visitor make an enquiry, what fields are required.
	BandActionable Band = "actionable"
)

type CallFailure

type CallFailure struct {
	Reason string `json:"reason"`
	Count  int    `json:"count"`
}

CallFailure is one reason calls failed, and how many times it did.

type Condition

type Condition string

Condition names one side of the comparison.

const (
	// ConditionRaw gives the agent the served HTML, as an unaided agent would
	// have to read it.
	ConditionRaw Condition = "raw"
	// ConditionArtifact gives the agent the distilled Markdown.
	ConditionArtifact Condition = "artifact"
)

type ConditionMetrics

type ConditionMetrics struct {
	Questions int `json:"questions"`
	// Answered is how many questions actually produced an answer. It differs
	// from Questions whenever a call failed, and the accuracy below is the mean
	// over these rather than over all of them.
	Answered int `json:"answered"`
	// Scored is how many of those answers were graded, and Ungraded how many
	// were not. Accuracy is the mean over Scored: an answer the grader never
	// reached is excluded rather than counted as wrong.
	Scored         int              `json:"scored"`
	Ungraded       int              `json:"ungraded"`
	MeanAccuracy   float64          `json:"mean_accuracy"`
	MeanInputToks  float64          `json:"mean_input_tokens"`
	TotalInputToks int64            `json:"total_input_tokens"`
	MeanLatencyMS  float64          `json:"mean_latency_ms"`
	Refusals       int              `json:"refusals"`
	Errors         int              `json:"errors"`
	ByBand         map[Band]float64 `json:"accuracy_by_band"`
}

ConditionMetrics aggregates one side of the comparison.

type CoverageResult

type CoverageResult struct {
	Coverage float64     `json:"coverage"`
	Total    int         `json:"total"`
	Found    int         `json:"found"`
	Facts    []FactCheck `json:"facts"`
}

CoverageResult is the whole check.

func CheckCoverage

func CheckCoverage(set *Set, g *graph.Graph) CoverageResult

CheckCoverage measures coverage without calling a model.

Coverage is pure string matching against the artifact, so it never needed a provider -- but it was only reachable by running the full benchmark, which does. Anyone writing a question set therefore had to spend a complete graded run to discover that a fact was worded in a way the page never uses, and several such mistakes were only found by hand-editing a temporary test. A set is a test; checking a test should be free.

type FactCheck

type FactCheck struct {
	QuestionID string `json:"question_id"`
	Fact       string `json:"fact"`
	Present    bool   `json:"present"`
	// Missing lists the fact's distinctive words that appear nowhere in the
	// artifact. It is what turns "coverage 0.81" into something actionable:
	// usually either a real gap in the extraction or a fact whose wording was
	// never on the page to begin with.
	Missing []string `json:"missing,omitempty"`
}

FactCheck is one ground-truth fact and whether the artifact carries it.

type Input

type Input struct {
	Set *Set
	// Artifact is the distilled graph.
	Artifact *graph.Graph
	// RawHTML is what an unaided agent would have had to read.
	RawHTML string
}

Input is what a run needs.

type Options

type Options struct {
	Model       string
	GraderModel string
	APIKey      string
	// Budget caps total tokens across the whole run.
	Budget int64
	// Concurrency bounds simultaneous API calls.
	Concurrency int
	// BaseURL points the run at an OpenAI-compatible provider. Empty means
	// Anthropic, or whatever LLM_BASE_URL names.
	BaseURL string
	// RawContextTokens caps how much of the raw page the control is given.
	//
	// Without a cap the control simply errors on any large page, so the
	// benchmark could measure everything except the case the tool exists for:
	// pear.no serves around four hundred and seventy thousand tokens and no
	// model accepts it. Truncating models what an unaided agent actually gets
	// -- the top of the document and no more -- and the report says how much
	// was left behind, so nobody mistakes the handicap for a result.
	RawContextTokens int
	// GraderRepeats re-grades a sample of answers to measure how much the
	// grader agrees with itself. Zero disables it.
	GraderRepeats int
	Logf          func(format string, args ...any)
}

Options configures a run.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns usable settings.

The model is left empty rather than pinned to the Anthropic default, because naming it here would override whatever provider the user configured. It used to be pinned, and the effect was that pointing sieve at another provider sent that provider a model name it had never heard of: a request to Groq asking for claude-opus-5, and a 404 that read like a broken build rather than a misconfiguration. Resolution belongs in one place, and that place is the client, which knows which provider it is talking to.

type Question

type Question struct {
	ID   string `yaml:"id" json:"id"`
	Band Band   `yaml:"band" json:"band"`
	Ask  string `yaml:"ask" json:"ask"`
	// Expect is the ground-truth answer, written by hand against the real page.
	Expect string `yaml:"expect" json:"expect"`
	// Facts are the atomic claims a correct answer must contain. They are what
	// coverage is measured against, and they are why coverage means something
	// here and not in the self-audit.
	Facts []string `yaml:"facts" json:"facts"`
}

Question is one item in a question set.

type Report

type Report struct {
	Target      string    `json:"target"`
	GeneratedAt time.Time `json:"generated_at"`
	Model       string    `json:"model"`
	GraderModel string    `json:"grader_model"`
	ContentHash string    `json:"content_hash"`
	Tier        string    `json:"tier"`

	// RawTokens is the size of the page an unaided agent would have been
	// handed, and RawTokensSent is how much of it actually fitted.
	//
	// These differ on exactly the pages sieve exists for. A context ceiling is
	// not a limitation of this benchmark, it is the condition the control is
	// under in real use -- an agent given a four-hundred-thousand-token page
	// does not read four hundred thousand tokens of it either. Recording both
	// is what keeps that honest rather than hidden.
	RawTokens     int  `json:"raw_tokens"`
	RawTokensSent int  `json:"raw_tokens_sent"`
	RawTruncated  bool `json:"raw_truncated"`

	// RawVisibleChars is how much readable text the served page carried, with
	// markup and scripts removed. It decides which criteria can be judged at
	// all: a page that serves nothing can neither verify the artifact nor be
	// reduced in size.
	RawVisibleChars int `json:"raw_visible_chars"`

	Answers []Answer `json:"answers"`

	// CallFailures groups the distinct reasons calls failed, commonest first.
	//
	// A failure count on its own is a dead end. The reason is always in the
	// report's per-answer records, but a reader watching the terminal sees only
	// a number, and the difference between a wrong model name, an expired key
	// and a rate limit is the difference between a five-second fix and an
	// afternoon. The provider already says which it is; this is only a matter
	// of not throwing it away.
	CallFailures []CallFailure `json:"call_failures,omitempty"`

	Metrics struct {
		Raw      ConditionMetrics `json:"raw"`
		Artifact ConditionMetrics `json:"artifact"`
	} `json:"metrics"`

	// Coverage is measured against ground-truth facts, not against what the
	// capture happened to observe.
	Coverage float64 `json:"coverage"`
	// Fidelity is the share of sampled artifact statements verifiable in the
	// source. It gates the release: a distiller that invents content is worse
	// than no distiller.
	Fidelity float64 `json:"fidelity"`
	// FidelityMeasured distinguishes a fidelity of zero from a check that could
	// not run. They are opposite conclusions -- one says the artifact invented
	// everything, the other that nothing could be verified -- and collapsing
	// them into a single number condemned sieve for extracting content which
	// was correctly absent from the served HTML.
	FidelityMeasured bool     `json:"fidelity_measured"`
	FidelityNotes    []string `json:"fidelity_notes,omitempty"`

	// GraderAgreement is how often the grader gave the same verdict when asked
	// twice about the same answer. It is the error bar on every accuracy figure
	// above: a grader that disagrees with itself a fifth of the time cannot
	// support a claim of a five-point difference, and without measuring it
	// there is no way to know which claims are safe.
	GraderAgreement float64 `json:"grader_agreement,omitempty"`
	GraderRegraded  int     `json:"grader_regraded,omitempty"`

	// Stability is reported only when a stability run was requested.
	Stability *Stability `json:"stability,omitempty"`

	// Verdict states plainly whether the run met the success criteria.
	Verdict Verdict `json:"verdict"`
}

Report is a complete benchmark run.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner executes a benchmark.

func NewRunner

func NewRunner(opts Options) (*Runner, error)

NewRunner builds a runner.

func (*Runner) Model

func (r *Runner) Model() string

Model reports the model the runner resolved to, so a caller can name it before the run rather than after.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, in Input) (*Report, error)

Run answers every question under both conditions and grades the results.

func (*Runner) Usage

func (r *Runner) Usage() (answer, grader llm.Usage)

Usage reports what the run cost.

type Set

type Set struct {
	URL       string     `yaml:"url" json:"url"`
	Name      string     `yaml:"name" json:"name"`
	Questions []Question `yaml:"questions" json:"questions"`
}

Set is a question set for one target.

func LoadSet

func LoadSet(path string) (*Set, error)

LoadSet reads a question set from YAML or JSON.

type Stability

type Stability struct {
	// TierStable reports whether both runs chose the same escalation tier.
	// It is separate from content stability on purpose: a tool that wavers
	// between tiers and one that extracts inconsistently are different bugs.
	TierStable bool   `json:"tier_stable"`
	TierA      string `json:"tier_a"`
	TierB      string `json:"tier_b"`

	// HashStable reports whether the semantic content hash matched.
	HashStable bool `json:"hash_stable"`
	// BlockAgreement is the share of blocks present in both runs with identical
	// text. A page that is genuinely personalised will score low here, and
	// correctly so.
	BlockAgreement float64  `json:"block_agreement"`
	BlocksA        int      `json:"blocks_a"`
	BlocksB        int      `json:"blocks_b"`
	OnlyInA        []string `json:"only_in_a,omitempty"`
	OnlyInB        []string `json:"only_in_b,omitempty"`
}

Stability is what two runs of the same URL produced.

func MeasureStability

func MeasureStability(a, b *graph.Graph) Stability

MeasureStability compares two distillations of the same URL.

type Verdict

type Verdict struct {
	TokenReduction float64 `json:"token_reduction"`
	// AccuracyGain is the mean difference over questions answered under both
	// conditions, not the difference of the two means. See judge.
	AccuracyGain float64 `json:"accuracy_gain"`
	// ComparedQuestions is how many questions that pairing covered.
	ComparedQuestions int  `json:"compared_questions"`
	MetTokenTarget    bool `json:"met_token_target"`
	MetAccuracyTarget bool `json:"met_accuracy_target"`
	MetCoverageTarget bool `json:"met_coverage_target"`
	MetFidelityTarget bool `json:"met_fidelity_target"`
	// TokenReductionApplies is false on a page that served no readable text,
	// where there was nothing to reduce. MetTokenTarget is then true by
	// vacancy rather than by achievement, and this is how to tell them apart.
	TokenReductionApplies bool `json:"token_reduction_applies"`

	Passed  bool   `json:"passed"`
	Summary string `json:"summary"`
	// Notes record criteria that did not apply, so a pass is never mistaken
	// for having cleared a bar that was never raised.
	Notes []string `json:"notes,omitempty"`
}

Verdict states whether the run met the criteria for a release.

Jump to

Keyboard shortcuts

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