feat(harness): add information gain stop and audit
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.superbiz.agent.harness.release;
|
||||
|
||||
import com.superbiz.agent.harness.agent.DiagnosisAgentExecution;
|
||||
import com.superbiz.agent.harness.contract.DiagnosisDraft;
|
||||
import com.superbiz.agent.harness.audit.DiagnosisTraceRecorder;
|
||||
import com.superbiz.agent.harness.audit.TraceAuditEvents;
|
||||
@@ -15,10 +16,13 @@ import com.superbiz.agent.harness.guard.evidence.VerifiedEvidenceSnapshot;
|
||||
import com.superbiz.agent.harness.guard.semantic.SemanticGuard;
|
||||
import com.superbiz.agent.harness.guard.semantic.SemanticGuardDecision;
|
||||
import com.superbiz.agent.harness.guard.semantic.SemanticGuardInput;
|
||||
import com.superbiz.agent.harness.progress.DiagnosisProgressSnapshot;
|
||||
import com.superbiz.agent.harness.progress.DiagnosisStopReason;
|
||||
import com.superbiz.agent.harness.retry.RetryExecutionException;
|
||||
import com.superbiz.agent.harness.retry.RetryFailure;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
|
||||
public final class DiagnosisReleaseUseCase {
|
||||
|
||||
@@ -50,12 +54,44 @@ public final class DiagnosisReleaseUseCase {
|
||||
}
|
||||
|
||||
public DiagnosisReleaseResult execute(RunContext context, String query, DiagnosisDraft draft) {
|
||||
return execute(context, query, DiagnosisAgentExecution.completed(
|
||||
Objects.requireNonNull(draft, "draft must not be null"),
|
||||
DiagnosisProgressSnapshot.empty()));
|
||||
}
|
||||
|
||||
public DiagnosisReleaseResult execute(
|
||||
RunContext context, String query, DiagnosisAgentExecution execution) {
|
||||
Objects.requireNonNull(context, "context must not be null");
|
||||
if (query == null || query.isBlank()) {
|
||||
throw new IllegalArgumentException("query must not be blank");
|
||||
}
|
||||
Objects.requireNonNull(draft, "draft must not be null");
|
||||
Objects.requireNonNull(execution, "execution must not be null");
|
||||
|
||||
DiagnosisDraft draft = execution.draft();
|
||||
if (draft == null) {
|
||||
return releaseControlledStop(context, execution.progress(), execution.stopReason());
|
||||
}
|
||||
if (draft.conclusion() == null) {
|
||||
return releaseNoConclusion(context, draft, execution.progress());
|
||||
}
|
||||
return releaseConclusion(context, query, draft);
|
||||
}
|
||||
|
||||
public DiagnosisReleaseResult releaseInvalidDraft(
|
||||
RunContext context, DiagnosisProgressSnapshot progress) {
|
||||
Objects.requireNonNull(context, "context must not be null");
|
||||
Objects.requireNonNull(progress, "progress must not be null");
|
||||
if (!progress.hasObservedFacts()) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid Diagnosis Draft has no verified publishable progress");
|
||||
}
|
||||
return progressFallback(context,
|
||||
fallbackFactory.insufficientEvidence(progress, List.of()),
|
||||
FallbackType.INSUFFICIENT_EVIDENCE);
|
||||
}
|
||||
|
||||
private DiagnosisReleaseResult releaseConclusion(
|
||||
RunContext context, String query, DiagnosisDraft draft) {
|
||||
DiagnosisDraft candidate = draft;
|
||||
EvidenceGuardResult evidence = evidenceGuard.validate(context, candidate);
|
||||
traceRecorder.record(TraceAuditEvents.evidenceValidation(
|
||||
@@ -101,6 +137,61 @@ public final class DiagnosisReleaseUseCase {
|
||||
return DiagnosisReleaseResult.fallback(fallbackFactory.semanticUnsupported(snapshot));
|
||||
}
|
||||
|
||||
private DiagnosisReleaseResult releaseNoConclusion(
|
||||
RunContext context, DiagnosisDraft draft, DiagnosisProgressSnapshot progress) {
|
||||
EvidenceGuardResult evidence = evidenceGuard.validateNoConclusionReferences(context, draft);
|
||||
traceRecorder.record(TraceAuditEvents.evidenceValidation(
|
||||
context, TraceEventType.EVIDENCE_GUARD_INITIAL, evidence, draft));
|
||||
if (!evidence.valid()) {
|
||||
return evidenceFailure(context, evidence);
|
||||
}
|
||||
|
||||
List<String> missingInfo = missingInfo(draft);
|
||||
if (progress.hasObservedFacts()) {
|
||||
return progressFallback(context,
|
||||
fallbackFactory.insufficientEvidence(progress, missingInfo),
|
||||
FallbackType.INSUFFICIENT_EVIDENCE);
|
||||
}
|
||||
if (!missingInfo.isEmpty()) {
|
||||
return progressFallback(context,
|
||||
fallbackFactory.missingRequiredContext(missingInfo),
|
||||
FallbackType.MISSING_REQUIRED_CONTEXT);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"No-conclusion Diagnosis has neither verified progress nor missing context");
|
||||
}
|
||||
|
||||
private DiagnosisReleaseResult releaseControlledStop(
|
||||
RunContext context,
|
||||
DiagnosisProgressSnapshot progress,
|
||||
DiagnosisStopReason stopReason) {
|
||||
if (stopReason != DiagnosisStopReason.INFORMATION_SATURATED
|
||||
&& stopReason != DiagnosisStopReason.BUDGET_LIMIT_REACHED) {
|
||||
throw new IllegalStateException("Unsupported Diagnosis stop reason");
|
||||
}
|
||||
if (!progress.hasObservedFacts()) {
|
||||
throw new IllegalStateException(
|
||||
"Controlled Diagnosis stop has no verified publishable progress");
|
||||
}
|
||||
return progressFallback(context,
|
||||
fallbackFactory.insufficientEvidence(progress, List.of()),
|
||||
FallbackType.INSUFFICIENT_EVIDENCE);
|
||||
}
|
||||
|
||||
private DiagnosisReleaseResult progressFallback(
|
||||
RunContext context,
|
||||
com.superbiz.agent.harness.contract.SafeFallback fallback,
|
||||
FallbackType type) {
|
||||
traceRecorder.record(TraceAuditEvents.releaseDecision(
|
||||
context, com.superbiz.agent.harness.contract.ReleaseOutcome.FALLBACK, type));
|
||||
return DiagnosisReleaseResult.fallback(fallback);
|
||||
}
|
||||
|
||||
private List<String> missingInfo(DiagnosisDraft draft) {
|
||||
return draft.limitations() == null
|
||||
? List.of() : draft.limitations().missingInfo();
|
||||
}
|
||||
|
||||
private DiagnosisReleaseResult evidenceFailure(
|
||||
RunContext context, EvidenceGuardResult evidence) {
|
||||
traceRecorder.record(TraceAuditEvents.releaseDecision(
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.audit.ModelCallComponent;
|
||||
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
|
||||
import com.superbiz.agent.harness.core.RunContext;
|
||||
import com.superbiz.agent.harness.guard.evidence.EvidenceViolation;
|
||||
@@ -92,7 +93,8 @@ public final class EvidenceRepair {
|
||||
context.retryPolicies().evidenceRepair(),
|
||||
() -> {
|
||||
DiagnosisDraft repaired = parse(modelCall.call(
|
||||
context, modelPrompt, limits.timeout(), limits.maxOutputBytes()));
|
||||
context, ModelCallComponent.EVIDENCE_REPAIR,
|
||||
modelPrompt, limits.timeout(), limits.maxOutputBytes()));
|
||||
if (!originalSemantics.hasSameUserVisibleSemantics(
|
||||
SemanticDraftView.from(repaired))) {
|
||||
throw new GuardModelCallException(
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 com.superbiz.agent.harness.progress.DiagnosisProgressSnapshot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -53,6 +54,47 @@ public final class SafeFallbackFactory {
|
||||
List.of());
|
||||
}
|
||||
|
||||
public SafeFallback insufficientEvidence(
|
||||
DiagnosisProgressSnapshot progress, List<String> missingInfo) {
|
||||
Objects.requireNonNull(progress, "progress must not be null");
|
||||
if (!progress.hasObservedFacts()) {
|
||||
throw new IllegalArgumentException(
|
||||
"insufficient-evidence fallback requires verified progress");
|
||||
}
|
||||
List<String> safeMissingInfo = boundedMissingInfo(missingInfo);
|
||||
List<String> limitations = new ArrayList<>(progress.limitations());
|
||||
limitations.add("当前已检查范围不足以支持根因结论");
|
||||
safeMissingInfo.forEach(item -> limitations.add("仍缺少:" + item));
|
||||
return fallback(
|
||||
FallbackType.INSUFFICIENT_EVIDENCE,
|
||||
progress.verifiedSources(),
|
||||
"已完成有限范围的检查,但现有证据不足以确认根因",
|
||||
List.copyOf(limitations),
|
||||
List.of(safeMissingInfo.isEmpty()
|
||||
? "补充故障对象、发生时间、错误信息或新的可查询范围后重试"
|
||||
: "补充缺失信息后,在新的明确范围内继续诊断"),
|
||||
"DIAGNOSIS_COLLECTION",
|
||||
progress.observedFacts(),
|
||||
List.of());
|
||||
}
|
||||
|
||||
public SafeFallback missingRequiredContext(List<String> missingInfo) {
|
||||
List<String> safeMissingInfo = boundedMissingInfo(missingInfo);
|
||||
if (safeMissingInfo.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"missing-context fallback requires missing information");
|
||||
}
|
||||
return fallback(
|
||||
FallbackType.MISSING_REQUIRED_CONTEXT,
|
||||
List.of(),
|
||||
"当前缺少执行定向诊断所需的信息,暂时无法开始有效查询或确认根因",
|
||||
safeMissingInfo.stream().map(item -> "缺少:" + item).toList(),
|
||||
List.of("补充上述故障上下文后重试"),
|
||||
"DIAGNOSIS_INPUT",
|
||||
List.of(),
|
||||
List.of());
|
||||
}
|
||||
|
||||
private SafeFallback fallback(FallbackType type,
|
||||
List<SafeFallback.VerifiedSource> sources,
|
||||
String message,
|
||||
@@ -100,6 +142,20 @@ public final class SafeFallbackFactory {
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
private List<String> boundedMissingInfo(List<String> missingInfo) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String item : missingInfo == null ? List.<String>of() : missingInfo) {
|
||||
String safe = bounded(item);
|
||||
if (safe != null && !result.contains(safe)) {
|
||||
result.add(safe);
|
||||
}
|
||||
if (result.size() >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
private String bounded(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user