Documentation
¶
Overview ¶
Package evals is a small, type-safe toolkit for evaluating non-deterministic functions — LLM calls, agents, retrieval pipelines, anything whose output you can't assert with a single ==.
It's a Go port of Pydantic Evals. You bring a function to test; the library runs it over a set of cases, scores each result with evaluators you choose, and gives you a report you can print, diff, serialize, or ship to an OpenTelemetry backend like Pydantic Logfire.
The mental model ¶
Think of it as a test framework for probabilistic code:
- A Case is one scenario: an input, an optional expected output, optional metadata.
- A Dataset is a named collection of cases plus the evaluators that score them — your test suite.
- A TaskFunc is the function under test: func(ctx, input) (output, error).
- An Evaluator looks at a task's output and returns a result. Booleans become pass/fail assertions, numbers become scores, strings become labels.
- An EvaluationReport collects every result, with per-case detail and an aggregate summary.
Everything is generic over three type parameters — the input (I), the output (O), and the metadata (M) — so your evaluators receive already-typed values with no casting.
Your first evaluation ¶
Bind the three types once with For, then build a dataset and run it. Use [any] for any type parameter you don't need to pin down:
s := evals.For[string, string, any]()
dataset := s.Dataset("capitals",
s.Case("What is the capital of France?").Expect("Paris"),
).With(s.EqualsExpected())
answer := func(ctx context.Context, q string) (string, error) {
return "Paris", nil
}
report, err := dataset.Evaluate(context.Background(), answer)
if err != nil {
log.Fatal(err)
}
report.Print()
For returns a Suite that carries the type parameters, so you never repeat [string, string, any] at the call sites. See the Suite examples for the full builder.
Built-in evaluators ¶
The package ships evaluators for the common deterministic checks: Equals, EqualsExpected, Contains, IsInstance, and MaxDuration. Add them to a dataset with Dataset.With, or to a single case with CaseBuilder.Eval.
Custom evaluators ¶
An evaluator is any type with an Evaluate method. Return a result with Score, Assertion, or Category — add a “why” with WithReason, or return several named results at once with Named:
type Closeness struct{}
func (Closeness) Evaluate(ctx context.Context, ec *evals.EvaluatorContext[string, string, any]) (evals.Output, error) {
if ec.Output == ec.ExpectedOutput {
return evals.Score(1.0), nil
}
return evals.Score(0.0).WithReason("not an exact match"), nil
}
That's the whole contract. The evaluator's name in the report defaults to its Go type name; implement NamedEvaluator to choose another, VersionedEvaluator to tag results with a version, or SpecEvaluator to make it serializable (see below).
Reports ¶
Dataset.Evaluate returns an EvaluationReport. Render it as a table with EvaluationReport.Render, write it anywhere with EvaluationReport.Fprint, or print it to stdout with EvaluationReport.Print. RenderOptions controls which columns appear. EvaluationReport.Averages gives you the aggregate summary, and EvaluationReport.CaseGroups groups repeated runs when you set Config.Repeat.
Metrics and attributes ¶
Inside a task, record extra data that flows into the report and to evaluators: IncrementMetric for numeric counters (averaged across cases) and SetAttribute for arbitrary values. Both are no-ops outside an evaluation, so instrumented code is safe to call anywhere.
Lifecycle hooks ¶
For per-case setup and teardown — spin up a fixture, enrich the context before evaluators run, clean up afterwards — implement Lifecycle (embed BaseLifecycle for no-op defaults) and pass a factory to Dataset.EvaluateWithLifecycle.
Saving and loading datasets ¶
Datasets serialize to YAML or JSON with Dataset.Save, and load back with LoadDataset. Loading rebuilds evaluators through a Registry: call Registry.RegisterDefaults for the built-ins, or Registry.Register your own. Custom evaluators that should round-trip implement SpecEvaluator.
OpenTelemetry and Logfire ¶
Dataset.Evaluate emits an OpenTelemetry span tree — an experiment span, a span per case, a span per task run, and a span per evaluator — using the global tracer provider. It costs nothing until you configure one. Point an OTLP exporter at Logfire and call otel.SetTracerProvider to see your experiments in Logfire's evaluation views; see the Logfire example below for the full wiring. The repository's examples directory also has end-to-end programs evaluating agents built on the OpenAI Go SDK, Genkit, and Eino.
Example ¶
Example shows the shortest path to an evaluation: bind the types once with For, build a dataset, run a task, and print the report.
package main
import (
"context"
evals "github.com/Kludex/pydantic-evals-go"
)
// renderOpts hides durations so example output is deterministic. The title is
// omitted because its centering depends on terminal width.
var renderOpts = evals.RenderOptions{
IncludeInput: true,
IncludeOutput: true,
IncludeAverages: true,
OmitTitle: true,
}
func main() {
s := evals.For[string, string, any]()
dataset := s.Dataset("capitals",
s.Case("What is the capital of France?").Name("france").Expect("Paris"),
).With(s.EqualsExpected())
answer := func(_ context.Context, q string) (string, error) {
return "Paris", nil
}
report, err := dataset.Evaluate(context.Background(), answer)
if err != nil {
panic(err)
}
report.Print(renderOpts)
}
Output: ┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Case ID ┃ Inputs ┃ Outputs ┃ Assertions ┃ ┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ france │ What is the capital of France? │ Paris │ ✔ │ ├──────────┼────────────────────────────────┼─────────┼────────────┤ │ Averages │ │ │ 100.0% ✔ │ └──────────┴────────────────────────────────┴─────────┴────────────┘
Example (Logfire) ¶
Example_logfire wires an OTLP exporter to Pydantic Logfire so that the span tree emitted by Evaluate — an experiment span, a span per case, per task run, and per evaluator — shows up in Logfire's evaluation views.
Instrumentation uses the global tracer provider, so configuring one is all it takes; the library code is unchanged. Set LOGFIRE_TOKEN to a Logfire write token (the US endpoint is used here; use logfire-eu for the EU region).
package main
import (
"context"
"os"
evals "github.com/Kludex/pydantic-evals-go"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func main() {
ctx := context.Background()
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpointURL("https://logfire-us.pydantic.dev/v1/traces"),
otlptracehttp.WithHeaders(map[string]string{
"Authorization": os.Getenv("LOGFIRE_TOKEN"),
}),
)
if err != nil {
panic(err)
}
tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))
otel.SetTracerProvider(tp)
defer tp.Shutdown(ctx) // flush buffered spans before exit
s := evals.For[string, string, any]()
dataset := s.Dataset("capitals",
s.Case("What is the capital of France?").Name("france").Expect("Paris"),
).With(s.EqualsExpected())
answer := func(ctx context.Context, q string) (string, error) {
return "Paris", nil
}
report, err := dataset.Evaluate(ctx, answer, evals.Config{Name: "capitals-experiment"})
if err != nil {
panic(err)
}
report.Print()
}
Output:
Index ¶
- func Assertion(passed bool) single
- func Category(label string) single
- func IncrementMetric(ctx context.Context, name string, amount float64)
- func Score(value float64) single
- func ScoreInt(value int) single
- func SetAttribute(ctx context.Context, name string, value any)
- type BaseLifecycle
- func (BaseLifecycle[I, O, M]) PrepareContext(_ context.Context, ec *EvaluatorContext[I, O, M]) (*EvaluatorContext[I, O, M], error)
- func (BaseLifecycle[I, O, M]) Setup(context.Context) error
- func (BaseLifecycle[I, O, M]) Teardown(context.Context, *ReportCase[I, O, M], *ReportCaseFailure[I, O, M]) error
- type Bool
- type Case
- type CaseBuilder
- func (b *CaseBuilder[I, O, M]) Build() Case[I, O, M]
- func (b *CaseBuilder[I, O, M]) Eval(evaluators ...Evaluator[I, O, M]) *CaseBuilder[I, O, M]
- func (b *CaseBuilder[I, O, M]) Expect(expected O) *CaseBuilder[I, O, M]
- func (b *CaseBuilder[I, O, M]) Meta(metadata M) *CaseBuilder[I, O, M]
- func (b *CaseBuilder[I, O, M]) Name(name string) *CaseBuilder[I, O, M]
- type CaseOption
- type Config
- type Contains
- type Dataset
- func (d *Dataset[I, O, M]) AddCase(c Case[I, O, M]) error
- func (d *Dataset[I, O, M]) AddEvaluator(e Evaluator[I, O, M])
- func (d *Dataset[I, O, M]) AddEvaluatorForCase(caseName string, e Evaluator[I, O, M]) error
- func (d *Dataset[I, O, M]) Evaluate(ctx context.Context, task TaskFunc[I, O], cfg ...Config) (*EvaluationReport[I, O, M], error)
- func (d *Dataset[I, O, M]) EvaluateWithLifecycle(ctx context.Context, task TaskFunc[I, O], ...) (*EvaluationReport[I, O, M], error)
- func (d *Dataset[I, O, M]) Save(opts SaveOptions) ([]byte, error)
- func (d *Dataset[I, O, M]) With(evaluators ...Evaluator[I, O, M]) *Dataset[I, O, M]
- type Equals
- type EqualsExpected
- type EvaluationReason
- type EvaluationReport
- func (r *EvaluationReport[I, O, M]) Averages() *ReportCaseAggregate
- func (r *EvaluationReport[I, O, M]) CaseGroups() []ReportCaseGroup[I, O, M]
- func (r *EvaluationReport[I, O, M]) Fprint(w io.Writer, opts ...RenderOptions)
- func (r *EvaluationReport[I, O, M]) Print(opts ...RenderOptions)
- func (r *EvaluationReport[I, O, M]) Render(opts ...RenderOptions) string
- type EvaluationResult
- type Evaluator
- type EvaluatorContext
- type EvaluatorFactory
- type EvaluatorFailure
- type EvaluatorSpec
- type Float
- type Int
- type IsInstance
- type Label
- type Lifecycle
- type LoadOptions
- type MaxDuration
- type NamedEvaluator
- type Output
- type Registry
- type RenderOptions
- type ReportCase
- type ReportCaseAggregate
- type ReportCaseFailure
- type ReportCaseGroup
- type SaveOptions
- type Scalar
- type SpecEvaluator
- type Suite
- func (Suite[I, O, M]) Case(inputs I) *CaseBuilder[I, O, M]
- func (Suite[I, O, M]) Contains(value any) Contains[I, O, M]
- func (Suite[I, O, M]) Dataset(name string, cases ...*CaseBuilder[I, O, M]) *Dataset[I, O, M]
- func (Suite[I, O, M]) Equals(value O) Equals[I, O, M]
- func (Suite[I, O, M]) EqualsExpected() EqualsExpected[I, O, M]
- func (Suite[I, O, M]) IsInstance(typeName string) IsInstance[I, O, M]
- func (Suite[I, O, M]) MaxDuration(max time.Duration) MaxDuration[I, O, M]
- type TaskFunc
- type VersionedEvaluator
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Assertion ¶
func Assertion(passed bool) single
Assertion returns a boolean assertion Output (pass/fail).
func IncrementMetric ¶
IncrementMetric increments a numeric metric on the current task run by amount.
It is a no-op if ctx is not a task-run context. The accumulated metrics are exposed to evaluators via EvaluatorContext.Metrics and on the resulting ReportCase, and are averaged in the report summary.
Example ¶
ExampleIncrementMetric records a metric from inside the task; it surfaces on the report and is averaged across cases.
package main
import (
"context"
"fmt"
evals "github.com/Kludex/pydantic-evals-go"
)
func main() {
s := evals.For[string, string, any]()
dataset := s.Dataset("metered", s.Case("hello").Name("c1"))
report, _ := dataset.Evaluate(context.Background(),
func(ctx context.Context, in string) (string, error) {
evals.IncrementMetric(ctx, "chars", float64(len(in)))
return in, nil
})
fmt.Println("chars:", report.Cases[0].Metrics["chars"])
}
Output: chars: 5
func Score ¶
func Score(value float64) single
Score returns a numeric score Output. Use [single.WithReason] to explain it.
Example ¶
ExampleScore demonstrates a custom evaluator that returns a numeric score. The evaluator's report name defaults to its Go type name ("matchAnswer").
package main
import (
"context"
"fmt"
"strings"
evals "github.com/Kludex/pydantic-evals-go"
)
// matchAnswer is a custom evaluator returning a graded score.
type matchAnswer struct{}
func (matchAnswer) Evaluate(_ context.Context, ec *evals.EvaluatorContext[string, string, any]) (evals.Output, error) {
switch {
case ec.Output == ec.ExpectedOutput:
return evals.Score(1.0), nil
case strings.Contains(strings.ToLower(ec.Output), strings.ToLower(ec.ExpectedOutput)):
return evals.Score(0.8).WithReason("substring match"), nil
default:
return evals.Score(0.0), nil
}
}
func main() {
s := evals.For[string, string, any]()
dataset := s.Dataset("quiz",
s.Case("capital of France?").Name("france").Expect("Paris"),
).With(matchAnswer{})
report, _ := dataset.Evaluate(context.Background(),
func(_ context.Context, q string) (string, error) { return "Paris", nil })
for _, c := range report.Cases {
fmt.Printf("%s: %v\n", c.Name, c.Scores["matchAnswer"].Value)
}
}
Output: france: 1
func ScoreInt ¶
func ScoreInt(value int) single
ScoreInt returns an integer score Output, rendered without decimals.
func SetAttribute ¶
SetAttribute records an arbitrary attribute on the current task run.
It is a no-op if ctx is not a task-run context (i.e. not the context passed to a task during Dataset.Evaluate). The recorded attributes are exposed to evaluators via EvaluatorContext.Attributes and on the resulting ReportCase.
Types ¶
type BaseLifecycle ¶
type BaseLifecycle[I, O, M any] struct{}
BaseLifecycle is a no-op Lifecycle suitable for embedding.
func (BaseLifecycle[I, O, M]) PrepareContext ¶
func (BaseLifecycle[I, O, M]) PrepareContext(_ context.Context, ec *EvaluatorContext[I, O, M]) (*EvaluatorContext[I, O, M], error)
func (BaseLifecycle[I, O, M]) Teardown ¶
func (BaseLifecycle[I, O, M]) Teardown(context.Context, *ReportCase[I, O, M], *ReportCaseFailure[I, O, M]) error
type Case ¶
type Case[I, O, M any] struct { // Name identifies the case in reports and for filtering. When empty, a // generic name ("Case N") is assigned during evaluation. Name string // Inputs to the task being evaluated. Inputs I // Metadata associated with the case, available to evaluators. Metadata M // HasMetadata reports whether Metadata was provided. HasMetadata bool // ExpectedOutput of the task, for comparison by evaluators. ExpectedOutput O // HasExpectedOutput reports whether ExpectedOutput was provided. HasExpectedOutput bool // Evaluators run only on this case, in addition to dataset-level evaluators. Evaluators []Evaluator[I, O, M] }
Case is a single row of a Dataset.
Each case represents one test scenario. Inputs is required; Name, Metadata, ExpectedOutput, and case-specific Evaluators are optional. Use NewCase with option functions to construct a case, which sets the Has* flags consistently.
type CaseBuilder ¶
type CaseBuilder[I, O, M any] struct { // contains filtered or unexported fields }
CaseBuilder is a fluent builder for a Case, returned by Suite.Case.
func (*CaseBuilder[I, O, M]) Build ¶
func (b *CaseBuilder[I, O, M]) Build() Case[I, O, M]
Build returns the configured Case.
func (*CaseBuilder[I, O, M]) Eval ¶
func (b *CaseBuilder[I, O, M]) Eval(evaluators ...Evaluator[I, O, M]) *CaseBuilder[I, O, M]
Eval appends case-specific evaluators.
func (*CaseBuilder[I, O, M]) Expect ¶
func (b *CaseBuilder[I, O, M]) Expect(expected O) *CaseBuilder[I, O, M]
Expect sets the expected output.
func (*CaseBuilder[I, O, M]) Meta ¶
func (b *CaseBuilder[I, O, M]) Meta(metadata M) *CaseBuilder[I, O, M]
Meta sets the case metadata.
func (*CaseBuilder[I, O, M]) Name ¶
func (b *CaseBuilder[I, O, M]) Name(name string) *CaseBuilder[I, O, M]
Name sets the case name.
type CaseOption ¶
CaseOption configures a Case built with NewCase.
func WithCaseEvaluators ¶
func WithCaseEvaluators[I, O, M any](evaluators ...Evaluator[I, O, M]) CaseOption[I, O, M]
WithCaseEvaluators appends case-specific evaluators.
func WithCaseName ¶
func WithCaseName[I, O, M any](name string) CaseOption[I, O, M]
WithCaseName sets the case name.
func WithExpectedOutput ¶
func WithExpectedOutput[I, O, M any](expected O) CaseOption[I, O, M]
WithExpectedOutput sets the case expected output and marks it as present.
func WithMetadata ¶
func WithMetadata[I, O, M any](metadata M) CaseOption[I, O, M]
WithMetadata sets the case metadata and marks it as present.
type Config ¶
type Config struct {
// Name of the experiment; defaults to TaskName.
Name string
// TaskName is the displayed task name; defaults to "task".
TaskName string
// MaxConcurrency limits concurrent case evaluations; <= 0 means unlimited.
MaxConcurrency int
// Repeat runs each case this many times (default/zero is treated as 1).
// When > 1, results are grouped by the original case name for aggregation.
Repeat int
// Metadata is arbitrary experiment metadata recorded on the report.
Metadata map[string]any
}
Config configures a call to Dataset.Evaluate. The zero value is valid: it runs each case once, unbounded, with the experiment named after the task.
Config is intentionally non-generic so that Evaluate calls need no type parameters. For per-case lifecycle hooks, use Dataset.EvaluateWithLifecycle.
Example (Repeat) ¶
ExampleConfig_repeat runs each case several times and reads the aggregate.
package main
import (
"context"
"fmt"
evals "github.com/Kludex/pydantic-evals-go"
)
func main() {
s := evals.For[string, string, any]()
dataset := s.Dataset("repeated",
s.Case("hi").Name("c1").Expect("hi"),
).With(s.EqualsExpected())
report, _ := dataset.Evaluate(context.Background(),
func(_ context.Context, in string) (string, error) { return in, nil },
evals.Config{Repeat: 3})
groups := report.CaseGroups()
fmt.Printf("%d run(s) of %q\n", len(groups[0].Runs), groups[0].Name)
}
Output: 3 run(s) of "c1"
type Contains ¶
type Contains[I, O, M any] struct { // Value to look for within the output. Value any // CaseSensitive controls string comparison (default false: case-insensitive). CaseSensitive bool // AsStrings forces both sides to be compared as strings. AsStrings bool // Name overrides the result name in reports (default "Contains"). Name string }
Contains checks whether the output contains the provided value.
For strings it checks substring containment; for slices/arrays, element membership; for maps, that every key/value in Value (when Value is a map) is present, or that Value is a key. CaseSensitive applies only to string checks.
func (Contains[I, O, M]) EvaluationName ¶
func (Contains[I, O, M]) Spec ¶
func (c Contains[I, O, M]) Spec() EvaluatorSpec
type Dataset ¶
type Dataset[I, O, M any] struct { // Name of the dataset. Name string // Cases in the dataset. Cases []Case[I, O, M] // Evaluators applied to all cases. Evaluators []Evaluator[I, O, M] }
Dataset is a collection of test [Case]s evaluated against a task.
func LoadDataset ¶
func LoadDataset[I, O, M any](data []byte, reg *Registry[I, O, M], opts LoadOptions[I, O, M]) (*Dataset[I, O, M], error)
LoadDataset parses a Dataset from YAML or JSON bytes, constructing case-level and dataset-level evaluators via the registry.
Inputs, expected outputs and metadata are decoded with the Decode* hooks in opts; when a hook is nil the deserialized value is converted to the target type via a JSON round-trip.
Example ¶
ExampleLoadDataset rebuilds a dataset from YAML through a registry of the built-in evaluators.
package main
import (
"fmt"
evals "github.com/Kludex/pydantic-evals-go"
)
func main() {
data := []byte(`
name: greetings
cases:
- name: c1
inputs: hi
expected_output: hello
evaluators:
- EqualsExpected
`)
reg := evals.NewRegistry[string, string, any]()
reg.RegisterDefaults()
dataset, err := evals.LoadDataset(data, reg, evals.LoadOptions[string, string, any]{})
if err != nil {
panic(err)
}
fmt.Printf("%s: %d case(s), %d evaluator(s)\n", dataset.Name, len(dataset.Cases), len(dataset.Evaluators))
}
Output: greetings: 1 case(s), 1 evaluator(s)
func NewDataset ¶
func NewDataset[I, O, M any](name string, cases []Case[I, O, M], evaluators ...Evaluator[I, O, M]) (*Dataset[I, O, M], error)
NewDataset creates a Dataset, validating that case names are unique.
func (*Dataset[I, O, M]) AddEvaluator ¶
AddEvaluator adds an evaluator to all cases in the dataset.
func (*Dataset[I, O, M]) AddEvaluatorForCase ¶
AddEvaluatorForCase adds an evaluator to the case with the given name. It returns an error if no such case exists.
func (*Dataset[I, O, M]) Evaluate ¶
func (d *Dataset[I, O, M]) Evaluate(ctx context.Context, task TaskFunc[I, O], cfg ...Config) (*EvaluationReport[I, O, M], error)
Evaluate runs the task against every case in the dataset, applying the dataset-level and case-level evaluators, and returns an EvaluationReport.
Cases run concurrently (bounded by cfg.MaxConcurrency). A task error produces a ReportCaseFailure; an evaluator error produces an EvaluatorFailure on the case rather than failing the case. Evaluate itself only returns an error if the config is invalid or ctx is cancelled.
cfg is optional; pass at most one Config. For per-case lifecycle hooks, use Dataset.EvaluateWithLifecycle.
func (*Dataset[I, O, M]) EvaluateWithLifecycle ¶
func (d *Dataset[I, O, M]) EvaluateWithLifecycle(ctx context.Context, task TaskFunc[I, O], newLifecycle func(c Case[I, O, M]) Lifecycle[I, O, M], cfg ...Config) (*EvaluationReport[I, O, M], error)
EvaluateWithLifecycle is like Dataset.Evaluate but invokes newLifecycle once per case to obtain its Lifecycle hooks (Setup, PrepareContext, Teardown).
func (*Dataset[I, O, M]) Save ¶
func (d *Dataset[I, O, M]) Save(opts SaveOptions) ([]byte, error)
Save serializes the dataset to YAML or JSON bytes, using the short form for each evaluator spec.
Example ¶
ExampleDataset_Save serializes a dataset to YAML, using the short form for each evaluator.
package main
import (
"fmt"
evals "github.com/Kludex/pydantic-evals-go"
)
func main() {
s := evals.For[string, string, any]()
dataset := s.Dataset("greetings",
s.Case("hi").Name("c1").Expect("hello"),
).With(s.EqualsExpected(), s.IsInstance("string"))
out, _ := dataset.Save(evals.SaveOptions{Format: "yaml"})
fmt.Print(string(out))
}
Output: name: greetings cases: - name: c1 inputs: hi expected_output: hello evaluators: - EqualsExpected - IsInstance: string
type Equals ¶
type Equals[I, O, M any] struct { // Value to compare the output against. Value O // Name overrides the result name in reports (default "Equals"). Name string }
Equals checks whether the output exactly equals the provided value.
func (Equals[I, O, M]) EvaluationName ¶
func (Equals[I, O, M]) Spec ¶
func (e Equals[I, O, M]) Spec() EvaluatorSpec
type EqualsExpected ¶
type EqualsExpected[I, O, M any] struct { // Name overrides the result name in reports (default "EqualsExpected"). Name string }
EqualsExpected checks whether the output exactly equals the case's expected output. If the case has no expected output, it yields no result.
func (EqualsExpected[I, O, M]) Evaluate ¶
func (e EqualsExpected[I, O, M]) Evaluate(_ context.Context, ec *EvaluatorContext[I, O, M]) (Output, error)
func (EqualsExpected[I, O, M]) EvaluationName ¶
func (e EqualsExpected[I, O, M]) EvaluationName() string
func (EqualsExpected[I, O, M]) Spec ¶
func (e EqualsExpected[I, O, M]) Spec() EvaluatorSpec
type EvaluationReason ¶
EvaluationReason is a Scalar result with an optional explanation.
type EvaluationReport ¶
type EvaluationReport[I, O, M any] struct { // Name of the report (the experiment name). Name string // Cases that were evaluated successfully. Cases []ReportCase[I, O, M] // Failures are cases whose task execution raised an error. Failures []ReportCaseFailure[I, O, M] // ExperimentMetadata associated with this experiment, if any. ExperimentMetadata map[string]any }
EvaluationReport is the result of evaluating a task over a Dataset.
func (*EvaluationReport[I, O, M]) Averages ¶
func (r *EvaluationReport[I, O, M]) Averages() *ReportCaseAggregate
Averages returns the overall summary aggregate for the report, or nil if there are no cases. For multi-run experiments it averages the per-group summaries.
func (*EvaluationReport[I, O, M]) CaseGroups ¶
func (r *EvaluationReport[I, O, M]) CaseGroups() []ReportCaseGroup[I, O, M]
CaseGroups groups cases by SourceCaseName and computes per-group aggregates. It returns nil for a single-run experiment (no case has a SourceCaseName).
func (*EvaluationReport[I, O, M]) Fprint ¶
func (r *EvaluationReport[I, O, M]) Fprint(w io.Writer, opts ...RenderOptions)
Fprint writes the rendered report to w.
func (*EvaluationReport[I, O, M]) Print ¶
func (r *EvaluationReport[I, O, M]) Print(opts ...RenderOptions)
Print writes the rendered report to standard output. To write elsewhere (a file, a buffer, a test), use EvaluationReport.Fprint.
func (*EvaluationReport[I, O, M]) Render ¶
func (r *EvaluationReport[I, O, M]) Render(opts ...RenderOptions) string
Render returns the report rendered as a box-drawing table string.
type EvaluationResult ¶
type EvaluationResult struct {
Name string
Value Scalar
Reason string
Source EvaluatorSpec
EvaluatorVersion string
}
EvaluationResult is the detail of a single named evaluation result.
type Evaluator ¶
type Evaluator[I, O, M any] interface { // Evaluate scores the task output described by ec, returning an [Output] // built with [Score], [Assertion], [Category] or [Named]. Evaluate(ctx context.Context, ec *EvaluatorContext[I, O, M]) (Output, error) }
Evaluator assesses the result of running a task against a single Case.
Implementations only need Evaluate. The name used for an evaluator's results in reports defaults to its Go type name; implement NamedEvaluator to override it, SpecEvaluator to make the evaluator serializable to YAML/JSON, and VersionedEvaluator to tag its results with a version.
type EvaluatorContext ¶
type EvaluatorContext[I, O, M any] struct { // Name is the name of the case, if any. Name string // Inputs is the input provided to the task for this case. Inputs I // Metadata associated with the case. Metadata M // HasMetadata reports whether Metadata was set on the case. HasMetadata bool // ExpectedOutput is the expected output for the case. ExpectedOutput O // HasExpectedOutput reports whether ExpectedOutput was set on the case. HasExpectedOutput bool // Output is the actual output produced by the task. Output O // Duration is how long the task run took. Duration time.Duration // Attributes recorded during the task run via [SetAttribute]. Attributes map[string]any // Metrics recorded during the task run via [IncrementMetric]. Metrics map[string]float64 }
EvaluatorContext carries everything an Evaluator needs to score one case.
It holds the case inputs, the actual and expected outputs, metadata, the task duration, and any attributes and metrics recorded during the task run via SetAttribute and IncrementMetric.
type EvaluatorFactory ¶
type EvaluatorFactory[I, O, M any] func(spec EvaluatorSpec) (Evaluator[I, O, M], error)
EvaluatorFactory builds an Evaluator from a deserialized EvaluatorSpec.
type EvaluatorFailure ¶
type EvaluatorFailure struct {
Name string
ErrorMessage string
Source EvaluatorSpec
EvaluatorVersion string
ErrorType string
}
EvaluatorFailure represents an error raised while running an Evaluator.
type EvaluatorSpec ¶
type EvaluatorSpec struct {
// Name of the evaluator class to construct.
Name string
// Args holds the single positional argument, if any (length 0 or 1).
Args []any
// Kwargs holds the keyword arguments, if any.
Kwargs map[string]any
}
EvaluatorSpec is the serializable specification of an evaluator.
It supports three short forms when (de)serialized to YAML/JSON, matching the Python `EvaluatorSpec`:
- "MyEvaluator" — the bare name, when there are no arguments;
- {"MyEvaluator": arg} — a single positional argument;
- {"MyEvaluator": {k1: v1, k2: v2}} — keyword arguments.
Args holds a single positional argument (length 0 or 1); Kwargs holds keyword arguments. At most one of them is non-empty.
func NewSpec ¶
func NewSpec(name string) EvaluatorSpec
NewSpec builds an EvaluatorSpec with no arguments.
func NewSpecArg ¶
func NewSpecArg(name string, arg any) EvaluatorSpec
NewSpecArg builds an EvaluatorSpec with a single positional argument.
func NewSpecKwargs ¶
func NewSpecKwargs(name string, kwargs map[string]any) EvaluatorSpec
NewSpecKwargs builds an EvaluatorSpec with keyword arguments.
type Float ¶
type Float float64
Float is a Scalar score result. Inf and NaN are not valid scores and are rejected when an evaluator's output is normalized.
type IsInstance ¶
type IsInstance[I, O, M any] struct { // TypeName is the unqualified Go type name the output must have. TypeName string // Name overrides the result name in reports (default "IsInstance"). Name string }
IsInstance checks whether the output's runtime type name matches TypeName.
The match is against the unqualified type name (e.g. "string", "int", or a struct's name), making it useful when O is an interface type like `any`.
func (IsInstance[I, O, M]) Evaluate ¶
func (e IsInstance[I, O, M]) Evaluate(_ context.Context, ec *EvaluatorContext[I, O, M]) (Output, error)
func (IsInstance[I, O, M]) EvaluationName ¶
func (e IsInstance[I, O, M]) EvaluationName() string
func (IsInstance[I, O, M]) Spec ¶
func (e IsInstance[I, O, M]) Spec() EvaluatorSpec
type Lifecycle ¶
type Lifecycle[I, O, M any] interface { // Setup runs before task execution. Setup(ctx context.Context) error // PrepareContext runs after the task, before evaluators, and may enrich the // evaluator context (e.g. add metrics or attributes). PrepareContext(ctx context.Context, ec *EvaluatorContext[I, O, M]) (*EvaluatorContext[I, O, M], error) // Teardown runs after evaluators complete. result is nil if the case ended // without producing a report (e.g. cancellation). Teardown(ctx context.Context, result *ReportCase[I, O, M], failure *ReportCaseFailure[I, O, M]) error }
Lifecycle provides per-case setup, context preparation, and teardown hooks. A fresh instance is created for each case via the factory passed to [WithLifecycle]. All methods are optional; embed BaseLifecycle to get no-op defaults.
type LoadOptions ¶
type LoadOptions[I, O, M any] struct { // Format is "yaml" or "json". When empty, it is inferred from the data: a // leading '{' implies JSON, otherwise YAML. Format string // DefaultName is used when the serialized data has no name. DefaultName string // DecodeInputs converts a deserialized inputs value into I. DecodeInputs func(any) (I, error) // DecodeOutput converts a deserialized expected_output value into O. DecodeOutput func(any) (O, error) // DecodeMetadata converts a deserialized metadata value into M. DecodeMetadata func(any) (M, error) }
LoadOptions configures dataset loading.
type MaxDuration ¶
type MaxDuration[I, O, M any] struct { // Max is the inclusive upper bound on the task duration. Max time.Duration }
MaxDuration checks whether the task ran in at most Max.
func (MaxDuration[I, O, M]) Evaluate ¶
func (e MaxDuration[I, O, M]) Evaluate(_ context.Context, ec *EvaluatorContext[I, O, M]) (Output, error)
func (MaxDuration[I, O, M]) Spec ¶
func (e MaxDuration[I, O, M]) Spec() EvaluatorSpec
type NamedEvaluator ¶
type NamedEvaluator interface {
EvaluationName() string
}
NamedEvaluator is an optional interface an Evaluator may implement to override the name used for its results in reports.
type Output ¶
type Output interface {
// contains filtered or unexported methods
}
Output is the value an Evaluator returns from Evaluate.
Build one with Score, Assertion or Category for a single result, or Named for several named results from one evaluator. A bare Score becomes a numeric score in the report, Assertion a boolean assertion, and Category a string label.
func Named ¶
Named groups several results from one evaluator under explicit names. Pass alternating name and Output arguments; each Output must be a single result (from Score/Assertion/Category), not another Named.
return evals.Named(
"length", evals.Score(0.8).WithReason("close"),
"sentiment", evals.Category("neutral"),
), nil
Named panics if given an odd number of arguments or a non-string name, which are programming errors rather than runtime conditions.
Example ¶
ExampleNamed shows one evaluator returning several named results at once.
package main
import (
"context"
"fmt"
evals "github.com/Kludex/pydantic-evals-go"
)
func main() {
type quality struct{}
_ = quality{}
eval := evals.For[string, string, any]()
dataset := eval.Dataset("multi",
eval.Case("hello").Name("c1"),
).With(namedEvaluator{})
report, _ := dataset.Evaluate(context.Background(),
func(_ context.Context, in string) (string, error) { return in, nil })
c := report.Cases[0]
fmt.Println("score:", c.Scores["length"].Value)
fmt.Println("label:", c.Labels["shape"].Value)
}
type namedEvaluator struct{}
func (namedEvaluator) Evaluate(_ context.Context, ec *evals.EvaluatorContext[string, string, any]) (evals.Output, error) {
shape := "short"
if len(ec.Output) > 10 {
shape = "long"
}
return evals.Named(
"length", evals.ScoreInt(len(ec.Output)),
"shape", evals.Category(shape),
), nil
}
Output: score: 5 label: short
type Registry ¶
type Registry[I, O, M any] struct { // contains filtered or unexported fields }
Registry maps evaluator names to factories used when loading a Dataset from YAML or JSON. Construct one with NewRegistry and register built-ins via Registry.RegisterDefaults or custom evaluators via Registry.Register.
func NewRegistry ¶
NewRegistry returns an empty Registry.
func (*Registry[I, O, M]) Register ¶
func (r *Registry[I, O, M]) Register(name string, factory EvaluatorFactory[I, O, M])
Register associates an evaluator name with a factory. It overrides any existing registration for that name.
func (*Registry[I, O, M]) RegisterDefaults ¶
func (r *Registry[I, O, M]) RegisterDefaults()
RegisterDefaults registers the built-in evaluators that do not require an LLM or telemetry: Equals, EqualsExpected, Contains, IsInstance and MaxDuration.
type RenderOptions ¶
type RenderOptions struct {
IncludeInput bool
IncludeMetadata bool
IncludeExpectedOutput bool
IncludeOutput bool
IncludeDurations bool
IncludeTotalDuration bool
IncludeAverages bool
IncludeReasons bool
// Title overrides the default "Evaluation Summary: <name>" title. An empty
// Title uses the default; set OmitTitle to render no title at all.
Title string
// OmitTitle renders the table without a title row.
OmitTitle bool
}
RenderOptions configures EvaluationReport.Render and EvaluationReport.Print.
func DefaultRenderOptions ¶
func DefaultRenderOptions() RenderOptions
DefaultRenderOptions returns the options used when none are supplied: durations and averages included, matching the Python defaults.
type ReportCase ¶
type ReportCase[I, O, M any] struct { // Name of the case in the report. Name string // Inputs to the task. Inputs I // Metadata associated with the case. Metadata M HasMetadata bool // ExpectedOutput of the task. ExpectedOutput O HasExpectedOutput bool // Output produced by the task. Output O // Metrics recorded during the task run. Metrics map[string]float64 // Attributes recorded during the task run. Attributes map[string]any // Scores are evaluation results with numeric values. Scores map[string]EvaluationResult // Labels are evaluation results with string values. Labels map[string]EvaluationResult // Assertions are evaluation results with boolean values. Assertions map[string]EvaluationResult // TaskDuration is the duration of the task run. TaskDuration time.Duration // TotalDuration includes evaluator execution time. TotalDuration time.Duration // SourceCaseName is the original case name before run-indexing, used as the // aggregation key for multi-run (repeat) experiments. Empty when repeat == 1. SourceCaseName string // EvaluatorFailures are evaluators that errored for this case. EvaluatorFailures []EvaluatorFailure }
ReportCase is a single successfully-evaluated case in an EvaluationReport.
type ReportCaseAggregate ¶
type ReportCaseAggregate struct {
Name string
Scores map[string]float64
Labels map[string]map[string]float64
Metrics map[string]float64
// Assertions is the pass rate across all assertions, or nil if there were none.
Assertions *float64
TaskDuration time.Duration
TotalDuration time.Duration
}
ReportCaseAggregate summarizes a set of cases by averaging their quantitative attributes. The synthetic "Averages" row in a report is a ReportCaseAggregate.
type ReportCaseFailure ¶
type ReportCaseFailure[I, O, M any] struct { Name string Inputs I Metadata M HasMetadata bool ExpectedOutput O HasExpectedOutput bool // ErrorMessage is the message of the error that caused the failure. ErrorMessage string // ErrorType is the Go type name of the error. ErrorType string // SourceCaseName mirrors [ReportCase.SourceCaseName]. SourceCaseName string }
ReportCaseFailure is a case whose task execution raised an error.
type ReportCaseGroup ¶
type ReportCaseGroup[I, O, M any] struct { Name string Inputs I Metadata M HasMetadata bool ExpectedOutput O HasExpectedOutput bool Runs []ReportCase[I, O, M] Failures []ReportCaseFailure[I, O, M] Summary ReportCaseAggregate }
ReportCaseGroup is the grouped result of running the same case multiple times (when repeat > 1). It is a computed view obtained via EvaluationReport.CaseGroups.
type SaveOptions ¶
type SaveOptions struct {
// Format is "yaml" or "json". Defaults to "yaml".
Format string
// Schema, when set, is written as the "$schema" reference.
Schema string
}
SaveOptions configures dataset serialization.
type Scalar ¶
type Scalar interface {
// String returns the human-readable form used when rendering reports.
String() string
// contains filtered or unexported methods
}
Scalar is the most primitive output allowed from an Evaluator.
The concrete kinds are Bool, Int, Float and Label (a string). A Bool is treated as an assertion, an Int or finite Float as a score, and a Label as a categorical label. This mirrors the Python `EvaluationScalar` union of `bool | int | float | str`.
type SpecEvaluator ¶
type SpecEvaluator interface {
Spec() EvaluatorSpec
}
SpecEvaluator is an optional interface an Evaluator may implement so it can be serialized to and reconstructed from a Dataset file. Evaluators that are only used in-memory do not need it.
type Suite ¶
type Suite[I, O, M any] struct{}
Suite is a typed entry point that captures the inputs, output and metadata types once so you don't repeat them at every call site. Obtain one with For:
s := evals.For[string, string, any]()
c := s.Case("hi").Name("greeting").Expect("hello")
ds, _ := s.Dataset("quiz", c).With(s.IsInstance("string"))
Suite is a zero-size value; create as many as you like.
func (Suite[I, O, M]) Case ¶
func (Suite[I, O, M]) Case(inputs I) *CaseBuilder[I, O, M]
Case starts building a Case with the given inputs. Chain Name/Expect/Meta/ Eval to configure it; the CaseBuilder is usable directly as a Case argument or via CaseBuilder.Build.
func (Suite[I, O, M]) Dataset ¶
func (Suite[I, O, M]) Dataset(name string, cases ...*CaseBuilder[I, O, M]) *Dataset[I, O, M]
Dataset creates a Dataset from the given case builders. Add dataset-level evaluators with Dataset.With. It panics if two cases share a name, which is a test-definition error; use NewDataset if you need to handle that as a value.
func (Suite[I, O, M]) EqualsExpected ¶
func (Suite[I, O, M]) EqualsExpected() EqualsExpected[I, O, M]
EqualsExpected builds an EqualsExpected evaluator bound to this suite's types.
func (Suite[I, O, M]) IsInstance ¶
func (Suite[I, O, M]) IsInstance(typeName string) IsInstance[I, O, M]
IsInstance builds an IsInstance evaluator bound to this suite's types.
func (Suite[I, O, M]) MaxDuration ¶
func (Suite[I, O, M]) MaxDuration(max time.Duration) MaxDuration[I, O, M]
MaxDuration builds a MaxDuration evaluator bound to this suite's types.
type TaskFunc ¶
TaskFunc is the function under evaluation. It receives the task-run context (on which SetAttribute and IncrementMetric may be called) and a case's inputs, and returns the produced output.
type VersionedEvaluator ¶
type VersionedEvaluator interface {
EvaluatorVersion() string
}
VersionedEvaluator is an optional interface an Evaluator may implement to tag its results with a version string for downstream filtering.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
capitals
command
Command capitals demonstrates evaluating a simple function with the evals library.
|
Command capitals demonstrates evaluating a simple function with the evals library. |
|
logfire
command
Command logfire runs a multi-faceted evaluation and exports the traces to Pydantic Logfire via OTLP, so the experiment shows up in Logfire's eval views.
|
Command logfire runs a multi-faceted evaluation and exports the traces to Pydantic Logfire via OTLP, so the experiment shows up in Logfire's eval views. |