defineAgent wraps a send function: it receives the input, drives the Agent, and returns the turn’s result.
This tutorial first gets a minimal integration running with an Adapter, an Experiment, and an eval, then explains how parameters flow from the Experiment to the Adapter and on to the app under test. Event streams, multi-turn, HITL, and tracing are optional capabilities, with the corresponding tutorials listed at the end.
Choose the integration path for your subject under test
AI SDK app
Apps built with the Vercel AI SDK can connect to an existing HTTP interface using the built-in adapter.
Agent
Evaluating a standalone Agent like Claude Code / Codex / bub: use the built-in Sandbox Agent.
Other AI Agent
Your own Agent needs to write Send. If your app already has OTel instrumentation, you can connect OTel.
Minimal integration example
Assume the project already hasnpx niceeval init run, so niceeval.config.ts and the evals/ directory exist. Three files, one job each: the Adapter connects to the system under test, the Experiment pins down the run configuration, and the eval defines the interaction and assertions.
1. Write the Adapter. The minimal integration only fills status and events: put the Agent’s reply into a message event.
send contract (every field of TurnInput / AgentContext / Turn) is in Adapter.
Even when the Agent runtime and the evals live in the same codebase, still call the interface the way a frontend user would, and do not replace fetch with an in-process function call, because:
- An in-process call is not the path your users take. The HTTP layer, serialization, middleware, and streaming are all bypassed; a passing eval does not mean production behavior is correct.
- An Adapter that calls in-process cannot be reused across deployment environments. An HTTP Adapter connects to local, staging, or production just by swapping
baseUrl(see the two Experiment files below); an in-process call is tied to the current codebase.
npx niceeval view --run <runId> to inspect each eval’s per-turn inputs, events, and assertion details.
If it doesn’t run, triage by where the error shows up, into three buckets:
fetchthrows directly (connection refused, etc.): the app is not running, or the URL insendis wrong — first send the same request to that endpoint withcurlto confirm.t.succeeded()fails and the turn’s verdict is failed: the request went out, but the Turn the app returned isfailed. Map the protocol’s failures ontoTurn.statusor a standarderrorevent; keep any extra bounded context you need withctx.diagnostic(...), not by printing the full response body.- Only content assertions fail: the integration itself already works — compare the actual value of
t.replyinview, then adjust the assertion or the app.
Experiment flags
Configuration belongs to exactly two channels; keep them separate and the integration stays untangled:- Static configuration goes through the Adapter factory. Environment-level configuration such as URL, auth, and protocol details is written as factory parameters in the Experiment file. The
agentfield ofdefineExperimentreceives an already-configured instance. - Per-turn dynamic values go through
ctx. Themodelandflagsdeclared by the experiment are handed tosendverbatim viactxon every turn; the Adapter does not interpret their meaning, it only forwards them to the app with the request.
my-agent from an instance with a hardcoded URL into a factory that receives configuration. Change the default export to a function that returns defineAgent(...), and have send read the factory parameters. The model and flags declared by the Experiment arrive via ctx on every turn, and send forwards them with the request:
options the same way. The experiment side changes in two small places: a named import of the factory, and the agent field goes from referencing an instance to calling the factory:
ctx fields a turn may use, and how to consume them:
Progress, diagnostics, and fatal errors in the Adapter
progress is short-lived status that later updates overwrite; diagnostic is a bounded record you can still review after the run ends. Neither can specify a phase or output stream, and neither automatically changes Turn.status or the Attempt verdict. Infrastructure errors — a failed connection, parsing that cannot continue — should throw an exception; a normal failure of the Agent under test is expressed with Turn.status: "failed".
The terminal shows only a one-layer error summary and Attempt identity. The full code, message, cause, stack, and diagnostics live in Attempt-owned channels; inspect them with niceeval show --run <runId> --page attempt-<attemptId>. An OTel trace only adds call relationships and timing — it is not a prerequisite for recording errors.
To evaluate local and production separately, create two Experiment files and pass in different factory parameters:
attempts, budget, concurrency, sandbox), see Write Experiments.
Add optional capabilities
Once the minimal integration is done, extend the Adapter as needed. Existing evals don’t need to change:
For the integration tier and scope each capability corresponds to, see Tier.
Reference implementations
examples/zh/tier1 provides five runnable non-intrusive integration examples (ai-sdk-v7, claude-sdk, codex-sdk, pi-sdk, langgraph), covering event-stream assertions, multi-turn isolation, HITL approve/reject, and the trace waterfall. Hand-writing send only requires implementing the transport and the mapping table; session continuation and HITL pause/resume are provided by ctx.session, and frame-by-frame driving can use the built-in implementation — see Built-in agent capabilities.
Related reading
- Official adapters overview — Sandbox and non-Sandbox Adapters and their configuration options.
- Write Send — the complete tutorial for hand-writing an Adapter: seven progressive steps, from sending one message to HITL, OTel, and flags.
- Adapter — the
sendcontract:TurnInput/AgentContext/Turn, field by field. - OTel Integration — send the app’s spans to NiceEval too, in exchange for the call waterfall in
niceeval view. - Tier — the requirements and capabilities of the three integration tiers.
- Write Experiments — the full
defineExperimentfields.