在上一篇中,我们梳理了 Agent 质量评估的三层金字塔,并掌握了黄金数据集的构建与大模型裁判的偏置校准方法。
但当技术团队尝试将评估体系真正融入日常研发流水线时,工程师往往会遇到另一个非常棘手的现实工程问题:面对具体业务中的某个 Agent(例如用户画像解析、选校推荐、导师背调),我们到底该从哪些具体维度来给它打分?如何像传统前后端开发写单测、跑持续集成(CI)那样,建立起改 Prompt 或调工具的代码门禁?
如果评估只是停留在跑完测试后打印一段长长的报告,或者只让大模型随手给出一个笼统的主观分数(比如“85 分”),那么这套评测就无法直接阻断不合格的代码合并,甚至会掩盖严重的业务漏洞。
本文将从真实业务场景中的“翻车痛点”切入,建立一套面向任意 Agent 的多维量化评分卡(Scorecard)设计方法;随后,以意向导师背调 Agent 为具象案例,演示如何用纯 TypeScript 代码实现高确定性(零 Token 消耗)的量化打分器;最后,打通从“录入失败用例 -> 迭代优化 -> 回归测试 -> CI 自动化阻断”的 Agent 测试驱动开发(TDD)完整交付闭环。
从玩具教材到生产现实:评分卡与门禁的断层#
在很多开源教程或入门示例中,对 Agent 的测试通常停留在极其简单的断言上:
- 单一布尔值判断:只看 Agent 最终有没有抛出异常,或者某个字段是否存在(
expect(result.data).toBeDefined()),完全无法衡量输出内容的真实性与准确度; - 笼统的大模型主观总分:让大模型裁判看一遍输入输出,直接输出一个 1~5 星的评价。这种分数波动性大,不仅耗时昂贵,而且一旦不及格,工程师根本无法根据这个笼统的分数定位到是哪一段 Prompt 或哪个工具出错了;
- 改完 Prompt 凭感觉发布:开发者本地输入一两条测试用例,肉眼看回答顺畅、排版漂亮,就直接合并代码上线。然而,这行 Prompt 修改很可能导致之前稳定的另外数十个边缘场景静默失效。
在真实的商业级生产工程中,测试与交付必须满足两个刚性要求:
- 可定位与高确定性:必须将 Agent 的质量拆解为具体的细分维度(如门禁拦截、关键字段匹配、外部凭证覆盖、工具调用步数)。能用确定性代码判断的维度,坚决不依赖大模型,做到毫秒级运行与零成本消耗;
- 刚性自动化门禁:评测脚本必须与团队的代码仓库(如 GitHub Actions、GitLab CI)绑定。任何一次代码提交或合并请求(PR),只要自动化评分卡未达到预设阈值,必须直接抛出非零退出码(
process.exit(1))中断部署流水线,彻底杜绝靠侥幸上线。
在业界工程中,这对应于 Promptfoo 或 DeepEval 中的自定义断言指标(Custom Assertions / Metrics),以及基于脚本命令的持续集成阻断流。
一、 评分卡设计思考路径:翻车反推四问法#
很多开发者在为 Agent 设计评分卡时,容易陷入“凭空生造抽象概念”的误区,机械地罗列一些模糊的标准。
设计量化评分卡最朴素、最有效的思考路径只有一句话:不要空想“该给它打哪些分”,先问“这个 Agent 在生产环境里最容易以哪几种姿势翻车?”
每一个严重的业务翻车点,反过来就是一个必须用代码度量的硬性指标。
在软件工程中,任何 Agent 在运行时都会经历“接收输入 -> 控制流与工具调用 -> 交付物输出 -> 状态维护”的过程。面对任意一个全新业务的 Agent,我们可以通过以下 4 个问题推导指标:
| 核心提问 | 业务关注的现实矛盾 | 映射到的评测维度 | 推荐实现手段 |
|---|---|---|---|
| 1. 遇到不该处理的输入会怎样? | 闲聊、攻击性指令或前置数据缺失时,Agent 是否会盲目调用外部工具消耗资源? | 门禁判定准确率 (Gate Accuracy) | 纯 TypeScript 逻辑判定状态码(0 Token 消耗) |
| 2. 输出的结论是不是真的? | 面对复杂问题,模型是否会凭空编造不存在的事实、虚构链接或假邮箱? | 真实溯源与防捏造 (Grounding) | 正则表达式提取、URL 凭据链核验 |
| 3. 业务核心交付物对不对? | 要求的关键字段是否抽取完整?数值单位有没有换算正确?历史数据是否被意外冲刷? | 字段完备与状态合规 (Deliverables) | 纯 TypeScript 键值对匹配与 Schema 校验 |
| 4. 跑完这一趟花了多大代价? | 工具调用是否存在死循环或多余轮次?响应耗时与执行步数是否超标? | 工具轨迹与运行效率 (Trajectory) | 执行日志中的工具调用计数与耗时断言 |
二、 真实场景实战:意向导师背调 Agent 评分卡实现#
为了让这套方法论落地,我们以留学申请规划业务中的 AdvisorAgent(意向导师背调智能体) 为例,完整演练评分卡的设计与代码实现。
1. 业务场景与现实事实#
- 输入背景:学生的专业意向(如“计算机视觉、拟申请研究型硕士或博士”)、意向院校列表(如 Oxford、CMU)。
- Agent 职责:判断是否需要背调;若需要,调用学术搜索工具,查找匹配该研究方向的教授,提炼教授近 5 年代表作的方法论切入点(Hook),并获取公开办公邮箱。
- 最终交付物:一份包含导师姓名、匹配理由、代表作 Hook 列表、真实联系方式的结构化画像。
2. 从翻车点推导量化指标#
在真实测试中,该 Agent 最常出现以下 4 类典型问题:
- 门禁穿透:学生申请的是无需套磁的“授课型硕士”,但 Agent 依然盲目发起了 10 次学术搜索,白白消耗数十秒网络耗时;
- 凭空捏造假邮箱:模型未能从主页中提取到有效邮箱,为了交差,擅自输出了形如
prof.smith@institution.edu的占位邮箱,导致后续自动化邮件发送失败; - 内容敷衍缺乏依据:推荐理由只写泛泛空话(如“该导师在业内极富盛名”),未能提取近 5 年具体论文的实验方法切入点;
- 工具调用冗余:搜索关键词过于宽泛,导致工具循环调用超过 8 次仍未收敛。
针对上述翻车点,我们将其转化为 4 个量化指标并分配权重:
gate_intercept_accuracy(门禁判定准确率,权重 0.30)zero_fake_email(零假邮箱合规率,权重 0.25)publication_hooks_depth(代表作学术切入点提取深度,权重 0.25)tool_efficiency(工具调用步数与效率,权重 0.20)
3. 确定性打分器代码实现#
大部分指标都可以通过纯 TypeScript 静态逻辑直接算出得分,执行耗时通常低于 1 毫秒,且结果 100% 确定:
export interface MetricScore {
score: number; // 0 ~ 100
weight: number; // 0 ~ 1.0
passed: boolean;
reason: string;
}
export interface AdvisorRecord {
name: string;
email?: string;
publicationHooks?: string[];
}
export class AdvisorScorer {
/**
* 针对导师背调 Agent 计算多维量化评分卡
*/
public static score(
expectedGatePassed: boolean,
run: {
isGatePassed: boolean;
deliverables?: { advisors?: AdvisorRecord[] };
toolStepsCount: number;
}
) {
const metrics: Record<string, MetricScore> = {};
// 1. 门禁判定指标 (权重 30%)
const gateOk = run.isGatePassed === expectedGatePassed;
metrics.gate_intercept = {
score: gateOk ? 100 : 0,
weight: 0.30,
passed: gateOk,
reason: gateOk ? "门禁判定与预期一致" : `门禁异常: 期望 ${expectedGatePassed} 实际 ${run.isGatePassed}`,
};
// 若本就期望被门禁拦截(如授课型硕士),拦截成功即判定整体通过
if (!expectedGatePassed) {
return { totalScore: gateOk ? 100 : 0, passed: gateOk, metrics };
}
// 2. 零假邮箱合规 (权重 25%)
const advisors = run.deliverables?.advisors || [];
const hasFakeEmail = advisors.some(
(a) => !a.email || a.email.includes("@institution.edu") || a.email.includes("example.com")
);
metrics.zero_fake_email = {
score: hasFakeEmail ? 0 : 100,
weight: 0.25,
passed: !hasFakeEmail,
reason: hasFakeEmail ? "检测到假邮箱或占位符生成" : "零假邮箱,符合真实性规范",
};
// 3. 代表作学术切入点提取深度 (权重 25%)
const hooksValid = advisors.length > 0 && advisors.every((a) => (a.publicationHooks?.length || 0) >= 1);
metrics.publication_hooks = {
score: hooksValid ? 100 : 0,
weight: 0.25,
passed: hooksValid,
reason: hooksValid ? `成功挂载 ${advisors.length} 位导师代表作切入点` : "缺失代表作学术依据",
};
// 4. 工具调用步数与效率 (权重 20%)
const steps = run.toolStepsCount;
const efficiencyOk = steps <= 4;
metrics.tool_efficiency = {
score: efficiencyOk ? 100 : Math.max(0, 100 - (steps - 4) * 25),
weight: 0.20,
passed: efficiencyOk,
reason: `工具调用步数: ${steps} 步 (标准上限: 4 步)`,
};
// 5. 综合判定:加权总分 >= 85 且 单项全部达标 (一票否决机制)
let totalScore = 0;
for (const m of Object.values(metrics)) {
totalScore += m.score * m.weight;
}
totalScore = Math.round(totalScore * 10) / 10;
const passed = totalScore >= 85 && Object.values(metrics).every((m) => m.passed);
return { totalScore, passed, threshold: 85, metrics };
}
}三、 评分卡判定逻辑:为什么必须“一票否决”?#
在上述代码的最后一行,隐藏着一个非常核心的工程细节:
const passed = totalScore >= threshold && Object.values(metrics).every((m) => m.passed);许多团队在做评分卡时,往往只看 totalScore >= threshold(比如总分达到 80 分就算通过)。但在 Agent 生产系统中,这会导致严重的木桶短板效应与错误级联放大:
- 假设大模型的文采极好、排版规整,提取了详尽的生平背景,在格式分、效率分上拿到了满分;
- 但在最关键的“邮箱真实性”上,模型捏造了一个虚假的邮箱(该项得分为 0 分);
- 按照各维度的权重计算,该次运行的加权总分依然可能高达 80~85 分。
如果仅根据加权总分判断,这条满是假数据的建议就会被判定为合格并交付给终端用户。因此,在评分卡组装时,必须明确区分可妥协的平滑指标(如格式分扣 5 分)与不可妥协的一票否决指标(如数据造假、关键门禁穿透)。只要有一项核心安全底线未达标,整张卡必须直接判定为失败。
四、 Agent 测试驱动开发(TDD)闭环#
有了多维量化评分卡,我们就可以将传统的测试驱动开发(TDD)完整迁移至 Agent 研发流程中。
传统的 TDD 是“先写单测断言 -> 运行失败 -> 编写业务代码 -> 测试通过 -> 重构”。而在面对具备随机性的大模型时,Agent TDD 的闭环流程演进如下:
flowchart TD
A["1. 发现线上 Bad Case 或新业务需求"] --> B["2. 固化为一条黄金用例 (GoldenTestCase)
锁定输入条件与预期评分卡契约"]
B --> C["3. 运行评测脚本 (Eval Run)
评分卡亮红灯 [FAIL] (测试驱动先行)"]
C --> D["4. 迭代优化 System Prompt / 调整工具逻辑"]
D --> E["5. 单用例回归直至 [PASS] (绿灯)"]
E --> F["6. 全量基准集回归 (Batch Regression)
验证没有引发历史用例退化"]
F --> G["7. 提交代码触发 CI 门禁自动化拦截
抛出 process.exit(1) 阻断不合格 PR"]自动化门禁脚本的实现#
在持续集成(CI)流水线中,门禁脚本通过加载基准集并调用打分器,若检测到未通过用例,直接退出进程:
import { AdvisorScorer } from "./advisor-scorer";
interface TestCase {
id: string;
name: string;
input: { isTaughtMaster: boolean; major: string };
expectedGate: boolean;
}
async function runCiGate(testCases: TestCase[]) {
let passedCount = 0;
const failedList: string[] = [];
for (const c of testCases) {
// 运行被测 Agent(在 CI 中推荐对网络接口进行 Mock 打桩)
const record = await executeAdvisorAgent(c.input);
const card = AdvisorScorer.score(c.expectedGate, record);
if (card.passed) {
passedCount++;
console.log(`[PASS] ${c.name} (${card.totalScore}分)`);
} else {
failedList.push(c.name);
console.error(`[FAIL] ${c.name} (得分: ${card.totalScore} / 阈值: ${card.threshold})`);
for (const [key, metric] of Object.entries(card.metrics)) {
if (!metric.passed) {
console.error(` └─ 不达标项 [${key}]: ${metric.reason}`);
}
}
}
}
const passRate = (passedCount / testCases.length) * 100;
console.log(`\n测试用例总计: ${testCases.length}, 通过率: ${passRate.toFixed(1)}%`);
// 严格拦截:只要存在未通过用例,直接抛出非零退出码中断 CI
if (failedList.length > 0 || passedCount < testCases.length) {
console.error(`CI 门禁拦截: 存在未通过用例 [${failedList.join(", ")}],阻断部署!`);
process.exit(1);
}
console.log("CI 门禁全部通过,允许合并!");
process.exit(0);
}五、 实战推演:短视优化引发的 CI 拦截#
为了直观体会这套自动化门禁的威力,我们推演一个团队协作中极其普遍的翻车场景:
团队工程师小李希望降低 Token 消耗并缩短响应时间。他在 Prompt 末尾加上了一句看似聪明的提示:
“为了提升回答效率,请尽量简明扼要,省略冗长的论文方法论和外部凭据。”小李在本地简单测试了一条用例,发现 Agent 的吐字速度明显加快,回答篇幅精炼,看起来效果非常理想,于是自信地向主干提交了代码合并请求(Pull Request)。
当 GitHub Actions 流水线触发后,CI 评测脚本自动回放包含 30 条黄金用例的基准集,终端立刻输出了如下阻断日志:
========================================================================
[Golden Benchmark Eval] 正在评测智能体: [ADVISOR AGENT]
========================================================================
[PASS] 计算机科学博士申请-顶会导师匹配 (91.0分)
[FAIL] 机器人工程研究型硕士-牛津教授推荐 (得分: 65.0 / 阈值: 85.0)
├─ [通过] gate_intercept (100分): 门禁判定与预期一致
├─ [通过] zero_fake_email (100分): 零假邮箱,符合真实性规范
├─ [未达标] publication_hooks (0分): 缺失代表作学术依据
└─ [通过] tool_efficiency (100分): 工具调用步数: 2 步 (标准上限: 4 步)
测试用例总计: 30, 通过率: 93.3%
CI 门禁拦截: 存在未通过用例 [机器人工程研究型硕士-牛津教授推荐],阻断部署!
Process exited with code 1.小李的 Prompt 改动虽然提升了效率分(tool_efficiency),但牺牲了业务的核心价值——丢失了套磁信撰写所必需的论文代表作切入点(publication_hooks)。
如果没有这套带有“一票否决”机制的自动化评分卡,这项劣质改动就会直接发布到生产环境。正是 CI 门禁的刚性拦截,让系统的退化被扼杀在合并代码之前。
六、 通用设计迁移手册#
无论读者在未来的工程实践中面对何种业务形态的 Agent(如客服排障、数据分析、代码重构),都可以遵循以下标准化步骤落地评分卡:
[第一步:明确生命周期底线]
明确输入前置条件、核心产出物 Schema 与工具使用上限。
把业务最害怕发生的事故逐一列出(如:算错金额、向愤怒用户机械复读、删除已有单测)。
[第二步:拆解四维量化评分]
1. 门禁准确度:不该介入时是否坚决拒识;
2. 真实防捏造:输出是否有凭据,是否存在占位符或模型脑补数据;
3. 业务完备度:关键字段与约束规则是否完全满足;
4. 运行效率度:工具调用步数、执行耗时是否超标。
[第三步:优先确定性断言,辅助大模型裁判]
80% 的硬性指标用纯 TypeScript 逻辑编写(0 Token、毫秒级响应、结果稳定);
剩余 20% 的主观语气、表达自然度等软性指标,交由上一章校准过偏置的 LLM 裁判打分。
[第四步:配置加权总分与一票否决]
关键底线指标设置一票否决判定(.every(m => m.passed));
在 CI 流水线中将评测脚本设为合并门禁,非零退出码阻断发布。通过这套方法,我们彻底将不可控的大模型推演,收敛为具备确定性质量保障的现代软件工程实践。