@smthrs/evals 0.0.0-stage → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -0
- package/LICENSE +21 -0
- package/README.md +62 -2
- package/dist/cjs/Baseline.d.ts +118 -0
- package/dist/cjs/Baseline.d.ts.map +1 -0
- package/dist/cjs/Baseline.js +158 -0
- package/dist/cjs/Baseline.js.map +7 -0
- package/dist/cjs/CaseExecutor.d.ts +85 -0
- package/dist/cjs/CaseExecutor.d.ts.map +1 -0
- package/dist/cjs/CaseExecutor.js +59 -0
- package/dist/cjs/CaseExecutor.js.map +7 -0
- package/dist/cjs/EvalError.d.ts +64 -0
- package/dist/cjs/EvalError.d.ts.map +1 -0
- package/dist/cjs/EvalError.js +54 -0
- package/dist/cjs/EvalError.js.map +7 -0
- package/dist/cjs/Gate.d.ts +55 -0
- package/dist/cjs/Gate.d.ts.map +1 -0
- package/dist/cjs/Gate.js +88 -0
- package/dist/cjs/Gate.js.map +7 -0
- package/dist/cjs/Regression.d.ts +116 -0
- package/dist/cjs/Regression.d.ts.map +1 -0
- package/dist/cjs/Regression.js +191 -0
- package/dist/cjs/Regression.js.map +7 -0
- package/dist/cjs/Report.d.ts +114 -0
- package/dist/cjs/Report.d.ts.map +1 -0
- package/dist/cjs/Report.js +161 -0
- package/dist/cjs/Report.js.map +7 -0
- package/dist/cjs/Runner.d.ts +229 -0
- package/dist/cjs/Runner.d.ts.map +1 -0
- package/dist/cjs/Runner.js +430 -0
- package/dist/cjs/Runner.js.map +7 -0
- package/dist/cjs/Suite.d.ts +139 -0
- package/dist/cjs/Suite.d.ts.map +1 -0
- package/dist/cjs/Suite.js +221 -0
- package/dist/cjs/Suite.js.map +7 -0
- package/dist/cjs/Trials.d.ts +93 -0
- package/dist/cjs/Trials.d.ts.map +1 -0
- package/dist/cjs/Trials.js +82 -0
- package/dist/cjs/Trials.js.map +7 -0
- package/dist/cjs/index.d.ts +76 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +51 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/CanonicalJson.d.ts +44 -0
- package/dist/cjs/internal/CanonicalJson.d.ts.map +1 -0
- package/dist/cjs/internal/CanonicalJson.js +114 -0
- package/dist/cjs/internal/CanonicalJson.js.map +7 -0
- package/dist/cjs/internal/controlCharacters.d.ts +44 -0
- package/dist/cjs/internal/controlCharacters.d.ts.map +1 -0
- package/dist/cjs/internal/controlCharacters.js +42 -0
- package/dist/cjs/internal/controlCharacters.js.map +7 -0
- package/dist/cjs/internal/scorerLabel.d.ts +33 -0
- package/dist/cjs/internal/scorerLabel.d.ts.map +1 -0
- package/dist/cjs/internal/scorerLabel.js +28 -0
- package/dist/cjs/internal/scorerLabel.js.map +7 -0
- package/dist/cjs/internal/tupleKey.d.ts +20 -0
- package/dist/cjs/internal/tupleKey.d.ts.map +1 -0
- package/dist/cjs/internal/tupleKey.js +25 -0
- package/dist/cjs/internal/tupleKey.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Baseline.d.ts +118 -0
- package/dist/esm/Baseline.d.ts.map +1 -0
- package/dist/esm/Baseline.js +203 -0
- package/dist/esm/Baseline.js.map +1 -0
- package/dist/esm/CaseExecutor.d.ts +85 -0
- package/dist/esm/CaseExecutor.d.ts.map +1 -0
- package/dist/esm/CaseExecutor.js +60 -0
- package/dist/esm/CaseExecutor.js.map +1 -0
- package/dist/esm/EvalError.d.ts +64 -0
- package/dist/esm/EvalError.d.ts.map +1 -0
- package/dist/esm/EvalError.js +65 -0
- package/dist/esm/EvalError.js.map +1 -0
- package/dist/esm/Gate.d.ts +55 -0
- package/dist/esm/Gate.d.ts.map +1 -0
- package/dist/esm/Gate.js +90 -0
- package/dist/esm/Gate.js.map +1 -0
- package/dist/esm/Regression.d.ts +116 -0
- package/dist/esm/Regression.d.ts.map +1 -0
- package/dist/esm/Regression.js +195 -0
- package/dist/esm/Regression.js.map +1 -0
- package/dist/esm/Report.d.ts +114 -0
- package/dist/esm/Report.d.ts.map +1 -0
- package/dist/esm/Report.js +188 -0
- package/dist/esm/Report.js.map +1 -0
- package/dist/esm/Runner.d.ts +229 -0
- package/dist/esm/Runner.d.ts.map +1 -0
- package/dist/esm/Runner.js +430 -0
- package/dist/esm/Runner.js.map +1 -0
- package/dist/esm/Suite.d.ts +139 -0
- package/dist/esm/Suite.d.ts.map +1 -0
- package/dist/esm/Suite.js +255 -0
- package/dist/esm/Suite.js.map +1 -0
- package/dist/esm/Trials.d.ts +93 -0
- package/dist/esm/Trials.d.ts.map +1 -0
- package/dist/esm/Trials.js +116 -0
- package/dist/esm/Trials.js.map +1 -0
- package/dist/esm/index.d.ts +76 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +76 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/CanonicalJson.d.ts +44 -0
- package/dist/esm/internal/CanonicalJson.d.ts.map +1 -0
- package/dist/esm/internal/CanonicalJson.js +202 -0
- package/dist/esm/internal/CanonicalJson.js.map +1 -0
- package/dist/esm/internal/controlCharacters.d.ts +44 -0
- package/dist/esm/internal/controlCharacters.d.ts.map +1 -0
- package/dist/esm/internal/controlCharacters.js +65 -0
- package/dist/esm/internal/controlCharacters.js.map +1 -0
- package/dist/esm/internal/scorerLabel.d.ts +33 -0
- package/dist/esm/internal/scorerLabel.d.ts.map +1 -0
- package/dist/esm/internal/scorerLabel.js +33 -0
- package/dist/esm/internal/scorerLabel.js.map +1 -0
- package/dist/esm/internal/tupleKey.d.ts +20 -0
- package/dist/esm/internal/tupleKey.d.ts.map +1 -0
- package/dist/esm/internal/tupleKey.js +20 -0
- package/dist/esm/internal/tupleKey.js.map +1 -0
- package/docs/README.md +130 -0
- package/docs/api.md +626 -0
- package/docs/concepts/determinism.md +64 -0
- package/docs/concepts/fixed-suites.md +71 -0
- package/docs/concepts/step-keys.md +60 -0
- package/docs/guides/author-a-suite.md +115 -0
- package/docs/guides/gate-a-run-in-ci.md +129 -0
- package/docs/guides/maintain-a-baseline.md +91 -0
- package/docs/guides/run-a-suite.md +150 -0
- package/docs/installation.md +46 -0
- package/docs/quickstart.md +238 -0
- package/docs/troubleshooting.md +148 -0
- package/package.json +198 -3
- package/src/Baseline.ts +259 -0
- package/src/CaseExecutor.ts +104 -0
- package/src/EvalError.ts +74 -0
- package/src/Gate.ts +128 -0
- package/src/Regression.ts +314 -0
- package/src/Report.ts +268 -0
- package/src/Runner.ts +759 -0
- package/src/Suite.ts +358 -0
- package/src/Trials.ts +154 -0
- package/src/index.ts +76 -0
- package/src/internal/CanonicalJson.ts +234 -0
- package/src/internal/controlCharacters.ts +66 -0
- package/src/internal/scorerLabel.ts +35 -0
- package/src/internal/tupleKey.ts +20 -0
package/docs/api.md
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "API reference"
|
|
3
|
+
description: "Every @smthrs/evals export: the pipeline, comparison semantics, failure codes, the batch protocol, and limits."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Fixed-suite evaluation, baselines, regression reports, and score gates for
|
|
7
|
+
flows.
|
|
8
|
+
|
|
9
|
+
For how to add the package to a project, see
|
|
10
|
+
[Installation](./installation.md).
|
|
11
|
+
|
|
12
|
+
## The pipeline
|
|
13
|
+
|
|
14
|
+
One direction, six modules:
|
|
15
|
+
|
|
16
|
+
1. `Suite` declares the fixed cases and the scorer bindings that grade them.
|
|
17
|
+
2. `Runner` executes every case through an injected `CaseExecutor` and grades the
|
|
18
|
+
executions, producing observations.
|
|
19
|
+
3. `Baseline` records what a run scored, as a committed artifact.
|
|
20
|
+
4. `Regression` compares the next run with that artifact.
|
|
21
|
+
5. `Report` renders the comparison as JSON or Markdown.
|
|
22
|
+
6. `Gate` turns the comparison into a verdict and a CI exit code.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Baseline, CaseExecutor, Gate, Regression, Runner, Suite } from "@smthrs/evals"
|
|
26
|
+
import { Binding, Scorer } from "@smthrs/scorers"
|
|
27
|
+
import { Effect, Layer } from "effect"
|
|
28
|
+
import { readFile } from "node:fs/promises"
|
|
29
|
+
|
|
30
|
+
const greet = { name: "greet" }
|
|
31
|
+
|
|
32
|
+
const polite = Scorer.make({
|
|
33
|
+
id: "example/polite",
|
|
34
|
+
version: "1",
|
|
35
|
+
name: "polite",
|
|
36
|
+
score: ({ output }) => Effect.succeed({ score: String(output).startsWith("Hello") ? 1 : 0 })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const executor = CaseExecutor.make((suiteCase) =>
|
|
40
|
+
Effect.succeed({
|
|
41
|
+
output: `Hello, ${(suiteCase.input as { readonly name: string }).name}`,
|
|
42
|
+
stepKey: suiteCase.name,
|
|
43
|
+
latencyMs: 0,
|
|
44
|
+
target: greet
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const program = Effect.gen(function*() {
|
|
49
|
+
const suite = yield* Suite.make({
|
|
50
|
+
name: "smoke",
|
|
51
|
+
cases: [{ name: "hello", input: { name: "Ada" } }],
|
|
52
|
+
bindings: [Binding.make({ scorer: polite, appliesTo: greet })],
|
|
53
|
+
concurrency: 1
|
|
54
|
+
})
|
|
55
|
+
const run = yield* Runner.run(suite, { runId: "nightly-2026-01-01", at: "2026-01-01T00:00:00.000Z" })
|
|
56
|
+
const committed = yield* Baseline.load(
|
|
57
|
+
yield* Effect.promise(() => readFile("baseline.json", "utf8"))
|
|
58
|
+
)
|
|
59
|
+
const comparison = yield* Regression.compare(committed, run)
|
|
60
|
+
return Gate.ciGrade(yield* Gate.check(comparison, { mean: 0.9 }))
|
|
61
|
+
}).pipe(Effect.provide(Layer.succeed(CaseExecutor.CaseExecutor)(executor)))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`Runner.run` needs only `CaseExecutor`. Scoring runs in process by default;
|
|
65
|
+
provide `Runner.layerInline` to say so explicitly, `Runner.layerNoop` to state
|
|
66
|
+
that a suite must not score, or your own adapter through `options.scorer` or
|
|
67
|
+
the `Runner` service.
|
|
68
|
+
|
|
69
|
+
A run executes under the span `Eval.run` (attributes `suite` and `runId`), and
|
|
70
|
+
each case under a child span `Eval.case` (attribute `case`), so any tracer the
|
|
71
|
+
host provides shows which case a slow or failing run spent its time in.
|
|
72
|
+
|
|
73
|
+
## How comparison works
|
|
74
|
+
|
|
75
|
+
Four rules decide what a comparison reports, and none of them are obvious:
|
|
76
|
+
|
|
77
|
+
- **The step key decides which finding you get.** A score that dropped at a
|
|
78
|
+
_changed_ step key is a `regression`: the target produced different work and it
|
|
79
|
+
graded worse. A score that moved at an _unchanged_ step key is
|
|
80
|
+
`nondeterminism`: the same work graded differently twice. A gate reads both as
|
|
81
|
+
red.
|
|
82
|
+
- **`Observation.scorer` is a digest, not a name.** It is the scorer key, derived
|
|
83
|
+
from the scorer's own `{ id, version, config }`, and it is what a baseline
|
|
84
|
+
matches on. `Observation.scorerName` carries the readable name beside it, and a
|
|
85
|
+
Markdown report prints `name (first 8 of the key)`, or the bare key for a
|
|
86
|
+
scorer declared without a name. Report cells, gate summaries, and runner
|
|
87
|
+
diagnostics all use that one label.
|
|
88
|
+
- **A binding matches its target by reference identity.** `binding.appliesTo` has
|
|
89
|
+
to be the same flow value the execution reports as its `target`; a structurally
|
|
90
|
+
equal copy grades nothing.
|
|
91
|
+
- **A baseline belongs to one suite.** Validated baselines and newly written
|
|
92
|
+
artifacts require a top-level `suite`, even with no records. Every record
|
|
93
|
+
requires its own `suite`. `Baseline.load` accepts a suite-less version-1
|
|
94
|
+
artifact only when its nonempty records all name the same suite, then sets
|
|
95
|
+
the top-level `suite` to that value. Empty or ambiguous legacy artifacts fail
|
|
96
|
+
with `invalid_baseline` at `suite`. Comparison refuses an artifact or any
|
|
97
|
+
record that names a suite other than the run's with `invalid_baseline`.
|
|
98
|
+
|
|
99
|
+
## Failure codes
|
|
100
|
+
|
|
101
|
+
`EvalError.code` is the stable branch point.
|
|
102
|
+
|
|
103
|
+
| Code | Raised when | Who fixes it |
|
|
104
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
|
|
105
|
+
| `invalid_suite` | The suite declaration is wrong: a name, a case, a concurrency, a fixture line, non-cloneable case data, or an undecidable sampling policy. | The suite author |
|
|
106
|
+
| `invalid_run_options` | `Runner.run`'s own options are wrong: an empty `runId`, or an `at` that is not a canonical UTC instant. | The caller |
|
|
107
|
+
| `invalid_baseline` | The committed baseline is unreadable, holds a record the schema rejects, or belongs to another suite. | Regenerate the baseline |
|
|
108
|
+
| `invalid_tolerance` | A comparison tolerance is not a finite non-negative number. | The caller |
|
|
109
|
+
| `invalid_trials` | A `Trials` run, pass, or `k` count is outside its bounds. | The caller |
|
|
110
|
+
| `executor` | The target flow failed for a case, or no executor was available. | The target or the wiring |
|
|
111
|
+
| `ambiguous_score_job` | Two jobs share a step key and scorer, so an order-only runner cannot attribute their results to cases. | The suite or batch runner |
|
|
112
|
+
| `scorer_protocol` | A batch runner returned the wrong number of observations, or observations identifying jobs other than the ones it was given. | The batch runner |
|
|
113
|
+
| `scorer_unavailable` | No batch runner was available to score with. | The wiring |
|
|
114
|
+
|
|
115
|
+
Most failures carry a `path` locating the offending value: `cases[1].input`,
|
|
116
|
+
`records[3].score`, `options.at`, `runBatch[0]`. A case whose target failed keeps
|
|
117
|
+
the executor's own path when it named one and is located at `cases['<name>']`
|
|
118
|
+
otherwise, so a failed case is always locatable. Only two failures carry no path:
|
|
119
|
+
one about an artifact as a whole (`Baseline` is not JSON, or is not an object)
|
|
120
|
+
and one about the absence of a runner (`scorer_unavailable`).
|
|
121
|
+
|
|
122
|
+
## The batch protocol
|
|
123
|
+
|
|
124
|
+
`Runner.ScoreBatchRunner.runBatchCorrelated` returns each observation with its
|
|
125
|
+
job identity. A correlated runner may return results in any order. A run rejects
|
|
126
|
+
a duplicate identity, an unknown identity, a missing identity, the wrong result
|
|
127
|
+
count, or an observation that does not echo its job's `targetStepKey` and
|
|
128
|
+
`scorerKey` with `scorer_protocol`.
|
|
129
|
+
|
|
130
|
+
`runBatch` remains the order-only protocol for adapters that cannot return job
|
|
131
|
+
identities. Its contract is positional:
|
|
132
|
+
|
|
133
|
+
1. Exactly one observation per job.
|
|
134
|
+
2. In the order the jobs were given.
|
|
135
|
+
3. Each observation repeats its job's `targetStepKey` and `scorerKey`.
|
|
136
|
+
|
|
137
|
+
A run verifies all three and fails with `scorer_protocol` when one is broken.
|
|
138
|
+
Before calling an order-only runner, it also refuses two jobs that share a step
|
|
139
|
+
key and scorer with `ambiguous_score_job`. Give each case its own step key, or
|
|
140
|
+
provide a runner that implements `runBatchCorrelated`. A returned score that is
|
|
141
|
+
not finite and inside [0, 1] becomes an inconclusive observation naming the
|
|
142
|
+
scorer and the offending value; the run's own timestamp, not the adapter's, is
|
|
143
|
+
what reaches the observation.
|
|
144
|
+
|
|
145
|
+
Both adapter methods retain their receiver and receive the suite's concurrency.
|
|
146
|
+
Scorer callbacks execute inside their job effects. A callback that throws
|
|
147
|
+
synchronously or a batch that fails produces inconclusive observations; failure
|
|
148
|
+
text is bounded and safe to convert. Interruption still cancels the run.
|
|
149
|
+
`layerNoop` returns identity-correlated inconclusive results, including when
|
|
150
|
+
cases share a step key and scorer, without invoking scorer callbacks.
|
|
151
|
+
|
|
152
|
+
## Determinism and limits
|
|
153
|
+
|
|
154
|
+
- `Runner.run` takes `runId` and `at` from the caller. `runId` identifies the
|
|
155
|
+
`RunResult` and joins every score job's identity; `at` stamps every
|
|
156
|
+
observation. Given identical executor results, scorer results, metadata, and
|
|
157
|
+
options, two runs produce identical observations and identical `Report.json`
|
|
158
|
+
bytes. Executor and scorer effects run afresh each time, so clocks, models,
|
|
159
|
+
randomness, and external state inside them are the caller's to control.
|
|
160
|
+
- When the `Suite.make` effect runs, it snapshots its options, copies case and
|
|
161
|
+
binding data with `structuredClone`, and freezes the arrays it returns. The
|
|
162
|
+
suite cannot change after it is validated.
|
|
163
|
+
- `Suite.limits` declares the ceilings: `concurrency` 1024, `cases` 10000,
|
|
164
|
+
`fixtureLength` 8388608 code units for one JSON Lines fixture.
|
|
165
|
+
- `Report.json` embeds each case's raw `execution.output`. Strings are capped at
|
|
166
|
+
8192 code units and everything JSON cannot express becomes a named marker
|
|
167
|
+
(`[circular]`, `[depth exceeded]`, `[NaN]`, `[function]`), so the serializer is
|
|
168
|
+
total. Nothing is redacted: a suite whose cases carry secrets must not print
|
|
169
|
+
the report where the log is readable.
|
|
170
|
+
- The encoder is bounded in total, not only per branch. A value referenced
|
|
171
|
+
twice is expanded twice, which a shared acyclic graph turns into exponential
|
|
172
|
+
work well below the depth ceiling, so traversal stops after 2000000 values or
|
|
173
|
+
33554432 output code units and every remaining value becomes
|
|
174
|
+
`[budget exceeded]`. The budget is spent in traversal order, which the key
|
|
175
|
+
sort fixes, so one value always truncates at the same place.
|
|
176
|
+
- `Report.markdown` neutralizes inline GFM and raw HTML in every cell and the
|
|
177
|
+
suite heading value. It caps each value at 240 escaped UTF-16 code units,
|
|
178
|
+
plus an ellipsis when truncated, without splitting escapes or code points.
|
|
179
|
+
|
|
180
|
+
## Module reference
|
|
181
|
+
|
|
182
|
+
The root entry point exports these namespaces; each is also importable from
|
|
183
|
+
`@smthrs/evals/<Module>`. `@smthrs/evals/package.json` is exported too;
|
|
184
|
+
`internal/*` and nested `*/index` subpaths are not public.
|
|
185
|
+
|
|
186
|
+
### EvalError
|
|
187
|
+
|
|
188
|
+
Typed evaluation failures and the stable codes a caller branches on.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
class EvalError extends Schema.TaggedError<EvalError>()("flows/evals/EvalError", {
|
|
192
|
+
code: EvalErrorCode,
|
|
193
|
+
message: Schema.String,
|
|
194
|
+
path: Schema.optional(Schema.String),
|
|
195
|
+
cause: Schema.optional(Schema.Defect())
|
|
196
|
+
}) {}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`code` is the stable branch point, `message` is the sentence a CI log shows,
|
|
200
|
+
and `path` locates the offending value inside the input the caller supplied
|
|
201
|
+
(`records[3].score`, `cases[1].input`, `options.at`). `cause` retains the
|
|
202
|
+
original failure; it is never rendered on its own, so a message that matters
|
|
203
|
+
to an operator says so itself. `EvalErrorCode` is the schema and type for the
|
|
204
|
+
nine codes in the failure table.
|
|
205
|
+
|
|
206
|
+
### Suite
|
|
207
|
+
|
|
208
|
+
Fixed suite declarations and their JSON Lines fixture format.
|
|
209
|
+
|
|
210
|
+
- `Case`: `{ name, input, expected? }`. `input` is handed to the executor and
|
|
211
|
+
`expected` is offered to a bound scorer as ground truth. A declared
|
|
212
|
+
`expected`, including `null`, wins over the binding's `groundTruth`; only an
|
|
213
|
+
absent one defers to the binding. Both are snapshots taken by `make`.
|
|
214
|
+
- `Binding`: a scorer binding accepted from [@smthrs/scorers](/api/scorers).
|
|
215
|
+
The binding's `appliesTo` flow is matched against an execution's `target` by
|
|
216
|
+
reference identity.
|
|
217
|
+
- `MakeOptions`: `{ name, cases, bindings?, concurrency }`.
|
|
218
|
+
- `Suite`: `{ name, cases, bindings, concurrency }`, with `cases` and
|
|
219
|
+
`bindings` frozen and every data field a copy the caller cannot reach.
|
|
220
|
+
- `limits`: `{ concurrency: 1024, cases: 10000, fixtureLength: 8388608 }`, the
|
|
221
|
+
declared ceilings a suite is validated against.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const make = (options: MakeOptions): Effect.Effect<Suite, EvalError>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Builds and validates a fixed suite. When the effect runs, it reads every
|
|
228
|
+
option, case field, and binding field exactly once, then validates and copies
|
|
229
|
+
only what it read, so a getter cannot hand the suite a value validation never
|
|
230
|
+
saw. Every case and binding is then copied with `structuredClone`, which is
|
|
231
|
+
also the check that the data is inert. Fails with `invalid_suite` for an empty
|
|
232
|
+
or control-character name, no cases, more than `limits.cases` cases, a
|
|
233
|
+
duplicate case name, or a concurrency that is not a safe integer in
|
|
234
|
+
[1, `limits.concurrency`].
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const fromJsonLines = (text: string, options: JsonLinesOptions): Effect.Effect<Suite, EvalError>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Loads the `{ name, input, expected? }` JSON Lines fixture format.
|
|
241
|
+
`JsonLinesOptions` is `{ name, bindings?, concurrency }`. Blank lines are
|
|
242
|
+
skipped, a leading byte-order mark is stripped, and both LF and CRLF terminate
|
|
243
|
+
a line. A malformed line fails with `invalid_suite` carrying the 1-based line
|
|
244
|
+
number in both the message and the path; a fixture larger than
|
|
245
|
+
`limits.fixtureLength` is rejected before any of it is parsed. All ceilings
|
|
246
|
+
are inclusive. After 10000 cases, the next non-blank line fails before parsing
|
|
247
|
+
or decoding with `invalid_suite` at `cases` and the message
|
|
248
|
+
`Suite must contain at most 10000 cases, got 10001`; later lines are not decoded.
|
|
249
|
+
|
|
250
|
+
### CaseExecutor
|
|
251
|
+
|
|
252
|
+
The injectable boundary that executes one case against a target flow. A suite
|
|
253
|
+
says what to run; this service says how.
|
|
254
|
+
|
|
255
|
+
- `Execution`: `{ output, stepKey, latencyMs, target }`. `target` is the flow
|
|
256
|
+
value the case actually executed, matched against each binding's `appliesTo`
|
|
257
|
+
by reference identity, so it has to be the declared flow itself rather than
|
|
258
|
+
a copy of it.
|
|
259
|
+
- `Run`: `(suiteCase: Suite.Case) => Effect.Effect<Execution, EvalError>`.
|
|
260
|
+
- `Service`: `{ run: Run }`.
|
|
261
|
+
- `CaseExecutor`: the `Context.Service` tag, `flows/evals/CaseExecutor`.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
const make = (run: Run): Service
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Builds an executor from its one callback. Throws a `TypeError` when `run` is
|
|
268
|
+
not a function: an executor that
|
|
269
|
+
silently degraded would turn one wiring mistake into a whole suite of cases
|
|
270
|
+
failing with `executor`, which reads as a broken target rather than a missing
|
|
271
|
+
one.
|
|
272
|
+
|
|
273
|
+
`makeNoop()` builds an executor that fails every case with a typed `executor`
|
|
274
|
+
error, and `layerNoop` provides it.
|
|
275
|
+
|
|
276
|
+
### Runner
|
|
277
|
+
|
|
278
|
+
Deterministic suite execution and bound scorer evaluation.
|
|
279
|
+
|
|
280
|
+
- `Observation`: one score observation emitted by a suite run. Every
|
|
281
|
+
observation carries `case`, `scorer`, `stepKey`, and `at`, plus an optional
|
|
282
|
+
`scorerName`; a `score` observation adds `score` in [0, 1] with an optional
|
|
283
|
+
`reason`, and an `inconclusive` observation adds a required `reason`. Both
|
|
284
|
+
kinds may carry `meta`. `at` is the run's timestamp, not the scorer's: a
|
|
285
|
+
run's observations all carry one instant so a baseline stays reproducible.
|
|
286
|
+
- `ScoreRequest`: a request sent to the scorers batch runner, joining the
|
|
287
|
+
case, the step key, the binding, and the scorer input (`input`, `output`,
|
|
288
|
+
optional `groundTruth` and `context`, `latencyMs`).
|
|
289
|
+
- `ScoreJob`: a blocking scorer job, matching the `Runner` module in
|
|
290
|
+
[@smthrs/scorers](/api/scorers): an `identity`, an `observation` seed with
|
|
291
|
+
`targetStepKey` and `scorerKey`, the `score` effect, and `at` in
|
|
292
|
+
milliseconds.
|
|
293
|
+
- `ScoreObservation`: a score result aligned with a `ScoreRequest`.
|
|
294
|
+
- `BatchResult`: `{ identity, observation }`, a batch result tagged with the
|
|
295
|
+
identity of the job that produced it.
|
|
296
|
+
- `CaseResult`: `{ case, execution?, error?, observations }`, the per-case
|
|
297
|
+
result retained by the runner.
|
|
298
|
+
- `RunResult`: `{ runId, suite, cases, observations }`, the stable result of a
|
|
299
|
+
suite run.
|
|
300
|
+
- `RunOptions`: `{ scorer?, runId, sampleId?, at }`.
|
|
301
|
+
- `Runner`: the `Context.Service` tag, `flows/evals/Runner`, for an injectable
|
|
302
|
+
batch runner. `run` does not require it: a run scores with `options.scorer`
|
|
303
|
+
when one is passed, otherwise with this service when one is provided,
|
|
304
|
+
otherwise in process through `makeInline`.
|
|
305
|
+
- `ScoreBatchRunner`: the structural adapter described in the batch protocol
|
|
306
|
+
section.
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const run = (
|
|
310
|
+
suite: Suite,
|
|
311
|
+
options: RunOptions
|
|
312
|
+
): Effect.Effect<RunResult, EvalError, CaseExecutor>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Runs a fixed suite with bounded execution and declaration-order results. Cases
|
|
316
|
+
run through the provided `CaseExecutor` at the suite's concurrency. Every
|
|
317
|
+
execution is graded by the bindings whose `appliesTo` is the flow the
|
|
318
|
+
execution reports as its target, matched by reference identity. A case whose
|
|
319
|
+
target failed keeps its typed error and produces no observations; a scorer
|
|
320
|
+
that failed produces an inconclusive observation. The run itself fails only
|
|
321
|
+
with `invalid_run_options` for a non-canonical run identity or timestamp,
|
|
322
|
+
`invalid_suite` for an unusable sampling policy, `ambiguous_score_job` when an
|
|
323
|
+
order-only runner cannot distinguish two jobs, or `scorer_protocol` for a
|
|
324
|
+
batch runner that broke the `ScoreBatchRunner` contract.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const makeInline = (): ScoreBatchRunner
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Builds the in-process batch runner a run scores with by default. Each job's
|
|
331
|
+
scorer runs in the current process, its result is checked by the scorers
|
|
332
|
+
package's own `Scorer.validate`, and a scorer that fails becomes an
|
|
333
|
+
inconclusive observation naming its cause rather than failing the run.
|
|
334
|
+
`layerInline` provides it as the `Runner` service.
|
|
335
|
+
|
|
336
|
+
`layerNoop` provides a batch runner that is never available. Every bound score
|
|
337
|
+
under it becomes an inconclusive observation, which a gate grades as an
|
|
338
|
+
undecidable run rather than a red. Provide it to state that a suite must not
|
|
339
|
+
score.
|
|
340
|
+
|
|
341
|
+
### Baseline
|
|
342
|
+
|
|
343
|
+
Committed baselines: what a suite used to score.
|
|
344
|
+
|
|
345
|
+
- `version`: `1`, the current committed baseline artifact version.
|
|
346
|
+
- `BaselineRecord`: `{ suite, case, scorer, scorerName?, stepKey, score }`,
|
|
347
|
+
one successful score retained by a baseline.
|
|
348
|
+
- `Baseline`: `{ version, suite, records }`. `suite` records artifact
|
|
349
|
+
ownership even when `records` is empty.
|
|
350
|
+
- `limits`: `{ length: 16777216 }`, the longest baseline JSON text `load`
|
|
351
|
+
parses, in UTF-16 code units.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const fromRun = (run: RunResult): Effect.Effect<Baseline, EvalError>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Builds and validates a baseline from a run's successful observations.
|
|
358
|
+
Inconclusive observations are dropped: a baseline records what was measured,
|
|
359
|
+
and an inconclusive observation measured nothing.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
const make = (
|
|
363
|
+
baseline: Omit<Baseline, "version"> & { readonly version?: typeof version }
|
|
364
|
+
): Effect.Effect<Baseline, EvalError>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Validates an in-memory baseline. Every known field is read once, records are
|
|
368
|
+
rebuilt from the validated values, and the array is frozen. Fails with
|
|
369
|
+
`invalid_baseline` carrying the record index and field name in `path` for a
|
|
370
|
+
wrong version, a non-array `records`, a record that is not an object, an
|
|
371
|
+
identity field that is not a string or holds a control character, or a score
|
|
372
|
+
that is not finite in [0, 1]. A control character is any of the C0 controls (U+0000 through U+001F), DEL (U+007F), C1 controls (U+0080
|
|
373
|
+
through U+009F), and the bidirectional controls U+061C, U+200E, U+200F,
|
|
374
|
+
U+202A through U+202E, and U+2066 through U+2069.
|
|
375
|
+
A top-level `suite` holding a control character fails at `path: "suite"`.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
const write = (baseline: Baseline): string
|
|
379
|
+
const load = (text: string): Effect.Effect<Baseline, EvalError>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`write` serializes a baseline with recursively sorted keys and stable numbers,
|
|
383
|
+
records ordered by an injective encoding of `(suite, case, scorer, stepKey)`,
|
|
384
|
+
ending with a newline and always including the top-level `suite`. `load`
|
|
385
|
+
parses and validates baseline JSON, failing with `invalid_baseline`. For a
|
|
386
|
+
version-1 artifact without a top-level `suite`, it derives ownership only when
|
|
387
|
+
all records name the same suite. Empty or ambiguous legacy artifacts fail at
|
|
388
|
+
`path: "suite"` with an explicit reason. A present non-string `suite` is invalid.
|
|
389
|
+
Text longer than `limits.length` fails at `path: "text"` before any of it is
|
|
390
|
+
parsed.
|
|
391
|
+
Writing the loaded baseline retains its scores and adds the inferred `suite`.
|
|
392
|
+
|
|
393
|
+
### Regression
|
|
394
|
+
|
|
395
|
+
Step-key-aware comparison of a run against a baseline.
|
|
396
|
+
|
|
397
|
+
- `Tolerances`: `{ absolute?, relative? }`. A move is reported only when it
|
|
398
|
+
exceeds both tolerances, so either one alone is enough to silence it. Both
|
|
399
|
+
default to 0, which reports every move.
|
|
400
|
+
- `Regression`: `{ case, scorer, baseline, actual, drop }`, a score drop at a
|
|
401
|
+
changed step key.
|
|
402
|
+
- `Nondeterminism`: `{ case, scorer, baseline, actual, delta }`, a changed
|
|
403
|
+
score at the same step key.
|
|
404
|
+
- `MissingObservation`: `{ side, case, scorer, scorerName?, stepKey }`. `side`
|
|
405
|
+
names the side the observation is missing from: `"run"` for a baseline
|
|
406
|
+
record the run never reproduced, `"baseline"` for a score no baseline record
|
|
407
|
+
accounts for.
|
|
408
|
+
- `Report`: `{ suite, baseline, run, regressions, nondeterminism, missing,
|
|
409
|
+
samples, inconclusive }`, the complete comparison. `samples` and
|
|
410
|
+
`inconclusive` are in-memory views filtered out of `run.observations` by
|
|
411
|
+
`kind`; `Report.json` recomputes rather than serializes them.
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
const compare = (
|
|
415
|
+
baseline: Baseline,
|
|
416
|
+
run: RunResult,
|
|
417
|
+
tolerances?: Tolerances
|
|
418
|
+
): Effect.Effect<Report, EvalError>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Compares a run to a baseline, preserving missing and inconclusive
|
|
422
|
+
observations. Records and observations are grouped by `(case, scorer)` and
|
|
423
|
+
then paired by step key, so a scorer that ran several times against one case
|
|
424
|
+
is compared pairwise instead of by array position. Fails with
|
|
425
|
+
`invalid_tolerance` when a tolerance is not finite and non-negative, and with
|
|
426
|
+
`invalid_baseline` when the artifact or any of its records belongs to a suite
|
|
427
|
+
other than the one the run reports.
|
|
428
|
+
|
|
429
|
+
### Report
|
|
430
|
+
|
|
431
|
+
Canonical JSON and Markdown renderings of a comparison.
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
const version: 1
|
|
435
|
+
interface Data {
|
|
436
|
+
readonly reportVersion: 1
|
|
437
|
+
readonly suite: string
|
|
438
|
+
readonly baseline: Baseline.Baseline
|
|
439
|
+
readonly run: { runId, suite, cases: ReadonlyArray<Case>, observations: ReadonlyArray<Runner.Observation> }
|
|
440
|
+
readonly regressions: ReadonlyArray<Omit<Regression.Regression, "actual"> & { actual: number }>
|
|
441
|
+
readonly nondeterminism: ReadonlyArray<Omit<Regression.Nondeterminism, "actual"> & { actual: number }>
|
|
442
|
+
readonly missing: ReadonlyArray<Regression.MissingObservation>
|
|
443
|
+
}
|
|
444
|
+
const data = (report: Regression.Report): Data
|
|
445
|
+
const json = (report: Regression.Report): string
|
|
446
|
+
const markdown = (report: Regression.Report): string
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`Data` is the serialized wire shape and `version` is the format version
|
|
450
|
+
stamped on it as `reportVersion`. It versions the comparison independently of
|
|
451
|
+
the nested `Baseline.version`: the wire shape can change without the committed
|
|
452
|
+
baseline artifact changing.
|
|
453
|
+
|
|
454
|
+
`run.observations` is the observation table. Every observation the report
|
|
455
|
+
mentions appears in it exactly once and everything else refers to one by
|
|
456
|
+
index: `run.cases[i].observations` is a list of indexes, and a regression or
|
|
457
|
+
nondeterminism entry's `actual` is a single index. `samples` and
|
|
458
|
+
`inconclusive` are absent, because both are filters of `run.observations` by
|
|
459
|
+
`kind` and a reader recomputes them. An in-memory `Regression.Report` holds
|
|
460
|
+
the same observation in up to four of those places, so encoding it directly
|
|
461
|
+
repeated every `reason` and every `meta` that many times.
|
|
462
|
+
|
|
463
|
+
`data` projects a comparison into that shape. Observations are pooled by
|
|
464
|
+
identity, not by value, so two distinct objects that happen to be equal stay
|
|
465
|
+
two rows. The table starts as `run.observations` and grows to hold any
|
|
466
|
+
observation reached only through a case or a comparison entry, so a hand-built
|
|
467
|
+
report whose case lists are not flattened still serializes every reference.
|
|
468
|
+
|
|
469
|
+
`json` serializes a regression report as stable, sorted-key `Data` JSON. The
|
|
470
|
+
report embeds each case's raw `execution.output`, which comes from an
|
|
471
|
+
arbitrary target flow, so the encoding is total rather than trusting: keys are
|
|
472
|
+
sorted by code unit, embedded strings are capped, total traversal is bounded,
|
|
473
|
+
and anything JSON cannot express becomes a named marker instead of a
|
|
474
|
+
`RangeError` or a silent `null`. Two identical runs therefore produce
|
|
475
|
+
byte-identical JSON. Nothing redacts the embedded output.
|
|
476
|
+
|
|
477
|
+
`markdown` renders the report an operator reads in a CI log. Every count in
|
|
478
|
+
the summary that is not zero has a section naming its rows: the regressions
|
|
479
|
+
and the nondeterminism a gate reads as red, and the case failures, missing
|
|
480
|
+
observations, and inconclusive observations that leave a gate undecided.
|
|
481
|
+
Every cell and the suite heading value replace each control character (see
|
|
482
|
+
[Baseline](#baseline)) with a space. Backslashes, pipes, backticks, asterisks,
|
|
483
|
+
underscores, brackets, angle brackets, exclamation marks, hashes, tildes,
|
|
484
|
+
ampersands, dots, colons, and at signs are backslash-escaped. This preserves
|
|
485
|
+
literal text instead of interpreting table delimiters, inline GFM formatting,
|
|
486
|
+
links, images, raw HTML, entities, or URL/email autolinks. Each value is capped
|
|
487
|
+
at 240 escaped UTF-16 code units, followed by an ellipsis when truncated;
|
|
488
|
+
escapes and Unicode code points remain whole.
|
|
489
|
+
|
|
490
|
+
### Gate
|
|
491
|
+
|
|
492
|
+
Score thresholds and the CI exit grade a comparison earns.
|
|
493
|
+
|
|
494
|
+
- `Options`: `{ mean?, min?, perCase? }`. `mean` gates the arithmetic mean of
|
|
495
|
+
every score observation, `min` gates the lowest one, and `perCase` gates
|
|
496
|
+
each named case's lowest score. With none of them set the gate still runs,
|
|
497
|
+
as `mean(0)`, so a run with no score at all is undecidable rather than a
|
|
498
|
+
pass.
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
const check = (report: Regression.Report, options?: Options): Effect.Effect<Verdict, ScoreGateError>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Checks thresholds through the shared ScoreGate arithmetic in
|
|
505
|
+
[@smthrs/scorers/ScoreGate](/api/scorers). The threshold gates always run: an
|
|
506
|
+
unobserved case cannot excuse the cases that were observed. Regressions and
|
|
507
|
+
nondeterminism are findings and force a `Failed` verdict; failed cases and
|
|
508
|
+
missing observations are environment faults and travel beside the verdict. A
|
|
509
|
+
threshold outside [0, 1] fails in the error channel with a `ScoreGateError`,
|
|
510
|
+
code `invalid_threshold`.
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
const ciGrade = (verdict: Verdict): { readonly exitCode: 0 | 1 | 5; readonly summary: string }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Maps a gate verdict to the shared CI convention: a finding is exit code 1, an
|
|
517
|
+
undecidable run is exit code 5, and a clean pass is exit code 0. A pass that
|
|
518
|
+
carries unresolved observations exits 5 as well: the gates it met were met
|
|
519
|
+
over fewer observations than the suite declared. The summary is one log line:
|
|
520
|
+
every control character in it is replaced with a space, so a step key or a
|
|
521
|
+
target failure that reached a verdict reason cannot print its own line or a
|
|
522
|
+
terminal escape.
|
|
523
|
+
|
|
524
|
+
### Trials
|
|
525
|
+
|
|
526
|
+
Repeated-run statistics for live model evaluations. A case that runs `n`
|
|
527
|
+
times and passes `c` of them is summarized at `k`.
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
const passAtK = (n: number, c: number, k: number): number
|
|
531
|
+
const passHatK = (n: number, c: number, k: number): number
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`passAtK` is the unbiased estimator `1 - C(n - c, k) / C(n, k)` (Chen et al.,
|
|
535
|
+
2021): the chance at least one of `k` runs passes. `passHatK` is
|
|
536
|
+
`C(c, k) / C(n, k)`: the chance all `k` pass, the consistency measure of
|
|
537
|
+
tau-bench. Both are computed as products, so large `n` stays exact. Counts must
|
|
538
|
+
be integers with `0 <= c <= n` and `1 <= k <= n`; anything else throws an
|
|
539
|
+
`EvalError` with code `invalid_trials`.
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
const summarize = (outcomes: ReadonlyArray<boolean>, k: number): Summary
|
|
543
|
+
const aggregate = (cases: Readonly<Record<string, ReadonlyArray<boolean>>>, k: number): Aggregate
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
- `Summary`: `{ n, passes, rate, passAt1, passAtK, passHatK, stderr }`.
|
|
547
|
+
`stderr` is `sqrt(rate * (1 - rate) / n)`, and 0 for one run.
|
|
548
|
+
- `Aggregate`: `{ cases, passAt1, passAtK, passHatK, allPass, perCase }`. The
|
|
549
|
+
rates are means over cases; `allPass` is the fraction of cases whose every
|
|
550
|
+
run passed. A case with fewer than `k` runs is summarized at `k = n`, and no
|
|
551
|
+
cases aggregate to zeros. A case with no runs throws `invalid_trials`.
|
|
552
|
+
|
|
553
|
+
## Export index
|
|
554
|
+
|
|
555
|
+
| Export | Category | Summary |
|
|
556
|
+
| ------------------------------- | ------------- | ------------------------------------------------------------------------------- |
|
|
557
|
+
| `EvalError.EvalErrorCode` | models | Stable evaluation failure codes. |
|
|
558
|
+
| `EvalError.EvalError` | errors | A typed failure raised while loading or executing an evaluation. |
|
|
559
|
+
| `Suite.Binding` | models | A scorer binding accepted from `@smthrs/scorers`. |
|
|
560
|
+
| `Suite.Case` | models | One immutable fixed-suite case. |
|
|
561
|
+
| `Suite.MakeOptions` | models | Options for constructing a suite. |
|
|
562
|
+
| `Suite.Suite` | models | A validated, named collection of fixed cases and scorer bindings. |
|
|
563
|
+
| `Suite.limits` | models | The declared ceilings a suite is validated against. |
|
|
564
|
+
| `Suite.make` | constructors | Builds and validates a fixed suite. |
|
|
565
|
+
| `Suite.JsonLinesOptions` | models | Options used when decoding JSON Lines. |
|
|
566
|
+
| `Suite.fromJsonLines` | constructors | Loads the `{ name, input, expected? }` JSON Lines fixture format. |
|
|
567
|
+
| `CaseExecutor.Execution` | models | The result of executing one target-flow case. |
|
|
568
|
+
| `CaseExecutor.Run` | models | The one callback a case executor is. |
|
|
569
|
+
| `CaseExecutor.Service` | services | Runtime shape for an injectable target-flow executor. |
|
|
570
|
+
| `CaseExecutor.CaseExecutor` | services | Injectable execution boundary for a target flow. |
|
|
571
|
+
| `CaseExecutor.make` | constructors | Builds an executor from its one callback. |
|
|
572
|
+
| `CaseExecutor.makeNoop` | constructors | Builds an executor that fails every case with a typed executor error. |
|
|
573
|
+
| `CaseExecutor.layerNoop` | layers | Provides the unavailable executor. |
|
|
574
|
+
| `Runner.Observation` | models | One score observation emitted by a suite run. |
|
|
575
|
+
| `Runner.ScoreRequest` | models | A request sent to the scorers batch runner. |
|
|
576
|
+
| `Runner.ScoreJob` | models | A blocking scorer job, matching the `Runner` module of `@smthrs/scorers`. |
|
|
577
|
+
| `Runner.ScoreBatchRunner` | services | Structural adapter for `@smthrs/scorers`' blocking batch runner. |
|
|
578
|
+
| `Runner.ScoreObservation` | models | A score result aligned with a `ScoreRequest`. |
|
|
579
|
+
| `Runner.BatchResult` | models | A batch result tagged with the identity of the job that produced it. |
|
|
580
|
+
| `Runner.CaseResult` | models | Per-case result retained by the deterministic runner. |
|
|
581
|
+
| `Runner.RunResult` | models | Stable result of a suite run. |
|
|
582
|
+
| `Runner.RunOptions` | models | Options for a deterministic suite run. |
|
|
583
|
+
| `Runner.Runner` | services | Injectable batch-runner service used when a caller wants a reusable adapter. |
|
|
584
|
+
| `Runner.makeInline` | constructors | Builds the in-process batch runner a run scores with by default. |
|
|
585
|
+
| `Runner.layerInline` | layers | Provides the in-process batch runner built by `makeInline`. |
|
|
586
|
+
| `Runner.run` | constructors | Runs a fixed suite with bounded execution and declaration-order results. |
|
|
587
|
+
| `Runner.layerNoop` | layers | Provides a batch runner that is never available. |
|
|
588
|
+
| `Baseline.version` | models | Current committed baseline artifact version. |
|
|
589
|
+
| `Baseline.BaselineRecord` | models | One successful score retained by a baseline. |
|
|
590
|
+
| `Baseline.Baseline` | models | Canonical committed evaluation baseline. |
|
|
591
|
+
| `Baseline.limits` | models | The declared ceilings a committed baseline is validated against. |
|
|
592
|
+
| `Baseline.fromRun` | constructors | Builds and validates a baseline from a run's successful observations. |
|
|
593
|
+
| `Baseline.make` | constructors | Validates an in-memory baseline. |
|
|
594
|
+
| `Baseline.write` | serialization | Serializes a baseline with recursively sorted keys and stable numbers. |
|
|
595
|
+
| `Baseline.load` | serialization | Loads and validates canonical baseline JSON. |
|
|
596
|
+
| `Regression.Tolerances` | models | Tolerances used for score comparisons. |
|
|
597
|
+
| `Regression.Regression` | models | A score drop at a changed step key. |
|
|
598
|
+
| `Regression.Nondeterminism` | models | A changed score at the same step key, indicating nondeterminism. |
|
|
599
|
+
| `Regression.MissingObservation` | models | An observation present on only one side of a comparison. |
|
|
600
|
+
| `Regression.Report` | models | Complete regression comparison. |
|
|
601
|
+
| `Regression.compare` | constructors | Compares a run to a baseline, preserving missing and inconclusive observations. |
|
|
602
|
+
| `Report.version` | serialization | Current serialized report format version. |
|
|
603
|
+
| `Report.Data` | serialization | The serialized shape of a regression report. |
|
|
604
|
+
| `Report.data` | serialization | Projects a comparison into its serialized form. |
|
|
605
|
+
| `Report.json` | serialization | Serializes a regression report as stable, sorted-key JSON. |
|
|
606
|
+
| `Report.markdown` | rendering | Renders a concise stable Markdown regression report. |
|
|
607
|
+
| `Gate.Options` | models | Thresholds accepted by a CI score gate. |
|
|
608
|
+
| `Gate.check` | constructors | Checks thresholds through `@smthrs/scorers`' shared ScoreGate arithmetic. |
|
|
609
|
+
| `Gate.ciGrade` | grading | Maps a gate verdict to the shared CI convention. |
|
|
610
|
+
| `Trials.Summary` | models | Repeated-run statistics for one case. |
|
|
611
|
+
| `Trials.Aggregate` | models | Repeated-run statistics averaged over cases. |
|
|
612
|
+
| `Trials.passAtK` | statistics | The unbiased pass@k estimator. |
|
|
613
|
+
| `Trials.passHatK` | statistics | The chance all `k` of `n` recorded runs pass. |
|
|
614
|
+
| `Trials.summarize` | statistics | Summarizes one case's run outcomes at `k`. |
|
|
615
|
+
| `Trials.aggregate` | statistics | Summarizes every case at `k` and averages over cases. |
|
|
616
|
+
|
|
617
|
+
## A worked suite
|
|
618
|
+
|
|
619
|
+
[`evals/agent`](https://github.com/smithersai/smithers/tree/main/evals/agent)
|
|
620
|
+
is a suite built on these modules that evaluates the Smithers agent from
|
|
621
|
+
[@smthrs/agent](/api/agent). Each case is a whole agent run against a scripted
|
|
622
|
+
model with no network access, reduced to one observation, and two scorers grade
|
|
623
|
+
it: one asks whether the run did what the case declares, the other asks whether
|
|
624
|
+
the observation is well formed at all. The run is gated on a committed
|
|
625
|
+
baseline. For the shape of that pipeline in your own code, see
|
|
626
|
+
[Gate a run in CI](./guides/gate-a-run-in-ci.md).
|