《评测驱动开发(EDD)》系列用 14 篇把评测从"感觉"做成了"工程":数据集、断言、judge、轨迹、CI 门禁、线上监控,一层层砌完整条流水线。换到 TypeScript 阵营自然会问——Mastra 作为 TS 优先的 Agent 框架,把这些环节内置了多少?开箱能用的部分长什么样?
先给结论:Mastra evals 的内置部分是"组件级快评"——一批现成的 LLM judge 评分器、挂到 Agent 与工作流步骤上的随行打分、外加 Studio 里的一块评测界面;数据集工程与 CI 硬门禁仍在框架之外,要回到 EDD 的方法论里解决。本文按"定位 → 指标清单 → Agent 打分 → 流程门禁 → vitest 集成 → 与 EDD 分工 → Studio 界面 → 总结"组织,代码基于 2026 年 10 月官方文档逐一核实过的 scorer 接口。
1、内置评测的定位
判断一个框架"内置了多少评测",先看它把评测拆成了哪些件活。EDD 系列第 8 篇把评测分成组件级与端到端两层:组件级回答"这个输出好不好",端到端回答"整个任务成没成"。Mastra 内置的 evals 明确站在前者——每个 scorer 只对一次运行(run)的输入输出打一个分,不管业务链路通不通。
这套接口在 2026 年经历了一次大改版。2025 年刚引入时(见官方博客的引入文章)还是 AnswerRelevancyMetric 一类的 Metric 类,手动调 measure();如今官方 evals 总览的口径已全面换成 scorer 工厂函数 + runEvals + Quick Checks 三件套,指标本体、批量运行器、确定性断言各归其位。老教程里 new AnswerRelevancyMetric(model) 的写法已经过时,照抄跑不通。
运行形态是"随行打分":scorer 挂到 Agent 上之后,每次响应完成会异步补一次判分,不阻塞返回;分数连同 judge 给出的理由落进 mastra_scorers 表,Studio 里可以直接翻。这决定了它的定位——框架替你把"judge 怎么调、结果存哪、界面怎么看"包圆了,但评测集怎么建、多少分算过线、CI 里怎么卡,仍然是你的事。放到第 2 篇的语境里:generate 给你答案,scorer 补一句"这个答案值几分",答案与质量分在同一套 storage 里关联着,排障时能对上号。
2、内置指标清单
指标分三路:LLM judge 类、确定性类、Quick Checks。前两路都在 prebuilt scorer 里,按官方的内置 scorer 清单整理如下:
| 类别 | scorer | 判什么 |
|---|---|---|
| 准确与可靠 | answer-relevancy |
回答是否切题,越高越好 |
| 准确与可靠 | faithfulness |
是否忠实于给定上下文,需 context |
| 准确与可靠 | hallucination |
事实矛盾与无依据断言,需 context |
| 准确与可靠 | completeness |
该说的信息是否说全 |
| 准确与可靠 | answer-similarity |
与标准答案的语义相似度,需参考答案 |
| 准确与可靠 | prompt-alignment |
指令的意图、完整度与格式遵从度 |
| 准确与可靠 | tool-call-accuracy |
工具选择是否正确,需期望的工具调用 |
| 准确与可靠 | trajectory-accuracy |
工具与步骤序列是否符合预期轨迹 |
| 上下文质量 | context-precision |
RAG 检索排序质量,早命中加分 |
| 上下文质量 | context-relevance |
检索内容相关度与缺失检测 |
| 输出质量 | tone-consistency |
语气正式度与风格一致性 |
| 输出质量 | toxicity、bias |
有害内容与偏见倾向 |
| 输出质量 | keyword-coverage |
关键术语覆盖度 |
| 确定性 | content-similarity、textual-difference |
字符级相似度,不调 LLM |
注意:
hallucination、toxicity、bias三个是分数越低越好,其余都是越高越好。写断言前先看清方向,别把 0.9 的幻觉分当喜事。
再往下是 Quick Checks:checks.includes、checks.matches、checks.calledTool、checks.noToolErrors 等十余个确定性微断言,不调 LLM、微秒级出分,输出 0/1。几个细节:checks.equals 默认大小写不敏感;checks.similarity 用 Dice 系数算 0~1 相似度,配个阈值就能转成两值判定;checks.toolOrder、checks.maxToolCalls 则管调用顺序与次数上限。这一路对应 EDD 第 4 篇的"能断言的先断言"——字符串、正则、工具调用形态这些可精确判定的维度,根本轮不到 judge 出场。
顺带说一句:网上常被猜的"代笔检测/文风指纹"类指标并不存在,名单里全是主流质量维度。多轮对话有专门的 multi-turn-judge,按你给的一段自然语言标准逐轮打分,算是清单里最灵活的一个。
3、给 Agent 输出打分
用法从"构造 Agent 时挂 scorer"开始。judge 模型在 scorer 工厂里指定,走与 Agent 相同的模型字符串规则;判分本质是分类任务,配个便宜模型就够,不必与主模型同档:
import { Agent } from '@mastra/core/agent'
import {
createAnswerRelevancyScorer,
createHallucinationScorer,
} from '@mastra/evals/scorers/prebuilt'
export const judgedAgent = new Agent({
id: 'judged-agent',
model: 'anthropic/claude-sonnet-5-5',
instructions: '你是严谨的助手。',
scorers: {
relevancy: {
scorer: createAnswerRelevancyScorer({ model: 'openai/gpt-6-luna' }),
sampling: { type: 'ratio', rate: 1 }, // 每次响应都评
},
hallucination: {
scorer: createHallucinationScorer({ model: 'openai/gpt-6-luna' }),
sampling: { type: 'ratio', rate: 0.5 }, // 一半采样
},
},
})
四个点值得展开。其一,sampling.rate 控制打分覆盖面,1 是全量、0.5 是一半;采样不是掷骰子,按 trace ID 确定性抽取——同 rate 的 scorer 永远选中同一批样本,两轮分数才可比。其二,判分异步进行,不增加响应延迟,代价是分数晚到一步。其三,分数要落库得给 Mastra 实例配 storage(如 LibSQLStore),并把 scorer 注册到实例的 scorers 选项,否则跑了但不持久化。其四,filter 能按请求上下文圈定打分资格,比如只对企业版租户生效。
judge 模型怎么选?EDD 第 5 篇的结论直接适用:judge 与被评模型解耦,避开自评偏好;判分本质是结构化分类,便宜的小模型反而稳定,官方示例统一用 openai/gpt-6-luna 一档。要抠成本也有现成组合——Quick Checks 全量跑、LLM scorer 低采样跑,两路的成本模型完全不同。
不想常驻挂载、只想手动评一条,scorer 实例本身带 run 方法(各 scorer 的入参细节见幻觉 scorer 参考):
const scored = await createHallucinationScorer({
model: 'openai/gpt-6-luna',
}).run({
input: [{ role: 'user', content: '总结这份检索结果' }],
output: { text: '根据资料,该功能于 2026 年 3 月上线……' },
})
console.log(scored.score) // 0~1,越低越好
console.log(scored.reason) // judge 给出的判分理由
输入是消息数组,输出是运行结果对象,run 返回分数与理由。要定制自己的 judge 也不难:createScorer({ id, judge }) 链上 .analyze() 与 .generateScore(),用 Zod 约束中间产物,详见自定义 scorer 文档。
4、Workflow 步骤门禁
Agent 是"整段输出一个分",工作流还能把评测下沉到步骤。createStep 同样收 scorers 配置,中间产物照样被打分:
import { createWorkflow, createStep } from '@mastra/core/workflows'
import { z } from 'zod'
import { toneScorer } from '../scorers/tone.ts' // 自定义 scorer,见第 3 节
const draftStep = createStep({
id: 'draft-step',
inputSchema: z.object({ topic: z.string() }),
outputSchema: z.object({ content: z.string() }),
scorers: {
tone: { scorer: toneScorer(), sampling: { type: 'ratio', rate: 1 } },
},
execute: async ({ inputData }) => ({
content: await draft(inputData.topic), // 草稿生成,略
}),
})
export const blogWorkflow = createWorkflow({
id: 'blog-workflow',
inputSchema: z.object({ topic: z.string() }),
outputSchema: z.object({ content: z.string() }),
}).then(draftStep).commit()
步骤级打分的价值在于质量门禁前移:草稿步骤的语气分连续走低,你看到的是"第 2 步坏了",而不是"最终输出不对劲"再倒查。官方还提供 gates/verdicts 一类阈值判定 API,把分数转成通过与否的结论;但边界要说清——这仍是框架内的观测与软门禁,"改完不过线就不许合并"的硬卡口属于 CI,Mastra 没有替你做。
5、vitest 集成测试
官方文档没有专门的 vitest 集成页,也不需要:scorer 与 runEvals 都是普通异步函数,vitest 直接调。runEvals 收一批输入、一个目标 Agent、一组 scorer,返回按 scorer id 归位的分数:
import { runEvals } from '@mastra/core/evals'
import { checks } from '@mastra/evals/checks'
const result = await runEvals({
data: [{ input: 'What is the weather in Brooklyn?' }],
target: weatherAgent,
scorers: [checks.calledTool('get_weather'), checks.noToolErrors()],
})
console.log(result.scores)
// { 'check-called-tool': 1, 'check-no-tool-errors': 1 }
data 就是一个最小数据集——EDD 第 3 篇说的"冒烟集",在 TS 里先写成常量数组就够用。套进 vitest,给第 2 篇的 timeAgent 配一组评测:
import { describe, it, expect } from 'vitest'
import { runEvals } from '@mastra/core/evals'
import { checks } from '@mastra/evals/checks'
import { createHallucinationScorer } from '@mastra/evals/scorers/prebuilt'
import { timeAgent } from '../src/mastra/agents/index.ts'
describe('timeAgent 评测', () => {
it('调对工具且无幻觉', async () => {
const { scores } = await runEvals({
data: [{ input: '现在几点了?' }],
target: timeAgent,
scorers: [
checks.calledTool('get-current-time'),
checks.noToolErrors(),
createHallucinationScorer({ model: 'openai/gpt-6-luna' }),
],
})
expect(scores['check-called-tool']).toBe(1)
expect(scores['check-no-tool-errors']).toBe(1)
expect(scores['hallucination']).toBeLessThanOrEqual(0.2)
}, 60_000)
})
两类断言要区别对待。checks.* 微秒级、完全可复现,放进每次提交都跑的常规用例;LLM judge 有轮次波动(EDD 第 5 篇讲透的漂移问题),要给长超时、留宽松阈值,必要时用环境变量分流——比如 describe.skipIf(!process.env.RUN_LLM_EVALS) 包一层,日常提交只跑 checks,夜间流水线才带上 judge。这个测试文件本身就是 EDD 第 10 篇"评测进 CI"的最小 TS 版本。
6、与 EDD 方法论分工
把两边对齐成一张表,谁管什么一目了然:
| 环节 | Mastra 内置 | EDD 系列方法论 |
|---|---|---|
| judge 质量维度 | 17 个 prebuilt scorer,构造时指定 judge 模型 | 第 5、6 篇:rubric 设计与漂移治理 |
| 确定性断言 | 11 个 Quick Checks | 第 4 篇:精确断言优先 |
| 轨迹与工具调用 | tool-call-accuracy、trajectory-accuracy |
第 7 篇:轨迹评测 |
| 线上随行打分 | sampling 按 trace 采样、分数落库 |
第 11 篇:线上监控闭环 |
| 数据集管理 | 无,Studio Evaluate 面板仅起步 | 第 3 篇:冒烟集到黄金集 |
| CI 门禁 | 无原生卡口,gates/verdicts 偏软 | 第 10 篇:CI 硬门禁 |
一句话记法:Mastra 管"单次输出打分"的零件,EDD 管"分数驱动迭代"的体系。框架替你省掉的是 judge 的样板代码与结果存储;评测集的版本化、阈值的校准、红队与人工反馈,框架不碰。至于 LangSmith、Langfuse 这类外部观测平台,接的是 OpenTelemetry 导出的 trace;mastra_scorers 的分数在自家 storage 里,两边各管一段,并不冲突。
7、Studio 评测界面
第 2 篇用过的调试台 pnpm dev 里就有评测的位置。操作路径:左侧进 Observability 区,先在 Scorers 列表确认 scorer 已注册;随后是分页的打分结果,点开任意一条详情,分数、judge 理由、输入输出、以及 judge 实际使用的 prompt 全部可见——最后一样尤其值钱,rubric 调优时不用猜裁判到底读了什么。
Playground 侧还有 Agent Evaluate 标签:选数据集与已注册的 scorer 跑一轮实验,得到逐条 pass/fail 与版本标签,也可以对历史 trace/span 补打分。它不是完整的数据集管理台——没有版本 diff、没有审批流——但"改完 instructions 立刻跑同一组输入对比分数"这个高频动作,不用写代码就能完成。
8、总结
Mastra 的 evals 把评测里三件最脏的活——judge 调用、结果落库、查看界面——做成了框架内置:17 个 prebuilt scorer 覆盖切题度、幻觉、忠实度、语气、毒性等主流维度,11 个 Quick Checks 兜住确定性断言,sampling 提供线上轻量采样,Studio 里能直接看到 judge 的判分理由。而数据集工程、CI 硬门禁、阈值校准仍在框架之外,正是 EDD 系列 14 篇的地盘。
练习:回到第 2 篇的 timeAgent,挂一个 hallucination scorer(工具返回天然就是判分上下文),rate 设 1 连跑 10 次,在 Studio 观察分数分布;再把阈值写进 vitest 断言,从 0.2 起调。体会一次"调阈值"到底在调什么——不是拍一个数字,而是看完分布再画线。对于刚起步的 TS 团队,建议从 Quick Checks 加一个幻觉 scorer 入手,EDD 的数据集与门禁方法论随后补上。

