diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrectionRecognizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrectionRecognizer.java index 5381edd6..3db87fb3 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrectionRecognizer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrectionRecognizer.java @@ -30,7 +30,7 @@ public class ActionCorrectionRecognizer extends AbstractAgentModule.Sub messages = List.of( - resolveContextMessage(), - resolveTaskMessage(input) - ); - result = formattedChat(messages, ExtractorResult.class); - } catch (Exception e) { - log.error("ParamsExtractor解析结果失败", e); - result = new ExtractorResult(); - result.setOk(false); - result.setParams(new HashMap<>()); + List messages = List.of( + resolveContextMessage(), + resolveTaskMessage(input) + ); + Result 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; + return result.getOrThrow(); } private Message resolveTaskMessage(ExtractorInput input) { 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 1de158c4..59e3b6ba 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 @@ -15,6 +15,7 @@ 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.action.planner.evaluator.entity.EvaluatorInput; import work.slhaf.partner.module.action.planner.evaluator.entity.EvaluatorResult; @@ -61,10 +62,15 @@ public class ActionEvaluator extends AbstractAgentModule.Sub result = formattedChat( messages, EvaluatorResult.class ); + if (result.isFailure()) { + log.error("ActionEvaluator评估失败: {}", tendency, result.exceptionOrNull()); + return; + } + EvaluatorResult evaluatorResult = result.getOrThrow(); evaluatorResult.setTendency(tendency); synchronized (evaluatorResults) { evaluatorResults.add(evaluatorResult); 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 1b7bbf86..cb512989 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 @@ -7,6 +7,7 @@ import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCa import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; 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.action.planner.extractor.entity.ExtractorResult; import java.util.List; @@ -18,23 +19,18 @@ public class ActionExtractor extends AbstractAgentModule.Sub messages = List.of( - cognitionCapability.contextWorkspace().resolve(List.of( - ContextBlock.VisibleDomain.COGNITION, - ContextBlock.VisibleDomain.ACTION - )).encodeToMessage(), - new Message(Message.Character.USER, input) - ); - return formattedChat( - messages, - ExtractorResult.class - ); - } catch (Exception e) { - log.error("提取信息出错", e); - } + List messages = List.of( + cognitionCapability.contextWorkspace().resolve(List.of( + ContextBlock.VisibleDomain.COGNITION, + ContextBlock.VisibleDomain.ACTION + )).encodeToMessage(), + new Message(Message.Character.USER, input) + ); + Result result = formattedChat(messages, ExtractorResult.class); + if (result.isSuccess()) { + return result.getOrThrow(); } + log.error("提取信息出错", result.exceptionOrNull()); return new ExtractorResult(); } 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 c6ab7a27..22f2f7de 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 @@ -12,6 +12,7 @@ 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; @@ -28,6 +29,8 @@ import java.util.stream.Collectors; @Data public class CommunicationProducer extends AbstractAgentModule.Running implements ActivateModel { + private static final String INTERRUPTED_MARKER = " [response interrupted due to internal exception]"; + private static final String MODULE_PROMPT = """ 你是 Partner 的表达模块。 你接下来收到的消息固定分为三个区段: @@ -64,7 +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); + } updateChatMessages(runningFlowContext, consumer.collectResponse()); updateContext(); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/DialogRollingService.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/DialogRollingService.java index 3fe1c664..bd7e94c2 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/DialogRollingService.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/DialogRollingService.java @@ -45,7 +45,7 @@ public class DialogRollingService extends AbstractAgentModule.Standalone impleme List messages = List.of( resolveTaskBlock(snapshotMessages) ); - return chat(messages); + return chat(messages).getOrThrow(); } private @NotNull BlockContent buildDialogAbstractBlock(String summary, @Nullable String unitId, @Nullable String sliceId) { 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 58067995..8026668f 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,6 +15,7 @@ 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; @@ -64,13 +65,16 @@ 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); } } - } catch (Exception ignore) { } 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 3b6b62ab..e8d71ee2 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 @@ -13,6 +13,7 @@ import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAg import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule; 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.runtime.MemoryRuntime; import work.slhaf.partner.module.memory.selector.extractor.entity.ExtractorInput; @@ -34,18 +35,19 @@ public class MemorySelectExtractor extends AbstractAgentModule.Sub messages = List.of( - resolveContextMessage(), - resolveTaskMessage(input) - ); - extractorResult = formattedChat( - messages, - ExtractorResult.class - ); + List messages = List.of( + resolveContextMessage(), + resolveTaskMessage(input) + ); + Result result = formattedChat( + messages, + ExtractorResult.class + ); + if (result.isSuccess()) { + extractorResult = result.getOrThrow(); log.debug("[MemorySelectExtractor] 主题提取结果: {}", extractorResult); - } catch (Exception e) { - log.error("[MemorySelectExtractor] 主题提取出错: ", e); + } else { + log.error("[MemorySelectExtractor] 主题提取出错: ", result.exceptionOrNull()); extractorResult = new ExtractorResult(); extractorResult.setRecall(false); extractorResult.setMatches(List.of()); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/MultiSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/MultiSummarizer.java index 901b837b..74d26baa 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/MultiSummarizer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/MultiSummarizer.java @@ -28,7 +28,7 @@ public class MultiSummarizer extends AbstractAgentModule.Sub, Voi private String singleExecute(String primaryContent) { try { - return chat(List.of(new Message(Message.Character.USER, primaryContent))); + return chat(List.of(new Message(Message.Character.USER, primaryContent))).getOrThrow(); } catch (Exception e) { log.error("[SingleSummarizer] 单消息总结出错: ", e); return primaryContent; diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/TotalSummarizer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/TotalSummarizer.java index 10cb871f..8b4777bd 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/TotalSummarizer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/memory/updater/summarizer/TotalSummarizer.java @@ -17,7 +17,7 @@ public class TotalSummarizer extends AbstractAgentModule.Sub): String { + fun chat(messages: List): Result { return ModelRuntimeRegistry.resolveProvider(modelKey()).chat(mergeMessages(messages)) } - fun streamChat(messages: List, handler: StreamChatMessageConsumer) { - ModelRuntimeRegistry.resolveProvider(modelKey()).streamChat(mergeMessages(messages), handler) + fun streamChat( + messages: List, + handler: StreamChatMessageConsumer + ): work.slhaf.partner.framework.agent.support.Result { + return ModelRuntimeRegistry.resolveProvider(modelKey()).streamChat(mergeMessages(messages), handler) } - fun formattedChat(messages: List, responseType: Class): T { + fun formattedChat(messages: List, responseType: Class): Result { return ModelRuntimeRegistry.resolveProvider(modelKey()).formattedChat(mergeMessages(messages), responseType) } @@ -39,4 +43,4 @@ interface ActivateModel { } fun modulePrompt(): List = emptyList() -} \ No newline at end of file +} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/ModelRuntimeRegistry.kt b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/ModelRuntimeRegistry.kt index 4a3dd0f2..963d34c1 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/ModelRuntimeRegistry.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/model/ModelRuntimeRegistry.kt @@ -46,7 +46,13 @@ object ModelRuntimeRegistry : Configurable, ConfigRegistration baseProvider[config.name] = - OpenAiCompatibleProvider(config.baseUrl, config.apiKey, config.defaultModel) + OpenAiCompatibleProvider( + config.name, + DEFAULT_PROVIDER, + config.baseUrl, + config.apiKey, + config.defaultModel + ) } } @@ -61,11 +67,7 @@ object ModelRuntimeRegistry : Configurable, ConfigRegistration, consumer: StreamChatMessageConsumer) + abstract fun streamChat(messages: List, consumer: StreamChatMessageConsumer): Result - abstract fun chat(messages: List): String + abstract fun chat(messages: List): Result - abstract fun formattedChat(messages: List, type: Class): T + abstract fun formattedChat(messages: List, type: Class): Result } data class ProviderOverride( @@ -24,4 +27,4 @@ data class ProviderOverride( val maxTokens: Int?, val extras: JSONObject? -) \ No newline at end of file +) 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 28042a77..47ae83b4 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 @@ -6,24 +6,33 @@ import com.openai.client.okhttp.OpenAIOkHttpClient; import com.openai.core.JsonValue; 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.ModelInvokeException; import work.slhaf.partner.framework.agent.model.StreamChatMessageConsumer; import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.provider.ModelProvider; import work.slhaf.partner.framework.agent.model.provider.ProviderOverride; +import work.slhaf.partner.framework.agent.support.Result; import java.time.Duration; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class OpenAiCompatibleProvider extends ModelProvider { + private static final int MAX_ATTEMPTS = 3; + private final String baseUrl; private final String apiKey; private final String model; private final OpenAIClient client; - public OpenAiCompatibleProvider(String baseUrl, String apikey, String model) { + public OpenAiCompatibleProvider(String providerName, String modelKey, String baseUrl, String apikey, String model) { + super(providerName, modelKey, null); this.client = OpenAIOkHttpClient.builder() .baseUrl(baseUrl) .apiKey(apikey) @@ -34,8 +43,8 @@ public class OpenAiCompatibleProvider extends ModelProvider { this.model = model; } - public OpenAiCompatibleProvider(String baseUrl, String apikey, String model, ProviderOverride override) { - super(override); + public OpenAiCompatibleProvider(String providerName, String modelKey, String baseUrl, String apikey, String model, ProviderOverride override) { + super(providerName, modelKey, override); this.client = OpenAIOkHttpClient.builder() .baseUrl(baseUrl) .apiKey(apikey) @@ -46,27 +55,59 @@ public class OpenAiCompatibleProvider extends ModelProvider { this.model = model; } - public @NotNull String chat(@NotNull List messages) { - ChatCompletionCreateParams params = buildParams(messages); - return extractText(client.chat().completions().create(params)); + @Override + public @NotNull Result chat(@NotNull List messages) { + return executeWithRetry( + "OpenAI-compatible provider failed to complete the chat request after 3 attempts.", + () -> extractText(client.chat().completions().create(buildParams(messages))) + ); } - public void streamChat(@NotNull List messages, StreamChatMessageConsumer handler) { - ChatCompletionCreateParams params = buildParams(messages); - try (StreamResponse streamResponse = client.chat().completions().createStreaming(params)) { - streamResponse.stream() - .flatMap(completion -> completion.choices().stream()) - .flatMap(choice -> choice.delta().content().stream()) - .filter(delta -> !delta.isEmpty()) - .forEach(handler::onDelta); + @Override + public @NotNull Result streamChat(@NotNull List messages, @NotNull StreamChatMessageConsumer handler) { + Exception lastFailure = null; + int remainingAttempts = MAX_ATTEMPTS; + while (remainingAttempts > 0) { + boolean emitted = false; + try (StreamResponse streamResponse = client.chat().completions().createStreaming(buildParams(messages))) { + Iterator iterator = streamResponse.stream().iterator(); + while (iterator.hasNext()) { + ChatCompletionChunk chunk = iterator.next(); + for (ChatCompletionChunk.Choice choice : chunk.choices()) { + String delta = choice.delta().content().orElse(""); + if (delta.isEmpty()) { + continue; + } + emitted = true; + handler.onDelta(delta); + } + } + return Result.success(Unit.INSTANCE); + } catch (Exception e) { + lastFailure = e; + remainingAttempts--; + if (emitted || remainingAttempts == 0) { + break; + } + } } + return Result.failure(invokeException( + "OpenAI-compatible provider failed to stream the chat response after 3 attempts.", + lastFailure + )); } - public T formattedChat(@NotNull List messages, @NotNull Class responseType) { - StructuredChatCompletionCreateParams params = buildParams(messages).toBuilder() - .responseFormat(responseType) - .build(); - return extractStructured(client.chat().completions().create(params)); + @Override + public @NotNull Result formattedChat(@NotNull List messages, @NotNull Class responseType) { + return executeWithRetry( + "OpenAI-compatible provider failed to complete the structured chat request after 3 attempts.", + () -> { + StructuredChatCompletionCreateParams params = buildParams(messages).toBuilder() + .responseFormat(responseType) + .build(); + return extractStructured(client.chat().completions().create(params)); + } + ); } private ChatCompletionCreateParams buildParams(List messages) { @@ -87,9 +128,7 @@ public class OpenAiCompatibleProvider extends ModelProvider { } JSONObject extras = override.getExtras(); if (extras != null) { - extras.forEach((key, value) -> { - paramsBuilder.putAdditionalBodyProperty(key, JsonValue.from(value)); - }); + extras.forEach((key, value) -> paramsBuilder.putAdditionalBodyProperty(key, JsonValue.from(value))); } } @@ -98,22 +137,81 @@ public class OpenAiCompatibleProvider extends ModelProvider { private String extractText(ChatCompletion completion) { if (completion.choices().isEmpty()) { - throw new IllegalStateException("OpenAI chat completion returned no choices."); + throw invokeException("OpenAI chat completion returned no choices.", null); } return completion.choices().getFirst().message().content() - .orElseThrow(() -> new IllegalStateException("OpenAI chat completion returned empty content.")); + .orElseThrow(() -> invokeException("OpenAI chat completion returned empty content.", null)); } private T extractStructured(StructuredChatCompletion completion) { if (completion.choices().isEmpty()) { - throw new IllegalStateException("OpenAI structured chat completion returned no choices."); + throw invokeException("OpenAI structured chat completion returned no choices.", null); } return completion.choices().getFirst().message().content() - .orElseThrow(() -> new IllegalStateException("OpenAI structured chat completion returned empty content.")); + .orElseThrow(() -> invokeException("OpenAI structured chat completion returned empty content.", null)); } @Override - public @NotNull ModelProvider fork(@NotNull ProviderOverride override) { - return new OpenAiCompatibleProvider(baseUrl, apiKey, override.getModel(), override); + public @NotNull ModelProvider fork(@NotNull String modelKey, ProviderOverride override) { + if (override == null) { + return new OpenAiCompatibleProvider(getProviderName(), modelKey, baseUrl, apiKey, model, getOverride()); + } + return new OpenAiCompatibleProvider(getProviderName(), modelKey, baseUrl, apiKey, override.getModel(), override); + } + + private Result executeWithRetry(String failureMessage, ThrowingSupplier supplier) { + Exception lastFailure = null; + for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + Result result = Result.runCatching(supplier::get); + if (result.isSuccess()) { + 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)); + } + return Result.failure(invokeException(failureMessage, lastFailure)); + } + + private ModelInvokeException invokeException(String message, Throwable cause) { + return new ModelInvokeException( + message, + getProviderName(), + getModelKey(), + baseUrl, + model, + getOverride() == null ? Map.of() : toOverrideReport(getOverride()), + cause + ); + } + + private Map toOverrideReport(ProviderOverride override) { + Map result = new LinkedHashMap<>(); + result.put("model", override.getModel()); + if (override.getTemperature() != null) { + result.put("temperature", override.getTemperature().toString()); + } + if (override.getTopP() != null) { + result.put("topP", override.getTopP().toString()); + } + if (override.getMaxTokens() != null) { + result.put("maxTokens", override.getMaxTokens().toString()); + } + JSONObject extras = override.getExtras(); + if (extras != null) { + extras.forEach((key, value) -> result.put("extra." + key, value == null ? "null" : value.toString())); + } + return result; + } + + @FunctionalInterface + private interface ThrowingSupplier { + T get() throws Exception; } } 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 new file mode 100644 index 00000000..a41882ea --- /dev/null +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/support/Result.kt @@ -0,0 +1,76 @@ +package work.slhaf.partner.framework.agent.support + +import work.slhaf.partner.framework.agent.exception.AgentRuntimeException + +class Result private constructor( + private val value: T?, + private val exception: Throwable? +) { + + 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 RuntimeException -> throw exception + is Error -> throw exception + else -> throw IllegalStateException(exception.message, exception) + } + } + + fun getOrDefault(defaultValue: T): T { + return if (exception == null) { + @Suppress("UNCHECKED_CAST") + value as T + } else { + defaultValue + } + } + + override fun toString(): String { + return if (exception == null) { + "Result.success($value)" + } else { + "Result.failure($exception)" + } + } + + fun interface ThrowingSupplier { + @Throws(Throwable::class) + fun get(): T + } + + companion object { + @JvmStatic + fun success(value: T): Result = Result(value, null) + + @JvmStatic + fun failure(exception: Throwable): Result = Result(null, exception) + + @JvmStatic + fun runCatching(block: ThrowingSupplier): Result { + return try { + success(block.get()) + } catch (throwable: Throwable) { + failure( + when (throwable) { + is AgentRuntimeException, is Error -> throwable + else -> AgentRuntimeException( + throwable.message ?: "Unexpected runtime failure.", + throwable + ) + } + ) + } + } + } +}