eval

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package eval replays recorded incident cases through the investigation loop and scores whether the agent identifies the root cause — a reproducible RCA benchmark (cf. ITBench). A case records the evidence each tool returns, so the eval measures the model+loop's reasoning over fixed evidence, independent of a live cluster.

Index

Constants

This section is empty.

Variables

View Source
var Rubric = []Dimension{
	{"root_cause", 3},
	{"evidence", 3},
	{"solution", 3},
	{"description", 3},
	{"calibration", 2},
}

Rubric is the RCA-quality grading rubric (matches the design spec §5).

Functions

func GateError

func GateError(c Campaign, failUnder float64) error

GateError returns a non-nil error when the campaign pass-rate is below failUnder (which is only enforced when failUnder > 0). The message names the cases that did not reach RCA and any flaky cases, so CI logs explain the failure.

func RubricMax

func RubricMax() int

RubricMax is the maximum total score across all dimensions.

func WriteCase

func WriteCase(dir string, c Case) error

WriteCase writes a Case as <dir>/<name>.yaml.

Types

type Call

type Call struct {
	Name   string
	Args   string
	Output string
	Err    string
}

Call is one recorded tool invocation during a live investigation.

type Campaign

type Campaign struct {
	N          int
	Aggregates []CaseAggregate
}

Campaign is the aggregate of a multi-repeat replay run.

func (Campaign) FlakyNames

func (c Campaign) FlakyNames() []string

FlakyNames lists cases whose repeats disagreed too much to trust.

func (Campaign) JSON

func (c Campaign) JSON() ([]byte, error)

JSON renders the campaign as an indented report for CI artifacts.

func (Campaign) PassRate

func (c Campaign) PassRate() float64

PassRate is the fraction of cases that reached RCA (0 for empty).

func (Campaign) ReachedCases

func (c Campaign) ReachedCases() int

ReachedCases counts cases whose pass-rate met the k-of-n bar.

type Case

type Case struct {
	Name     string            `yaml:"name"`
	Prompt   string            `yaml:"prompt"` // the incident description (seeds the loop)
	Tools    map[string]string `yaml:"tools"`  // tool name -> recorded evidence the tool returns
	Expected Expected          `yaml:"expected"`
}

Case is one replayable incident.

func Load

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

Load reads every *.yaml / *.yml case in dir.

func RecordedCase

func RecordedCase(scn Scenario, calls []Call) Case

RecordedCase converts a live run's recorded tool calls into a replayable Case (the existing examples/eval format). v1 keeps the LAST output per tool: the replay staticTool returns one fixed output per tool regardless of args, so multi-call tools are flattened. submit_findings is excluded (it is the model's own output, not evidence).

type CaseAggregate

type CaseAggregate struct {
	Name        string
	Runs        int
	PassRate    float64  // fraction of repeats whose Result.Pass is true
	Reached     bool     // PassRate >= evalMinPassRate
	Flaky       bool     // PassRate in (1-evalMinPassRate, evalMinPassRate): runs disagree
	Confidence  float64  // median confidence over repeats
	Missing     []string // union of missing keywords/entities across repeats
	OverClaimed []string // union of over-claimed distractors across repeats
}

CaseAggregate is the k-of-n verdict for one case over N replay repeats.

type Coverage

type Coverage struct {
	Touched     []string // mandatory source groups actually exercised
	Missing     []string // mandatory groups never touched
	Bonus       []string // optional groups touched
	CrossSignal bool     // >=2 distinct source groups exercised
	ToolErrors  []string // distinct tool names that returned an error
	Ratio       float64  // |touched| / |expected|  (1.0 when no expected sources)
}

Coverage is the deterministic data-source coverage result for one run.

func ScoreCoverage

func ScoreCoverage(expected, optional []string, calls []Call) Coverage

ScoreCoverage computes coverage of the mandatory expected sources from the recorded calls. optional sources count as Bonus and never affect Ratio.

type Dimension

type Dimension struct {
	Key string
	Max int
}

Dimension is one rubric axis and its max score.

type Expected

type Expected struct {
	MustContain       []string `yaml:"must_contain"`        // keywords that must appear in the findings (recall, over full findings text)
	MinConfidence     float64  `yaml:"min_confidence"`      // confidence floor (0 = no floor)
	RootCauseEntities []string `yaml:"root_cause_entities"` // entities that MUST be named as the cause (entity recall, over claim text)
	Distractors       []string `yaml:"distractors"`         // plausible-but-wrong entities that must NOT be blamed (over-claim/FP); only evaluated when root_cause_entities is non-empty
}

Expected is the RCA scoring spec for a case.

type GroundTruth

type GroundTruth struct {
	RootCause       string   `yaml:"root_cause"`
	ExpectedSources []string `yaml:"expected_sources"` // MANDATORY data-source groups -> coverage gate
	OptionalSources []string `yaml:"optional_sources"` // bonus if touched, never gates
	ExpectedAction  string   `yaml:"expected_action"`
	MustReachRoot   bool     `yaml:"must_reach_root"`
}

GroundTruth is the human-authored truth a scenario is graded against.

type Judge

type Judge interface {
	Grade(ctx context.Context, scn Scenario, inv providers.Investigation) (Verdict, error)
}

Judge grades an investigation against a scenario's ground truth.

type LiveReport

type LiveReport struct {
	At      string       `json:"at"`
	N       int          `json:"n"`   // runs per scenario (live mode); 0 in replay mode
	Ran     int          `json:"ran"` // scenarios actually investigated (not skipped)
	Passed  int          `json:"passed"`
	Skipped int          `json:"skipped"`
	Results []LiveResult `json:"results"`
}

LiveReport is the serializable output of one live-fire campaign run.

func NewLiveReport

func NewLiveReport(at string, n int, results []LiveResult) LiveReport

NewLiveReport tallies results into a report.

func (LiveReport) JSON

func (rep LiveReport) JSON() []byte

JSON is the machine-readable sibling of the markdown report.

func (LiveReport) Markdown

func (rep LiveReport) Markdown() string

Markdown renders the human report: summary, per-scenario table, coverage heatmap.

func (LiveReport) RegressionsVS

func (rep LiveReport) RegressionsVS(prev LiveReport) []string

RegressionsVS returns scenarios that passed in prev but fail/skip now.

type LiveResult

type LiveResult struct {
	Scenario       string
	Skipped        bool
	SkipReason     string
	Runs           []RunOutcome
	CoverageRatio  float64        // median
	DimMedian      map[string]int // median per rubric dimension
	DimVariance    map[string]float64
	ConfidentWrong bool     // any run confident-wrong
	Flaky          bool     // root_cause scores vary too much across runs to trust
	ToolErrors     []string // union across runs
	Pass           bool
}

LiveResult aggregates the N runs of one scenario.

type LiveRunner

type LiveRunner struct {
	Model     providers.ModelProvider
	BaseTools []investigate.Tool
	Judge     Judge
	Steps     StepRunner
	Log       *slog.Logger
	N         int                    // runs per scenario (default 1 if 0)
	OnRecord  func(Scenario, []Call) // optional: persist the run's calls (replay corpus)
	Recall    *investigate.Recall    // optional; when set, runOnce takes the instant-recall short-circuit (production path). nil ⇒ no recall.
}

LiveRunner runs scenarios against real tools, grading coverage + RCA quality. BaseTools and Model are the LIVE tools/model (built by cmd/lore via buildModelAndTools); Judge uses a separate, stronger model.

func (*LiveRunner) RunScenario

func (lr *LiveRunner) RunScenario(ctx context.Context, scn Scenario) LiveResult

RunScenario runs setup (or precheck), N investigations, judging each, then always tears down. Pass gate: at least evalMinPassRate of runs reach root_cause >= evalRootCauseBar, coverage median == 1.0, no confident-wrong run, and root_cause variance within evalMaxRootCauseVariance (not flaky).

type ModelJudge

type ModelJudge struct {
	Model providers.ModelProvider
}

ModelJudge grades with an LLM (use a stronger model than the one under test).

func (ModelJudge) Grade

Grade builds a blind grading prompt and parses the JSON verdict.

type Recorder

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

Recorder collects tool calls made during one investigation run. Safe for concurrent use (the loop is sequential today, but tools may fan out later).

func (*Recorder) Calls

func (r *Recorder) Calls() []Call

Calls returns a copy of the recorded calls in order.

type Report

type Report struct {
	Results []Result
}

Report aggregates case results.

func (Report) Passed

func (r Report) Passed() int

Passed counts cases whose root cause was identified.

func (Report) RCARate

func (r Report) RCARate() float64

RCARate is the fraction of cases whose root cause was identified.

type Result

type Result struct {
	Name        string
	Pass        bool
	Confidence  float64
	Missing     []string // expected keywords/entities not found (or an error note); includes "over-claimed: <e>" markers
	OverClaimed []string // distractor entities the investigation wrongly blamed (over-claim/FP)
}

Result is the score for one case.

func Score

func Score(name string, inv providers.Investigation, exp Expected) Result

Score reports whether the investigation identifies the expected root cause. Keyword recall (must_contain) is matched over the full findings text. Entity scoring — recall over root_cause_entities and an over-claim penalty over distractors — is matched over the CLAIM text only (what was blamed), and engages only when root_cause_entities is set. A case passes when nothing is missing, no distractor was blamed, and confidence meets the floor.

type RunOutcome

type RunOutcome struct {
	Investigation providers.Investigation
	Coverage      Coverage
	Verdict       Verdict
}

RunOutcome is one of the N runs of a scenario.

type Runner

type Runner struct {
	Model providers.ModelProvider
	Log   *slog.Logger
}

Runner replays cases through the investigation loop with a given model.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, cases []Case) Report

Run replays every case and scores it.

func (*Runner) RunN

func (r *Runner) RunN(ctx context.Context, cases []Case, n int) Campaign

RunN replays every case n times and returns the aggregated campaign.

type Scenario

type Scenario struct {
	ID          string      `yaml:"id"`
	Category    string      `yaml:"category"` // what-changed | saturation | network | cloud | dependency | cert | dns | storage | instant-recall
	Description string      `yaml:"description"`
	Invasive    bool        `yaml:"invasive"` // true => has setup/teardown; false => natural failure
	Precheck    string      `yaml:"precheck"` // optional shell; non-zero exit => SKIP (natural scenarios)
	Setup       []string    `yaml:"setup"`    // shell steps (kubectl/flux) to induce the fault
	Trigger     Trigger     `yaml:"trigger"`
	GroundTruth GroundTruth `yaml:"ground_truth"`
	Teardown    []string    `yaml:"teardown"` // shell steps to revert; always run
}

Scenario is one live-fire eval case: an induced-or-natural failure, how to trigger an investigation, and the ground truth to grade against. It is a superset of Case (which is the recorded/replay form this produces).

func LoadScenarios

func LoadScenarios(dir string) ([]Scenario, error)

LoadScenarios reads every *.yaml / *.yml scenario in dir.

type StepRunner

type StepRunner interface {
	Run(ctx context.Context, step string) error
}

StepRunner executes a scenario's shell setup/teardown/precheck steps. The real implementation shells out (kubectl/flux); tests use a fake.

type Trigger

type Trigger struct {
	Mode      string `yaml:"mode"`      // "cli" (default) | "webhook"
	Symptom   string `yaml:"symptom"`   // free-text incident description
	Namespace string `yaml:"namespace"` // affected namespace (optional)
}

Trigger describes how the investigation is started.

type Verdict

type Verdict struct {
	Scores         map[string]int `json:"scores"`
	ConfidentWrong bool           `json:"confident_wrong"`
	Rationale      string         `json:"rationale"`
}

Verdict is the judge's structured grade for one investigation.

func (Verdict) Total

func (v Verdict) Total() int

Total sums the dimension scores.

Jump to

Keyboard shortcuts

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