跳到主要内容

自定义 Scorer

Mastra 提供统一的 createScorer 工厂,允许你在每个 Step 中使用 JavaScript 函数或基于 LLM 的提示词对象构建自定义评估逻辑。这种灵活性让你可以为评估 Pipeline 的每一部分选择最合适的方式。

四步 Pipeline
四步 Pipeline的直接链接

Mastra 中的所有 Scorer 都遵循一致的四步评估 Pipeline:

  1. preprocess(可选):准备或转换输入/输出数据
  2. analyze(可选):执行评估分析并收集信息
  3. generateScore(必需):将分析转换为数值分数
  4. generateReason(可选):生成便于阅读的解释

每个 Step 都可以使用函数提示词对象(基于 LLM 的评估),让你能够根据需要将确定性算法与 AI 判断结合起来。

函数与提示词对象
函数与提示词对象的直接链接

函数使用 JavaScript 实现确定性逻辑,适合:

  • 具有明确标准的算法评估
  • 对性能要求较高的场景
  • 与现有库集成
  • 一致、可复现的结果

提示词对象使用 LLM 作为评估 Judge,适合:

  • 需要类似人工判断的主观评估
  • 难以通过算法代码表示的复杂标准
  • 自然语言理解任务
  • 细致入微的上下文评估

“提示词对象”的含义: Step 不是函数,而是一个包含 description + createPrompt 的对象(还提供 outputSchema,供 preprocess/analyze 使用)。该对象会指示 Mastra 为此 Step 运行 Judge LLM,并将结构化输出存储在 results.<step>StepResult 中。

可以在同一个 Scorer 中混合使用不同方式,例如使用函数预处理数据,再使用 LLM 分析质量。

初始化 Scorer
初始化 Scorer的直接链接

每个 Scorer 都从 createScorer 工厂函数开始。该函数要求提供 ID 和描述,还可接受类型规范及 Judge 配置。

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(...)

只有计划在任一 Step 中使用提示词对象时,才需要配置 Judge。各个 Step 可以使用自己的 Judge 设置覆盖此默认配置。

如果所有 Step 都基于函数,则不会调用 Judge,也不会产生 Judge 输出。要查看 LLM 输出,请将至少一个 Step 定义为提示词对象,并读取相应的 Step 结果(例如 results.analyzeStepResult)。

最小 Judge 示例(提示词对象)
最小 Judge 示例(提示词对象)的直接链接

以下示例在 analyze 中使用提示词对象,因此会运行 Judge,其结构化输出可在 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"] }

用于评估 Agent 的 Agent 类型
用于评估 Agent 的 Agent 类型的直接链接

为获得类型安全,并同时兼容实时 Agent 评分和 Trace 评分,在创建用于评估 Agent 的 Scorer 时,请使用 type: 'agent'。这样,同一个 Scorer 既可用于 Agent,也可用于 Trace 评分:

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

分步说明
分步说明的直接链接

preprocess Step(可选)
preprocess Step(可选)的直接链接

当需要提取特定元素、筛选内容或转换复杂数据结构时,用于准备输入/输出数据。

函数: ({ 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)
};
})

提示词对象: 使用 descriptionoutputSchemacreatePrompt 构建基于 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.
`
})

数据流: 结果可通过 results.preprocessStepResult 供后续 Step 使用

analyze Step(可选)
analyze Step(可选)的直接链接

执行核心评估分析,收集将用于评分决策的信息。

函数: ({ 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
};
})

提示词对象: 使用 descriptionoutputSchemacreatePrompt 执行基于 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).
`
})

数据流: 结果可通过 results.analyzeStepResult 供后续 Step 使用

generateScore Step(必需)
generatescore-step-required的直接链接

将分析结果转换为数值分数。这是 Pipeline 中唯一必需的 Step。

函数: ({ 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;
})

提示词对象: 请参阅 createScorer API Reference,了解在 generateScore 中使用提示词对象的详细信息,包括必需的 calculateScore 函数。

数据流: 分数可作为 score 参数供 generateReason 使用

generateReason Step(可选)
generatereason-step-optional的直接链接

为分数生成便于阅读的解释,可用于调试、提高透明度或提供用户反馈。

函数: ({ 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(', ')}`;
}
})

提示词对象: 使用 descriptioncreatePrompt 生成由 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.
`
})

输入筛选
输入筛选的直接链接

Agent 对话可能包含数百条带有 Tool 调用和数据部分的消息,以及系统元数据。大多数 Scorer 只需要这些数据的一个子集。prepareRun 选项会在 Scorer Pipeline 执行前转换运行数据,减少干扰并让 Scorer 保持专注。

使用 filterRun() 进行声明式筛选
declarative-filtering-with-filterrun的直接链接

filterRun() 工具会根据声明式选项创建 prepareRun 函数:

src/mastra/scorers/tool-scorer.ts
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
})

常用选项包括:

  • partTypes:只保留具有匹配部分类型的消息(例如 'tool-invocation''text''reasoning'
  • toolNames:只保留涉及特定 Tool 的消息(例如 ['write_file', 'execute_command']
  • maxRememberedMessages:限制上下文窗口大小
  • dropRequestContextdropGroundTruthdropExpectedTrajectory:移除未使用的字段

完整选项列表请参阅 filterRun() Reference

自定义 prepareRun 函数
custom-preparerun-functions的直接链接

对于 filterRun() 未涵盖的逻辑,可直接编写 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
})

prepareRun 函数也可以是异步函数。

始终保留系统消息

filterRun() 从不筛选 systemMessagestaggedSystemMessages。这些消息包含 Agent 指令,是评分所需的关键上下文。

示例:创建自定义 Scorer
示例:创建自定义 Scorer的直接链接

Mastra 中的自定义 Scorer 使用 createScorer,并包含四个核心组件:

  1. Judge 配置
  2. 分析 Step
  3. 生成分数
  4. 生成原因

这些组件共同支持使用 LLM 作为 Judge 来定义自定义评估逻辑。完整 API 和配置选项请访问 createScorer

src/mastra/scorers/gluten-checker.ts
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,
})
},
})

Judge 配置
Judge 配置的直接链接

设置 LLM 模型,并将其角色定义为领域专家。

judge: {
model: 'openai/gpt-5-mini',
instructions: GLUTEN_INSTRUCTIONS,
}

分析 Step
分析 Step的直接链接

定义 LLM 应如何分析输入,以及应返回怎样的结构化输出。

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

分析 Step 使用提示词对象来:

  • 清楚描述分析任务
  • 使用标准 JSON Schema 定义预期输出结构(包括布尔结果和麸质来源列表)
  • 根据输入内容生成运行时提示词

生成分数
生成分数的直接链接

将 LLM 的结构化分析转换为数值分数。

.generateScore(({ results }) => {
return results.analyzeStepResult.isGlutenFree ? 1 : 0;
})

分数生成函数会获取分析结果,并应用业务逻辑生成分数。在此例中,LLM 会直接判断食谱是否不含麸质,因此我们使用该布尔结果:不含麸质为 1,含麸质为 0。

生成原因
生成原因的直接链接

通过另一次 LLM 调用,为分数提供便于阅读的解释。

.generateReason({
description: 'Generate a reason for the score',
createPrompt: ({ results }) => {
return generateReasonPrompt({
glutenSources: results.analyzeStepResult.glutenSources,
isGlutenFree: results.analyzeStepResult.isGlutenFree,
});
},
})

原因生成 Step 会使用布尔结果以及分析 Step 识别出的具体麸质来源来创建解释,帮助用户了解为何会得到该分数。

高无麸质得分示例
高无麸质得分示例的直接链接

src/example-high-gluten-free.ts
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)

高无麸质得分输出
高无麸质得分输出的直接链接

{
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.'
}

部分含麸质的示例
部分含麸质的示例的直接链接

src/example-partial-gluten.ts
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)

部分含麸质时的输出
部分含麸质时的输出的直接链接

{
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.'
}

低无麸质得分示例
低无麸质得分示例的直接链接

src/example-low-gluten-free.ts
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)

低无麸质得分输出
低无麸质得分输出的直接链接

{
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.'
}

示例与资源: