Aller au contenu principal

É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ée
Lien direct vers Choisir l’approche appropriée

ObjectifApproche
Une conversation partagée entre tous les élémentsrunEvals avec un targetOptions.memory global
Un thread indépendant par élément dans une boucle CI cibléerunEvals 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 runEvals
Lien 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.

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 est global pour chaque appel. Il n’existe actuellement aucune surcharge par élément sur RunEvalsDataItem.

Threads par élément avec runEvals
Lien 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.

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
remarque

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 inline
Lien 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.

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

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.