Scorers personnalisés
Mastra fournit une fonction de fabrique createScorer unifiée qui permet de créer une logique d’évaluation personnalisée en utilisant, à chaque étape, soit des fonctions JavaScript, soit des objets de prompt basés sur un LLM. Cette flexibilité vous permet de choisir l’approche la mieux adaptée à chaque partie de votre pipeline d’évaluation.
Le pipeline en quatre étapesLien direct vers Le pipeline en quatre étapes
Tous les scorers de Mastra suivent un pipeline d’évaluation cohérent en quatre étapes :
- preprocess (facultative) : prépare ou transforme les données d’entrée et de sortie
- analyze (facultative) : effectue l’analyse d’évaluation et recueille des informations
- generateScore (obligatoire) : convertit l’analyse en score numérique
- generateReason (facultative) : génère des explications lisibles par un humain
Chaque étape peut utiliser soit des fonctions, soit des objets de prompt (évaluation basée sur un LLM), ce qui vous permet de combiner des algorithmes déterministes avec le jugement de l’IA selon vos besoins.
Fonctions ou objets de promptLien direct vers Fonctions ou objets de prompt
Les fonctions utilisent JavaScript pour appliquer une logique déterministe. Elles conviennent parfaitement aux cas suivants :
- Évaluations algorithmiques reposant sur des critères clairs
- Scénarios où les performances sont critiques
- Intégration à des bibliothèques existantes
- Résultats cohérents et reproductibles
Les objets de prompt utilisent des LLM comme juges de l’évaluation. Ils conviennent parfaitement aux cas suivants :
- Évaluations subjectives nécessitant un jugement comparable à celui d’un humain
- Critères complexes difficiles à exprimer sous forme d’algorithme
- Tâches de compréhension du langage naturel
- Évaluation nuancée du contexte
Définition d’un « objet de prompt » : au lieu d’une fonction, l’étape est un objet doté de description + createPrompt (ainsi que de outputSchema pour preprocess/analyze). Cet objet indique à Mastra d’exécuter le LLM juge pour cette étape et de stocker la sortie structurée dans results.<step>StepResult.
Vous pouvez combiner ces approches au sein d’un même scorer. Par exemple, vous pouvez utiliser une fonction pour prétraiter les données et un LLM pour en analyser la qualité.
Initialiser un scorerLien direct vers Initialiser un scorer
Chaque scorer commence par la fonction de fabrique createScorer, qui nécessite un identifiant et une description, et accepte facultativement une spécification de type et une configuration du juge.
import { createScorer } from '@mastra/core/evals';
const glutenCheckerScorer = createScorer({
id: 'gluten-checker',
description: 'Check if recipes contain gluten ingredients',
judge: { // Optional: for prompt object steps
model: 'openai/gpt-5.6-sol',
instructions: 'You are a Chef that identifies if recipes contain gluten.'
}
})
// Chain step methods here
.preprocess(...)
.analyze(...)
.generateScore(...)
.generateReason(...)
La configuration du juge n’est nécessaire que si vous prévoyez d’utiliser des objets de prompt dans au moins une étape. Chaque étape peut remplacer cette configuration par défaut par ses propres paramètres de juge.
Si toutes les étapes reposent sur des fonctions, le juge n’est jamais appelé et ne produit aucune sortie. Pour obtenir la sortie du LLM, définissez au moins une étape sous forme d’objet de prompt et lisez le résultat de l’étape correspondante (par exemple, results.analyzeStepResult).
Exemple minimal de juge (objet de prompt)Lien direct vers Exemple minimal de juge (objet de prompt)
Cet exemple utilise un objet de prompt dans analyze : le juge est donc exécuté et sa sortie structurée est disponible dans results.analyzeStepResult.
import { createScorer } from '@mastra/core/evals'
import { z } from 'zod'
const quoteSourcesScorer = createScorer({
id: 'quote-sources',
description: 'Check if the response includes sources',
judge: {
model: 'openai/gpt-5-mini',
instructions: 'You are a strict evaluator.',
},
})
.analyze({
description: 'Detect whether sources are present',
outputSchema: z.object({
hasSources: z.boolean(),
sources: z.array(z.string()),
}),
createPrompt: ({ run }) => `
Does the response contain sources? Extract them as a list.
Response:
${run.output}
`,
})
.generateScore(({ results }) => (results.analyzeStepResult.hasSources ? 1 : 0))
// Run the scorer and inspect judge output
const result = await quoteSourcesScorer.run({
input: 'What is the capital of France?',
output: 'Paris is the capital of France [1]. Source: [1] Wikipedia',
})
console.log(result.score) // 1
console.log(result.analyzeStepResult) // { hasSources: true, sources: ["Wikipedia"] }
Type Agent pour l’évaluation d’un AgentLien direct vers Type Agent pour l’évaluation d’un Agent
Pour garantir la sûreté des types et la compatibilité avec la notation d’un Agent en direct comme avec celle des traces, utilisez type: 'agent' lorsque vous créez des scorers destinés à l’évaluation d’un Agent. Vous pouvez ainsi utiliser le même scorer pour un Agent et pour noter des traces :
const myScorer = createScorer({
type: 'agent', // Automatically handles agent input/output types
}).generateScore(({ run, results }) => {
// run.output is automatically typed as ScorerRunOutputForAgent
// run.input is automatically typed as ScorerRunInputForAgent
})
Détail des étapesLien direct vers Détail des étapes
Étape preprocess (facultative)Lien direct vers Étape preprocess (facultative)
Prépare les données d’entrée et de sortie lorsque vous devez extraire des éléments précis, filtrer du contenu ou transformer des structures de données complexes.
Fonctions : ({ run, results }) => any
const glutenCheckerScorer = createScorer(...)
.preprocess(({ run }) => {
// Extract and clean recipe text
const recipeText = run.output.text.toLowerCase();
const wordCount = recipeText.split(' ').length;
return {
recipeText,
wordCount,
hasCommonGlutenWords: /flour|wheat|bread|pasta/.test(recipeText)
};
})
Objets de prompt : utilisez description, outputSchema et createPrompt pour structurer un prétraitement basé sur un LLM.
const glutenCheckerScorer = createScorer(...)
.preprocess({
description: 'Extract ingredients from the recipe',
outputSchema: z.object({
ingredients: z.array(z.string()),
cookingMethods: z.array(z.string())
}),
createPrompt: ({ run }) => `
Extract all ingredients and cooking methods from this recipe:
${run.output.text}
Return JSON with ingredients and cookingMethods arrays.
`
})
Flux de données : les résultats sont accessibles aux étapes suivantes dans results.preprocessStepResult.
Étape analyze (facultative)Lien direct vers Étape analyze (facultative)
Effectue l’analyse principale de l’évaluation et recueille les informations qui guideront la décision de notation.
Fonctions : ({ run, results }) => any
const glutenCheckerScorer = createScorer({...})
.preprocess(...)
.analyze(({ run, results }) => {
const { recipeText, hasCommonGlutenWords } = results.preprocessStepResult;
// Simple gluten detection algorithm
const glutenKeywords = ['wheat', 'flour', 'barley', 'rye', 'bread'];
const foundGlutenWords = glutenKeywords.filter(word =>
recipeText.includes(word)
);
return {
isGlutenFree: foundGlutenWords.length === 0,
detectedGlutenSources: foundGlutenWords,
confidence: hasCommonGlutenWords ? 0.9 : 0.7
};
})
Objets de prompt : utilisez description, outputSchema et createPrompt pour effectuer une analyse basée sur un LLM.
const glutenCheckerScorer = createScorer({...})
.preprocess(...)
.analyze({
description: 'Analyze recipe for gluten content',
outputSchema: z.object({
isGlutenFree: z.boolean(),
glutenSources: z.array(z.string()),
confidence: z.number().min(0).max(1)
}),
createPrompt: ({ run, results }) => `
Analyze this recipe for gluten content:
"${results.preprocessStepResult.recipeText}"
Look for wheat, barley, rye, and hidden sources like soy sauce.
Return JSON with isGlutenFree, glutenSources array, and confidence (0-1).
`
})
Flux de données : les résultats sont accessibles aux étapes suivantes dans results.analyzeStepResult.
Étape generateScore (obligatoire)Lien direct vers generatescore-step-required
Convertit les résultats de l’analyse en score numérique. Il s’agit de la seule étape obligatoire du pipeline.
Fonctions : ({ run, results }) => number
const glutenCheckerScorer = createScorer({...})
.preprocess(...)
.analyze(...)
.generateScore(({ results }) => {
const { isGlutenFree, confidence } = results.analyzeStepResult;
// Return 1 for gluten-free, 0 for contains gluten
// Weight by confidence level
return isGlutenFree ? confidence : 0;
})
Objets de prompt : consultez la référence de l’API createScorer pour savoir comment utiliser des objets de prompt avec generateScore, notamment la fonction calculateScore obligatoire.
Flux de données : le score est accessible à generateReason par l’intermédiaire du paramètre score.
Étape generateReason (facultative)Lien direct vers generatereason-step-optional
Génère des explications lisibles par un humain concernant le score, utiles pour le débogage, la transparence ou les retours destinés aux utilisateurs.
Fonctions : ({ run, results, score }) => string
const glutenCheckerScorer = createScorer({...})
.preprocess(...)
.analyze(...)
.generateScore(...)
.generateReason(({ results, score }) => {
const { isGlutenFree, glutenSources } = results.analyzeStepResult;
if (isGlutenFree) {
return `Score: ${score}. This recipe is gluten-free with no harmful ingredients detected.`;
} else {
return `Score: ${score}. Contains gluten from: ${glutenSources.join(', ')}`;
}
})
Objets de prompt : utilisez description et createPrompt pour générer des explications à l’aide d’un LLM.
const glutenCheckerScorer = createScorer({...})
.preprocess(...)
.analyze(...)
.generateScore(...)
.generateReason({
description: 'Explain the gluten assessment',
createPrompt: ({ results, score }) => `
Explain why this recipe received a score of ${score}.
Analysis: ${JSON.stringify(results.analyzeStepResult)}
Provide a clear explanation for someone with dietary restrictions.
`
})
Filtrage des entréesLien direct vers Filtrage des entrées
Les conversations d’un Agent peuvent contenir des centaines de messages avec des appels de Tool et des parties de données, ainsi que des métadonnées système. La plupart des scorers n’ont besoin que d’une partie de ces données. L’option prepareRun transforme les données d’exécution avant le lancement du pipeline du scorer, ce qui réduit le bruit et permet aux scorers de rester concentrés sur les informations pertinentes.
Filtrage déclaratif avec filterRun()Lien direct vers declarative-filtering-with-filterrun
L’utilitaire filterRun() crée une fonction prepareRun à partir d’options déclaratives :
import { createScorer, filterRun } from '@mastra/core/evals'
const toolScorer = createScorer({
id: 'tool-quality',
description: 'Evaluates tool usage quality',
type: 'agent',
prepareRun: filterRun({
partTypes: ['tool-invocation', 'text'],
maxRememberedMessages: 20,
}),
}).generateScore(({ run }) => {
// run.input.rememberedMessages has only tool and text messages, max 20
return 1
})
Les options courantes comprennent :
partTypes: conserve uniquement les messages dont le type de partie correspond (par exemple,'tool-invocation','text','reasoning')toolNames: conserve uniquement les messages qui font intervenir certains Tools (par exemple,['write_file', 'execute_command'])maxRememberedMessages: limite la taille de la fenêtre de contextedropRequestContext,dropGroundTruth,dropExpectedTrajectory: supprime les champs inutilisés
Consultez la référence de filterRun() pour obtenir la liste complète des options.
Fonctions prepareRun personnaliséesLien direct vers custom-preparerun-functions
Pour une logique qui n’est pas prise en charge par filterRun(), écrivez directement une fonction prepareRun :
import { createScorer } from '@mastra/core/evals'
const customScorer = createScorer({
id: 'recent-output',
description: 'Scores only the last response',
type: 'agent',
prepareRun: (run) => ({
...run,
output: run.output.slice(-1), // Keep only the last message
requestContext: undefined,
}),
})
.generateScore(({ run }) => {
return run.output.length > 0 ? 1 : 0
})
La fonction prepareRun peut également être asynchrone.
filterRun() ne filtre jamais systemMessages ni taggedSystemMessages. Ces champs contiennent les instructions de l’Agent et constituent un contexte essentiel pour la notation.
Exemple : créer un scorer personnaliséLien direct vers Exemple : créer un scorer personnalisé
Dans Mastra, un scorer personnalisé utilise createScorer avec quatre composants principaux :
Ensemble, ces composants vous permettent de définir une logique d’évaluation personnalisée qui utilise des LLM comme juges. Consultez la documentation de createScorer pour connaître l’ensemble de l’API et des options de configuration.
import { createScorer } from '@mastra/core/evals'
import { z } from 'zod'
export const GLUTEN_INSTRUCTIONS = `You are a Chef that identifies if recipes contain gluten.`
export const generateGlutenPrompt = ({
output,
}: {
output: string
}) => `Check if this recipe is gluten-free.
Check for:
- Wheat
- Barley
- Rye
- Common sources like flour, pasta, bread
Example with gluten:
"Mix flour and water to make dough"
Response: {
"isGlutenFree": false,
"glutenSources": ["flour"]
}
Example gluten-free:
"Mix rice, beans, and vegetables"
Response: {
"isGlutenFree": true,
"glutenSources": []
}
Recipe to analyze:
${output}
Return your response in this format:
{
"isGlutenFree": boolean,
"glutenSources": ["list ingredients containing gluten"]
}`
export const generateReasonPrompt = ({
isGlutenFree,
glutenSources,
}: {
isGlutenFree: boolean
glutenSources: string[]
}) => `Explain why this recipe is${isGlutenFree ? '' : ' not'} gluten-free.
${glutenSources.length > 0 ? `Sources of gluten: ${glutenSources.join(', ')}` : 'No gluten-containing ingredients found'}
Return your response in this format:
"This recipe is [gluten-free/contains gluten] because [explanation]"`
export const glutenCheckerScorer = createScorer({
id: 'gluten-checker',
description: 'Check if the output contains any gluten',
judge: {
model: 'openai/gpt-5-mini',
instructions: GLUTEN_INSTRUCTIONS,
},
})
.analyze({
description: 'Analyze the output for gluten',
outputSchema: z.object({
isGlutenFree: z.boolean(),
glutenSources: z.array(z.string()),
}),
createPrompt: ({ run }) => {
const { output } = run
return generateGlutenPrompt({ output: output.text })
},
})
.generateScore(({ results }) => {
return results.analyzeStepResult.isGlutenFree ? 1 : 0
})
.generateReason({
description: 'Generate a reason for the score',
createPrompt: ({ results }) => {
return generateReasonPrompt({
glutenSources: results.analyzeStepResult.glutenSources,
isGlutenFree: results.analyzeStepResult.isGlutenFree,
})
},
})
Configuration du jugeLien direct vers Configuration du juge
Configure le modèle LLM et définit son rôle d’expert du domaine.
judge: {
model: 'openai/gpt-5-mini',
instructions: GLUTEN_INSTRUCTIONS,
}
Étape d’analyseLien direct vers Étape d’analyse
Définit la manière dont le LLM doit analyser l’entrée et la sortie structurée qu’il doit renvoyer.
.analyze({
description: 'Analyze the output for gluten',
outputSchema: z.object({
isGlutenFree: z.boolean(),
glutenSources: z.array(z.string()),
}),
createPrompt: ({ run }) => {
const { output } = run;
return generateGlutenPrompt({ output: output.text });
},
})
L’étape d’analyse utilise un objet de prompt pour :
- Fournir une description claire de la tâche d’analyse
- Définir la structure de sortie attendue à l’aide d’un schéma JSON standard (à la fois un résultat booléen et une liste de sources de gluten)
- Générer à l’exécution des prompts basés sur le contenu de l’entrée
Génération du scoreLien direct vers Génération du score
Convertit l’analyse structurée du LLM en score numérique.
.generateScore(({ results }) => {
return results.analyzeStepResult.isGlutenFree ? 1 : 0;
})
La fonction de génération du score utilise les résultats de l’analyse et applique une logique métier pour produire un score. Dans ce cas, le LLM détermine directement si la recette est sans gluten. Nous utilisons donc ce résultat booléen : 1 si la recette est sans gluten, 0 si elle en contient.
Génération de la justificationLien direct vers Génération de la justification
Fournit des explications lisibles par un humain concernant le score au moyen d’un autre appel au LLM.
.generateReason({
description: 'Generate a reason for the score',
createPrompt: ({ results }) => {
return generateReasonPrompt({
glutenSources: results.analyzeStepResult.glutenSources,
isGlutenFree: results.analyzeStepResult.isGlutenFree,
});
},
})
L’étape de génération de la justification crée des explications qui aident les utilisateurs à comprendre pourquoi un score a été attribué. Elle utilise à la fois le résultat booléen et les sources précises de gluten détectées lors de l’étape d’analyse.
Exemple avec un score élevé pour l’absence de glutenLien direct vers Exemple avec un score élevé pour l’absence de gluten
const result = await glutenCheckerScorer.run({
input: [{ role: 'user', content: 'Mix rice, beans, and vegetables' }],
output: { text: 'Mix rice, beans, and vegetables' },
})
console.log('Score:', result.score)
console.log('Gluten sources:', result.analyzeStepResult.glutenSources)
console.log('Reason:', result.reason)
Sortie avec un score élevé pour l’absence de glutenLien direct vers Sortie avec un score élevé pour l’absence de gluten
{
score: 1,
analyzeStepResult: {
isGlutenFree: true,
glutenSources: []
},
reason: 'This recipe is gluten-free because rice, beans, and vegetables are naturally gluten-free ingredients that are safe for people with celiac disease.'
}
Exemple avec présence de glutenLien direct vers Exemple avec présence de gluten
const result = await glutenCheckerScorer.run({
input: [{ role: 'user', content: 'Mix flour and water to make dough' }],
output: { text: 'Mix flour and water to make dough' },
})
console.log('Score:', result.score)
console.log('Gluten sources:', result.analyzeStepResult.glutenSources)
console.log('Reason:', result.reason)
Sortie avec présence de glutenLien direct vers Sortie avec présence de gluten
{
score: 0,
analyzeStepResult: {
isGlutenFree: false,
glutenSources: ['flour']
},
reason: 'This recipe is not gluten-free because it contains flour. Regular flour is made from wheat and contains gluten, making it unsafe for people with celiac disease or gluten sensitivity.'
}
Exemple avec un faible score pour l’absence de glutenLien direct vers Exemple avec un faible score pour l’absence de gluten
const result = await glutenCheckerScorer.run({
input: [{ role: 'user', content: 'Add soy sauce and noodles' }],
output: { text: 'Add soy sauce and noodles' },
})
console.log('Score:', result.score)
console.log('Gluten sources:', result.analyzeStepResult.glutenSources)
console.log('Reason:', result.reason)
Sortie avec un faible score pour l’absence de glutenLien direct vers Sortie avec un faible score pour l’absence de gluten
{
score: 0,
analyzeStepResult: {
isGlutenFree: false,
glutenSources: ['soy sauce', 'noodles']
},
reason: 'This recipe is not gluten-free because it contains soy sauce, noodles. Regular soy sauce contains wheat and most noodles are made from wheat flour, both of which contain gluten and are unsafe for people with gluten sensitivity.'
}
Exemples et ressources :
- Référence de l’API createScorer : documentation technique complète
- Code source des scorers intégrés : implémentations réelles à consulter comme exemples