evals

package
v1.6.2 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: GPL-3.0 Imports: 33 Imported by: 0

README

Assist eval suite

Cases live in cases/e.*/case.yaml. The harness is go test ./tests/evals.

Case hygiene

Every new case must:

  1. Use the canonical schema: required tier (smoke | core | deep) and gating, explicit connections when a grader or fixture touches a live connection, tags only for --tags selection, fixtures as registry names.
  2. State the user ask as a ticket (intention text).
  3. Ship at least one outcome grader (query, rows_equal, tests_pass, outcome, or a live sling … test/run) unless tagged negative.
  4. Ship at least one mutant the required graders reject.

Unknown YAML keys fail load. TestEvalCasesValid lints the tree.

Do not put smoke or flaky in tags. Use tier and gating.

Every tier: smoke case must list noskills in arms.

Tiers

Tier Default use
smoke PR: mock all + claude on smoke
core Nightly with smoke
deep Weekly, including Lane B real APIs

--tier smoke selects only smoke. Thresholds apply to gating cases only. A baseline case id missing from the current run is removed, not failed.

Fixtures

Named datasets in fixtures/registry.yaml. --reset-fixtures drops eval_* schemas and the local TPC-H DuckDB file.

e.48 (CDC outcome) is not shipped. It needs a wal_level=logical Postgres fixture.

Arms

Arm Binary Auth Notes
mock (none) Offline invariants (passable + mutants). Always available.
claude claude ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN / ~/.claude Host HOME kept for keychain.
grok grok XAI_API_KEY / ~/.grok/auth.json Sandbox HOME.
opencode2 opencode2 provider API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, XAI_API_KEY) or ~/.local/share/opencode/auth.json OpenCode v2. Sandbox HOME + XDG pins + --standalone. Default model opencode/x-preview-f-free (seeded opencode.json). No auto-download — install the beta yourself (npm i -g opencode2 or the documented installer). Override the binary with OPENCODE2_PATH. Override the seeded model with EVAL_OPENCODE2_MODEL.
codex codex OPENAI_API_KEY / CODEX_API_KEY / ~/.codex/auth.json Codex CLI (codex exec). Sandbox CODEX_HOME. Uses the host default model from ~/.codex/config.toml (whitelist-copied model / model_provider / model_providers). MCP: [mcp_servers.sling] default_tools_approval_mode = "approve" so tools run under approval_policy = "never". Eval instructions are prepended to the exec prompt ($CODEX_HOME/AGENTS.md is not a reliable channel for codex exec). No auto-download — install Codex yourself. Override the binary with CODEX_PATH. Override the seeded model with EVAL_CODEX_MODEL.
noskills claude same as claude Skills-delta control. Smoke cases must list it.

Missing binary or login skips that arm; the suite stays green.

Pro token

api_spec test is a Pro feature. Set SLING_CLI_TOKEN in the host env before a live run. The runner passes it to each arm's MCP server: codex via [mcp_servers.sling.env] in the seeded config.toml, claude via env in .mcp.json, grok via env in .grok/config.toml, opencode2 via environment in .opencode/opencode.json. Without the token, the suite still runs (one warning at codex arm setup), but agent-side live api tests fail at the license gate and the trial is marked InfraError. Token-bearing config files (.mcp.json) are never persisted into results/.

Run

cd cmd/sling && go build .
go test ./tests/evals -count=1 -timeout 30m
go test ./tests/evals -count=1 -timeout 45m -run TestEvalAssist -- --arms mock
go test ./tests/evals -count=1 -timeout 45m -run TestEvalAssist -- --arms opencode2 --tier smoke --trials 1
go test ./tests/evals -count=1 -timeout 45m -run TestEvalAssist -- --arms codex --tier smoke --trials 1

Parallel runs lose trials to scheduler contention. After a parallel run (--parallel > 1), the suite re-runs each failed live trial once, sequentially, and the retry row replaces the parallel one (retried: true in the JSONL). Disable with --retry-failed=false.

Documentation

Index

Constants

View Source
const (
	TierSmoke = "smoke"
	TierCore  = "core"
	TierDeep  = "deep"
)

ValidTier values. Replaces the old smoke tag.

Variables

View Source
var DefaultTierCostCaps = map[string]float64{
	TierSmoke: 5,
	TierCore:  15,
	TierDeep:  40,
}

DefaultTierCostCaps is the suite-level spend cap per selected tier.

View Source
var DefaultTierThresholds = map[string]float64{
	TierSmoke: 0.90,
	TierCore:  0.80,
	TierDeep:  0.70,
}

DefaultTierThresholds apply to gating cases only.

View Source
var KnownConnections = map[string]bool{
	"POSTGRES":    true,
	"MYSQL":       true,
	"CLICKHOUSE":  true,
	"DUCKDB":      true,
	"SQLITE":      true,
	"LOCAL":       true,
	"AWS_S3_TEST": true,
	"MOCK_API":    true,
}

KnownConnections is the eval suite connection set.

View Source
var KnownGraderNames = map[string]bool{
	"file_exists":           true,
	"file_absent":           true,
	"yaml_valid":            true,
	"sling":                 true,
	"expected":              true,
	"dry_run":               true,
	"transcript_contains":   true,
	"transcript_absent":     true,
	"transcript_absent_raw": true,
	"yq":                    true,
	"sql_equiv":             true,
	"skeleton":              true,
	"outcome":               true,
	"query":                 true,
	"rows_equal":            true,
	"dag":                   true,
	"tests_pass":            true,
	"api_spec":              true,
}

KnownGraderNames is the set of grader kinds RunGraders accepts.

Functions

func CompileReplicationMap

func CompileReplicationMap(path string) (map[string]any, error)

CompileReplicationMap loads a replication YAML and expands defaults. It uses sling.LoadReplicationConfig and SetStreamDefaults (the product path). Full Compile() needs a live source conn; we fall back to defaults-only when Compile fails so mock trials stay local.

func CompileReplicationYAML

func CompileReplicationYAML(body []byte) (map[string]any, error)

CompileReplicationYAML compiles from bytes (tests / in-memory).

func EnsureEcomParquet

func EnsureEcomParquet() error

EnsureEcomParquet writes deterministic dirty ecommerce parquet if missing.

func EnsureFreshSlingBin

func EnsureFreshSlingBin(bin string) (string, error)

EnsureFreshSlingBin rebuilds cmd/sling when the binary is older than any core/ source file. A live run against a stale binary must not happen silently.

func FindSlingBin

func FindSlingBin() (string, error)

FindSlingBin locates the built sling binary.

func FormatSummary

func FormatSummary(s SuiteSummary) string

FormatSummary prints the ARM table.

func GenerateEcomParquet

func GenerateEcomParquet(dir string) error

GenerateEcomParquet writes dirty ecommerce tables as parquet via DuckDB.

func HostHasClaudeAuth

func HostHasClaudeAuth() bool

HostHasClaudeAuth is true when an env key or a host login file exists.

func HostHasCodexAuth

func HostHasCodexAuth() bool

HostHasCodexAuth is true when an env key or the host auth file exists.

func HostHasGrokAuth

func HostHasGrokAuth() bool

HostHasGrokAuth is true when an env key or host ~/.grok/auth.json exists.

func HostHasOpenCode2Auth

func HostHasOpenCode2Auth() bool

HostHasOpenCode2Auth is true when a provider env key or an opencode auth file exists. v2 stores credentials at $XDG_DATA_HOME/opencode/auth.json (default ~/.local/share/opencode/auth.json).

func LinkHostAuth

func LinkHostAuth(home, arm string) error

LinkHostAuth points the sandbox HOME at the host login files. It does not copy the full ~/.claude or ~/.grok trees.

func LoadFixtureRegistry

func LoadFixtureRegistry(path string) (map[string]FixtureDef, error)

LoadFixtureRegistry reads tests/evals/fixtures/registry.yaml.

func NewRunID

func NewRunID() string

func PreflightConns

func PreflightConns(bin string, names []string, env []string) map[string]bool

PreflightConns runs `sling conns test` for each name. Down conns are skipped, not failed.

func RunTimed

func RunTimed(name string, args []string, dir string, envv []string, timeout time.Duration) (stdout string, err error)

RunTimed starts name+args in a new process group and kills the whole group when timeout elapses. CommandContext only signals the parent. Stdout and stderr are merged (legacy graders parse the combined log).

func RunTimedStdout

func RunTimedStdout(name string, args []string, dir string, envv []string, timeout time.Duration) (stdout string, err error)

RunTimedStdout is RunTimed but keeps stderr off the returned transcript.

func SeedHome

func SeedHome(home, fixture string, noSkills bool) error

SeedHome copies fixtures/home_* and overlays embedded skills.

func SmokeFlipFail

func SmokeFlipFail(flips []Flip, gating map[string]bool) bool

SmokeFlipFail is true when any gating case went pass→fail. A "removed" flip does not fail the gate.

func TrialPasses

func TrialPasses(graders []GraderResult) bool

TrialPasses is true only when every required (non-skip, non-optional, non-judge) grader passes.

func ValidateCase

func ValidateCase(c Case, fixtures map[string]FixtureDef) []string

ValidateCase lints one loaded case. Returns error strings (empty = ok).

func WeightedScore

func WeightedScore(graders []GraderResult) float64

WeightedScore: required=1.0, optional=0.5, judge=0.5. Skip is ignored.

func YAMLMap

func YAMLMap(path string) (map[string]any, error)

YAMLMap loads a YAML file into a generic map.

Types

type ArmAvail

type ArmAvail struct {
	Name   string
	Skip   string // empty = available
	Binary string
}

ArmAvail describes whether an arm can run.

func CheckArm

func CheckArm(name string) ArmAvail

type ArmSummary

type ArmSummary struct {
	Cases    int     `json:"cases"`
	PassAt1  float64 `json:"pass_at_1"`
	PassHatK float64 `json:"pass_hat_k"`
	CostUSD  float64 `json:"cost_usd"`
	Flips    string  `json:"flips,omitempty"`
}

ArmSummary is one row of the headline table.

type Case

type Case struct {
	ID           string            `yaml:"id"`
	Task         string            `yaml:"task"`
	Tier         string            `yaml:"tier"`
	Gating       *bool             `yaml:"gating"`
	Tags         []string          `yaml:"tags"`
	Intention    string            `yaml:"intention"`
	EditPath     string            `yaml:"edit_path"`
	Arms         []string          `yaml:"arms"`
	Trials       int               `yaml:"trials"`
	BudgetUSD    float64           `yaml:"budget_usd"`
	Timeout      string            `yaml:"timeout"`
	Setup        []any             `yaml:"setup"`
	Teardown     []any             `yaml:"teardown"`
	Fixtures     []string          `yaml:"fixtures"`
	Connections  []string          `yaml:"conns"`
	Env          map[string]string `yaml:"env"`
	Artifact     string            `yaml:"artifact"`
	Graders      CaseGraders       `yaml:"graders"`
	Mock         MockHints         `yaml:"mock"`
	SeedFiles    map[string]string `yaml:"seed_files"`
	ResetSchemas []string          `yaml:"reset_schemas"`

	Path    string `yaml:"-"` // case.yaml path
	CaseDir string `yaml:"-"` // folder that holds case.yaml
}

Case is one eval YAML file.

func LoadCases

func LoadCases(dir string) ([]Case, error)

LoadCases reads tests/evals/cases/e.*/case.yaml

func SelectCases

func SelectCases(all []Case, f Flags) []Case

SelectCases filters loaded cases by flags.

func (Case) DefaultArms

func (c Case) DefaultArms() []string

func (Case) ExpectedPath

func (c Case) ExpectedPath() string

func (Case) HasTag

func (c Case) HasTag(tag string) bool

func (Case) IsGating

func (c Case) IsGating() bool

func (Case) IsNegative

func (c Case) IsNegative() bool

func (Case) IsSmoke

func (c Case) IsSmoke() bool

func (Case) MutantPaths

func (c Case) MutantPaths() []string

func (Case) TaskID

func (c Case) TaskID() string

TaskID is a report/taxonomy label. Never pass it to the CLI.

func (Case) TimeoutDur

func (c Case) TimeoutDur() time.Duration

func (Case) TrialCount

func (c Case) TrialCount(override int) int

func (Case) UsedConnections

func (c Case) UsedConnections() []string

type CaseGraders

type CaseGraders struct {
	Required []GraderSpec `yaml:"required"`
	Optional []GraderSpec `yaml:"optional"`
	Judge    []string     `yaml:"judge"`
}

CaseGraders is the graders block.

type CaseMeta

type CaseMeta struct {
	Gating bool
	Tier   string
}

CaseMeta is per-case suite metadata used during aggregation.

type CaseMetrics

type CaseMetrics struct {
	Case       string  `json:"case"`
	Arm        string  `json:"arm"`
	Trials     int     `json:"trials"`
	Passed     int     `json:"passed"`
	PassAtK    bool    `json:"pass_at_k"`
	PassHatK   bool    `json:"pass_hat_k"`
	MeanScore  float64 `json:"mean_score"`
	MeanCost   float64 `json:"mean_cost"`
	MeanDur    float64 `json:"mean_duration_s"`
	SkipReason string  `json:"skip_reason,omitempty"`
	Gating     bool    `json:"gating"`
	Tier       string  `json:"tier,omitempty"`
	Timeouts   int     `json:"timeouts,omitempty"`
}

CaseMetrics aggregates k trials for one case+arm.

func AggregateCase

func AggregateCase(caseID, arm string, trials []TrialResult, gating bool) CaseMetrics

AggregateCase computes pass@k / pass^k for one case+arm.

func AggregateCaseMeta

func AggregateCaseMeta(caseID, arm string, trials []TrialResult, meta CaseMeta) CaseMetrics

type CompareResult

type CompareResult struct {
	Path    string
	Pass    bool
	Message string
	Want    any
	Got     any
}

CompareResult is one path check.

func CompareCompiled

func CompareCompiled(actual, expected map[string]any, policy MatchPolicy) []CompareResult

CompareCompiled walks two compiled maps with the match policy.

func CompareCompiledFiles

func CompareCompiledFiles(actualPath, expectedPath string, policy MatchPolicy) ([]CompareResult, error)

CompareCompiledFiles compiles both YAML files then applies the policy.

type FixtureDef

type FixtureDef struct {
	Connection string       `yaml:"connection"`
	Requires   []string     `yaml:"requires"`
	Provision  []GraderSpec `yaml:"provision"`
	ReadyCheck ReadyCheck   `yaml:"ready_check"`
}

FixtureDef is one named dataset in fixtures/registry.yaml.

type FixtureServer

type FixtureServer struct {
	URL string
	// contains filtered or unexported fields
}

FixtureServer is the hermetic API used by spec cases.

func StartFixtureServer

func StartFixtureServer() (*FixtureServer, error)

StartFixtureServer binds 127.0.0.1:0 and serves fixture routes.

func (*FixtureServer) Close

func (fs *FixtureServer) Close()

type Flags

type Flags struct {
	Arms          []string
	Tags          []string
	Tiers         []string
	Cases         []string
	Trials        int
	Baseline      string
	MaxSuiteUSD   float64
	Parallel      int
	ResetFixtures bool
	RetryFailed   bool
}

Flags are TestEvalAssist CLI flags (args after --).

func DefaultFlags

func DefaultFlags() Flags

DefaultFlags returns suite defaults.

func ParseFlags

func ParseFlags(args []string) Flags

ParseFlags reads os.Args after --.

type Flip

type Flip struct {
	Case  string  `json:"case"`
	Arm   string  `json:"arm"`
	From  string  `json:"from"` // pass | fail
	To    string  `json:"to"`   // pass | fail | removed
	Delta float64 `json:"score_delta"`
}

Flip is a per-case pass/fail transition vs a baseline run.

func PairedDiff

func PairedDiff(current, baseline []TrialResult, gating map[string]bool) []Flip

PairedDiff reports pass→fail / fail→pass vs a baseline run. A baseline case id absent from current is "removed", not "failed".

type GradeContext

type GradeContext struct {
	WorkDir        string
	Artifact       string // relative to WorkDir
	CaseDir        string // case folder with case.yaml, expected/, mutants/
	Transcript     string
	SlingBin       string
	Env            []string
	ConnDown       map[string]bool // name → down
	SkipExecute    bool            // mock arm: do not run L5 execute graders
	FixtureBaseURL string
	FixtureToken   string
	APIPageCap     int
	APITimeout     time.Duration
}

GradeContext is the trial sandbox the graders see.

type GraderResult

type GraderResult struct {
	Name     string `json:"name"`
	Pass     bool   `json:"pass"`
	Skip     bool   `json:"skip,omitempty"`
	Optional bool   `json:"optional,omitempty"`
	Judge    bool   `json:"judge,omitempty"`
	Detail   string `json:"detail,omitempty"`
	Critique string `json:"critique,omitempty"`
}

GraderResult is one grader outcome.

func RunGraders

func RunGraders(ctx GradeContext, required, optional []GraderSpec) []GraderResult

RunGraders executes required then optional specs. Judge is separate.

func RunJudge

func RunJudge(questions []string, intention, artifact, transcript string, persistDir ...string) []GraderResult

RunJudge batches questions into one claude -p call. Results never gate. Skip when the judge binary is missing. persistDir, when set, stores the raw request and response for triage.

type GraderSpec

type GraderSpec map[string]any

GraderSpec is one YAML grader item (one primary key).

type MatchPolicy

type MatchPolicy struct {
	MustMatch    []string `yaml:"must_match"`
	MustNotExist []string `yaml:"must_not_exist"`
}

MatchPolicy is the compiled-form compare rule set.

type MockHints

type MockHints struct {
	Transcript       string `yaml:"transcript"`
	MutantTranscript string `yaml:"mutant_transcript"`
}

MockHints control the mock arm plant/transcript.

type Provisioner

type Provisioner struct {
	Bin      string
	Env      []string
	Logf     func(string, ...any)
	Registry map[string]FixtureDef
	// contains filtered or unexported fields
}

Provisioner loads and caches named fixtures once per suite.

func NewProvisioner

func NewProvisioner(bin string, env []string, logf func(string, ...any)) (*Provisioner, error)

func (*Provisioner) Ensure

func (p *Provisioner) Ensure(names []string) error

Ensure provisions names (and requires) unless ready_check already passes.

func (*Provisioner) Ready

func (p *Provisioner) Ready(name string) bool

func (*Provisioner) Reset

func (p *Provisioner) Reset() error

Reset drops eval_* schemas and the local TPC-H duckdb file.

func (*Provisioner) Skipped

func (p *Provisioner) Skipped(name string) string

type ReadyCheck

type ReadyCheck struct {
	Connection string `yaml:"connection"`
	SQL        string `yaml:"sql"`
	Equals     any    `yaml:"equals"`
}

ReadyCheck is an idempotent probe.

type ResultsWriter

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

ResultsWriter appends JSONL and can aggregate.

func NewResultsWriter

func NewResultsWriter(dir, runID string) (*ResultsWriter, error)

NewResultsWriter creates the JSONL file.

func (*ResultsWriter) Append

func (w *ResultsWriter) Append(tr TrialResult) error

func (*ResultsWriter) Path

func (w *ResultsWriter) Path() string

func (*ResultsWriter) Replace

func (w *ResultsWriter) Replace(tr TrialResult) error

Replace swaps the last row matching case+arm+trial with tr and rewrites the JSONL file.

func (*ResultsWriter) RunID

func (w *ResultsWriter) RunID() string

func (*ResultsWriter) Trials

func (w *ResultsWriter) Trials() []TrialResult

type Suite

type Suite struct {
	Flags       Flags
	Bin         string
	Writer      *ResultsWriter
	Logf        func(string, ...any)
	Server      *FixtureServer
	Provisioner *Provisioner
	// contains filtered or unexported fields
}

Suite is the eval run coordinator.

func (*Suite) RunSuite

func (s *Suite) RunSuite(cases []Case) (*SuiteSummary, error)

RunSuite executes the selected cases.

type SuiteSummary

type SuiteSummary struct {
	Run          string                `json:"run"`
	Arms         map[string]ArmSummary `json:"arms"`
	Cases        []CaseMetrics         `json:"cases"`
	Flips        []Flip                `json:"flips,omitempty"`
	Threshold    float64               `json:"threshold"`
	TierRates    map[string]float64    `json:"tier_rates,omitempty"`
	Verdict      string                `json:"verdict"`
	CostUSD      float64               `json:"cost_usd"`
	SkillsDelta  string                `json:"skills_delta,omitempty"`
	CostByFamily map[string]float64    `json:"cost_by_family,omitempty"`
	Judge        string                `json:"judge,omitempty"`
	Binary       string                `json:"binary,omitempty"`
	Timeouts     int                   `json:"timeouts,omitempty"`
	GatingN      int                   `json:"gating_n,omitempty"`
	GatingPass   int                   `json:"gating_pass,omitempty"`
	GatingRate   float64               `json:"gating_rate,omitempty"`
	SchemaReset  string                `json:"schema_reset,omitempty"`
}

SuiteSummary is printed and written as summary.json.

func AggregateSuite

func AggregateSuite(runID string, trials []TrialResult, gating map[string]bool, threshold float64) SuiteSummary

AggregateSuite groups trials into case metrics and arm headlines.

func AggregateSuiteMeta

func AggregateSuiteMeta(runID string, trials []TrialResult, meta map[string]CaseMeta, threshold float64, tierThresholds map[string]float64) SuiteSummary

type TrialResult

type TrialResult struct {
	Run           string         `json:"run"`
	Case          string         `json:"case"`
	Arm           string         `json:"arm"`
	Trial         int            `json:"trial"`
	Invariant     string         `json:"invariant,omitempty"` // passable | sensitivity (mock)
	Pass          bool           `json:"pass"`
	Score         float64        `json:"score"`
	Graders       []GraderResult `json:"graders"`
	CostUSD       float64        `json:"cost_usd"`
	DurationS     float64        `json:"duration_s"`
	Turns         int            `json:"turns,omitempty"`
	PromptMD5     string         `json:"prompt_md5,omitempty"`
	SkillsVersion string         `json:"skills_version,omitempty"`
	Model         string         `json:"model,omitempty"`
	SkipReason    string         `json:"skip_reason,omitempty"`
	Error         string         `json:"error,omitempty"`
	InfraError    bool           `json:"infra_error,omitempty"`
	Retried       bool           `json:"retried,omitempty"`
	Timeout       bool           `json:"timeout,omitempty"`
	Binary        string         `json:"binary,omitempty"`
	Transcript    string         `json:"-"`
}

TrialResult is one JSONL line.

func LoadJSONL

func LoadJSONL(path string) ([]TrialResult, error)

LoadJSONL reads a results file.

Jump to

Keyboard shortcuts

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