Évaluations avec Memory
Les Agents qui utilisent la Memory avec une portée thread, notamment la mémoire observationnelle, nécessitent un ID de thread lors de l’exécution. Lorsqu’une évaluation appelle l’Agent sans cet ID, le message suivant apparaît :
ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList.
Cette page présente les trois méthodes permettant d’exécuter des évaluations Mastra sur des Agents dotés de Memory, les fonctionnalités prises en charge par chaque méthode et les critères de choix. Un exemple complet et exécutable des trois approches est disponible dans examples/evals-with-memory.
Choisir l’approche appropriéeLien direct vers Choisir l’approche appropriée
| Objectif | Approche |
|---|---|
| Une conversation partagée entre tous les éléments | runEvals avec un targetOptions.memory global |
| Un thread indépendant par élément dans une boucle CI ciblée | runEvals pour chaque élément |
Des threads par élément pilotés par un Dataset stocké | dataset.startExperiment avec une tâche inline |
Préremplir RequestContext avec MastraMemory ne constitue pas une méthode prise en charge pour transmettre la Memory à un Agent. La résolution du thread lit args.memory.thread, tandis que RequestContext.MastraMemory est renseigné par prepare-memory-step après que l’Agent a déjà résolu son thread.
Thread partagé avec runEvalsLien direct vers shared-thread-with-runevals
runEvals accepte targetOptions, qui est transmis à agent.generate(). Le passage de memory: { thread, resource } exécute chaque élément de données sur le même thread, ce qui permet de tester le rappel au fil d’une conversation à plusieurs tours.
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 est global pour chaque appel. Il n’existe actuellement aucune surcharge par élément sur RunEvalsDataItem.
Threads par élément avec runEvalsLien direct vers per-item-threads-with-runevals
Lorsque chaque élément de données nécessite son propre thread (cas courant en CI), appelez runEvals une fois par élément avec un targetOptions.memory unique, puis agrégez vous-même les scores.
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
Créez le thread avant d’exécuter l’évaluation. Avec la portée thread, la mémoire observationnelle lit un enregistrement qui doit déjà exister.
Expériences de Dataset avec une tâche inlineLien direct vers Expériences de Dataset avec une tâche inline
dataset.startExperiment({ target: agent }) ne transmet pas d’option memory à l’Agent, mais uniquement requestContext. Pour exécuter un Dataset stocké sur un Agent doté de Memory, utilisez une fonction task inline et placez { threadId, resourceId } dans les metadata de chaque élément. Le pipeline des scorers s’exécute toujours normalement.
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
},
})
La fonction task inline reçoit les metadata de l’élément ; chaque ligne peut ainsi piloter son propre thread sans modifier l’Agent ni aucun scorer. Consultez la référence de runEvals et la référence des Datasets pour découvrir la configuration complète.