niceeval 0.4.6 → 0.5.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/docs-site/zh/concepts/adapter.mdx +6 -6
- package/docs-site/zh/concepts/experiment.mdx +7 -7
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +63 -20
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +243 -0
- package/docs-site/zh/guides/experiments.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +252 -0
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +224 -0
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/viewing-results.mdx +147 -77
- package/docs-site/zh/guides/write-experiment.mdx +9 -9
- package/docs-site/zh/guides/write-send.mdx +6 -6
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/introduction.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +10 -2
- package/docs-site/zh/reference/define-agent.mdx +5 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/package.json +3 -2
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/ai-sdk.ts +2 -2
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +4 -4
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +104 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +3 -3
- package/src/context/session.ts +2 -2
- package/src/context/types.ts +2 -2
- package/src/i18n/en.ts +29 -7
- package/src/i18n/zh-CN.ts +24 -2
- package/src/report/aggregate.ts +79 -50
- package/src/report/components.tsx +190 -0
- package/src/report/compute.ts +149 -84
- package/src/report/default-report.tsx +222 -0
- package/src/report/dual-face.test.tsx +527 -0
- package/src/report/format.ts +29 -0
- package/src/report/index.ts +79 -24
- package/src/report/load.ts +67 -0
- package/src/report/metrics.ts +2 -1
- package/src/report/param.ts +18 -0
- package/src/report/primitives.tsx +117 -0
- package/src/report/react/CaseList.tsx +2 -2
- package/src/report/react/DeltaTable.tsx +2 -2
- package/src/report/react/MetricBars.tsx +109 -0
- package/src/report/react/MetricLine.tsx +200 -0
- package/src/report/react/MetricMatrix.tsx +1 -1
- package/src/report/react/MetricScatter.tsx +1 -1
- package/src/report/react/MetricTable.tsx +1 -1
- package/src/report/react/RunOverview.tsx +4 -4
- package/src/report/react/Scoreboard.tsx +3 -3
- package/src/report/react/cell.tsx +1 -1
- package/src/report/react/fixtures.ts +98 -36
- package/src/report/react/format.ts +3 -28
- package/src/report/react/index.tsx +41 -24
- package/src/report/react/render.test.tsx +8 -8
- package/src/report/react/styles.css +77 -0
- package/src/report/report.test.ts +248 -128
- package/src/report/report.ts +69 -0
- package/src/report/text/faces.ts +356 -0
- package/src/report/text/layout.ts +125 -0
- package/src/report/text/plot.ts +127 -0
- package/src/report/tree.ts +220 -0
- package/src/report/types.ts +75 -27
- package/src/report/web.ts +40 -0
- package/src/results/copy.ts +95 -41
- package/src/results/format.ts +12 -11
- package/src/results/index.ts +34 -17
- package/src/results/open.ts +201 -81
- package/src/results/results.test.ts +595 -191
- package/src/results/select.ts +107 -55
- package/src/results/types.ts +137 -45
- package/src/results/writer.ts +258 -0
- package/src/runner/attempt.ts +3 -3
- package/src/runner/fingerprint.ts +1 -1
- package/src/runner/reporters/artifacts.ts +38 -78
- package/src/runner/reporters/braintrust.test.ts +2 -2
- package/src/runner/reporters/braintrust.ts +3 -3
- package/src/runner/run.ts +1 -1
- package/src/runner/types.ts +18 -12
- package/src/show/compose.ts +215 -0
- package/src/show/index.ts +263 -0
- package/src/show/render.ts +456 -0
- package/src/show/show.test.ts +505 -0
- package/src/view/app/App.tsx +85 -24
- package/src/view/app/components/CostScoreChart.tsx +29 -10
- package/src/view/app/components/ExperimentTable.tsx +27 -9
- package/src/view/app/components/GroupSelector.tsx +1 -1
- package/src/view/app/i18n.ts +18 -3
- package/src/view/app/lib/attempt-route.test.ts +9 -9
- package/src/view/app/lib/attempt-route.ts +5 -5
- package/src/view/app/lib/outcome.ts +1 -3
- package/src/view/app/lib/rows.ts +95 -15
- package/src/view/app/main.tsx +18 -6
- package/src/view/app/pages/RunsPage.tsx +4 -7
- package/src/view/app/pages/TracesPage.tsx +3 -8
- package/src/view/app/types.ts +50 -6
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.test.ts +254 -0
- package/src/view/data.ts +336 -0
- package/src/view/index.ts +64 -10
- package/src/view/server.ts +31 -9
- package/src/view/shared/types.ts +47 -41
- package/src/view/styles.css +5 -0
- package/src/view/template.html +1 -0
- package/src/view/view-report.test.ts +308 -0
- package/docs-site/.mintignore +0 -13
- package/docs-site/AGENTS.md +0 -46
- package/docs-site/concepts/adapter.mdx +0 -287
- package/docs-site/concepts/assert.mdx +0 -243
- package/docs-site/concepts/drive.mdx +0 -118
- package/docs-site/concepts/evals.mdx +0 -108
- package/docs-site/concepts/experiment.mdx +0 -37
- package/docs-site/concepts/hitl.mdx +0 -83
- package/docs-site/concepts/judge.mdx +0 -129
- package/docs-site/concepts/overview.mdx +0 -98
- package/docs-site/concepts/tier.mdx +0 -42
- package/docs-site/docs-ref/00-index.md +0 -23
- package/docs-site/docs-ref/01-page-types.md +0 -43
- package/docs-site/docs-ref/03-style-rules.md +0 -45
- package/docs-site/docs-ref/04-code-examples.md +0 -37
- package/docs-site/docs-ref/05-dx-failure-paths.md +0 -45
- package/docs-site/docs-ref/06-checklists.md +0 -53
- package/docs-site/docs.json +0 -256
- package/docs-site/example/ai-agent-application.mdx +0 -148
- package/docs-site/example/claude-code-codex-plugin.mdx +0 -162
- package/docs-site/example/claude-code-codex-skill.mdx +0 -147
- package/docs-site/example/showcase.mdx +0 -39
- package/docs-site/example/tier1-ai-sdk-v7.mdx +0 -136
- package/docs-site/example/tier1-claude-sdk.mdx +0 -119
- package/docs-site/example/tier1-codex-sdk.mdx +0 -111
- package/docs-site/example/tier1-langgraph.mdx +0 -119
- package/docs-site/example/tier1-pi-sdk.mdx +0 -119
- package/docs-site/favicon.svg +0 -5
- package/docs-site/github-diff.css +0 -135
- package/docs-site/github-diff.js +0 -11
- package/docs-site/guides/authoring.mdx +0 -129
- package/docs-site/guides/ci-integration.mdx +0 -97
- package/docs-site/guides/connect-otel.mdx +0 -209
- package/docs-site/guides/connect-your-agent.mdx +0 -221
- package/docs-site/guides/dataset-fanout.mdx +0 -98
- package/docs-site/guides/experiments.mdx +0 -71
- package/docs-site/guides/fixtures.mdx +0 -147
- package/docs-site/guides/reporters.mdx +0 -113
- package/docs-site/guides/runner.mdx +0 -79
- package/docs-site/guides/sandbox-agent.mdx +0 -138
- package/docs-site/guides/sandbox-backends.mdx +0 -64
- package/docs-site/guides/scoring-guide.mdx +0 -92
- package/docs-site/guides/viewing-results.mdx +0 -195
- package/docs-site/guides/write-experiment.mdx +0 -81
- package/docs-site/guides/write-send.mdx +0 -347
- package/docs-site/index.mdx +0 -181
- package/docs-site/introduction.mdx +0 -141
- package/docs-site/quickstart.mdx +0 -136
- package/docs-site/reference/builtin-agents.mdx +0 -161
- package/docs-site/reference/capabilities.mdx +0 -76
- package/docs-site/reference/cli.mdx +0 -120
- package/docs-site/reference/define-agent.mdx +0 -168
- package/docs-site/reference/define-config.mdx +0 -41
- package/docs-site/reference/define-eval.mdx +0 -160
- package/docs-site/reference/events.mdx +0 -131
- package/docs-site/reference/expect.mdx +0 -112
- package/docs-site/tracker.js +0 -8
- package/src/report/react/data.ts +0 -17
- package/src/view/aggregate.ts +0 -103
- package/src/view/loader.test.ts +0 -61
- package/src/view/loader.ts +0 -257
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "How to write send"
|
|
3
|
-
sidebarTitle: "Write Send"
|
|
4
|
-
description: "Hand-write the Adapter's send function along one storyline in seven steps: send a message and get the reply, continue from earlier messages, record usage, parse tools into events, human-in-the-loop (HITL), hook up OTel traces, and pass through experiment flags. Each step adds only a few lines to the previous step's code, and each step unlocks a family of assertions."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
[Adapter](/concepts/adapter) covers the contract: `send` receives `TurnInput` and `AgentContext` and returns a `Turn`. This page covers how to write it — starting from the minimal "send one message, get the reply" form and building step by step to a complete integration. Each step adds only a few lines to the previous step's code; parts already shown are marked with `// …omitted` in the code, and each step ends by noting which assertions those lines unlock. Every step is immediately verifiable: write the newly unlocked assertions into an eval, rerun `npx niceeval exp`, and `niceeval view` shows the data this step added — multi-turn trajectories, usage, tool events, pending requests.
|
|
8
|
-
|
|
9
|
-
Two principles run through the whole page:
|
|
10
|
-
|
|
11
|
-
- **Always connect at the interface your user-facing frontend already uses.** Whatever endpoint the page calls and whatever format it receives, the Adapter calls that endpoint and receives that format — do not open a new endpoint for evals, and do not import app internals to call functions directly (for why, see [Connect your agent](/guides/connect-your-agent)).
|
|
12
|
-
- **The only thing you truly hand-write is transport** — URL, auth, and request body differ per app anyway. Parsing (raw response → standard event stream) has official converters, exported from `niceeval/adapter`; the state slots for orchestration (session continuation, HITL pause/resume) hang off `ctx.session`, ready to use, with nothing extra to declare.
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
## Starting point: what shape is your app's interface?
|
|
17
|
-
|
|
18
|
-
NiceEval does not invent protocols. Your app is almost certainly built on one of the industry protocols below (or a variant of one), and the official pieces cover exactly these shapes:
|
|
19
|
-
|
|
20
|
-
| Protocol | Who defines it | In one sentence | Reference |
|
|
21
|
-
|---|---|---|---|
|
|
22
|
-
| **Chat Completions** | OpenAI; the industry's de facto standard — nearly every model provider offers a compatible endpoint | Stateless question-and-answer: the client sends the full `messages` list every turn and one JSON comes back | [API reference](https://platform.openai.com/docs/api-reference/chat) |
|
|
23
|
-
| **Responses / Open Responses** | OpenAI (the Responses API); Open Responses is an open specification built on it, initiated by OpenAI and co-developed with Hugging Face and the community for multi-provider interoperability | Stateful and agent-oriented: requests carry `previous_response_id` so the server continues the history, and the `output` array promises a record of the full process | [API reference](https://platform.openai.com/docs/api-reference/responses) · [Open Responses spec](https://www.openresponses.org/specification) |
|
|
24
|
-
| **AI SDK (Vercel)** | Vercel; the de facto standard of the TypeScript ecosystem | Not a wire protocol but an SDK: `generateText` returns a complete result, and `useChat` speaks the UI Message Stream protocol it defines | [generateText reference](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text) · [UI Message Stream protocol](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol) |
|
|
25
|
-
|
|
26
|
-
The storyline below writes an app with a Chat Completions-shaped interface (the most common shape) from zero to complete; if your interface is Responses-shaped or streaming, the replacement pieces are given in steps two and four respectively.
|
|
27
|
-
|
|
28
|
-
## Step 1: send one message, get the reply
|
|
29
|
-
|
|
30
|
-
The minimal `send` does exactly three things: send `input.text` to the app's interface, put the reply into one `message` event, and report this turn's `status`:
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
// agents/chat-app.ts
|
|
34
|
-
import { defineAgent } from "niceeval/adapter";
|
|
35
|
-
|
|
36
|
-
const BASE_URL = "http://localhost:8080"; // to switch the URL per experiment, make it a factory option: see the integration guide
|
|
37
|
-
|
|
38
|
-
export default defineAgent({
|
|
39
|
-
name: "chat-app",
|
|
40
|
-
async send(input, ctx) {
|
|
41
|
-
const res = await fetch(`${BASE_URL}/v1/chat/completions`, { // ← the interface the frontend already uses
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { "content-type": "application/json" },
|
|
44
|
-
body: JSON.stringify({ messages: [{ role: "user", content: input.text }] }),
|
|
45
|
-
signal: ctx.signal, // ← runner timeout and cancellation
|
|
46
|
-
}).then((r) => r.json());
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
status: "completed",
|
|
50
|
-
events: [{ type: "message", role: "assistant", text: res.choices[0].message.content }],
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**This step unlocks**: `t.reply`, `t.messageIncludes()`, and all the conversation material for judges.
|
|
57
|
-
|
|
58
|
-
It has two obvious limitations: every turn is a brand-new conversation (the second `t.send` cannot continue from the first), and tool calls are completely invisible. The next two steps solve one each.
|
|
59
|
-
|
|
60
|
-
## Step 2: continue from earlier messages
|
|
61
|
-
|
|
62
|
-
The runner promises exactly one thing about sessions: **every `send` on the same session line gets the same `ctx.session`; a new session line (the eval's first turn, or after `t.newSession()`) gets a brand-new one.**
|
|
63
|
-
|
|
64
|
-
How "earlier messages" get continued depends on the shape of the app's interface. The industry has exactly two patterns, and `ctx.session` provides a pair of accessors for each:
|
|
65
|
-
|
|
66
|
-
- **Client carries the full history** (stateless server; the complete message list is sent every turn: the Chat Completions shape is the typical case) → `ctx.session.history<TMsg>()`
|
|
67
|
-
- **Server keeps the history** (the interface takes a session id: the Responses shape's `previous_response_id`, the native sessions / threads of various SDKs) → `ctx.session.id` + `ctx.session.capture(id)`
|
|
68
|
-
|
|
69
|
-
The storyline's interface is the former:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
// agents/chat-app.ts
|
|
73
|
-
import { defineAgent } from "niceeval/adapter";
|
|
74
|
-
|
|
75
|
-
const BASE_URL = "http://localhost:8080";
|
|
76
|
-
|
|
77
|
-
interface Msg { role: "user" | "assistant"; content: string | null }
|
|
78
|
-
|
|
79
|
-
export default defineAgent({
|
|
80
|
-
name: "chat-app",
|
|
81
|
-
async send(input, ctx) {
|
|
82
|
-
const history = ctx.session.history<Msg>(); // ← this session line's history slot; empty on a new session line
|
|
83
|
-
const messages = [...history.get(), { role: "user" as const, content: input.text }];
|
|
84
|
-
|
|
85
|
-
const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
|
86
|
-
// …method, headers, signal same as step one, omitted
|
|
87
|
-
body: JSON.stringify({ messages }), // ← the change: send the full history
|
|
88
|
-
}).then((r) => r.json());
|
|
89
|
-
|
|
90
|
-
const reply = res.choices[0].message;
|
|
91
|
-
history.commit([...messages, reply]); // ← write back; the next turn on the same line picks it up via get()
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
status: "completed",
|
|
95
|
-
events: [{ type: "message", role: "assistant", text: reply.content }],
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Notice there is **no "first turn" branch**: `history.get()` on a new session line naturally returns an empty array — the "first send" form is the natural consequence of a new session line, not a condition for you to check. Nothing needs declaring on `defineAgent` either: connect `ctx.session` and multi-turn continues; skip it and every turn is a new conversation.
|
|
102
|
-
|
|
103
|
-
If the app's interface takes a session id, the history lives on the server and the Adapter only records the id — just two changes inside `send`:
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
// Inside send:
|
|
107
|
-
// the request carries the previous turn's id … previous_response_id: ctx.session.id ← naturally undefined on a new session line
|
|
108
|
-
// write back whatever id the response returns … ctx.session.capture(res.id) ← the next turn continues from it
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
`capture` only lands when no id has been recorded yet; a backend re-sending the id (or even changing it due to a fork) will not overwrite the line being continued.
|
|
112
|
-
|
|
113
|
-
**This step unlocks**: multi-turn conversations, and `t.newSession()` session isolation.
|
|
114
|
-
|
|
115
|
-
## Step 3: record usage
|
|
116
|
-
|
|
117
|
-
An agent that answers correctly but burns ten times the tokens should not get the same score as one that is frugal. Usage is `usage`, the fourth field on `Turn` alongside `events` and `status`: if the app's interface returns usage, report it truthfully, and the runner accumulates it turn by turn into the session line and the whole run. Chat Completions-shaped responses come with `usage` — copy it over; the rest of `send` is exactly the same as step two:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
// …transport and session same as step two, omitted
|
|
121
|
-
return {
|
|
122
|
-
status: "completed",
|
|
123
|
-
events: [{ type: "message", role: "assistant", text: reply.content }],
|
|
124
|
-
usage: { // ← the change: report the usage the interface returned, as-is
|
|
125
|
-
inputTokens: res.usage.prompt_tokens,
|
|
126
|
-
outputTokens: res.usage.completion_tokens,
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
The full `Usage` fields also include the optional `cacheReadTokens` / `cacheWriteTokens`, the request count `requests`, and `costUSD` — if a gateway returns measured cost, fill that in; it takes precedence over price-table estimation. If the interface returns no usage, leave `usage` out entirely; other assertions are unaffected. This hand-copying is also transitional: the official converter in the next step fills in `usage` along with everything else.
|
|
132
|
-
|
|
133
|
-
**This step unlocks**: the `t.maxTokens()` / `t.maxCost()` scorers (`maxCost` uses `costUSD` or the price table in the config), plus usage in reports and `niceeval view`.
|
|
134
|
-
|
|
135
|
-
## Step 4: parse tools into events
|
|
136
|
-
|
|
137
|
-
The app's response contains more than reply text — the `tool_calls` in a Chat Completions-shaped response record which tools this turn called. The Adapter's most important job is **normalizing the interface's response into the standard event stream**: one object per thing that happened this turn, ordered by actual occurrence in `Turn.events`, each object one of the nine types below (for the actual field values, [the contract page has a complete one-turn example](/concepts/adapter)):
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
type StreamEvent =
|
|
141
|
-
| { type: "message"; role: "assistant" | "user"; text: string }
|
|
142
|
-
| { type: "action.called"; callId: string; name: string; input: JsonValue; tool?: ToolName }
|
|
143
|
-
| { type: "action.result"; callId: string; output?: JsonValue;
|
|
144
|
-
status: "completed" | "failed" | "rejected" }
|
|
145
|
-
| { type: "subagent.called"; callId: string; name: string; remoteUrl?: string }
|
|
146
|
-
| { type: "subagent.completed"; callId: string; output?: JsonValue;
|
|
147
|
-
status: "completed" | "failed" }
|
|
148
|
-
| { type: "input.requested"; request: InputRequest }
|
|
149
|
-
| { type: "thinking"; text: string }
|
|
150
|
-
| { type: "compaction"; reason?: string }
|
|
151
|
-
| { type: "error"; message: string };
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Parsing is one "response field → event" mapping. Hand-written it looks like this — the rest of `send` is exactly the same as step two:
|
|
155
|
-
|
|
156
|
-
```ts
|
|
157
|
-
// …transport and session same as step two, usage same as step three, omitted
|
|
158
|
-
const reply = res.choices[0].message;
|
|
159
|
-
|
|
160
|
-
const events: StreamEvent[] = [];
|
|
161
|
-
for (const call of reply.tool_calls ?? []) {
|
|
162
|
-
events.push({ type: "action.called", callId: call.id,
|
|
163
|
-
name: call.function.name, input: JSON.parse(call.function.arguments) });
|
|
164
|
-
}
|
|
165
|
-
if (reply.content) events.push({ type: "message", role: "assistant", text: reply.content });
|
|
166
|
-
|
|
167
|
-
return { events, status: "completed", usage };
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
But you usually **do not have to write this loop**. When the response is a standard shape, one line of official converter replaces all of the above — `events`, `status`, even step three's hand-copied `usage` are all in the return value; just `return` it:
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
// …transport and session same as step two, omitted
|
|
174
|
-
return fromChatCompletion(res); // ← the mapping above plus step three's usage, official edition
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
If the interface is not this shape, swap in the matching piece:
|
|
178
|
-
|
|
179
|
-
| How the interface's data arrives | Official piece | What you write |
|
|
180
|
-
|---|---|---|
|
|
181
|
-
| Chat Completions-shaped response | `fromChatCompletion(res)` | Zero mapping |
|
|
182
|
-
| Responses-shaped response | `fromResponses(res)` | Zero mapping |
|
|
183
|
-
| AI SDK `generateText` complete result | `fromAiSdk(result)` | Zero mapping |
|
|
184
|
-
| Streaming: SDK-native events passed through (one complete unit per frame) | `fromClaudeSdkMessages()` / `fromPiAgentEvents()` / `fromCodexThreadEvents()` | Zero mapping |
|
|
185
|
-
| Streaming: the AI SDK's UI Message Stream (the `useChat` protocol) | `uiMessageStreamAgent()` | Zero mapping (the whole Agent is prebuilt — you do not even write send) |
|
|
186
|
-
| Streaming: token-by-token / argument-by-argument deltas | The protocol's official reducer; only if there is none, `deltaStream({ toOps })` | One "frame type → operation" mapping table |
|
|
187
|
-
| Final answer only | A few hand-written events | Start with one `message` (step one is this tier) |
|
|
188
|
-
|
|
189
|
-
The prebuilt pieces go as far as "shape", not "protocol" — interfaces arrive in only these few shapes, one official piece each; only "delta stream with no official reducer from the protocol side" requires you to write a mapping, and what you write is a declaration (which operation this frame corresponds to) — concatenation, pairing, and landing timing are handled uniformly by `deltaStream`.
|
|
190
|
-
|
|
191
|
-
Once normalized, whichever events you emit determine which assertion families eval authors can write:
|
|
192
|
-
|
|
193
|
-
| Events you emit | Assertions unlocked |
|
|
194
|
-
|---|---|
|
|
195
|
-
| `message` | `t.reply`, `t.messageIncludes()`, judge conversation material |
|
|
196
|
-
| `action.called` / `action.result` (paired by `callId`) | `t.calledTool()` / `t.toolOrder()` / `t.maxToolCalls()` / `t.noFailedActions()`… |
|
|
197
|
-
| `input.requested` (together with status `"waiting"`) | `t.parked()`, `t.requireInputRequest()`, `t.respond()` (step five) |
|
|
198
|
-
| `thinking` / `compaction` / `error` | The matching assertions and o11y counts |
|
|
199
|
-
|
|
200
|
-
One honest disclosure: the Chat Completions shape **does not promise** that "response = full process record" (the app may well run the tool loop server-side and hand you only the final answer), so `fromChatCompletion`'s return carries no completeness proof — positive assertions like `calledTool` work as usual, while negative assertions like `notCalledTool` warn that they cannot be trusted. The Responses shape is the exact opposite: the protocol contract promises the `output` array records the full process, so `fromResponses`'s return carries a completeness proof and negative assertions are trustworthy. Same zero mapping — the difference in trustworthiness comes entirely from the interface shape itself.
|
|
201
|
-
|
|
202
|
-
**This step unlocks**: the whole family of tool assertions — `t.calledTool()`, `t.toolOrder()`, `t.maxToolCalls()`, `t.noFailedActions()`, and more.
|
|
203
|
-
|
|
204
|
-
## Step 5: HITL
|
|
205
|
-
|
|
206
|
-
When the app stops mid-turn to wait for a human (tool approval, missing information), `send` has obligations on both sides:
|
|
207
|
-
|
|
208
|
-
- **The pausing turn**: return `status: "waiting"`, and emit one `input.requested` event with a stable `id` per pending question — `t.parked()` and `t.requireInputRequest()` read them, and answers are matched by this `id`.
|
|
209
|
-
- **The answer turn**: `t.respond(...)` in the eval reaches the Adapter as **just another ordinary `send`** (still the same session line, the same state); the human verdict arrives in structured form via `input.responses`, one entry per answer carrying `requestId` and `optionId` or `text` (shapes in [the answer input shapes](/concepts/adapter#input-turninput)). The Adapter hands the verdict back to the app first, then continues fetching the result. For calls a human rejected, set the `action.result` `status` to `"rejected"` rather than `"failed"` — a rejection is a human decision, not a tool failure, so `noFailedActions()` does not misfire.
|
|
210
|
-
|
|
211
|
-
The "scene read halfway when the turn paused" (say, an SSE stream read halfway through) also lives on `ctx.session`: `ctx.session.hold(scene)` when pausing, `ctx.session.take()` at the start of the answer turn — taking clears it, single consumption.
|
|
212
|
-
|
|
213
|
-
HITL almost always happens on streaming interfaces (pausing mid-stream), so this step's example switches to an app that passes native events through over SSE — it also exercises everything from the earlier steps (complete runnable version in the [tier1 examples](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1)):
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
// agents/my-app.ts
|
|
217
|
-
import { defineAgent, sseJsonFrames, fromPiAgentEvents, driveFrameStream } from "niceeval/adapter";
|
|
218
|
-
import type { AgentContext, PiAgentStream, SseFrameCursor } from "niceeval/adapter";
|
|
219
|
-
import type { Turn, TurnInput } from "niceeval";
|
|
220
|
-
|
|
221
|
-
const BASE_URL = "http://localhost:5299"; // the app's own port; evals do not manage the process
|
|
222
|
-
|
|
223
|
-
interface Pending { cursor: SseFrameCursor<Frame>; stream: PiAgentStream; callId: string }
|
|
224
|
-
|
|
225
|
-
function readStream(cursor: SseFrameCursor<Frame>, ctx: AgentContext, stream: PiAgentStream): Promise<Turn> {
|
|
226
|
-
return driveFrameStream(cursor, stream, ctx, (frame) => {
|
|
227
|
-
if (frame.type === "session") {
|
|
228
|
-
ctx.session.capture(frame.sessionId); // ← step two: write back the returned id; the next turn continues from it
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (frame.type === "approval_request") { // ← this step: the app stops for approval; hold the scene
|
|
232
|
-
ctx.session.hold<Pending>({ cursor, stream, callId: frame.toolCallId });
|
|
233
|
-
return { pause: { id: frame.toolCallId, action: frame.toolName,
|
|
234
|
-
options: [{ id: "approve" }, { id: "deny" }] } };
|
|
235
|
-
// driveFrameStream on pause: emits an input.requested, sets status to "waiting", stops reading the stream
|
|
236
|
-
}
|
|
237
|
-
if (frame.type === "server_error") return { fail: frame.message };
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export default defineAgent({
|
|
242
|
-
name: "my-app",
|
|
243
|
-
async send(input: TurnInput, ctx: AgentContext): Promise<Turn> {
|
|
244
|
-
const held = ctx.session.take<Pending>();
|
|
245
|
-
if (held) { // ← t.respond("approve"): an ordinary send; deliver the verdict first
|
|
246
|
-
const approved = input.responses?.[0]?.optionId === "approve"; // with multiple pending requests, match by requestId
|
|
247
|
-
if (!approved) held.stream.markRejected(held.callId);
|
|
248
|
-
await fetch(`${BASE_URL}/api/chat/approve`, { method: "POST", signal: ctx.signal,
|
|
249
|
-
body: JSON.stringify({ toolUseId: held.callId, approved }) });
|
|
250
|
-
return readStream(held.cursor, ctx, held.stream); // then keep reading the same stream; no new request
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const res = await fetch(`${BASE_URL}/api/chat`, { // ← transport: the only part you truly hand-write
|
|
254
|
-
method: "POST",
|
|
255
|
-
body: JSON.stringify({
|
|
256
|
-
message: input.text, // ← step one: send the message
|
|
257
|
-
sessionId: ctx.session.id, // ← step two: absent on the first send, present automatically afterward
|
|
258
|
-
}),
|
|
259
|
-
signal: ctx.signal,
|
|
260
|
-
});
|
|
261
|
-
return readStream(sseJsonFrames<Frame>(res.body!), ctx, fromPiAgentEvents()); // ← step four: official converter, zero mapping
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
For interfaces without HITL, delete the three held-scene parts (`Pending`, `hold`, and the opening `take` branch) and keep the rest unchanged. The complete mental model of pause / answer / resume is in [HITL](/concepts/hitl).
|
|
267
|
-
|
|
268
|
-
**This step unlocks**: `t.parked()`, `t.requireInputRequest()`, `t.respond()` / `t.respondAll()`, and the precise assertion `calledTool(..., { status: "rejected" })`.
|
|
269
|
-
|
|
270
|
-
## Step 6: hook up OTel traces
|
|
271
|
-
|
|
272
|
-
If the app is already instrumented (standard OTel HTTP server instrumentation is enough), the integration comes in two halves — one is startup-time configuration, the other lives in `send`. Keeping them apart means separating "what never changes" from "what changes every turn".
|
|
273
|
-
|
|
274
|
-
**The endpoint is startup-time configuration; it is not passed from send.** NiceEval's OTLP receiver address is the same on every run, so it does not go through `ctx`: pin the receiver port in `niceeval.config.ts`, point the app's OTel exporter at that fixed URL on startup, and never touch it again no matter how many eval runs follow:
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
// niceeval.config.ts
|
|
278
|
-
import { defineConfig } from "niceeval";
|
|
279
|
-
|
|
280
|
-
export default defineConfig({
|
|
281
|
-
telemetry: { port: 4318 }, // the receiver always listens on http://localhost:4318/v1/traces
|
|
282
|
-
});
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces node server.js
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**What send passes is this turn's trace context, not the endpoint.** `ctx.telemetry.headers` is a W3C `traceparent` header the runner generates fresh every turn — spread it into the request, and the spans your app produces this turn attach precisely to this turn's trace, with no misattribution when multiple evals run concurrently. Back on the storyline's chat-app, `send` gains exactly one line:
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
|
293
|
-
// …rest same as step four, omitted
|
|
294
|
-
headers: { "content-type": "application/json", ...ctx.telemetry?.headers }, // ← the only added line
|
|
295
|
-
}).then((r) => r.json());
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
`ctx.telemetry` only exists when OTel integration is configured; spreading an `undefined` is safe when it is not, so the line can stay permanently. Spans still arrive without this header, but attribution degrades to time windows and that agent's turns fall back to serial execution — carrying it is what makes attribution accurate under concurrency.
|
|
299
|
-
|
|
300
|
-
**This step unlocks**: the call waterfall in `niceeval view` — every model call and every tool execution inside the app, with duration and tokens, laid out as a per-turn timeline. Assertions are unchanged: the verdict evidence was already complete in the event mapping of the earlier steps; spans go only into the waterfall and never feed assertions. For how the receiver starts and how spans attribute to turns, see [OTel integration](/guides/connect-otel).
|
|
301
|
-
|
|
302
|
-
## Step 7: pass through the experiment's flags (A/B comparison)
|
|
303
|
-
|
|
304
|
-
Once the app exposes variants as switchable configuration, the experiment declares `flags`, and the runner hands them verbatim to `send` via `ctx.flags` every turn; the Adapter does not interpret their meaning, it only forwards them with the request — the app switches variants on the flag:
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
// …rest of send same as before, omitted
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
-
messages,
|
|
310
|
-
flags: ctx.flags, // ← experiment.flags passed through as-is; {} when not configured
|
|
311
|
-
}),
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
```ts
|
|
315
|
-
// experiments/concise.ts
|
|
316
|
-
import { defineExperiment } from "niceeval";
|
|
317
|
-
import chatApp from "../agents/chat-app.ts";
|
|
318
|
-
|
|
319
|
-
export default defineExperiment({
|
|
320
|
-
agent: chatApp,
|
|
321
|
-
flags: { promptVariant: "concise" }, // ← reaches send via ctx.flags every turn
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
Two experiment files, each declaring its own `flags`, running the same set of evals via `npx niceeval exp` — that is an A/B comparison. This is Tier 3 of the three integration tiers (it requires the app to cooperate by exposing switches); cost and payoff in [Tier](/concepts/tier); `flags` alongside `model`, `runs`, and the other experiment fields in [Write experiments](/guides/write-experiment).
|
|
326
|
-
|
|
327
|
-
**This step unlocks**: score comparison across variants over the same set of evals.
|
|
328
|
-
|
|
329
|
-
## Reference: the five t APIs as send sees them
|
|
330
|
-
|
|
331
|
-
With all seven steps written, look back at the five driving APIs on the eval side — by the time they reach `send` they are just the same function receiving different fields; there is no second method to implement:
|
|
332
|
-
|
|
333
|
-
| Eval side | Reaching the Adapter | What `send` receives |
|
|
334
|
-
|---|---|---|
|
|
335
|
-
| `t.send(text)` | One `send` | `input.text`; `ctx.session` is this session line's own state |
|
|
336
|
-
| `t.sendFile(path, text?)` | One `send` | Same as above, plus `input.files` (base64 `InputFile[]`); put them into the request however the app's interface expects, or ignore them if it is not multimodal |
|
|
337
|
-
| `t.newSession()` | Does not trigger a `send` by itself | Opens a second session line; that line's next first `send` gets **completely empty** state — exactly the same form as the eval's very first `send` |
|
|
338
|
-
| `t.respond(...answers)` | One **ordinary** `send` | `input.text` = the answer text; `input.responses` has one `{ requestId, optionId }` per request (`{ requestId, text }` for free-text answers); still the same line, the same state |
|
|
339
|
-
| `t.respondAll(optionId)` | One **ordinary** `send` | Same as above, with one answer per pending request; `optionId` is validated eval-side against every request — a typo throws instead of silently reaching the app |
|
|
340
|
-
|
|
341
|
-
## Related reading
|
|
342
|
-
|
|
343
|
-
- [Adapter](/concepts/adapter) — the contract itself: what `send` receives and returns, the three integration tiers, where capabilities come from.
|
|
344
|
-
- [Connect your agent](/guides/connect-your-agent) — the integration picture: minimal integration, parameter channels, and the increment map.
|
|
345
|
-
- [HITL](/concepts/hitl) — the complete concept of pausing for a human: handshake timing and both sides' obligations.
|
|
346
|
-
- [Drive](/concepts/drive) — the eval-side view: how to use `t.send()`, `t.newSession()`, and HITL.
|
|
347
|
-
- [Assert](/concepts/assert) — the complete assertion vocabulary driven by the standard event stream.
|
package/docs-site/index.mdx
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "NiceEval"
|
|
3
|
-
sidebarTitle: "Home"
|
|
4
|
-
description: "NiceEval is a progressive, full-featured, developer-friendly, agent-native eval tool for AI agents."
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
<div align="center">
|
|
8
|
-
|
|
9
|
-
# NiceEval
|
|
10
|
-
|
|
11
|
-
**A progressive, full-featured, developer-friendly, agent-native eval tool for AI agents**
|
|
12
|
-
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
[NiceEval](https://niceeval.com/) is an agent-native eval tool inspired by [eve](https://eve.dev). It aims for strong developer experience first: a project can usually integrate it in about 10 minutes, eval files stay readable, and results are easy to inspect.
|
|
16
|
-
|
|
17
|
-
It is also broad enough to evaluate coding-agent plugins, hooks, and Skills built for Claude Code / Codex, as well as your own AI agent application. Whether your app uses AI SDK, LangGraph, Pi, or a custom agent loop, you can connect it through an adapter.
|
|
18
|
-
|
|
19
|
-
After each run, [NiceEval](https://niceeval.com/) generates readable reports and structured artifacts so you can inspect agent behavior, debug failures, and keep improving pass rate, cost, and latency.
|
|
20
|
-
|
|
21
|
-
<CardGroup cols={3}>
|
|
22
|
-
<Card title="Claude Code / Codex plugin" icon="plug" href="/example/claude-code-codex-plugin">
|
|
23
|
-
Use sandbox workspaces to give a coding agent real tasks, then verify plugin behavior through tests, diffs, and tool-call records.
|
|
24
|
-
</Card>
|
|
25
|
-
<Card title="Claude Code / Codex Skill" icon="wand-magic-sparkles" href="/example/claude-code-codex-skill">
|
|
26
|
-
Inject the Skill through experiment setup, then compare pass rate, cost, and behavior against a no-Skill baseline.
|
|
27
|
-
</Card>
|
|
28
|
-
<Card title="AI agent application" icon="globe" href="/example/ai-agent-application">
|
|
29
|
-
Wrap your AI agent with `defineAgent` and bring replies, tool calls, and structured output into the same eval surface.
|
|
30
|
-
</Card>
|
|
31
|
-
</CardGroup>
|
|
32
|
-
|
|
33
|
-
## Architecture
|
|
34
|
-
|
|
35
|
-
[NiceEval](https://niceeval.com/) supports two integration modes, depending on whether the subject under test needs an isolated workspace.
|
|
36
|
-
|
|
37
|
-
### Mode 1: Sandbox
|
|
38
|
-
|
|
39
|
-
Run coding agents such as Codex and Claude Code that need a sandbox:
|
|
40
|
-
|
|
41
|
-
```text
|
|
42
|
-
evals/*.eval.ts
|
|
43
|
-
|
|
|
44
|
-
v
|
|
45
|
-
┌────────────┐
|
|
46
|
-
│ niceeval │
|
|
47
|
-
└────────────┘
|
|
48
|
-
|
|
|
49
|
-
| Agent adapter (official)
|
|
50
|
-
v
|
|
51
|
-
┌───────────────────────────────┐
|
|
52
|
-
│ Docker Sandbox │
|
|
53
|
-
│ ┌───────────────────────┐ │
|
|
54
|
-
│ │ Codex / Claude Code / │ │
|
|
55
|
-
│ │ apps that need an │ │
|
|
56
|
-
│ │ isolated workspace │ │
|
|
57
|
-
│ └───────────────────────┘ │
|
|
58
|
-
└───────────────────────────────┘
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Mode 2: Direct
|
|
62
|
-
|
|
63
|
-
Connect directly to your own AI agent:
|
|
64
|
-
|
|
65
|
-
```text
|
|
66
|
-
evals/*.eval.ts
|
|
67
|
-
|
|
|
68
|
-
v
|
|
69
|
-
┌────────────┐
|
|
70
|
-
│ niceeval │
|
|
71
|
-
└────────────┘
|
|
72
|
-
|
|
|
73
|
-
| Agent adapter (official / custom)
|
|
74
|
-
v
|
|
75
|
-
┌────────────────────────────┐
|
|
76
|
-
│ Your own AI agent app │
|
|
77
|
-
│ (AI SDK · LangGraph · Pi · │
|
|
78
|
-
│ custom agent loop, etc.) │
|
|
79
|
-
└────────────────────────────┘
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
- **[NiceEval](https://niceeval.com/) core** handles eval discovery, scheduling, scoring, reporting, and artifacts.
|
|
83
|
-
- **Agent adapters** are the open boundary: you decide how to call the subject under test and how to map its output into the standard event stream.
|
|
84
|
-
- **Sandbox** handles file-system isolation: coding agents that need a real workspace use Docker or a cloud sandbox; direct web agents do not need Docker.
|
|
85
|
-
|
|
86
|
-
## Core Concepts
|
|
87
|
-
|
|
88
|
-
| Concept | In one sentence |
|
|
89
|
-
|---|---|
|
|
90
|
-
| [Eval](/concepts/evals) | A test case: written in `evals/*.eval.ts`, describing what to verify. |
|
|
91
|
-
| [Experiment](/concepts/experiment) | A check-in-able run configuration: decides which adapter, model, and flags to use. |
|
|
92
|
-
| [Adapter](/concepts/adapter) | The connection layer for the subject under test: implements `send` and translates responses into the standard event stream. |
|
|
93
|
-
| [Sandbox](/guides/sandbox-backends) | Needed only for coding agents that require isolated workspaces; direct web agents do not need one. |
|
|
94
|
-
| [Tier](/concepts/tier) | Three investment levels for adapter integration: Tier 1 only connects `send`, Tier 2 adds OTel for call waterfalls, and Tier 3 makes invasive changes for feature A/B tests. |
|
|
95
|
-
|
|
96
|
-
See [Architecture Overview](/concepts/overview) for the full terminology map.
|
|
97
|
-
|
|
98
|
-
## Examples
|
|
99
|
-
|
|
100
|
-
To run one eval you need two files: the eval itself (what to test) and an experiment (which agent to run). The CLI does not accept a bare eval ID. In `niceeval exp <experiment> <eval-prefix>`, the experiment decides which subject gets connected. Here is a real direct web-agent scenario from `examples/zh/ai-sdk/`: verify that the agent calls a tool for a live weather question and answers from the tool result instead of inventing one.
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
// evals/weather-tool.eval.ts
|
|
104
|
-
import { defineEval } from "niceeval";
|
|
105
|
-
|
|
106
|
-
export default defineEval({
|
|
107
|
-
description: "Verify that the agent calls the weather tool and answers from the tool result",
|
|
108
|
-
|
|
109
|
-
async test(t) {
|
|
110
|
-
const turn = await t.send("What's the weather like in Beijing today?");
|
|
111
|
-
turn.succeeded();
|
|
112
|
-
|
|
113
|
-
await t.group("Calls get_weather with the right city", () => {
|
|
114
|
-
t.calledTool("get_weather", { input: { city: "Beijing" } });
|
|
115
|
-
t.messageIncludes(/°C|temperature|weather|sunny|cloudy|rain/i);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
t.judge.autoevals
|
|
119
|
-
.closedQA("Does the assistant answer from the returned weather data instead of inventing a temperature?")
|
|
120
|
-
.atLeast(0.7);
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
// experiments/local.ts
|
|
127
|
-
import { defineExperiment } from "niceeval";
|
|
128
|
-
import { webAgent } from "./adapter"; // Your adapter for the web agent under test.
|
|
129
|
-
|
|
130
|
-
export default defineExperiment({
|
|
131
|
-
agent: webAgent({ baseUrl: "http://127.0.0.1:5188" }),
|
|
132
|
-
model: "gpt-5.4",
|
|
133
|
-
});
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
```sh
|
|
137
|
-
npx niceeval exp local weather-tool
|
|
138
|
-
npx niceeval view
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
If the subject under test is a coding agent that needs an isolated workspace, such as Codex or a Claude Code plugin / Skill, use `t.sandbox.uploadDirectory()` to lay down the workspace, `t.sandbox.fileChanged()` / `t.sandbox.file()` to inspect changes, and `t.sandbox.runCommand()` to run tests. See `examples/zh/coding-agent-skill/` in the repository.
|
|
142
|
-
|
|
143
|
-
## Quickstart
|
|
144
|
-
|
|
145
|
-
If you want a coding agent to integrate [NiceEval](https://niceeval.com/) into the project for you, hand it the matching scenario page:
|
|
146
|
-
|
|
147
|
-
```text
|
|
148
|
-
READ https://raw.githubusercontent.com/CorrectRoadH/niceeval/refs/heads/main/docs-site/zh/example/claude-code-codex-plugin.mdx and install niceeval for this repo.
|
|
149
|
-
READ https://raw.githubusercontent.com/CorrectRoadH/niceeval/refs/heads/main/docs-site/zh/example/claude-code-codex-skill.mdx and install niceeval for this repo.
|
|
150
|
-
READ https://raw.githubusercontent.com/CorrectRoadH/niceeval/refs/heads/main/docs-site/zh/example/ai-agent-application.mdx and install niceeval for this repo.
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
<CardGroup cols={3}>
|
|
154
|
-
<Card title="Eval your Claude Code / Codex plugin" icon="plug" href="/example/claude-code-codex-plugin">
|
|
155
|
-
Start from a real plugin task, upload a workspace, inject the plugin, and use pass rate to see whether it works reliably.
|
|
156
|
-
</Card>
|
|
157
|
-
<Card title="Eval your Claude Code / Codex Skill" icon="wand-magic-sparkles" href="/example/claude-code-codex-skill">
|
|
158
|
-
Verify that the Skill is loaded, uses tools as intended, and produces testable outcomes.
|
|
159
|
-
</Card>
|
|
160
|
-
<Card title="Eval your AI agent application" icon="globe" href="/example/ai-agent-application">
|
|
161
|
-
Wrap your app interface and write assertions over conversations, tool calls, structured outputs, and cost.
|
|
162
|
-
</Card>
|
|
163
|
-
</CardGroup>
|
|
164
|
-
|
|
165
|
-
## FAQ
|
|
166
|
-
|
|
167
|
-
<AccordionGroup>
|
|
168
|
-
<Accordion title="Do I need to create an account?">
|
|
169
|
-
No. Running evals and inspecting reports with `niceeval view` happens locally. Docker or E2B-style sandbox backends are only needed when the subject under test is a coding agent that needs an isolated workspace.
|
|
170
|
-
</Accordion>
|
|
171
|
-
<Accordion title="Can I connect an agent that is not written in TypeScript or JavaScript?">
|
|
172
|
-
Yes. An adapter only sends to and receives from the interface your app already exposes, such as HTTP, gRPC, or WebSocket. [NiceEval](https://niceeval.com/) does not care which language your app is written in or where it is deployed. See [Adapter](/concepts/adapter).
|
|
173
|
-
</Accordion>
|
|
174
|
-
<Accordion title="Can this run in CI?">
|
|
175
|
-
Yes. See [CI Integration](/guides/ci-integration).
|
|
176
|
-
</Accordion>
|
|
177
|
-
</AccordionGroup>
|
|
178
|
-
|
|
179
|
-
<Tip>
|
|
180
|
-
For the shortest path, read [Quickstart](/quickstart). To understand the boundaries first, read [Architecture Overview](/concepts/overview).
|
|
181
|
-
</Tip>
|