feat(harness): improve trace fallback and reasoning audit

This commit is contained in:
zhuyongxin
2026-07-23 17:52:01 +08:00
parent 8fbc443f76
commit e20249c5d9
38 changed files with 1355 additions and 75 deletions
@@ -1,6 +1,10 @@
package com.superbiz.agent.harness.release;
import com.superbiz.agent.harness.contract.DiagnosisDraft;
import com.superbiz.agent.harness.audit.DiagnosisTraceRecorder;
import com.superbiz.agent.harness.audit.TraceAuditEvents;
import com.superbiz.agent.harness.audit.TraceEventType;
import com.superbiz.agent.harness.contract.FallbackType;
import com.superbiz.agent.harness.contract.SemanticVerdict;
import com.superbiz.agent.harness.core.BudgetExceededException;
import com.superbiz.agent.harness.core.RunAbortedException;
@@ -22,16 +26,27 @@ public final class DiagnosisReleaseUseCase {
private final EvidenceRepair evidenceRepair;
private final SemanticGuard semanticGuard;
private final SafeFallbackFactory fallbackFactory;
private final DiagnosisTraceRecorder traceRecorder;
public DiagnosisReleaseUseCase(EvidenceGuard evidenceGuard,
EvidenceRepair evidenceRepair,
SemanticGuard semanticGuard,
SafeFallbackFactory fallbackFactory) {
this(evidenceGuard, evidenceRepair, semanticGuard, fallbackFactory,
DiagnosisTraceRecorder.noop());
}
public DiagnosisReleaseUseCase(EvidenceGuard evidenceGuard,
EvidenceRepair evidenceRepair,
SemanticGuard semanticGuard,
SafeFallbackFactory fallbackFactory,
DiagnosisTraceRecorder traceRecorder) {
this.evidenceGuard = Objects.requireNonNull(evidenceGuard, "evidenceGuard must not be null");
this.evidenceRepair = Objects.requireNonNull(evidenceRepair, "evidenceRepair must not be null");
this.semanticGuard = Objects.requireNonNull(semanticGuard, "semanticGuard must not be null");
this.fallbackFactory = Objects.requireNonNull(
fallbackFactory, "fallbackFactory must not be null");
this.traceRecorder = Objects.requireNonNull(traceRecorder, "traceRecorder must not be null");
}
public DiagnosisReleaseResult execute(RunContext context, String query, DiagnosisDraft draft) {
@@ -43,16 +58,20 @@ public final class DiagnosisReleaseUseCase {
DiagnosisDraft candidate = draft;
EvidenceGuardResult evidence = evidenceGuard.validate(context, candidate);
traceRecorder.record(TraceAuditEvents.evidenceValidation(
context, TraceEventType.EVIDENCE_GUARD_INITIAL, evidence, candidate));
if (!evidence.valid()) {
try {
candidate = evidenceRepair.repair(context, query, draft, evidence.violations());
evidence = evidenceGuard.validate(context, candidate);
traceRecorder.record(TraceAuditEvents.evidenceValidation(
context, TraceEventType.EVIDENCE_GUARD_RECHECK, evidence, candidate));
} catch (RuntimeException exception) {
propagateTerminal(exception);
return evidenceFailure();
return evidenceFailure(context, evidence);
}
if (!evidence.valid()) {
return evidenceFailure();
return evidenceFailure(context, evidence);
}
}
@@ -63,18 +82,32 @@ public final class DiagnosisReleaseUseCase {
context, SemanticGuardInput.from(query, candidate, snapshot));
} catch (RuntimeException exception) {
propagateTerminal(exception);
traceRecorder.record(TraceAuditEvents.semanticUnavailable(context));
traceRecorder.record(TraceAuditEvents.releaseDecision(
context, com.superbiz.agent.harness.contract.ReleaseOutcome.FALLBACK,
FallbackType.SEMANTIC_UNAVAILABLE));
return DiagnosisReleaseResult.fallback(
fallbackFactory.semanticUnavailable(snapshot));
}
return decision.verdict() == SemanticVerdict.SUPPORTED
? DiagnosisReleaseResult.success(candidate, snapshot)
: DiagnosisReleaseResult.fallback(
fallbackFactory.semanticUnsupported(snapshot));
traceRecorder.record(TraceAuditEvents.semanticDecision(context, decision.verdict()));
if (decision.verdict() == SemanticVerdict.SUPPORTED) {
traceRecorder.record(TraceAuditEvents.releaseDecision(
context, com.superbiz.agent.harness.contract.ReleaseOutcome.SUCCESS, null));
return DiagnosisReleaseResult.success(candidate, snapshot);
}
traceRecorder.record(TraceAuditEvents.releaseDecision(
context, com.superbiz.agent.harness.contract.ReleaseOutcome.FALLBACK,
FallbackType.SEMANTIC_UNSUPPORTED));
return DiagnosisReleaseResult.fallback(fallbackFactory.semanticUnsupported(snapshot));
}
private DiagnosisReleaseResult evidenceFailure() {
private DiagnosisReleaseResult evidenceFailure(
RunContext context, EvidenceGuardResult evidence) {
traceRecorder.record(TraceAuditEvents.releaseDecision(
context, com.superbiz.agent.harness.contract.ReleaseOutcome.FALLBACK,
FallbackType.EVIDENCE_VALIDATION_FAILED));
return DiagnosisReleaseResult.fallback(
fallbackFactory.evidenceValidationFailed());
fallbackFactory.evidenceValidationFailed(evidence.violations()));
}
private void propagateTerminal(RuntimeException exception) {
@@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.superbiz.agent.harness.contract.DiagnosisDraft;
import com.superbiz.agent.harness.audit.DiagnosisTraceRecorder;
import com.superbiz.agent.harness.audit.TraceAuditEvents;
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
import com.superbiz.agent.harness.core.RunContext;
import com.superbiz.agent.harness.guard.evidence.EvidenceViolation;
@@ -32,6 +34,7 @@ public final class EvidenceRepair {
private final ObjectReader draftReader;
private final EvidenceRepairLimits limits;
private final Consumer<RetryAttempt> attemptRecorder;
private final DiagnosisTraceRecorder traceRecorder;
private final String prompt;
public EvidenceRepair(DiagnosisHarnessCore core,
@@ -40,6 +43,17 @@ public final class EvidenceRepair {
ObjectMapper objectMapper,
EvidenceRepairLimits limits,
Consumer<RetryAttempt> attemptRecorder) {
this(core, retryExecutor, modelCall, objectMapper, limits,
attemptRecorder, DiagnosisTraceRecorder.noop());
}
public EvidenceRepair(DiagnosisHarnessCore core,
HarnessRetryExecutor retryExecutor,
GuardModelCall modelCall,
ObjectMapper objectMapper,
EvidenceRepairLimits limits,
Consumer<RetryAttempt> attemptRecorder,
DiagnosisTraceRecorder traceRecorder) {
this.core = Objects.requireNonNull(core, "core must not be null");
this.retryExecutor = Objects.requireNonNull(retryExecutor, "retryExecutor must not be null");
this.modelCall = Objects.requireNonNull(modelCall, "modelCall must not be null");
@@ -50,6 +64,7 @@ public final class EvidenceRepair {
this.limits = Objects.requireNonNull(limits, "limits must not be null");
this.attemptRecorder = Objects.requireNonNull(
attemptRecorder, "attemptRecorder must not be null");
this.traceRecorder = Objects.requireNonNull(traceRecorder, "traceRecorder must not be null");
this.prompt = EvidenceRepairPrompt.load();
}
@@ -87,7 +102,10 @@ public final class EvidenceRepair {
return repaired;
},
this::classify,
attemptRecorder);
attempt -> {
attemptRecorder.accept(attempt);
traceRecorder.record(TraceAuditEvents.evidenceRepairAttempt(context, attempt));
});
}
private DiagnosisDraft parse(String output) {
@@ -2,50 +2,109 @@ package com.superbiz.agent.harness.release;
import com.superbiz.agent.harness.contract.FallbackType;
import com.superbiz.agent.harness.contract.SafeFallback;
import com.superbiz.agent.harness.guard.evidence.EvidenceViolation;
import com.superbiz.agent.harness.guard.evidence.VerifiedEvidence;
import com.superbiz.agent.harness.guard.evidence.VerifiedEvidenceSnapshot;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class SafeFallbackFactory {
public SafeFallback evidenceValidationFailed() {
private static final int MAX_OBSERVED_FACTS = 12;
private static final int MAX_SUMMARY_CHARS = 320;
public SafeFallback evidenceValidationFailed(List<EvidenceViolation> violations) {
return fallback(
FallbackType.EVIDENCE_VALIDATION_FAILED,
List.of(),
"当前证据无法完成真实性校验,无法确认根因",
"证据引用校验未通过",
"重新收集当前诊断范围内的证据后再发起诊断");
"已完成证据引用检查,但当前报告无法通过真实性校验,因此不能确认根因",
List.of("证据引用或报告结构校验未通过"),
List.of("根据 validation_issues 修正报告结构,或补充对应范围的证据后重试"),
"EVIDENCE_VALIDATION",
List.of(),
issues(violations));
}
public SafeFallback semanticUnsupported(VerifiedEvidenceSnapshot snapshot) {
return fallback(
FallbackType.SEMANTIC_UNSUPPORTED,
sources(snapshot),
"当前证据不足,无法确认根因",
"语义校验未通过",
"补充当前缺失的数据后重新发起诊断");
"已收集到可验证事实,但这些事实不足以支持当前根因结论",
List.of("语义校验未通过,已确认事实仍可用于后续排查"),
List.of("围绕 observed_facts 补充缺失的实时日志、指标或数据库证据后重试"),
"SEMANTIC_VALIDATION",
facts(snapshot),
List.of());
}
public SafeFallback semanticUnavailable(VerifiedEvidenceSnapshot snapshot) {
return fallback(
FallbackType.SEMANTIC_UNAVAILABLE,
sources(snapshot),
"当前证据暂时无法完成语义校验,无法确认根因",
"语义校验暂不可用",
"稍后重试或补充当前缺失的数据");
"已收集到可验证事实,但当前无法完成语义校验,因此暂不发布根因结论",
List.of("语义校验暂不可用"),
List.of("稍后重试;已确认事实可继续用于人工排查"),
"SEMANTIC_VALIDATION",
facts(snapshot),
List.of());
}
private SafeFallback fallback(FallbackType type,
List<SafeFallback.VerifiedSource> sources,
String message,
String limitation,
String nextStep) {
List<String> limitations,
List<String> nextSteps,
String failureStage,
List<SafeFallback.ObservedFact> observedFacts,
List<SafeFallback.ValidationIssue> validationIssues) {
return new SafeFallback(
type, null, message, sources, List.of(limitation), List.of(nextStep));
type, null, message, sources, limitations, nextSteps,
failureStage, observedFacts, validationIssues);
}
private List<SafeFallback.VerifiedSource> sources(VerifiedEvidenceSnapshot snapshot) {
return Objects.requireNonNull(snapshot, "snapshot must not be null").verifiedSources();
}
private List<SafeFallback.ObservedFact> facts(VerifiedEvidenceSnapshot snapshot) {
Objects.requireNonNull(snapshot, "snapshot must not be null");
Map<String, SafeFallback.ObservedFact> unique = new LinkedHashMap<>();
for (var analysis : snapshot.analyses()) {
for (VerifiedEvidence evidence : analysis.evidence()) {
if (unique.size() >= MAX_OBSERVED_FACTS) {
return List.copyOf(unique.values());
}
String summary = bounded(evidence.excerpt());
if (summary == null) {
summary = "已验证结构化证据,具体值保留在受限审计记录中";
}
String key = evidence.sourceType() + '\u0000' + evidence.source()
+ '\u0000' + evidence.scope() + '\u0000' + summary;
unique.putIfAbsent(key, new SafeFallback.ObservedFact(
evidence.sourceType(), evidence.source(), evidence.scope(), summary));
}
}
return List.copyOf(unique.values());
}
private List<SafeFallback.ValidationIssue> issues(List<EvidenceViolation> violations) {
List<SafeFallback.ValidationIssue> result = new ArrayList<>();
for (EvidenceViolation violation : violations == null ? List.<EvidenceViolation>of() : violations) {
result.add(new SafeFallback.ValidationIssue(
violation.code().name(), violation.target()));
}
return List.copyOf(result);
}
private String bounded(String value) {
if (value == null || value.isBlank()) {
return null;
}
return value.length() <= MAX_SUMMARY_CHARS
? value : value.substring(0, MAX_SUMMARY_CHARS);
}
}