在断言或 judge 之前,先要让 agent 动起来。Drive 是 test(t) 里发送输入、拿到结果的那部分——t.send()、t.sendFile()、t.newSession(),以及 HITL 的 t.respond() / t.respondAll()。驱动产出的都是一个 Turn,NiceEval 里的所有断言和 judge 都从 Turn 的数据上读,具体看 Assert 和 Judge。
t.send() 和它返回的 Turn
t.send(input) 是唯一的动词。它底层调用 Adapter 的 send(input, ctx),把返回值归一成一个 Turn:
t.reply 是主 session 最后一条 assistant 消息的简写,等价于最近一次 t.send() 返回的 turn.message;t.events 是主 session 目前累积的完整事件流。
要结构化输出时,在 send 上声明 output(Standard Schema,zod 即可)。声明既是给应用的要求,也是 turn.data 的类型来源:
adapter 返回后运行器立刻按声明校验:data 不匹配,本轮直接 failed 并报出差异。没声明 output 的轮次没有 turn.data——它不是”agent 顺手返回点什么”的口袋,而是你声明了才存在、存在就强类型的字段。
turn.succeeded() 只看这一轮:检查这一轮的 status,以及是否卡在未回答的 HITL 请求上。后面几轮依赖前一轮成功时,紧跟着这一轮调它——在一个已经失败的轮次上继续挂断言,通常只会产出一堆混乱的连带失败。
带文件的一轮 —— t.sendFile()
t.sendFile(path, text?) 读一个本地文件(相对评估用例所在目录),按扩展名推断 MIME 类型,把它作为 data URL 附加到这一轮的输入里:
多轮对话
每次 await t.send(...) 都是同一个对话上的新一轮。把每轮的返回赋给局部变量,除了 run 级的 t 断言外还能单独断言这一轮:
多轮 t.send() 能不能真的续上上文,取决于 Adapter 的 send 是否接了 ctx.session 的会话续接存取器(history() 或 id + capture())——没接时每轮各是一场新对话。怎么接见 Adapter 与写 send。
独立会话 —— t.newSession()
t.newSession() 开一条与主会话并行、互不干扰的第二条对话线。它返回的 session handle 带同一套 drive API(send / sendFile / respond / respondAll)和同一套作用域断言词汇,但只看这条 session 自己的事件:
常见错误是想当然认为 t.newSession() 天然保证隔离。运行器保证的只是新会话线的状态是空的——真正对应用开一条新对话是 adapter 的事。一个不看自己的会话状态、永远续接同一个底层上下文的 adapter,会让 t.newSession() 悄悄共享状态,且不会报错。写 adapter 时,先用上面这条评估用例验一下隔离,再信任它。
人工介入(HITL)
有些 agent 会在一轮执行中间停下来,等审批或缺失信息,而不是直接跑完。这时这一轮以 status: "waiting" 结束,并带一条或多条 input.requested 事件说明在等什么。完整的心智模型(握手时序、Adapter 侧义务、rejected 语义)见 HITL,这里讲评估侧怎么用。
t.requireInputRequest(filter) 把一个待处理的 HITL 请求变成可检查、可回应的具体值——如果匹配到 0 个或超过 1 个待处理请求就会抛出,所以尽量把能填的 filter 字段都填上(id / prompt / display / action / optionIds / input)来消歧。t.respond(...) 回答它并发出下一轮;每个参数是一条回答:字符串按待处理请求的顺序对位,{ request, optionId } 对象形式显式指名(多个请求并停时用它)。在底层它只是又一次普通的 send:回答文本进 input.text,同时以结构化形式逐条进 input.responses——命中请求选项的回答带 { requestId, optionId },自由文本回答带 { requestId, text },adapter 不用解析文本,按 requestId 就能对上”哪个回答给哪个请求”。每种回答到 adapter 长什么样,见不同回答的入参。
如果当前轮有多个同类待处理请求、都该给同一个答案(比如逐个批准一批文件改动),用 t.respondAll(optionId) 一次性处理,不用挨个解。optionId 会先对每条待处理请求校验——不在请求的 options 里就直接抛错,打错的字不会被静默当成别的答案发出去:
相关阅读
- HITL — 停轮等人的完整概念:握手时序与两侧义务。
- Assert — 从
Turn.events 和 Turn.data 上读的断言词汇。
- Judge —
t.judge / session.judge / turn.judge 各自默认评什么材料。
- Adapter — 能力从哪来:什么解锁
t.newSession()、HITL 和工具相关断言。