AI 项目的验收标准——怎么定义「AI 做得好」
AI 项目的验收标准——怎么定义「AI 做得好」
适读人群:做 AI 项目交付的工程师,以及要验收 AI 项目的业务方
阅读时长:约 22 分钟
文章价值:AI 验收标准体系设计 + 自动化验收框架实现 + 业务对齐方法
一场关于「好不好」的争论
我做过一个 AI 项目,开发周期三个月,开发团队认为做得很好,客户认为做得很差。
双方都不是在说谎,只是评估标准不同。
开发团队的标准:准确率 87%,响应时间平均 1.2 秒,在测试集上的 ROUGE 分数比基线提升了 23%。这些指标,我们认为已经相当优秀了。
客户的标准:我们的客服之前每个问题平均处理 4 分钟,用了 AI 之后还是 4 分钟,效率没有提升。而且偶尔出现的 13% 的错误,集中在某几类高频问题上,刚好是客户最在意的那几类。
双方说的都是真的,但对「好不好」的判断完全相反。
这件事之后,我开始认真思考一个问题:AI 项目的验收标准,应该由谁来定,怎么定,什么时候定?
这篇文章是我过去两年的思考结果。
AI 验收和功能测试验收的本质差异
传统软件项目的验收测试有相对清晰的通过/失败标准:
- 用户点击「提交」,系统在 2 秒内返回确认消息 → 通过
- 系统计算金额,结果 = 数量 × 单价 → 通过/失败(精确对比)
- 下单流程的所有步骤都能正常执行 → 通过
这种标准有一个前提:行为是确定性的。给定同样的输入,系统要给出同样的输出,这个输出是「对的」还是「错的」有明确标准。
AI 系统打破了这个前提:
- AI 的输出不是确定性的(同样的问题,今天和明天的回答可能略有不同)
- 输出的质量是一个光谱,不是对/错的二元判断(答案可以「很好」「一般」「差」,没有绝对的「正确」)
- 不同的评估者对同一个答案的质量判断可能不同
这意味着,传统的测试框架不能直接套用到 AI 项目上。需要一套新的评估体系。
验收标准的四个维度
我现在用四个维度来定义 AI 项目的验收标准。
1. 准确性维度:回答对不对
这是最基础的维度,也是技术团队最熟悉的。
但「准确性」要分类型:
- 事实准确性:AI 给出的信息是否符合事实(特别是 RAG 系统,有没有产生幻觉)
- 任务完成度:用户的问题是否被完整回答(不只是字面上,是用户真正需要的那个答案)
- 一致性:同样的问题,在不同时间、不同表述下,AI 的回答是否一致
每一类准确性的测量方法不一样:
- 事实准确性可以用参考文档比对
- 任务完成度需要人工评估或者精心设计的自动化评分
- 一致性用同一问题多次提问对比
关键:测试集必须来自真实用户问题,不是技术团队设计的问题。
2. 效率维度:有没有实际提升效率
AI 项目的核心业务价值,通常是效率提升。但「效率」要具体到可测量的指标:
- 如果是客服 AI:人均处理工单数量、平均处理时长、人工干预率
- 如果是知识查询 AI:找到所需信息的时间、首次接触到答案的点击数
- 如果是文档分析 AI:分析一份文档的时间、需要人工复查的比例
这些指标要有基线数据(AI 上线前的数值)和目标数据(AI 上线后要达到的数值)。没有基线,就没有比较,「效率提升了」就是说说而已。
3. 业务价值维度:对业务结果有没有贡献
效率提升不等于业务价值。处理速度快了,但客户满意度下降了,是得不偿失的。
业务价值指标通常是业务方的核心 KPI:
- 客服 AI:客户满意度评分、投诉率、转人工率
- 销售辅助 AI:商机转化率、销售周期
- 知识管理 AI:员工生产力指标
这些指标由业务方定义和监控,技术团队提供数据支持。
4. 风险控制维度:有没有造成不可接受的错误
AI 的错误不是均匀分布的,有些错误是可以接受的(影响小、可以纠正),有些是不可接受的(影响大、造成损失)。
验收标准里要明确:
- 红线错误(绝对不能发生):比如在医疗 AI 里给出错误的用药建议,在金融 AI 里给出错误的合规信息
- 黄线错误(发生后需要快速修复):答复不准确但不会造成直接损失
- 白线错误(可以接受一定比例):答复质量一般,但不影响核心价值
这三类错误的判断完全取决于具体业务场景,没有通用标准。但要在验收前明确,而不是出了事再来判断。
和业务方对齐的方法
定义好了四个维度的验收标准,还需要和业务方对齐。这个对齐过程本身是有方法的。
对齐时机:立项时,不是上线前
这是我犯过错误后得到的教训。验收标准要在项目立项时和业务方一起定,不是开发完了再来谈。
上线前再谈,有两个问题:一是「既成事实」的心理——系统都做好了,改起来成本高,双方都有压力接受;二是业务方对 AI 的预期在这几个月里可能已经漂移了,和当初想的不一样了。
立项时对齐,成本最低,也最诚实。
对齐方式:用具体场景,不用指标数字
直接问业务方「你要 90% 的准确率」,他们通常没有感觉,不知道 90% 意味着什么。
换一种问法:「我给你看 10 个 AI 回答,其中一个是错的,你觉得这个系统值得用吗?」这样他们有了具体感受,才能真正定义他们的容忍边界。
再问:「如果这个错误发生在[最重要的业务场景],你接受吗?」这样确定红线在哪里。
对齐内容:AI 能做什么,不能做什么
验收标准的对齐,一半是「要达到什么标准」,另一半是「明确系统的边界」。
把 AI 系统不擅长、不应该处理的场景明确列出来,告诉业务方:遇到这些场景,系统会说「我不确定,请人工处理」,这不是缺陷,这是设计决策。
这个预期管理做好了,上线后的「系统答不出来」就不会被当成 bug,而是被当成正常行为。
自动化验收测试框架
光有标准还不够,需要有工具来自动化地检验这些标准。
@Service
public class AIAcceptanceTestFramework {
private final ChatClient evaluatorClient; // 用于评估AI答复质量的LLM
private final VectorStore referenceStore; // 存储参考答案的向量库
private final TestCaseRepository testCaseRepo;
private final AcceptanceReportGenerator reportGenerator;
/**
* 运行完整的验收测试套件
*/
public AcceptanceTestReport runAcceptanceTests(String projectId, TestSuiteConfig config) {
List<TestCase> testCases = testCaseRepo.findByProjectId(projectId);
List<TestCaseResult> results = testCases.parallelStream()
.map(testCase -> runSingleTestCase(testCase, config))
.collect(Collectors.toList());
// 统计各维度得分
AcceptanceTestReport report = buildReport(projectId, results, config);
// 判断是否通过验收
report.setPassResult(evaluatePassFail(report, config.getPassCriteria()));
return report;
}
private TestCaseResult runSingleTestCase(TestCase testCase, TestSuiteConfig config) {
long startTime = System.currentTimeMillis();
// 1. 调用被测AI系统
String actualResponse = callTargetSystem(testCase.getInput(), config.getTargetSystemUrl());
long responseTime = System.currentTimeMillis() - startTime;
// 2. 评估各维度得分
FactualAccuracyScore factualScore = evaluateFactualAccuracy(
testCase, actualResponse
);
TaskCompletionScore taskScore = evaluateTaskCompletion(
testCase, actualResponse
);
RiskAssessmentResult riskResult = assessRisk(
testCase, actualResponse
);
return TestCaseResult.builder()
.testCaseId(testCase.getId())
.category(testCase.getCategory())
.input(testCase.getInput())
.expectedOutput(testCase.getExpectedOutput())
.actualOutput(actualResponse)
.responseTimeMs(responseTime)
.factualAccuracyScore(factualScore)
.taskCompletionScore(taskScore)
.riskResult(riskResult)
.isRedLineViolation(riskResult.isRedLineViolation())
.build();
}
private FactualAccuracyScore evaluateFactualAccuracy(
TestCase testCase, String actualResponse) {
// 策略1:如果有标准答案,用LLM对比评估
if (testCase.hasReferenceAnswer()) {
return evaluateAgainstReference(testCase.getReferenceAnswer(), actualResponse);
}
// 策略2:如果有参考文档,检查答复内容是否与文档一致
if (testCase.hasReferenceDocuments()) {
return evaluateAgainstDocuments(testCase.getReferenceDocuments(), actualResponse);
}
// 策略3:自一致性检查(同一问题多次提问,答案是否一致)
return evaluateSelfConsistency(testCase.getInput(), actualResponse);
}
private FactualAccuracyScore evaluateAgainstReference(
String referenceAnswer, String actualResponse) {
String evaluationPrompt = """
你是一个专业的AI回答质量评估专家。
请对比以下参考答案和实际答案,评估实际答案的事实准确性。
参考答案(权威标准):
%s
实际答案(待评估):
%s
评估要求:
1. 判断实际答案中是否包含事实性错误
2. 判断关键信息是否完整覆盖
3. 判断是否有超出参考答案的额外声明(可能是幻觉)
输出 JSON 格式:
{
"factualCorrectness": 85,
"keyInfoCoverage": 90,
"hallucinationRisk": 10,
"issues": ["问题1描述", "问题2描述"],
"overallScore": 85
}
分数均为0-100。
""".formatted(referenceAnswer, actualResponse);
String evalResponse = evaluatorClient.prompt()
.user(evaluationPrompt)
.call()
.content();
try {
String json = extractJsonFromResponse(evalResponse);
Map<String, Object> parsed = new ObjectMapper().readValue(json, Map.class);
return FactualAccuracyScore.fromMap(parsed);
} catch (Exception e) {
log.error("Failed to parse factual accuracy evaluation", e);
return FactualAccuracyScore.evaluationFailed();
}
}
private TaskCompletionScore evaluateTaskCompletion(
TestCase testCase, String actualResponse) {
String evaluationPrompt = """
用户的原始问题:%s
用户的真实意图:%s
AI的实际回答:%s
评估AI的回答是否完整解决了用户的需求。
评估维度:
1. 是否直接回应了用户的核心问题
2. 如果需要操作步骤,步骤是否完整可操作
3. 如果需要联系方式或后续指引,是否提供了
4. 回答长度是否适当(不过短也不过长)
输出 JSON:
{
"coreQuestionAnswered": true,
"actionableSteps": 80,
"nextStepGuidance": 90,
"responseQuality": 75,
"taskCompletionScore": 80,
"missingElements": ["缺少的内容1"]
}
""".formatted(
testCase.getInput(),
testCase.getUserIntent(),
actualResponse
);
String evalResponse = evaluatorClient.prompt()
.user(evaluationPrompt)
.call()
.content();
try {
String json = extractJsonFromResponse(evalResponse);
Map<String, Object> parsed = new ObjectMapper().readValue(json, Map.class);
return TaskCompletionScore.fromMap(parsed);
} catch (Exception e) {
return TaskCompletionScore.evaluationFailed();
}
}
private RiskAssessmentResult assessRisk(TestCase testCase, String actualResponse) {
// 红线检查:使用规则引擎,不依赖LLM(避免LLM自身的不稳定性)
boolean isRedLine = testCase.getRedLineKeywords().stream()
.anyMatch(keyword -> checkRedLineViolation(actualResponse, keyword, testCase));
if (isRedLine) {
return RiskAssessmentResult.redLineViolation(
testCase.getId(),
"回答内容触发了红线规则"
);
}
// 黄线检查
List<String> yellowLineWarnings = checkYellowLineConditions(testCase, actualResponse);
return RiskAssessmentResult.normal(yellowLineWarnings);
}
private boolean checkRedLineViolation(
String response, String redLineKeyword, TestCase testCase) {
// 红线规则逻辑:根据测试用例配置的红线条件判断
// 比如:如果测试用例是医疗建议类,回答中不能出现具体药物用量
return testCase.getRedLineConditionChecker().check(response, redLineKeyword);
}
private AcceptanceTestReport buildReport(
String projectId,
List<TestCaseResult> results,
TestSuiteConfig config) {
// 按类别分组统计
Map<String, List<TestCaseResult>> byCategory = results.stream()
.collect(Collectors.groupingBy(r -> r.getCategory().toString()));
Map<String, CategoryStats> categoryStats = byCategory.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> CategoryStats.compute(e.getValue())
));
// 全局统计
double avgFactualScore = results.stream()
.mapToInt(r -> r.getFactualAccuracyScore().getOverallScore())
.average().orElse(0);
double avgTaskScore = results.stream()
.mapToInt(r -> r.getTaskCompletionScore().getTaskCompletionScore())
.average().orElse(0);
long redLineViolations = results.stream()
.filter(TestCaseResult::isRedLineViolation)
.count();
double avgResponseTime = results.stream()
.mapToLong(TestCaseResult::getResponseTimeMs)
.average().orElse(0);
// 找出失败的测试用例(用于报告重点展示)
List<TestCaseResult> failures = results.stream()
.filter(r -> r.getFactualAccuracyScore().getOverallScore() < 70
|| r.getTaskCompletionScore().getTaskCompletionScore() < 70
|| r.isRedLineViolation())
.collect(Collectors.toList());
return AcceptanceTestReport.builder()
.projectId(projectId)
.totalCases(results.size())
.avgFactualAccuracyScore(avgFactualScore)
.avgTaskCompletionScore(avgTaskScore)
.redLineViolations((int) redLineViolations)
.avgResponseTimeMs(avgResponseTime)
.categoryStats(categoryStats)
.failedCases(failures)
.testTime(LocalDateTime.now())
.build();
}
private boolean evaluatePassFail(AcceptanceTestReport report, PassCriteria criteria) {
// 红线违规:一票否决
if (report.getRedLineViolations() > 0) {
report.setFailReason("存在红线违规,验收不通过");
return false;
}
// 准确率门槛
if (report.getAvgFactualAccuracyScore() < criteria.getMinFactualAccuracy()) {
report.setFailReason(String.format(
"事实准确率 %.1f%% 低于要求 %.1f%%",
report.getAvgFactualAccuracyScore(),
criteria.getMinFactualAccuracy()
));
return false;
}
// 任务完成度门槛
if (report.getAvgTaskCompletionScore() < criteria.getMinTaskCompletion()) {
report.setFailReason(String.format(
"任务完成度 %.1f%% 低于要求 %.1f%%",
report.getAvgTaskCompletionScore(),
criteria.getMinTaskCompletion()
));
return false;
}
// 响应时间门槛
if (report.getAvgResponseTimeMs() > criteria.getMaxResponseTimeMs()) {
report.setFailReason(String.format(
"平均响应时间 %.0fms 超过要求 %dms",
report.getAvgResponseTimeMs(),
criteria.getMaxResponseTimeMs()
));
return false;
}
return true;
}
}这个框架有几个设计要点:
1. 评估维度是多层次的
事实准确性、任务完成度、风险评估分别独立计算,因为这三个维度是不同性质的评估,不能混为一谈。
2. 红线检查不依赖 LLM
红线是「绝对不能发生的」,用 LLM 来评判红线存在不确定性(LLM 自己也可能出错)。所以红线检查用规则引擎,确定性更高。
3. 参考答案来源分层
有标准答案就对比标准答案,没有就对比参考文档,再没有就做自一致性检查。这个分层处理保证了不同类型的测试用例都能被合理评估。
AI 验收流程
验收不只是一次,是持续的
很多人以为验收是「上线前的最后一步」,做完就结束了。
AI 系统的验收应该是持续的,原因有几个:
一是模型漂移:底层 LLM 模型可能会更新,更新后的行为可能和之前不同,需要重新验证。
二是数据漂移:用户的问题模式会随时间变化,当初验收时没覆盖到的场景可能慢慢变成高频场景。
三是知识库更新:RAG 系统的知识库更新后,检索行为和答复质量会变化,每次更新后要做回归测试。
我建议的持续验收频率:
- 每次代码发布:跑核心测试用例,确保没有回退
- 每月:跑完整测试套件,监控各维度趋势
- 每季度:更新测试集(补充这段时间的新型用户问题),和业务方重新对齐标准
测试集的黄金标准:「真实、多样、有难度」
最后说说测试集设计,这是验收有没有意义的基础。
真实:测试用例必须来自真实用户,不是工程师想象的。获取方式:上线前从业务方获取历史咨询记录、上线后从真实用户交互日志中抽取。
多样:覆盖所有主要场景类别,包括:常见问题、边界情况、模糊问题、敌意问题(故意要让系统答错的)、需要多步操作的问题。
有难度:测试集不能只包含容易的问题。要刻意包含一些 AI 容易出错的场景:信息不足时的处理、相似但不同的问题的区分、需要判断的模糊问题。
一个好的测试集,应该让工程师看了会有点紧张——「这几道题我不确定 AI 能答好」。如果工程师觉得测试集全是送分题,这个测试集的价值就很有限。
总结:验收标准是 AI 项目质量的「合同」
AI 项目的验收标准,本质上是技术团队和业务方之间的「质量合同」。
合同要在立项时签,不是上线时签。 合同要双方都清楚地理解,不是技术方自说自话。 合同要包含「不能做什么」,不只是「要达到什么」。
有了这份合同,双方才有共同的语言来讨论「AI 做得好不好」。没有这份合同,这个问题永远有争议。
