feat(harness): add information gain stop and audit

This commit is contained in:
aruo
2026-07-27 01:03:34 +08:00
parent de5a5b09d9
commit d0452184ee
92 changed files with 5019 additions and 122 deletions
@@ -1,6 +1,7 @@
package com.superbiz.agent.harness.audit;
import com.superbiz.agent.harness.contract.DiagnosisDraft;
import com.superbiz.agent.harness.agent.DiagnosisAgentOutputException;
import com.superbiz.agent.harness.contract.FallbackType;
import com.superbiz.agent.harness.contract.IntentType;
import com.superbiz.agent.harness.contract.ReleaseOutcome;
@@ -8,6 +9,8 @@ import com.superbiz.agent.harness.contract.SemanticVerdict;
import com.superbiz.agent.harness.core.RunContext;
import com.superbiz.agent.harness.guard.evidence.EvidenceGuardResult;
import com.superbiz.agent.harness.guard.evidence.EvidenceViolation;
import com.superbiz.agent.harness.progress.DiagnosisProgressSnapshotState;
import com.superbiz.agent.harness.progress.InformationGain;
import com.superbiz.agent.harness.retry.RetryAttempt;
import java.util.ArrayList;
@@ -33,11 +36,27 @@ public final class TraceAuditEvents {
public static DiagnosisTraceAuditEvent runFinished(
RunContext context, IntentType intent, ReleaseOutcome outcome, int durationMs) {
var budget = context.budget().snapshot();
ModelCallLedger.Snapshot modelCalls = context.modelCalls().snapshot();
Map<String, Object> details = new LinkedHashMap<>();
if (intent != null) {
details.put("intent", intent.name());
}
details.put("release_outcome", outcome.name());
details.put("model_call_count", budget.modelCalls());
details.put("run_input_tokens", budget.inputTokens());
details.put("run_output_tokens", budget.outputTokens());
details.put("run_total_tokens", budget.totalTokens());
details.put("audited_model_call_count", modelCalls.auditedCallCount());
details.put("audited_input_tokens", modelCalls.inputTokens());
details.put("audited_output_tokens", modelCalls.outputTokens());
details.put("audited_total_tokens", modelCalls.totalTokens());
details.put("usage_unavailable_count", modelCalls.usageUnavailableCount());
details.put("tokens_reconciled", budget.modelCalls() == modelCalls.auditedCallCount()
&& modelCalls.usageUnavailableCount() == 0
&& budget.inputTokens() == modelCalls.inputTokens()
&& budget.outputTokens() == modelCalls.outputTokens()
&& budget.totalTokens() == modelCalls.totalTokens());
return event(context, TracePhase.RUN, TraceEventType.RUN_FINISHED,
terminalStatus(outcome), null, durationMs, details);
}
@@ -66,6 +85,41 @@ public final class TraceAuditEvents {
TraceEventStatus.SUCCEEDED, stepIndex + 1, durationMs, details);
}
public static DiagnosisTraceAuditEvent modelTokenUsage(
RunContext context,
ModelCallLedger.Call call,
long inputTokens,
long outputTokens,
boolean usageAvailable) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("component", call.component().name());
details.put("component_round", call.componentRound());
details.put("usage_available", usageAvailable);
if (usageAvailable) {
details.put("input_tokens", inputTokens);
details.put("output_tokens", outputTokens);
details.put("total_tokens", safeAdd(inputTokens, outputTokens));
}
return new DiagnosisTraceAuditEvent(
context.sessionId(), context.runId(), call.component().tracePhase(),
TraceEventType.MODEL_TOKEN_USAGE,
usageAvailable ? TraceEventStatus.SUCCEEDED : TraceEventStatus.UNAVAILABLE,
call.componentRound(), null, details);
}
public static DiagnosisTraceAuditEvent agentDraftInvalid(
RunContext context,
DiagnosisAgentOutputException.Kind kind,
long outputBytes,
boolean hasPublishableProgress) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("failure_kind", kind.name());
details.put("output_bytes", outputBytes);
details.put("has_publishable_progress", hasPublishableProgress);
return event(context, TracePhase.AGENT, TraceEventType.AGENT_DRAFT_INVALID,
TraceEventStatus.REJECTED, null, null, details);
}
public static DiagnosisTraceAuditEvent toolInvocation(ToolInvocationAuditEvent tool) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("tool_call_id", tool.toolCallId());
@@ -84,6 +138,55 @@ public final class TraceAuditEvents {
null, tool.durationMs(), details);
}
public static DiagnosisTraceAuditEvent toolRequestRejected(
RunContext context, String toolCallId, String toolName, String errorCode) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("tool_call_id", safeIdentifier(toolCallId));
details.put("tool_name", safeIdentifier(toolName));
details.put("error_code", safeIdentifier(errorCode));
return event(context, TracePhase.TOOL, TraceEventType.TOOL_REQUEST_REJECTED,
TraceEventStatus.REJECTED, null, null, details);
}
public static DiagnosisTraceAuditEvent toolProgress(
RunContext context,
String toolCallId,
String toolName,
String scopeSummary,
InformationGain informationGain,
String producer,
DiagnosisProgressSnapshotState state) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("tool_call_id", toolCallId);
details.put("tool_name", toolName);
details.put("scope_summary", scopeSummary);
details.put("information_gain", informationGain.name());
details.put("producer", producer);
details.put("consecutive_no_gain", state.consecutiveNoGain());
details.put("collection_state", state.collectionState().name());
if (state.stopReason() != null) {
details.put("stop_reason", state.stopReason().name());
}
return event(context, TracePhase.TOOL, TraceEventType.TOOL_PROGRESS,
TraceEventStatus.SUCCEEDED, null, null, details);
}
public static DiagnosisTraceAuditEvent collectionStop(
RunContext context,
String toolCallId,
String toolName,
DiagnosisProgressSnapshotState state) {
Map<String, Object> details = new LinkedHashMap<>();
details.put("tool_call_id", toolCallId);
details.put("tool_name", toolName);
details.put("collection_state", state.collectionState().name());
if (state.stopReason() != null) {
details.put("stop_reason", state.stopReason().name());
}
return event(context, TracePhase.TOOL, TraceEventType.COLLECTION_STOP,
TraceEventStatus.SUCCEEDED, null, null, details);
}
public static DiagnosisTraceAuditEvent evidenceValidation(
RunContext context, TraceEventType type,
EvidenceGuardResult result, DiagnosisDraft draft) {
@@ -198,6 +301,18 @@ public final class TraceAuditEvents {
return new ToolReferences(List.copyOf(safeIds), invalid);
}
private static String safeIdentifier(String value) {
return value != null && SAFE_TOOL_CALL_ID.matcher(value).matches() ? value : "INVALID";
}
private static long safeAdd(long left, long right) {
try {
return Math.addExact(left, right);
} catch (ArithmeticException exception) {
return Long.MAX_VALUE;
}
}
private record ToolReferences(List<String> safeIds, int invalidCount) {
}
}