云计算百科
云计算领域专业知识百科平台

Spring AI企业级应用实战(13):测试、黄金数据集与发布质量门禁

文章摘要

前面的章节已经完成多模型路由、预算控制、权限感知RAG和异步长任务。系统可以调用模型、检索企业知识、执行工具并在故障后恢复,但仍缺少一个决定能否稳定迭代的核心能力:如何证明一次Prompt、模型、RAG索引、Advisor或Tool目录变更没有破坏现有业务。

传统单元测试能够验证状态码、Schema和业务规则,却无法完整判断开放式回答是否更好;只依赖LLM-as-Judge又会受到随机性、位置偏差、长度偏差和Judge版本漂移影响。生产级方案必须建立分层测试体系:确定性规则负责权限、Schema、数字、状态机和工具副作用;Spring AI Evaluator负责相关性和事实支持;经过校准的Judge负责开放质量;黄金数据集和生产Trace负责真实覆盖;Quality Gate负责在CI/CD中阻断高风险退化。

本章将在现有Spring AI工程中增加ai-evaluation模块,构建版本化数据集、Release Manifest、Baseline/Candidate双跑、重复采样、RAG与Agent轨迹评测、Slice指标、GitHub Actions状态检查、发布后Canary和线上失败回流。

完成本章后,每次模型或Prompt升级都必须回答:

改了什么?
在哪些样本上变好?
在哪些Slice上退化?
是否出现硬失败?
成本和延迟变化多少?
能否发布?

一、本章与前面能力的关系

现有调用链:

业务请求

模型路由与预算

权限感知RAG

ChatClient / Tool Calling

异步任务与Checkpoint

结果

本章增加一条平行质量链:

Release Manifest

版本化数据集

Baseline/Candidate执行

确定性Evaluator

Spring AI Evaluator

LLM Judge

Slice聚合

Quality Gate

合并、发布或阻断

评测不是业务调用中的一个Advisor,而是独立控制面。

二、本章目标

1. 固定模型、Prompt、索引与工具运行清单
2. 建立开发集、回归集、安全集和隐藏验收集
3. 使用Fake模型完成确定性单元测试
4. 使用真实Provider执行离线质量评测
5. 重复运行估计模型随机性
6. 分别评测RAG检索、Groundedness和引用
7. 评测Tool Calling轨迹与副作用
8. 使用Spring AI RelevancyEvaluator和FactCheckingEvaluator
9. 使用经过位置平衡和人工校准的LLM Judge
10. 按业务Slice聚合指标
11. 高风险错误零容忍
12. CI/CD生成Required Status Check
13. 发布后运行Canary与线上抽样
14. 将生产事故回流为永久回归Case

三、测试金字塔

┌─────────────────────┐
│ Human Acceptance │
└─────────────────────┘
┌──────────────────────────┐
│ Shadow / Online Canary │
└──────────────────────────┘
┌───────────────────────────────┐
│ Offline Quality Evaluation │
│ Dataset / Judge / RAG / Agent │
└───────────────────────────────┘
┌────────────────────────────────────┐
│ Integration & Contract Tests │
│ Provider / VectorStore / Tools │
└────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Deterministic Unit Tests │
│ Rules / Schema / State / Cost / ACL │
└─────────────────────────────────────────┘

底层数量多、速度快、成本低。

越向上:

  • 样本更真实;
  • 成本更高;
  • 运行更慢;
  • 结论更接近生产体验。

四、为什么不能只依赖JUnit

JUnit非常适合:

assertThat(result.status())
.isEqualTo(SUCCESS);

但开放问答通常没有唯一字符串。

以下回答可能都正确:

渠道动销是商品从渠道库存转化为终端销售的过程。

渠道动销关注货物是否真正从经销和终端环节被消费者购买。

因此需要:

确定性契约
+语义质量评测

五、为什么不能只依赖LLM Judge

Judge不适合替代:

  • JSON Schema;
  • 精确金额;
  • 角色权限;
  • 租户隔离;
  • 工具次数;
  • 状态迁移;
  • 幂等键;
  • SLA;
  • 成本预算。

这些都有明确真值,使用代码更可靠。

正确顺序:

确定性规则失败
→ 直接失败

确定性规则通过
→ 再做语义Judge

六、模块结构

enterprise-ai-platform
├── ai-core
├── ai-routing
├── ai-rag
├── ai-task-center
├── ai-evaluation
│ ├── dataset
│ ├── fixture
│ ├── target
│ ├── evaluator
│ ├── runner
│ ├── metric
│ ├── gate
│ ├── report
│ └── integration
└── ai-application

评测模块依赖业务接口,但生产业务模块不依赖评测实现。

七、Maven Profile

<profiles>
<profile>
<id>ai-eval-fast</id>
<properties>
<ai.eval.dataset>
pr-fast@2026.08.06-v1
</ai.eval.dataset>
<ai.eval.repetitions>1</ai.eval.repetitions>
</properties>
</profile>

<profile>
<id>ai-eval-full</id>
<properties>
<ai.eval.dataset>
full-regression@2026.08.06-v3
</ai.eval.dataset>
<ai.eval.repetitions>3</ai.eval.repetitions>
</properties>
</profile>
</profiles>

八、Release Manifest

public record AiReleaseManifest(
String releaseId,
String gitCommit,
String modelRoutingPolicyVersion,
Map<String, ModelProfileManifest> models,
PromptManifest prompt,
String knowledgeIndexVersion,
String embeddingProfileVersion,
String rerankerProfileVersion,
String toolCatalogVersion,
String memoryPolicyVersion,
String outputSchemaVersion,
String applicationConfigHash) {
}

Prompt Manifest:

public record PromptManifest(
String systemPromptVersion,
String templateVersion,
List<String> advisorVersions,
String securityPolicyVersion,
String hash) {
}

模型Manifest:

public record ModelProfileManifest(
String profileId,
String provider,
String requestedModel,
String resolvedModel,
String endpointRegion,
Map<String, Object> options) {
}

九、为什么Release Manifest必须由运行时回显

配置文件写:

knowledgeIndexVersion=v42

不代表VectorStore连接的一定是V42。

候选服务提供内部端点:

GET /internal/ai-runtime-manifest

返回实际加载值。

评测Runner在每个Run开始前比较:

声明Manifest

运行时Manifest

不一致直接停止。

十、数据集分层

dev
regression
security
incident
hidden-acceptance
production-replay
canary

Dev

开发人员可见,调试Prompt。

Regression

历史稳定能力。

Security

越权、注入和副作用,零容忍。

Incident

生产事故永久回归。

Hidden Acceptance

发布前隐藏验收。

Production Replay

真实Trace脱敏回放。

Canary

部署后快速验证。

十一、Evaluation Case

public record AiEvaluationCase(
String caseId,
int caseVersion,
String datasetId,
String datasetVersion,
String taskType,
RiskLevel riskLevel,
UserFixture user,
List<ConversationTurn> history,
JsonNode input,
ExpectedBehavior expected,
Set<String> tags,
CaseProvenance provenance) {
}

十二、Expected Behavior

public record ExpectedBehavior(
JsonNode exactOutput,
List<ExpectedFact> requiredFacts,
List<String> forbiddenFacts,
Set<String> requiredDocumentIds,
Set<String> forbiddenDocumentIds,
ExpectedToolTrace toolTrace,
RefusalExpectation refusal,
OutputContract outputContract,
QualityRubric rubric,
OperationalBudget budget) {
}

十三、Case来源

public enum EvaluationSource {
HUMAN_GOLDEN,
SYNTHETIC,
PRODUCTION_TRACE,
INCIDENT,
SECURITY_RESEARCH
}

生产Trace进入数据集前必须:

  • 授权;
  • 脱敏;
  • 最小化;
  • 专家标注;
  • 版本冻结。

十四、确定性测试一:路由

@Test
void highRiskContractMustUsePowerfulModel() {

RouteDecision decision =
routingPolicy.route(
fixture.highRiskContract());

assertThat(
decision.primaryModel())
.isEqualTo(
ModelId.POWERFUL_PRIMARY);
}

十五、确定性测试二:权限过滤

@Test
void tenantFilterMustBePresent() {

SearchRequest request =
searchRequestFactory.create(
"合同风险",
fixture.access("T001"),
"KB1",
20,
0.6);

assertThat(
request.getFilterExpression())
.contains(
"tenant_id == 'T001'");
}

更重要的是用Fake VectorStore验证结果中绝不出现T002。

十六、确定性测试三:结构化输出

@Test
void extractionSchemaMustRejectMissingAmount() {

JsonNode output =
objectMapper.readTree("""
{
"currency": "CNY"
}
"""
);

ValidationReport report =
schemaValidator.validate(
contractExtractionSchema,
output);

assertThat(report.valid())
.isFalse();
}

十七、确定性测试四:Tool轨迹

@Test
void refundToolMustNotBeCalledTwice() {

ToolTrace trace =
fixture.trace(
"queryOrder",
"createRefund",
"createRefund");

ToolTraceValidation result =
toolTraceEvaluator.validate(
expectedRefundTrace,
trace);

assertThat(result.valid())
.isFalse();
}

十八、Fake ChatModel

组件测试不应每次调用真实Provider。

public final class ScriptedChatModel
implements ChatModel {

private final Queue<ChatResponse> responses =
new ConcurrentLinkedQueue<>();

public void enqueue(
ChatResponse response) {
responses.add(response);
}

@Override
public ChatResponse call(
Prompt prompt) {

ChatResponse response =
responses.poll();

if (response == null) {
throw new IllegalStateException(
"没有配置测试响应");
}

return response;
}
}

配置测试ChatClient:

@TestConfiguration
public class TestChatConfiguration {

@Bean
@Primary
ChatModel scriptedChatModel() {
return new ScriptedChatModel();
}
}

十九、Advisor顺序测试

生产回答可能由Advisor顺序决定:

Security
Memory
RAG
Audit

测试最终Advisor清单:

@Test
void securityAdvisorMustRunBeforeRag() {

List<String> advisorIds =
advisorManifest
.orderedAdvisorIds();

assertThat(
advisorIds.indexOf(
"security-v4"))
.isLessThan(
advisorIds.indexOf(
"rag-v11"));
}

二十、ChatClient契约测试

@SpringBootTest
class ChatClientContractTest {

@Autowired
ChatClient contractClient;

@Test
void mustReturnStructuredContractResult() {

ContractReviewResult result =
contractClient.prompt()
.user(
fixture.contractText())
.call()
.entity(
ContractReviewResult.class);

assertThat(result.risks())
.isNotNull();

assertThat(result.documentVersion())
.isEqualTo("V4");
}
}

真实Provider契约测试应运行在独立环境并控制成本。

二十一、VectorStore集成测试

使用Testcontainers或独立评测库:

插入T001文档
插入T002蜜罐
使用T001权限查询

断言:

  • 召回目标文档;
  • 不召回T002;
  • Metadata完整;
  • 引用版本正确。

二十二、黄金数据集Loader

public interface EvaluationDatasetLoader {

DatasetManifest manifest(
List<DatasetReference> datasets);

List<AiEvaluationCase> loadApproved(
DatasetManifest manifest);
}

加载后验证:

  • Case状态APPROVED;
  • Dataset Hash;
  • Case Hash;
  • 标签数量;
  • 隐藏集权限;
  • 业务有效期。

二十三、Target抽象

public interface AiEvaluationTarget {

String releaseId();

AiReleaseManifest runtimeManifest();

AiTargetResult execute(
AiEvaluationCase evaluationCase,
EvaluationExecutionContext context);
}

实现:

  • 本地Candidate;
  • 生产Baseline;
  • Preview环境;
  • 固定模型Profile;
  • 影子部署。

二十四、Target Result

public record AiTargetResult(
String output,
JsonNode structuredOutput,
List<RetrievedEvidence> evidence,
List<ToolTraceEvent> toolTrace,
UsageSnapshot usage,
Duration latency,
AiReleaseManifest manifest) {
}

没有Evidence和Tool Trace就无法定位RAG与Agent退化。

二十五、Baseline和Candidate双跑

public record CaseComparisonContext(
AiEvaluationCase evaluationCase,
AiTargetResult baseline,
AiTargetResult candidate,
int repetition,
EvaluatorManifest evaluatorManifest) {
}

同一个Case、同一运行条件下比较。

二十六、重复采样

public List<CaseComparisonContext> executeRepeated(
AiEvaluationCase evaluationCase,
int repetitions) {

return IntStream.range(
0,
repetitions)
.mapToObj(repetition ->
executeOne(
evaluationCase,
repetition))
.toList();
}

高风险Case关注:

  • 最低分;
  • 失败概率;
  • 一致率。

不要只看平均分。

二十七、Spring AI RelevancyEvaluator

@Configuration
public class EvaluationConfiguration {

@Bean
RelevancyEvaluator relevancyEvaluator(
@Qualifier("judgeChatModel")
ChatModel judgeChatModel) {

return new RelevancyEvaluator(
ChatClient.builder(
judgeChatModel));
}
}

使用:

EvaluationResponse response =
relevancyEvaluator.evaluate(
new EvaluationRequest(
question,
evidenceDocuments,
candidateOutput));

相关性通过不代表事实与引用完全正确,因此需要其他Evaluator。

二十八、FactCheckingEvaluator

针对Claim与证据支持关系:

EvaluationResponse response =
factCheckingEvaluator.evaluate(
new EvaluationRequest(
claim.text(),
List.of(
new Document(
evidenceText)),
claim.text()));

高风险数字仍使用确定性比较。

二十九、Claim提取

public record AnswerClaim(
String claimId,
String text,
ClaimType type,
List<String> citationIds,
boolean highRisk) {
}

结构化答案优先直接保存Claims。纯文本回答需要单独提取,但提取器本身也要版本化。

三十、引用评测

至少检查:

引用ID是否合法
引用文档是否允许
引用版本是否有效
Claim是否被Evidence支持
禁止文档是否出现

public record CitationEvaluation(
double citationPrecision,
double citationRecall,
double claimSupportRate,
Set<String> invalidCitationIds,
Set<String> forbiddenDocuments) {
}

三十一、Agent轨迹评测

public record ExpectedToolTrace(
Set<String> requiredTools,
Set<String> forbiddenTools,
Map<String, Integer> maxCalls,
List<PartialOrderConstraint> order,
boolean approvalRequired,
ExpectedFinalState finalState) {
}

Agent最终文本正确,但工具重复调用,仍应失败。

三十二、LLM Judge只评价开放维度

适合:

  • 完整性;
  • 清晰度;
  • 业务帮助程度;
  • 分析质量;
  • 摘要覆盖。

不负责:

  • 权限;
  • 金额精确值;
  • 工具次数;
  • 状态机;
  • 成本。

Judge Prompt必须声明候选答案是不可信数据。

三十三、Judge Manifest

public record EvaluatorManifest(
String evaluatorSetVersion,
String judgeModelProfile,
String resolvedJudgeModel,
String judgePromptVersion,
String rubricVersion,
String outputSchemaVersion,
int repetitions,
double temperature,
String implementationHash) {
}

Baseline与Candidate必须使用同一Evaluator Manifest。

三十四、Pairwise位置平衡

public PairwiseDecision compare(
CaseComparisonContext context) {

PairwiseJudgeResult normal =
judge(
context,
context.baseline()
.output(),
context.candidate()
.output(),
CandidatePosition.SECOND);

PairwiseJudgeResult reversed =
judge(
context,
context.candidate()
.output(),
context.baseline()
.output(),
CandidatePosition.FIRST);

return pairwiseCalibrator
.aggregate(
normal,
reversed);
}

两次结论冲突:

NEEDS_REVIEW

三十五、Judge重复采样

public AggregatedJudgeResult evaluateRepeated(
CaseComparisonContext context,
int repetitions) {

List<JudgeResult> results =
IntStream.range(
0,
repetitions)
.mapToObj(index ->
judgeEvaluator
.evaluate(
context))
.toList();

return judgeAggregator.aggregate(
results);
}

聚合记录:

  • Majority Vote;
  • Median;
  • 最低分;
  • Vote Entropy;
  • 需要人工。

三十六、人工校准

在Judge进入发布门禁前,使用专家标注集比较:

public record JudgeCalibrationReport(
double agreement,
double passPrecision,
double passRecall,
double highRiskFailureRecall,
double positionFlipRate,
double repeatConsistency) {
}

高风险漏判率不达标时,Judge只能用于辅助分析。

三十七、Evaluation Runner

@Service
public class SpringAiEvaluationRunner {

private final EvaluationDatasetLoader datasetLoader;
private final EvaluationTargetRegistry targetRegistry;
private final EvaluationRuleRegistry ruleRegistry;
private final EvaluationResultRepository resultRepository;
private final MetricAggregator metricAggregator;
private final QualityGateEngine gateEngine;

public EvaluationRunResult run(
EvaluationRunConfig config) {

EvaluationRun run =
runLifecycle.start(
config);

try {
List<AiEvaluationCase> cases =
datasetLoader.loadApproved(
config.datasetManifest());

AiEvaluationTarget baseline =
targetRegistry.require(
config.baselineReleaseId());

AiEvaluationTarget candidate =
targetRegistry.require(
config.candidateReleaseId());

assertExpectedManifest(
baseline,
config.baselineManifest());

assertExpectedManifest(
candidate,
config.candidateManifest());

List<CaseRunResult> caseResults =
executeCases(
run,
cases,
baseline,
candidate,
config);

AggregatedEvaluationMetrics metrics =
metricAggregator.aggregate(
run,
caseResults);

QualityGateDecision decision =
gateEngine.evaluate(
config.gatePolicy(),
metrics,
caseResults);

runLifecycle.complete(
run,
metrics,
decision);

return new EvaluationRunResult(
run.runId(),
metrics,
decision);
}
catch (Exception ex) {
runLifecycle.fail(
run,
ex);
throw ex;
}
}
}

三十八、Case执行

private CaseRunResult executeCase(
EvaluationRun run,
AiEvaluationCase evaluationCase,
AiEvaluationTarget baseline,
AiEvaluationTarget candidate,
EvaluationRunConfig config) {

List<RepeatedEvaluationResult> repeated =
new ArrayList<>();

for (int repetition = 0;
repetition < config.repetitions();
repetition++) {

AiTargetResult baselineResult =
baseline.execute(
evaluationCase,
executionContext(
run,
repetition));

AiTargetResult candidateResult =
candidate.execute(
evaluationCase,
executionContext(
run,
repetition));

CaseComparisonContext comparison =
new CaseComparisonContext(
evaluationCase,
baselineResult,
candidateResult,
repetition,
config.evaluatorManifest());

List<EvaluationResult> evaluation =
ruleRegistry.evaluateAll(
comparison);

resultRepository.save(
run,
comparison,
evaluation);

repeated.add(
new RepeatedEvaluationResult(
repetition,
baselineResult,
candidateResult,
evaluation));
}

return repeatedResultAggregator
.aggregate(
evaluationCase,
repeated);
}

三十九、Evaluator执行顺序

推荐:

1. Permission与安全
2. Output Schema
3. 数字与枚举
4. Tool Trace
5. RAG禁止证据
6. Citation ID
7. Relevancy
8. Fact Checking
9. LLM Judge
10. 成本与延迟

硬失败出现后,可跳过昂贵Judge。

四十、评测状态

public enum EvaluationDecision {
PASS,
FAIL,
NEEDS_REVIEW,
EVALUATOR_ERROR,
SKIPPED
}

Judge超时属于:

EVALUATOR_ERROR

不是候选业务失败。

Evaluator Error过高会导致整个Gate状态:

ERROR

四十一、Slice聚合

public record SliceMetrics(
String slice,
int caseCount,
double passRate,
double meanScore,
double minimumScore,
double candidateWinRate,
double baselineWinRate,
double evaluatorErrorRate,
double p95LatencyMs,
BigDecimal meanCost,
int hardFailureCount) {
}

Slice:

  • global;
  • contract-review;
  • rag-citation;
  • security;
  • tool-side-effect;
  • long-context;
  • multi-turn;
  • production-trace;
  • incident。

四十二、为什么全局提升仍可能失败

候选:

FAQ提升5%
合同审查下降8%

全局可能上升,但高风险能力退化。

Quality Gate按Slice独立检查。

四十三、Gate Policy

ai:
evaluation:
gate:
global:
minimum-pass-rate: 0.92
maximum-pass-rate-regression: 0.02
minimum-mean-score: 4.20
maximum-evaluator-error-rate: 0.01

slices:
contract-review:
minimum-pass-rate: 0.95
maximum-regression: 0.00

rag-citation:
minimum-claim-support-rate: 0.97
maximum-forbidden-document-rate: 0.00

security:
allowed-failures: 0

tool-side-effect:
duplicate-tool-call-rate: 0.00

operations:
maximum-p95-latency-regression: 0.15
maximum-cost-regression: 0.20

四十四、Gate规则

public record QualityGateRule(
String ruleId,
String slice,
MetricName metric,
ComparisonOperator operator,
BigDecimal threshold,
boolean compareWithBaseline,
GateSeverity severity) {
}

严重级别:

public enum GateSeverity {
BLOCKING,
WARNING
}

四十五、Quality Gate Engine

@Service
public class QualityGateEngine {

public QualityGateDecision evaluate(
QualityGatePolicy policy,
AggregatedEvaluationMetrics metrics,
List<CaseRunResult> cases) {

List<GateViolation> violations =
policy.rules()
.stream()
.map(rule ->
evaluateRule(
rule,
metrics,
cases))
.flatMap(
Optional::stream)
.toList();

boolean blocking =
violations.stream()
.anyMatch(violation ->
violation.severity()
== GateSeverity.BLOCKING);

boolean needsReview =
cases.stream()
.anyMatch(
CaseRunResult
::needsHumanReview);

if (blocking) {
return QualityGateDecision.fail(
violations);
}

if (needsReview) {
return QualityGateDecision
.needsReview(
violations);
}

return QualityGateDecision.pass(
violations);
}
}

四十六、统计波动

评测结果需要配对比较。

public record MetricDelta(
double baseline,
double candidate,
double delta,
double lowerConfidenceBound,
double upperConfidenceBound) {
}

如果Delta很小且置信区间跨越0,可标记:

NO_SIGNIFICANT_CHANGE

避免门禁因随机波动频繁抖动。

四十七、评测成本预算

public record EvaluationBudget(
BigDecimal maximumRunCost,
BigDecimal maximumJudgeCost,
int maximumModelCalls,
Duration maximumRunDuration) {
}

超出预算时:

RUN_ABORTED

不能使用不完整结果做PASS结论。

四十八、Judge缓存

相同输入与Manifest可缓存:

public record JudgeCacheKey(
String evaluatorManifestHash,
String caseHash,
String outputHash,
String evidenceHash) {
}

评测缓存与业务答案缓存完全隔离。

四十九、使用上一章长任务中心运行完整评测

完整回归可能持续几十分钟,直接绑定HTTP不可靠。

复用第12章任务中心:

POST /evaluation-runs

创建EVALUATION任务

队列

Runner Worker

按Case保存Checkpoint

SSE进度

生成Report

每个Case可作为Checkpoint,Worker崩溃后不必重跑全部。

五十、评测任务步骤

validate-manifest
load-dataset
execute-baseline
execute-candidate
run-deterministic-evaluators
run-llm-judges
aggregate-metrics
evaluate-gate
render-report
publish-check

五十一、CLI接入CI

@Component
@Profile("ai-eval-cli")
public class EvaluationCli
implements CommandLineRunner {

public void run(
String... args) {

EvaluationRunResult result =
runner.run(
configLoader
.fromArgs(args));

reportService.write(
result,
Path.of(
"build/ai-evaluation"));

int code =
switch (result.decision()) {
case PASS -> 0;
case NEEDS_REVIEW -> 2;
case FAIL, ERROR -> 1;
};

SpringApplication.exit(
context,
() -> code);
}
}

五十二、GitHub Actions

name: Spring AI Quality Gate

on:
pull_request:
workflow_dispatch:

jobs:
ai-quality-gate:
runs-on: ubuntulatest
timeout-minutes: 45

permissions:
contents: read
checks: write

steps:
uses: actions/checkout@v4

uses: actions/setupjava@v4
with:
distribution: temurin
java-version: "21"
cache: maven

name: Unit and contract tests
run: ./mvnw B test

name: Package candidate
run: ./mvnw B DskipTests package

name: Run PR evaluation
env:
EVAL_PROVIDER_KEY: ${{ secrets.EVAL_PROVIDER_KEY }}
BASELINE_RELEASE: production
CANDIDATE_RELEASE: ${{ github.sha }}
run: |
./mvnw -B spring-boot:run \\
-Pai-eval-fast \\
-Dspring-boot.run.profiles=ai-eval-cli \\
-Dspring-boot.run.arguments="\\
–baseline=${BASELINE_RELEASE} \\
–candidate=${CANDIDATE_RELEASE} \\
–report-dir=build/ai-evaluation"

name: Upload evaluation report
if: always()
uses: actions/uploadartifact@v4
with:
name: springaievaluation
path: build/aievaluation

将ai-quality-gate设为Required Status Check后,失败时不能合并受保护分支。

五十三、PR、Nightly和Release三套流程

PR Fast

  • 关键硬失败;
  • 50—200条;
  • 低成本Judge;
  • 1—3次重复。

Nightly Full

  • 完整回归;
  • 生产Trace;
  • 多Judge;
  • 统计分析;
  • 趋势。

Release Acceptance

  • 隐藏数据集;
  • 人工抽样;
  • Preview环境;
  • 影子流量;
  • 发布批准。

五十四、报告

Decision: FAIL

Global:
– Pass rate: 93.4% → 92.8%
– Mean score: 4.32 → 4.28
– P95 latency: +9%
– Cost: +14%

Blocking:
– security: 1 failure
– contract-review: 93.8% < 95%
– citation support: 95.9% < 97%

Top regressions:
– INCIDENT-2026-031
– CONTRACT-108
– RAG-CITATION-022

五十五、失败诊断

按第一次分叉分类:

Routing
Retrieval
Evidence
Generation
Tool
Output Validation
Judge Infrastructure
Operational

例如答案错误但检索正确:

Generation Regression

正确文档未进入Top K:

Retrieval Regression

五十六、发布后Canary

部署后运行少量固定Case:

security-canary
cross-tenant-canary
citation-canary
tool-side-effect-canary
structured-output-canary

Canary使用专用测试租户,不应污染真实数据。

五十七、影子流量

生产请求复制给Candidate:

用户仍收到Baseline
Candidate只记录结果

影子执行必须:

  • 禁止真实副作用;
  • 使用只读工具;
  • 脱敏;
  • 控制成本;
  • 遵守数据区域;
  • 不影响主链路延迟。

五十八、线上评测

线上可以抽样运行:

  • 格式检查;
  • 安全;
  • Groundedness;
  • 引用合法性;
  • 低成本Judge。

不要在主请求同步等待Judge。

主请求完成

Trace事件

异步线上Evaluator

五十九、线上质量信号

用户点踩
重复提问
人工改写
引用点击失败
工具失败
回退模型
拒答
超时
高成本
转人工
业务结果失败

线上信号用于发现问题,不直接等同于标准答案。

六十、失败回流

线上失败

候选池

脱敏

专家标注

新Incident Case

回归数据集新版本

修复后重新评测

每次严重事故至少新增一个硬失败Case。

六十一、Judge自身Canary

Judge模型也可能升级。

固定Judge Canary:

  • 明确PASS;
  • 明确FAIL;
  • 位置交换;
  • 冗长变体;
  • 多语言;
  • Prompt Injection。

监控:

judge_repeat_consistency
judge_position_flip_rate
judge_human_agreement
judge_canary_score

Judge漂移时冻结自动门禁。

六十二、模型升级流程

新模型Profile

契约测试

开发数据集

完整离线回归

隐藏验收

影子流量

小比例Canary

逐步扩大

不要直接把latest别名指向新模型并全量上线。

六十三、Prompt升级流程

Prompt本身作为Artifact:

promptId
version
hash
owner
changeReason
approvedBy

每次变更必须关联:

  • Evaluation Run;
  • Gate Result;
  • Release ID。

六十四、知识索引升级流程

新Chunk策略或Embedding模型需要:

索引构建

检索Recall评测

引用评测

答案评测

影子双查

切换别名

只评测最终答案很难定位索引问题。

六十五、Tool目录升级流程

Tool描述变化可能改变模型选择。

需要比较:

  • 选Tool准确率;
  • 参数;
  • 调用次数;
  • 权限;
  • 幂等;
  • 副作用;
  • 延迟。

新Tool默认不应对所有Agent自动开放。

六十六、监控指标

ai_eval_run_total{
status,
gate_policy
}

ai_eval_pass_rate{
slice
}

ai_eval_score{
evaluator,
slice
}

ai_eval_candidate_win_rate{
slice
}

ai_eval_hard_failure_total{
category
}

ai_eval_evaluator_error_rate{
evaluator
}

ai_eval_latency_seconds{
target
}

ai_eval_cost_total{
target,
evaluator
}

ai_eval_gate_violation_total{
rule,
severity
}

ai_judge_position_flip_rate

ai_judge_repeat_consistency

ai_canary_failure_total{
canary_type
}

六十七、测试场景

1. Manifest不一致

评测声明V42,运行时V41,Run必须ERROR。

2. 全局提升但安全失败

Gate必须FAIL。

3. Judge 5xx

Case为Evaluator Error,不直接判Candidate失败。

4. Pairwise位置反转

进入Needs Review。

5. 模型随机失败

重复采样通过率低于阈值时FAIL。

6. RAG禁止文档

即使最终答案没泄露,只要候选Evidence出现禁止文档,也FAIL。

7. 工具重复

最终结果正确但重复执行副作用,FAIL。

8. 成本回归

质量提升但成本超出策略,阻断或人工审批。

六十八、发布回滚

发布记录:

public record AiReleaseRecord(
String releaseId,
AiReleaseManifest manifest,
String evaluationRunId,
QualityGateDecision gate,
Instant deployedAt,
String previousReleaseId) {
}

线上Canary失败时,回滚:

  • 模型路由Profile;
  • Prompt别名;
  • 知识索引别名;
  • Tool目录;
  • 应用版本。

AI发布不是只回滚Docker镜像。

六十九、上线检查清单

□ 已建立五层测试体系
□ Release Manifest覆盖模型、Prompt、索引、Tool和Memory
□ 运行时能够回显真实Manifest
□ 数据集包含黄金、生产Trace、安全和事故样本
□ 数据集与Case均版本化
□ 确定性规则优先于Judge
□ RAG分别评测检索、证据、Claim和引用
□ Agent评测完整Tool轨迹
□ Judge使用固定Manifest并经过人工校准
□ Pairwise执行位置互换
□ 高风险Case重复运行
□ 指标按业务Slice聚合
□ 安全和副作用硬失败零容忍
□ Gate同时检查质量、成本和延迟
□ PR、Nightly和Release分层运行
□ Required Status Check已经启用
□ 发布后运行Canary和影子流量
□ Judge本身有Canary
□ 线上失败能够回流数据集
□ 发布记录支持模型、Prompt和索引回滚

七十、本章完整链路

代码/Prompt/模型/索引变更

生成Candidate Manifest

确定性单元与契约测试

加载冻结数据集

Baseline/Candidate重复执行

规则Evaluator

Spring AI Evaluator

LLM Judge

Slice与统计聚合

Quality Gate

GitHub Required Check

Preview与影子

Canary

发布

线上评测与失败回流

总结

企业级Spring AI应用不能依赖“人工试几个问题感觉还行”发布,也不能把单次LLM Judge分数当作绝对标准。

可靠质量体系需要:

Release Manifest
+版本化黄金数据集
+确定性测试
+Spring AI Evaluator
+校准Judge
+重复采样
+业务Slice
+硬失败
+CI质量门禁
+线上反馈闭环

当每一次模型、Prompt、RAG和Tool变更都必须通过相同的可复现基准,并且生产事故会永久进入回归集,AI系统才具备持续升级而不持续失控的工程基础。

下一篇将继续实现:

Spring AI企业级应用实战(14):容器化部署、灰度发布与自动回滚。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Spring AI企业级应用实战(13):测试、黄金数据集与发布质量门禁
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!