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

Evals with memory

在 thread 作用域中使用内存(包括观察式内存)的 agents 在运行时需要线程 ID。当 eval 在没有线程 ID 的情况下调用 agent 时,你会看到:

ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList.

请根据你希望在评估项之间共享内存的方式选择方法。可运行的示例请参阅 examples/evals-with-memory。

何时使用哪种方法
何时使用哪种方法的直接链接

目标方法
所有项共享一次对话runEvals 配合全局 targetOptions.memory
每项使用独立线程,聚焦的 CI 循环逐项调用 runEvals
在存储的 Dataset 中为每项创建新线程dataset.startExperiment 配合已注册的 agent
在存储的 Dataset 中为每项显式选择线程dataset.startExperiment 配合内联任务

用 MastraMemory 预先填充 RequestContext 不是将内存驱动到 agent 的受支持方式。线程解析读取的是 args.memory.thread,而 RequestContext.MastraMemory 是在 agent 已经解析线程之后由 prepare-memory-step 填充的。

使用 runEvals 的共享线程
shared-thread-with-runevals的直接链接

runEvals 接受 targetOptions,它会被转发给 agent.generate()。传入 memory: { thread, resource } 会让每个数据项针对同一线程运行,适用于测试多轮对话中的回忆。

src/mastra/agents/support-agent.test.ts
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 的逐项线程
per-item-threads-with-runevals的直接链接

当每个数据项需要自己的线程时(常见的 CI 形式),请为每项调用一次 runEvals,使用唯一的 targetOptions.memory,并自行聚合得分。

src/mastra/agents/support-agent.test.ts
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
备注

请在运行 eval 之前创建线程。thread 作用域中的观察式内存会从一条必须已存在的记录中读取。

使用内联任务的数据集实验
使用内联任务的数据集实验的直接链接

dataset.startExperiment() 接受用于已注册目标的 targetType 和 targetId,或内联 task 函数。对于已注册的启用内存的 agent,运行器默认会为每项创建新线程。资源隔离和请求上下文选项请参阅启用内存的实验目标。

要通过 agent.generate() 显式选择每一项的线程,请使用内联 task,并将 { threadId, resourceId } 存储在每个项的 metadata 中。scorer 流水线仍会照常运行。

src/mastra/evals/dataset-experiment.ts
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,因此每一行都可以驱动自己的线程,而无需更改 agent 或任何 scorer。完整配置请参阅 runEvals reference 和 Dataset reference。

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

On this page