运行实验
新增于: @mastra/core@1.4.0
实验会让 Dataset 中的每个 item 通过一个目标(Agent、Workflow 或 Scorer),然后可以选择对输出进行评分。如果要评估 LLM judge 本身,请将 Scorer 用作目标。默认情况下,结果会持久保存到 Storage,因此你可以比较不同 prompt、模型或代码变更下的运行结果。
基础实验基础实验的直接链接
使用目标和 Scorer 调用 startExperiment():
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 完成。如需触发后不等待的执行方式,请参阅异步实验。
StudioStudio的直接链接
你也可以在 Studio 中运行实验。添加 Dataset item 后,打开它并选择 Run Experiment,然后配置目标、Scorer 和选项。
实验运行后,Experiments 选项卡会显示该 Dataset 的所有运行(包括状态、数量和时间戳)。选择一个实验,可以查看每个 item 的结果、分数和执行 Trace。
在 Experiments 选项卡中选择 Compare,然后选择两个或更多实验,以并排比较其分数和结果。
实验目标实验目标的直接链接
可以将实验指向已注册的 Agent、Workflow 或 Scorer。
已注册的 Agent已注册的 Agent的直接链接
指向在 Mastra 实例上注册的 Agent:
const summary = await dataset.startExperiment({
name: 'agent-v2-eval',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy'],
})
每个 item 的 input 会直接传给 agent.generate(),因此必须是 string、string[] 或 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:
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:
const summary = await dataset.startExperiment({
name: 'judge-accuracy-eval',
targetType: 'scorer',
targetId: 'accuracy',
})
Scorer 会接收每个 item 的 input 和 groundTruth。随着底层模型发生变化,基于 LLM 的 judge 可能随时间漂移,因此定期根据已知正确的标签重新对齐非常重要。Dataset 可提供稳定的基准,用于检测这种漂移。
对结果评分对结果评分的直接链接
每个 item 的目标执行后,Scorer 会自动运行。传入 Scorer 实例或已注册的 Scorer ID:
- Scorer ID
- Scorer 实例
// Reference scorers registered on the Mastra instance
const summary = await dataset.startExperiment({
name: 'with-registered-scorers',
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy', 'fluency'],
})
import { createAnswerRelevancyScorer } from '@mastra/evals/scorers/prebuilt'
const relevancy = createAnswerRelevancyScorer({ model: 'openai/gpt-5-mini' })
const summary = await dataset.startExperiment({
name: 'with-scorer-instances',
targetType: 'agent',
targetId: 'translation-agent',
scorers: [relevancy],
})
每个 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 写入。可以分别禁用实验记录和分数记录:
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()。
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() 拒绝,同时返回 id 为 EXPERIMENT_EVENT_OBSERVER_FAILED 的 MastraError。它不会通过失败的 observer 发送最终事件。对于 startExperimentAsync(),分离的 observer 失败时,该方法已经返回。因此,如果调用方需要直接错误报告,请在 observer 内部处理交付失败。
Mastra 会等待 experiment.run.finished 事件,然后再持久保存最终实验状态。禁用实验持久化时,请将该事件视为权威的最终 signal,但不要将其用作 Storage 的写后读 signal。
导出的事件类型包括 ExperimentEvent、ExperimentRunStartedEvent、ExperimentItemCompletedEvent 和 ExperimentRunFinishedEvent。读取事件特有属性前,请使用可区分的 type 字段缩小事件类型范围。
Tool mockTool mock的直接链接
当实验运行会调用具有副作用 Tool 的 Agent 时,请将静态 Tool mock 附加到各个 Dataset item,使运行具有确定性。在实验期间,被 mock 的 Tool 会返回其声明的输出,而不执行实际操作。默认情况下,item 上没有 mock 的 Tool 会实际运行。
Mock 位于 Dataset item 上,因此会随该行进行版本控制,并与测试用例一起转移。每个 mock 都声明 Tool 名称、预期参数以及要返回的输出:
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() 在后台启动实验:
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 失败时,
completedWithErrors为true。 - 通过
signal取消的 item 会计入skippedCount。
有关完整的参数和返回类型文档,请访问 startExperiment Reference。