package com.superbiz.agent.harness.application; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.superbiz.agent.harness.application.persistence.ChatRunStore; import com.superbiz.agent.harness.audit.DiagnosisTraceRecorder; import com.superbiz.agent.harness.audit.TraceAuditEvents; import com.superbiz.agent.harness.application.persistence.RoutingHistory; import com.superbiz.agent.harness.application.routing.IntentRouterInput; import com.superbiz.agent.harness.application.routing.IntentRoutingException; import com.superbiz.agent.harness.contract.IntentType; import com.superbiz.agent.harness.contract.PreviousTurn; import com.superbiz.agent.harness.contract.PublishedResult; import com.superbiz.agent.harness.contract.ReleaseOutcome; import com.superbiz.agent.harness.core.DiagnosisHarnessCore; import com.superbiz.agent.harness.core.RunCancellationReason; import com.superbiz.agent.harness.core.RunContext; import com.superbiz.agent.harness.core.RunState; import com.superbiz.agent.harness.retry.RetryExecutionException; import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import java.util.regex.Pattern; /** * Chat 应用编排(Harness Application 层):一次请求从创建到公开结果的负责人。 * *
调用链: *
* Controller → execute() * → core.startRun() // 建 RunContext 边界 * → router.route() // Intent Router(Spring AI 单次模型调用) * → executePath(intent) // 按意图分叉 * DIAGNOSIS → DiagnosisChatExecutor(Agent → Release) * → completePath / persistFinish ** *
它编排路径,但不做业务根因判断;也不把 HTTP/SSE 细节塞进 Core。
*/
public final class ChatApplicationUseCase {
private static final Pattern SAFE_ID = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._-]{0,63}");
/** 执行规则:预算、deadline、取消、终态(first-terminal-wins)。 */
private final DiagnosisHarnessCore core;
private final Supplier observer 通常是 SSE session:onStarted 推 metadata,onStatus 推进度。
*/
public ChatApplicationResult execute(ChatApplicationRequest request,
ChatApplicationObserver observer) {
Objects.requireNonNull(request, "request must not be null");
Objects.requireNonNull(observer, "observer must not be null");
String sessionId = resolveSessionId(request.sessionId());
// 会话级上下文:上一路由结果 + 上一轮诊断摘要(给 Router / Diagnosis 用)
Optional 诊断预算耗尽且已由子路径产出 FALLBACK 时,不二次 completeSuccess
*(lifecycle 已是 BUDGET_EXHAUSTED)。
*/
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);
}
/**
* 根据 Router 产出的 intent 选择执行分支。
*
* 这是 Application 的核心编排决策:Router 只给枚举,分支执行权在这里。
* DIAGNOSIS 继续进入 {@link com.superbiz.agent.harness.application.executor.DiagnosisChatExecutor}。
*/
private PathResult executePath(IntentType intent,
RunContext context,
String query,
PreviousTurn previousTurn,
ChatApplicationObserver observer) {
return switch (intent) {
case SYSTEM_CHAT -> {
observer.onStatus(ChatApplicationStatus.SYSTEM_RESPONDING);
yield new PathResult(
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, false);
}
case DIAGNOSIS -> {
// 诊断子编排:Agent(ReAct) → Guard/Release,不在本类展开
DiagnosisExecutionResult result = diagnosis.execute(
context, query, previousTurn, observer::onStatus);
yield new PathResult(result.outcome(), result.content(), result.publishedResult(),
result.handledBudgetTermination());
}
};
}
private void persistStart(RunContext context, String query) {
try {
runStore.start(context, query);
} catch (RuntimeException exception) {
throw new ChatApplicationException(
ChatFailureCode.RUN_PERSISTENCE_FAILED,
"无法创建诊断运行记录,请稍后重试", exception);
}
}
private void persistIntent(String runId, IntentType intent) {
try {
runStore.markIntent(runId, intent);
} catch (RuntimeException exception) {
throw new ChatApplicationException(
ChatFailureCode.RUN_PERSISTENCE_FAILED,
"无法记录请求类型,请稍后重试", exception);
}
}
private void persistFinish(RunContext context,
IntentType intent,
ReleaseOutcome outcome,
String safeJson,
PublishedResult publishedResult,
int durationMs) {
try {
runStore.finish(context, intent, outcome, safeJson, publishedResult, durationMs);
} catch (RuntimeException exception) {
throw new ChatApplicationException(
ChatFailureCode.RUN_PERSISTENCE_FAILED,
"无法记录运行终态,请稍后重试", exception);
}
}
private ReleaseOutcome terminalOutcome(RunContext context) {
RunState state = context.lifecycle().state();
if (state == RunState.CANCELLED) {
return ReleaseOutcome.CANCELLED;
}
if (!state.isTerminal()) {
core.completeFailure(context, "APPLICATION_EXECUTION_FAILED");
}
return context.lifecycle().state() == RunState.CANCELLED
? ReleaseOutcome.CANCELLED : ReleaseOutcome.FAILED;
}
private ChatApplicationException safeFailure(RuntimeException exception,
ReleaseOutcome terminal) {
if (terminal == ReleaseOutcome.CANCELLED) {
return new ChatApplicationException(
ChatFailureCode.RUN_CANCELLED, "Chat Run was cancelled", exception);
}
if (exception instanceof IntentRoutingException) {
return new ChatApplicationException(
ChatFailureCode.ROUTING_UNAVAILABLE,
"当前暂时无法识别请求类型,请稍后重试", exception);
}
if (exception instanceof ChatApplicationException applicationFailure) {
return applicationFailure;
}
if (exception instanceof RetryExecutionException retry
&& retry.failure() == com.superbiz.agent.harness.retry.RetryFailure.CANCELLED) {
return new ChatApplicationException(
ChatFailureCode.RUN_CANCELLED, "Chat Run was cancelled", exception);
}
return new ChatApplicationException(
ChatFailureCode.INTERNAL_FAILURE, "当前暂时无法处理该请求,请稍后重试", exception);
}
private String resolveSessionId(String requested) {
String value = requested == null ? sessionIdSupplier.get() : requested;
if (value == null || !SAFE_ID.matcher(value).matches()) {
throw new IllegalArgumentException("sessionId is invalid");
}
return value;
}
private String write(ChatApplicationContent content) {
try {
return objectMapper.writeValueAsString(content);
} catch (JsonProcessingException exception) {
throw new ChatApplicationException(
ChatFailureCode.INTERNAL_FAILURE, "Public content is not serializable", exception);
}
}
private static int durationMillis(long startedNanos) {
long millis = Math.max(0L, (System.nanoTime() - startedNanos) / 1_000_000L);
return millis >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) millis;
}
private record PathResult(
ReleaseOutcome outcome,
ChatApplicationContent content,
PublishedResult publishedResult,
boolean handledBudgetTermination) {
}
private static final class CoreRunControl implements ChatRunControl {
private final DiagnosisHarnessCore core;
private final RunContext context;
private CoreRunControl(DiagnosisHarnessCore core, RunContext context) {
this.core = core;
this.context = context;
}
@Override
public String sessionId() {
return context.sessionId();
}
@Override
public String runId() {
return context.runId();
}
@Override
public boolean cancelClientDisconnect() {
return core.cancel(context, RunCancellationReason.CLIENT_DISCONNECTED);
}
}
}