跳到主要内容
非官方简体中文翻译,内容版权归 Mastra 所有,遵循 Apache-2.0 许可。

实验

新增于: @mastra/core@1.4.0

实验会将数据集中的每个数据项通过一个目标(agent、workflow 或 scorer)运行一遍,然后可选地对输出进行评分。当你想评估 LLM judge 本身时,可以将 scorer 作为目标。默认情况下,结果会持久化到存储中,以便你比较不同提示词、模型或代码变更下的多次运行。

基本实验
基本实验的直接链接

调用 startExperiment(),传入目标和 scorers:

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() 会阻塞,直到所有数据项完成。若需即发即忘(fire-and-forget)的执行方式,请参阅异步实验。

在 Studio 中运行实验
在 Studio 中运行实验的直接链接

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

若要为单个数据项配置 scorers,请编辑该数据项并启用 Override dataset scorers。选择一个或多个 scorer;若留空选择,则该数据项不运行任何 scorer。

运行实验后,Experiments 标签页会显示该数据集的全部运行记录(含状态、计数和时间戳)。选择一个实验即可查看逐项结果、分数和执行轨迹。

在 Experiments 标签页中,选择 Compare,然后选择两个或更多实验,即可并排比较它们的分数和结果。

删除实验
删除实验的直接链接

在 Studio 中打开全局 Experiments 列表,选择某一行上的 Delete experiment;或打开实验后选择 Delete Experiment。全局列表还可用于删除因数据集被删除而成为孤儿的实验(这些实验已没有数据集详情页)。

删除实验会永久移除其结果记录,以及它产生的可观测性轨迹(trace)及其关联的 span、分数、反馈、指标和日志。不支持删除轨迹的存储适配器会保留这些轨迹、记录一条警告,但仍会删除实验及其结果记录。

你也可以通过 Core API 或 Client SDK 删除实验。同样的操作也可通过服务器路由执行。

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

使用 targetType 和 targetId 选择 Mastra 实例上注册的 agent、workflow 或 scorer。目标负责处理每个数据集数据项,实验的 scorers 随后评估目标的输出。

如需自定义执行方式,可改传一个内联 task 函数。参见使用内联 task 的数据集实验,其中演示了如何为每个数据项控制 memory thread。

已注册的 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'],
})

每个数据项的 input 会直接传给该 agent。请使用受支持的消息格式,例如提示词字符串、消息对象或消息数组。可接受的输入请参阅 agent.generate() 参考。

启用 memory 的 agent
启用 memory 的 agent的直接链接

当目标 agent 有自己的 memory 时,实验运行器会为每个数据项及每次重试注入一个全新的 memory thread。如果请求上下文中没有 resource id,每个 thread 会获得一个隔离的、由实验拥有的 resource。

若要针对已有 resource 运行,可在实验的 requestContext 中,或在某个数据项的 requestContext 中设置 MASTRA_RESOURCE_ID_KEY。数据项的值会覆盖实验的值。运行器仍会为每个数据项及每次重试创建新 thread,但使用同一 resource id 的所有 thread 会共享该 resource 的 memory。

注入的 thread 会打上标签,以便你将其映射回本次运行:thread 元数据会携带 experimentId 以及作为 experimentItemId 的数据项 id。这些 thread 不会生成标题。

当多个数据项共享同一个 resource 时,resource 作用域的 memory 功能可以读写同一份状态:

  • 资源作用域的工作记忆(working memory)更新会持久化到该 resource,并可能影响其他数据项。数据项默认并发运行,因此不要依赖执行顺序。
  • 资源作用域的语义回忆(semantic recall)可能把该 resource 先前的对话呈现给实验,而实验的对话记录也会在该 resource 后续的对话中被回忆到。

使用这种方式可以针对真实用户的累计上下文来评估 agent。若希望实验运行不触碰真实用户的状态,请使用专用的评估 resource id。

以下情况会跳过 thread 注入:

  • 如果请求上下文设置了 MASTRA_THREAD_ID_KEY,运行器会原样使用该 thread。使用同一 thread id 的数据项和重试会共享同一段对话。
  • 如果 agent 没有 memory,或请求上下文显式将 resource id 设为空字符串或 null,运行器会跳过 thread 注入。

已注册的 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 会在 run.start() 中将每个数据项的 input 作为 inputData 接收。请按 workflow 的输入 schema 来组织输入。

已注册的 scorer
已注册的 scorer的直接链接

将 scorer 作为目标,可以评估 judge 本身。运行器会调用 scorer.run(item.input),因此数据集数据项的 input 必须包含该 scorer 期望的完整载荷。

以下示例假设已注册一个 accuracy scorer,它接受字符串类型的 input、output 和 groundTruth 字段。请根据你的 scorer 所期望的结构调整这些字段:

src/mastra/experiments/scorer-target.ts
await dataset.addItem({
input: {
input: 'What is the capital of France?',
output: 'Paris',
groundTruth: 'Paris',
},
groundTruth: { score: 1 },
})

const summary = await dataset.startExperiment({
name: 'judge-accuracy-eval',
targetType: 'scorer',
targetId: 'accuracy',
})

两个 groundTruth 字段的用途不同:

  • input.groundTruth 是传给 judge 的参考答案:'Paris'。
  • 顶层 groundTruth 是期望 judge 给出的结果:{ score: 1 }。它不会传给目标 scorer。

目标的输出包含其 score 和 reason。若要基于顶层 ground truth 评估该输出,可通过实验的 scorers 选项再添加一个 scorer。若没有额外 scorer,实验会记录 judge 的输出,但不会评估它与预期结果的一致程度。

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

每个数据项的目标执行完毕后,scorers 会自动运行。可传入 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'],
})

每个数据项的结果都包含按 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 的详细信息,请参阅 Scorers 概览。

为每个数据项选择 scorers
为每个数据项选择 scorers的直接链接

将已注册的 scorer ID 添加到数据集数据项,可覆盖附加到其数据集上的 scorers:

src/mastra/datasets/items.ts
await dataset.addItem({
input: 'Translate "hello" to French.',
scorerIds: ['accuracy', 'fluency'],
})

对于每个数据项,实验只会使用一个 scorer 来源,优先级如下:

  1. 实验的 scorers 选项(若提供)
  2. 数据项的 scorerIds 字段(若提供)
  3. 数据集的 scorerIds 字段
  4. 不使用任何 scorers

Mastra 不会合并这些来源。在运行级或数据项级显式传入空数组表示不选择任何 scorers,并阻止回退到下一个来源。空的分类运行级配置也有同样效果。重复的数据项或数据集 ID 只会运行一次,按首次出现的顺序处理。

以下更新在显式空覆盖与数据集继承之间切换:

src/mastra/datasets/items.ts
// Run no scorers for this item
await dataset.updateItem({
itemId: 'translation-item-id',
scorerIds: [],
})

// Remove the item override and inherit the dataset's scorers
await dataset.updateItem({
itemId: 'translation-item-id',
scorerIds: null,
})

更新时省略 scorerIds 会保留其当前值。每个数据项版本都会存储其 scorer ID 列表,包括空列表。Scorer 定义不会复制到数据项版本中,而是继续使用其现有的注册表或 Editor 行为。

Mastra 会在运行目标之前解析每个数据项的 scorer ID。它先检查已注册的 scorers,再请求 Editor 补全(hydrate)已存储的 scorer。过期的数据项级 ID 会为该数据项产生 EXPERIMENT_ITEM_SCORER_NOT_FOUND 错误码。Mastra 会跳过该目标执行且不重试该数据项,而实验的其余部分继续进行。

所选运行级或数据集级来源中缺失 ID 会导致实验设置失败。Mastra 不会从被优先级规则忽略的低优先级来源中解析 ID。

Workflow 步骤 scorer 只能通过运行级的分类 scorers 配置使用。数据项级 ID 适用于扁平的 agent、workflow 和 trajectory scorer 分派。

按运行控制持久化
按运行控制持久化的直接链接

使用 persistence 可跳过某次运行的存储写入。实验记录和评分记录可以分别禁用:

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

目标和 scorers 仍会运行,startExperiment() 仍会在 summary 中返回数据项结果和分数。这些设置彼此独立。例如,只设置 scores: 'none' 会持久化实验及其数据项结果,但不创建评分记录。

省略的设置默认为 'default',即保留标准存储行为。此策略仅控制本次运行创建的实验记录和评分记录,不会禁用目标所使用的存储,例如 agent memory、向量、可观测性或自定义工具存储。

当 startExperimentAsync() 以 experiments: 'none' 运行时,它不会持久化实验记录、进度更新或数据项结果。评分持久化仍由 persistence.scores 单独控制。如果没有实验事件观察者,该运行即为即发即忘,实验 API 无法报告它是完成还是失败。

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

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

使用 onEvent 可在实验运行期间接收带版本号、JSON 安全(JSON-safe)的生命周期事件。这适用于 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)
},
})

观察者会收到以下事件类型:

  • experiment.run.started:标识本次运行、目标、已解析的数据集版本和数据项数量。
  • experiment.item.completed:在评分后报告已提交的数据项结果,包括分数、错误、重试次数、工具 mock 详情和稳定的数据项标识。
  • experiment.run.finished:报告最终结果和汇总计数。

Mastra 会在投递下一个事件前等待每次观察者调用完成。这种串行投递会形成背压(backpressure),并确保事件的 sequence 值与投递顺序一致,同时数据项执行仍可保持并发。

如果观察者抛出异常或 reject,Mastra 会中止剩余运行,并以 id 为 EXPERIMENT_EVENT_OBSERVER_FAILED 的 MastraError 拒绝 runExperiment()。它不会通过失败的观察者发送终止事件。对于 startExperimentAsync(),分离的观察者失败时该方法已经返回,因此当调用方需要直接获取错误报告时,应在观察者内部处理投递失败。

experiment.run.finished 事件会在 Mastra 持久化最终实验状态之前被等待。当实验持久化被禁用时,可将该事件视为权威的终止信号,但不要把它当作存储的写后读(read-after-write)信号。

导出的事件类型包括 ExperimentEvent、ExperimentRunStartedEvent、ExperimentItemCompletedEvent 和 ExperimentRunFinishedEvent。使用可区分的 type 字段收窄事件类型,再读取该事件特有的属性。

生命周期钩子
生命周期钩子的直接链接

使用生命周期钩子,可在目标运行前准备状态,并在运行后清理。当数据项无法在空环境中评估时,这很有用。某次运行可能需要先把 fixture 文件复制到 agent 的工作区,或者先预置一个沙盒,agent 才能操作它。

钩子分两个层级运行。beforeAll 和 afterAll 每次实验运行一次,beforeEach 和 afterEach 每个数据项运行一次:

src/mastra/experiments/hooks.ts
const summary = await dataset.startExperiment({
targetType: 'agent',
targetId: 'document-agent',
scorers: ['accuracy'],
beforeAll: async ({ experimentId }) => {
await createWorkspace(experimentId)
},
beforeEach: async ({ item }) => {
await copyFixture(item.metadata?.fixture)
},
afterEach: async ({ item, result }) => {
await clearWorkspaceFiles(item.id)
},
afterAll: async ({ summary }) => {
await deleteWorkspace(summary.experimentId)
},
})

每个钩子都可以是异步的。每个钩子都会接收 experimentId、mastra 实例和运行级的 signal,因此长时间运行的 setup 可以随实验一起被取消。逐项钩子还会接收 item。清理钩子会接收其所跟随的结果:afterEach 接收数据项的 result(含分数),afterAll 接收即将返回的 summary。

传给钩子的数据项会暴露 id、input、groundTruth 和 metadata。控制执行的字段(例如工具 mock 和 scorer 选择)不会暴露,因此钩子无法改变数据项的运行方式。

钩子失败
钩子失败的直接链接

每个钩子在抛出异常时的后果不同,取决于运行对该钩子的依赖程度:

钩子失败时
beforeAll实验失败,不运行任何数据项。
beforeEach该数据项以 EXPERIMENT_ITEM_BEFORE_EACH_FAILED 失败,其他数据项继续。
afterEach记录日志,数据项已记录的结果不变。
afterAll记录日志,返回的 summary 不变。

当 beforeAll 失败时,实验会被标记为失败,并且 experiment.run.finished 事件仍会在错误传播之前发出。

当 beforeEach 失败时,该数据项的目标及其 scorers 会被跳过,因为数据项的前置条件从未满足。该数据项的 afterEach 也会被跳过,理由是未完成的 setup 自行负责清理。

清理失败会记录日志而不会向上传播。到 afterEach 运行时,目标已经产生了真实结果;若因为清理不够整洁而丢弃它,就会丢失实验本要收集的数据。

afterAll 在每条退出路径上都会运行,包括实验失败、beforeAll 失败以及事件观察者失败的情况,因此出问题时清理不会被跳过。每次实验最多运行一次。

工具 mock
工具 mock的直接链接

当实验运行一个会调用有副作用工具的 agent 时,可将静态工具 mock 附加到各个数据集数据项,使运行具有确定性。在实验期间,被 mock 的工具会返回其声明的输出,而不会真正执行。数据项上没有 mock 的工具默认会真实运行。

Mock 会随数据集数据项一起存储和版本化。每个 mock 指定工具名、预期参数以及要返回的输出:

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' },
},
],
})

工具 mock 仅支持 agent 目标。

阻止未声明的工具
阻止未声明的工具的直接链接

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

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

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

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

数据项的值优先于实验的值。被拒绝的调用会在工具执行前以 TOOL_MOCK_NOT_DECLARED 失败。该失败不会重试,也不会加入 liveCalls。

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

参数采用严格匹配:对象键的顺序会被忽略,但数组顺序有意义。值不会强制转换为其他类型。只有当 agent 调用工具的参数与 mock 的 args 深度相等时,才会使用该 mock。

当一个数据项为同一工具和参数声明了多个 mock 时,它们会按顺序被消费:第一次调用使用第一个 mock,下一次调用使用第二个,依此类推。顺序按 (toolName, args) 分组跟踪,不同参数之间相互独立。

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

默认情况下,每个 mock 会严格依据其 args 匹配。设置 matchArgs: 'ignore' 可只按工具名匹配:不比较 mock 的 args,无论 agent 如何调用,都会使用该工具下一个未消费的 mock:

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

当工具参数杂乱或由模型生成时,这很有用。最常见的场景是 mock 子 agent 的响应。父 agent 将委托的子 agent 视为 agent-<name> 工具,其参数包括由 LLM 编写的 prompt 以及运行时注入的字段。mock agent-<name> 会返回预设响应,而不会运行子 agent 及其内部工具。当你从 trace 创建 mock 时,子 agent 委托调用会自动以 matchArgs: 'ignore' 派生。你可以改为 'strict' 以固定精确参数。

失败
失败的直接链接

当工具调用违反 mock 配置时,该数据项会失败:

  • TOOL_MOCK_MISMATCH:调用工具时传入的参数没有匹配的 mock。
  • TOOL_MOCK_EXHAUSTED:所有匹配的 mock 都已被消费。
  • TOOL_MOCK_NOT_DECLARED:该工具没有 mock,且生效的 unmockedToolPolicy 为 'deny'。

出现上述任一失败时,agent 运行会立即中止,因此模型无法继续调用任何工具,包括本会真实运行的未 mock、有副作用的工具。这些失败是确定性的,因此不会重试。声明了但从未使用的 mock 不会导致数据项失败,它们会作为未消费项报告。

mock 拦截处于活动状态时,agent 的工具会按顺序执行,因此重复的 (toolName, args) mock 会按提供方的调用顺序被消费。当数据项声明了 mock 或其生效的 unmockedToolPolicy 为 'deny' 时,拦截即处于活动状态。

诊断
诊断的直接链接

每个数据项结果都包含一个 toolMockReport,列出使用了哪些 mock,以及哪些工具调用在没有 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 中,编辑数据集数据项即可用 JSON 数组编写工具 mock,打开实验结果即可看到同样的报告。

限制
限制的直接链接

  • 被 mock 的调用没有工具 span。 被 mock 的调用在工具执行前就返回输出,因此不会创建工具 span。基于已存储 trace 的 trajectory scorers 可能因此看不到被 mock 的工具调用。回退到 agent 消息输出的 trajectory 提取仍能看到它们,因此 trajectory 评分可能因可观测性配置不同而有所差异。
  • 存储支持。 LibSQL、PostgreSQL、MongoDB 和 Spanner 适配器会持久化工具 mock 和工具 mock 报告,而 MySQL 适配器会拒绝携带任一者的写入。所有数据集存储适配器都会持久化 unmockedToolPolicy。

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

startExperiment() 会阻塞,直到每个数据项完成。对于长时间运行的数据集,可使用 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 })
if (!experiment) throw new Error('Experiment not found')

while (experiment.status === 'pending' || experiment.status === 'running') {
await new Promise(resolve => setTimeout(resolve, 5000))
experiment = await dataset.getExperiment({ experimentId })
if (!experiment) throw new Error('Experiment not found')
}

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

调用方驱动的实验
调用方驱动的实验的直接链接

startExperiment() 会让 Mastra 掌控整个运行过程。如果改由持久化编排器(例如 Temporal、Airflow 或自定义 worker 集群)掌控循环,请先创建实验,再按以下任一方式自行驱动:

  • 调用方驱动循环,Mastra 运行每个数据项。 先带着目标创建实验,再为每个数据项调用一次 runExperimentItem()。Mastra 执行目标、运行 scorers,然后持久化结果。
  • 调用方运行所有环节。 创建实验时不带目标,在你自己的基础设施上执行并评分数据项,再用 submitExperimentResult() 逐条写入结果。

两种方式都以 finalizeExperiment() 收尾,并且都写入与原生运行相同的表,因此 Studio 视图、比较和审查摘要都照常工作。

在服务端运行数据项
在服务端运行数据项的直接链接

使用 createExperiment() 创建实验,传入目标和 scorers。传入你自己的 id(例如某个 workflow 运行 id)可使创建具备幂等性:用相同 id 重试调用会返回已有实验,而不是失败。

src/mastra/experiments/caller-driven.ts
const dataset = await mastra.datasets.get({ id: 'translation-dataset-id' })

const { experimentId, totalItems, datasetVersion } = await dataset.createExperiment({
id: 'temporal-wf-run-42', // optional: reuse your workflow run id for idempotent creates
targetType: 'agent',
targetId: 'translation-agent',
scorers: ['accuracy'],
})

然后从你的编排器运行每个数据项。Mastra 会在固定的数据集版本上解析该数据项,并使用解析出的 scorers 执行目标。结果 upsert 以 (experimentId, itemId, attempt) 为键。重试的 activity 会收敛到同一行:

const { result, scores } = await dataset.runExperimentItem({
experimentId,
itemId: 'item-1',
})

重试和超时由你的编排器负责:每次 runExperimentItem 调用会恰好执行该数据项一次。Scorer 的解析优先级与原生运行相同:实验 scorers 优先于数据项 scorerIds,数据项 scorerIds 优先于数据集 scorerIds。

写入外部结果
写入外部结果的直接链接

如果你的 worker 自行执行并评分数据项,请创建实验时不带目标:

src/mastra/experiments/ingestion.ts
const { experimentId } = await dataset.createExperiment({
id: 'temporal-wf-run-42',
name: 'external-eval',
})

使用 submitExperimentResult() 为每个数据项提交一条结果。提交是按 (experimentId, itemId, attempt) 键控的 upsert。重试的 worker 会收敛到同一行,而不会重复结果:

await dataset.submitExperimentResult({
experimentId,
itemId: 'item-1',
output: { translation: 'Hola' },
scores: [{ scorerId: 'accuracy', score: 0.92, reason: 'Faithful translation' }],
})

input 和 groundTruth 默认取数据集数据项在实验固定版本中的值。内联 scores 会持久化到实验下的评分存储中,因此它们会与原生 scorer 运行一起出现在比较中。带目标创建的实验会拒绝 submitExperimentResult,以免两个写入者竞争同一批行。

收尾
收尾的直接链接

所有数据项完成后,调用 finalizeExperiment()。Mastra 会根据已持久化的行计算 succeededCount、failedCount 和 skippedCount,因此你的 worker 无需跟踪完成情况。收尾是幂等的。再次调用会返回已存储的记录:

const experiment = await dataset.finalizeExperiment({ experimentId })

console.log(experiment.status) // 'completed'
console.log(experiment.succeededCount) // items with at least one attempt that didn't error
console.log(experiment.failedCount) // items where every attempt errored
console.log(experiment.skippedCount) // items never submitted

同一生命周期也可通过 HTTP(POST /api/datasets/:datasetId/experiments 且 start: false、POST .../experiments/:experimentId/items/:itemId/run、POST .../experiments/:experimentId/results、POST .../experiments/:experimentId/finalize,参见服务器路由)以及通过 @mastra/client-js(参见 Datasets API)使用。

重试与幂等性约定
重试与幂等性约定的直接链接

调用方驱动的调用被设计为可积极重试(例如通过 Temporal 的重试策略)。具体保证如下:

  • 创建在 id 上幂等。 使用调用方提供的相同 id 再次调用 createExperiment 会返回已有实验。复用属于另一个数据集或目标不同的实验的 id 会以 EXPERIMENT_ID_CONFLICT(HTTP 409)失败。
  • 结果按 (experimentId, itemId, attempt) upsert。 重新提交相同的键会更新已有行,所有可变字段以最后写入为准。重试的 worker 不会产生重复数据。
  • attempt 用于区分有意试验与重试。 同一次运行的重试应复用相同的 attempt(默认 0)以便收敛。若要将某个数据项的重复试验记录为不同行,请分别使用 attempt: 0、attempt: 1 等提交。
  • 数据集版本在创建时固定。 提交会依据该版本可见的数据项进行校验,因此之后编辑或删除数据项不会影响进行中的实验。提交在固定版本中不可见的 itemId 会以 404 失败。
  • 收尾是幂等且终态的。 对已完成的实验再次收尾会返回已存储的记录,不会重新计算。收尾之后,后续提交会以 EXPERIMENT_ALREADY_FINALIZED(HTTP 409)被拒绝。
  • 计数由服务端按数据项计算。 收尾时,Mastra 按数据项汇总尝试:succeededCount(至少有一次尝试未出错)、failedCount(每次尝试都出错)、skippedCount(从未提交)。始终满足 succeededCount + failedCount + skippedCount === totalItems,调用方无需保留完成记录。尝试级行仍可通过 listExperimentResults 获取。

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

并发
并发的直接链接

控制并行运行的数据项数量(默认:5):

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

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

设置每个数据项的超时(毫秒)和重试次数:

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
})

重试采用指数退避。中止(abort)错误永不重试。

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

传入 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,
})

剩余数据项会在 summary 中被标记为跳过。

固定数据集版本
固定数据集版本的直接链接

针对数据集的特定快照运行:

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',
})
if (!experiment) throw new Error('Experiment not found')

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

观看 Mastra datasets and experiments workflow,了解数据集和实验如何帮助提升可靠性。

数据项级结果
数据项级结果的直接链接

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() 返回一个 ExperimentSummary,包含计数和逐项结果:

  • 当实验完成但部分数据项失败时,completedWithErrors 为 true。
  • 通过 signal 取消的数据项会出现在 skippedCount 中。

完整的参数和返回类型文档请参阅 startExperiment 参考。

相关内容
相关内容的直接链接