refactor(framework): add Result chain APIs and align runtime exception handling

This commit is contained in:
2026-04-12 16:58:36 +08:00
parent 94d91d9746
commit 19f56d11f0
14 changed files with 274 additions and 190 deletions

View File

@@ -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.LocalRunnerClient;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.framework.agent.config.ConfigCenter; 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.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore;
import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod; import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod;
@@ -184,31 +185,32 @@ public class ActionCore implements StateSerializable {
} }
private void applyInterventions(List<MetaIntervention> interventions, ExecutableAction executableAction) { private void applyInterventions(List<MetaIntervention> interventions, ExecutableAction executableAction) {
boolean rebuildCleanTag = false; boolean[] rebuildCleanTag = {false};
interventions.sort(Comparator.comparingInt(MetaIntervention::getOrder)); interventions.sort(Comparator.comparingInt(MetaIntervention::getOrder));
for (MetaIntervention intervention : interventions) { for (MetaIntervention intervention : interventions) {
Result<List<MetaAction>> actionsResult = resolveInterventionActions(intervention); Result<List<MetaAction>> actionsResult = resolveInterventionActions(intervention);
if (actionsResult.isFailure()) { actionsResult
reportLookupFailure(actionsResult.exceptionOrNull()); .onFailure(ExceptionReporterHandler.INSTANCE::report)
continue; .onSuccess(actions -> {
}
List<MetaAction> actions = actionsResult.getOrNull();
switch (intervention.getType()) { switch (intervention.getType()) {
case InterventionType.APPEND -> handleAppend(executableAction, intervention.getOrder(), actions); case InterventionType.APPEND ->
case InterventionType.INSERT -> handleInsert(executableAction, intervention.getOrder(), actions); handleAppend(executableAction, intervention.getOrder(), actions);
case InterventionType.DELETE -> handleDelete(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.CANCEL -> handleCancel(executableAction);
case InterventionType.REBUILD -> { case InterventionType.REBUILD -> {
if (!rebuildCleanTag) { if (!rebuildCleanTag[0]) {
cleanActionData(executableAction); cleanActionData(executableAction);
rebuildCleanTag = true; rebuildCleanTag[0] = true;
} }
handleRebuild(executableAction, intervention.getOrder(), actions); handleRebuild(executableAction, intervention.getOrder(), actions);
} }
} }
});
} }
} }
@@ -217,23 +219,14 @@ public class ActionCore implements StateSerializable {
List<MetaAction> actions = new ArrayList<>(); List<MetaAction> actions = new ArrayList<>();
for (String actionKey : intervention.getActions()) { for (String actionKey : intervention.getActions()) {
Result<MetaAction> metaActionResult = loadMetaAction(actionKey); Result<MetaAction> metaActionResult = loadMetaAction(actionKey);
if (metaActionResult.isFailure()) { AgentRuntimeException failure = metaActionResult.onSuccess(actions::add).exceptionOrNull();
Throwable throwable = metaActionResult.exceptionOrNull(); if (failure != null) {
return Result.failure(throwable == null return Result.failure(failure);
? new ActionLookupException("Meta action lookup failed: " + actionKey, actionKey, "META_ACTION")
: throwable);
} }
actions.add(metaActionResult.getOrNull());
} }
return Result.success(actions); return Result.success(actions);
} }
private void reportLookupFailure(Throwable throwable) {
if (throwable instanceof ActionLookupException lookupException) {
ExceptionReporterHandler.INSTANCE.report(lookupException);
}
}
/** /**
* 在未进入执行阶段的行动单元组新增新的行动 * 在未进入执行阶段的行动单元组新增新的行动
*/ */

View File

@@ -8,6 +8,7 @@ import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.Action; import work.slhaf.partner.core.action.entity.Action;
import work.slhaf.partner.core.action.entity.ExecutableAction; 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.MetaActionInfo;
import work.slhaf.partner.core.action.entity.intervention.InterventionType; import work.slhaf.partner.core.action.entity.intervention.InterventionType;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; 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.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.ContextWorkspace; 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.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent; import work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent;
@@ -310,13 +311,9 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider {
List<String> actions = requireActions(params, type); List<String> actions = requireActions(params, type);
ExecutableAction target = requireTargetAction(targetId); ExecutableAction target = requireTargetAction(targetId);
Result<Void> validationResult = validateActionKeys(actions); Result<Void> validationResult = validateActionKeys(actions);
if (validationResult.isFailure()) { AgentRuntimeException validationFailure = validationResult.onFailure(ExceptionReporterHandler.INSTANCE::report).exceptionOrNull();
reportFailure(validationResult.exceptionOrNull()); if (validationFailure != null) {
Throwable throwable = validationResult.exceptionOrNull(); return JSONObject.of("ok", false, "result", validationFailure.getLocalizedMessage()).toJSONString();
return JSONObject.of(
"ok", false,
"result", throwable == null ? "Intervention action validation failed" : throwable.getLocalizedMessage()
).toJSONString();
} }
MetaIntervention intervention = new MetaIntervention(); MetaIntervention intervention = new MetaIntervention();
@@ -398,24 +395,13 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider {
private Result<Void> validateActionKeys(List<String> actions) { private Result<Void> validateActionKeys(List<String> actions) {
for (String actionKey : actions) { for (String actionKey : actions) {
Result<work.slhaf.partner.core.action.entity.MetaAction> metaActionResult = actionCapability.loadMetaAction(actionKey); Result<MetaAction> metaActionResult = actionCapability.loadMetaAction(actionKey);
if (metaActionResult.isFailure()) { AgentRuntimeException failure = metaActionResult.exceptionOrNull();
Throwable throwable = metaActionResult.exceptionOrNull(); if (failure != null) {
return Result.failure(throwable == null return Result.failure(failure);
? new work.slhaf.partner.core.action.exception.ActionLookupException(
"Meta action lookup failed: " + actionKey,
actionKey,
"META_ACTION"
) : throwable);
} }
} }
return Result.success(null); return Result.success(null);
} }
private void reportFailure(Throwable throwable) {
if (throwable instanceof AgentException agentException) {
ExceptionReporterHandler.INSTANCE.report(agentException);
}
}
} }

View File

@@ -7,7 +7,7 @@ import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.core.cognition.CognitionCapability; 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.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; 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.abstracts.AbstractAgentModule;
@@ -314,14 +314,15 @@ public class ActionExecutor extends AbstractAgentModule.Standalone {
val executingStage = actionData.getExecutingStage(); val executingStage = actionData.getExecutingStage();
Result<ExtractorInput> extractorInputResult = assemblyHelper.buildExtractorInput(metaAction.getKey(), actionData.getUuid(), actionData.getDescription()); Result<ExtractorInput> extractorInputResult = assemblyHelper.buildExtractorInput(metaAction.getKey(), actionData.getUuid(), actionData.getDescription())
if (extractorInputResult.isFailure()) { .onFailure(ExceptionReporterHandler.INSTANCE::report);
reportFailure(extractorInputResult.exceptionOrNull()); AgentRuntimeException exception = extractorInputResult.exceptionOrNull();
Throwable throwable = extractorInputResult.exceptionOrNull(); if (exception != null) {
failureReason = buildAttemptFailureReason("参数提取失败", throwable == null ? null : throwable.getLocalizedMessage()); failureReason = exception.getMessage();
break; break;
} }
val extractorInput = extractorInputResult.getOrNull();
ExtractorInput extractorInput = extractorInputResult.getOrThrow();
ExtractorResult extractorResult = paramsExtractor.execute(extractorInput); ExtractorResult extractorResult = paramsExtractor.execute(extractorInput);
if (extractorResult == null || !extractorResult.isOk()) { if (extractorResult == null || !extractorResult.isOk()) {
@@ -508,21 +509,12 @@ public class ActionExecutor extends AbstractAgentModule.Standalone {
} }
private String resolveHistoryDescription(String actionKey) { private String resolveHistoryDescription(String actionKey) {
Result<MetaActionInfo> metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey); return actionCapability.loadMetaActionInfo(actionKey)
if (metaActionInfoResult.isFailure()) { .onFailure(ExceptionReporterHandler.INSTANCE::report)
reportFailure(metaActionInfoResult.exceptionOrNull()); .fold(
return actionKey; metaActionInfo -> metaActionInfo.getDescription().isBlank() ? actionKey : metaActionInfo.getDescription(),
} exception -> 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);
}
} }
private record MetaActionsListeningRecord(AtomicBoolean accepting, int phase) { private record MetaActionsListeningRecord(AtomicBoolean accepting, int phase) {
@@ -540,21 +532,16 @@ public class ActionExecutor extends AbstractAgentModule.Standalone {
} }
private Result<ExtractorInput> buildExtractorInput(String actionKey, @NotNull String uuid, @NotNull String description) { private Result<ExtractorInput> buildExtractorInput(String actionKey, @NotNull String uuid, @NotNull String description) {
Result<MetaActionInfo> metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey); return actionCapability.loadMetaActionInfo(actionKey).fold(
if (metaActionInfoResult.isFailure()) { metaActionInfo -> {
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(); ExtractorInput input = new ExtractorInput();
input.setMetaActionInfo(metaActionInfoResult.getOrNull()); input.setMetaActionInfo(metaActionInfo);
input.setTargetActionId(uuid); input.setTargetActionId(uuid);
input.setTargetActionDesc(description); input.setTargetActionDesc(description);
return Result.success(input); return Result.success(input);
},
Result::failure
);
} }
private CorrectorInput buildCorrectorInput(ExecutableAction executableAction) { private CorrectorInput buildCorrectorInput(ExecutableAction executableAction) {

View File

@@ -34,14 +34,16 @@ public class ParamsExtractor extends AbstractAgentModule.Sub<ExtractorInput, Ext
resolveTaskMessage(input) resolveTaskMessage(input)
); );
Result<ExtractorResult> result = formattedChat(messages, ExtractorResult.class); Result<ExtractorResult> result = formattedChat(messages, ExtractorResult.class);
if (result.isFailure()) { return result.fold(
log.error("ParamsExtractor解析结果失败", result.exceptionOrNull()); extractorResult -> extractorResult,
exception -> {
log.error("ParamsExtractor解析结果失败", exception);
ExtractorResult fallback = new ExtractorResult(); ExtractorResult fallback = new ExtractorResult();
fallback.setOk(false); fallback.setOk(false);
fallback.setParams(new HashMap<>()); fallback.setParams(new HashMap<>());
return fallback; return fallback;
} }
return result.getOrThrow(); );
} }
private Message resolveTaskMessage(ExtractorInput input) { private Message resolveTaskMessage(ExtractorInput input) {

View File

@@ -11,6 +11,7 @@ import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.CommunicationBlockContent; import work.slhaf.partner.core.cognition.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.ContextBlock; 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.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; 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.abstracts.AbstractAgentModule;
@@ -384,11 +385,12 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
List<MetaAction> metaActions = new ArrayList<>(); List<MetaAction> metaActions = new ArrayList<>();
for (String actionKey : entry.getValue()) { for (String actionKey : entry.getValue()) {
Result<MetaAction> metaActionResult = actionCapability.loadMetaAction(actionKey); Result<MetaAction> metaActionResult = actionCapability.loadMetaAction(actionKey);
if (metaActionResult.isFailure()) { AgentRuntimeException failure = metaActionResult.onSuccess(metaActions::add)
reportFailure(metaActionResult); .onFailure(ExceptionReporterHandler.INSTANCE::report)
.exceptionOrNull();
if (failure != null) {
return null; return null;
} }
metaActions.add(metaActionResult.getOrNull());
} }
actionChain.put(entry.getKey(), metaActions); actionChain.put(entry.getKey(), metaActions);
} }
@@ -408,12 +410,14 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
List<String> actionKeys = primaryActionChain.get(fixedOrder); List<String> actionKeys = primaryActionChain.get(fixedOrder);
for (String actionKey : actionKeys) { for (String actionKey : actionKeys) {
// 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖 // 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖
Result<MetaActionInfo> metaActionInfoResult = actionCapability.loadMetaActionInfo(actionKey);
if (metaActionInfoResult.isFailure()) { Result<MetaActionInfo> infoResult = actionCapability.loadMetaActionInfo(actionKey)
reportFailure(metaActionInfoResult); .onFailure(ExceptionReporterHandler.INSTANCE::report);
if (infoResult.exceptionOrNull() != null) {
return false; return false;
} }
MetaActionInfo metaActionInfo = metaActionInfoResult.getOrNull();
MetaActionInfo metaActionInfo = infoResult.getOrThrow();
Set<String> preActions = metaActionInfo.getPreActions(); Set<String> preActions = metaActionInfo.getPreActions();
boolean preActionsExist = preActions.isEmpty(); boolean preActionsExist = preActions.isEmpty();
if (!preActionsExist) { if (!preActionsExist) {
@@ -444,12 +448,6 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
return true; return true;
} }
private void reportFailure(Result<?> result) {
if (result.exceptionOrNull() instanceof work.slhaf.partner.framework.agent.exception.AgentException agentException) {
ExceptionReporterHandler.INSTANCE.report(agentException);
}
}
private void fixOrder(Map<Integer, List<String>> primaryActionChain) { private void fixOrder(Map<Integer, List<String>> primaryActionChain) {
Map<Integer, List<String>> tempChain = new HashMap<>(primaryActionChain); Map<Integer, List<String>> tempChain = new HashMap<>(primaryActionChain);
primaryActionChain.clear(); primaryActionChain.clear();

View File

@@ -66,15 +66,14 @@ public class ActionEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, Lis
messages, messages,
EvaluatorResult.class EvaluatorResult.class
); );
if (result.isFailure()) { result
log.error("ActionEvaluator评估失败: {}", tendency, result.exceptionOrNull()); .onFailure(exception -> log.error("ActionEvaluator评估失败: {}", tendency, exception))
return; .onSuccess(evaluatorResult -> {
}
EvaluatorResult evaluatorResult = result.getOrThrow();
evaluatorResult.setTendency(tendency); evaluatorResult.setTendency(tendency);
synchronized (evaluatorResults) { synchronized (evaluatorResults) {
evaluatorResults.add(evaluatorResult); evaluatorResults.add(evaluatorResult);
} }
});
} finally { } finally {
latch.countDown(); latch.countDown();
} }

View File

@@ -27,12 +27,14 @@ public class ActionExtractor extends AbstractAgentModule.Sub<String, ExtractorRe
new Message(Message.Character.USER, input) new Message(Message.Character.USER, input)
); );
Result<ExtractorResult> result = formattedChat(messages, ExtractorResult.class); Result<ExtractorResult> result = formattedChat(messages, ExtractorResult.class);
if (result.isSuccess()) { return result.fold(
return result.getOrThrow(); extractorResult -> extractorResult,
} exception -> {
log.error("提取信息出错", result.exceptionOrNull()); log.error("提取信息出错", exception);
return new ExtractorResult(); return new ExtractorResult();
} }
);
}
@NotNull @NotNull
@Override @Override

View File

@@ -6,13 +6,13 @@ import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.*; 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.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; 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.factory.component.annotation.Init;
import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.ActivateModel;
import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer; import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer;
import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.pojo.Message;
import work.slhaf.partner.framework.agent.support.Result;
import work.slhaf.partner.runtime.PartnerRunningFlowContext; import work.slhaf.partner.runtime.PartnerRunningFlowContext;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
@@ -67,11 +67,11 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
private void executeChat(PartnerRunningFlowContext runningFlowContext) { private void executeChat(PartnerRunningFlowContext runningFlowContext) {
StreamChatMessageConsumer consumer = ReplyDispatcher.INSTANCE.createConsumer(runningFlowContext.getTarget()); StreamChatMessageConsumer consumer = ReplyDispatcher.INSTANCE.createConsumer(runningFlowContext.getTarget());
Result<kotlin.Unit> result = this.streamChat(buildChatMessages(runningFlowContext), consumer); this.streamChat(buildChatMessages(runningFlowContext), consumer)
if (result.isFailure()) { .onFailure(exception -> {
log.error("Streaming response failed", result.exceptionOrNull()); ExceptionReporterHandler.INSTANCE.report(exception);
consumer.onDelta(INTERRUPTED_MARKER); consumer.onDelta(INTERRUPTED_MARKER);
} });
updateChatMessages(runningFlowContext, consumer.collectResponse()); updateChatMessages(runningFlowContext, consumer.collectResponse());
updateContext(); updateContext();
} }

View File

@@ -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.factory.component.annotation.Init;
import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.ActivateModel;
import work.slhaf.partner.framework.agent.model.pojo.Message; 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.TaskBlock;
import work.slhaf.partner.module.memory.selector.ActivatedMemorySlice; import work.slhaf.partner.module.memory.selector.ActivatedMemorySlice;
import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchInput; import work.slhaf.partner.module.memory.selector.evaluator.entity.EvaluatorBatchInput;
@@ -65,16 +64,15 @@ public class SliceSelectEvaluator extends AbstractAgentModule.Sub<EvaluatorInput
contextMessage, contextMessage,
resolveTaskMessage(batchInput) resolveTaskMessage(batchInput)
); );
Result<EvaluatorBatchResult> batchResult = formattedChat(messages, EvaluatorBatchResult.class); formattedChat(messages, EvaluatorBatchResult.class)
if (batchResult.isFailure()) { .onFailure(exception -> log.debug("切片评估失败,已跳过当前切片", exception))
log.debug("切片评估失败,已跳过当前切片", batchResult.exceptionOrNull()); .onSuccess(evaluatorBatchResult -> {
return; if (evaluatorBatchResult.isPassed()) {
}
if (batchResult.getOrThrow().isPassed()) {
synchronized (result) { synchronized (result) {
result.add(slice); result.add(slice);
} }
} }
});
} finally { } finally {
latch.countDown(); latch.countDown();
} }

View File

@@ -43,15 +43,19 @@ public class MemorySelectExtractor extends AbstractAgentModule.Sub<ExtractorInpu
messages, messages,
ExtractorResult.class ExtractorResult.class
); );
if (result.isSuccess()) { extractorResult = result.fold(
extractorResult = result.getOrThrow(); value -> {
log.debug("[MemorySelectExtractor] 主题提取结果: {}", extractorResult); log.debug("[MemorySelectExtractor] 主题提取结果: {}", value);
} else { return value;
log.error("[MemorySelectExtractor] 主题提取出错: ", result.exceptionOrNull()); },
extractorResult = new ExtractorResult(); exception -> {
extractorResult.setRecall(false); log.error("[MemorySelectExtractor] 主题提取出错: ", exception);
extractorResult.setMatches(List.of()); ExtractorResult fallback = new ExtractorResult();
fallback.setRecall(false);
fallback.setMatches(List.of());
return fallback;
} }
);
return fix(extractorResult); return fix(extractorResult);
} }

View File

@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.exception.ActionLookupException; 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.framework.agent.support.Result;
import work.slhaf.partner.module.action.executor.entity.HistoryAction; import work.slhaf.partner.module.action.executor.entity.HistoryAction;
@@ -350,15 +351,12 @@ class ActionCoreTest {
actionCore.registerMetaActions(Map.of("builtin::demo", metaActionInfo)); actionCore.registerMetaActions(Map.of("builtin::demo", metaActionInfo));
Result<MetaAction> success = actionCore.loadMetaAction("builtin::demo"); Result<MetaAction> success = actionCore.loadMetaAction("builtin::demo");
assertTrue(success.isSuccess()); assertEquals("demo", success.fold(MetaAction::getName, ex -> fail(ex.getMessage())));
assertEquals("demo", success.getOrNull().getName());
Result<MetaAction> failure = actionCore.loadMetaAction("builtin::missing"); Result<MetaAction> failure = actionCore.loadMetaAction("builtin::missing");
assertTrue(failure.isFailure()); assertInstanceOf(ActionLookupException.class, assertThrows(AgentRuntimeException.class, failure::getOrThrow));
assertInstanceOf(ActionLookupException.class, failure.exceptionOrNull());
Result<MetaActionInfo> infoFailure = actionCore.loadMetaActionInfo("builtin::missing"); Result<MetaActionInfo> infoFailure = actionCore.loadMetaActionInfo("builtin::missing");
assertTrue(infoFailure.isFailure()); assertInstanceOf(ActionLookupException.class, assertThrows(AgentRuntimeException.class, infoFailure::getOrThrow));
assertInstanceOf(ActionLookupException.class, infoFailure.exceptionOrNull());
} }
} }

View File

@@ -8,6 +8,7 @@ import com.openai.core.http.StreamResponse;
import com.openai.models.chat.completions.*; import com.openai.models.chat.completions.*;
import kotlin.Unit; import kotlin.Unit;
import org.jetbrains.annotations.NotNull; 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.exception.ModelInvokeException;
import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer; import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer;
import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.pojo.Message;
@@ -160,21 +161,14 @@ public class OpenAiCompatibleProvider extends ModelProvider {
} }
private <T> Result<T> executeWithRetry(String failureMessage, ThrowingSupplier<T> supplier) { private <T> Result<T> executeWithRetry(String failureMessage, ThrowingSupplier<T> supplier) {
Exception lastFailure = null; AgentRuntimeException lastFailure = null;
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
Result<T> result = Result.runCatching(supplier::get); Result<T> result = Result.runCatching(supplier::get);
if (result.isSuccess()) { AgentRuntimeException failure = result.exceptionOrNull();
if (failure == null) {
return result; return result;
} }
Throwable throwable = result.exceptionOrNull(); lastFailure = failure;
if (throwable instanceof Exception exception) {
lastFailure = exception;
continue;
}
if (throwable instanceof Error error) {
throw error;
}
return Result.failure(invokeException(failureMessage, throwable));
} }
return Result.failure(invokeException(failureMessage, lastFailure)); return Result.failure(invokeException(failureMessage, lastFailure));
} }

View File

@@ -1,29 +1,20 @@
package work.slhaf.partner.framework.agent.support package work.slhaf.partner.framework.agent.support
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException import work.slhaf.partner.framework.agent.exception.AgentRuntimeException
import java.util.function.Consumer
import java.util.function.Function
class Result<T> private constructor( class Result<T> private constructor(
private val value: T?, 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 { fun getOrThrow(): T {
if (exception == null) { if (exception == null) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return value as T return value as T
} }
when (exception) { throw exception
is AgentRuntimeException, is Error -> throw exception
else -> throw AgentRuntimeException(exception.localizedMessage, exception)
}
} }
fun getOrDefault(defaultValue: T): T { fun getOrDefault(defaultValue: T): T {
@@ -35,6 +26,35 @@ class Result<T> private constructor(
} }
} }
fun exceptionOrNull(): AgentRuntimeException? = exception
fun onSuccess(consumer: Consumer<in T>): Result<T> {
if (exception == null) {
@Suppress("UNCHECKED_CAST")
consumer.accept(value as T)
}
return this
}
fun onFailure(consumer: Consumer<in AgentRuntimeException>): Result<T> {
if (exception != null) {
consumer.accept(exception)
}
return this
}
fun <R> fold(
onSuccess: Function<in T, out R>,
onFailure: Function<in AgentRuntimeException, out R>
): R {
return if (exception == null) {
@Suppress("UNCHECKED_CAST")
onSuccess.apply(value as T)
} else {
onFailure.apply(exception)
}
}
override fun toString(): String { override fun toString(): String {
return if (exception == null) { return if (exception == null) {
"Result.success($value)" "Result.success($value)"
@@ -44,7 +64,7 @@ class Result<T> private constructor(
} }
fun interface ThrowingSupplier<T> { fun interface ThrowingSupplier<T> {
@Throws(Throwable::class) @Throws(Exception::class)
fun get(): T fun get(): T
} }
@@ -53,19 +73,19 @@ class Result<T> private constructor(
fun <T> success(value: T): Result<T> = Result(value, null) fun <T> success(value: T): Result<T> = Result(value, null)
@JvmStatic @JvmStatic
fun <T> failure(exception: Throwable): Result<T> = Result(null, exception) fun <T> failure(exception: AgentRuntimeException): Result<T> = Result(null, exception)
@JvmStatic @JvmStatic
fun <T> runCatching(block: ThrowingSupplier<T>): Result<T> { fun <T> runCatching(block: ThrowingSupplier<T>): Result<T> {
return try { return try {
success(block.get()) success(block.get())
} catch (throwable: Throwable) { } catch (exception: Exception) {
failure( failure(
when (throwable) { when (exception) {
is AgentRuntimeException, is Error -> throwable is AgentRuntimeException -> exception
else -> AgentRuntimeException( else -> AgentRuntimeException(
throwable.message ?: "Unexpected runtime failure.", exception.message ?: "Unexpected runtime failure.",
throwable exception
) )
} }
) )

View File

@@ -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<String> result = Result.failure(exception);
assertSame(exception, result.exceptionOrNull());
AtomicReference<AgentRuntimeException> 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<String> 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<String> 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<String> successValue = new AtomicReference<>();
AtomicReference<AgentRuntimeException> 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.<String>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.<String>failure(new AgentRuntimeException("bad")).fold(
value -> "success:" + value,
ex -> "failure:" + ex.getMessage()
);
assertEquals("success:ok", success);
assertEquals("failure:bad", failure);
}
}