feat(eval): add evidence pipeline acceptance closure

This commit is contained in:
aruo
2026-07-09 00:47:48 +08:00
parent a77c947cd4
commit db0f229285
46 changed files with 1434 additions and 53 deletions
@@ -100,12 +100,12 @@ class DiagnosisEvalBaselineDiffTest {
}
private void degradeRedisCase(DiagnosisEvalReport report) {
report.setPassedCases(7);
report.setPassRate(0.875);
report.setPassedCases(9);
report.setPassRate(0.9);
report.setAverageToolCallCount(3.0);
report.setAverageDurationMs(44875.0);
report.setAverageDurationMs(39800.0);
report.setVerdictDistribution(new LinkedHashMap<>());
report.getVerdictDistribution().put("PASS", 2L);
report.getVerdictDistribution().put("PASS", 4L);
report.getVerdictDistribution().put("LOW_CONFID", 4L);
report.getVerdictDistribution().put("REJECT", 2L);
@@ -24,13 +24,23 @@ class DiagnosisTraceEvaluatorTest {
DiagnosisEvalReport report = evaluator.evaluate(cases, Path.of("mvp/eval/fixtures"));
assertEquals(8, report.getTotalCases());
assertEquals(8, report.getPassedCases());
assertEquals(10, report.getTotalCases());
assertEquals(10, report.getPassedCases());
assertEquals(1.0, report.getPassRate(), 0.001);
assertEquals(2L, report.getVerdictDistribution().get("PASS"));
assertEquals(4L, report.getVerdictDistribution().get("PASS"));
assertEquals(5L, report.getVerdictDistribution().get("LOW_CONFID"));
assertEquals(1L, report.getVerdictDistribution().get("REJECT"));
DiagnosisEvalResult narrowHighCpu = result(report, "narrow-highcpu-observation");
assertTrue(narrowHighCpu.isPassed());
assertEquals("gatekeeper-rules-v1", narrowHighCpu.getGatekeeperRuleSetVersion());
assertEquals("pass", narrowHighCpu.getGatekeeperStatus());
DiagnosisEvalResult hikariNoEvidence = result(report, "hikari-no-evidence-negative-observation");
assertTrue(hikariNoEvidence.isPassed());
assertEquals("gatekeeper-rules-v1", hikariNoEvidence.getGatekeeperRuleSetVersion());
assertEquals("pass", hikariNoEvidence.getGatekeeperStatus());
DiagnosisEvalResult payment = result(report, "payment-timeout");
assertTrue(payment.isPassed());
assertTrue(payment.getEvidenceCoverage().get("lookup_knowledge"));
@@ -153,6 +163,39 @@ class DiagnosisTraceEvaluatorTest {
assertTrue(result.getFailedChecks().contains("gatekeeper fail cannot have PASS verdict"));
}
@Test
void evaluateFailsWhenGatekeeperRuleSetVersionMismatches() {
DiagnosisEvalCase evalCase = DiagnosisEvalCase.builder()
.id("rule-version")
.title("Rule version")
.expectedRootCauseKeywords(List.of())
.requiredEvidenceTools(List.of())
.allowedVerdicts(List.of("PASS"))
.expectedGatekeeperRuleSetVersion("gatekeeper-rules-v1")
.build();
DiagnosisTraceResponse trace = DiagnosisTraceResponse.builder()
.session(DiagnosisTraceResponse.SessionTrace.builder()
.answer("安全回答")
.selfEvaluation(java.util.Map.of(
"verifier_evaluation", java.util.Map.of(
"verdict", "PASS",
"gatekeeper_result", java.util.Map.of(
"status", "pass",
"rule_set_version", "old-rules"
)
)))
.build())
.toolInvocations(List.of())
.build();
DiagnosisEvalResult result = evaluator.evaluate(evalCase, trace);
assertFalse(result.isPassed());
assertTrue(result.getFailedChecks().contains(
"gatekeeper rule set version not expected: old-rules"));
}
@Test
void evaluateFailsWhenUnsupportedClaimLeaksIntoFinalAnswer() {
DiagnosisEvalCase evalCase = DiagnosisEvalCase.builder()
@@ -8,12 +8,25 @@ import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class ExecutorGatekeeperServiceTest {
@Test
void ruleCatalogLoadsDefaultMetadata() {
GatekeeperRuleCatalog catalog = GatekeeperRuleCatalog.loadDefault(new com.fasterxml.jackson.databind.ObjectMapper());
assertEquals("gatekeeper-rules-v1", catalog.version());
assertFalse(catalog.auditRules().isEmpty());
assertTrue(catalog.auditRules().stream()
.anyMatch(rule -> "evidence.raw_path".equals(rule.get("id"))));
assertEquals(0.5, catalog.doubleParameter("evidence.excerpt_mismatch",
"min_token_overlap", 0.0), 0.001);
}
@Test
void validatePassesForExecutorEvidenceV2WithMatchingInvocation() {
ToolInvocationRepository repository = mock(ToolInvocationRepository.class);
@@ -30,6 +43,7 @@ class ExecutorGatekeeperServiceTest {
assertEquals("pass", result.get("status"));
assertEquals("none", result.get("severity"));
assertRuleAudit(result);
assertTrue(((List<?>) result.get("failed_rules")).isEmpty());
}
@@ -49,6 +63,7 @@ class ExecutorGatekeeperServiceTest {
assertEquals("pass", result.get("status"));
assertEquals("none", result.get("severity"));
assertRuleAudit(result);
assertTrue(((List<?>) result.get("failed_rules")).isEmpty());
}
@@ -115,6 +130,7 @@ class ExecutorGatekeeperServiceTest {
assertEquals("fail", result.get("status"));
assertEquals("reject", result.get("severity"));
assertRuleAudit(result);
assertTrue(((List<?>) result.get("failed_rules")).contains("evidence.excerpt_mismatch"));
}
@@ -167,9 +183,22 @@ class ExecutorGatekeeperServiceTest {
assertEquals("fail", result.get("status"));
assertEquals("reject", result.get("severity"));
assertRuleAudit(result);
assertTrue(((List<?>) result.get("failed_rules")).contains("evidence.invocation_ref"));
}
@Test
void failResultIncludesRuleAuditMetadata() {
ToolInvocationRepository repository = mock(ToolInvocationRepository.class);
ExecutorGatekeeperService service = new ExecutorGatekeeperService(repository);
Map<String, Object> result = service.fail("gatekeeper.internal_error", "gatekeeper", "boom");
assertEquals("fail", result.get("status"));
assertEquals("reject", result.get("severity"));
assertRuleAudit(result);
}
@Test
void validateFailsForToolNameMismatch() {
ToolInvocationRepository repository = mock(ToolInvocationRepository.class);
@@ -329,4 +358,13 @@ class ExecutorGatekeeperServiceTest {
"missing_info", List.of()
));
}
private void assertRuleAudit(Map<String, Object> result) {
assertEquals("gatekeeper-rules-v1", result.get("rule_set_version"));
assertTrue(result.get("rules") instanceof List<?>);
List<?> rules = (List<?>) result.get("rules");
assertFalse(rules.isEmpty());
assertTrue(rules.stream().anyMatch(rule ->
rule instanceof Map<?, ?> map && "evidence.raw_path".equals(map.get("id"))));
}
}