diff --git a/code-execution-engines/langchain4j-code-execution-engine-judge0/pom.xml b/code-execution-engines/langchain4j-code-execution-engine-judge0/pom.xml
index 7bb825fb83..930421a92d 100644
--- a/code-execution-engines/langchain4j-code-execution-engine-judge0/pom.xml
+++ b/code-execution-engines/langchain4j-code-execution-engine-judge0/pom.xml
@@ -22,8 +22,16 @@
- com.squareup.okhttp3
- okhttp
+ dev.langchain4j
+ langchain4j-http-client
+ 1.18.0-SNAPSHOT
+
+
+
+ dev.langchain4j
+ langchain4j-http-client-jdk
+ 1.18.0-SNAPSHOT
+ runtime
diff --git a/code-execution-engines/langchain4j-code-execution-engine-judge0/src/main/java/dev/langchain4j/code/judge0/Judge0JavaScriptEngine.java b/code-execution-engines/langchain4j-code-execution-engine-judge0/src/main/java/dev/langchain4j/code/judge0/Judge0JavaScriptEngine.java
index ffa2989e87..9b32a090f7 100644
--- a/code-execution-engines/langchain4j-code-execution-engine-judge0/src/main/java/dev/langchain4j/code/judge0/Judge0JavaScriptEngine.java
+++ b/code-execution-engines/langchain4j-code-execution-engine-judge0/src/main/java/dev/langchain4j/code/judge0/Judge0JavaScriptEngine.java
@@ -1,15 +1,19 @@
package dev.langchain4j.code.judge0;
+import static dev.langchain4j.http.client.HttpMethod.POST;
import static dev.langchain4j.internal.Utils.isNullOrBlank;
import dev.langchain4j.code.CodeExecutionEngine;
-import java.io.IOException;
+import dev.langchain4j.exception.HttpException;
+import dev.langchain4j.http.client.HttpClient;
+import dev.langchain4j.http.client.HttpRequest;
+import dev.langchain4j.http.client.HttpClientBuilderLoader;
+import dev.langchain4j.http.client.SuccessfulHttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,7 +27,6 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
private static final Logger log = LoggerFactory.getLogger(Judge0JavaScriptEngine.class);
// HTTP Constants
- private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json");
private static final String RAPID_API_HOST = "judge0-ce.p.rapidapi.com";
private static final String API_URL =
"https://judge0-ce.p.rapidapi.com/submissions?base64_encoded=true&wait=true&fields=*";
@@ -46,7 +49,7 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
private final String apiKey;
private final int languageId;
- private final OkHttpClient client;
+ private final HttpClient httpClient;
/**
* Creates a new Judge0JavaScriptEngine with the specified API key, language ID, and timeout.
@@ -65,11 +68,9 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
this.apiKey = apiKey;
this.languageId = languageId;
- this.client = new OkHttpClient.Builder()
+ this.httpClient = HttpClientBuilderLoader.loadHttpClientBuilder()
.connectTimeout(timeout)
.readTimeout(timeout)
- .writeTimeout(timeout)
- .callTimeout(timeout)
.build();
}
@@ -87,8 +88,7 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
String base64EncodedCode = Base64.getEncoder().encodeToString(code.getBytes(StandardCharsets.UTF_8));
Submission submission = new Submission(languageId, base64EncodedCode);
- RequestBody requestBody = RequestBody.create(Json.toJson(submission), MEDIA_TYPE_JSON);
- Request request = buildRequest(requestBody);
+ HttpRequest request = buildRequest(Json.toJson(submission));
return executeWithRetry(request);
}
@@ -96,15 +96,17 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
/**
* Builds a request to the Judge0 API.
*
- * @param requestBody The request body
+ * @param body The request body
* @return The built request
*/
- private Request buildRequest(RequestBody requestBody) {
- return new Request.Builder()
+ private HttpRequest buildRequest(String body) {
+ return HttpRequest.builder()
+ .method(POST)
.url(API_URL)
.addHeader("x-rapidapi-host", RAPID_API_HOST)
.addHeader("x-rapidapi-key", apiKey)
- .post(requestBody)
+ .addHeader("Content-Type", "application/json")
+ .body(body)
.build();
}
@@ -114,8 +116,8 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
* @param request The request to execute
* @return The result of the request or an error message
*/
- private String executeWithRetry(Request request) {
- Exception lastException = null;
+ private String executeWithRetry(HttpRequest request) {
+ RuntimeException lastException = null;
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
if (attempt > 0) {
@@ -128,56 +130,44 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
}
}
+ SuccessfulHttpResponse response;
try {
- return processRequest(request);
- } catch (IOException e) {
+ response = httpClient.execute(request);
+ } catch (HttpException e) {
+ return handleErrorResponse(e);
+ } catch (RuntimeException e) {
lastException = e;
log.warn("Request failed (attempt {}/{}): {}", attempt + 1, MAX_RETRIES, e.getMessage());
+ continue;
}
+
+ String responseBody = response.body();
+ if (isNullOrBlank(responseBody)) {
+ log.warn(ERROR_NULL_RESPONSE);
+ return ERROR_NULL_RESPONSE;
+ }
+ return processResponseBody(responseBody);
}
log.error("All retry attempts failed", lastException);
return "Failed after " + MAX_RETRIES + " attempts: " + lastException.getMessage();
}
- /**
- * Processes a single request to the Judge0 API.
- *
- * @param request The request to process
- * @return The result of the request or an error message
- * @throws IOException if an I/O error occurs
- */
- private String processRequest(Request request) throws IOException {
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) {
- return handleErrorResponse(response);
- }
-
- if (response.body() == null) {
- log.warn(ERROR_NULL_RESPONSE);
- return ERROR_NULL_RESPONSE;
- }
-
- String responseBody = response.body().string();
- return processResponseBody(responseBody);
- }
- }
-
/**
* Handles error responses from the Judge0 API.
*
- * @param response The error response
+ * @param exception The HTTP error carrying the status code and response body
* @return An appropriate error message
*/
- private String handleErrorResponse(Response response) {
+ private String handleErrorResponse(HttpException exception) {
String errorMessage =
- switch (response.code()) {
+ switch (exception.statusCode()) {
case 429 -> ERROR_RATE_LIMIT;
case 403 -> ERROR_FORBIDDEN;
case 404 -> ERROR_NOT_FOUND;
case 500 -> ERROR_SERVER;
case 503 -> ERROR_UNAVAILABLE;
- default -> "Unexpected error code " + response.code() + ": " + response.message();
+ default -> "Unexpected error code " + exception.statusCode() + ": " + exception.getMessage();
};
log.warn(errorMessage);
diff --git a/langchain4j-jina/pom.xml b/langchain4j-jina/pom.xml
index 7b855d8b35..4fd1b0dcda 100644
--- a/langchain4j-jina/pom.xml
+++ b/langchain4j-jina/pom.xml
@@ -20,13 +20,21 @@
- com.squareup.retrofit2
- retrofit
+ dev.langchain4j
+ langchain4j-http-client
+ 1.18.0-SNAPSHOT
- com.squareup.retrofit2
- converter-jackson
+ dev.langchain4j
+ langchain4j-http-client-jdk
+ 1.18.0-SNAPSHOT
+ runtime
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
diff --git a/langchain4j-jina/revapi.json b/langchain4j-jina/revapi.json
index cd9b68505d..3a4d0b8f1f 100644
--- a/langchain4j-jina/revapi.json
+++ b/langchain4j-jina/revapi.json
@@ -34,6 +34,16 @@
"new": "method retrofit2.Call dev.langchain4j.model.jina.internal.api.JinaApi::embedMultimodal(dev.langchain4j.model.jina.internal.api.JinaMultimodalEmbeddingRequest, java.lang.String)",
"justification": "JinaApi is an internal Retrofit API interface (internal.api package), not part of the public API"
},
+ {
+ "code": "java.class.removed",
+ "old": "interface dev.langchain4j.model.jina.internal.api.JinaApi",
+ "justification": "Internal Retrofit API interface (internal.api package) removed as part of migrating the HTTP transport to the langchain4j HttpClient abstraction; not part of the public API"
+ },
+ {
+ "code": "java.class.externalClassExposedInAPI",
+ "new": "missing-class dev.langchain4j.http.client.HttpClientBuilder",
+ "justification": "HttpClientBuilder is intentionally exposed by the Jina integration for custom HTTP client configuration"
+ },
{
"code": "java.method.removed",
"old": "method dev.langchain4j.model.output.Response> dev.langchain4j.model.jina.JinaEmbeddingModel::embedAll(java.util.List)",
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaEmbeddingModel.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaEmbeddingModel.java
index e9197ad3c3..de20f38998 100644
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaEmbeddingModel.java
+++ b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaEmbeddingModel.java
@@ -6,6 +6,7 @@ import dev.langchain4j.data.message.ContentType;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.exception.UnsupportedFeatureException;
+import dev.langchain4j.http.client.HttpClientBuilder;
import dev.langchain4j.model.ModelProvider;
import dev.langchain4j.model.embedding.DimensionAwareEmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
@@ -73,6 +74,7 @@ public class JinaEmbeddingModel extends DimensionAwareEmbeddingModel {
public JinaEmbeddingModel(JinaEmbeddingModelBuilder builder) {
this.client = JinaClient.builder()
+ .httpClientBuilder(builder.httpClientBuilder)
.baseUrl(getOrDefault(builder.baseUrl, DEFAULT_BASE_URL))
.apiKey(builder.apiKey)
.timeout(getOrDefault(builder.timeout, ofSeconds(60)))
@@ -199,6 +201,7 @@ public class JinaEmbeddingModel extends DimensionAwareEmbeddingModel {
private Boolean logRequests;
private Boolean logResponses;
private Logger logger;
+ private HttpClientBuilder httpClientBuilder;
private List listeners;
JinaEmbeddingModelBuilder() {
@@ -258,6 +261,18 @@ public class JinaEmbeddingModel extends DimensionAwareEmbeddingModel {
return this;
}
+ /**
+ * Sets a custom HTTP client builder, allowing fine-grained control over the HTTP client
+ * configuration such as timeouts and proxy settings.
+ *
+ * @param httpClientBuilder the HTTP client builder
+ * @return {@code this}
+ */
+ public JinaEmbeddingModelBuilder httpClientBuilder(HttpClientBuilder httpClientBuilder) {
+ this.httpClientBuilder = httpClientBuilder;
+ return this;
+ }
+
public JinaEmbeddingModel build() {
return new JinaEmbeddingModel(this);
}
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaScoringModel.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaScoringModel.java
index 74031f5050..b2838f058f 100644
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaScoringModel.java
+++ b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/JinaScoringModel.java
@@ -8,6 +8,7 @@ import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList;
import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.http.client.HttpClientBuilder;
import dev.langchain4j.model.jina.internal.api.JinaRerankingRequest;
import dev.langchain4j.model.jina.internal.api.JinaRerankingResponse;
import dev.langchain4j.model.jina.internal.client.JinaClient;
@@ -52,6 +53,7 @@ public class JinaScoringModel implements ScoringModel {
public JinaScoringModel(JinaScoringModelBuilder builder) {
this.client = JinaClient.builder()
+ .httpClientBuilder(builder.httpClientBuilder)
.baseUrl(getOrDefault(builder.baseUrl, DEFAULT_BASE_URL))
.apiKey(ensureNotBlank(builder.apiKey, "apiKey"))
.timeout(getOrDefault(builder.timeout, ofSeconds(60)))
@@ -97,6 +99,7 @@ public class JinaScoringModel implements ScoringModel {
private Boolean logRequests;
private Boolean logResponses;
private Logger logger;
+ private HttpClientBuilder httpClientBuilder;
JinaScoringModelBuilder() {}
@@ -144,6 +147,18 @@ public class JinaScoringModel implements ScoringModel {
return this;
}
+ /**
+ * Sets a custom HTTP client builder, allowing fine-grained control over the HTTP client
+ * configuration such as timeouts and proxy settings.
+ *
+ * @param httpClientBuilder the HTTP client builder
+ * @return {@code this}
+ */
+ public JinaScoringModelBuilder httpClientBuilder(HttpClientBuilder httpClientBuilder) {
+ this.httpClientBuilder = httpClientBuilder;
+ return this;
+ }
+
public JinaScoringModel build() {
return new JinaScoringModel(this);
}
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/api/JinaApi.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/api/JinaApi.java
deleted file mode 100644
index a002aeecda..0000000000
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/api/JinaApi.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package dev.langchain4j.model.jina.internal.api;
-
-import retrofit2.Call;
-import retrofit2.http.Body;
-import retrofit2.http.Header;
-import retrofit2.http.Headers;
-import retrofit2.http.POST;
-
-public interface JinaApi {
-
- @POST("v1/embeddings")
- @Headers({"Content-Type: application/json"})
- Call embed(@Body JinaEmbeddingRequest request,
- @Header("Authorization") String authorizationHeader);
-
- @POST("v1/embeddings")
- @Headers({"Content-Type: application/json"})
- Call embedMultimodal(@Body JinaMultimodalEmbeddingRequest request,
- @Header("Authorization") String authorizationHeader);
-
- @POST("rerank")
- @Headers({"Content-Type: application/json"})
- Call rerank(@Body JinaRerankingRequest request,
- @Header("Authorization") String authorizationHeader);
-}
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaClient.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaClient.java
index 81589a1c2a..a4f122709a 100644
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaClient.java
+++ b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaClient.java
@@ -1,53 +1,50 @@
package dev.langchain4j.model.jina.internal.client;
-import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static dev.langchain4j.http.client.HttpMethod.POST;
+import static dev.langchain4j.internal.Utils.ensureTrailingForwardSlash;
+import static dev.langchain4j.internal.Utils.getOrDefault;
import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
+import static dev.langchain4j.model.jina.internal.client.JinaJsonUtils.fromJson;
+import static dev.langchain4j.model.jina.internal.client.JinaJsonUtils.toJson;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import dev.langchain4j.internal.Utils;
-import dev.langchain4j.model.jina.internal.api.JinaApi;
+import dev.langchain4j.http.client.HttpClient;
+import dev.langchain4j.http.client.HttpClientBuilder;
+import dev.langchain4j.http.client.HttpClientBuilderLoader;
+import dev.langchain4j.http.client.HttpRequest;
+import dev.langchain4j.http.client.SuccessfulHttpResponse;
+import dev.langchain4j.http.client.log.LoggingHttpClient;
import dev.langchain4j.model.jina.internal.api.JinaEmbeddingRequest;
import dev.langchain4j.model.jina.internal.api.JinaEmbeddingResponse;
+import dev.langchain4j.model.jina.internal.api.JinaMultimodalEmbeddingRequest;
import dev.langchain4j.model.jina.internal.api.JinaRerankingRequest;
import dev.langchain4j.model.jina.internal.api.JinaRerankingResponse;
-import java.io.IOException;
import java.time.Duration;
-import okhttp3.OkHttpClient;
import org.slf4j.Logger;
-import retrofit2.Retrofit;
-import retrofit2.converter.jackson.JacksonConverterFactory;
public class JinaClient {
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(INDENT_OUTPUT);
-
- private final JinaApi jinaApi;
+ private final HttpClient httpClient;
+ private final String baseUrl;
private final String authorizationHeader;
- JinaClient(
- String baseUrl, String apiKey, Duration timeout, boolean logRequests, boolean logResponses, Logger logger) {
+ JinaClient(JinaClientBuilder builder) {
+ HttpClientBuilder httpClientBuilder =
+ getOrDefault(builder.httpClientBuilder, HttpClientBuilderLoader::loadHttpClientBuilder);
- OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
- .callTimeout(timeout)
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout);
-
- if (logRequests) {
- okHttpClientBuilder.addInterceptor(new RequestLoggingInterceptor(logger));
- }
- if (logResponses) {
- okHttpClientBuilder.addInterceptor(new ResponseLoggingInterceptor(logger));
- }
-
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(Utils.ensureTrailingForwardSlash(baseUrl))
- .client(okHttpClientBuilder.build())
- .addConverterFactory(JacksonConverterFactory.create(OBJECT_MAPPER))
+ HttpClient httpClient = httpClientBuilder
+ .connectTimeout(builder.timeout)
+ .readTimeout(builder.timeout)
.build();
- this.jinaApi = retrofit.create(JinaApi.class);
- this.authorizationHeader = "Bearer " + ensureNotBlank(apiKey, "apiKey");
+ if (builder.logRequests || builder.logResponses) {
+ this.httpClient =
+ new LoggingHttpClient(httpClient, builder.logRequests, builder.logResponses, builder.logger);
+ } else {
+ this.httpClient = httpClient;
+ }
+
+ this.baseUrl = ensureTrailingForwardSlash(builder.baseUrl);
+ this.authorizationHeader = "Bearer " + ensureNotBlank(builder.apiKey, "apiKey");
}
public static JinaClientBuilder builder() {
@@ -55,54 +52,29 @@ public class JinaClient {
}
public JinaEmbeddingResponse embed(JinaEmbeddingRequest request) {
- try {
- retrofit2.Response retrofitResponse =
- jinaApi.embed(request, authorizationHeader).execute();
- if (retrofitResponse.isSuccessful()) {
- return retrofitResponse.body();
- } else {
- throw toException(retrofitResponse);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return post("v1/embeddings", request, JinaEmbeddingResponse.class);
}
- public JinaEmbeddingResponse embedMultimodal(
- dev.langchain4j.model.jina.internal.api.JinaMultimodalEmbeddingRequest request) {
- try {
- retrofit2.Response retrofitResponse =
- jinaApi.embedMultimodal(request, authorizationHeader).execute();
- if (retrofitResponse.isSuccessful()) {
- return retrofitResponse.body();
- } else {
- throw toException(retrofitResponse);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ public JinaEmbeddingResponse embedMultimodal(JinaMultimodalEmbeddingRequest request) {
+ return post("v1/embeddings", request, JinaEmbeddingResponse.class);
}
public JinaRerankingResponse rerank(JinaRerankingRequest request) {
- try {
- retrofit2.Response retrofitResponse =
- jinaApi.rerank(request, authorizationHeader).execute();
-
- if (retrofitResponse.isSuccessful()) {
- return retrofitResponse.body();
- } else {
- throw toException(retrofitResponse);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return post("rerank", request, JinaRerankingResponse.class);
}
- private static RuntimeException toException(retrofit2.Response> response) throws IOException {
- int code = response.code();
- String body = response.errorBody().string();
- String errorMessage = String.format("status code: %s; body: %s", code, body);
- return new RuntimeException(errorMessage);
+ private T post(String path, Object request, Class responseType) {
+ HttpRequest httpRequest = HttpRequest.builder()
+ .method(POST)
+ .url(baseUrl + path)
+ .addHeader("Content-Type", "application/json")
+ .addHeader("Authorization", authorizationHeader)
+ .body(toJson(request))
+ .build();
+
+ SuccessfulHttpResponse response = httpClient.execute(httpRequest);
+
+ return fromJson(response.body(), responseType);
}
public static class JinaClientBuilder {
@@ -112,6 +84,7 @@ public class JinaClient {
private boolean logRequests;
private boolean logResponses;
private Logger logger;
+ private HttpClientBuilder httpClientBuilder;
JinaClientBuilder() {}
@@ -145,9 +118,13 @@ public class JinaClient {
return this;
}
+ public JinaClientBuilder httpClientBuilder(HttpClientBuilder httpClientBuilder) {
+ this.httpClientBuilder = httpClientBuilder;
+ return this;
+ }
+
public JinaClient build() {
- return new JinaClient(
- this.baseUrl, this.apiKey, this.timeout, this.logRequests, this.logResponses, this.logger);
+ return new JinaClient(this);
}
public String toString() {
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaJsonUtils.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaJsonUtils.java
new file mode 100644
index 0000000000..d4fb5f2fa6
--- /dev/null
+++ b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/JinaJsonUtils.java
@@ -0,0 +1,31 @@
+package dev.langchain4j.model.jina.internal.client;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+class JinaJsonUtils {
+
+ private JinaJsonUtils() throws InstantiationException {
+ throw new InstantiationException("Can't instantiate this utility class.");
+ }
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(INDENT_OUTPUT);
+
+ static String toJson(Object object) {
+ try {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static T fromJson(String jsonStr, Class clazz) {
+ try {
+ return OBJECT_MAPPER.readValue(jsonStr, clazz);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/RequestLoggingInterceptor.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/RequestLoggingInterceptor.java
deleted file mode 100644
index d8d6eae26f..0000000000
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/RequestLoggingInterceptor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package dev.langchain4j.model.jina.internal.client;
-
-import okhttp3.Headers;
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import okio.Buffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-import static java.util.stream.StreamSupport.stream;
-
-class RequestLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
-
- private static final Pattern BEARER_PATTERN = Pattern.compile("(Bearer\\s)(\\w{2})(\\w+)(\\w{2})");
-
- private final Logger log;
-
- RequestLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- log(request);
- return chain.proceed(request);
- }
-
- private void log(Request request) {
- log.debug(
- "Request:\n" +
- "- method: {}\n" +
- "- url: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- request.method(),
- request.url(),
- inOneLine(request.headers()),
- getBody(request)
- );
- }
-
- static String inOneLine(Headers headers) {
- return stream(headers.spliterator(), false)
- .map((header) -> {
- String headerKey = header.component1();
- String headerValue = header.component2();
- if (headerKey.equals("Authorization")) {
- headerValue = maskAuthorizationHeaderValue(headerValue);
- }
- return String.format("[%s: %s]", headerKey, headerValue);
- }).collect(Collectors.joining(", "));
- }
-
- private static String maskAuthorizationHeaderValue(String authorizationHeaderValue) {
- try {
- Matcher matcher = BEARER_PATTERN.matcher(authorizationHeaderValue);
- StringBuffer sb = new StringBuffer();
-
- while (matcher.find()) {
- matcher.appendReplacement(sb, matcher.group(1) + matcher.group(2) + "..." + matcher.group(4));
- }
-
- matcher.appendTail(sb);
- return sb.toString();
- } catch (Exception e) {
- return "[failed to mask the API key]";
- }
- }
-
- private String getBody(Request request) {
- try {
- Buffer buffer = new Buffer();
- request.body().writeTo(buffer);
- return buffer.readUtf8();
- } catch (Exception e) {
- log.warn("Exception happened while reading request body", e);
- return "[Exception happened while reading request body. Check logs for more details.]";
- }
- }
-}
diff --git a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/ResponseLoggingInterceptor.java b/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/ResponseLoggingInterceptor.java
deleted file mode 100644
index 2288d3821a..0000000000
--- a/langchain4j-jina/src/main/java/dev/langchain4j/model/jina/internal/client/ResponseLoggingInterceptor.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package dev.langchain4j.model.jina.internal.client;
-
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-
-class ResponseLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(ResponseLoggingInterceptor.class);
-
- private final Logger log;
-
- ResponseLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- Response response = chain.proceed(request);
- log(response);
- return response;
- }
-
- void log(Response response) {
- log.debug(
- "Response:\n" +
- "- status code: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- response.code(),
- RequestLoggingInterceptor.inOneLine(response.headers()),
- getBody(response)
- );
- }
-
- private String getBody(Response response) {
- try {
- return response.peekBody(Long.MAX_VALUE).string();
- } catch (IOException e) {
- log.warn("Failed to log response", e);
- return "[failed to log response]";
- }
- }
-}
diff --git a/langchain4j-nomic/pom.xml b/langchain4j-nomic/pom.xml
index 5fa4df1a2a..d2687bdd24 100644
--- a/langchain4j-nomic/pom.xml
+++ b/langchain4j-nomic/pom.xml
@@ -20,18 +20,16 @@
- com.squareup.retrofit2
- retrofit
+ dev.langchain4j
+ langchain4j-http-client
+ 1.18.0-SNAPSHOT
- com.squareup.okhttp3
- okhttp
-
-
-
- com.squareup.retrofit2
- converter-jackson
+ dev.langchain4j
+ langchain4j-http-client-jdk
+ 1.18.0-SNAPSHOT
+ runtime
diff --git a/langchain4j-nomic/revapi.json b/langchain4j-nomic/revapi.json
new file mode 100644
index 0000000000..c3bc042f7b
--- /dev/null
+++ b/langchain4j-nomic/revapi.json
@@ -0,0 +1,15 @@
+[
+ {
+ "extension": "revapi.differences",
+ "configuration": {
+ "ignore": true,
+ "differences": [
+ {
+ "code": "java.class.externalClassExposedInAPI",
+ "new": "missing-class dev.langchain4j.http.client.HttpClientBuilder",
+ "justification": "HttpClientBuilder is intentionally exposed by the Nomic integration for custom HTTP client configuration"
+ }
+ ]
+ }
+ }
+]
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicApi.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicApi.java
deleted file mode 100644
index 1425cff11e..0000000000
--- a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicApi.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package dev.langchain4j.model.nomic;
-
-import retrofit2.Call;
-import retrofit2.http.Body;
-import retrofit2.http.Header;
-import retrofit2.http.Headers;
-import retrofit2.http.POST;
-
-interface NomicApi {
-
- @POST("embedding/text")
- @Headers({"Content-Type: application/json"})
- Call embed(@Body EmbeddingRequest request, @Header("Authorization") String authorizationHeader);
-}
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicClient.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicClient.java
index e26a57014b..844d663294 100644
--- a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicClient.java
+++ b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicClient.java
@@ -1,55 +1,46 @@
package dev.langchain4j.model.nomic;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import dev.langchain4j.internal.Utils;
-import okhttp3.OkHttpClient;
-import org.slf4j.Logger;
-import retrofit2.Retrofit;
-import retrofit2.converter.jackson.JacksonConverterFactory;
-
-import java.io.IOException;
-import java.time.Duration;
-
+import static dev.langchain4j.http.client.HttpMethod.POST;
+import static dev.langchain4j.internal.Utils.ensureTrailingForwardSlash;
+import static dev.langchain4j.internal.Utils.getOrDefault;
import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
+import static dev.langchain4j.model.nomic.NomicJsonUtils.fromJson;
+import static dev.langchain4j.model.nomic.NomicJsonUtils.toJson;
+
+import dev.langchain4j.http.client.HttpClient;
+import dev.langchain4j.http.client.HttpClientBuilder;
+import dev.langchain4j.http.client.HttpClientBuilderLoader;
+import dev.langchain4j.http.client.HttpRequest;
+import dev.langchain4j.http.client.SuccessfulHttpResponse;
+import dev.langchain4j.http.client.log.LoggingHttpClient;
+import java.time.Duration;
+import org.slf4j.Logger;
class NomicClient {
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
- .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
- .enable(SerializationFeature.INDENT_OUTPUT)
- .setSerializationInclusion(JsonInclude.Include.NON_NULL)
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-
- private final NomicApi nomicApi;
+ private final HttpClient httpClient;
+ private final String baseUrl;
private final String authorizationHeader;
- NomicClient(String baseUrl, String apiKey, Duration timeout, Boolean logRequests, Boolean logResponses, Logger logger) {
+ NomicClient(NomicClientBuilder builder) {
+ HttpClientBuilder httpClientBuilder =
+ getOrDefault(builder.httpClientBuilder, HttpClientBuilderLoader::loadHttpClientBuilder);
- OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
- .callTimeout(timeout)
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout);
-
- if (logRequests) {
- okHttpClientBuilder.addInterceptor(new RequestLoggingInterceptor(logger));
- }
- if (logResponses) {
- okHttpClientBuilder.addInterceptor(new ResponseLoggingInterceptor(logger));
- }
-
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(Utils.ensureTrailingForwardSlash(baseUrl))
- .client(okHttpClientBuilder.build())
- .addConverterFactory(JacksonConverterFactory.create(OBJECT_MAPPER))
+ HttpClient httpClient = httpClientBuilder
+ .connectTimeout(builder.timeout)
+ .readTimeout(builder.timeout)
.build();
- this.nomicApi = retrofit.create(NomicApi.class);
- this.authorizationHeader = "Bearer " + ensureNotBlank(apiKey, "apiKey");
+ if (builder.logRequests != null && builder.logRequests
+ || builder.logResponses != null && builder.logResponses) {
+ this.httpClient =
+ new LoggingHttpClient(httpClient, builder.logRequests, builder.logResponses, builder.logger);
+ } else {
+ this.httpClient = httpClient;
+ }
+
+ this.baseUrl = ensureTrailingForwardSlash(builder.baseUrl);
+ this.authorizationHeader = "Bearer " + ensureNotBlank(builder.apiKey, "apiKey");
}
public static NomicClientBuilder builder() {
@@ -57,25 +48,17 @@ class NomicClient {
}
public EmbeddingResponse embed(EmbeddingRequest request) {
- try {
- retrofit2.Response retrofitResponse
- = nomicApi.embed(request, authorizationHeader).execute();
+ HttpRequest httpRequest = HttpRequest.builder()
+ .method(POST)
+ .url(baseUrl + "embedding/text")
+ .addHeader("Content-Type", "application/json")
+ .addHeader("Authorization", authorizationHeader)
+ .body(toJson(request))
+ .build();
- if (retrofitResponse.isSuccessful()) {
- return retrofitResponse.body();
- } else {
- throw toException(retrofitResponse);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ SuccessfulHttpResponse response = httpClient.execute(httpRequest);
- private static RuntimeException toException(retrofit2.Response> response) throws IOException {
- int code = response.code();
- String body = response.errorBody().string();
- String errorMessage = String.format("status code: %s; body: %s", code, body);
- return new RuntimeException(errorMessage);
+ return fromJson(response.body(), EmbeddingResponse.class);
}
public static class NomicClientBuilder {
@@ -85,6 +68,7 @@ class NomicClient {
private Boolean logRequests;
private Boolean logResponses;
private Logger logger;
+ private HttpClientBuilder httpClientBuilder;
NomicClientBuilder() {
}
@@ -119,8 +103,13 @@ class NomicClient {
return this;
}
+ public NomicClientBuilder httpClientBuilder(HttpClientBuilder httpClientBuilder) {
+ this.httpClientBuilder = httpClientBuilder;
+ return this;
+ }
+
public NomicClient build() {
- return new NomicClient(this.baseUrl, this.apiKey, this.timeout, this.logRequests, this.logResponses, this.logger);
+ return new NomicClient(this);
}
public String toString() {
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicEmbeddingModel.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicEmbeddingModel.java
index 427177ff7d..002f35c969 100644
--- a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicEmbeddingModel.java
+++ b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicEmbeddingModel.java
@@ -8,6 +8,7 @@ import static java.util.stream.Collectors.toList;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.http.client.HttpClientBuilder;
import dev.langchain4j.model.embedding.DimensionAwareEmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.model.output.TokenUsage;
@@ -56,11 +57,13 @@ public class NomicEmbeddingModel extends DimensionAwareEmbeddingModel {
public NomicEmbeddingModel(NomicEmbeddingModelBuilder builder) {
this.client = NomicClient.builder()
+ .httpClientBuilder(builder.httpClientBuilder)
.baseUrl(getOrDefault(builder.baseUrl, DEFAULT_BASE_URL))
.apiKey(ensureNotBlank(builder.apiKey, "apiKey"))
.timeout(getOrDefault(builder.timeout, ofSeconds(60)))
.logRequests(getOrDefault(builder.logRequests, false))
.logResponses(getOrDefault(builder.logResponses, false))
+ .logger(builder.logger)
.build();
this.modelName = ensureNotBlank(builder.modelName, "modelName");
this.taskType = builder.taskType;
@@ -130,6 +133,7 @@ public class NomicEmbeddingModel extends DimensionAwareEmbeddingModel {
private Boolean logRequests;
private Boolean logResponses;
private Logger logger;
+ private HttpClientBuilder httpClientBuilder;
NomicEmbeddingModelBuilder() {}
@@ -187,6 +191,18 @@ public class NomicEmbeddingModel extends DimensionAwareEmbeddingModel {
return this;
}
+ /**
+ * Sets a custom HTTP client builder, allowing fine-grained control over the HTTP client
+ * configuration such as timeouts and proxy settings.
+ *
+ * @param httpClientBuilder the HTTP client builder
+ * @return {@code this}
+ */
+ public NomicEmbeddingModelBuilder httpClientBuilder(HttpClientBuilder httpClientBuilder) {
+ this.httpClientBuilder = httpClientBuilder;
+ return this;
+ }
+
public NomicEmbeddingModel build() {
return new NomicEmbeddingModel(this);
}
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicJsonUtils.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicJsonUtils.java
new file mode 100644
index 0000000000..2f98323a1f
--- /dev/null
+++ b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/NomicJsonUtils.java
@@ -0,0 +1,37 @@
+package dev.langchain4j.model.nomic;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+class NomicJsonUtils {
+
+ private NomicJsonUtils() throws InstantiationException {
+ throw new InstantiationException("Can't instantiate this utility class.");
+ }
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ static String toJson(Object object) {
+ try {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static T fromJson(String jsonStr, Class clazz) {
+ try {
+ return OBJECT_MAPPER.readValue(jsonStr, clazz);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/RequestLoggingInterceptor.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/RequestLoggingInterceptor.java
deleted file mode 100644
index bf4d3d325d..0000000000
--- a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/RequestLoggingInterceptor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package dev.langchain4j.model.nomic;
-
-import okhttp3.Headers;
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import okio.Buffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-import static java.util.stream.StreamSupport.stream;
-
-class RequestLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
-
- private static final Pattern BEARER_PATTERN = Pattern.compile("(Bearer\\s*)([\\w-]{5})([\\w-]+)([\\w-]{2})");
-
- private final Logger log;
-
- RequestLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- log(request);
- return chain.proceed(request);
- }
-
- private void log(Request request) {
- log.debug(
- "Request:\n" +
- "- method: {}\n" +
- "- url: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- request.method(),
- request.url(),
- inOneLine(request.headers()),
- getBody(request)
- );
- }
-
- static String inOneLine(Headers headers) {
- return stream(headers.spliterator(), false)
- .map((header) -> {
- String headerKey = header.component1();
- String headerValue = header.component2();
- if (headerKey.equals("Authorization")) {
- headerValue = maskAuthorizationHeaderValue(headerValue);
- }
- return String.format("[%s: %s]", headerKey, headerValue);
- }).collect(Collectors.joining(", "));
- }
-
- private static String maskAuthorizationHeaderValue(String authorizationHeaderValue) {
- try {
- Matcher matcher = BEARER_PATTERN.matcher(authorizationHeaderValue);
- StringBuffer sb = new StringBuffer();
-
- while (matcher.find()) {
- matcher.appendReplacement(sb, matcher.group(1) + matcher.group(2) + "..." + matcher.group(4));
- }
-
- matcher.appendTail(sb);
- return sb.toString();
- } catch (Exception e) {
- return "[failed to mask the API key]";
- }
- }
-
- private String getBody(Request request) {
- try {
- Buffer buffer = new Buffer();
- request.body().writeTo(buffer);
- return buffer.readUtf8();
- } catch (Exception e) {
- log.warn("Exception happened while reading request body", e);
- return "[Exception happened while reading request body. Check logs for more details.]";
- }
- }
-}
diff --git a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/ResponseLoggingInterceptor.java b/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/ResponseLoggingInterceptor.java
deleted file mode 100644
index b182befcee..0000000000
--- a/langchain4j-nomic/src/main/java/dev/langchain4j/model/nomic/ResponseLoggingInterceptor.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package dev.langchain4j.model.nomic;
-
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-import static dev.langchain4j.model.nomic.RequestLoggingInterceptor.inOneLine;
-
-class ResponseLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(ResponseLoggingInterceptor.class);
-
- private final Logger log;
-
- ResponseLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- Response response = chain.proceed(request);
- log(response);
- return response;
- }
-
- void log(Response response) {
- log.debug(
- "Response:\n" +
- "- status code: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- response.code(),
- inOneLine(response.headers()),
- getBody(response)
- );
- }
-
- private String getBody(Response response) {
- try {
- return response.peekBody(Long.MAX_VALUE).string();
- } catch (IOException e) {
- log.warn("Failed to log response", e);
- return "[failed to log response]";
- }
- }
-}
diff --git a/langchain4j-ovh-ai/pom.xml b/langchain4j-ovh-ai/pom.xml
index a1544fa12f..49e54fcc6b 100644
--- a/langchain4j-ovh-ai/pom.xml
+++ b/langchain4j-ovh-ai/pom.xml
@@ -25,13 +25,16 @@
- com.squareup.retrofit2
- retrofit
+ dev.langchain4j
+ langchain4j-http-client
+ 1.18.0-SNAPSHOT
- com.squareup.retrofit2
- converter-jackson
+ dev.langchain4j
+ langchain4j-http-client-jdk
+ 1.18.0-SNAPSHOT
+ runtime
diff --git a/langchain4j-ovh-ai/revapi.json b/langchain4j-ovh-ai/revapi.json
new file mode 100644
index 0000000000..2fdce93bce
--- /dev/null
+++ b/langchain4j-ovh-ai/revapi.json
@@ -0,0 +1,15 @@
+[
+ {
+ "extension": "revapi.differences",
+ "configuration": {
+ "ignore": true,
+ "differences": [
+ {
+ "code": "java.class.removed",
+ "old": "interface dev.langchain4j.model.ovhai.internal.api.OvhAiApi",
+ "justification": "Internal Retrofit API interface (internal.api package) removed as part of migrating the HTTP transport to the langchain4j HttpClient abstraction; not part of the public API"
+ }
+ ]
+ }
+ }
+]
diff --git a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/api/OvhAiApi.java b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/api/OvhAiApi.java
deleted file mode 100644
index 0328de1764..0000000000
--- a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/api/OvhAiApi.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package dev.langchain4j.model.ovhai.internal.api;
-
-import retrofit2.Call;
-import retrofit2.http.Body;
-import retrofit2.http.Header;
-import retrofit2.http.Headers;
-import retrofit2.http.POST;
-
-import java.util.List;
-
-/**
- * @deprecated Do not use anymore, use {@code langchain4j-open-ai} module instead
- */
-@Deprecated(forRemoval = true, since = "1.14.0")
-public interface OvhAiApi {
-
- @POST("api/batch_text2vec")
- @Headers({"Content-Type: application/json"})
- Call> embed(@Body EmbeddingRequest request, @Header("Authorization") String authorizationHeader);
-}
diff --git a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/DefaultOvhAiClient.java b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/DefaultOvhAiClient.java
index 583de8fee4..53801e0053 100644
--- a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/DefaultOvhAiClient.java
+++ b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/DefaultOvhAiClient.java
@@ -1,19 +1,20 @@
package dev.langchain4j.model.ovhai.internal.client;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import dev.langchain4j.internal.Utils;
+import static dev.langchain4j.http.client.HttpMethod.POST;
+import static dev.langchain4j.internal.Utils.ensureTrailingForwardSlash;
+import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
+import static dev.langchain4j.model.ovhai.internal.client.OvhAiJsonUtils.fromJson;
+import static dev.langchain4j.model.ovhai.internal.client.OvhAiJsonUtils.toJson;
+
+import dev.langchain4j.http.client.HttpClient;
+import dev.langchain4j.http.client.HttpClientBuilder;
+import dev.langchain4j.http.client.HttpClientBuilderLoader;
+import dev.langchain4j.http.client.HttpRequest;
+import dev.langchain4j.http.client.SuccessfulHttpResponse;
+import dev.langchain4j.http.client.log.LoggingHttpClient;
import dev.langchain4j.model.ovhai.internal.api.EmbeddingRequest;
import dev.langchain4j.model.ovhai.internal.api.EmbeddingResponse;
-import dev.langchain4j.model.ovhai.internal.api.OvhAiApi;
-import okhttp3.OkHttpClient;
-import retrofit2.Retrofit;
-import retrofit2.converter.jackson.JacksonConverterFactory;
-
-import java.io.IOException;
-import java.util.List;
-
-import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
-import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
+import java.util.Arrays;
/**
* @deprecated Do not use anymore, use {@code langchain4j-open-ai} module instead
@@ -21,13 +22,8 @@ import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
@Deprecated(forRemoval = true, since = "1.14.0")
public class DefaultOvhAiClient extends OvhAiClient {
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(INDENT_OUTPUT);
-
- private final OkHttpClient okHttpClient;
-
- private final String apiKey;
- private final boolean logResponses;
- private final OvhAiApi ovhAiApi;
+ private final HttpClient httpClient;
+ private final String baseUrl;
private final String authorizationHeader;
public static Builder builder() {
@@ -44,52 +40,36 @@ public class DefaultOvhAiClient extends OvhAiClient {
DefaultOvhAiClient(Builder builder) {
ensureNotBlank(builder.apiKey, "%s", "OVHcloud API key must be defined. It can be generated here: https://endpoints.ai.cloud.ovh.net/");
- this.apiKey = builder.apiKey;
- this.logResponses = builder.logResponses;
+ HttpClientBuilder httpClientBuilder = HttpClientBuilderLoader.loadHttpClientBuilder();
+ HttpClient httpClient = httpClientBuilder
+ .connectTimeout(builder.timeout)
+ .readTimeout(builder.timeout)
+ .build();
- OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
- .callTimeout(builder.timeout)
- .connectTimeout(builder.timeout)
- .readTimeout(builder.timeout)
- .writeTimeout(builder.timeout);
-
- if (builder.logRequests) {
- okHttpClientBuilder.addInterceptor(new RequestLoggingInterceptor(builder.logger));
- }
- if (logResponses) {
- okHttpClientBuilder.addInterceptor(new ResponseLoggingInterceptor(builder.logger));
+ if (builder.logRequests != null && builder.logRequests
+ || builder.logResponses != null && builder.logResponses) {
+ this.httpClient =
+ new LoggingHttpClient(httpClient, builder.logRequests, builder.logResponses, builder.logger);
+ } else {
+ this.httpClient = httpClient;
}
- this.okHttpClient = okHttpClientBuilder.build();
-
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(Utils.ensureTrailingForwardSlash(ensureNotBlank(builder.baseUrl, "baseUrl")))
- .client(okHttpClient)
- .addConverterFactory(JacksonConverterFactory.create(OBJECT_MAPPER))
- .build();
-
- this.ovhAiApi = retrofit.create(OvhAiApi.class);
- this.authorizationHeader = "Bearer " + ensureNotBlank(apiKey, "apiKey");
+ this.baseUrl = ensureTrailingForwardSlash(ensureNotBlank(builder.baseUrl, "baseUrl"));
+ this.authorizationHeader = "Bearer " + ensureNotBlank(builder.apiKey, "apiKey");
}
public EmbeddingResponse embed(EmbeddingRequest request) {
- try {
- retrofit2.Response> retrofitResponse = ovhAiApi.embed(request, authorizationHeader).execute();
+ HttpRequest httpRequest = HttpRequest.builder()
+ .method(POST)
+ .url(baseUrl + "api/batch_text2vec")
+ .addHeader("Content-Type", "application/json")
+ .addHeader("Authorization", authorizationHeader)
+ .body(toJson(request))
+ .build();
- if (retrofitResponse.isSuccessful()) {
- return new EmbeddingResponse(retrofitResponse.body());
- } else {
- throw toException(retrofitResponse);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
+ SuccessfulHttpResponse response = httpClient.execute(httpRequest);
- private static RuntimeException toException(retrofit2.Response> response) throws IOException {
- int code = response.code();
- String body = response.errorBody().string();
- String errorMessage = String.format("status code: %s; body: %s", code, body);
- return new RuntimeException(errorMessage);
+ float[][] embeddings = fromJson(response.body(), float[][].class);
+ return new EmbeddingResponse(Arrays.asList(embeddings));
}
}
diff --git a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/OvhAiJsonUtils.java b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/OvhAiJsonUtils.java
new file mode 100644
index 0000000000..61f566488c
--- /dev/null
+++ b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/OvhAiJsonUtils.java
@@ -0,0 +1,31 @@
+package dev.langchain4j.model.ovhai.internal.client;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+class OvhAiJsonUtils {
+
+ private OvhAiJsonUtils() throws InstantiationException {
+ throw new InstantiationException("Can't instantiate this utility class.");
+ }
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().enable(INDENT_OUTPUT);
+
+ static String toJson(Object object) {
+ try {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static T fromJson(String jsonStr, Class clazz) {
+ try {
+ return OBJECT_MAPPER.readValue(jsonStr, clazz);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/RequestLoggingInterceptor.java b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/RequestLoggingInterceptor.java
deleted file mode 100644
index 9558374dc7..0000000000
--- a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/RequestLoggingInterceptor.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package dev.langchain4j.model.ovhai.internal.client;
-
-import okhttp3.Headers;
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import okio.Buffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-import static java.util.stream.StreamSupport.stream;
-
-/**
- * @deprecated Do not use anymore, use {@code langchain4j-open-ai} module instead
- */
-@Deprecated(forRemoval = true, since = "1.14.0")
-class RequestLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(RequestLoggingInterceptor.class);
-
- private static final Pattern BEARER_PATTERN = Pattern.compile("(Bearer\\s*)([\\w-]{5})([\\w-]+)([\\w-]{2})");
-
- private final Logger log;
-
- RequestLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- log(request);
- return chain.proceed(request);
- }
-
- private void log(Request request) {
- log.debug(
- "Request:\n" +
- "- method: {}\n" +
- "- url: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- request.method(),
- request.url(),
- inOneLine(request.headers()),
- getBody(request)
- );
- }
-
- static String inOneLine(Headers headers) {
- return stream(headers.spliterator(), false)
- .map((header) -> {
- String headerKey = header.component1();
- String headerValue = header.component2();
- if (headerKey.equals("Authorization")) {
- headerValue = maskAuthorizationHeaderValue(headerValue);
- }
- return String.format("[%s: %s]", headerKey, headerValue);
- }).collect(Collectors.joining(", "));
- }
-
- private static String maskAuthorizationHeaderValue(String authorizationHeaderValue) {
- try {
- Matcher matcher = BEARER_PATTERN.matcher(authorizationHeaderValue);
- StringBuffer sb = new StringBuffer();
-
- while (matcher.find()) {
- matcher.appendReplacement(sb, matcher.group(1) + matcher.group(2) + "..." + matcher.group(4));
- }
-
- matcher.appendTail(sb);
- return sb.toString();
- } catch (Exception e) {
- return "[failed to mask the API key]";
- }
- }
-
- private String getBody(Request request) {
- try {
- Buffer buffer = new Buffer();
- request.body().writeTo(buffer);
- return buffer.readUtf8();
- } catch (Exception e) {
- log.warn("Exception happened while reading request body", e);
- return "[Exception happened while reading request body. Check logs for more details.]";
- }
- }
-}
diff --git a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/ResponseLoggingInterceptor.java b/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/ResponseLoggingInterceptor.java
deleted file mode 100644
index 595cb30e83..0000000000
--- a/langchain4j-ovh-ai/src/main/java/dev/langchain4j/model/ovhai/internal/client/ResponseLoggingInterceptor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package dev.langchain4j.model.ovhai.internal.client;
-
-import okhttp3.Interceptor;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static dev.langchain4j.internal.Utils.getOrDefault;
-import static dev.langchain4j.model.ovhai.internal.client.RequestLoggingInterceptor.inOneLine;
-import java.io.IOException;
-
-/**
- * @deprecated Do not use anymore, use {@code langchain4j-open-ai} module instead
- */
-@Deprecated(forRemoval = true, since = "1.14.0")
-class ResponseLoggingInterceptor implements Interceptor {
-
- private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(ResponseLoggingInterceptor.class);
-
- private final Logger log;
-
- ResponseLoggingInterceptor(Logger logger) {
- this.log = getOrDefault(logger, DEFAULT_LOG);
- }
-
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Request request = chain.request();
- Response response = chain.proceed(request);
- log(response);
- return response;
- }
-
- void log(Response response) {
- log.debug(
- "Response:\n" +
- "- status code: {}\n" +
- "- headers: {}\n" +
- "- body: {}",
- response.code(),
- inOneLine(response.headers()),
- getBody(response)
- );
- }
-
- private String getBody(Response response) {
- try {
- return response.peekBody(Long.MAX_VALUE).string();
- } catch (IOException e) {
- log.warn("Failed to log response", e);
- return "[failed to log response]";
- }
- }
-}