结合 Memory 使用 Evals
使用 thread 作用域 Memory(包括 Observational Memory)的 Agent 在运行时需要 thread ID。当 Evals 调用 Agent 时未提供该 ID,你会看到:
ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList.
本页介绍针对启用了 Memory 的 Agent 运行 Mastra Evals 的三种可用模式、每种路径支持的功能,以及如何选择。三种方式的完整可运行复现均位于 examples/evals-with-memory。
如何选择使用方式如何选择使用方式的直接链接
| 目标 | 方式 |
|---|---|
| 所有项目共享一次对话 | 使用 runEvals 和全局 targetOptions.memory |
| 每个项目使用独立 thread,专注于 CI 循环 | 对每个项目运行 runEvals |
由已存储 Dataset 驱动的逐项目 thread | 使用内联任务的 dataset.startExperiment |
预先填充 RequestContext 并设置 MastraMemory 并不是将 Memory 传入 Agent 的受支持方式。解析 thread 时会读取 args.memory.thread;而 RequestContext.MastraMemory 是在 Agent 已经解析完 thread 后,由 prepare-memory-step 填充的。
使用 runEvals 共享 threadshared-thread-with-runevals的直接链接
runEvals 接受 targetOptions,并将其转发给 agent.generate()。传入 memory: { thread, resource } 会让所有数据项目针对同一个 thread 运行,适用于测试多轮对话中的召回能力。
import { runEvals } from '@mastra/core/evals'
import { supportAgent } from './support-agent'
import { recallScorer } from '../scorers/recall-scorer'
const memory = await supportAgent.getMemory()
await memory!.createThread({ threadId: 'eval-thread', resourceId: 'ci-user' })
const result = await runEvals({
target: supportAgent,
scorers: [recallScorer],
targetOptions: {
memory: { thread: 'eval-thread', resource: 'ci-user' },
},
data: [
{ input: 'My order number is 12345' },
{ input: 'What is my order number?', groundTruth: '12345' },
],
})
targetOptions 对每次调用而言是全局的。目前 RunEvalsDataItem 不支持逐项目覆盖。
使用 runEvals 为每个项目创建 threadper-item-threads-with-runevals的直接链接
当每个数据项目都需要自己的 thread 时(常见 CI 形式),请针对每个项目调用一次 runEvals,使用唯一的 targetOptions.memory,并自行聚合分数。
import { randomUUID } from 'node:crypto'
import { runEvals } from '@mastra/core/evals'
import { supportAgent } from './support-agent'
import { recallScorer } from '../scorers/recall-scorer'
const memory = await supportAgent.getMemory()
const resourceId = 'ci-user'
const items = [
{ input: 'Cats are mammals', groundTruth: 'mammals' },
{ input: 'Dogs are mammals too', groundTruth: 'mammals' },
]
// `runEvals` returns `{ scores: Record<string, number>; summary: { totalItems } }`.
const scores: number[] = []
for (const item of items) {
const threadId = `eval-${randomUUID()}`
await memory!.createThread({ threadId, resourceId, title: item.input })
const result = await runEvals({
target: supportAgent,
scorers: [recallScorer],
targetOptions: { memory: { thread: threadId, resource: resourceId } },
data: [item],
})
scores.push(result.scores[recallScorer.id])
}
const average = scores.reduce((a, b) => a + b, 0) / scores.length
请在运行 Evals 前创建 thread。thread 作用域中的 Observational Memory 会从必须已存在的记录中读取数据。
使用内联任务运行 Dataset 实验使用内联任务运行 Dataset 实验的直接链接
dataset.startExperiment({ target: agent }) 不会将 memory 选项转发给 Agent,只会转发 requestContext。要针对启用了 Memory 的 Agent 运行已存储的 Dataset,请使用内联 task 函数,并将 { threadId, resourceId } 存入每个项目的 metadata。Scorer Pipeline 仍会照常运行。
import { randomUUID } from 'node:crypto'
import { mastra } from '../index'
import { supportAgent } from '../agents/support-agent'
import { recallScorer } from '../scorers/recall-scorer'
const memory = await supportAgent.getMemory()
const resourceId = 'ci-user'
const items = [
{ input: 'Cats are mammals', groundTruth: 'mammals', thread: `ds-${randomUUID()}` },
{ input: 'Dogs are mammals too', groundTruth: 'mammals', thread: `ds-${randomUUID()}` },
]
for (const it of items) {
await memory!.createThread({ threadId: it.thread, resourceId, title: it.input })
}
const dataset = await mastra.datasets.create({
name: 'support-recall',
description: 'Per-item memory via inline task + item metadata',
})
await dataset.addItems({
items: items.map(it => ({
input: it.input,
groundTruth: it.groundTruth,
metadata: { threadId: it.thread, resourceId },
})),
})
const summary = await dataset.startExperiment({
scorers: [recallScorer],
task: async ({ input, metadata }) => {
const { threadId, resourceId: rid } = (metadata ?? {}) as {
threadId: string
resourceId: string
}
const result = await supportAgent.generate(input as string, {
memory: { thread: threadId, resource: rid },
})
return result.text
},
})
内联 task 会接收项目的 metadata,因此每一行都能驱动自己的 thread,而不必更改 Agent 或任何 Scorer。完整配置请访问 runEvals Reference 和 Dataset Reference。