feat(harness): add information gain stop and audit
This commit is contained in:
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user