eve 0.10.0 → 0.11.1
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 +17 -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/execution/sandbox/bindings/docker.js +1 -1
- package/dist/src/execution/sandbox/bindings/microsandbox-lifecycle.js +1 -1
- package/dist/src/execution/sandbox/bindings/microsandbox-runtime.d.ts +2 -1
- package/dist/src/execution/sandbox/bindings/microsandbox-runtime.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel-errors.d.ts +1 -0
- package/dist/src/execution/sandbox/bindings/vercel-errors.js +1 -2
- package/dist/src/execution/sandbox/bindings/vercel.d.ts +0 -5
- package/dist/src/execution/sandbox/bindings/vercel.js +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 +72 -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 → introduction.mdx} +14 -12
- package/docs/reference/cli.md +99 -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/responsible-use.md +14 -0
- 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
package/docs/connections.mdx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Connections"
|
|
3
|
-
description: "Expose external MCP and OpenAPI servers to the model
|
|
3
|
+
description: "Expose external MCP and OpenAPI servers to the model, with connection tokens the model never sees."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
A connection wires an agent into an external server you don't author
|
|
6
|
+
A connection wires an agent into an external server you don't author, either an MCP server (Linear, GitHub, a warehouse) or any HTTP API with an OpenAPI document. Eve handles the parts you'd otherwise hand-roll, discovering the remote tools, surfacing them to the model, and brokering auth.
|
|
7
7
|
|
|
8
8
|
Connections live under `agent/connections/`. The runtime name comes from the filename, so `agent/connections/linear.ts` registers as `"linear"`. The model never sees a connection's URL or credentials. It discovers tools through the built-in `connection__search` and calls them by their qualified name, `connection__<connection>__<tool>` (e.g. `connection__linear__list_issues`).
|
|
9
9
|
|
|
10
10
|
## MCP connections
|
|
11
11
|
|
|
12
|
-
`defineMcpClientConnection` points at an MCP server.
|
|
12
|
+
`defineMcpClientConnection` points at an MCP server. Supply a `url` and a `description`:
|
|
13
13
|
|
|
14
14
|
```ts title="agent/connections/linear.ts"
|
|
15
15
|
import { defineMcpClientConnection } from "eve/connections";
|
|
@@ -23,11 +23,11 @@ export default defineMcpClientConnection({
|
|
|
23
23
|
});
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model
|
|
26
|
+
The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection__search`, and the model uses it to decide which connection to query.
|
|
27
27
|
|
|
28
28
|
### Static-token auth
|
|
29
29
|
|
|
30
|
-
`getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets
|
|
30
|
+
`getToken` returns a `TokenResult` (`{ token, expiresAt? }`), and Eve sends it as `Authorization: Bearer <token>` on every request. Because it runs on each connection attempt, you can mint a fresh token from wherever you keep secrets, including an env var, a secrets manager, an internal vault, or your own OAuth exchange. If the token has a known TTL, set `expiresAt` (milliseconds since epoch) and Eve refreshes ahead of time rather than waiting for a `401`.
|
|
31
31
|
|
|
32
32
|
When `getToken` is the only auth, `principalType` defaults to `"app"`: one shared credential keyed across all sessions. Switch to `principalType: "user"` when each end-user carries their own token.
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ Eve resolves and caches connection tokens per step; they never land in conversat
|
|
|
35
35
|
|
|
36
36
|
### No auth
|
|
37
37
|
|
|
38
|
-
Drop `auth` entirely for servers that need no token,
|
|
38
|
+
Drop `auth` entirely for servers that need no token, such as a localhost server during development or a public one:
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
export default defineMcpClientConnection({
|
|
@@ -44,9 +44,11 @@ export default defineMcpClientConnection({
|
|
|
44
44
|
});
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
We recommend using no-auth connections only for services that are intentionally public, local-only, or otherwise protected outside Eve. Do not use no-auth connections for sensitive third-party services.
|
|
48
|
+
|
|
47
49
|
### Headers
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
Use `headers` when the server wants a non-Bearer scheme (an API-key header) or extra configuration. Headers stack on top of `auth`:
|
|
50
52
|
|
|
51
53
|
```ts
|
|
52
54
|
export default defineMcpClientConnection({
|
|
@@ -58,7 +60,7 @@ export default defineMcpClientConnection({
|
|
|
58
60
|
|
|
59
61
|
### Tool filters
|
|
60
62
|
|
|
61
|
-
To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools
|
|
63
|
+
To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools do not appear in `connection__search`:
|
|
62
64
|
|
|
63
65
|
```ts
|
|
64
66
|
export default defineMcpClientConnection({
|
|
@@ -84,11 +86,13 @@ export default defineMcpClientConnection({
|
|
|
84
86
|
});
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
`never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume
|
|
89
|
+
`never()` lets every call through, `once()` asks for approval the first time in a session, and `always()` asks every time. The pause and resume is the same human-in-the-loop flow covered in [Tools](./tools).
|
|
90
|
+
|
|
91
|
+
For connection tools that can create, modify, delete, transmit, purchase, message, or access sensitive data, use approval, tool allow-lists, or other safeguards appropriate to the action.
|
|
88
92
|
|
|
89
93
|
## OpenAPI connections
|
|
90
94
|
|
|
91
|
-
`defineOpenAPIConnection` turns any OpenAPI 3.x
|
|
95
|
+
`defineOpenAPIConnection` turns any OpenAPI 3.x document into connection tools, one per operation. Pass an HTTPS URL Eve fetches at runtime, or an inline parsed object:
|
|
92
96
|
|
|
93
97
|
```ts title="agent/connections/petstore.ts"
|
|
94
98
|
import { defineOpenAPIConnection } from "eve/connections";
|
|
@@ -104,10 +108,10 @@ Each operation becomes `connection__<connection>__<operationId>` (e.g. `connecti
|
|
|
104
108
|
|
|
105
109
|
`auth`, `headers`, and `approval` work exactly as they do for MCP. There are two fields specific to OpenAPI:
|
|
106
110
|
|
|
107
|
-
| Field | Purpose
|
|
108
|
-
| ------------ |
|
|
109
|
-
| `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry
|
|
110
|
-
| `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools.
|
|
111
|
+
| Field | Purpose |
|
|
112
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `baseUrl` | Base URL operation paths resolve against. Optional; defaults to the document's first usable `servers` entry. |
|
|
114
|
+
| `operations` | Filter keyed on `operationId` (`allow` or `block`). Mirrors `tools` on MCP connections, but names operations not tools. |
|
|
111
115
|
|
|
112
116
|
## Interactive OAuth via Vercel Connect
|
|
113
117
|
|
|
@@ -128,7 +132,7 @@ export default defineMcpClientConnection({
|
|
|
128
132
|
|
|
129
133
|
## Self-hosted interactive OAuth
|
|
130
134
|
|
|
131
|
-
|
|
135
|
+
To run your own OAuth, use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form and needs no Vercel Connect. Eve mints a callback URL, parks (durably suspends) the turn on a framework-owned webhook, and resumes once the token comes back. Interactive auth is always `principalType: "user"`, and the factory pins that for you.
|
|
132
136
|
|
|
133
137
|
```ts title="agent/connections/linear.ts"
|
|
134
138
|
import {
|
|
@@ -168,9 +172,18 @@ export default defineMcpClientConnection({
|
|
|
168
172
|
});
|
|
169
173
|
```
|
|
170
174
|
|
|
171
|
-
`getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event.
|
|
175
|
+
`getToken` runs before every tool call. `startAuthorization` and `completeAuthorization` are both-or-neither: provide one without the other and you get a definition error. The `challenge` rides along verbatim on the `authorization.required` event. Its fields:
|
|
176
|
+
|
|
177
|
+
| Field | Purpose |
|
|
178
|
+
| -------------- | ----------------------------------------------------------------------------------------- |
|
|
179
|
+
| `url` | The authorize URL for redirect or device flows. |
|
|
180
|
+
| `userCode` | The device code, for device flows. |
|
|
181
|
+
| `instructions` | The call to action when there's no URL. |
|
|
182
|
+
| `displayName` | Human-readable provider name channels show on the sign-in affordance (e.g. "Salesforce"). |
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
Drop `resume` when the provider keeps flow state server-side, so nothing has to cross the step boundary.
|
|
185
|
+
|
|
186
|
+
`displayName` is presentation-only. The connection's path-derived name still keys the authorization scope, token cache, and callback URL. You can also set `displayName` on the `auth` definition itself (e.g. `auth: { ...connect("sfdc"), displayName: "Salesforce" }`); that definition-level value wins over one the strategy stamps on the challenge, and channels fall back to title-casing the connection name when neither is set.
|
|
174
187
|
|
|
175
188
|
### Signaling authorization state
|
|
176
189
|
|
|
@@ -192,24 +205,31 @@ To narrow a caught error, use `isConnectionAuthorizationRequiredError(err)` and
|
|
|
192
205
|
|
|
193
206
|
### Handling a revoked token mid-call
|
|
194
207
|
|
|
195
|
-
`getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is
|
|
208
|
+
`getToken` only runs _before_ a tool call, so a grant revoked while a tool is mid-flight first surfaces as a downstream `401` inside your `execute`. A plain throw there is only a tool error, so the model sees a failure and the cached bearer sticks around. Instead, map a provider `401` to `ctx.requireAuth()` (or rethrow `ConnectionAuthorizationRequiredError`). Eve then evicts the rejected token from its per-step cache and re-runs the consent flow with a fresh one, exactly as it does for a connection whose server rejects the bearer.
|
|
196
209
|
|
|
197
210
|
```ts title="agent/tools/list_issues.ts"
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
211
|
+
import { defineTool } from "eve/tools";
|
|
212
|
+
import { z } from "zod";
|
|
213
|
+
|
|
214
|
+
export default defineTool({
|
|
215
|
+
description: "List open Linear issues.",
|
|
216
|
+
inputSchema: z.object({}),
|
|
217
|
+
async execute(_input, ctx) {
|
|
218
|
+
const { token } = await ctx.getToken();
|
|
219
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
220
|
+
headers: { authorization: `Bearer ${token}` },
|
|
221
|
+
});
|
|
222
|
+
// The grant was revoked since getToken ran: re-challenge instead of
|
|
223
|
+
// returning a dead-token error to the model.
|
|
224
|
+
if (res.status === 401) ctx.requireAuth();
|
|
225
|
+
return await res.json();
|
|
226
|
+
},
|
|
227
|
+
});
|
|
208
228
|
```
|
|
209
229
|
|
|
210
230
|
### Authorization and approval together
|
|
211
231
|
|
|
212
|
-
A tool can require both sign-in (`auth`) and a human approval.
|
|
232
|
+
A tool can require both sign-in (`auth`) and a human approval. The model's approval gate runs before the tool's `execute`, so the order the user sees is **approve, then sign in**. Eve records the approval on session state the moment it's granted, and that record survives the sign-in park, so when the turn resumes after authorization the tool is not put through approval again. You get one approval and one sign-in, never a double prompt.
|
|
213
233
|
|
|
214
234
|
## What to read next
|
|
215
235
|
|
|
@@ -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
|