> ## Documentation Index
> Fetch the complete documentation index at: https://niceeval.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 接入你的 Agent

> 写一个 Adapter、配置一个 Experiment，并运行第一条评估用例；再把配置和参数从 Experiment 传给 Adapter 与被测应用。

把一个被测对象接入 [NiceEval](https://niceeval.com/) 需要一个 **Adapter**。`defineAgent` 包住一个 `send` 函数；该函数接收输入、驱动 Agent，并返回本轮结果。

本教程先用 Adapter、Experiment 和评估用例跑通最小接入，再说明参数从 Experiment 到 Adapter 和被测应用的传递路径。事件流、多轮、HITL 与 tracing 属于可选能力，文末列出对应教程。

## 按被测对象选择接入方式

<CardGroup cols={3}>
  <Card title="AI SDK 应用" icon="bolt" href="/docs/zh/reference/builtin-agents">
    Vercel AI SDK 应用可使用内置适配器连接现有 HTTP 接口。
  </Card>

  <Card title="Agent" icon="terminal" href="/docs/zh/tutorials/sandbox-agent">
    评估 Claude Code / Codex / bub 这种独立的 Agent：用内置 Sandbox Agent。
  </Card>

  <Card title="其它 AI Agent" icon="plug">
    自己的 Agent 需要 [编写 Send](/docs/zh/tutorials/write-send)。如应用已有 OTel 埋点，可 [接入OTel ](/docs/zh/tutorials/connect-otel)
  </Card>
</CardGroup>

## 最小接入示例

以下步骤假设项目已经运行 `npx niceeval init`，并包含 `niceeval.config.ts` 和 `evals/` 目录。三个文件各有一项职责：**Adapter 连接被测系统，Experiment 固定运行配置，评估用例定义交互和断言。**

**1. 写 Adapter。** 最小接入只填 `status` 和 `events`，把 Agent 回复放进一条 `message` 事件。

```ts theme={null}
// agents/my-agent.ts
import { defineAgent } from "niceeval/adapter";

export default defineAgent({
  name: "my-agent",
  async send(input, ctx) {
    // 示例地址：换成你自己 agent 的真实端点（HTTP、CLI、SDK 都行，只要 send 里能拿到回复文本）
    const r = await fetch("http://localhost:3000/chat", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ message: input.text, model: ctx.model }),  // ← experiment.model 经 ctx.model 到这里
      signal: ctx.signal,
    });
    const body = await r.json();
    return {
      status: r.ok ? "completed" : "failed",
      events: [{ type: "message", role: "assistant", text: body.reply }],
    };
  },
});
```

最小示例先使用固定 URL。需要按环境传入 URL 时，使用下文的[参数传递方式](#实验-flag)。`send` 的完整契约（`TurnInput` / `AgentContext` / `Turn` 各字段）见 [Adapter](/docs/zh/explanation/adapter)。

即使 Agent runtime 和评估用例位于同一个代码库，也应通过像前端用户一样调用接口，也不要把 `fetch` 换成进程内函数调用，因为

* **代码内部调用不是用户走的那条链路。** HTTP 层、序列化、中间件、流式传输全被绕过，评估用例通过不代表线上行为正确。
* **Adapter 无法复用于其它部署环境。** HTTP Adapter 只需更换 `baseUrl`，即可连接本地、预发或生产环境（见下文两个 Experiment 文件）；进程内调用依赖当前代码库。

**2. 实验**

```ts theme={null}
// experiments/my-agent.ts
import { defineExperiment } from "niceeval";
import myAgent from "../agents/my-agent.ts";

export default defineExperiment({
  description: "my-agent 基线",
  agent: myAgent,
  model: "gpt-4o",
  runs: 1,
});
```

**3. 评估用例**

```ts theme={null}
// evals/refund-policy.eval.ts
import { defineEval } from "niceeval";
import { includes } from "niceeval/expect";

export default defineEval({
  description: "退款政策问答",
  async test(t) {
    await t.send("你们的退款政策是什么?");
    t.succeeded();
    t.check(t.reply, includes("30 天"));
    t.judge.autoevals.closedQA("回答是否说明了退款期限?").atLeast(0.7);
  },
});
```

```bash theme={null}
npx niceeval exp my-agent        # 跑这个 experiment 下的全部 eval
npx niceeval exp my-agent refund # 只跑 ID 以 refund 开头的
npx niceeval view              # 本地查看器里看结果
```

**验证运行结果**：终端会显示动态 dashboard，完成和排队数量在原位更新；失败、错误和 warning 会保留在输出中。运行结束后会打印摘要、失败 locator 和结果路径。`npx niceeval view` 会显示每条评估用例逐轮的输入、事件和评分明细。

没跑通时，按报错的位置分三类排查：

* **`fetch` 直接抛错**（连接被拒等）：应用没起来，或 `send` 里的 URL 不对——先用 `curl` 对那个接口发一次同样的请求确认。
* **`t.succeeded()` 没过、本轮判定是 failed**：请求发出去了，但应用返回的 Turn 是 `failed`。把协议中的失败映射到 `Turn.status` 或标准 `error` event；需要额外保留的有限上下文用 `ctx.diagnostic(...)`，不要打印完整响应体。
* **只有内容断言没过**：接入本身已经通了——在 `view` 里对照 `t.reply` 的实际值，调断言或调应用。

完成这些步骤后，文本断言和 Judge 评分即可使用。工具、多轮和审批流断言需要继续添加文末列出的可选能力。

## 实验 Flag

配置归属只有两条通道，分清它们，接入就不会乱：

1. **静态配置走 Adapter 工厂。** URL、鉴权和协议细节等环境级配置作为工厂参数写在 Experiment 文件里。`defineExperiment` 的 `agent` 字段接收**已经配置好的实例**。
2. **每轮动态值走 `ctx`。** experiment 声明的 `model`、`flags`，运行器每轮经 `ctx` 原样递给 `send`；Adapter 不解释它们的含义，只随请求转发给应用。

把第一步的 `my-agent` 从固定 URL 的实例改成接收配置的工厂。将 default export 改为返回 `defineAgent(...)` 的函数，并让 `send` 读取工厂参数。Experiment 声明的 `model` 和 `flags` 每轮经 `ctx` 到达，再由 `send` 随请求转发：

```ts theme={null}
// agents/my-agent.ts —— 工厂：静态配置进闭包
import { defineAgent } from "niceeval/adapter";
import type { Agent } from "niceeval/adapter";

export function myAgent(options: { baseUrl: string }): Agent {
  return defineAgent({
    name: "my-agent",
    async send(input, ctx) {
      const r = await fetch(`${options.baseUrl}/chat`, {   // ← 工厂配置：连哪
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          message: input.text,
          model: ctx.model,     // ← experiment.model：应用接口收模型选择就转发
          flags: ctx.flags,   // ← experiment.flags：原样交给应用，应用按参数切换
        }),
        signal: ctx.signal,     // ← 运行器的超时与取消：挂到每个请求上
      });
      const body = await r.json();
      return {
        status: r.ok ? "completed" : "failed",
        events: [{ type: "message", role: "assistant", text: body.reply }],
      };
    },
  });
}
```

鉴权 header、协议开关这类同属静态配置，一样加进 `options`。experiment 侧对应两处小改：具名导入工厂，`agent` 字段从「引用实例」变成「调用工厂」：

```ts theme={null}
// experiments/my-agent.ts —— 参数的声明处
import { defineExperiment } from "niceeval";
import { myAgent } from "../agents/my-agent.ts";

export default defineExperiment({
  description: "my-agent 基线",
  agent: myAgent({ baseUrl: "http://localhost:3000" }),  // ← 静态配置：实例化时传
  model: "gpt-5.4",                                    // ← 动态值：经 ctx.model 到 send
  flags: { promptVariant: "concise" },                // ← 动态值：经 ctx.flags 到 send
});
```

`ctx` 上每轮可能用到的字段和消费方式：

| `ctx` 字段            | 来源                    | Adapter 用法                                                                                                                                   |
| ------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `signal`            | 运行器（超时与取消）            | 挂到发出的每个请求上                                                                                                                                   |
| `model`             | experiment 的 `model`  | 应用接口收模型选择就随请求转发；不收就忽略                                                                                                                        |
| `flags`             | experiment 的 `flags`  | 原样转发（请求体、header 都行），应用按参数切换变体                                                                                                                |
| `telemetry`         | 配置了 OTel 接入时出现        | 只碰 `headers`：每轮新的 W3C `traceparent`，spread 进请求头。接收端点每次运行都一样，在 `defineConfig` 里固定、应用启动时指向它，不从 send 传——见 [OTel 接入](/docs/zh/tutorials/connect-otel) |
| `session`           | 运行器（每条会话线一份）          | 会话续接与 HITL 停轮现场的存取器都在它上面：`history()`、`id` / `capture()`、`hold()` / `take()`，见[写 send](/docs/zh/tutorials/write-send)                              |
| `progress(update)`  | 运行器（绑定当前 `agent.run`） | 报告 Turn/tool 的短期状态；Human dashboard 可显示，结果不保存                                                                                                 |
| `diagnostic(input)` | 运行器（绑定当前 `agent.run`） | 保存协议退化、响应不完整等 warning/error；可由 locator 下钻回顾                                                                                                  |

### Adapter 里的进度、诊断和致命错误

```ts theme={null}
async send(input, ctx) {
  ctx.progress({ message: "等待上游模型" });
  const response = await callAgent(input, { signal: ctx.signal });

  if (response.eventsIncomplete) {
    ctx.diagnostic({
      code: "incomplete-event-stream",
      level: "warning",
      message: "上游响应缺少工具结果事件",
      data: { requestId: response.requestId },
      dedupeKey: `incomplete-event-stream:${response.requestId}`,
    });
  }

  return toTurn(response);
}
```

`progress` 是可覆盖的短期状态；`diagnostic` 是运行结束后仍能回顾的有界记录。两者都不能指定 phase 或输出流，也不会自动改变 `Turn.status` 或 Attempt 判定。连接失败、解析无法继续等基础设施错误应抛出异常；正常收到的被测 Agent 失败通过 `Turn.status: "failed"` 表达。

终端只显示错误的一层摘要和 locator。完整 code、message、cause、stack 与 diagnostics 在 `result.json` 中，使用 `niceeval show @<locator>` 查看。OTel trace 只补充调用关系和耗时，不是错误记录的前提。

要分别评估本地和生产环境，创建两个 Experiment 文件，并传入不同的工厂参数：

```ts theme={null}
// experiments/local.ts
export default defineExperiment({
  agent: myAgent({ baseUrl: "http://localhost:3000" }),
  model: "gpt-5.4",
});

// experiments/prod.ts
export default defineExperiment({
  agent: myAgent({ baseUrl: "https://api.example.com" }),
  model: "gpt-5.4",
});
```

```bash theme={null}
npx niceeval exp local
npx niceeval exp prod
```

不要把 URL 放进 CLI 位置参数——experiment 名之后的位置参数只用于过滤评估用例 ID。experiment 的完整字段（`runs`、`budget`、并发、`sandbox`）见[写实验](/docs/zh/tutorials/write-experiment)。

## 添加可选能力

最小接入完成后，可以按需扩展 Adapter。已有评估用例不需要修改：

| 目标能力                                   | Adapter 改动                                        | 教程                                                                 |
| -------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
| 工具断言（`calledTool` / `toolOrder` / 负断言） | 把应用返回映射成标准事件流                                     | [写 send](/docs/zh/tutorials/write-send)、[事件流参考](/docs/zh/reference/events)   |
| 多轮对话、`t.newSession()` 隔离               | 接上 `ctx.session`：`history()` 或 `id` + `capture()` | [写 send](/docs/zh/tutorials/write-send)                                 |
| 审批流（HITL，人工介入）                         | 停轮返回 `waiting` + `input.requested`，回答轮续跑          | [HITL](/docs/zh/explanation/hitl)                                       |
| `niceeval view` 的调用瀑布图                 | 应用把 OTel span 发给 NiceEval（不影响断言）                  | [OTel 接入](/docs/zh/tutorials/connect-otel)                              |
| feature A/B 对比                         | 应用把变体暴露成 `flags` 可切换的配置                           | [Tier](/docs/zh/explanation/tier)、[写实验](/docs/zh/tutorials/write-experiment) |

各项能力对应的接入等级和功能范围见 [Tier](/docs/zh/explanation/tier)。

## 参考实现

[`examples/zh/tier1`](https://github.com/CorrectRoadH/niceeval/tree/main/examples/zh/tier1) 提供五个可运行的无侵入接入示例（ai-sdk-v7、claude-sdk、codex-sdk、pi-sdk、langgraph），覆盖事件流断言、多轮隔离、HITL 批准与拒绝，以及 trace 瀑布图。手写 `send` 只需实现 transport 和映射表；会话续接与 HITL 暂停恢复由 `ctx.session` 提供，逐帧驱动可使用内置实现，见[内置 Agent 能力](/docs/zh/reference/builtin-agents)。

## 相关阅读

* [官方适配器一览](/docs/zh/reference/official-adapters) —— 查看 Sandbox 与非 Sandbox Adapter 及其配置项。
* [写 send](/docs/zh/tutorials/write-send) —— 手写 Adapter 的完整教程：七步递进，从发一条消息到 HITL、OTel、flags。
* [Adapter](/docs/zh/explanation/adapter) —— `send` 的契约：`TurnInput` / `AgentContext` / `Turn` 逐字段。
* [OTel 接入](/docs/zh/tutorials/connect-otel) —— 把应用的 span 也发给 [NiceEval](https://niceeval.com/)，换 `niceeval view` 的调用瀑布图。
* [Tier](/docs/zh/explanation/tier) —— 查看三个接入等级的要求与能力。
* [写实验](/docs/zh/tutorials/write-experiment) —— `defineExperiment` 的完整字段。
