From ac7c997aa37f43ae36e2578ef990150af8423c1b Mon Sep 17 00:00:00 2001 From: Marc Yin <3253984909@qq.com> Date: Sun, 9 Aug 2026 10:41:14 +0800 Subject: [PATCH 1/2] fix(ragflow): preserve final retry response --- .../integration/ragflow/RAGFlowClient.java | 9 ++++-- .../ragflow/RAGFlowClientTest.java | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java index ccdac26d7e..440bb4f566 100644 --- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java +++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java @@ -316,17 +316,20 @@ public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; IOException lastException = null; + boolean responseReturned = false; try { for (int attempt = 0; attempt <= maxRetries; attempt++) { try { if (response != null) { response.close(); + response = null; } response = chain.proceed(request); // Don't retry on successful responses or client errors (4xx) if (response.isSuccessful() || response.code() < 500) { + responseReturned = true; return response; } @@ -365,10 +368,12 @@ public Response intercept(Chain chain) throws IOException { throw lastException; } + responseReturned = true; return response; } finally { - // Ensure response is closed if we're not returning it successfully - if (response != null && (lastException != null || !response.isSuccessful())) { + // Keep the response open for the caller to consume, including final error + // responses. + if (response != null && !responseReturned) { response.close(); } } diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java index 5473ff3c60..1f2ea55712 100644 --- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java +++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java @@ -742,6 +742,36 @@ void testHttp503ServiceUnavailable() { assertTrue(exception.getMessage().contains("server error")); } + @Test + void testRetryKeepsFinalErrorResponseBodyReadable() { + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(500) + .setBody("{\"message\": \"first failure\"}")); + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(500) + .setBody("{\"message\": \"final failure\"}")); + + RAGFlowConfig config = + RAGFlowConfig.builder() + .apiKey("test-api-key") + .baseUrl(mockWebServer.url("").toString().replaceAll("/$", "")) + .addDatasetId("dataset-123") + .maxRetries(1) + .build(); + + RAGFlowClient client = new RAGFlowClient(config); + + RAGFlowApiException exception = + assertThrows( + RAGFlowApiException.class, + () -> client.retrieve("test query", null, null, null).block()); + + assertTrue(exception.getMessage().contains("final failure")); + assertEquals(2, mockWebServer.getRequestCount()); + } + @Test void testApiErrorWithNonZeroCode() { String errorResponse = From fcabde808c0b47fd878d10f1e7e5c1189afa0882 Mon Sep 17 00:00:00 2001 From: Marc Yin <3253984909@qq.com> Date: Sun, 9 Aug 2026 19:27:37 +0800 Subject: [PATCH 2/2] fix(ragflow): prefer final response after retries --- .../integration/ragflow/RAGFlowClient.java | 6 -- .../ragflow/RAGFlowClientTest.java | 59 +++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java index 440bb4f566..064ba4fccc 100644 --- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java +++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java @@ -315,7 +315,6 @@ private static class RetryInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; - IOException lastException = null; boolean responseReturned = false; try { @@ -340,7 +339,6 @@ public Response intercept(Chain chain) throws IOException { maxRetries + 1); } catch (IOException e) { - lastException = e; logger.warn( "RAGFlow request failed with exception, attempt {}/{}: {}", attempt + 1, @@ -364,10 +362,6 @@ public Response intercept(Chain chain) throws IOException { } } - if (lastException != null) { - throw lastException; - } - responseReturned = true; return response; } finally { diff --git a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java index 1f2ea55712..10e49fbde3 100644 --- a/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java +++ b/agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java @@ -17,8 +17,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.fasterxml.jackson.core.type.TypeReference; import io.agentscope.core.rag.integration.ragflow.exception.RAGFlowApiException; @@ -26,8 +32,14 @@ import io.agentscope.core.rag.integration.ragflow.model.RAGFlowResponse; import io.agentscope.core.util.JsonUtils; import java.io.IOException; +import java.lang.reflect.Field; import java.util.List; import java.util.Map; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -772,6 +784,53 @@ void testRetryKeepsFinalErrorResponseBodyReadable() { assertEquals(2, mockWebServer.getRequestCount()); } + @Test + void testRetryReturnsFinalErrorResponseAfterEarlierIOException() throws Exception { + RAGFlowConfig config = + RAGFlowConfig.builder() + .apiKey("test-api-key") + .baseUrl(mockWebServer.url("").toString().replaceAll("/$", "")) + .addDatasetId("dataset-123") + .maxRetries(1) + .build(); + RAGFlowClient client = new RAGFlowClient(config); + + Field httpClientField = RAGFlowClient.class.getDeclaredField("httpClient"); + httpClientField.setAccessible(true); + OkHttpClient httpClient = (OkHttpClient) httpClientField.get(client); + Interceptor retryInterceptor = + httpClient.interceptors().stream() + .filter( + interceptor -> + interceptor + .getClass() + .getSimpleName() + .equals("RetryInterceptor")) + .findFirst() + .orElseThrow(); + + Interceptor.Chain chain = mock(Interceptor.Chain.class); + Request request = new Request.Builder().url(mockWebServer.url("/api/v1/retrieval")).build(); + ResponseBody responseBody = mock(ResponseBody.class); + Response finalErrorResponse = mock(Response.class); + + when(chain.request()).thenReturn(request); + when(chain.proceed(request)) + .thenThrow(new IOException("first attempt failed")) + .thenReturn(finalErrorResponse); + when(finalErrorResponse.isSuccessful()).thenReturn(false); + when(finalErrorResponse.code()).thenReturn(500); + when(finalErrorResponse.body()).thenReturn(responseBody); + when(responseBody.string()).thenReturn("{\"message\": \"final failure\"}"); + + Response returned = retryInterceptor.intercept(chain); + + assertSame(finalErrorResponse, returned); + assertEquals("{\"message\": \"final failure\"}", returned.body().string()); + verify(chain, times(2)).proceed(request); + verify(finalErrorResponse, never()).close(); + } + @Test void testApiErrorWithNonZeroCode() { String errorResponse =