跳到主要内容

运行实验

新增于: @mastra/core@1.4.0

实验会让 Dataset 中的每个 item 通过一个目标(Agent、Workflow 或 Scorer),然后可以选择对输出进行评分。如果要评估 LLM judge 本身,请将 Scorer 用作目标。默认情况下,结果会持久保存到 Storage,因此你可以比较不同 prompt、模型或代码变更下的运行结果。

基础实验
基础实验的直接链接

使用目标和 Scorer 调用 startExperiment()

src/mastra/experiments/basic.ts
import { mastra } from '../index'

const dataset = await mastra.datasets.get({ id: 'translation-dataset-id' })

const summary = await dataset.startExperiment({
name: 'gpt-5.1-baseline',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy', 'fluency'],
})

console.log(summary.status) // 'completed' | 'failed'
console.log(summary.succeededCount) // number of items that ran successfully
console.log(summary.failedCount) // number of items that failed

startExperiment() 会阻塞,直到所有 item 完成。如需触发后不等待的执行方式,请参阅异步实验

Studio
Studio的直接链接

你也可以在 Studio 中运行实验。添加 Dataset item 后,打开它并选择 Run Experiment,然后配置目标、Scorer 和选项。

实验运行后,Experiments 选项卡会显示该 Dataset 的所有运行(包括状态、数量和时间戳)。选择一个实验,可以查看每个 item 的结果、分数和执行 Trace。

Experiments 选项卡中选择 Compare,然后选择两个或更多实验,以并排比较其分数和结果。

实验目标
实验目标的直接链接

可以将实验指向已注册的 Agent、Workflow 或 Scorer。

已注册的 Agent
已注册的 Agent的直接链接

指向在 Mastra 实例上注册的 Agent:

src/mastra/experiments/agent-target.ts
const summary = await dataset.startExperiment({
name: 'agent-v2-eval',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy'],
})

每个 item 的 input 会直接传给 agent.generate(),因此必须是 stringstring[]CoreMessage[]

启用了 Memory 的 Agent
启用了 Memory 的 Agent的直接链接

当目标 Agent 拥有自己的 Memory,且 request context 携带资源 ID(MASTRA_RESOURCE_ID_KEY,由身份验证 middleware、实验或 item 的 requestContext,或 Studio Run Experiment 表单设置)时,实验 runner 会为每个 item 注入新的 Memory 线程。Request context 中的资源 ID 表示“以此资源的身份运行”:每个 item 的对话都会作为该资源下的线程持久保存;重试的 item 每次尝试都会获得新线程,因此此前失败的尝试不会泄漏到重试上下文中。

注入的线程会带上标签,以便映射回运行:线程元数据包含 experimentId,以及 Dataset item 的 ID(形式为 experimentItemId)。系统不会为这些线程生成标题。

由于线程属于调用方的资源,资源范围的 Memory 功能会在运行期间同时读取和写入该资源的 state:

  • 资源范围的 working memory 更新会持久保存到该资源,运行中后续 item 可以看到此前 item 所做的更新。
  • 资源范围的语义召回可以向实验提供该资源过去的对话,实验 transcript 也可在该资源之后的对话中被召回。

当你要根据真实用户积累的上下文评估 Agent 时,此功能很有用。如果不希望实验运行改动真实用户 state,请改用专用的评估资源 ID 运行实验。

以下情况会跳过线程注入:

  • 如果 request context 还设置了 MASTRA_THREAD_ID_KEY,runner 会原样使用该线程,因此每个 item(及重试)都会共享同一对话。
  • 如果 Agent 没有 Memory,或 request context 没有资源 ID,则运行不使用 Memory,也不会持久保存任何内容。

已注册的 Workflow
已注册的 Workflow的直接链接

指向在 Mastra 实例上注册的 Workflow:

src/mastra/experiments/workflow-target.ts
const summary = await dataset.startExperiment({
name: 'workflow-eval',
targetType: 'workflow',
targetId: 'translation-workflow',
scorers: ['accuracy'],
})

Workflow 会接收每个 item 的 input 作为触发数据。

已注册的 Scorer
已注册的 Scorer的直接链接

指向一个 Scorer,以根据 ground truth 评估 LLM judge:

src/mastra/experiments/scorer-target.ts
const summary = await dataset.startExperiment({
name: 'judge-accuracy-eval',
targetType: 'scorer',
targetId: 'accuracy',
})

Scorer 会接收每个 item 的 inputgroundTruth。随着底层模型发生变化,基于 LLM 的 judge 可能随时间漂移,因此定期根据已知正确的标签重新对齐非常重要。Dataset 可提供稳定的基准,用于检测这种漂移。

对结果评分
对结果评分的直接链接

每个 item 的目标执行后,Scorer 会自动运行。传入 Scorer 实例或已注册的 Scorer ID:

// Reference scorers registered on the Mastra instance
const summary = await dataset.startExperiment({
name: 'with-registered-scorers',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy', 'fluency'],
})

每个 item 的结果都包含各 Scorer 的分数:

for (const item of summary.results) {
console.log(item.itemId, item.output)
for (const score of item.scores) {
console.log(` ${score.scorerName}: ${score.score}${score.reason}`)
}
}

有关可用 Scorer 和自定义 Scorer 的详细信息,请访问 Scorer 概览

控制每次运行的持久化
控制每次运行的持久化的直接链接

使用 persistence 跳过特定运行的 Storage 写入。可以分别禁用实验记录和分数记录:

src/mastra/experiments/no-persistence.ts
const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy'],
persistence: {
experiments: 'none',
scores: 'none',
},
})

目标和 Scorer 仍会运行,startExperiment() 仍会在 summary 中返回 item 结果和分数。这些设置彼此独立。例如,只设置 scores: 'none',可以持久保存实验及其 item 结果,而不创建分数记录。

省略的设置默认为 'default',会保留标准 Storage 行为。此策略只控制由该次运行创建的实验记录和分数记录,不会禁用目标使用的 Storage,例如 Agent Memory、vector、Observability 或自定义 Tool Storage。

startExperimentAsync() 使用 experiments: 'none' 运行时,它不会持久保存实验记录、进度更新或 item 结果。分数持久化仍由 persistence.scores 单独控制。如果没有实验事件 observer,该运行会触发后不再跟踪,实验 API 也无法报告其完成还是失败。

当调用方需要返回的 summary 时,请使用同步的 startExperiment()。实验事件 observer 可以接收生命周期事件和最终 summary。

观察实验事件
观察实验事件的直接链接

使用 onEvent 在实验运行期间接收带版本且可安全进行 JSON 序列化的生命周期事件。它适用于 startExperiment()startExperimentAsync()runExperiment()

src/mastra/experiments/observe.ts
import type { ExperimentEvent } from '@mastra/core/datasets'

const events: ExperimentEvent[] = []

await dataset.startExperimentAsync({
task: async ({ input }) => processItem(input),
persistence: { experiments: 'none' },
onEvent: async event => {
events.push(event)
await publishEvent(event)
},
})

Observer 会接收以下事件类型:

  • experiment.run.started:标识运行、目标、已解析的 Dataset 版本和 item 数量。
  • experiment.item.completed:报告评分后已提交的 item 结果,包括分数、错误、重试次数、Tool mock 详情和稳定的 item 身份。
  • experiment.run.finished:报告最终结果和 summary 计数器。

Mastra 会等待每次 observer 调用完成,然后再交付下一个事件。这种串行交付会施加背压,并确保事件的 sequence 值与交付顺序一致,同时 item 仍可并发执行。

如果 observer 抛出异常或拒绝,Mastra 会中止剩余运行,并使 runExperiment() 拒绝,同时返回 idEXPERIMENT_EVENT_OBSERVER_FAILEDMastraError。它不会通过失败的 observer 发送最终事件。对于 startExperimentAsync(),分离的 observer 失败时,该方法已经返回。因此,如果调用方需要直接错误报告,请在 observer 内部处理交付失败。

Mastra 会等待 experiment.run.finished 事件,然后再持久保存最终实验状态。禁用实验持久化时,请将该事件视为权威的最终 signal,但不要将其用作 Storage 的写后读 signal。

导出的事件类型包括 ExperimentEventExperimentRunStartedEventExperimentItemCompletedEventExperimentRunFinishedEvent。读取事件特有属性前,请使用可区分的 type 字段缩小事件类型范围。

Tool mock
Tool mock的直接链接

当实验运行会调用具有副作用 Tool 的 Agent 时,请将静态 Tool mock 附加到各个 Dataset item,使运行具有确定性。在实验期间,被 mock 的 Tool 会返回其声明的输出,而不执行实际操作。默认情况下,item 上没有 mock 的 Tool 会实际运行。

Mock 位于 Dataset item 上,因此会随该行进行版本控制,并与测试用例一起转移。每个 mock 都声明 Tool 名称、预期参数以及要返回的输出:

src/mastra/experiments/tool-mocks.ts
await dataset.addItem({
input: 'What is the weather in Seattle?',
toolMocks: [
{
toolName: 'getWeather',
args: { city: 'Seattle' },
output: { temperature: 60, conditions: 'rainy' },
},
],
})

Tool mock 只支持 agent 目标。

阻止未声明的 Tool
阻止未声明的 Tool的直接链接

在实验上设置 unmockedToolPolicy: 'deny',可阻止所有没有 mock 的 Tool 调用。当实际调用可能产生副作用时,此设置很有用:

const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'weather-agent',
unmockedToolPolicy: 'deny',
})

默认策略为 'allow'。可以在单个已存储或内联 item 上覆盖实验策略:

await dataset.addItem({
input: 'What is the weather in Seattle?',
unmockedToolPolicy: 'allow',
})

Item 值优先于实验值。被拒绝的调用会在 Tool 执行前以 TOOL_MOCK_NOT_DECLARED 失败。该失败不会重试,也不会添加到 liveCalls

匹配与消耗
匹配与消耗的直接链接

参数会严格匹配:忽略对象键顺序,但数组顺序有意义,并且不会进行类型强制转换。只有当 Agent 调用 Tool 时所用参数与 mock 的 args 深度相等,才会提供该 mock。

当 item 为同一 Tool 和参数声明多个 mock 时,会按顺序消耗:第一次调用获取第一个 mock,下一次调用获取第二个,依此类推。顺序按各 (toolName, args) 分组跟踪,不同参数之间互不影响。

匹配模式
匹配模式的直接链接

默认情况下,每个 mock 都会严格匹配其 args。设置 matchArgs: 'ignore' 可以只匹配 Tool 名称;系统不会比较 mock 的 args,无论 Agent 如何调用 Tool,都会提供该 Tool 下一个未消耗的 mock:

const subAgentMock = {
toolName: 'agent-balanceAgent',
args: { prompt: 'look up the balance for YJ' },
output: { text: "YJ's balance is $100." },
matchArgs: 'ignore',
}

当 Tool 参数不稳定或由模型生成时,此设置很有用。最常见的情况是模拟 Subagent 的响应:受委托的 Subagent 会作为 agent-<name> Tool 暴露给父 Agent,其参数包括由 LLM 编写的 prompt 和运行时注入的字段。Mock agent-<name> 会返回预设响应,而不是运行 Subagent 及其内部 Tool。从 Trace 创建 mock 时,Subagent 委托调用会自动使用 matchArgs: 'ignore' 派生。可以将其改为 'strict',以固定确切参数。

失败
失败的直接链接

Tool 调用违反 mock 配置时,该 item 会失败:

  • TOOL_MOCK_MISMATCH:调用 Tool 时使用的参数与任何 mock 都不匹配。
  • TOOL_MOCK_EXHAUSTED:所有匹配的 mock 都已消耗。
  • TOOL_MOCK_NOT_DECLARED:Tool 没有 mock,且有效的 unmockedToolPolicy'deny'

发生任何上述失败时,Agent 运行都会立即中止,因此模型无法继续调用任何其他 Tool,包括本应实际运行、未被 mock 且有副作用的 Tool。这些失败是确定性的,因此不会重试。已声明但从未使用的 mock 不会导致 item 失败,系统会将其报告为未消耗。

启用 mock 拦截时,Agent 的 Tool 会按顺序执行,因此重复的 (toolName, args) mock 会按 Provider 的调用顺序消耗。当 item 声明了 mock,或其有效 unmockedToolPolicy'deny' 时,拦截会启用。

诊断
诊断的直接链接

每个 item 结果都带有 toolMockReport,说明该次运行如何处理 item 的 mock:

for (const item of summary.results) {
const report = item.toolMockReport
if (!report) continue

console.log(report.served) // mocks matched and returned
console.log(report.unconsumed) // mocks declared but never used
console.log(report.liveCalls) // undeclared tools allowed to run live
console.log(report.failure) // the first deterministic mock failure, if any
}

Studio 中编辑 Dataset item,可以通过 JSON 数组编写 Tool mock;打开实验结果可以查看同一份报告。

限制
限制的直接链接

  • 被 mock 的调用没有 Tool span。 被 mock 的调用会在 Tool 执行前返回输出,因此不会创建 Tool span。由已存储 Trace 支持的轨迹 Scorer 可能因此无法看到被 mock 的 Tool 调用。回退到 Agent 消息输出的轨迹提取仍能看到这些调用,因此轨迹评分可能会因 Observability 配置而异。
  • Storage 支持。 LibSQL、PostgreSQL、MongoDB 和 Spanner 适配器会持久保存 Tool mock 和 Tool mock 报告。MySQL 适配器不支持这些内容,并会拒绝携带 Tool mock 或 Tool mock 报告的写入。所有 Dataset Storage 适配器都会持久保存 unmockedToolPolicy

异步实验
异步实验的直接链接

startExperiment() 会阻塞,直到每个 item 都完成。对于长时间运行的 Dataset,请使用 startExperimentAsync() 在后台启动实验:

src/mastra/experiments/async.ts
const { experimentId, status } = await dataset.startExperimentAsync({
name: 'large-dataset-run',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy'],
})

console.log(experimentId) // UUID
console.log(status) // 'pending'

使用 getExperiment() 轮询完成状态:

let experiment = await dataset.getExperiment({ experimentId })

while (experiment.status === 'pending' || experiment.status === 'running') {
await new Promise(resolve => setTimeout(resolve, 5000))
experiment = await dataset.getExperiment({ experimentId })
}

console.log(experiment.status) // 'completed' | 'failed'

配置选项
配置选项的直接链接

并发
并发的直接链接

控制并行运行的 item 数量(默认值:5):

const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'translation-agent',
maxConcurrency: 10,
})

超时和重试
超时和重试的直接链接

设置每个 item 的超时(以毫秒为单位)和重试次数:

const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'translation-agent',
itemTimeout: 30_000, // 30 seconds per item
maxRetries: 2, // retry failed items up to 2 times
})

重试使用指数退避。中止错误绝不会重试。

中止实验
中止实验的直接链接

传入 AbortSignal 可取消正在运行的实验:

const controller = new AbortController()

// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60_000)

const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'translation-agent',
signal: controller.signal,
})

其余 item 会在 summary 中标记为已跳过。

固定 Dataset 版本
固定 Dataset 版本的直接链接

针对 Dataset 的特定快照运行:

const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'translation-agent',
version: 3, // use items from dataset version 3
})

查看结果
查看结果的直接链接

列出实验
列出实验的直接链接

const { experiments, pagination } = await dataset.listExperiments({
page: 0,
perPage: 10,
})

for (const exp of experiments) {
console.log(`${exp.name}${exp.status} (${exp.succeededCount}/${exp.totalItems})`)
}

实验详情
实验详情的直接链接

const experiment = await dataset.getExperiment({
experimentId: 'exp-abc-123',
})

console.log(experiment.status)
console.log(experiment.startedAt)
console.log(experiment.completedAt)
📹 观看

观看 Mastra Dataset 和实验 Workflow,了解 Dataset 和实验如何帮助提高可靠性。

Item 级结果
Item 级结果的直接链接

const { results, pagination } = await dataset.listExperimentResults({
experimentId: 'exp-abc-123',
page: 0,
perPage: 50,
})

for (const result of results) {
console.log(result.itemId, result.output, result.error)
}

理解 summary
理解 summary的直接链接

startExperiment() 返回包含计数和每个 item 结果的 ExperimentSummary

  • 当实验已完成但部分 item 失败时,completedWithErrorstrue
  • 通过 signal 取消的 item 会计入 skippedCount

有关完整的参数和返回类型文档,请访问 startExperiment Reference