feat(harness): add information gain stop and audit
This commit is contained in:
@@ -107,8 +107,7 @@ public final class ChatApplicationUseCase {
|
||||
|
||||
PathResult path = executePath(
|
||||
intent, context, request.query(), previousTurn.orElse(null), observer);
|
||||
core.checkActive(context);
|
||||
core.completeSuccess(context);
|
||||
completePath(context, intent, path);
|
||||
String safeJson = write(path.content());
|
||||
persistFinish(context, intent, path.outcome(), safeJson,
|
||||
path.publishedResult(), durationMillis(startedNanos));
|
||||
@@ -131,6 +130,20 @@ public final class ChatApplicationUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
private void completePath(RunContext context, IntentType intent, PathResult path) {
|
||||
if (path.handledBudgetTermination()) {
|
||||
if (intent != IntentType.DIAGNOSIS
|
||||
|| path.outcome() != ReleaseOutcome.FALLBACK
|
||||
|| path.publishedResult() != null
|
||||
|| context.lifecycle().state() != RunState.BUDGET_EXHAUSTED) {
|
||||
throw new IllegalStateException("Invalid handled Diagnosis budget fallback");
|
||||
}
|
||||
return;
|
||||
}
|
||||
core.checkActive(context);
|
||||
core.completeSuccess(context);
|
||||
}
|
||||
|
||||
private PathResult executePath(IntentType intent,
|
||||
RunContext context,
|
||||
String query,
|
||||
@@ -140,18 +153,19 @@ public final class ChatApplicationUseCase {
|
||||
case SYSTEM_CHAT -> {
|
||||
observer.onStatus(ChatApplicationStatus.SYSTEM_RESPONDING);
|
||||
yield new PathResult(
|
||||
ReleaseOutcome.SUCCESS, systemChat.execute(context, query), null);
|
||||
ReleaseOutcome.SUCCESS, systemChat.execute(context, query), null, false);
|
||||
}
|
||||
case KNOWLEDGE_QUERY -> {
|
||||
observer.onStatus(ChatApplicationStatus.KNOWLEDGE_SEARCHING);
|
||||
observer.onStatus(ChatApplicationStatus.KNOWLEDGE_ANSWERING);
|
||||
yield new PathResult(
|
||||
ReleaseOutcome.SUCCESS, knowledgeQuery.execute(context, query), null);
|
||||
ReleaseOutcome.SUCCESS, knowledgeQuery.execute(context, query), null, false);
|
||||
}
|
||||
case DIAGNOSIS -> {
|
||||
DiagnosisExecutionResult result = diagnosis.execute(
|
||||
context, query, previousTurn, observer::onStatus);
|
||||
yield new PathResult(result.outcome(), result.content(), result.publishedResult());
|
||||
yield new PathResult(result.outcome(), result.content(), result.publishedResult(),
|
||||
result.handledBudgetTermination());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -251,7 +265,8 @@ public final class ChatApplicationUseCase {
|
||||
private record PathResult(
|
||||
ReleaseOutcome outcome,
|
||||
ChatApplicationContent content,
|
||||
PublishedResult publishedResult) {
|
||||
PublishedResult publishedResult,
|
||||
boolean handledBudgetTermination) {
|
||||
}
|
||||
|
||||
private static final class CoreRunControl implements ChatRunControl {
|
||||
|
||||
@@ -8,7 +8,15 @@ import java.util.Objects;
|
||||
public record DiagnosisExecutionResult(
|
||||
ReleaseOutcome outcome,
|
||||
ChatApplicationContent content,
|
||||
PublishedResult publishedResult) {
|
||||
PublishedResult publishedResult,
|
||||
boolean handledBudgetTermination) {
|
||||
|
||||
public DiagnosisExecutionResult(
|
||||
ReleaseOutcome outcome,
|
||||
ChatApplicationContent content,
|
||||
PublishedResult publishedResult) {
|
||||
this(outcome, content, publishedResult, false);
|
||||
}
|
||||
|
||||
public DiagnosisExecutionResult {
|
||||
Objects.requireNonNull(outcome, "outcome must not be null");
|
||||
@@ -25,5 +33,9 @@ public record DiagnosisExecutionResult(
|
||||
if (outcome != ReleaseOutcome.SUCCESS && publishedResult != null) {
|
||||
throw new IllegalArgumentException("only success can contain PublishedResult");
|
||||
}
|
||||
if (handledBudgetTermination && outcome != ReleaseOutcome.FALLBACK) {
|
||||
throw new IllegalArgumentException(
|
||||
"handled budget termination requires fallback release");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-5
@@ -1,6 +1,8 @@
|
||||
package com.superbiz.agent.harness.application.executor;
|
||||
|
||||
import com.superbiz.agent.harness.agent.DiagnosisAgentInput;
|
||||
import com.superbiz.agent.harness.agent.DiagnosisAgentExecution;
|
||||
import com.superbiz.agent.harness.agent.DiagnosisAgentOutputException;
|
||||
import com.superbiz.agent.harness.agent.DiagnosisAgentUseCase;
|
||||
import com.superbiz.agent.harness.application.ChatApplicationStatus;
|
||||
import com.superbiz.agent.harness.application.DiagnosisContent;
|
||||
@@ -8,7 +10,8 @@ import com.superbiz.agent.harness.application.DiagnosisExecutionResult;
|
||||
import com.superbiz.agent.harness.application.DiagnosisOperation;
|
||||
import com.superbiz.agent.harness.application.FallbackContent;
|
||||
import com.superbiz.agent.harness.application.persistence.PublishedResultPolicy;
|
||||
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.contract.PreviousTurn;
|
||||
import com.superbiz.agent.harness.contract.PublishedResult;
|
||||
import com.superbiz.agent.harness.contract.ReleaseOutcome;
|
||||
@@ -25,13 +28,22 @@ public final class DiagnosisChatExecutor implements DiagnosisOperation {
|
||||
private final DiagnosisAgentUseCase diagnosisAgent;
|
||||
private final DiagnosisReleaseUseCase releaseUseCase;
|
||||
private final PublishedResultPolicy publishedPolicy;
|
||||
private final DiagnosisTraceRecorder traceRecorder;
|
||||
|
||||
public DiagnosisChatExecutor(DiagnosisAgentUseCase diagnosisAgent,
|
||||
DiagnosisReleaseUseCase releaseUseCase,
|
||||
PublishedResultPolicy publishedPolicy) {
|
||||
this(diagnosisAgent, releaseUseCase, publishedPolicy, DiagnosisTraceRecorder.noop());
|
||||
}
|
||||
|
||||
public DiagnosisChatExecutor(DiagnosisAgentUseCase diagnosisAgent,
|
||||
DiagnosisReleaseUseCase releaseUseCase,
|
||||
PublishedResultPolicy publishedPolicy,
|
||||
DiagnosisTraceRecorder traceRecorder) {
|
||||
this.diagnosisAgent = Objects.requireNonNull(diagnosisAgent, "diagnosisAgent must not be null");
|
||||
this.releaseUseCase = Objects.requireNonNull(releaseUseCase, "releaseUseCase must not be null");
|
||||
this.publishedPolicy = Objects.requireNonNull(publishedPolicy, "publishedPolicy must not be null");
|
||||
this.traceRecorder = Objects.requireNonNull(traceRecorder, "traceRecorder must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,15 +53,22 @@ public final class DiagnosisChatExecutor implements DiagnosisOperation {
|
||||
Consumer<ChatApplicationStatus> statusSink) {
|
||||
Objects.requireNonNull(statusSink, "statusSink must not be null");
|
||||
statusSink.accept(ChatApplicationStatus.DIAGNOSIS_RUNNING);
|
||||
DiagnosisDraft draft = diagnosisAgent.execute(
|
||||
context, new DiagnosisAgentInput(query, previousTurn));
|
||||
DiagnosisAgentExecution execution;
|
||||
try {
|
||||
execution = diagnosisAgent.execute(
|
||||
context, new DiagnosisAgentInput(query, previousTurn));
|
||||
} catch (DiagnosisAgentOutputException exception) {
|
||||
return recoverInvalidDraft(context, exception, statusSink);
|
||||
}
|
||||
statusSink.accept(ChatApplicationStatus.SAFETY_VALIDATING);
|
||||
DiagnosisReleaseResult released = releaseUseCase.execute(context, query, draft);
|
||||
DiagnosisReleaseResult released = releaseUseCase.execute(context, query, execution);
|
||||
if (released.outcome() == ReleaseOutcome.FALLBACK) {
|
||||
return new DiagnosisExecutionResult(
|
||||
ReleaseOutcome.FALLBACK,
|
||||
new FallbackContent(released.fallback()),
|
||||
null);
|
||||
null,
|
||||
execution.stopReason()
|
||||
== com.superbiz.agent.harness.progress.DiagnosisStopReason.BUDGET_LIMIT_REACHED);
|
||||
}
|
||||
PublishedResult published = publishedPolicy.create(
|
||||
query, released.draft(), released.verifiedEvidence()).orElse(null);
|
||||
@@ -60,4 +79,26 @@ public final class DiagnosisChatExecutor implements DiagnosisOperation {
|
||||
released.verifiedEvidence().verifiedSources()),
|
||||
published);
|
||||
}
|
||||
|
||||
private DiagnosisExecutionResult recoverInvalidDraft(
|
||||
RunContext context,
|
||||
DiagnosisAgentOutputException exception,
|
||||
Consumer<ChatApplicationStatus> statusSink) {
|
||||
if (!exception.isDraftContractFailure()) {
|
||||
throw exception;
|
||||
}
|
||||
boolean hasProgress = exception.progress().hasObservedFacts();
|
||||
traceRecorder.record(TraceAuditEvents.agentDraftInvalid(
|
||||
context, exception.kind(), exception.outputBytes(), hasProgress));
|
||||
if (!hasProgress) {
|
||||
throw exception;
|
||||
}
|
||||
statusSink.accept(ChatApplicationStatus.SAFETY_VALIDATING);
|
||||
DiagnosisReleaseResult released = releaseUseCase.releaseInvalidDraft(
|
||||
context, exception.progress());
|
||||
return new DiagnosisExecutionResult(
|
||||
ReleaseOutcome.FALLBACK,
|
||||
new FallbackContent(released.fallback()),
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import com.superbiz.agent.harness.contract.SourceDocument;
|
||||
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
|
||||
import com.superbiz.agent.harness.core.RunContext;
|
||||
import com.superbiz.agent.harness.guard.semantic.GuardModelCall;
|
||||
import com.superbiz.agent.harness.audit.ModelCallComponent;
|
||||
import com.superbiz.agent.harness.tool.boundary.ToolBoundaryResult;
|
||||
import com.superbiz.agent.harness.tool.contract.AgentToolContracts;
|
||||
import com.superbiz.agent.harness.tool.contract.RagEvidence;
|
||||
@@ -106,7 +107,7 @@ public final class KnowledgeQueryExecutor implements KnowledgeQueryOperation {
|
||||
}
|
||||
core.reserveRunBytes(context, bytes);
|
||||
String output = modelCall.call(
|
||||
context,
|
||||
context, ModelCallComponent.KNOWLEDGE_ANSWER,
|
||||
new Prompt(List.of(new SystemMessage(prompt), new UserMessage(modelInput))),
|
||||
limits.modelTimeout(),
|
||||
limits.maxModelOutputBytes());
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.superbiz.agent.harness.application.SystemChatOperation;
|
||||
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
|
||||
import com.superbiz.agent.harness.core.RunContext;
|
||||
import com.superbiz.agent.harness.guard.semantic.GuardModelCall;
|
||||
import com.superbiz.agent.harness.audit.ModelCallComponent;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
@@ -47,7 +48,7 @@ public final class SystemChatExecutor implements SystemChatOperation {
|
||||
}
|
||||
core.reserveRunBytes(context, bytes);
|
||||
String answer = modelCall.call(
|
||||
context,
|
||||
context, ModelCallComponent.SYSTEM_CHAT,
|
||||
new Prompt(List.of(new SystemMessage(SYSTEM_PROMPT), new UserMessage(query))),
|
||||
limits.timeout(),
|
||||
limits.maxOutputBytes());
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.superbiz.agent.harness.contract.PublishedResult;
|
||||
import com.superbiz.agent.harness.contract.ReleaseOutcome;
|
||||
import com.superbiz.agent.harness.core.RunBudgetUsage;
|
||||
import com.superbiz.agent.harness.core.RunContext;
|
||||
import com.superbiz.agent.harness.audit.ModelCallComponent;
|
||||
import com.superbiz.agent.repository.ChatSessionRepository;
|
||||
import com.superbiz.agent.repository.DiagnosisRunRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -119,6 +120,8 @@ public class JpaChatRunStore implements ChatRunStore {
|
||||
run.setTotalDurationMs(Math.max(0, durationMs));
|
||||
RunBudgetUsage usage = context.budget().snapshot();
|
||||
run.setTotalTokenCount(saturatingInt(usage.totalTokens()));
|
||||
run.setStepCount(context.modelCalls().componentCallCount(
|
||||
ModelCallComponent.DIAGNOSIS_AGENT));
|
||||
run.setToolCallCount(saturatingInt(usage.toolCalls()));
|
||||
runs.save(run);
|
||||
if (outcome == ReleaseOutcome.SUCCESS || outcome == ReleaseOutcome.FALLBACK) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.superbiz.agent.harness.contract.IntentType;
|
||||
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.application.IntentRouting;
|
||||
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
|
||||
import com.superbiz.agent.harness.core.RunContext;
|
||||
@@ -86,7 +87,8 @@ public final class IntentRouter implements IntentRouting {
|
||||
context,
|
||||
context.retryPolicies().intentRouter(),
|
||||
() -> parse(modelCall.call(
|
||||
context, prompt, remaining(started), limits.maxOutputBytes())),
|
||||
context, ModelCallComponent.INTENT_ROUTER,
|
||||
prompt, remaining(started), limits.maxOutputBytes())),
|
||||
this::classify,
|
||||
attempt -> {
|
||||
attemptRecorder.accept(attempt);
|
||||
|
||||
Reference in New Issue
Block a user