feat(harness): improve trace fallback and reasoning audit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user