From 19f56d11f0fe458973f5fb65210abcf73d446be1 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Sun, 12 Apr 2026 16:58:36 +0800 Subject: [PATCH] refactor(framework): add Result chain APIs and align runtime exception handling --- .../slhaf/partner/core/action/ActionCore.java | 57 +++++----- .../BuiltinInterventionActionProvider.java | 32 ++---- .../action/executor/ActionExecutor.java | 61 ++++------- .../action/executor/ParamsExtractor.java | 18 +-- .../module/action/planner/ActionPlanner.java | 24 ++-- .../planner/evaluator/ActionEvaluator.java | 17 ++- .../planner/extractor/ActionExtractor.java | 12 +- .../communication/CommunicationProducer.java | 12 +- .../evaluator/SliceSelectEvaluator.java | 20 ++-- .../extractor/MemorySelectExtractor.java | 22 ++-- .../partner/core/action/ActionCoreTest.java | 10 +- .../openai/OpenAiCompatibleProvider.java | 16 +-- .../partner/framework/agent/support/Result.kt | 60 ++++++---- .../framework/agent/support/ResultTest.java | 103 ++++++++++++++++++ 14 files changed, 274 insertions(+), 190 deletions(-) create mode 100644 Partner-Framework/src/test/java/work/slhaf/partner/framework/agent/support/ResultTest.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java index 7bddcbf7..2f8a3d3d 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/ActionCore.java @@ -14,6 +14,7 @@ import work.slhaf.partner.core.action.exception.ActionLookupException; import work.slhaf.partner.core.action.runner.LocalRunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.framework.agent.config.ConfigCenter; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod; @@ -184,31 +185,32 @@ public class ActionCore implements StateSerializable { } private void applyInterventions(List interventions, ExecutableAction executableAction) { - boolean rebuildCleanTag = false; + boolean[] rebuildCleanTag = {false}; interventions.sort(Comparator.comparingInt(MetaIntervention::getOrder)); for (MetaIntervention intervention : interventions) { Result> actionsResult = resolveInterventionActions(intervention); - if (actionsResult.isFailure()) { - reportLookupFailure(actionsResult.exceptionOrNull()); - continue; - } - List actions = actionsResult.getOrNull(); - - switch (intervention.getType()) { - case InterventionType.APPEND -> handleAppend(executableAction, intervention.getOrder(), actions); - case InterventionType.INSERT -> handleInsert(executableAction, intervention.getOrder(), actions); - case InterventionType.DELETE -> handleDelete(executableAction, intervention.getOrder(), actions); - case InterventionType.CANCEL -> handleCancel(executableAction); - case InterventionType.REBUILD -> { - if (!rebuildCleanTag) { - cleanActionData(executableAction); - rebuildCleanTag = true; - } - handleRebuild(executableAction, intervention.getOrder(), actions); - } - } + actionsResult + .onFailure(ExceptionReporterHandler.INSTANCE::report) + .onSuccess(actions -> { + switch (intervention.getType()) { + case InterventionType.APPEND -> + handleAppend(executableAction, intervention.getOrder(), actions); + case InterventionType.INSERT -> + handleInsert(executableAction, intervention.getOrder(), actions); + case InterventionType.DELETE -> + handleDelete(executableAction, intervention.getOrder(), actions); + case InterventionType.CANCEL -> handleCancel(executableAction); + case InterventionType.REBUILD -> { + if (!rebuildCleanTag[0]) { + cleanActionData(executableAction); + rebuildCleanTag[0] = true; + } + handleRebuild(executableAction, intervention.getOrder(), actions); + } + } + }); } } @@ -217,23 +219,14 @@ public class ActionCore implements StateSerializable { List actions = new ArrayList<>(); for (String actionKey : intervention.getActions()) { Result metaActionResult = loadMetaAction(actionKey); - if (metaActionResult.isFailure()) { - Throwable throwable = metaActionResult.exceptionOrNull(); - return Result.failure(throwable == null - ? new ActionLookupException("Meta action lookup failed: " + actionKey, actionKey, "META_ACTION") - : throwable); + AgentRuntimeException failure = metaActionResult.onSuccess(actions::add).exceptionOrNull(); + if (failure != null) { + return Result.failure(failure); } - actions.add(metaActionResult.getOrNull()); } return Result.success(actions); } - private void reportLookupFailure(Throwable throwable) { - if (throwable instanceof ActionLookupException lookupException) { - ExceptionReporterHandler.INSTANCE.report(lookupException); - } - } - /** * 在未进入执行阶段的行动单元组新增新的行动 */ diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java index 153da146..ba25e2c3 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java @@ -8,6 +8,7 @@ import org.w3c.dom.Element; import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.entity.Action; import work.slhaf.partner.core.action.entity.ExecutableAction; +import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.intervention.InterventionType; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; @@ -15,7 +16,7 @@ import work.slhaf.partner.core.cognition.BlockContent; import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.ContextWorkspace; -import work.slhaf.partner.framework.agent.exception.AgentException; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent; @@ -310,13 +311,9 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { List actions = requireActions(params, type); ExecutableAction target = requireTargetAction(targetId); Result validationResult = validateActionKeys(actions); - if (validationResult.isFailure()) { - reportFailure(validationResult.exceptionOrNull()); - Throwable throwable = validationResult.exceptionOrNull(); - return JSONObject.of( - "ok", false, - "result", throwable == null ? "Intervention action validation failed" : throwable.getLocalizedMessage() - ).toJSONString(); + AgentRuntimeException validationFailure = validationResult.onFailure(ExceptionReporterHandler.INSTANCE::report).exceptionOrNull(); + if (validationFailure != null) { + return JSONObject.of("ok", false, "result", validationFailure.getLocalizedMessage()).toJSONString(); } MetaIntervention intervention = new MetaIntervention(); @@ -398,24 +395,13 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { private Result validateActionKeys(List actions) { for (String actionKey : actions) { - Result metaActionResult = actionCapability.loadMetaAction(actionKey); - if (metaActionResult.isFailure()) { - Throwable throwable = metaActionResult.exceptionOrNull(); - return Result.failure(throwable == null - ? new work.slhaf.partner.core.action.exception.ActionLookupException( - "Meta action lookup failed: " + actionKey, - actionKey, - "META_ACTION" - ) : throwable); + Result metaActionResult = actionCapability.loadMetaAction(actionKey); + AgentRuntimeException failure = metaActionResult.exceptionOrNull(); + if (failure != null) { + return Result.failure(failure); } } return Result.success(null); } - private void reportFailure(Throwable throwable) { - if (throwable instanceof AgentException agentException) { - ExceptionReporterHandler.INSTANCE.report(agentException); - } - } - } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionExecutor.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionExecutor.java index 7d5d294e..3561047e 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionExecutor.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionExecutor.java @@ -7,7 +7,7 @@ import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.cognition.CognitionCapability; -import work.slhaf.partner.framework.agent.exception.AgentException; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; @@ -314,14 +314,15 @@ public class ActionExecutor extends AbstractAgentModule.Standalone { val executingStage = actionData.getExecutingStage(); - Result extractorInputResult = assemblyHelper.buildExtractorInput(metaAction.getKey(), actionData.getUuid(), actionData.getDescription()); - if (extractorInputResult.isFailure()) { - reportFailure(extractorInputResult.exceptionOrNull()); - Throwable throwable = extractorInputResult.exceptionOrNull(); - failureReason = buildAttemptFailureReason("参数提取失败", throwable == null ? null : throwable.getLocalizedMessage()); + Result extractorInputResult = assemblyHelper.buildExtractorInput(metaAction.getKey(), actionData.getUuid(), actionData.getDescription()) + .onFailure(ExceptionReporterHandler.INSTANCE::report); + AgentRuntimeException exception = extractorInputResult.exceptionOrNull(); + if (exception != null) { + failureReason = exception.getMessage(); break; } - val extractorInput = extractorInputResult.getOrNull(); + + ExtractorInput extractorInput = extractorInputResult.getOrThrow(); ExtractorResult extractorResult = paramsExtractor.execute(extractorInput); if (extractorResult == null || !extractorResult.isOk()) { @@ -508,21 +509,12 @@ public class ActionExecutor extends AbstractAgentModule.Standalone { } private String resolveHistoryDescription(String actionKey) { - Result metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey); - if (metaActionInfoResult.isFailure()) { - reportFailure(metaActionInfoResult.exceptionOrNull()); - return actionKey; - } - MetaActionInfo metaActionInfo = metaActionInfoResult.getOrNull(); - return metaActionInfo == null || metaActionInfo.getDescription().isBlank() - ? actionKey - : metaActionInfo.getDescription(); - } - - private void reportFailure(Throwable throwable) { - if (throwable instanceof AgentException agentException) { - ExceptionReporterHandler.INSTANCE.report(agentException); - } + return actionCapability.loadMetaActionInfo(actionKey) + .onFailure(ExceptionReporterHandler.INSTANCE::report) + .fold( + metaActionInfo -> metaActionInfo.getDescription().isBlank() ? actionKey : metaActionInfo.getDescription(), + exception -> actionKey + ); } private record MetaActionsListeningRecord(AtomicBoolean accepting, int phase) { @@ -540,21 +532,16 @@ public class ActionExecutor extends AbstractAgentModule.Standalone { } private Result buildExtractorInput(String actionKey, @NotNull String uuid, @NotNull String description) { - Result metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey); - if (metaActionInfoResult.isFailure()) { - Throwable throwable = metaActionInfoResult.exceptionOrNull(); - return Result.failure(throwable == null - ? new work.slhaf.partner.core.action.exception.ActionLookupException( - "Meta action description not found for action key: " + actionKey, - actionKey, - "META_ACTION_INFO" - ) : throwable); - } - ExtractorInput input = new ExtractorInput(); - input.setMetaActionInfo(metaActionInfoResult.getOrNull()); - input.setTargetActionId(uuid); - input.setTargetActionDesc(description); - return Result.success(input); + return actionCapability.loadMetaActionInfo(actionKey).fold( + metaActionInfo -> { + ExtractorInput input = new ExtractorInput(); + input.setMetaActionInfo(metaActionInfo); + input.setTargetActionId(uuid); + input.setTargetActionDesc(description); + return Result.success(input); + }, + Result::failure + ); } private CorrectorInput buildCorrectorInput(ExecutableAction executableAction) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ParamsExtractor.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ParamsExtractor.java index 8c9cb451..fb74a1e9 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ParamsExtractor.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ParamsExtractor.java @@ -34,14 +34,16 @@ public class ParamsExtractor extends AbstractAgentModule.Sub result = formattedChat(messages, ExtractorResult.class); - if (result.isFailure()) { - log.error("ParamsExtractor解析结果失败", result.exceptionOrNull()); - ExtractorResult fallback = new ExtractorResult(); - fallback.setOk(false); - fallback.setParams(new HashMap<>()); - return fallback; - } - return result.getOrThrow(); + return result.fold( + extractorResult -> extractorResult, + exception -> { + log.error("ParamsExtractor解析结果失败", exception); + ExtractorResult fallback = new ExtractorResult(); + fallback.setOk(false); + fallback.setParams(new HashMap<>()); + return fallback; + } + ); } private Message resolveTaskMessage(ExtractorInput input) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java index 59e72ac8..7dfc8feb 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/ActionPlanner.java @@ -11,6 +11,7 @@ import work.slhaf.partner.core.cognition.BlockContent; import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CommunicationBlockContent; import work.slhaf.partner.core.cognition.ContextBlock; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; @@ -384,11 +385,12 @@ public class ActionPlanner extends AbstractAgentModule.Running metaActions = new ArrayList<>(); for (String actionKey : entry.getValue()) { Result metaActionResult = actionCapability.loadMetaAction(actionKey); - if (metaActionResult.isFailure()) { - reportFailure(metaActionResult); + AgentRuntimeException failure = metaActionResult.onSuccess(metaActions::add) + .onFailure(ExceptionReporterHandler.INSTANCE::report) + .exceptionOrNull(); + if (failure != null) { return null; } - metaActions.add(metaActionResult.getOrNull()); } actionChain.put(entry.getKey(), metaActions); } @@ -408,12 +410,14 @@ public class ActionPlanner extends AbstractAgentModule.Running actionKeys = primaryActionChain.get(fixedOrder); for (String actionKey : actionKeys) { // 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖 - Result metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey); - if (metaActionInfoResult.isFailure()) { - reportFailure(metaActionInfoResult); + + Result infoResult = actionCapability.loadMetaActionInfo(actionKey) + .onFailure(ExceptionReporterHandler.INSTANCE::report); + if (infoResult.exceptionOrNull() != null) { return false; } - MetaActionInfo metaActionInfo = metaActionInfoResult.getOrNull(); + + MetaActionInfo metaActionInfo = infoResult.getOrThrow(); Set preActions = metaActionInfo.getPreActions(); boolean preActionsExist = preActions.isEmpty(); if (!preActionsExist) { @@ -444,12 +448,6 @@ public class ActionPlanner extends AbstractAgentModule.Running result) { - if (result.exceptionOrNull() instanceof work.slhaf.partner.framework.agent.exception.AgentException agentException) { - ExceptionReporterHandler.INSTANCE.report(agentException); - } - } - private void fixOrder(Map> primaryActionChain) { Map> tempChain = new HashMap<>(primaryActionChain); primaryActionChain.clear(); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/ActionEvaluator.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/ActionEvaluator.java index 59e3b6ba..0200c0a7 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/ActionEvaluator.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/evaluator/ActionEvaluator.java @@ -66,15 +66,14 @@ public class ActionEvaluator extends AbstractAgentModule.Sub log.error("ActionEvaluator评估失败: {}", tendency, exception)) + .onSuccess(evaluatorResult -> { + evaluatorResult.setTendency(tendency); + synchronized (evaluatorResults) { + evaluatorResults.add(evaluatorResult); + } + }); } finally { latch.countDown(); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/extractor/ActionExtractor.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/extractor/ActionExtractor.java index cb512989..a45c4524 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/extractor/ActionExtractor.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/planner/extractor/ActionExtractor.java @@ -27,11 +27,13 @@ public class ActionExtractor extends AbstractAgentModule.Sub result = formattedChat(messages, ExtractorResult.class); - if (result.isSuccess()) { - return result.getOrThrow(); - } - log.error("提取信息出错", result.exceptionOrNull()); - return new ExtractorResult(); + return result.fold( + extractorResult -> extractorResult, + exception -> { + log.error("提取信息出错", exception); + return new ExtractorResult(); + } + ); } @NotNull diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java index 22f2f7de..ed953b2c 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java @@ -6,13 +6,13 @@ import org.jetbrains.annotations.NotNull; import org.w3c.dom.Document; import org.w3c.dom.Element; import work.slhaf.partner.core.cognition.*; +import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer; import work.slhaf.partner.framework.agent.model.pojo.Message; -import work.slhaf.partner.framework.agent.support.Result; import work.slhaf.partner.runtime.PartnerRunningFlowContext; import javax.xml.parsers.DocumentBuilderFactory; @@ -67,11 +67,11 @@ public class CommunicationProducer extends AbstractAgentModule.Running result = this.streamChat(buildChatMessages(runningFlowContext), consumer); - if (result.isFailure()) { - log.error("Streaming response failed", result.exceptionOrNull()); - consumer.onDelta(INTERRUPTED_MARKER); - } + this.streamChat(buildChatMessages(runningFlowContext), consumer) + .onFailure(exception -> { + ExceptionReporterHandler.INSTANCE.report(exception); + consumer.onDelta(INTERRUPTED_MARKER); + }); updateChatMessages(runningFlowContext, consumer.collectResponse()); updateContext(); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/evaluator/SliceSelectEvaluator.java b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/evaluator/SliceSelectEvaluator.java index 8026668f..10d8d41f 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/evaluator/SliceSelectEvaluator.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/evaluator/SliceSelectEvaluator.java @@ -15,7 +15,6 @@ import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAg import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.pojo.Message; -import work.slhaf.partner.framework.agent.support.Result; import work.slhaf.partner.module.TaskBlock; import work.slhaf.partner.module.memory.selector.ActivatedMemorySlice; import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchInput; @@ -65,16 +64,15 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub batchResult = formattedChat(messages, EvaluatorBatchResult.class); - if (batchResult.isFailure()) { - log.debug("切片评估失败,已跳过当前切片", batchResult.exceptionOrNull()); - return; - } - if (batchResult.getOrThrow().isPassed()) { - synchronized (result) { - result.add(slice); - } - } + formattedChat(messages, EvaluatorBatchResult.class) + .onFailure(exception -> log.debug("切片评估失败,已跳过当前切片", exception)) + .onSuccess(evaluatorBatchResult -> { + if (evaluatorBatchResult.isPassed()) { + synchronized (result) { + result.add(slice); + } + } + }); } finally { latch.countDown(); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/extractor/MemorySelectExtractor.java b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/extractor/MemorySelectExtractor.java index e8d71ee2..14e178f2 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/extractor/MemorySelectExtractor.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/selector/extractor/MemorySelectExtractor.java @@ -43,15 +43,19 @@ public class MemorySelectExtractor extends AbstractAgentModule.Sub { + log.debug("[MemorySelectExtractor] 主题提取结果: {}", value); + return value; + }, + exception -> { + log.error("[MemorySelectExtractor] 主题提取出错: ", exception); + ExtractorResult fallback = new ExtractorResult(); + fallback.setRecall(false); + fallback.setMatches(List.of()); + return fallback; + } + ); return fix(extractorResult); } diff --git a/Partner-Core/src/test/java/work/slhaf/partner/core/action/ActionCoreTest.java b/Partner-Core/src/test/java/work/slhaf/partner/core/action/ActionCoreTest.java index 8d396f49..2f9ce35b 100644 --- a/Partner-Core/src/test/java/work/slhaf/partner/core/action/ActionCoreTest.java +++ b/Partner-Core/src/test/java/work/slhaf/partner/core/action/ActionCoreTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.exception.ActionLookupException; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.support.Result; import work.slhaf.partner.module.action.executor.entity.HistoryAction; @@ -350,15 +351,12 @@ class ActionCoreTest { actionCore.registerMetaActions(Map.of("builtin::demo", metaActionInfo)); Result success = actionCore.loadMetaAction("builtin::demo"); - assertTrue(success.isSuccess()); - assertEquals("demo", success.getOrNull().getName()); + assertEquals("demo", success.fold(MetaAction::getName, ex -> fail(ex.getMessage()))); Result failure = actionCore.loadMetaAction("builtin::missing"); - assertTrue(failure.isFailure()); - assertInstanceOf(ActionLookupException.class, failure.exceptionOrNull()); + assertInstanceOf(ActionLookupException.class, assertThrows(AgentRuntimeException.class, failure::getOrThrow)); Result infoFailure = actionCore.loadMetaActionInfo("builtin::missing"); - assertTrue(infoFailure.isFailure()); - assertInstanceOf(ActionLookupException.class, infoFailure.exceptionOrNull()); + assertInstanceOf(ActionLookupException.class, assertThrows(AgentRuntimeException.class, infoFailure::getOrThrow)); } } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/provider/openai/OpenAiCompatibleProvider.java b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/provider/openai/OpenAiCompatibleProvider.java index 47ae83b4..8154789b 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/provider/openai/OpenAiCompatibleProvider.java +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/provider/openai/OpenAiCompatibleProvider.java @@ -8,6 +8,7 @@ import com.openai.core.http.StreamResponse; import com.openai.models.chat.completions.*; import kotlin.Unit; import org.jetbrains.annotations.NotNull; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.ModelInvokeException; import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer; import work.slhaf.partner.framework.agent.model.pojo.Message; @@ -160,21 +161,14 @@ public class OpenAiCompatibleProvider extends ModelProvider { } private Result executeWithRetry(String failureMessage, ThrowingSupplier supplier) { - Exception lastFailure = null; + AgentRuntimeException lastFailure = null; for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { Result result = Result.runCatching(supplier::get); - if (result.isSuccess()) { + AgentRuntimeException failure = result.exceptionOrNull(); + if (failure == null) { return result; } - Throwable throwable = result.exceptionOrNull(); - if (throwable instanceof Exception exception) { - lastFailure = exception; - continue; - } - if (throwable instanceof Error error) { - throw error; - } - return Result.failure(invokeException(failureMessage, throwable)); + lastFailure = failure; } return Result.failure(invokeException(failureMessage, lastFailure)); } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/support/Result.kt b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/support/Result.kt index d7ca5fac..9f5cc7f9 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/support/Result.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/support/Result.kt @@ -1,29 +1,20 @@ package work.slhaf.partner.framework.agent.support import work.slhaf.partner.framework.agent.exception.AgentRuntimeException +import java.util.function.Consumer +import java.util.function.Function class Result private constructor( private val value: T?, - private val exception: Throwable? + private val exception: AgentRuntimeException? ) { - fun isSuccess(): Boolean = exception == null - - fun isFailure(): Boolean = exception != null - - fun getOrNull(): T? = value - - fun exceptionOrNull(): Throwable? = exception - fun getOrThrow(): T { if (exception == null) { @Suppress("UNCHECKED_CAST") return value as T } - when (exception) { - is AgentRuntimeException, is Error -> throw exception - else -> throw AgentRuntimeException(exception.localizedMessage, exception) - } + throw exception } fun getOrDefault(defaultValue: T): T { @@ -35,6 +26,35 @@ class Result private constructor( } } + fun exceptionOrNull(): AgentRuntimeException? = exception + + fun onSuccess(consumer: Consumer): Result { + if (exception == null) { + @Suppress("UNCHECKED_CAST") + consumer.accept(value as T) + } + return this + } + + fun onFailure(consumer: Consumer): Result { + if (exception != null) { + consumer.accept(exception) + } + return this + } + + fun fold( + onSuccess: Function, + onFailure: Function + ): R { + return if (exception == null) { + @Suppress("UNCHECKED_CAST") + onSuccess.apply(value as T) + } else { + onFailure.apply(exception) + } + } + override fun toString(): String { return if (exception == null) { "Result.success($value)" @@ -44,7 +64,7 @@ class Result private constructor( } fun interface ThrowingSupplier { - @Throws(Throwable::class) + @Throws(Exception::class) fun get(): T } @@ -53,19 +73,19 @@ class Result private constructor( fun success(value: T): Result = Result(value, null) @JvmStatic - fun failure(exception: Throwable): Result = Result(null, exception) + fun failure(exception: AgentRuntimeException): Result = Result(null, exception) @JvmStatic fun runCatching(block: ThrowingSupplier): Result { return try { success(block.get()) - } catch (throwable: Throwable) { + } catch (exception: Exception) { failure( - when (throwable) { - is AgentRuntimeException, is Error -> throwable + when (exception) { + is AgentRuntimeException -> exception else -> AgentRuntimeException( - throwable.message ?: "Unexpected runtime failure.", - throwable + exception.message ?: "Unexpected runtime failure.", + exception ) } ) diff --git a/Partner-Framework/src/test/java/work/slhaf/partner/framework/agent/support/ResultTest.java b/Partner-Framework/src/test/java/work/slhaf/partner/framework/agent/support/ResultTest.java new file mode 100644 index 00000000..9fe25d28 --- /dev/null +++ b/Partner-Framework/src/test/java/work/slhaf/partner/framework/agent/support/ResultTest.java @@ -0,0 +1,103 @@ +package work.slhaf.partner.framework.agent.support; + +import org.junit.jupiter.api.Test; +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class ResultTest { + + @Test + void shouldKeepAgentRuntimeExceptionInFailure() { + AgentRuntimeException exception = new AgentRuntimeException("runtime failure"); + + Result result = Result.failure(exception); + + assertSame(exception, result.exceptionOrNull()); + AtomicReference failure = new AtomicReference<>(); + result.onFailure(failure::set); + assertSame(exception, failure.get()); + AgentRuntimeException thrown = assertThrows(AgentRuntimeException.class, result::getOrThrow); + assertSame(exception, thrown); + } + + @Test + void shouldWrapCheckedExceptionInRunCatching() { + Result result = Result.runCatching(() -> { + throw new java.io.IOException("io failure"); + }); + + assertNotNull(result.exceptionOrNull()); + assertInstanceOf(java.io.IOException.class, result.exceptionOrNull().getCause()); + AgentRuntimeException thrown = assertThrows(AgentRuntimeException.class, result::getOrThrow); + assertInstanceOf(java.io.IOException.class, thrown.getCause()); + } + + @Test + void shouldKeepAgentRuntimeExceptionInRunCatching() { + AgentRuntimeException exception = new AgentRuntimeException("runtime failure"); + + Result result = Result.runCatching(() -> { + throw exception; + }); + + assertSame(exception, result.exceptionOrNull()); + AgentRuntimeException thrown = assertThrows(AgentRuntimeException.class, result::getOrThrow); + assertSame(exception, thrown); + } + + @Test + void shouldReturnNullExceptionForSuccess() { + assertNull(Result.success("ok").exceptionOrNull()); + } + + @Test + void shouldNotCatchErrorInRunCatching() { + AssertionError error = new AssertionError("boom"); + + AssertionError thrown = assertThrows(AssertionError.class, () -> Result.runCatching(() -> { + throw error; + })); + + assertSame(error, thrown); + } + + @Test + void shouldHandleSuccessAndFailureBranchesWithChainApis() { + AtomicReference successValue = new AtomicReference<>(); + AtomicReference failureValue = new AtomicReference<>(); + AgentRuntimeException exception = new AgentRuntimeException("runtime failure"); + + Result.success("ok") + .onSuccess(successValue::set) + .onFailure(failureValue::set); + + assertEquals("ok", successValue.get()); + assertNull(failureValue.get()); + + successValue.set(null); + Result.failure(exception) + .onSuccess(successValue::set) + .onFailure(failureValue::set); + + assertNull(successValue.get()); + assertSame(exception, failureValue.get()); + } + + @Test + void shouldFoldResult() { + String success = Result.success("ok").fold( + value -> "success:" + value, + ex -> "failure:" + ex.getMessage() + ); + String failure = Result.failure(new AgentRuntimeException("bad")).fold( + value -> "success:" + value, + ex -> "failure:" + ex.getMessage() + ); + + assertEquals("success:ok", success); + assertEquals("failure:bad", failure); + } +}