eval

package
v0.1.16 Latest Latest
Warning

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

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

Documentation

Overview

Package eval is the scientific measurement instrument for the AI schema- generation layer (AI-F2-S1): a stratified NL→schema gold test set + a paired ablation harness + statistics with the rigor the research demands. It measures the loop's real behavior on THIS domain (typed JSON schemas with FKs/RBAC/state machines) — a number published nowhere; every figure in the literature is an extrapolation from text-to-SQL/code-gen. It builds NO new generation technique; it is the instrument those techniques are measured with.

stats.go is the statistics core (pure Go, no deps):

  • Wilson score interval for a proportion (NOT Wald — Wald under-covers near 0/1).
  • McNemar's paired test (Dietterich 1998: the only test with acceptable Type I error for run-once algorithms; never a paired t-test or a difference of proportions). EXACT binomial when discordants b+c < 25, Edwards continuity- corrected χ² otherwise.
  • Cochran's Q omnibus for >2 paired conditions, with Holm-Bonferroni FWER control over the pairwise McNemar family.

E[iterations] is measured EMPIRICALLY by the harness (the loop is validator- guided, not i.i.d., so the geometric 1/p_sem is only a theoretical bound, marked as such — see report.go).

Index

Constants

View Source
const CorpusNote = "40/stratum (5x the original seed) — a substantial scale-up that narrows the " +
	"Wilson intervals, still below the ~120-160/stratum ideal for full power; an underpowered " +
	"McNemar (few discordants) remains EXPECTED and is flagged INCONCLUSIVE, not sold as significant"

CorpusNote is the honest one-liner about the corpus's statistical limits. At 40/stratum it is a 5x scale-up from the original seed (a real narrowing of the Wilson intervals) but still ~1/3 of the ~120-160/stratum the research wants for full power, so a McNemar with few discordants is still flagged INCONCLUSIVE.

View Source
const TargetPerStratum = 120

TargetPerStratum is the case count per stratum the research says is needed to detect a p_sem shift of 0.70→0.80 at 80% power — the seed is well below it, on purpose, and the harness grows to it without changing.

View Source
const Z95 = 1.959963984540054

Z95 is the standard normal quantile for a two-sided 95% interval.

Variables

View Source
var Strata = []string{"simple", "media", "compleja"}

Strata are the complexity tiers the research requires (to locate where a cheap model crosses the competence threshold). Ordered simple → complex.

Functions

func CorpusCounts

func CorpusCounts(cases []Case) map[string]int

CorpusCounts returns the number of cases per stratum.

func Format

func Format(a Analysis) string

Format renders the human report. It is explicit about statistical power: an underpowered McNemar (discordants < 25) is flagged INCONCLUSIVE, never sold as significant — the instrument does not lie about its own power.

func HolmBonferroni

func HolmBonferroni(p []float64) []float64

HolmBonferroni returns the Holm-adjusted p-values aligned to the input order, controlling the family-wise error rate (step-down). Adjusted values are monotone non-decreasing in the sorted order and capped at 1.

func WilsonInterval

func WilsonInterval(successes, n int, z float64) (phat, lo, hi float64)

WilsonInterval returns the point estimate and the Wilson score interval [lo, hi] for `successes` out of `n` at confidence implied by z (use Z95). For n == 0 it returns the whole [0,1]. The interval is clamped to [0,1] and never degenerate at the boundaries the way Wald is.

Types

type Analysis

type Analysis struct {
	Mode         string          `json:"mode"` // "SIMULATED" | "LIVE"
	Model        string          `json:"model"`
	Counts       map[string]int  `json:"counts"`
	TotalCases   int             `json:"total_cases"`
	Conditions   []string        `json:"conditions"`
	PerCondition []CondStats     `json:"per_condition"`
	Pairwise     []PairTest      `json:"pairwise"`
	Cochran      *CochranQResult `json:"cochran_q,omitempty"`
	Note         string          `json:"note"`
}

Analysis is the full statistical readout.

func Analyze

func Analyze(outcomes []Outcome, conds []Condition, mode, model string, cases []Case) Analysis

Analyze turns paired outcomes into the full statistical readout.

type Case

type Case struct {
	ID          string          `json:"id"`
	Stratum     string          `json:"stratum"`
	Domain      string          `json:"domain"`
	Description string          `json:"description"`
	Gold        json.RawMessage `json:"gold"`
}

Case is one curated pair: a natural-language app description and the GOLD schema — a hand-written, validate-clean Appximo schema capturing that intent. The gold is correct by construction (a test asserts every gold validates).

func LoadCorpus

func LoadCorpus() ([]Case, error)

LoadCorpus reads every embedded case, sorted by (stratum, id) for deterministic order (reproducibility — the same run twice yields the same sequence).

func SampleStratified

func SampleStratified(cases []Case, n int) []Case

SampleStratified returns at most n cases per stratum, taking the first n by the deterministic (stratum, id) order LoadCorpus already guarantees. n <= 0 returns all cases unchanged. Used to bound a cost-/rate-limited --live run to a stratified subsample while the full corpus stays the standing measurement set.

type ClientFactory

type ClientFactory func(c Case, cond Condition) aigen.ModelClient

ClientFactory builds the ModelClient for a (case, condition). Live mode returns the real Anthropic client (temperature 0 for determinism); the demonstration mode returns a deterministic SimulatedClient. This is the seam that lets the harness run with NO API key (reproducible) and against a real model identically.

type CochranQResult

type CochranQResult struct {
	Q  float64 `json:"q"`
	DF int     `json:"df"`
	P  float64 `json:"p_value"`
	K  int     `json:"k"` // number of conditions
	N  int     `json:"n"` // number of cases (rows)
}

CochranQResult is the omnibus test across k>=2 paired conditions.

func CochranQ

func CochranQ(rows [][]bool) CochranQResult

CochranQ runs Cochran's Q over `rows`, where each row is one case's binary outcome under each of the k conditions (same column order every row). Concordant rows (all-success or all-failure) contribute nothing, exactly as in McNemar.

type CondStats

type CondStats struct {
	Condition       string  `json:"condition"`
	Stratum         string  `json:"stratum,omitempty"` // "" = overall
	N               int     `json:"n"`
	FirstTrySuccess int     `json:"first_try_success"`
	Phat            float64 `json:"p_sem"`
	WilsonLo        float64 `json:"wilson_lo"`
	WilsonHi        float64 `json:"wilson_hi"`
	MeanIter        float64 `json:"mean_iter"`
	MedianIter      float64 `json:"median_iter"`
	NonConverged    int     `json:"non_converged"`
	TheoIIDBound    float64 `json:"theoretical_iid_bound"`
	MeanStructErr0  float64 `json:"mean_struct_err0"`
	MeanSemErr0     float64 `json:"mean_sem_err0"`
	MeanCostUSD     float64 `json:"mean_cost_usd"`
	// StructuredEngaged / ArrayIREngaged count outcomes whose final decoding
	// actually used structured / array-IR (vs fell back to plain). For the plain arm
	// these are 0 by design; for the structured/array-IR arms a value BELOW n means
	// the live API rejected the constrained request and the arm silently measured
	// plain — the honest signal that the structural foundation did not engage.
	StructuredEngaged int `json:"structured_engaged"`
	ArrayIREngaged    int `json:"array_ir_engaged"`
}

CondStats are the per-condition (optionally per-stratum) figures: p_sem with a Wilson interval, and the EMPIRICAL iteration distribution (mean/median) — never the geometric 1/p_sem, which assumes i.i.d. retries the validator-guided loop is not. TheoIIDBound carries 1/p_sem ONLY as the labeled "independent retries" reference, for contrast.

type Condition

type Condition struct {
	Name    string
	Options aigen.Options
}

Condition is one arm of the paired ablation — a named treatment plus the aigen.Options that realize it. New techniques (array-IR, constraint-aware, RAG) plug in as NEW conditions; the harness, the outcomes, and the statistics do not change. That genericity is the point: every future technique is measured against the baseline with McNemar on THIS domain, or discarded.

func BaselineConditions

func BaselineConditions() []Condition

BaselineConditions are the three arms: plain generation, the AI-F1-S1 structured-ENVELOPE decoding, and the AI-F2-S2 array-IR (structured DEEP) decoding. With three arms Cochran's Q + Holm engage automatically; the canonical per-stratum first-vs-last pair becomes plain vs array-IR (the full effect).

type McNemarResult

type McNemarResult struct {
	B          int     `json:"b"`
	C          int     `json:"c"`
	Discordant int     `json:"discordant"`
	Stat       float64 `json:"stat"`    // χ² statistic (0 for the exact path)
	P          float64 `json:"p_value"` // two-sided
	Method     string  `json:"method"`  // "exact-binomial" | "chi2-edwards" | "no-discordants"
}

McNemarResult is the outcome of a paired McNemar test on two conditions. B = cases where A succeeded and B failed; C = A failed and B succeeded. The power lives in the discordants (B+C); Concordant pairs carry no information.

func McNemar

func McNemar(b, c int) McNemarResult

McNemar runs the paired test. With fewer than 25 discordants it uses the EXACT two-sided binomial (p = 0.5); otherwise the Edwards continuity-corrected χ² with one degree of freedom.

func (McNemarResult) Underpowered

func (m McNemarResult) Underpowered() bool

Underpowered reports whether the discordant count is too small for the χ² approximation to be trustworthy — the instrument must say so rather than present a fragile p-value as conclusive.

type Outcome

type Outcome struct {
	CaseID      string  `json:"case_id"`
	Stratum     string  `json:"stratum"`
	Condition   string  `json:"condition"`
	FirstTry    bool    `json:"first_try"`    // valid at attempt 1 (the primary paired binary outcome)
	Converged   bool    `json:"converged"`    // valid within the iteration budget
	Iterations  int     `json:"iterations"`   // EMPIRICAL iterations to valid (not 1/p_sem)
	StructErrs0 int     `json:"struct_errs0"` // structural errors at attempt 1
	SemErrs0    int     `json:"sem_errs0"`    // semantic errors at attempt 1
	Refused     bool    `json:"refused"`
	InputTok    int     `json:"input_tokens"`
	OutputTok   int     `json:"output_tokens"`
	CostUSD     float64 `json:"cost_usd"`
	// EffStructured / EffArrayIR record the decoding that ACTUALLY ran for the
	// converged/final attempt — NOT what the condition requested. They differ when
	// the constrained-decoding request was rejected by the live API and the loop
	// fell back to plain generation (the live finding: the strict-outputs subset
	// rejects the Appximo grammar — open objects need additionalProperties:false,
	// and the field grammar exceeds the 16-union-parameter limit — so the structured
	// and array-IR arms fall back to plain). Surfacing this keeps the instrument
	// honest: an arm that fell back is measuring plain, not its named treatment.
	EffStructured bool `json:"eff_structured"`
	EffArrayIR    bool `json:"eff_array_ir"`
}

Outcome is one (case × condition) measurement — the paired observational unit.

func RunAblation

func RunAblation(ctx context.Context, cases []Case, conds []Condition, factory ClientFactory, model string) ([]Outcome, error)

RunAblation evaluates every case under every condition, in deterministic order, and returns the paired outcomes. The same cases run under each condition, so the outcomes are PAIRED by case id — the structure McNemar/Cochran require.

type PairTest

type PairTest struct {
	A       string        `json:"a"`
	B       string        `json:"b"`
	Stratum string        `json:"stratum,omitempty"`
	Result  McNemarResult `json:"result"`
	HolmP   float64       `json:"holm_p,omitempty"`
}

PairTest is a paired McNemar comparison between two conditions (optionally within one stratum), with its Holm-adjusted p when part of a family.

type SimulatedClient

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

── deterministic demonstration driver ─────────────────────────────────────

SimulatedClient is a deterministic ModelClient that lets the instrument run end-to-end WITHOUT an API key, for demonstrating + testing the harness. It is NOT a real measurement — real p_sem requires a real model (temperature 0). Its outcomes are a deterministic function of (case id, condition, attempt), modeling the documented mechanism with a FAITHFUL structural-depth split so the three arms genuinely differ (the point of measuring array-IR):

  • `plain` can emit BOTH a shallow ENVELOPE structural fault (unknown top-level key, bad const) AND a DEEP structural fault (a field's type outside the set).
  • `structured` (AI-F1-S1 envelope) removes the ENVELOPE class but can STILL emit a DEEP structural fault — the strict subset cannot reach the map-keyed depth.
  • `array-IR` (AI-F2-S2) removes BOTH classes by construction (an array of fixed items constrains the depth too), leaving only the SEMANTIC, cross-reference class — the hypothesis the harness measures: deep p_struct→1.

The semantic-fault probability is shared by all arms (no decoder constrains cross-reference semantics). The report labels this mode SIMULATED, loudly.

IMPORTANT (AI-F2-S3): this simulator ASSUMES the structured/array-IR decoders actually engage. The real --live run showed they DON'T on the current Anthropic API (the strict-outputs subset rejects the envelope's open objects and the IR's >16 union params, so both fall back to plain). So these arm differences are a demonstration of the MECHANISM, not a prediction of live behavior — the live numbers (and the EffStructured/EffArrayIR engagement counts) are the truth.

func NewSimulatedClient

func NewSimulatedClient(c Case, cond Condition) *SimulatedClient

NewSimulatedClient builds the demonstration driver for one (case, condition), deriving each arm's structural coverage from its Options: plain emits both fault depths, structured removes only the envelope, array-IR removes both.

func (*SimulatedClient) Complete

Complete returns the (faithfully fault-injected) schema for this attempt. In the array-IR arm it emits the IR FORM (the loop transforms IR→map); the other arms emit the map form. Each fault class fires independently per the arm's coverage, so the validator + the real loop react to exactly what a model of that capability would produce — the harness measures the real loop over the simulated model.

Jump to

Keyboard shortcuts

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