评估
评估是一种自动化测试,它让智能体运行在实时模型上,并对其可观察行为进行断言:它产生的回复、它调用的工具、它发出的数据。本指南介绍评估与普通测试的区别、使用 Vitest 设置评估套件、在进程内和通过 HTTP 运行被测智能体、使用断言和评判器对结果评分,以及在本地和 CI 中运行评估。
什么是评估
智能体的行为由其指令、模型和工具共同产生。你编写的部分是带有普通测试的普通代码——工具的run 函数是可以直接进行单元测试的普通函数,不涉及模型。单元测试无法覆盖的是模型的贡献:智能体是否调用了正确的工具、遵循了其指令并产生了正确的答案。评估通过运行完整循环——指令、模型、工具——并对结果进行断言来覆盖这一点。
两个属性决定了评估的编写方式:
- 评估是非确定性的。 相同的输入可能产生不同的措辞、不同的工具调用顺序,偶尔会产生不同的结果。对行为契约进行断言——必需的工具调用、回复中的关键事实、结构化数据的形状——而不是精确的输出字符串。
- 评估消耗真实 token 和真实时间。 每个用例运行一个或多个实时模型轮次。因此评估位于自己的套件中,有自己的配置、凭据、超时和运行节奏,与单元测试分开。
Flue 没有专门的评估框架。评估是一个 Vitest 测试,通过与其他调用者相同的公共接口驱动智能体——进程内 init() 句柄 或 HTTP 对话接口——并对结果进行断言。vitest-evals 集成在其之上提供了评估工具、评判器和 CI 报告;参见下文。
设置评估套件
将评估放在专用的 Vitest 配置中。实时模型测试需要与单元测试不同的文件发现、更长的超时和不同的凭据,单独的配置让每个可以独立运行:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['src/evals/**/*.eval.ts'],
testTimeout: 60_000,
},
});60_000 超时替代了 Vitest 的 5 秒默认值,单个实时模型轮次可能超过这个时间。添加一个脚本以便一键运行套件:
{
"scripts": {
"evals": "vitest run --config vitest.evals.config.ts"
}
}评估文件位于 src/evals/ 下,以它们评估的能力或场景命名——service-health.eval.ts、refund-policy.eval.ts——而不是每个智能体一个文件。
在进程中编写评估
在测试中运行智能体最直接的方式是使用 @flue/runtime/node 的 start(),它在测试进程内启动 Flue 运行时——无需服务器,无需构建。然后 init() 句柄发送消息并等待结算的回复:
import { init } from '@flue/runtime';
import { start } from '@flue/runtime/node';
import { afterAll, expect, it } from 'vitest';
import { ServiceStatus } from '../agents/service-status.ts';
const flue = await start({ agents: [ServiceStatus] });
afterAll(() => flue.stop());
it('checks live service status before answering', async () => {
const toolsCalled: string[] = [];
// No id: init() mints a fresh conversation for this case.
const agent = init(ServiceStatus);
const receipt = await agent.dispatch('Is the checkout service currently operational?');
const reply = await agent.read(receipt, {
onEvent: (chunk) => {
if (chunk.type === 'tool-input') toolsCalled.push(chunk.toolName);
},
});
expect(reply.text).toContain('operational');
expect(toolsCalled).toContain('get_service_status');
});这里的一切都是普通的编程接口:
- 每个用例全新的对话。 不带
id的init(agent)指向一个新的、唯一命名的对话,因此用例保持独立——保存的对话历史不会影响其他用例。评估对话记忆的用例复用一个句柄并通过它发送多个dispatch(...)/read(...)对。 - 回复是断言目标。
reply.text是最终的助手文本,reply.data携带命名的useDataWriter部分——这里可以对结构化结果进行断言。失败或中止的运行以AgentRunError拒绝read(),这会使测试失败。 - 工具调用作为事件到达。
read()的onEvent回调在每个对话块被记录时接收它;tool-input块携带模型发出的每个工具调用的toolName和input。 - 完整的运行时行为。 钩子、持久性和沙箱的工作方式与在服务器中完全相同——
start()是没有 HTTP 接口的相同组装。
三个约束适用。一个进程持有一个 Flue 运行时,因此每个测试文件调用一次 start() 并在文件完成时停止它——Vitest 的默认隔离为每个测试文件提供自己的 worker,防止文件冲突。提供商凭据来自测试进程环境(参见模型——提供商凭据)。评估直接导入智能体模块,因此模块必须在普通 Vitest 下加载:依赖构建解析导入的智能体,如 SKILL.md 导入,需要 Flue 构建,应该通过 HTTP 进行评估。
通过 HTTP 评估
在 app.ts 中挂载的智能体可以通过其 HTTP 接口使用 Flue 智能体 SDK 进行评估——与部署应用服务的相同边界,包括你的路由中间件。新的对话是挂载 URL 后附加的新 id:
import { createFlueClient } from '@flue/sdk';
import { expect, it } from 'vitest';
// The agent's mount URL from app.ts; point FLUE_AGENT_URL at a deployment.
const mountUrl = process.env.FLUE_AGENT_URL ?? 'http://127.0.0.1:5173/agents/service-status';
it('checks live service status before answering', async () => {
const conversation = createFlueClient({
url: `${mountUrl}/eval-${crypto.randomUUID()}`,
});
const admission = await conversation.send({
message: { kind: 'user', body: 'Is the checkout service currently operational?' },
});
await conversation.wait(admission);
const { messages } = await conversation.history();
const reply = messages.findLast((message) => message.role === 'assistant');
const text =
reply?.parts
.filter((part) => part.type === 'text')
.map((part) => part.text)
.join('') ?? '';
expect(text).toContain('operational');
});通过 HTTP 的提示是即发即忘的:send() 接纳消息,wait() 等待其完成,history() 返回完成的对话——包括助手回复及其工具调用部分。评估进程不启动应用;在另一个终端运行 vite dev(或构建的服务器),或将 URL 设置为部署环境。当路由受保护时,向 createFlueClient(...) 传递 token 或 headers——参见保护你的智能体。
根据评估应测试的内容选择接口:
- 进程内(
start()) 测试智能体本身——指令、模型、钩子、工具——需要测试环境中的提供商凭据。 - HTTP(
@flue/sdk) 测试智能体加上app.ts路由和中间件,需要运行中的开发服务器或部署。
两个接口都是公共 API,因此也可以作为其他评估库和托管平台(如 Braintrust)的集成点——以相同方式驱动智能体并将结果交给你的评分流水线。
vitest-evals
vitest-evals 通过评估工具、LLM 评判器、规范化报告和 CI 报告扩展了 Vitest。通过蓝图添加 Flue 的集成:
flue add tooling vitest-evals
蓝图从上文创建评估配置和脚本,并生成 src/evals/harness.ts——一个通过 @flue/sdk 驱动每个用例的对话并将回复、工具调用和使用量转换为规范化 vitest-evals 结果的工具。设置、生成的文件和报告命令记录在 vitest-evals 生态系统页面;完整的可运行项目在 examples/vitest-evals。
用例使用 describeEval 编写,它将工具绑定到套件并为每个测试提供一个 run(...) 函数:
import { expect } from 'vitest';
import { describeEval, toolCalls } from 'vitest-evals';
import { createFlueAgentHarness } from './harness.ts';
const harness = createFlueAgentHarness({
agentUrl: process.env.FLUE_AGENT_URL ?? 'http://127.0.0.1:5173/agents/service-status',
});
describeEval('service status agent', { harness }, (it) => {
it('checks live service status before answering', async ({ run }) => {
const result = await run('Is the checkout service currently operational?');
expect(result.output).toContain('operational');
expect(toolCalls(result).map((call) => call.name)).toContain('get_service_status');
});
});评判器
确定性断言覆盖精确契约:必需的工具、禁止的工具、结构化输出、稳定内容。对于语义行为——事实一致性、语气、策略遵守——vitest-evals 提供评判器,对结果评分并在低于阈值时使用例失败。LLM 支持的评判器运行在_评判工具_上:评判器自己的模型连接,与被评估的智能体分开配置:
import { expect } from 'vitest';
import { describeEval, FactualityJudge } from 'vitest-evals';
describeEval('service status agent', { harness, judgeHarness }, (it) => {
it('reports status consistent with the reference answer', async ({ run }) => {
const result = await run('Is the checkout service currently operational?');
await expect(result).toSatisfyJudge(FactualityJudge(), {
expected: 'The checkout service is currently operational.',
threshold: 0.6,
});
});
});
createJudge(...) 定义自定义评判器,确定性或 LLM 支持的;内置的 FactualityJudge、ToolCallJudge 和 StructuredOutputJudge 覆盖常见的评分标准。评判器构造和评判器工具适配器记录在 vitest-evals 文档。优先使用确定性断言,仅在行为无法精确检查时添加评判器。
在本地和 CI 中运行评估
本地运行时,一旦提供商凭据在环境中,进程内套件一键运行:
pnpm run evals
HTTP 套件还需要一个可达的目标:先在另一个终端启动应用,或将套件的 URL 变量设置为部署环境:
FLUE_AGENT_URL=https://preview.example.com/agents/service-status pnpm run evals
在 CI 中,评估套件是普通的 Vitest 运行——用例失败时以非零退出,因此它像其他测试作业一样拦截流水线。将其与单元测试分开作为单独作业:实时模型运行更慢、消耗 token,且可能在没有代码更改时失败,因此值得有自己的节奏——在合并时、按计划或按需。提供商凭据来自 CI 密钥;对于 HTTP 套件,要么在作业内构建并启动应用,要么指向预览部署。
对于报告,vitest-evals 蓝图添加了一个 evals:json 脚本,写入 vitest-results.json 产物。本地用 vitest-evals serve vitest-results.json 检查,或从 CI 用 getsentry/vitest-evals GitHub Action 发布。报告可能包含提示、输出、工具参数和结果以及错误——在上传前审查保留和访问要求。
后续步骤
- Vitest Evals——蓝图、生成的工具和报告命令。
- 智能体——
start()和独立脚本,评估构建的相同接口。 - 智能体 API——完整的
init()句柄契约、AgentReply和AgentRunError。 - 智能体 SDK——一个对话 URL 上的
send()、wait()和history()。 - 可观测性——使用 Braintrust 和其他提供商追踪智能体运行;
submissionId和对话 id 等标识符将失败用例与产生它的执行关联起来。