eve 0.10.0 → 0.11.0
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 +11 -0
- package/README.md +14 -0
- package/dist/src/cli/banner.d.ts +6 -3
- package/dist/src/cli/banner.js +1 -1
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/dev/tui/agent-header.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/evals/runner/run-evals.d.ts +1 -1
- package/dist/src/harness/input-requests.d.ts +20 -1
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/protocol/message.d.ts +11 -2
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/public/next/index.d.ts +1 -7
- package/dist/src/public/next/index.js +1 -1
- package/dist/src/public/next/vercel-output-config.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +27 -4
- package/dist/src/setup/scaffold/update/channels.js +1 -1
- package/docs/README.md +19 -5
- package/docs/agent-config.md +16 -19
- package/docs/channels/custom.mdx +24 -18
- package/docs/channels/discord.mdx +25 -13
- package/docs/channels/eve.mdx +24 -6
- package/docs/channels/github.mdx +41 -10
- package/docs/channels/linear.mdx +29 -36
- package/docs/channels/overview.mdx +16 -4
- package/docs/channels/slack.mdx +59 -32
- package/docs/channels/teams.mdx +19 -7
- package/docs/channels/telegram.mdx +23 -9
- package/docs/channels/twilio.mdx +28 -6
- package/docs/concepts/context-control.md +24 -41
- package/docs/concepts/default-harness.md +19 -9
- package/docs/concepts/execution-model-and-durability.md +14 -14
- package/docs/concepts/security-model.md +32 -8
- package/docs/concepts/sessions-runs-and-streaming.md +11 -9
- package/docs/connections.mdx +49 -29
- package/docs/evals/assertions.mdx +30 -29
- package/docs/evals/cases.mdx +10 -10
- package/docs/evals/judge.mdx +14 -14
- package/docs/evals/overview.mdx +15 -17
- package/docs/evals/reporters.mdx +19 -7
- package/docs/evals/running.mdx +2 -2
- package/docs/evals/targets.mdx +15 -4
- package/docs/getting-started.mdx +70 -31
- package/docs/guides/auth-and-route-protection.md +18 -12
- package/docs/guides/client/continuations.mdx +5 -13
- package/docs/guides/client/messages.mdx +2 -2
- package/docs/guides/client/output-schema.mdx +2 -2
- package/docs/guides/client/overview.mdx +4 -4
- package/docs/guides/client/streaming.mdx +6 -5
- package/docs/guides/deployment.md +22 -20
- package/docs/guides/dev-tui.md +85 -13
- package/docs/guides/dynamic-capabilities.md +24 -8
- package/docs/guides/dynamic-workflows.md +33 -12
- package/docs/guides/frontend/nextjs.mdx +28 -14
- package/docs/guides/frontend/nuxt.mdx +21 -4
- package/docs/guides/frontend/overview.mdx +11 -11
- package/docs/guides/frontend/sveltekit.mdx +20 -3
- package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
- package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
- package/docs/guides/hooks.md +14 -60
- package/docs/guides/instrumentation.md +24 -18
- package/docs/guides/remote-agents.md +19 -15
- package/docs/guides/session-context.md +12 -12
- package/docs/guides/state.md +25 -9
- package/docs/instructions.mdx +18 -10
- package/docs/introduction.md +12 -12
- package/docs/legal.md +16 -0
- package/docs/meta.json +1 -0
- package/docs/reference/cli.md +101 -53
- package/docs/reference/meta.json +1 -1
- package/docs/reference/project-layout.md +39 -32
- package/docs/reference/typescript-api.md +1 -1
- package/docs/sandbox.mdx +25 -19
- package/docs/schedules.mdx +16 -14
- package/docs/skills.mdx +8 -6
- package/docs/subagents.mdx +39 -13
- package/docs/tools.mdx +13 -9
- package/docs/tutorial/connect-a-warehouse.mdx +16 -7
- package/docs/tutorial/first-agent.mdx +21 -11
- package/docs/tutorial/guard-the-spend.mdx +11 -9
- package/docs/tutorial/how-it-runs.mdx +10 -8
- package/docs/tutorial/query-sample-data.mdx +8 -8
- package/docs/tutorial/remember-definitions.mdx +5 -5
- package/docs/tutorial/run-analysis.mdx +13 -11
- package/docs/tutorial/ship-it.mdx +70 -14
- package/docs/tutorial/team-playbooks.mdx +33 -10
- package/package.json +1 -1
|
@@ -3,37 +3,38 @@ title: "Assertions"
|
|
|
3
3
|
description: "Run-level methods, t.check value assertions, the matcher mini-language, and gate vs soft severity."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Assertions are how an eval grades what its `test(t)` function produced. Each one **records** a result onto `t` and returns a chainable handle
|
|
6
|
+
Assertions are how an eval grades what its `test(t)` function produced. Each one **records** a result onto `t` and returns a chainable handle. The runner reads the recorded results to compute the verdict, so a single run reports every failing assertion rather than dying on the first. There are two deterministic surfaces: run-level methods on `t`, and `t.check` for grading a specific value. For model-graded assertions, see [Judge](./judge).
|
|
7
7
|
|
|
8
8
|
## Run-level assertions
|
|
9
9
|
|
|
10
|
-
Run-level assertions read the whole run, so they take no value. They are methods on `t` and gate by default.
|
|
11
|
-
|
|
12
|
-
| Assertion | Asserts
|
|
13
|
-
| --------------------------------------------------- |
|
|
14
|
-
| `t.completed()` | The run did not fail and did not park on unanswered HITL input
|
|
15
|
-
| `t.didNotFail()` | No terminal failure and no `turn.failed`/`step.failed` events (parked runs pass)
|
|
16
|
-
| `t.waiting()` | The run parked on HITL input (for approval-shaped evals)
|
|
17
|
-
| `t.messageIncludes(token)` | Joined assistant text contains `token` (string or RegExp)
|
|
18
|
-
| `t.outputEquals(value)` / `t.outputMatches(schema)` | Deep equality
|
|
19
|
-
| `t.calledTool(name, opts?)` | A matching tool call happened (`input`, `output`, `isError`, `times` constraints)
|
|
20
|
-
| `t.notCalledTool(name)` | No call to `name`
|
|
21
|
-
| `t.toolOrder([...names])` | Tool names appear in order (other calls may interleave)
|
|
22
|
-
| `t.usedNoTools()` | No tool calls at all
|
|
23
|
-
| `t.maxToolCalls(n)` | At most `n` tool calls
|
|
24
|
-
| `t.noFailedActions()` | No tool, subagent, or skill action reported a failure
|
|
25
|
-
| `t.calledSubagent(name, opts?)` | A subagent delegation happened (`remoteUrl`, `output` constraints)
|
|
26
|
-
| `t.event(predicate, label)` | Escape hatch: any predicate over the typed event stream
|
|
27
|
-
|
|
28
|
-
`t.completed()` subsumes `t.didNotFail()
|
|
10
|
+
Run-level assertions read the whole run, so they take no value. They are methods on `t` and gate by default. Several key off whether a run **parked**: paused on an unanswered human-in-the-loop (HITL) input request, waiting for an approval or answer before it can continue.
|
|
11
|
+
|
|
12
|
+
| Assertion | Asserts |
|
|
13
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
14
|
+
| `t.completed()` | The run did not fail and did not park on unanswered HITL input |
|
|
15
|
+
| `t.didNotFail()` | No terminal failure and no `turn.failed`/`step.failed` events (parked runs pass) |
|
|
16
|
+
| `t.waiting()` | The run parked on HITL input (for approval-shaped evals) |
|
|
17
|
+
| `t.messageIncludes(token)` | Joined assistant text contains `token` (string or RegExp) |
|
|
18
|
+
| `t.outputEquals(value)` / `t.outputMatches(schema)` | Deep equality or Standard Schema (e.g. Zod) validation of the agent's structured output |
|
|
19
|
+
| `t.calledTool(name, opts?)` | A matching tool call happened (`input`, `output`, `isError`, `times` constraints) |
|
|
20
|
+
| `t.notCalledTool(name)` | No call to `name` |
|
|
21
|
+
| `t.toolOrder([...names])` | Tool names appear in order (other calls may interleave) |
|
|
22
|
+
| `t.usedNoTools()` | No tool calls at all |
|
|
23
|
+
| `t.maxToolCalls(n)` | At most `n` tool calls |
|
|
24
|
+
| `t.noFailedActions()` | No tool, subagent, or skill action reported a failure |
|
|
25
|
+
| `t.calledSubagent(name, opts?)` | A subagent delegation happened (`remoteUrl`, `output` constraints) |
|
|
26
|
+
| `t.event(predicate, label)` | Escape hatch: any predicate over the typed event stream |
|
|
27
|
+
|
|
28
|
+
`t.completed()` subsumes `t.didNotFail()`, so reach for `completed` unless you specifically want to allow a parked run. The structured output that `t.outputEquals` and `t.outputMatches` read is the agent's structured output (see the [output schema guide](../guides/client/output-schema)).
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
await t.send("What is the weather in Brooklyn?");
|
|
32
32
|
t.completed();
|
|
33
33
|
t.calledTool("get_weather");
|
|
34
|
-
t.usedNoTools(); // mutually exclusive with the line above — pick the one you mean
|
|
35
34
|
```
|
|
36
35
|
|
|
36
|
+
`t.calledTool` and `t.usedNoTools` are mutually exclusive; assert one or the other, never both in the same run.
|
|
37
|
+
|
|
37
38
|
## Value assertions with `t.check`
|
|
38
39
|
|
|
39
40
|
`t.check(value, assertion)` grades an explicit value against a builder from `eve/evals/expect`. The value can be `t.reply`, a turn's `.message`, parsed JSON, or any local you computed:
|
|
@@ -54,11 +55,11 @@ t.check(t.reply, similarity("Sunny, 72F")); // fuzzy 0–1 Levenshtein (soft)
|
|
|
54
55
|
| `matches(schema)` | validates against a Standard Schema | gate |
|
|
55
56
|
| `similarity(expected)` | normalized Levenshtein similarity, 1 = identical | soft |
|
|
56
57
|
|
|
57
|
-
Pick the cheapest builder that captures what "correct" means. When exact match is too strict but a judge model is overkill, `similarity` is the middle ground
|
|
58
|
+
Pick the cheapest builder that captures what "correct" means. When exact match is too strict but a judge model is overkill, `similarity` is the middle ground. For nuanced grading, reach for the [judge](./judge).
|
|
58
59
|
|
|
59
60
|
## The matcher mini-language
|
|
60
61
|
|
|
61
|
-
`t.calledTool` and `t.calledSubagent` take a matcher object
|
|
62
|
+
`t.calledTool` and `t.calledSubagent` take a matcher object: `{ input, output, isError, times }` for tools, `{ remoteUrl, output }` for subagents. Each field accepts a literal (objects partial-deep-match), a RegExp, or a function. A matcher function receives the value and returns either a boolean (acts as a predicate) or an expected value to compare against (handy for runner-assigned values like environment-provided URLs):
|
|
62
63
|
|
|
63
64
|
```ts
|
|
64
65
|
t.calledTool("bash", { input: { command: /^pwd/ }, isError: false, times: 1 });
|
|
@@ -73,9 +74,9 @@ t.calledSubagent("weather", {
|
|
|
73
74
|
|
|
74
75
|
## Run state and derived facts
|
|
75
76
|
|
|
76
|
-
A turn that leaves the session open for a next message is the normal end state of a successful turn
|
|
77
|
+
Beyond the raw `t.events` stream, the runner derives typed facts the assertions read: tool calls (name, input, output, error state), subagent calls, and HITL input requests. A turn that leaves the session open for a next message is the normal end state of a successful turn; parking on unanswered HITL input is tracked separately, and that is what `t.completed()` and `t.waiting()` key off.
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
The built-in assertions cover almost everything. When you need to read the stream directly, `t.event(predicate, label)` is the escape hatch:
|
|
79
80
|
|
|
80
81
|
```ts
|
|
81
82
|
t.event(
|
|
@@ -87,11 +88,11 @@ t.event(
|
|
|
87
88
|
|
|
88
89
|
## Severity
|
|
89
90
|
|
|
90
|
-
Every assertion returns a chainable handle. Severity rides on the assertion
|
|
91
|
+
Every assertion returns a chainable handle. Severity rides on the assertion, so there is no separate thresholds map to keep in sync.
|
|
91
92
|
|
|
92
|
-
- `.gate(threshold?)`
|
|
93
|
-
- `.soft(threshold?)`
|
|
94
|
-
- `.atLeast(threshold)`
|
|
93
|
+
- `.gate(threshold?)` is hard. A miss marks the eval `failed` and `eve eval` exits non-zero.
|
|
94
|
+
- `.soft(threshold?)` is tracked data. A below-threshold miss marks the eval `scored`, fatal only under `--strict`. With no threshold, it is tracked-only and never fails.
|
|
95
|
+
- `.atLeast(threshold)` is soft with a bar (equivalent to `.soft(threshold)`).
|
|
95
96
|
|
|
96
97
|
The defaults are chosen so you rarely set severity. Run-level methods and `includes`/`equals`/`matches` are gates; `similarity` and every `t.judge.*` assertion are soft. Annotate only when you deviate:
|
|
97
98
|
|
package/docs/evals/cases.mdx
CHANGED
|
@@ -3,11 +3,11 @@ title: "Cases"
|
|
|
3
3
|
description: "Author single-turn and multi-turn evals with test(t), and fan one file out over a dataset."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Each eval file is one graded case. The runner executes
|
|
6
|
+
Each eval file is one graded case by default, and a single file can fan out over a dataset by default-exporting an array (covered below). The runner executes each `test(t)` function against the target, captures every event, and computes a verdict from the [assertions](./assertions) you recorded. Every eval shares one shape, whether single-turn, multi-turn, human-in-the-loop (HITL), or dataset-driven: one `async test(t)` function that drives the agent and asserts inline.
|
|
7
7
|
|
|
8
8
|
## Single-turn evals
|
|
9
9
|
|
|
10
|
-
The common case sends one turn and asserts on the reply. `t.send(input)` resolves once the turn settles
|
|
10
|
+
The common case sends one turn and asserts on the reply. `t.send(input)` resolves once the turn settles, and `t.reply` is the last assistant message:
|
|
11
11
|
|
|
12
12
|
```ts title="evals/weather/brooklyn-forecast.eval.ts"
|
|
13
13
|
import { defineEval } from "eve/evals";
|
|
@@ -22,7 +22,7 @@ export default defineEval({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Some evals only care about behavior, not text
|
|
25
|
+
Some evals only care about behavior, not text. Assert on the run and skip the content check entirely:
|
|
26
26
|
|
|
27
27
|
```ts title="evals/weather/no-tools-for-greetings.eval.ts"
|
|
28
28
|
import { defineEval } from "eve/evals";
|
|
@@ -43,7 +43,7 @@ Identity is the file path, so directories are the grouping mechanism. `evals/wea
|
|
|
43
43
|
```text
|
|
44
44
|
evals/
|
|
45
45
|
├── weather/
|
|
46
|
-
│ ├── shared.ts # helpers
|
|
46
|
+
│ ├── shared.ts # helpers, not an eval
|
|
47
47
|
│ ├── brooklyn-forecast.eval.ts
|
|
48
48
|
│ └── no-tools-for-greetings.eval.ts
|
|
49
49
|
└── smoke.eval.ts
|
|
@@ -51,7 +51,7 @@ evals/
|
|
|
51
51
|
|
|
52
52
|
## Multi-turn evals
|
|
53
53
|
|
|
54
|
-
Drive several turns in sequence
|
|
54
|
+
Drive several turns in sequence for branching, HITL approvals, structured output, attachments, or multiple sessions. Because assertions live in the function, an intermediate value is a local variable. Judge a draft before the next turn overwrites it, then keep going.
|
|
55
55
|
|
|
56
56
|
```ts title="evals/draft-then-send.eval.ts"
|
|
57
57
|
import { defineEval } from "eve/evals";
|
|
@@ -69,7 +69,7 @@ export default defineEval({
|
|
|
69
69
|
});
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
For a precondition no built-in assertion expresses, `throw`. A thrown error marks the eval `failed` with the message in the result:
|
|
73
73
|
|
|
74
74
|
```ts title="evals/session-continuity.eval.ts"
|
|
75
75
|
import { defineEval } from "eve/evals";
|
|
@@ -96,22 +96,22 @@ export default defineEval({
|
|
|
96
96
|
|
|
97
97
|
`t` drives the primary session; `t.newSession()` returns an independent `EveEvalSession` against the same target, whose events feed the same run-level assertions.
|
|
98
98
|
|
|
99
|
-
- `t.send(input)` sends a turn and waits for it to settle. It accepts the same input as `ClientSession.send()`
|
|
99
|
+
- `t.send(input)` sends a turn and waits for it to settle. It accepts the same input as `ClientSession.send()` (a string or a structured message) and resolves to a turn carrying `.message` and `.expectOk()`.
|
|
100
100
|
- `t.sendFile(text, path, mediaType?)` attaches a local file as a data URL.
|
|
101
101
|
- `t.expectInputRequests(filter?)` asserts the previous turn parked on HITL input and returns the pending requests.
|
|
102
102
|
- `t.respond(...responses)` answers specific pending input requests and sends them as the next turn.
|
|
103
103
|
- `t.respondAll(optionId)` answers every pending input request with the same option and sends the responses as the next turn.
|
|
104
104
|
- `t.reply` is the last assistant message (or `null`); `t.sessionId` is the current session id; `t.events` is the full typed event stream captured so far.
|
|
105
105
|
|
|
106
|
-
Each `send` (and `respond`/`respondAll`) resolves to a turn whose `expectOk()` throws only when the turn ended failed
|
|
106
|
+
Each `send` (and `respond`/`respondAll`) resolves to a turn whose `expectOk()` throws only when the turn ended failed. A session left open for a next message is the normal end state of a successful turn.
|
|
107
107
|
|
|
108
108
|
Events from every session are captured in the result and artifacts. `t.log(message)` records debug lines into the eval artifact; `--verbose` also streams them to stdout as evals run. `t.signal` is an `AbortSignal` that fires on timeout.
|
|
109
109
|
|
|
110
|
-
For driving sessions created outside the eval
|
|
110
|
+
For driving sessions created outside the eval, by a channel webhook or a schedule, see [Targets](./targets).
|
|
111
111
|
|
|
112
112
|
## Datasets: exporting an array
|
|
113
113
|
|
|
114
|
-
To fan one file out over a dataset, default-export an array of `defineEval(...)` values. Eval modules are ESM, so top-level `await` can load anything. Ids derive from the file name plus a zero-padded index (`sql/0000`, `sql/0001`,
|
|
114
|
+
To fan one file out over a dataset, default-export an array of `defineEval(...)` values. Eval modules are ESM, so top-level `await` can load anything. Ids derive from the file name plus a zero-padded index in array order (`sql/0000`, `sql/0001`, and so on). The loaders (`loadJson`, `loadYaml` from `eve/evals/loaders`) parse fixture files relative to the app root:
|
|
115
115
|
|
|
116
116
|
```ts title="evals/sql.eval.ts"
|
|
117
117
|
import { defineEval } from "eve/evals";
|
package/docs/evals/judge.mdx
CHANGED
|
@@ -3,7 +3,7 @@ title: "Judge"
|
|
|
3
3
|
description: "Grade evals with an LLM judge via t.judge.autoevals, set thresholds on the assertion, and configure the judge model."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
When no deterministic [assertion](./assertions) captures what "good" means
|
|
6
|
+
When no deterministic [assertion](./assertions) captures what "good" means (factual correctness, summary quality, free-form criteria), grade the run with an LLM judge. The `t.judge.*` assertions are the only model-backed ones, and they use a judge model that is resolved separately from the agent under test. Eve only uses it for scoring, never to swap out the agent.
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
9
|
import { defineEval } from "eve/evals";
|
|
@@ -19,7 +19,7 @@ export default defineEval({
|
|
|
19
19
|
|
|
20
20
|
## The graders
|
|
21
21
|
|
|
22
|
-
The judges live under `t.judge.autoevals
|
|
22
|
+
The judges live under `t.judge.autoevals`. The namespace names the [Braintrust autoevals](https://github.com/braintrustdata/autoevals) grader family, so the factuality and closedQA semantics are autoevals', not Eve-invented. Each grader scores `t.reply` by default and is soft by default (tracked, no gate):
|
|
23
23
|
|
|
24
24
|
| Grader | Grades |
|
|
25
25
|
| ---------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
@@ -30,8 +30,8 @@ The judges live under `t.judge.autoevals` — the namespace names the [Braintrus
|
|
|
30
30
|
|
|
31
31
|
The reference or criteria is the positional argument. An options object follows:
|
|
32
32
|
|
|
33
|
-
- `on`
|
|
34
|
-
- `model`
|
|
33
|
+
- `on` is the value to grade, defaulting to `t.reply`. Pass an intermediate draft or parsed value to grade it instead.
|
|
34
|
+
- `model` and `modelOptions` are a per-call judge override (see below).
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
const draft = await t.send("Draft the welcome email.");
|
|
@@ -40,11 +40,11 @@ t.judge.autoevals.closedQA("professional tone", { on: draft.message }).atLeast(0
|
|
|
40
40
|
|
|
41
41
|
## Soft scoring and thresholds
|
|
42
42
|
|
|
43
|
-
Judge assertions are soft, so the threshold rides on the assertion handle
|
|
43
|
+
Judge assertions are soft, so the threshold rides on the assertion handle. There is no separate thresholds map:
|
|
44
44
|
|
|
45
|
-
- **No threshold**
|
|
46
|
-
- `.atLeast(threshold)`
|
|
47
|
-
- `.gate(threshold)`
|
|
45
|
+
- **No threshold** is tracked-only. The score lands in reports and artifacts and never fails the eval. Use it to watch a metric without gating on it.
|
|
46
|
+
- `.atLeast(threshold)` is a soft bar. A below-threshold score marks the eval `scored`, fatal only under `eve eval --strict`.
|
|
47
|
+
- `.gate(threshold)` promotes a judge to a hard gate that fails the eval outright.
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
t.judge.autoevals.closedQA("cites a source"); // tracked, never fails
|
|
@@ -58,9 +58,9 @@ A judge runs once per assertion and burns tokens, so reach for one only when not
|
|
|
58
58
|
|
|
59
59
|
The judge model is resolved once when the runner builds `t`. It is **never** the model under test. Three levels resolve innermost-wins:
|
|
60
60
|
|
|
61
|
-
1. **Per-call
|
|
62
|
-
2. **Per-eval
|
|
63
|
-
3. **Project default
|
|
61
|
+
1. **Per-call**: `t.judge.autoevals.closedQA("…", { model, modelOptions })`.
|
|
62
|
+
2. **Per-eval**: `defineEval({ judge: { model, modelOptions }, test })`.
|
|
63
|
+
3. **Project default**: `defineEvalConfig({ judge: { model, modelOptions } })` in `evals.config.ts`.
|
|
64
64
|
|
|
65
65
|
```ts title="evals/evals.config.ts"
|
|
66
66
|
import { defineEvalConfig } from "eve/evals";
|
|
@@ -78,14 +78,14 @@ export default defineEval({
|
|
|
78
78
|
async test(t) {
|
|
79
79
|
await t.send("Explain quantum tunneling to a 10-year-old.");
|
|
80
80
|
t.judge.autoevals.factuality(reference).atLeast(0.7);
|
|
81
|
-
t.judge.autoevals.closedQA("is concise", { model: "anthropic/claude-haiku-4
|
|
81
|
+
t.judge.autoevals.closedQA("is concise", { model: "anthropic/claude-haiku-4.5" }); // cheaper, per-call
|
|
82
82
|
},
|
|
83
83
|
});
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
`judge` in `evals.config.ts` is optional
|
|
86
|
+
`judge` in `evals.config.ts` is optional, and a tree of fully deterministic evals can omit it. Calling `t.judge.*` with no judge model resolved records a failed gate: the runner scores the assertion after the `test` function runs, the missing model throws, and the eval fails with that message.
|
|
87
87
|
|
|
88
|
-
A **string model id** (e.g. `"anthropic/claude-opus-4.8"`) routes through the Vercel AI Gateway and needs `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` in the environment
|
|
88
|
+
A **string model id** (e.g. `"anthropic/claude-opus-4.8"`) routes through the Vercel AI Gateway and needs `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` in the environment. An **AI SDK `LanguageModel` instance** is used directly. With a model configured but no credentials, a judge-backed eval **skips visibly** rather than failing, so the run reports the skip instead of a spurious error. For provider-specific judge settings, use `modelOptions.providerOptions`.
|
|
89
89
|
|
|
90
90
|
## What to read next
|
|
91
91
|
|
package/docs/evals/overview.mdx
CHANGED
|
@@ -3,13 +3,13 @@ title: "Overview"
|
|
|
3
3
|
description: "Define repeatable scored checks for an Eve agent with defineEval and run them with eve eval."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
An eval is a scored check that runs your agent against real sessions and grades the result
|
|
6
|
+
An eval is a scored check that runs your agent against real sessions and grades the result, catching regressions when you change a prompt or a tool. Drive the agent through one or more turns, assert on what it did (the run completed, the right tool ran, the reply contains the right text), and optionally ship the results to Braintrust.
|
|
7
7
|
|
|
8
|
-
Evals exercise the same HTTP surface your users hit. The runner boots (or targets) a real agent server, drives sessions through the [TypeScript client](../guides/client/overview) protocol, and grades what comes back
|
|
8
|
+
Evals exercise the same HTTP surface your users hit. The runner boots (or targets) a real agent server, drives sessions through the [TypeScript client](../guides/client/overview) protocol, and grades what comes back, so a passing eval means the agent booted, accepted a request, and produced the result you asserted.
|
|
9
9
|
|
|
10
10
|
## `defineEval`
|
|
11
11
|
|
|
12
|
-
Eve discovers evals under the app-root `evals/` directory, in `.eval.ts` files. Each file is
|
|
12
|
+
Eve discovers evals under the app-root `evals/` directory, in `.eval.ts` files. Each file is one eval by default. A file can also default-export an array to fan out over a dataset (see [Cases](./cases)). The file path is the eval's identity, so you don't author an `id` or `name`. Directories group related evals (`evals/weather/brooklyn-forecast.eval.ts` becomes id `weather/brooklyn-forecast`).
|
|
13
13
|
|
|
14
14
|
```text
|
|
15
15
|
my-agent/
|
|
@@ -40,7 +40,7 @@ export default defineEval({
|
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`test` is the only required field. The rest are optional: `description`, `judge`, `tags`, `metadata`, `timeoutMs`, `reporters`. The init template adds `evals/**/*.ts` to `tsconfig.json`, so your eval code type-checks alongside the app.
|
|
43
|
+
`test` is the only required field. The rest are optional: `description`, `judge`, `tags`, `metadata`, `timeoutMs`, and `reporters`. The init template adds `evals/**/*.ts` to `tsconfig.json`, so your eval code type-checks alongside the app.
|
|
44
44
|
|
|
45
45
|
## `evals.config.ts`
|
|
46
46
|
|
|
@@ -56,11 +56,11 @@ export default defineEvalConfig({
|
|
|
56
56
|
});
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Everything is optional. `judge` sets the default model for [LLM-as-judge](./judge) assertions (`t.judge.*`)
|
|
59
|
+
Everything is optional. `judge` sets the default model for [LLM-as-judge](./judge) assertions (`t.judge.*`); a tree of fully deterministic evals can omit it. `reporters`, `maxConcurrency`, and `timeoutMs` round out the defaults. Config `reporters` observe every eval in the run, so set one `Braintrust()` here instead of adding it to each eval. CLI flags (`--max-concurrency`, `--timeout`) and per-eval values take precedence over the config defaults.
|
|
60
60
|
|
|
61
61
|
## The `t` context
|
|
62
62
|
|
|
63
|
-
`t` is both the driver and the assertion surface. There are no separate `input`, `run`, `checks`, or `scores` fields
|
|
63
|
+
`t` is both the driver and the assertion surface. There are no separate `input`, `run`, `checks`, or `scores` fields. You write ordinary control flow, sending turns and asserting inline.
|
|
64
64
|
|
|
65
65
|
- **Drive** the agent: `t.send(...)`, `t.respond(...)`, `t.respondAll(...)`, `t.sendFile(...)`, `t.expectInputRequests(...)`, `t.newSession()`. Read what came back with `t.reply` (the last assistant message), `t.sessionId`, and `t.events`. See [Cases](./cases).
|
|
66
66
|
- **Assert** with three surfaces, covered next.
|
|
@@ -69,16 +69,16 @@ Everything is optional. `judge` sets the default model for [LLM-as-judge](./judg
|
|
|
69
69
|
|
|
70
70
|
Each surface matches a genuinely different kind of judgment:
|
|
71
71
|
|
|
72
|
-
- **Run-level methods** read the whole run
|
|
73
|
-
- **`t.check(value, assertion)`** grades an explicit value with a deterministic builder from `eve/evals/expect
|
|
74
|
-
- **`t.judge.autoevals.*`** is the LLM-as-judge surface
|
|
72
|
+
- **Run-level methods** read the whole run, like `t.completed()`, `t.calledTool("get_weather")`, `t.usedNoTools()`, and `t.toolOrder([...])`. They take no value because they observe the run itself. See [Assertions](./assertions).
|
|
73
|
+
- **`t.check(value, assertion)`** grades an explicit value with a deterministic builder from `eve/evals/expect`, such as `t.check(t.reply, includes("sunny"))`. Grade `t.reply`, an intermediate draft, parsed JSON, or anything else. See [Assertions](./assertions).
|
|
74
|
+
- **`t.judge.autoevals.*`** is the LLM-as-judge surface, like `t.judge.autoevals.closedQA("cites a source")`. It grades `t.reply` by default and uses the configured judge model, never the agent under test. See [Judge](./judge).
|
|
75
75
|
|
|
76
76
|
## Gate vs soft
|
|
77
77
|
|
|
78
|
-
Every assertion returns a chainable handle, so severity rides on the assertion itself
|
|
78
|
+
Every assertion returns a chainable handle, so severity rides on the assertion itself. There is no separate thresholds map.
|
|
79
79
|
|
|
80
80
|
- **Gates** are hard. A failed gate marks the eval `failed` and `eve eval` exits non-zero. Run-level methods, `includes`, `equals`, and `matches` are gates by default.
|
|
81
|
-
- **Soft** assertions are tracked data. They land in reports and artifacts, and a below-threshold soft assertion marks the eval `scored`
|
|
81
|
+
- **Soft** assertions are tracked data. They land in reports and artifacts, and a below-threshold soft assertion marks the eval `scored` (visible but not fatal, unless you pass `--strict`). `similarity` and every `t.judge.*` assertion are soft by default. A soft assertion with no threshold is tracked-only and never fails.
|
|
82
82
|
|
|
83
83
|
Override per assertion: `.gate(threshold?)` promotes to a hard gate, `.soft(threshold?)` demotes to tracked, and `.atLeast(threshold)` is a soft assertion with a bar.
|
|
84
84
|
|
|
@@ -89,7 +89,7 @@ t.judge.autoevals.closedQA("cites a source"); // soft, tracked (no threshold)
|
|
|
89
89
|
t.judge.autoevals.factuality(reference).atLeast(0.7); // soft, gated under --strict at 0.7
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
## Run
|
|
92
|
+
## Run evals with eve eval
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
eve eval # run all discovered evals against a local dev server
|
|
@@ -101,7 +101,9 @@ Exit code `0` means every eval passed its gates. See [Running evals](./running)
|
|
|
101
101
|
|
|
102
102
|
## A good baseline
|
|
103
103
|
|
|
104
|
-
Most apps do fine with a few small smoke evals. Assert behavior with `t.completed()` plus one or two content checks, keep dataset fixtures in `evals/data/`, and
|
|
104
|
+
Most apps do fine with a few small smoke evals. Assert behavior with `t.completed()` plus one or two content checks, keep dataset fixtures in `evals/data/`, and reach for a judge or Braintrust only when you need fuzzy grading or shared result review. In CI, run `eve eval --strict` so soft threshold misses fail the build too.
|
|
105
|
+
|
|
106
|
+
## What to read next
|
|
105
107
|
|
|
106
108
|
The rest of this section covers each piece:
|
|
107
109
|
|
|
@@ -111,8 +113,4 @@ The rest of this section covers each piece:
|
|
|
111
113
|
- [Targets](./targets): local vs remote targets for the same eval files
|
|
112
114
|
- [Reporters](./reporters): Braintrust experiments and JUnit XML
|
|
113
115
|
- [Running evals](./running): the `eve eval` CLI, exit codes, and artifacts
|
|
114
|
-
|
|
115
|
-
## What to read next
|
|
116
|
-
|
|
117
|
-
- [Cases](./cases): author your first evals
|
|
118
116
|
- [Tools](../tools): the surface most evals assert on
|
package/docs/evals/reporters.mdx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Reporters"
|
|
3
|
-
description: "Ship eval results to Braintrust experiments or JUnit XML
|
|
3
|
+
description: "Ship eval results to Braintrust experiments or JUnit XML. Eve runs and scores everything itself."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Eve runs and grades everything itself; reporters
|
|
6
|
+
Eve runs and grades everything itself; reporters ship the results out. The CLI prints a console summary by default (one line per eval, with failed assertions and their messages), and reporters from `eve/evals/reporters` add destinations on top.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
You are responsible for ensuring any observability or eval provider is approved for the data exported to it.
|
|
9
|
+
|
|
10
|
+
Reporters attach in two places. Declare them in `evals.config.ts` to observe **every** eval in the run, the usual choice for a shared destination like one Braintrust experiment, so you don't repeat the reporter in each file. Or list them on an individual eval's `reporters` to scope a destination to that eval (or to a group of evals that share one instance).
|
|
9
11
|
|
|
10
12
|
## Braintrust
|
|
11
13
|
|
|
@@ -38,13 +40,13 @@ export default defineEval({
|
|
|
38
40
|
|
|
39
41
|
The reporter config takes an optional `projectName` and `experimentName`, plus a base experiment (by name or id) to diff against. Gate assertions log as binary scores under a `gate:` prefix so experiments diff gate regressions the same way they diff soft-score regressions. Eval `metadata` rides along to reporters.
|
|
40
42
|
|
|
41
|
-
A reporter instance observes the evals that reference it
|
|
43
|
+
A reporter instance observes the evals that reference it. Share one instance across several evals (the config, a `shared.ts` export, or every entry of a dataset array) and their results land in a single experiment. Listing the same config reporter on an eval too does not double-report it.
|
|
42
44
|
|
|
43
|
-
Braintrust needs its SDK installed in the app and credentials in the environment
|
|
45
|
+
Braintrust needs its SDK installed in the app and credentials in the environment: install the `braintrust` package (`npm install braintrust`) and set `BRAINTRUST_API_KEY`. Pass `--skip-report` to run the eval without shipping results, which also suppresses config reporters and is useful locally when iterating.
|
|
44
46
|
|
|
45
47
|
## JUnit
|
|
46
48
|
|
|
47
|
-
`JUnit({ filePath })` writes JUnit XML for CI annotations. The `--junit <path>` CLI flag does the same thing without touching the eval file,
|
|
49
|
+
`JUnit({ filePath })` writes JUnit XML for CI annotations. The `--junit <path>` CLI flag does the same thing without touching the eval file, usually the better fit because CI owns the output path, not the eval:
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
52
|
eve eval --strict --junit .eve/junit.xml
|
|
@@ -54,7 +56,17 @@ Each eval becomes one `<testcase>` named by its path-derived id; failed gates an
|
|
|
54
56
|
|
|
55
57
|
## Custom reporters
|
|
56
58
|
|
|
57
|
-
A reporter implements the `EvalReporter` interface from `eve/evals/reporters` and receives the same structured results the built-ins do.
|
|
59
|
+
A reporter implements the `EvalReporter` interface from `eve/evals/reporters` and receives the same structured results the built-ins do. The runner calls three lifecycle methods, each of which may return a promise for async work like a remote upload:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
interface EvalReporter {
|
|
63
|
+
onRunStart(evaluations: readonly EveEval[], target: EveEvalTarget): void | Promise<void>;
|
|
64
|
+
onEvalComplete(result: EveEvalResult): void | Promise<void>;
|
|
65
|
+
onRunComplete(summary: EveEvalRunSummary): void | Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`onRunStart` fires once before any eval runs, `onEvalComplete` fires after each observed eval with its checks, scores, and verdict, and `onRunComplete` fires once with the aggregated summary. Reach for a custom reporter only when a destination isn't covered. The per-run artifacts under `.eve/evals/` already capture everything for ad-hoc inspection.
|
|
58
70
|
|
|
59
71
|
## What to read next
|
|
60
72
|
|
package/docs/evals/running.mdx
CHANGED
|
@@ -15,7 +15,7 @@ eve eval --timeout 60000 # per-eval timeout in milliseconds
|
|
|
15
15
|
eve eval --max-concurrency 4 # cap concurrent eval executions (default 8)
|
|
16
16
|
eve eval --junit .eve/junit.xml # write JUnit XML
|
|
17
17
|
eve eval --list # print discovered evals without running
|
|
18
|
-
eve eval --verbose # stream per-eval
|
|
18
|
+
eve eval --verbose # stream per-eval t.log lines to stdout
|
|
19
19
|
eve eval --json # machine-readable output
|
|
20
20
|
eve eval --skip-report # skip config and eval-defined reporters (e.g. Braintrust)
|
|
21
21
|
```
|
|
@@ -27,7 +27,7 @@ Positional ids match exactly or by directory prefix: `eve eval weather` runs `ev
|
|
|
27
27
|
| Code | Means |
|
|
28
28
|
| ---- | ------------------------------------------------------------------------------- |
|
|
29
29
|
| `0` | Every eval passed its gates (and soft thresholds, under `--strict`) |
|
|
30
|
-
| `1` | Any eval failed
|
|
30
|
+
| `1` | Any eval failed (a failed gate, an execution error, or a strict threshold miss) |
|
|
31
31
|
| `2` | Configuration error |
|
|
32
32
|
|
|
33
33
|
## Artifacts
|
package/docs/evals/targets.mdx
CHANGED
|
@@ -3,7 +3,7 @@ title: "Targets"
|
|
|
3
3
|
description: "Point evals at a local dev server or a deployment with the same eval files."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
An eval target is always an HTTP URL. `eve eval` starts a local dev server, while `eve eval --url <url>` runs against an existing server or deployment
|
|
6
|
+
An eval target is always an HTTP URL. `eve eval` starts a local dev server, while `eve eval --url <url>` runs against an existing server or deployment. The same eval files work for both, which is what makes evals usable as end-to-end tests in CI.
|
|
7
7
|
|
|
8
8
|
The runner polls `/eve/v1/health`, verifies `/eve/v1/info`, and exposes the live target as `t.target` inside the `test` function.
|
|
9
9
|
|
|
@@ -22,11 +22,22 @@ export default defineEval({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
- `t.target.fetch(path, init)` performs an authenticated fetch against the target
|
|
25
|
+
- `t.target.fetch(path, init)` performs an authenticated fetch against the target, useful for channel and webhook ingress. See [Authentication](#authentication) for how the runner authenticates.
|
|
26
26
|
- `t.target.dispatchSchedule(id)` triggers a [schedule](../schedules) through the dev-only schedule route and returns the session ids it created. It works only against a target with dev routes enabled (the local `eve eval` dev server, or a deployment running in development mode), and throws otherwise.
|
|
27
|
-
- `t.target.attachSession(sessionId, { startIndex? })` consumes one turn from a session created outside the eval
|
|
27
|
+
- `t.target.attachSession(sessionId, { startIndex? })` consumes one turn from a session created outside the eval, by a channel or a schedule, so its events feed the run-level assertions. `startIndex` skips events before that position, so a session already partway through its stream resumes from where you left off rather than replaying from the start.
|
|
28
28
|
|
|
29
|
-
Sessions attached this way are full `EveEvalSession`s: you can keep driving them with `send` and read their event streams. The run-level assertions on `t` (`t.completed()`, `t.calledTool(...)`) read the whole run, attached sessions
|
|
29
|
+
Sessions attached this way are full `EveEvalSession`s: you can keep driving them with `send` and read their event streams. The run-level assertions on `t` (`t.completed()`, `t.calledTool(...)`) read the whole run, including attached sessions.
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Local targets send no auth: `eve eval` owns the dev server it boots. A remote `--url` target connects with the same credentials as every other development client, resolved in this order:
|
|
34
|
+
|
|
35
|
+
- A Vercel OIDC trusted-IDP token, sent as a per-request header. It bypasses Deployment Protection without a per-project secret, so a CI job with a pulled OIDC token reaches a protected preview deployment without extra setup.
|
|
36
|
+
- An `x-vercel-protection-bypass` header, added when `VERCEL_AUTOMATION_BYPASS_SECRET` is set.
|
|
37
|
+
- A bearer token resolved from the same OIDC cascade.
|
|
38
|
+
- `EVE_EVAL_AUTH_TOKEN`, which overrides the bearer with a static token for targets whose auth is not OIDC-based.
|
|
39
|
+
|
|
40
|
+
`t.target.fetch(path, init)` carries these same credentials, so channel and webhook ingress you exercise through it authenticates the same way the session protocol does.
|
|
30
41
|
|
|
31
42
|
## What to read next
|
|
32
43
|
|