aicheck

package
v0.0.0-...-7eb2361 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package aicheck evaluates a flow's AI checks: the `expect:` block of request and graphql steps (a JSON schema, latency, time-to-first-token and token budgets, a G-Eval rubric judge). It runs after a flow's steps finish, so checks never add to step or flow timings. Ported from the Stresseur CLI's stress/internal/expect with its behavior; see docs/specs/AI_CHECKS.md.

Index

Constants

View Source
const (
	KindSchema  = "schema"
	KindLatency = "latency"
	KindTTFT    = "ttft"
	KindTokens  = "tokens"
	KindJudge   = "judge"
)

Check kinds, in the order they are evaluated and printed.

View Source
const (
	ProviderAnthropic = "anthropic"
	ProviderOpenAI    = "openai"
)

Providers.

View Source
const (
	DefaultSchemaAt = "response.body"
	DefaultUsage    = "response.body.usage"
	// StreamUsage is the usage a streamed response reported; the fallback when usage: is unset.
	StreamUsage = "response.usage"
)

Default paths of an expect: block.

View Source
const (
	StatusPassed  = "passed"
	StatusSkipped = "skipped"
	StatusWarn    = "warn"
	StatusFailed  = "failed"
)

Statuses, worst last.

View Source
const DefaultCachePath = ".devtools/judge-cache.json"

DefaultCachePath is the judge cache, relative to the working directory. The key is the Stresseur CLI's, so both can share one file (Stresseur keeps .stresseur/judge-cache.json).

View Source
const MaxCacheEntries = 5000

MaxCacheEntries bounds the cache file; the oldest entries go first.

View Source
const MaxReason = 300

MaxReason caps a reason, in characters.

View Source
const ReportOnlyHint = "Checks report only: add quality: { fail_below: 90% } to the flow file to fail on them."

ReportOnlyHint is printed once when checks failed but no quality.fail_below makes them count.

Variables

View Source
var DefaultBaseURLs = map[string]string{
	ProviderAnthropic: "https://api.anthropic.com",
	ProviderOpenAI:    "https://api.openai.com/v1",
}

DefaultBaseURLs are the provider APIs.

View Source
var DefaultModels = map[string]string{
	ProviderAnthropic: "claude-haiku-4-5-20251001",
	ProviderOpenAI:    "gpt-4.1-mini",
}

DefaultModels is the judge model when the config names none.

Kinds is every check kind in order.

Functions

func Borderline

func Borderline(score, minScore float64) bool

Borderline: a whole score at min_score or one below, where one sample decides the verdict.

func CacheKey

func CacheKey(cfg JudgeConfig, j *JudgeSpec, input, output string) string

CacheKey identifies a verdict: same judge, same rubric, same data → same verdict.

func Clip

func Clip(s string) string

Clip makes a reason one line of at most MaxReason characters.

func DecodeExpect

func DecodeExpect(n *yaml.Node) (mexpect.Expect, error)

DecodeExpect strictly decodes an expect: block. It is validated by CompileSpec.

func DecodeQuality

func DecodeQuality(n *yaml.Node) (*mexpect.Quality, error)

DecodeQuality strictly decodes and validates a quality: block.

func FoldFileSettings

func FoldFileSettings(c *mexpect.Checks, flowID idwrap.IDWrap) (mexpect.FlowSettings, bool)

FoldFileSettings is a flow's settings with the file's judge: and quality: folded in, as a workspace keeps them per flow: the judge merged key by key, the flow's quality: over the file's. ok is false when there is nothing to keep.

func FormatMS

func FormatMS(v float64) string

FormatMS is a duration for check lines: 812 ms, 1.8 s.

func JSONShape

func JSONShape(v any) (map[string]any, bool)

JSONShape turns a node's output into plain JSON values (float64 numbers, map[string]any), the shape a JSON report holds, so checks behave the same on engine values and on reports.

func KeyEnvironment

func KeyEnvironment(dotenvPath string) map[string]string

KeyEnvironment is where judge keys are read from: the dotenv file at dotenvPath (when it exists), overridden by the process environment.

func KeyEnvs

func KeyEnvs(cfg JudgeConfig) []string

KeyEnvs are the variables a judge key is read from, in order.

func NoKeyReason

func NoKeyReason(cfg JudgeConfig) string

NoKeyReason is why judge checks were skipped without a key.

func ParseDotEnv

func ParseDotEnv(b []byte) map[string]string

ParseDotEnv reads KEY=VALUE lines: `export ` prefixes, # comments and quoted values are accepted; anything else is skipped.

func ParseRate

func ParseRate(s string) (float64, error)

ParseRate reads 90%, "90%" or 0.9 as 0.9.

func ParseVerdict

func ParseVerdict(text string) (int, string, error)

ParseVerdict reads {"reason": "...", "score": n} from a reply (the first JSON object in it).

func Percentile

func Percentile(values []float64, p float64) float64

Percentile is the nearest-rank percentile of values (p in (0, 100]).

func Present

func Present(n yaml.Node) bool

Present says whether a YAML node holds a value (not absent, not null).

func Render

func Render(tmpl, step string, run Run) (out string, missing string)

Render fills every {{ path }} of a template. missing is the first path that did not resolve.

func Resolve

func Resolve(path, step string, run Run) (any, bool)

Resolve reads a path in a run: response.… / request.… (the current step) or Step.response.… / Step.request.… (another step).

func RunLines

func RunLines(step string, spec *Spec, results []Result) []string

RunLines are one run's check lines (`flow run`):

✓ AskAssistant latency 812 ms ≤ 4.0 s
✗ AskAssistant judge 3 (min 4): States a refund policy not in the docs.

func StatsLine

func StatsLine(s Stats) string

StatsLine is the judge's work: "Judge: 4 calls, 8 cached, 1.1 s". "" when nothing was judged.

func StrictDecode

func StrictDecode(n *yaml.Node, out any) error

StrictDecode decodes a YAML node into out, rejecting unknown keys.

func SummaryLines

func SummaryLines(step string, sums []Summary) []string

SummaryLines are a step's check lines over many runs (`ci`):

✓ AskAssistant schema 12 of 12
✓ AskAssistant latency 12 of 12, p95 1.8 s ≤ 4.0 s
✗ AskAssistant judge 9 of 12, score 3.8 (min 4): States a refund policy not in the docs.

func Text

func Text(v any) string

Text renders a value: strings as they are, everything else as JSON.

func ValidateJudge

func ValidateJudge(c JudgeConfig) error

ValidateJudge checks a judge: block.

func WeightedScore

func WeightedScore(tokens []openAILogprob) *float64

WeightedScore is G-Eval's probability-weighted score: at the score digit (the first 1–5 token after "score"), Σ p(s)·s over the 1–5 alternatives, renormalized. Nil when not found.

func Worst

func Worst(statuses ...string) string

Worst is the worst of some statuses; "" when there are none.

Types

type APIError

type APIError struct {
	Status int
	Body   string
}

APIError is a provider's non-2xx answer.

func (*APIError) Error

func (e *APIError) Error() string

type Cache

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

Cache is the verdict cache, kept in a JSON file. Safe for concurrent use.

func OpenCache

func OpenCache(path string) *Cache

OpenCache reads the cache file; a missing or unreadable file is an empty cache. path "" keeps it in memory only.

func (*Cache) Get

func (c *Cache) Get(key string) (CacheEntry, bool)

Get returns a stored verdict.

func (*Cache) Len

func (c *Cache) Len() int

Len is the number of stored verdicts.

func (*Cache) Put

func (c *Cache) Put(key string, e CacheEntry)

Put stores a verdict.

func (*Cache) Save

func (c *Cache) Save() error

Save writes the file (atomically) when something changed.

type CacheEntry

type CacheEntry struct {
	Score  float64 `json:"score"`
	Reason string  `json:"reason"`
	Model  string  `json:"model"`
	At     string  `json:"at"`
}

CacheEntry is one stored verdict.

type Client

type Client struct {
	Config JudgeConfig
	APIKey string
	HTTP   *http.Client
	// Backoff is the wait before retry n (1-based); exponential from 1 s when nil.
	Backoff func(n int) time.Duration
	// contains filtered or unexported fields
}

Client calls one provider.

func (*Client) Ask

func (c *Client) Ask(ctx context.Context, p Prompt, temperature float64) (Sample, error)

Ask sends one prompt. temperature 0 for the first answer, 1 for extra samples.

func (*Client) Judge

func (c *Client) Judge(ctx context.Context, j *JudgeSpec, input, output string) (Verdict, error)

Judge scores one output: one call at temperature 0; with logprobs, the weighted score; else, on a borderline score, samples−1 more calls at temperature 1 (in parallel) and the mean.

type Evaluator

type Evaluator struct {
	Env         map[string]string
	HTTP        *http.Client
	Cache       *Cache
	Concurrency int
	Backoff     func(n int) time.Duration

	Stats Stats
	// contains filtered or unexported fields
}

Evaluator runs the checks of flows after their timed runs. One per command.

func (*Evaluator) EvaluateFlow

func (e *Evaluator) EvaluateFlow(ctx context.Context, fl *Flow, runs []Run) FlowResult

EvaluateFlow runs every check of a flow over its measured runs: the deterministic ones inline, the judge calls in parallel (Concurrency at a time), through the cache.

type File

type File struct {
	Flows map[string]*Flow
}

File is a flow file's checks, by flow name.

func Parse

func Parse(content []byte, dir string) (File, error)

Parse reads the checks of a flow file on its own (dir resolves schema file paths). A file without any expect: block parses to a File without checks; a malformed one is an error naming the step.

func (File) Flow

func (f File) Flow(name string) *Flow

Flow returns the named flow's checks, nil when it has none.

func (File) HasChecks

func (f File) HasChecks() bool

HasChecks says whether any flow has a step with expect:.

type Flow

type Flow struct {
	Name string
	// Iterations is the flow's iterations: (for `stress ci`); 0 when unset.
	Iterations int
	Judge      JudgeConfig // merged and defaulted
	// AutoProvider is true when no provider, model, base_url or api_key_env was set:
	// the judge then follows the keys present (see JudgeFor).
	AutoProvider bool
	Quality      Quality // merged
	// Steps are the steps with an expect: block, in file order.
	Steps []string
	Specs map[string]*Spec
}

Flow is one flow's checks.

func NewFlow

func NewFlow(name string, file *mexpect.Checks, flow mexpect.FlowSettings) (*Flow, error)

NewFlow resolves a flow's judge and quality from the file's and the flow's settings.

func (*Flow) Add

func (fl *Flow) Add(step string, e mexpect.Expect, dir string) error

Add compiles a step's expect: block into the flow.

type FlowResult

type FlowResult struct {
	Runs      []map[string][]Result
	Summaries map[string][]Summary
}

FlowResult is a flow's checks: per run (parallel to the runs given), per step, the results; per step the summaries.

type JudgeConfig

type JudgeConfig = mexpect.JudgeConfig

JudgeConfig is the judge: which model, where, with which key.

func DecodeJudge

func DecodeJudge(n *yaml.Node) (*JudgeConfig, error)

DecodeJudge strictly decodes and validates a judge: block.

func JudgeDefaults

func JudgeDefaults(c JudgeConfig) JudgeConfig

JudgeDefaults fills the provider's defaults.

func JudgeFor

func JudgeFor(fl *Flow, env map[string]string) JudgeConfig

JudgeFor is a flow's judge for an environment. A flow that sets none of provider, model, base_url and api_key_env judges with OpenAI when OPENAI_API_KEY is the only key set; otherwise its configured (or default Anthropic) judge.

func MergeJudge

func MergeJudge(base JudgeConfig, over JudgeConfig) JudgeConfig

MergeJudge applies a flow's judge over the file's, key by key.

type JudgeSpec

type JudgeSpec = mexpect.JudgeSpec

JudgeSpec is one step's rubric.

type Prompt

type Prompt struct {
	System string
	User   string
}

Prompt is one judge request.

func BuildPrompt

func BuildPrompt(j *JudgeSpec, input, output string) Prompt

BuildPrompt is the G-Eval prompt: criteria, fixed evaluation steps, a 1–5 rubric, then the input and output as delimited data.

type Quality

type Quality struct {
	// FailBelow (0–1): a check passing in a smaller share of runs fails the command. Nil: report only.
	FailBelow *float64
}

Quality is the pass-rate gate.

func ParseQuality

func ParseQuality(q *mexpect.Quality) (Quality, error)

ParseQuality reads a quality: block; nil is no gate.

type Result

type Result struct {
	Kind       string   `json:"kind"`
	Passed     bool     `json:"passed"`
	Score      *float64 `json:"score"`
	Reason     string   `json:"reason"`
	JudgeModel *string  `json:"judge_model"`
	Cached     bool     `json:"cached"`
	Value      *float64 `json:"value"`
	Limit      *float64 `json:"limit"`

	// Skipped: the judge could not judge (no key, provider error). Not a pass or a failure.
	Skipped bool `json:"skipped,omitempty"`
	// Judge accounting: calls made (cache hits: 0) and their tokens.
	Calls        int `json:"judge_calls,omitempty"`
	InputTokens  int `json:"judge_input_tokens,omitempty"`
	OutputTokens int `json:"judge_output_tokens,omitempty"`
}

Result is one check in one run: the engine's report steps[].checks[].

func Deterministic

func Deterministic(spec *Spec, step string, run Run) []Result

Deterministic runs the schema, latency, ttft and token checks a spec declares, in that order.

type Run

type Run struct {
	Index int
	Steps map[string]StepRun
}

Run is one run of a flow: every step by name.

type Sample

type Sample struct {
	Score  int
	Reason string
	// Weighted is the logprob-weighted score (G-Eval), when the provider returned logprobs.
	Weighted     *float64
	InputTokens  int
	OutputTokens int
}

Sample is one judge answer.

type Spec

type Spec struct {
	Schema       *jsonschema.Schema
	SchemaAt     string
	MaxLatencyMS *float64
	MaxTTFTMS    *float64
	Usage        string
	// UsageSet: the step names its usage path, so there is no fallback to the stream's.
	UsageSet        bool
	MaxInputTokens  *float64
	MaxOutputTokens *float64
	MaxTotalTokens  *float64
	Judge           *JudgeSpec
}

Spec is one step's compiled expect: block.

func CompileSpec

func CompileSpec(e mexpect.Expect, dir string) (*Spec, error)

CompileSpec validates an expect: block and compiles its schema. dir resolves a schema path; with dir "" a schema path is checked only for being a string, not read.

func (*Spec) HasTokens

func (s *Spec) HasTokens() bool

HasTokens says whether any token budget is set.

type Stats

type Stats struct {
	Calls        int // API calls (borderline samples included)
	Judged       int // verdicts that needed calls
	Cached       int // verdicts from the cache or an identical output this run
	Skipped      int
	InputTokens  int
	OutputTokens int
	// Latencies are each verdict's wall time (its calls, samples in parallel).
	Latencies []time.Duration
	// Elapsed is the wall time of the judging phases.
	Elapsed time.Duration
	// NoKey are the reasons judge checks were skipped for a missing key, once each.
	NoKey []string
}

Stats is the judge's work over a command.

func (Stats) Since

func (s Stats) Since(before Stats) Stats

Since returns the difference between two stats (one flow's share of a command's).

type StepRun

type StepRun struct {
	// Ran: the engine executed it (Success or Failure).
	Ran        bool
	DurationMS *float64
	// Data is the node's output: {"request": {...}, "response": {...}}.
	Data map[string]any
}

StepRun is one step in one run, as the engine executed it.

type Summary

type Summary struct {
	Kind              string   `json:"kind"`
	Runs              int      `json:"runs"`
	Passed            int      `json:"passed"`
	Skipped           int      `json:"skipped"`
	PassRate          *float64 `json:"pass_rate"`
	Status            string   `json:"status"`
	FailBelow         *float64 `json:"fail_below"`
	Threshold         *float64 `json:"threshold"`
	MeanScore         *float64 `json:"mean_score"`
	JudgeModel        *string  `json:"judge_model"`
	Criteria          *string  `json:"criteria"`
	Reason            string   `json:"reason"`
	JudgeCalls        int      `json:"judge_calls"`
	JudgeInputTokens  int      `json:"judge_input_tokens"`
	JudgeOutputTokens int      `json:"judge_output_tokens"`
	JudgeCached       int      `json:"-"`

	// Values are the measured latency or token values, for the console's p95.
	Values []float64 `json:"-"`
}

Summary is one check over a step's measured runs (ingest-v1 steps[].checks[]).

func Summarize

func Summarize(spec *Spec, judgeModel string, q Quality, perRun [][]Result) []Summary

Summarize computes each declared check's pass rate over a step's runs (one []Result per run).

type Verdict

type Verdict struct {
	Score        float64
	Reason       string
	Calls        int
	InputTokens  int
	OutputTokens int
}

Verdict is a judged score: one answer, logprob-weighted, or the mean of k samples.

Jump to

Keyboard shortcuts

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