Documentation
¶
Overview ¶
Package eval provides an evaluation harness for LoopKit loops. It runs a dataset of test cases through a loop definition, scores the outputs, computes aggregates, and integrates with go test via RunSuite.
Usage:
suite := eval.Suite{
Name: "my-suite",
Dataset: eval.Dataset{...},
Run: myRunFunc,
Scorers: []eval.Scorer{eval.ExactMatch, eval.RubricFunc(...)},
Parallelism: 4,
}
report, err := suite.Execute(ctx)
Index ¶
- func WriteBaseline(path string, report Report) error
- type Aggregate
- type Case
- type CaseResult
- type Dataset
- type ExactMatchScorer
- type Gate
- type JudgeRunFunc
- type LLMJudgeScorer
- type RegressionCase
- type RegressionReport
- type Report
- type RubricFunc
- type RunFunc
- type Score
- type ScoredRun
- type Scorer
- type StructuralScorer
- type Suite
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func WriteBaseline ¶
WriteBaseline writes a report to path as the new baseline. Call this after a successful gate pass to update the baseline.
Types ¶
type Aggregate ¶
type Aggregate struct {
// ScorerName is the name of the scorer.
ScorerName string `json:"scorer_name"`
// Mean is the average score across all cases (excluding errored cases).
Mean float64 `json:"mean"`
// PassRate is the fraction of cases with score >= 0.5 (by default).
PassRate float64 `json:"pass_rate"`
// NumCases is the total number of cases evaluated.
NumCases int `json:"num_cases"`
// NumErrors is the number of cases that errored during scoring.
NumErrors int `json:"num_errors"`
}
Aggregate contains per-scorer aggregate statistics.
type Case ¶
type Case struct {
// ID is the unique identifier for this case.
ID string `json:"id"`
// Input is the input to the loop (arbitrary JSON-serializable value).
Input any `json:"input"`
// Expect is the expected output (arbitrary JSON-serializable value).
Expect any `json:"expect"`
// Tags are optional labels for filtering.
Tags []string `json:"tags,omitempty"`
}
Case is a single evaluation case.
type CaseResult ¶
type CaseResult struct {
// CaseID is the case identifier.
CaseID string `json:"case_id"`
// Scores maps scorer name to score value.
Scores map[string]Score `json:"scores"`
// Error is non-empty if the case run failed.
Error string `json:"error,omitempty"`
}
CaseResult is the per-case evaluation result.
type ExactMatchScorer ¶
type ExactMatchScorer struct{}
ExactMatchScorer scores 1.0 if the actual output exactly equals the expected output (by JSON round-trip equality), and 0.0 otherwise.
func (ExactMatchScorer) Name ¶
func (ExactMatchScorer) Name() string
type Gate ¶
type Gate struct {
// MinMean is the minimum acceptable mean score per scorer (0..1).
// If any scorer's mean falls below this threshold, the test fails.
// Zero means no minimum.
MinMean float64
// MaxRegression is the maximum allowed mean score drop vs baseline (0..1).
// If any scorer regresses by more than this amount, the test fails.
// Zero means any regression fails.
MaxRegression float64
// Baseline is a path to a baseline report JSON file (from a previous run).
// If empty, regression checking is disabled.
Baseline string
}
Gate defines the pass/fail criteria for an eval suite in go test.
type JudgeRunFunc ¶
JudgeRunFunc is the function that runs the LLM judge for a single case. It receives the judge context (case + run output) and returns a score in [0, 1]. Implementations typically run a LoopKit loop with the rubric as system prompt.
type LLMJudgeScorer ¶
type LLMJudgeScorer struct {
// contains filtered or unexported fields
}
LLMJudgeScorer runs a judge model as a LoopKit loop to score each case. The judge runs with its own budget; judge overspend causes score=0 (not panic). The judge's provider and rubric prompt are configured at construction time.
func (*LLMJudgeScorer) Name ¶
func (j *LLMJudgeScorer) Name() string
type RegressionCase ¶
type RegressionCase struct {
CaseID string `json:"case_id"`
ScorerName string `json:"scorer_name"`
OldScore float64 `json:"old_score"`
NewScore float64 `json:"new_score"`
Delta float64 `json:"delta"` // new - old (negative = regression)
}
RegressionCase is a case that regressed between old and new reports.
type RegressionReport ¶
type RegressionReport struct {
// Regressions is the list of cases that regressed (new score < old score).
Regressions []RegressionCase `json:"regressions"`
// Improvements is the list of cases that improved (new score > old score).
Improvements []RegressionCase `json:"improvements"`
// MeanDelta maps scorer name to mean score delta (new - old).
MeanDelta map[string]float64 `json:"mean_delta"`
}
RegressionReport compares two reports and identifies regressions.
func Compare ¶
func Compare(old, new Report) RegressionReport
Compare computes the regression between old and new reports.
type Report ¶
type Report struct {
// SuiteName is the suite's name.
SuiteName string `json:"suite_name"`
// Cases contains per-case results, sorted by CaseID.
Cases []CaseResult `json:"cases"`
// Aggregates contains per-scorer aggregate statistics.
Aggregates []Aggregate `json:"aggregates"`
// TotalDuration is how long the suite took.
TotalDuration time.Duration `json:"total_duration_ns"`
// GeneratedAt is the timestamp of this report.
GeneratedAt time.Time `json:"generated_at"`
}
Report is the result of executing a Suite.
func ReportFromJSON ¶
ReportFromJSON deserializes a Report from JSON bytes.
func RunSuite ¶
RunSuite executes the suite and applies the gate, calling t.Fatal on breach. If baseline is configured and exists, it also checks for regressions. The new report is written to t.TempDir()/report.json for CI artifact capture.
func (Report) MarshalJSON ¶
MarshalJSON provides a stable JSON schema for Report.
type RubricFunc ¶
RubricFunc creates a Scorer from a simple scoring function. The function receives the case and run result and returns a Score.
type Score ¶
type Score struct {
// Value is in [0, 1].
Value float64
// Label is a human-readable description of the score.
Label string
// Details is optional additional detail.
Details string
}
Score is the result of a scorer for one case. 0=worst, 1=best.
type ScoredRun ¶
type ScoredRun struct {
// CaseID is the case that was run.
CaseID string
// ActualOutput is what the loop returned (JSON-serializable).
ActualOutput any
// ActualJSON is the JSON-encoded actual output.
ActualJSON json.RawMessage
// Error is non-nil if the run itself failed (not a scoring failure).
Error error
}
ScoredRun is the result of running a single case.
type Scorer ¶
type Scorer interface {
// Name returns the scorer's identifier.
Name() string
// Score evaluates one case. Returns Score and any error.
// A scorer error causes the case to receive score 0 with error details.
Score(ctx context.Context, c Case, run ScoredRun) (Score, error)
}
Scorer evaluates a single case's output.
var ExactMatch Scorer = ExactMatchScorer{}
ExactMatch is a pre-built ExactMatchScorer ready for use.
var Structural Scorer = StructuralScorer{}
Structural is a pre-built StructuralScorer ready for use.
func NewLLMJudge ¶
func NewLLMJudge(name, rubric string, judgeFunc JudgeRunFunc) Scorer
NewLLMJudge creates an LLMJudgeScorer. name is the scorer name. rubric is the system prompt for the judge. judgeFunc is the implementation that runs the judge model (allows dogfooding).
func NewRubricFunc ¶
func NewRubricFunc(name string, fn RubricFunc) Scorer
NewRubricFunc creates a named Scorer from a function.
type StructuralScorer ¶
type StructuralScorer struct{}
StructuralScorer scores 1.0 if all expected fields are present in the actual output (subset match — actual may have extra fields). Uses JSON structural comparison.
func (StructuralScorer) Name ¶
func (StructuralScorer) Name() string
type Suite ¶
type Suite struct {
// Name is the suite's identifier.
Name string
// Dataset is the set of cases to evaluate.
Dataset Dataset
// Run is the function that runs each case.
Run RunFunc
// Scorers is the set of scorers to apply to each case's output.
Scorers []Scorer
// Parallelism controls how many cases run concurrently. 0 or 1 = sequential.
Parallelism int
}
Suite is a named collection of evaluation cases and scorers.