mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
refactor(framework): add Result chain APIs and align runtime exception handling
This commit is contained in:
@@ -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 <T> Result<T> executeWithRetry(String failureMessage, ThrowingSupplier<T> supplier) {
|
||||
Exception lastFailure = null;
|
||||
AgentRuntimeException lastFailure = null;
|
||||
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
Result<T> 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));
|
||||
}
|
||||
|
||||
@@ -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<T> 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<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 {
|
||||
return if (exception == null) {
|
||||
"Result.success($value)"
|
||||
@@ -44,7 +64,7 @@ class Result<T> private constructor(
|
||||
}
|
||||
|
||||
fun interface ThrowingSupplier<T> {
|
||||
@Throws(Throwable::class)
|
||||
@Throws(Exception::class)
|
||||
fun get(): T
|
||||
}
|
||||
|
||||
@@ -53,19 +73,19 @@ class Result<T> private constructor(
|
||||
fun <T> success(value: T): Result<T> = Result(value, null)
|
||||
|
||||
@JvmStatic
|
||||
fun <T> failure(exception: Throwable): Result<T> = Result(null, exception)
|
||||
fun <T> failure(exception: AgentRuntimeException): Result<T> = Result(null, exception)
|
||||
|
||||
@JvmStatic
|
||||
fun <T> runCatching(block: ThrowingSupplier<T>): Result<T> {
|
||||
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
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user