Documentation
¶
Overview ¶
Package eval measures how well SpecCritic reviews specifications whose defects are known in advance.
The corpus holds clean specifications. Each comes with mutations: small edits that seed one known defect each, labeled with the category and severity a reviewer should report and the lines it should point at. A clean spec and its seeded twin are reviewed side by side, so the eval sees both whether real defects are found and whether sound text is left alone.
Index ¶
- type Anchor
- type Base
- type Case
- type CaseVerdicts
- type CategoryStats
- type Config
- type Defect
- type DefectGrade
- type DefectStats
- type Edit
- type Finding
- type Grade
- type LineRange
- type Mutation
- type Proportion
- type Reviewer
- type RunError
- type RunResult
- type Summary
- type UnmatchedFinding
- type UsageStats
- type Variant
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Anchor ¶
type Anchor struct {
// Text selects the one line containing it.
Text string `json:"text,omitempty"`
// Section selects every line under the heading with this exact title, up
// to the next heading of the same or a higher level.
Section string `json:"section,omitempty"`
}
Anchor names lines of a spec by content, so labels survive edits that shift line numbers. Exactly one field is set.
type Base ¶
type Base struct {
ID string `json:"id"`
// Profile is the SpecCritic profile the spec is reviewed under.
Profile string `json:"profile"`
// Provenance says where the spec and its labels came from, so a reader
// knows how far to trust them.
Provenance string `json:"provenance"`
Mutations []Mutation `json:"mutations"`
// Text is the clean spec.
Text string `json:"-"`
}
Base is one clean specification and the defects that can be seeded into it.
func LoadCorpus ¶
LoadCorpus reads every base in dir: a spec NAME.md with its labels in NAME.defects.json.
type Case ¶
type Case struct {
// ID is unique in a run and safe to use as a file name.
ID string `json:"id"`
Base string `json:"base"`
Variant Variant `json:"variant"`
Profile string `json:"profile"`
// Spec is the text to review.
Spec string `json:"-"`
// Defects are the seeded defects. A clean case has none.
Defects []Defect `json:"defects"`
}
Case is one spec to review together with what a correct review reports.
func BuildCases ¶
BuildCases derives the cases to review from bases. Every base yields a clean case and a seeded case with all of its mutations. With isolated set, it also yields one case per mutation, which shows which single defect was missed at the cost of that many more reviews.
type CaseVerdicts ¶
type CaseVerdicts struct {
Case string `json:"case"`
Verdicts map[schema.Verdict]int `json:"verdicts"`
// Agreement is the share of runs that returned the most common verdict.
Agreement float64 `json:"agreement"`
}
CaseVerdicts says how the verdict of one case varied across its runs.
type CategoryStats ¶
type CategoryStats struct {
Category schema.Category `json:"category"`
// Recall is seeded defects of this category that were found.
Recall Proportion `json:"recall"`
// Precision is model issues reported under this category that matched a
// seeded defect. An issue matching no label counts against it even if it
// is a real defect the labels do not list, so it is a lower bound.
Precision Proportion `json:"precision"`
}
CategoryStats holds the per-category rates.
type Config ¶
type Config struct {
// Runs is how many times each case is reviewed. Verdict agreement needs
// at least two.
Runs int
// Concurrency is how many reviews run at once.
Concurrency int
// Tolerance is how many lines a finding may be off a defect's evidence
// and still be counted as pointing at it.
Tolerance int
// Timeout bounds one review. A review that exceeds it is recorded as an
// error, never as a review that found nothing.
Timeout time.Duration
// OutDir receives the specs reviewed, every report, and the results.
OutDir string
// Logf, when set, receives one line per finished review.
Logf func(format string, args ...any)
}
Config controls an eval run.
type Defect ¶
type Defect struct {
ID string `json:"id"`
Note string `json:"note"`
Categories []schema.Category `json:"categories"`
Severity schema.Severity `json:"severity"`
Keywords []string `json:"keywords"`
// Ranges are the lines a finding about this defect is expected to cite.
Ranges []LineRange `json:"ranges"`
}
Defect is a seeded defect located in the spec of one case.
type DefectGrade ¶
type DefectGrade struct {
ID string `json:"id"`
// Category is the defect's primary category.
Category schema.Category `json:"category"`
Expected schema.Severity `json:"expected_severity"`
// Found is true when at least one finding matched the defect.
Found bool `json:"found"`
// FoundByModel is true when a finding from the model, not preflight,
// matched.
FoundByModel bool `json:"found_by_model"`
// CategoryOK is true when a matching issue carried an accepted category.
CategoryOK bool `json:"category_ok"`
// Reported is the highest severity among the matching findings.
Reported schema.Severity `json:"reported_severity,omitempty"`
}
DefectGrade says how a review did on one seeded defect.
type DefectStats ¶
type DefectStats struct {
Base string `json:"base"`
Defect string `json:"defect"`
Category schema.Category `json:"category"`
Expected schema.Severity `json:"expected_severity"`
Runs int `json:"runs"`
Found int `json:"found"`
ByModel int `json:"found_by_model"`
CategoryOK int `json:"category_ok"`
SeverityOK int `json:"severity_ok"`
}
DefectStats says how often one seeded defect was found across its runs.
type Edit ¶
type Edit struct {
// Find is replaced by Replace.
Find string `json:"find,omitempty"`
Replace string `json:"replace,omitempty"`
// DeleteLine removes the whole line containing this text.
DeleteLine string `json:"delete_line,omitempty"`
// ReplaceLine replaces the whole line containing this text with With.
ReplaceLine string `json:"replace_line,omitempty"`
// InsertAfterLine adds With as a new line after the line containing this
// text.
InsertAfterLine string `json:"insert_after_line,omitempty"`
With string `json:"with,omitempty"`
}
Edit is one change to a spec. Exactly one of Find, DeleteLine, ReplaceLine and InsertAfterLine is set, and the text it names must occur exactly once.
type Finding ¶
type Finding struct {
// ID is the report's ID for the issue or question.
ID string `json:"id"`
Question bool `json:"question,omitempty"`
Severity schema.Severity `json:"severity"`
// Category is empty for a question.
Category schema.Category `json:"category,omitempty"`
Title string `json:"title"`
Ranges []LineRange `json:"ranges"`
// Preflight is true for a finding the preflight rules produced.
Preflight bool `json:"preflight,omitempty"`
// Defect is the ID of the seeded defect this finding was matched to, or
// empty when it matches none.
Defect string `json:"defect,omitempty"`
// CategoryOK is true when Category is one the matched defect accepts.
CategoryOK bool `json:"category_ok,omitempty"`
// contains filtered or unexported fields
}
Finding is one issue or question from a report, reduced to what grading needs.
type Grade ¶
type Grade struct {
Defects []DefectGrade `json:"defects"`
Findings []Finding `json:"findings"`
}
Grade is the result of comparing one report with one case.
func GradeReport ¶
GradeReport matches the findings of report to the seeded defects of c.
A finding matches a defect when it cites a line within tolerance lines of the defect's evidence and is about the same thing: its category is one the defect accepts, or its text contains one of the defect's keywords. Location alone is not enough, because an unrelated complaint about the same line would otherwise earn credit. Several findings may match one defect; they are repeats, not errors.
type Mutation ¶
type Mutation struct {
ID string `json:"id"`
// Note explains, for a human reader, what is wrong after the mutation.
Note string `json:"note"`
// Categories are the defect categories that correctly describe the
// defect. The first is the primary one, used to group results.
Categories []schema.Category `json:"categories"`
// Severity is the severity a reviewer should assign.
Severity schema.Severity `json:"severity"`
// Keywords are lowercase fragments, any one of which marks a finding as
// being about this defect when its category differs from Categories.
Keywords []string `json:"keywords"`
Edits []Edit `json:"edits"`
// Evidence names where in the mutated spec a finding about this defect
// is expected to point.
Evidence []Anchor `json:"evidence"`
}
Mutation seeds one defect into a base spec.
type Proportion ¶
type Proportion struct {
Hits int `json:"hits"`
Total int `json:"total"`
Rate float64 `json:"rate"`
Low float64 `json:"low"`
High float64 `json:"high"`
}
Proportion is a count of hits out of a total with its 95% Wilson interval. The interval says how far the rate could move on a rerun of the same size.
func (Proportion) String ¶
func (p Proportion) String() string
String renders a proportion as "7/9 (78%, 45-94%)".
type Reviewer ¶
Reviewer reviews the spec of one case, stored at specPath, and returns the report. An error means the review produced nothing to grade.
type RunError ¶
type RunError struct {
Case string `json:"case"`
Rep int `json:"rep"`
Error string `json:"error"`
WallMS int64 `json:"wall_ms"`
}
RunError is a review that produced nothing to grade: a provider failure, a timeout, or output that stayed invalid. It is kept apart from the results so that a broken run is never read as a review that found nothing.
type RunResult ¶
type RunResult struct {
Case string `json:"case"`
Base string `json:"base"`
Variant Variant `json:"variant"`
// Rep numbers the repeats of a case from 0.
Rep int `json:"rep"`
// Model is the model that answered, as the provider reported it.
Model string `json:"model"`
Verdict schema.Verdict `json:"verdict"`
Score int `json:"score"`
Grade Grade `json:"grade"`
// Usage is what the review's LLM calls used.
Usage *schema.UsageMeta `json:"usage,omitempty"`
DroppedFindings int `json:"dropped_findings"`
// WallMS is how long the whole review took, preflight included.
WallMS int64 `json:"wall_ms"`
}
RunResult is one graded review of one case.
type Summary ¶
type Summary struct {
Models []string `json:"models"`
// Runs counts graded reviews. Errors counts reviews with nothing to grade.
Runs int `json:"runs"`
Errors int `json:"errors"`
// Recall is seeded defects found, over every seeded and isolated run.
Recall Proportion `json:"recall"`
// ModelRecall counts only defects a model finding matched, leaving out
// those caught solely by the deterministic preflight rules.
ModelRecall Proportion `json:"model_recall"`
// CriticalRecall is defects labeled CRITICAL that were found and reported
// as CRITICAL. A miss here lets a blocking defect through the gate.
CriticalRecall Proportion `json:"critical_recall"`
// CategoryAgreement is found defects reported under an accepted category.
CategoryAgreement Proportion `json:"category_agreement"`
// SeverityAgreement is found defects reported at the labeled severity.
SeverityAgreement Proportion `json:"severity_agreement"`
// Precision is model findings on seeded specs that matched a label. See
// CategoryStats.Precision for why it is a lower bound.
Precision Proportion `json:"precision"`
Categories []CategoryStats `json:"categories"`
// FalseCritical is clean-spec runs that reported at least one CRITICAL.
FalseCritical Proportion `json:"false_critical"`
// CleanCriticalMean is the mean number of CRITICAL findings per clean run.
CleanCriticalMean float64 `json:"clean_critical_mean"`
// VerdictAgreement is the mean, over cases, of CaseVerdicts.Agreement.
VerdictAgreement float64 `json:"verdict_agreement"`
// VerdictExpected is runs whose verdict fits the case: INVALID for a spec
// seeded with a CRITICAL defect, anything else for a clean one.
VerdictExpected Proportion `json:"verdict_expected"`
Verdicts []CaseVerdicts `json:"verdicts"`
Usage UsageStats `json:"usage"`
Defects []DefectStats `json:"defects"`
Unmatched []UnmatchedFinding `json:"unmatched"`
}
Summary is the outcome of an eval run.
func Run ¶
Run reviews every case cfg.Runs times, grades the reports, and writes to cfg.OutDir:
specs/CASE.md the spec each case reviewed reports/CASE_repN.json every report, for tracing a surprising grade results.jsonl one graded review per line errors.jsonl reviews that produced nothing to grade summary.json, summary.md the metrics
type UnmatchedFinding ¶
type UnmatchedFinding struct {
Case string `json:"case"`
Rep int `json:"rep"`
Severity schema.Severity `json:"severity"`
Category schema.Category `json:"category,omitempty"`
Title string `json:"title"`
Ranges []LineRange `json:"ranges"`
}
UnmatchedFinding is a model finding on a seeded or clean spec that matches no label. It is either a false positive or a real defect the labels miss; only reading it tells which.
type UsageStats ¶
type UsageStats struct {
Calls int `json:"calls"`
RepairCalls int `json:"repair_calls"`
ContinuationCalls int `json:"continuation_calls"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
DroppedFindings int `json:"dropped_findings"`
WallMS int64 `json:"wall_ms"`
}
UsageStats totals what the graded reviews used.
type Variant ¶
type Variant string
Variant names how a case's spec was derived from its base.
const ( // VariantClean is the base spec unchanged. It should draw no CRITICAL. VariantClean Variant = "clean" // VariantSeeded is the base spec with every mutation applied. VariantSeeded Variant = "seeded" // VariantIsolated is the base spec with exactly one mutation applied. VariantIsolated Variant = "isolated" )