From cb7c861020a1524d69f446bff916534ab17e203c Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Sun, 5 Apr 2026 21:09:43 +0200 Subject: [PATCH 1/6] Convert EmbeddingRequest to builder class and add the `embeddingAsync` method Changes: - Converted EmbeddingRequest from record to builder class for extensibility - Created EmbeddingPayload record for internal API requests - Added `embeddingAsync` method - Extracted Parameters and ReturnOptions to separate classes --- .../com/ibm/watsonx/ai/chat/ChatRequest.java | 6 +- .../ai/embedding/DefaultRestClient.java | 29 ++++- .../ai/embedding/EmbeddingParameters.java | 3 +- .../ai/embedding/EmbeddingPayload.java | 23 ++++ .../ai/embedding/EmbeddingRequest.java | 118 +++++++++++++++--- .../ai/embedding/EmbeddingRestClient.java | 14 ++- .../ai/embedding/EmbeddingService.java | 29 +++-- .../ibm/watsonx/ai/embedding/Parameters.java | 21 ++++ .../ai/timeseries/TimeSeriesRequest.java | 23 +++- .../ibm/watsonx/ai/CustomHttpClientTest.java | 9 ++ .../impl/CustomEmbeddingRestClient.java | 10 +- 11 files changed, 248 insertions(+), 37 deletions(-) create mode 100644 modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingPayload.java create mode 100644 modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/Parameters.java diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/chat/ChatRequest.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/chat/ChatRequest.java index cdfedd23..c4443de6 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/chat/ChatRequest.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/chat/ChatRequest.java @@ -8,8 +8,8 @@ import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; +import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import com.ibm.watsonx.ai.chat.model.ChatMessage; import com.ibm.watsonx.ai.chat.model.ChatParameters; @@ -208,7 +208,7 @@ public Builder messages(ChatMessage... messages) { */ public Builder messages(List messages) { if (nonNull(messages)) - this.messages = new LinkedList<>(messages); + this.messages = new ArrayList<>(messages); return this; } @@ -236,7 +236,7 @@ public Builder addMessages(List messages) { if (isNull(messages) || messages.isEmpty()) return this; - this.messages = requireNonNullElse(this.messages, new LinkedList<>()); + this.messages = requireNonNullElse(this.messages, new ArrayList<>()); this.messages.addAll(messages); return this; } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/DefaultRestClient.java index bd67241a..2e55f44d 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/DefaultRestClient.java @@ -13,9 +13,14 @@ import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse.BodyHandlers; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import com.ibm.watsonx.ai.core.Json; import com.ibm.watsonx.ai.core.factory.HttpClientFactory; +import com.ibm.watsonx.ai.core.http.AsyncHttpClient; import com.ibm.watsonx.ai.core.http.SyncHttpClient; import com.ibm.watsonx.ai.core.http.interceptors.LoggerInterceptor.LogMode; +import com.ibm.watsonx.ai.core.provider.ExecutorProvider; /** * Default implementation of the {@link EmbeddingRestClient} abstract class. @@ -23,21 +28,23 @@ final class DefaultRestClient extends EmbeddingRestClient { private final SyncHttpClient syncHttpClient; + private final AsyncHttpClient asyncHttpClient; DefaultRestClient(Builder builder) { super(builder); requireNonNull(authenticator, "authenticator is mandatory"); syncHttpClient = HttpClientFactory.createSync(authenticator, httpClient, LogMode.of(logRequests, logResponses)); + asyncHttpClient = HttpClientFactory.createAsync(authenticator, httpClient, LogMode.of(logRequests, logResponses)); } @Override - public EmbeddingResponse embedding(String transactionId, EmbeddingRequest embeddingRequest) { + public EmbeddingResponse embedding(String transactionId, EmbeddingPayload embeddingPayload) { var httpRequest = HttpRequest .newBuilder(URI.create(baseUrl + "/ml/v1/text/embeddings?version=%s".formatted(version))) .header("Content-Type", "application/json") .header("Accept", "application/json") - .POST(BodyPublishers.ofString(toJson(embeddingRequest))) + .POST(BodyPublishers.ofString(toJson(embeddingPayload))) .timeout(timeout); if (nonNull(transactionId)) @@ -53,6 +60,24 @@ public EmbeddingResponse embedding(String transactionId, EmbeddingRequest embedd } } + @Override + public CompletableFuture embeddingAsync(String transactionId, EmbeddingPayload embeddingPayload) { + + var httpRequest = HttpRequest + .newBuilder(URI.create(baseUrl + "/ml/v1/text/embeddings?version=%s".formatted(version))) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .POST(BodyPublishers.ofString(toJson(embeddingPayload))) + .timeout(timeout); + + if (nonNull(transactionId)) + httpRequest.header(TRANSACTION_ID_HEADER, transactionId); + + return asyncHttpClient.send(httpRequest.build(), BodyHandlers.ofString()) + .thenApplyAsync(r -> Json.fromJson(r.body(), EmbeddingResponse.class), ExecutorProvider.cpuExecutor()) + .thenApplyAsync(Function.identity(), ExecutorProvider.ioExecutor()); + } + /** * Returns a new {@link Builder} instance. */ diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingParameters.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingParameters.java index d6875d2c..9bd2c6d7 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingParameters.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingParameters.java @@ -6,8 +6,7 @@ import static java.util.Objects.nonNull; import com.ibm.watsonx.ai.WatsonxParameters.WatsonxCryptoParameters; -import com.ibm.watsonx.ai.embedding.EmbeddingRequest.Parameters; -import com.ibm.watsonx.ai.embedding.EmbeddingRequest.ReturnOptions; +import com.ibm.watsonx.ai.embedding.Parameters.ReturnOptions; /** * Represents a set of parameters used to control the behavior of embedding generation. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingPayload.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingPayload.java new file mode 100644 index 00000000..31115e38 --- /dev/null +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingPayload.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025 IBM Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.watsonx.ai.embedding; + +import java.util.List; +import com.ibm.watsonx.ai.Crypto; + +/** + * Represents a request to generate embeddings from a given model. + * + * @param modelId the model identifier + * @param spaceId the space identifier + * @param projectId the project identifier + * @param inputs the list of input texts to embed + * @param parameters the embedding parameters + * @param crypto the crypto configuration for encryption + */ +public record EmbeddingPayload(String modelId, String spaceId, String projectId, + List inputs, Parameters parameters, Crypto crypto) {} + diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java index c61577a9..5df137e9 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java @@ -4,34 +4,118 @@ */ package com.ibm.watsonx.ai.embedding; +import static java.util.Objects.requireNonNullElse; +import java.util.ArrayList; import java.util.List; -import com.ibm.watsonx.ai.Crypto; /** - * Represents a request to generate embeddings from a given model. + * Represents an embedding request. + *

+ * Example usage: * - * @param modelId the model identifier - * @param spaceId the space identifier - * @param projectId the project identifier - * @param inputs the list of input texts to embed - * @param parameters the embedding parameters - * @param crypto the crypto configuration for encryption + *

{@code
+ * var parameters = EmbeddingParameters.builder()
+ *     .truncateInputTokens(512)
+ *     .returnOptions(ReturnOptions.builder().inputText(true).build())
+ *     .build();
+ *
+ * EmbeddingRequest request = EmbeddingRequest.builder()
+ *     .inputs("What is watsonx.ai?")
+ *     .parameters(parameters)
+ *     .build();
+ * }
*/ -public record EmbeddingRequest(String modelId, String spaceId, String projectId, - List inputs, Parameters parameters, Crypto crypto) { +public final class EmbeddingRequest { + private List inputs; + private EmbeddingParameters parameters; + + private EmbeddingRequest(Builder builder) { + inputs = builder.inputs; + parameters = builder.parameters; + } + + /** + * Returns the input texts to be converted into embeddings. + * + * @return the list of input texts, or {@code null} if not set + */ + public List inputs() { + return inputs; + } /** - * Parameters for embedding generation. + * Returns the embedding parameters. * - * @param truncateInputTokens the maximum number of tokens accepted per input - * @param returnOptions the return options + * @return the embedding parameters, or {@code null} if not set */ - public record Parameters(Integer truncateInputTokens, ReturnOptions returnOptions) {} + public EmbeddingParameters parameters() { + return parameters; + } /** - * Return options for embedding generation. + * Returns a new {@link Builder} instance. + *

+ * Example usage: + * + *

{@code
+     * var parameters = EmbeddingParameters.builder()
+     *     .truncateInputTokens(512)
+     *     .returnOptions(ReturnOptions.builder().inputText(true).build())
+     *     .build();
+     *
+     * EmbeddingRequest request = EmbeddingRequest.builder()
+     *     .inputs("What is watsonx.ai?")
+     *     .parameters(parameters)
+     *     .build();
+     * }
* - * @param inputText whether to include the input text in each result document + * @return {@link Builder} instance */ - public record ReturnOptions(boolean inputText) {} + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for constructing {@link EmbeddingRequest} instances. + */ + public final static class Builder { + private List inputs; + private EmbeddingParameters parameters; + + private Builder() {} + + /** + * Sets the input texts for the request, replacing any existing inputs. + *

+ * This method completely overwrites the current list of inputs with the provided values. + *

+ * Use {@link #addInputs(String...)} or {@link #addInputs(List)} to append inputs instead. + * + * @param inputs the list of input texts to embed + */ + public Builder inputs(List inputs) { + this.inputs = requireNonNullElse(this.inputs, new ArrayList<>()); + this.inputs.addAll(inputs); + return this; + } + + /** + * Sets the parameters controlling the embedding model behavior. + * + * @param parameters an {@link EmbeddingParameters} instance + */ + public Builder parameters(EmbeddingParameters parameters) { + this.parameters = parameters; + return this; + } + + /** + * Builds a {@link EmbeddingRequest} instance using the configured parameters. + * + * @return a new instance of {@link EmbeddingRequest} + */ + public EmbeddingRequest build() { + return new EmbeddingRequest(this); + } + } } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRestClient.java index 4a31cd3c..020c94c0 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRestClient.java @@ -5,6 +5,7 @@ package com.ibm.watsonx.ai.embedding; import java.util.ServiceLoader; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import com.ibm.watsonx.ai.WatsonxRestClient; @@ -21,10 +22,19 @@ protected EmbeddingRestClient(Builder builder) { * Executes an embedding request against the watsonx.ai API. * * @param transactionId an optional client-provided transaction identifier used for tracing - * @param embeddingRequest the structured embedding request payload + * @param embeddingPayload the structured embedding request payload * @return An {@link EmbeddingResponse} containing the embedding vectors and metadata */ - public abstract EmbeddingResponse embedding(String transactionId, EmbeddingRequest embeddingRequest); + public abstract EmbeddingResponse embedding(String transactionId, EmbeddingPayload embeddingPayload); + + /** + * Executes an embedding request against the watsonx.ai API. + * + * @param transactionId an optional client-provided transaction identifier used for tracing + * @param embeddingPayload the structured embedding request payload + * @return An {@link EmbeddingResponse} containing the embedding vectors and metadata + */ + public abstract CompletableFuture embeddingAsync(String transactionId, EmbeddingPayload embeddingPayload); /** * Creates a new {@link Builder} using the first available {@link EmbeddingRestClientBuilderFactory} discovered via {@link ServiceLoader}. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingService.java index 8f178ea2..8102a6a5 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingService.java @@ -8,7 +8,6 @@ import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.supplyAsync; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -16,8 +15,6 @@ import com.ibm.watsonx.ai.Crypto; import com.ibm.watsonx.ai.WatsonxService.ModelService; import com.ibm.watsonx.ai.core.auth.Authenticator; -import com.ibm.watsonx.ai.core.provider.ExecutorProvider; -import com.ibm.watsonx.ai.embedding.EmbeddingRequest.Parameters; /** * Service class to interact with IBM watsonx.ai Text Embeddings APIs. @@ -89,9 +86,27 @@ public EmbeddingResponse embedding(List inputs) { * @return An EmbeddingResponse object containing the embedding results. */ public EmbeddingResponse embedding(List inputs, EmbeddingParameters parameters) { + return embedding( + EmbeddingRequest.builder() + .inputs(inputs) + .parameters(parameters) + .build() + ); + } + + /** + * Embeds the provided request into a vector space and returns the embedding results. + * + * @param request The request to be embedded. + * @return An EmbeddingResponse object containing the embedding results. + */ + public EmbeddingResponse embedding(EmbeddingRequest request) { - requireNonNull(inputs, "Inputs cannot be null"); + requireNonNull(request, "Request cannot be null"); + requireNonNull(request.inputs(), "Inputs cannot be null"); + EmbeddingParameters parameters = request.parameters(); + List inputs = request.inputs(); ProjectSpace projectSpace = resolveProjectSpace(parameters); final String projectId = projectSpace.projectId(); final String spaceId = projectSpace.spaceId(); @@ -101,7 +116,7 @@ public EmbeddingResponse embedding(List inputs, EmbeddingParameters para final Parameters requestParameters = nonNull(parameters) ? parameters.toEmbeddingRequestParameters() : null; if (inputs.size() <= MAX_SIZE) { - var embeddingRequest = new EmbeddingRequest(modelId, spaceId, projectId, inputs, requestParameters, crypto); + var embeddingRequest = new EmbeddingPayload(modelId, spaceId, projectId, inputs, requestParameters, crypto); return client.embedding(transactionId, embeddingRequest); } @@ -112,8 +127,8 @@ public EmbeddingResponse embedding(List inputs, EmbeddingParameters para for (int fromIndex = 0; fromIndex < inputs.size(); fromIndex += MAX_SIZE) { var toIndex = Math.min(fromIndex + MAX_SIZE, inputs.size()); var subList = inputs.subList(fromIndex, toIndex); - var embeddingRequest = new EmbeddingRequest(modelId, spaceId, projectId, subList, requestParameters, crypto); - futures.add(supplyAsync(() -> client.embedding(transactionId, embeddingRequest), ExecutorProvider.callbackExecutor())); + var embeddingRequest = new EmbeddingPayload(modelId, spaceId, projectId, subList, requestParameters, crypto); + futures.add(client.embeddingAsync(transactionId, embeddingRequest)); } allOf(futures.toArray(new CompletableFuture[0])).join(); diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/Parameters.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/Parameters.java new file mode 100644 index 00000000..2f3611fc --- /dev/null +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/Parameters.java @@ -0,0 +1,21 @@ +/* + * Copyright 2025 IBM Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.watsonx.ai.embedding; + +/** + * Parameters for embedding generation. + * + * @param truncateInputTokens the maximum number of tokens accepted per input + * @param returnOptions the return options + */ +public record Parameters(Integer truncateInputTokens, ReturnOptions returnOptions) { + + /** + * Return options for embedding generation. + * + * @param inputText whether to include the input text in each result document + */ + public record ReturnOptions(boolean inputText) {} +} \ No newline at end of file diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/timeseries/TimeSeriesRequest.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/timeseries/TimeSeriesRequest.java index 2c667c65..df00ba0a 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/timeseries/TimeSeriesRequest.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/timeseries/TimeSeriesRequest.java @@ -10,8 +10,6 @@ /** * Represents a time series forecast request. *

- * Instances are created using the {@link Builder} pattern: - *

* Example usage: * *

{@code
@@ -84,6 +82,27 @@ public TimeSeriesParameters parameters() {
 
     /**
      * Returns a new {@link Builder} instance.
+     * 

+ * Example usage: + * + *

{@code
+     * var inputSchema = InputSchema.builder()
+     *     .timestampColumn("date")
+     *     .addIdColumn("ID1")
+     *     .build();
+     *
+     * var data = ForecastData.create()
+     *     .add("date", "2020-01-01T00:00:00")
+     *     .add("date", "2020-01-01T01:00:00")
+     *     .add("date", "2020-01-05T01:00:00")
+     *     .add("ID1", "D1", 3)
+     *     .addAll("TARGET1", 1.46, 2.34, 4.55);
+     *
+     * TimeSeriesRequest request = TimeSeriesRequest.builder()
+     *     .inputSchema(inputSchema)
+     *     .data(data)
+     *     .build();
+     * }
* * @return {@link Builder} instance */ diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java index be53803a..d42a5dd4 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java @@ -222,6 +222,11 @@ void should_use_custom_http_client_for_embedding_service() throws Exception { assertEquals(customClient, getFieldValue(syncHttpClient, "delegate")); assertNotEquals(HttpClientProvider.httpClient(true), getFieldValue(syncHttpClient, "delegate")); assertNotEquals(HttpClientProvider.httpClient(false), getFieldValue(syncHttpClient, "delegate")); + + Object asyncCosHttpClient = getFieldValue(restclient, "asyncHttpClient"); + assertEquals(customClient, getFieldValue(asyncCosHttpClient, "delegate")); + assertNotEquals(HttpClientProvider.httpClient(true), getFieldValue(asyncCosHttpClient, "delegate")); + assertNotEquals(HttpClientProvider.httpClient(false), getFieldValue(asyncCosHttpClient, "delegate")); } @Test @@ -248,6 +253,10 @@ void should_use_default_http_client_for_embedding_service() throws Exception { assertNotEquals(customClient, getFieldValue(syncHttpClient, "delegate")); assertEquals(HttpClientProvider.httpClient(verifySsl), getFieldValue(syncHttpClient, "delegate")); + Object asyncHttpClient = getFieldValue(restclient, "asyncHttpClient"); + assertNotEquals(customClient, getFieldValue(asyncHttpClient, "delegate")); + assertEquals(HttpClientProvider.httpClient(verifySsl), getFieldValue(asyncHttpClient, "delegate")); + } catch (Exception e) { fail(e); } diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomEmbeddingRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomEmbeddingRestClient.java index aab5fbcb..48480468 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomEmbeddingRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomEmbeddingRestClient.java @@ -4,7 +4,8 @@ */ package com.ibm.watsonx.ai.client.impl; -import com.ibm.watsonx.ai.embedding.EmbeddingRequest; +import java.util.concurrent.CompletableFuture; +import com.ibm.watsonx.ai.embedding.EmbeddingPayload; import com.ibm.watsonx.ai.embedding.EmbeddingResponse; import com.ibm.watsonx.ai.embedding.EmbeddingRestClient; @@ -15,10 +16,15 @@ public class CustomEmbeddingRestClient extends EmbeddingRestClient { } @Override - public EmbeddingResponse embedding(String transactionId, EmbeddingRequest embeddingRequest) { + public EmbeddingResponse embedding(String transactionId, EmbeddingPayload request) { throw new UnsupportedOperationException("Unimplemented method 'embedding'"); } + @Override + public CompletableFuture embeddingAsync(String transactionId, EmbeddingPayload embeddingPayload) { + throw new UnsupportedOperationException("Unimplemented method 'embeddingAsync'"); + } + public static final class CustomEmbeddingRestClientBuilderFactory implements EmbeddingRestClientBuilderFactory { @Override public Builder get() { From fbc4d1657eb9469df77794554c8a45378092950a Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Sun, 5 Apr 2026 22:36:25 +0200 Subject: [PATCH 2/6] Add async file deletion and improve batch timeout handling --- .../ibm/watsonx/ai/batch/BatchService.java | 42 +++++++++---------- .../watsonx/ai/file/DefaultRestClient.java | 29 +++++++++++++ .../ibm/watsonx/ai/file/FileRestClient.java | 9 ++++ .../com/ibm/watsonx/ai/file/FileService.java | 19 +++++++++ .../com/ibm/watsonx/ai/BatchServiceTest.java | 36 ++++++++++++++++ .../com/ibm/watsonx/ai/it/FileServiceIT.java | 8 +--- 6 files changed, 115 insertions(+), 28 deletions(-) diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/batch/BatchService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/batch/BatchService.java index 58cab912..04fcbff8 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/batch/BatchService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/batch/BatchService.java @@ -7,28 +7,22 @@ import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; -import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.runAsync; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.time.Duration; import java.time.LocalTime; -import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import com.ibm.watsonx.ai.WatsonxService.ProjectService; import com.ibm.watsonx.ai.chat.ChatRequest; import com.ibm.watsonx.ai.chat.ChatResponse; import com.ibm.watsonx.ai.chat.ChatUtility; import com.ibm.watsonx.ai.core.Json; import com.ibm.watsonx.ai.core.auth.Authenticator; -import com.ibm.watsonx.ai.core.provider.ExecutorProvider; import com.ibm.watsonx.ai.core.spi.json.TypeToken; import com.ibm.watsonx.ai.file.FileDeleteRequest; import com.ibm.watsonx.ai.file.FileService; @@ -292,10 +286,25 @@ public List> submitAndFetch(BatchCreateRequest request, Class while (status != Status.COMPLETED && status != Status.FAILED) { - if (LocalTime.now().isAfter(endTime)) + if (LocalTime.now().isAfter(endTime)) { + + cancel( + BatchCancelRequest.builder() + .batchId(batchData.id()) + .projectId(projectSpace.projectId()) + .spaceId(projectSpace.spaceId()) + .transactionId(request.transactionId()) + .build()); + + deleteFile( + removeUploadedFile ? batchData.inputFileId() : null, + null, + request.transactionId()); + throw new RuntimeException( "The execution of the batch operation for the file \"%s\" took longer than the timeout set by %s milliseconds" .formatted(request.inputFileId(), timeout.toMillis())); + } try { @@ -322,8 +331,7 @@ public List> submitAndFetch(BatchCreateRequest request, Class deleteFile( removeUploadedFile ? batchData.inputFileId() : null, null, - request.transactionId(), - timeout + request.transactionId() ); throw new RuntimeException("The batch operation failed: %s".formatted(batchData)); } @@ -337,8 +345,7 @@ public List> submitAndFetch(BatchCreateRequest request, Class deleteFile( removeUploadedFile ? batchData.inputFileId() : null, removeOutputFile ? batchData.outputFileId() : null, - request.transactionId(), - timeout + request.transactionId() ); return result; @@ -499,17 +506,12 @@ public BatchData cancel(BatchCancelRequest request) { /** * Deletes the input and/or output files associated with a completed batch job. - *

- * Each non-null file identifier is deleted concurrently. The method blocks until all deletions complete or the timeout expires. * * @param inputFileId the identifier of the input file to delete, or {@code null} to skip * @param outputFileId the identifier of the output file to delete, or {@code null} to skip * @param transactionId optional transaction identifier to propagate to the delete requests - * @param timeout the maximum time to wait for all deletions to complete */ - private void deleteFile(String inputFileId, String outputFileId, String transactionId, Duration timeout) { - - var futures = new ArrayList>(); + private void deleteFile(String inputFileId, String outputFileId, String transactionId) { if (nonNull(inputFileId)) { var request = FileDeleteRequest.builder() @@ -517,7 +519,7 @@ private void deleteFile(String inputFileId, String outputFileId, String transact .transactionId(transactionId) .build(); - futures.add(runAsync(() -> fileService.delete(request), ExecutorProvider.callbackExecutor())); + fileService.deleteAsync(request); } if (nonNull(outputFileId)) { @@ -526,10 +528,8 @@ private void deleteFile(String inputFileId, String outputFileId, String transact .transactionId(transactionId) .build(); - futures.add(runAsync(() -> fileService.delete(request), ExecutorProvider.callbackExecutor())); + fileService.deleteAsync(request); } - - allOf(futures.toArray(new CompletableFuture[0])).join(); } /** diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/DefaultRestClient.java index 6c8daf26..29fecc4c 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/DefaultRestClient.java @@ -14,10 +14,15 @@ import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import com.ibm.watsonx.ai.core.Json; import com.ibm.watsonx.ai.core.factory.HttpClientFactory; +import com.ibm.watsonx.ai.core.http.AsyncHttpClient; import com.ibm.watsonx.ai.core.http.MultipartBody; import com.ibm.watsonx.ai.core.http.SyncHttpClient; import com.ibm.watsonx.ai.core.http.interceptors.LoggerInterceptor.LogMode; +import com.ibm.watsonx.ai.core.provider.ExecutorProvider; /** * Default implementation of the {@link FileRestClient} abstract class. @@ -25,11 +30,13 @@ final class DefaultRestClient extends FileRestClient { private final SyncHttpClient syncHttpClient; + private final AsyncHttpClient asyncHttpClient; DefaultRestClient(Builder builder) { super(builder); requireNonNull(authenticator, "authenticator is mandatory"); syncHttpClient = HttpClientFactory.createSync(authenticator, httpClient, LogMode.of(logRequests, logResponses)); + asyncHttpClient = HttpClientFactory.createAsync(authenticator, httpClient, LogMode.of(logRequests, logResponses)); } @Override @@ -162,6 +169,28 @@ public FileDeleteResponse delete(FileDeleteRequest request) { } } + @Override + public CompletableFuture deleteAsync(FileDeleteRequest request) { + + var httpRequest = HttpRequest.newBuilder(URI.create(baseUrl + "/ml/v1/files/%s?version=%s".formatted(request.fileId(), version))) + .DELETE() + .timeout(timeout) + .header("Accept", "application/json"); + + if (nonNull(request.projectId())) + httpRequest.header("X-IBM-Project-ID", request.projectId()); + + if (nonNull(request.spaceId())) + httpRequest.header("X-IBM-Space-ID", request.spaceId()); + + if (nonNull(request.transactionId())) + httpRequest.header(TRANSACTION_ID_HEADER, request.transactionId()); + + return asyncHttpClient.send(httpRequest.build(), BodyHandlers.ofString()) + .thenApplyAsync(r -> Json.fromJson(r.body(), FileDeleteResponse.class), ExecutorProvider.cpuExecutor()) + .thenApplyAsync(Function.identity(), ExecutorProvider.ioExecutor()); + } + /** * Returns a new {@link Builder} instance. */ diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileRestClient.java index d9de1453..5dd928a0 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileRestClient.java @@ -5,6 +5,7 @@ package com.ibm.watsonx.ai.file; import java.util.ServiceLoader; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import com.ibm.watsonx.ai.WatsonxRestClient; @@ -49,6 +50,14 @@ protected FileRestClient(Builder builder) { */ public abstract FileDeleteResponse delete(FileDeleteRequest fileDeleteRequest); + /** + * Deletes an uploaded file by its identifier. + * + * @param fileDeleteRequest the {@link FileDeleteRequest} object. + * @return a {@link FileDeleteResponse} confirming the deletion. + */ + public abstract CompletableFuture deleteAsync(FileDeleteRequest fileDeleteRequest); + /** * Creates a new {@link Builder} using the first available {@link FileRestClientBuilderFactory} discovered via {@link ServiceLoader}. *

diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileService.java index c145e1aa..4b932664 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/file/FileService.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; import com.ibm.watsonx.ai.WatsonxService.ProjectService; import com.ibm.watsonx.ai.core.auth.Authenticator; @@ -222,6 +223,24 @@ public FileDeleteResponse delete(FileDeleteRequest request) { .build()); } + /** + * Deletes an uploaded file using the provided {@link FileDeleteRequest}. + * + * @param request the {@link FileDeleteRequest} object + * @return a {@link FileDeleteResponse} confirming the deletion + */ + public CompletableFuture deleteAsync(FileDeleteRequest request) { + requireNonNull(request, "request cannot be null"); + requireNonNull(request.fileId(), "request.fileId cannot be null"); + ProjectSpace projectSpace = resolveProjectSpace(request); + return client.deleteAsync(FileDeleteRequest.builder() + .projectId(projectSpace.projectId()) + .spaceId(projectSpace.spaceId()) + .transactionId(request.transactionId()) + .fileId(request.fileId()) + .build()); + } + /** * Returns a new {@link Builder} instance. *

diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java index cab69a97..ee1d202d 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java @@ -925,6 +925,18 @@ void should_throw_exception_when_timeout_is_exceeded() { .withStatus(200) .withBody(IN_PROGRESS_RESPONSE))); + wireMock.stubFor(post("/ml/v1/batches/%s/cancel?version=%s".formatted(BATCH_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody("{}"))); + + wireMock.stubFor(delete("/ml/v1/files/%s?version=%s".formatted(FILE_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody(FILE_DELETE_RESPONSE))); + var fileService = FileService.builder() .authenticator(mockAuthenticator) .projectId(PROJECT_ID) @@ -967,6 +979,18 @@ void should_throw_exception_when_service_level_timeout_is_exceeded() { .withStatus(200) .withBody(IN_PROGRESS_RESPONSE))); + wireMock.stubFor(post("/ml/v1/batches/%s/cancel?version=%s".formatted(BATCH_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody("{}"))); + + wireMock.stubFor(delete("/ml/v1/files/%s?version=%s".formatted(FILE_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody(FILE_DELETE_RESPONSE))); + var fileService = FileService.builder() .authenticator(mockAuthenticator) .projectId(PROJECT_ID) @@ -1011,6 +1035,18 @@ void should_use_request_timeout_over_service_level_timeout() { .withStatus(200) .withBody(IN_PROGRESS_RESPONSE))); + wireMock.stubFor(post("/ml/v1/batches/%s/cancel?version=%s".formatted(BATCH_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody("{}"))); + + wireMock.stubFor(delete("/ml/v1/files/%s?version=%s".formatted(FILE_ID, API_VERSION)) + .withHeader("X-IBM-Project-ID", equalTo(PROJECT_ID)) + .willReturn(aResponse() + .withStatus(200) + .withBody(FILE_DELETE_RESPONSE))); + var fileService = FileService.builder() .authenticator(mockAuthenticator) .projectId(PROJECT_ID) diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/FileServiceIT.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/FileServiceIT.java index c756775d..35b938d9 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/FileServiceIT.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/FileServiceIT.java @@ -40,7 +40,7 @@ public class FileServiceIT { .build(); @Test - void should_upload_files() throws Exception { + void should_upload_fetch_and_delete_files() throws Exception { var path = Path.of(ClassLoader.getSystemResource("file_to_upload.jsonl").toURI()); FileData fileData = fileService.upload(path); @@ -63,10 +63,7 @@ void should_upload_files() throws Exception { assertEquals("test.txt", fileData.filename()); assertEquals("batch", fileData.purpose()); assertEquals(Files.readString(path), fileService.retrieve(fileData.id())); - } - @Test - void should_list_files() throws Exception { var request = FileListRequest.builder() .limit(2) .order(Order.DESC) @@ -80,10 +77,7 @@ void should_list_files() throws Exception { assertNotNull(fileListResponse.firstId()); assertNotNull(fileListResponse.lastId()); assertNotNull(fileListResponse.hasMore()); - } - @Test - void should_delete_files() throws Exception { var files = fileService.list(); for (var file : files.data()) assertTrue(fileService.delete(file.id()).deleted()); From eac76f9013564ca0aa62daf2438f210754bd5114 Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Sun, 5 Apr 2026 23:40:20 +0200 Subject: [PATCH 3/6] Improve timeout handling with resource cleanup in text services --- .../TextClassificationService.java | 27 +++++++++++++++++-- .../textextraction/TextExtractionService.java | 27 +++++++++++++++++-- .../TextClassificationTest.java | 12 +++++++++ .../textextraction/TextExtractionTest.java | 15 ++++++++++- 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java index c2c4ac60..f168e6e2 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java @@ -505,13 +505,36 @@ private TextClassificationResponse startClassification(String requestId, String Status status; long sleepTime = 100; LocalTime endTime = LocalTime.now().plus(timeout); + String processId = null; do { - if (LocalTime.now().isAfter(endTime)) + if (LocalTime.now().isAfter(endTime)) { + + if (nonNull(processId)) { + deleteRequest( + processId, + TextClassificationDeleteParameters.builder() + .projectId(projectId) + .spaceId(spaceId) + .transactionId(transactionId) + .build() + ); + } + + if (removeUploadedFile) { + try { + var encodedFileName = new URI(null, null, path, null).toASCIIString(); + client.asyncDeleteFile(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + throw new TextClassificationException("timeout", "The execution of the classification %s file took longer than the timeout set by %s milliseconds" .formatted(path, timeout.toMillis())); + } try { @@ -523,7 +546,7 @@ private TextClassificationResponse startClassification(String requestId, String throw new TextClassificationException("interrupted", e.getMessage()); } - var processId = response.metadata().id(); + processId = response.metadata().id(); response = fetchClassificationRequest(requestId, processId, TextClassificationFetchParameters.builder() .projectId(projectId) .spaceId(spaceId) diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java index 22c2048c..2a481a04 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java @@ -608,13 +608,36 @@ private TextExtractionResponse startExtraction(String requestId, String path, Te Status status; long sleepTime = 100; LocalTime endTime = LocalTime.now().plus(timeout); + String processId = null; do { - if (LocalTime.now().isAfter(endTime)) + if (LocalTime.now().isAfter(endTime)) { + + if (nonNull(processId)) { + deleteRequest( + processId, + TextExtractionDeleteParameters.builder() + .projectId(projectId) + .spaceId(spaceId) + .transactionId(transactionId) + .build() + ); + } + + if (removeUploadedFile) { + try { + var encodedFileName = new URI(null, null, path, null).toASCIIString(); + client.asyncDeleteFile(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + throw new TextExtractionException("timeout", "Execution to extract %s file took longer than the timeout set by %s milliseconds" .formatted(path, timeout.toMillis())); + } try { @@ -626,7 +649,7 @@ private TextExtractionResponse startExtraction(String requestId, String path, Te throw new TextExtractionException("interrupted", e.getMessage()); } - var processId = response.metadata().id(); + processId = response.metadata().id(); response = fetchExtractionRequest(requestId, processId, TextExtractionFetchParameters.builder() .projectId(projectId) .spaceId(spaceId) diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java index 7088affd..1bf41bb9 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java @@ -689,6 +689,7 @@ void should_handle_long_running_classification_with_retries() throws Exception { @Test void should_throw_exception_when_classification_timeout_exceeded() throws Exception { + when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); var JOB = Files.readString(Path.of(ClassLoader.getSystemResource("classification_job.json").toURI())); watsonxServer.stubFor(post("/ml/v1/text/classifications?version=%s".formatted(API_VERSION)) @@ -725,9 +726,20 @@ void should_throw_exception_when_classification_timeout_exceeded() throws Except .withBody(JOB.formatted("completed")) )); + watsonxServer + .stubFor(delete("/ml/v1/text/classifications/id?version=%s&project_id=%s".formatted(API_VERSION, "project-id")) + .withHeader("Authorization", equalTo("Bearer token")) + .willReturn(aResponse() + .withStatus(204) + )); + + cosServer.stubFor(delete("/%s/%s".formatted("my-bucket", "test.pdf")) + .withHeader("Authorization", equalTo("Bearer token")) + .willReturn(aResponse().withStatus(200))); TextClassificationParameters parameters = TextClassificationParameters.builder() .timeout(Duration.ofMillis(100)) + .removeUploadedFile(true) .build(); var ex = assertThrows( diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java index 75ba31df..68ae31ac 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java @@ -1558,8 +1558,9 @@ void should_handle_long_running_extraction_with_retries() throws Exception { @Test void should_throw_exception_when_extraction_timeout_exceeded() { - when(mockAuthenticator.token()).thenReturn("my-super-token"); var outputFileName = FILE_NAME.replace(".pdf", ".md"); + when(mockAuthenticator.token()).thenReturn("my-super-token"); + when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-super-token")); watsonxServer.stubFor(post("/ml/v1/text/extractions?version=%s".formatted(API_VERSION)) .inScenario("long_response") @@ -1602,8 +1603,20 @@ void should_throw_exception_when_extraction_timeout_exceeded() { .withHeader("Authorization", equalTo("Bearer my-super-token")) .willReturn(aResponse().withStatus(200).withBody("Hello"))); + watsonxServer + .stubFor(delete("/ml/v1/text/extractions/%s?version=%s&project_id=%s".formatted(PROCESS_EXTRACTION_ID, API_VERSION, "projectid")) + .withHeader("Authorization", equalTo("Bearer my-super-token")) + .willReturn(aResponse() + .withStatus(204) + )); + + cosServer.stubFor(delete("/%s/%s".formatted(BUCKET_NAME, FILE_NAME)) + .withHeader("Authorization", equalTo("Bearer my-super-token")) + .willReturn(aResponse().withStatus(200))); + TextExtractionParameters options = TextExtractionParameters.builder() .timeout(Duration.ofMillis(100)) + .removeUploadedFile(true) .build(); var ex = assertThrows( From 0e93cf2eea52ac2b94303faae541d61a29fa1b25 Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Sun, 5 Apr 2026 23:51:12 +0200 Subject: [PATCH 4/6] Rename async methods to follow Java naming conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change async method naming from prefix to suffix pattern: - asyncToken() → tokenAsync() - asyncTokenize() → tokenizeAsync() - asyncDeleteFile() → deleteFileAsync() --- .../watsonx/ai/core/auth/Authenticator.java | 2 +- .../ai/core/auth/cp4d/CP4DAuthenticator.java | 4 +- .../ai/core/auth/cp4d/CP4DRestClient.java | 2 +- .../core/auth/cp4d/DefaultIAMRestClient.java | 2 +- .../auth/cp4d/DefaultLegacyRestClient.java | 2 +- .../core/auth/cp4d/DefaultZenRestClient.java | 2 +- .../core/auth/ibmcloud/DefaultRestClient.java | 2 +- .../auth/ibmcloud/IBMCloudAuthenticator.java | 4 +- .../auth/ibmcloud/IBMCloudRestClient.java | 2 +- .../AuthenticationInterceptor.java | 2 +- .../core/AuthenticationInterceptorTest.java | 8 +-- .../ai/core/CP4DAuthenticationTest.java | 16 ++--- .../ai/core/IBMCloudAuthenticatorTest.java | 10 ++-- .../textclassification/DefaultRestClient.java | 4 +- .../TextClassificationRestClient.java | 2 +- .../TextClassificationService.java | 4 +- .../textextraction/DefaultRestClient.java | 4 +- .../TextExtractionRestClient.java | 2 +- .../textextraction/TextExtractionService.java | 6 +- .../ai/tokenization/DefaultRestClient.java | 2 +- .../tokenization/TokenizationRestClient.java | 2 +- .../ai/tokenization/TokenizationService.java | 8 +-- .../ibm/watsonx/ai/DeploymentServiceTest.java | 12 ++-- .../watsonx/ai/TextGenerationServiceTest.java | 4 +- .../watsonx/ai/TokenizationServiceTest.java | 6 +- .../ai/chat/ChatServiceStreamingTest.java | 58 +++++++++---------- .../ai/chat/ChatServiceThinkingTest.java | 12 ++-- .../client/impl/CustomCP4DIAMRestClient.java | 4 +- .../impl/CustomCP4DLegacyRestClient.java | 4 +- .../client/impl/CustomCP4DZenRestClient.java | 4 +- .../client/impl/CustomIBMCloudRestClient.java | 4 +- .../CustomTextClassificationRestClient.java | 4 +- .../impl/CustomTextExtractionRestClient.java | 4 +- .../impl/CustomTokenizationRestClient.java | 4 +- .../watsonx/ai/it/TokenizationServiceIT.java | 2 +- .../TextClassificationTest.java | 12 ++-- .../textextraction/TextExtractionTest.java | 16 ++--- 37 files changed, 121 insertions(+), 121 deletions(-) diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/Authenticator.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/Authenticator.java index b75055c5..43fea1ec 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/Authenticator.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/Authenticator.java @@ -31,7 +31,7 @@ public interface Authenticator { * * @return a {@link CompletableFuture} that will complete with the access token */ - CompletableFuture asyncToken(); + CompletableFuture tokenAsync(); /** * Returns the authentication scheme. diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DAuthenticator.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DAuthenticator.java index fbbf2e5b..f1b0f858 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DAuthenticator.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DAuthenticator.java @@ -88,14 +88,14 @@ public String token() { } @Override - public CompletableFuture asyncToken() { + public CompletableFuture tokenAsync() { TokenResponse currentToken = token.get(); if (!isExpired(currentToken)) return completedFuture(token.get().accessToken()); - return client.asyncToken(new TokenRequest(username, password, apiKey)).thenApply(identityTokenResponse -> { + return client.tokenAsync(new TokenRequest(username, password, apiKey)).thenApply(identityTokenResponse -> { token.getAndSet(identityTokenResponse); return identityTokenResponse.accessToken(); }); diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DRestClient.java index 940e70b8..87f18463 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/CP4DRestClient.java @@ -40,7 +40,7 @@ protected CP4DRestClient(Builder builder) { * * @return a {@link CompletableFuture} that contains the token and related metadata */ - public abstract CompletableFuture asyncToken(TokenRequest request); + public abstract CompletableFuture tokenAsync(TokenRequest request); /** * Creates a new {@link Builder} by loading the first available {@code CP4D*RestClientBuilderFactory} discovered via {@link ServiceLoader}, diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultIAMRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultIAMRestClient.java index 729821fd..c0a05a9e 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultIAMRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultIAMRestClient.java @@ -49,7 +49,7 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { + public CompletableFuture tokenAsync(TokenRequest request) { return asyncHttpClient .send(createTokenRequest(request), BodyHandlers.ofString()) .thenCompose(response -> { diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultLegacyRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultLegacyRestClient.java index ca90fcd7..283bbfa3 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultLegacyRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultLegacyRestClient.java @@ -47,7 +47,7 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { + public CompletableFuture tokenAsync(TokenRequest request) { return asyncHttpClient .send(createTokenRequest(request), BodyHandlers.ofString()) .thenApplyAsync(this::parseTokenResponse, ExecutorProvider.cpuExecutor()) diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultZenRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultZenRestClient.java index 395fd491..1b2e890f 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultZenRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/cp4d/DefaultZenRestClient.java @@ -24,7 +24,7 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { + public CompletableFuture tokenAsync(TokenRequest request) { return completedFuture(createTokenResponse(request)); } diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/DefaultRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/DefaultRestClient.java index 3746f5d8..f2c51202 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/DefaultRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/DefaultRestClient.java @@ -47,7 +47,7 @@ public TokenResponse token(String apiKey, String grantType) { } @Override - public CompletableFuture asyncToken(String apiKey, String grantType) { + public CompletableFuture tokenAsync(String apiKey, String grantType) { return asyncHttpClient.send(createHttpRequest(apiKey, grantType), BodyHandlers.ofString()) .thenApplyAsync(response -> fromJson(response.body(), TokenResponse.class), ExecutorProvider.cpuExecutor()) .thenApplyAsync(Function.identity(), ExecutorProvider.ioExecutor()); diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudAuthenticator.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudAuthenticator.java index f5b25294..fbc01e93 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudAuthenticator.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudAuthenticator.java @@ -72,14 +72,14 @@ public String token() { } @Override - public CompletableFuture asyncToken() { + public CompletableFuture tokenAsync() { TokenResponse currentToken = token.get(); if (!isExpired(currentToken)) return completedFuture(currentToken.accessToken()); - return client.asyncToken(apiKey, grantType).thenApply(identityTokenResponse -> { + return client.tokenAsync(apiKey, grantType).thenApply(identityTokenResponse -> { token.getAndSet(identityTokenResponse); return identityTokenResponse.accessToken(); }); diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudRestClient.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudRestClient.java index 4565b81b..d7b31e2f 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudRestClient.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/auth/ibmcloud/IBMCloudRestClient.java @@ -44,7 +44,7 @@ protected IBMCloudRestClient(Builder builder) { * @param grantType the grant type to use * @return a {@link CompletableFuture} that completes with the IAM response or exceptionally on error */ - public abstract CompletableFuture asyncToken(String apiKey, String grantType); + public abstract CompletableFuture tokenAsync(String apiKey, String grantType); /** * Creates a new {@link Builder} using the first available {@link IBMCloudRestClientBuilderFactory} discovered via {@link ServiceLoader}. diff --git a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/http/interceptors/AuthenticationInterceptor.java b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/http/interceptors/AuthenticationInterceptor.java index 57ccaf57..c35fca16 100644 --- a/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/http/interceptors/AuthenticationInterceptor.java +++ b/modules/watsonx-ai-core/src/main/java/com/ibm/watsonx/ai/core/http/interceptors/AuthenticationInterceptor.java @@ -32,7 +32,7 @@ public AuthenticationInterceptor(Authenticator authenticator) { @Override public CompletableFuture> intercept(HttpRequest request, BodyHandler bodyHandler, int index, AsyncChain chain) { - return authenticator.asyncToken() + return authenticator.tokenAsync() .thenCompose(token -> chain.proceed(requestWithAuthHeader(request, token), bodyHandler)); } diff --git a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/AuthenticationInterceptorTest.java b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/AuthenticationInterceptorTest.java index 0c5135c4..7a4f6012 100644 --- a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/AuthenticationInterceptorTest.java +++ b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/AuthenticationInterceptorTest.java @@ -140,7 +140,7 @@ class Async { @Test void should_send_request_with_bearer_token() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my_super_token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my_super_token")); when(mockAuthenticator.scheme()).thenReturn("Bearer"); withWatsonxServiceMock(() -> { @@ -164,7 +164,7 @@ void should_send_request_with_bearer_token() throws Exception { void should_send_request_with_zen_api_key() throws Exception { var cp4dAuthenticatorMock = mock(CP4DAuthenticator.class); - when(cp4dAuthenticatorMock.asyncToken()).thenReturn(completedFuture("#1234")); + when(cp4dAuthenticatorMock.tokenAsync()).thenReturn(completedFuture("#1234")); when(cp4dAuthenticatorMock.scheme()).thenReturn("ZenApiKey"); when(cp4dAuthenticatorMock.isAuthMode(AuthMode.ZEN_API_KEY)).thenReturn(true); @@ -189,7 +189,7 @@ void should_send_request_with_zen_api_key() throws Exception { @Test void should_throw_exception_when_bearer_token_is_invalid() { - when(mockAuthenticator.asyncToken()).thenThrow(new RuntimeException("error")); + when(mockAuthenticator.tokenAsync()).thenThrow(new RuntimeException("error")); withWatsonxServiceMock(() -> { @@ -212,7 +212,7 @@ void should_throw_exception_when_bearer_token_is_invalid() { @SuppressWarnings("unchecked") void should_execute_with_custom_executor() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my_super_token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my_super_token")); var threadNames = new ArrayList<>(); var ioExecutor = Executors.newSingleThreadScheduledExecutor(r -> new Thread(() -> { diff --git a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/CP4DAuthenticationTest.java b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/CP4DAuthenticationTest.java index 6e66fa1b..b82700eb 100644 --- a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/CP4DAuthenticationTest.java +++ b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/CP4DAuthenticationTest.java @@ -511,7 +511,7 @@ void should_return_a_valid_token() throws Exception { .apiKey("api_key") .build(); - assertEquals("access-token", assertDoesNotThrow(() -> authenticator.asyncToken().get())); + assertEquals("access-token", assertDoesNotThrow(() -> authenticator.tokenAsync().get())); }); } @@ -543,7 +543,7 @@ void should_use_iam_authentication() { .authMode(AuthMode.IAM) .build(); - String token = authenticator.asyncToken().get(); + String token = authenticator.tokenAsync().get(); assertEquals("access-token", token); verify(mockSecureHttpClient, times(2)).sendAsync(reqCaptor.capture(), any()); @@ -587,9 +587,9 @@ void should_use_cached_token() throws Exception { .build(); // Execute the http request. - assertDoesNotThrow(() -> authenticator.asyncToken().get()); + assertDoesNotThrow(() -> authenticator.tokenAsync().get()); // Get the value from the cache. - assertDoesNotThrow(() -> authenticator.asyncToken().get()); + assertDoesNotThrow(() -> authenticator.tokenAsync().get()); verify(mockSecureHttpClient, times(1)).sendAsync(any(), any()); }); @@ -626,10 +626,10 @@ else if (url.endsWith("/v1/preauth/validateAuth GET")) .authMode(AuthMode.IAM) .build(); - var token = authenticator.asyncToken().get(); + var token = authenticator.tokenAsync().get(); assertEquals("iam-access-token", token); - token = authenticator.asyncToken().get(); + token = authenticator.tokenAsync().get(); assertEquals("iam-access-token", token); ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); @@ -663,7 +663,7 @@ void should_use_zen_api_key_authentication_mode() { .build(); var encoded = Base64.encodeBase64String("username:api_key".getBytes()); - assertEquals(encoded, assertDoesNotThrow(() -> authenticator.asyncToken().get())); + assertEquals(encoded, assertDoesNotThrow(() -> authenticator.tokenAsync().get())); assertTrue(authenticator.isAuthMode(AuthMode.ZEN_API_KEY)); } @@ -706,7 +706,7 @@ void should_use_the_correct_executors() throws Exception { .apiKey("api_key") .build(); - assertDoesNotThrow(() -> authenticator.asyncToken() + assertDoesNotThrow(() -> authenticator.tokenAsync() .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), ioExecutor) .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), cpuExecutor) .get(3, TimeUnit.SECONDS)); diff --git a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/IBMCloudAuthenticatorTest.java b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/IBMCloudAuthenticatorTest.java index 072bb6b1..9451d6b8 100644 --- a/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/IBMCloudAuthenticatorTest.java +++ b/modules/watsonx-ai-core/src/test/java/com/ibm/watsonx/ai/core/IBMCloudAuthenticatorTest.java @@ -288,7 +288,7 @@ void should_return_a_valid_token() throws Exception { .apiKey("my_super_api_key") .build(); - assertEquals("my_super_token", assertDoesNotThrow(() -> authenticator.asyncToken().get())); + assertEquals("my_super_token", assertDoesNotThrow(() -> authenticator.tokenAsync().get())); assertEquals("https://iam.cloud.ibm.com/identity/token", mockHttpRequest.getValue().uri().toString()); assertEquals("application/x-www-form-urlencoded", mockHttpRequest.getValue().headers().firstValue("Content-Type").get()); @@ -311,9 +311,9 @@ void should_use_a_cached_token() throws Exception { .build(); // Execute the http request. - assertDoesNotThrow(() -> authenticator.asyncToken().get()); + assertDoesNotThrow(() -> authenticator.tokenAsync().get()); // Get the value from the cache. - assertDoesNotThrow(() -> authenticator.asyncToken().get()); + assertDoesNotThrow(() -> authenticator.tokenAsync().get()); verify(mockSecureHttpClient, times(1)).sendAsync(any(), any()); }); @@ -335,7 +335,7 @@ void should_use_custom_parameters() throws Exception { .apiKey("my_super_api_key") .build(); - assertEquals("my_super_token", assertDoesNotThrow(() -> authenticator.asyncToken().get())); + assertEquals("my_super_token", assertDoesNotThrow(() -> authenticator.tokenAsync().get())); assertEquals("http://mytest.com/identity/token", mockHttpRequest.getValue().uri().toString()); assertEquals("application/x-www-form-urlencoded", mockHttpRequest.getValue().headers().firstValue("Content-Type").get()); @@ -378,7 +378,7 @@ void should_use_the_correct_executors() throws Exception { .apiKey("my_super_api_key") .build(); - assertDoesNotThrow(() -> authenticator.asyncToken() + assertDoesNotThrow(() -> authenticator.tokenAsync() .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), ioExecutor) .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), cpuExecutor) .get(3, TimeUnit.SECONDS)); diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/DefaultRestClient.java index caa6284e..285d55db 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/DefaultRestClient.java @@ -140,7 +140,7 @@ public boolean deleteClassification(DeleteClassificationRequest request) { @Override public boolean deleteFile(DeleteFileRequest request) { try { - return asyncDeleteFile(request).get(); + return deleteFileAsync(request).get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { @@ -152,7 +152,7 @@ public boolean deleteFile(DeleteFileRequest request) { } @Override - public CompletableFuture asyncDeleteFile(DeleteFileRequest request) { + public CompletableFuture deleteFileAsync(DeleteFileRequest request) { try { var fileName = request.fileName(); diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationRestClient.java index cb2eec28..5c894e88 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationRestClient.java @@ -65,7 +65,7 @@ protected TextClassificationRestClient(Builder builder) { * @param request The {@link DeleteFileRequest} containing bucket and file information. * @return A {@link CompletableFuture} that completes with {@code true} if the file was successfully deleted. */ - public abstract CompletableFuture asyncDeleteFile(DeleteFileRequest request); + public abstract CompletableFuture deleteFileAsync(DeleteFileRequest request); /** * Uploads a file stream to the specified COS bucket. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java index f168e6e2..18831a1f 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationService.java @@ -525,7 +525,7 @@ private TextClassificationResponse startClassification(String requestId, String if (removeUploadedFile) { try { var encodedFileName = new URI(null, null, path, null).toASCIIString(); - client.asyncDeleteFile(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); + client.deleteFileAsync(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -598,7 +598,7 @@ private ClassificationResult getClassificationResult(String requestId, TextClass try { var encodedFileName = new URI(null, null, uploadedPath, null).toASCIIString(); var request = DeleteFileRequest.of(requestId, documentBucketName, encodedFileName); - client.asyncDeleteFile(request); + client.deleteFileAsync(request); } catch (URISyntaxException e) { throw new RuntimeException(e); } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/DefaultRestClient.java index 661466b2..c06fba96 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/DefaultRestClient.java @@ -48,7 +48,7 @@ final class DefaultRestClient extends TextExtractionRestClient { @Override public boolean deleteFile(DeleteFileRequest request) throws FileNotFoundException { try { - return asyncDeleteFile(request).get(); + return deleteFileAsync(request).get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { @@ -65,7 +65,7 @@ public boolean deleteFile(DeleteFileRequest request) throws FileNotFoundExceptio } @Override - public CompletableFuture asyncDeleteFile(DeleteFileRequest request) { + public CompletableFuture deleteFileAsync(DeleteFileRequest request) { try { var fileName = request.fileName(); diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionRestClient.java index 4a6c3e85..95f832b8 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionRestClient.java @@ -47,7 +47,7 @@ protected TextExtractionRestClient(Builder builder) { * @param request the {@link DeleteFileRequest} containing bucket and file information. * @return a {@link CompletableFuture} that resolves to {@code true} if the file was successfully deleted, {@code false} otherwise. */ - public abstract CompletableFuture asyncDeleteFile(DeleteFileRequest request); + public abstract CompletableFuture deleteFileAsync(DeleteFileRequest request); /** * Reads the content of a file from the specified COS bucket. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java index 2a481a04..6d8da763 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionService.java @@ -628,7 +628,7 @@ private TextExtractionResponse startExtraction(String requestId, String path, Te if (removeUploadedFile) { try { var encodedFileName = new URI(null, null, path, null).toASCIIString(); - client.asyncDeleteFile(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); + client.deleteFileAsync(DeleteFileRequest.of(requestId, documentReference.bucket(), encodedFileName)); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -709,7 +709,7 @@ private String getExtractedText(String requestId, TextExtractionResponse textExt try { var encodedFileName = new URI(null, null, outputPath, null).toASCIIString(); var request = DeleteFileRequest.of(requestId, resultsBucketName, encodedFileName); - client.asyncDeleteFile(request); + client.deleteFileAsync(request); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -722,7 +722,7 @@ private String getExtractedText(String requestId, TextExtractionResponse textExt try { var encodedFileName = new URI(null, null, uploadedPath, null).toASCIIString(); var request = DeleteFileRequest.of(requestId, resultsBucketName, encodedFileName); - client.asyncDeleteFile(request); + client.deleteFileAsync(request); } catch (URISyntaxException e) { throw new RuntimeException(e); } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/DefaultRestClient.java index 6f129729..1ef9c66e 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/DefaultRestClient.java @@ -60,7 +60,7 @@ public TokenizationResponse tokenize(String transactionId, TokenizationRequest r } @Override - public CompletableFuture asyncTokenize(String transactionId, TokenizationRequest request) { + public CompletableFuture tokenizeAsync(String transactionId, TokenizationRequest request) { var httpRequest = HttpRequest .newBuilder(URI.create(baseUrl + "/ml/v1/text/tokenization?version=%s".formatted(version))) diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationRestClient.java index aad95aba..9075584c 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationRestClient.java @@ -35,7 +35,7 @@ protected TokenizationRestClient(Builder builder) { * @return A {@link CompletableFuture} resolving to a {@link TokenizationResponse} containing the tokenized representation of the input text and * related metadata. */ - public abstract CompletableFuture asyncTokenize(String transactionId, TokenizationRequest request); + public abstract CompletableFuture tokenizeAsync(String transactionId, TokenizationRequest request); /** * Creates a new {@link Builder} using the first available {@link TokenizationRestClientBuilderFactory} discovered via {@link ServiceLoader}. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationService.java index 85fdffdf..a4483df3 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/tokenization/TokenizationService.java @@ -67,8 +67,8 @@ public TokenizationResponse tokenize(String input) { * @param input The input string to tokenize * @return The tokenization response. */ - public CompletableFuture asyncTokenize(String input) { - return asyncTokenize(input, null); + public CompletableFuture tokenizeAsync(String input) { + return tokenizeAsync(input, null); } /** @@ -91,10 +91,10 @@ public TokenizationResponse tokenize(String input, TokenizationParameters parame * @param parameters Tokenization parameters. * @return The tokenization response. */ - public CompletableFuture asyncTokenize(String input, TokenizationParameters parameters) { + public CompletableFuture tokenizeAsync(String input, TokenizationParameters parameters) { var tokenizationRequest = buildTokenizationRequest(input, parameters); var transactionId = nonNull(parameters) ? parameters.transactionId() : null; - return client.asyncTokenize(transactionId, tokenizationRequest); + return client.tokenizeAsync(transactionId, tokenizationRequest); } /** diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/DeploymentServiceTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/DeploymentServiceTest.java index 17496d5f..f1d71658 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/DeploymentServiceTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/DeploymentServiceTest.java @@ -93,7 +93,7 @@ public class DeploymentServiceTest extends AbstractWatsonxTest { void setup() { when(mockAuthenticator.token()).thenReturn("token"); when(mockAuthenticator.scheme()).thenReturn("Bearer"); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); } @Test @@ -683,7 +683,7 @@ void should_stream_chat_response_with_thinking() throws Exception { .withChunkedDribbleDelay(159, 200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("my-super-token")); CompletableFuture result = new CompletableFuture<>(); ChatRequest chatRequest = ChatRequest.builder() @@ -1222,7 +1222,7 @@ void should_use_correct_executors() throws Exception { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.564Z","usage":{"completion_tokens":3,"prompt_tokens":38,"total_tokens":41}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var deploymentService = DeploymentService.builder() .baseUrl(URI.create("http://localhost:%s".formatted(wireMock.getPort()))) @@ -1582,7 +1582,7 @@ void should_not_override_assistant_content_in_streaming() { """))); var httpPort = wireMock.getPort(); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var delpoymentService = DeploymentService.builder() .authenticator(mockAuthenticator) @@ -1683,7 +1683,7 @@ void should_map_function_call_correctly_in_streaming() { """))); var httpPort = wireMock.getPort(); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var deploymentService = DeploymentService.builder() .authenticator(mockAuthenticator) @@ -2025,7 +2025,7 @@ void should_override_default_chat_parameters() throws Exception { @Test void should_handle_tool_calls_using_tool_registry() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/deployments/my-deployment-id/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TextGenerationServiceTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TextGenerationServiceTest.java index e3621ac0..527164a5 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TextGenerationServiceTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TextGenerationServiceTest.java @@ -482,7 +482,7 @@ void should_stream_text_generation_correctly() throws Exception { """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); when(mockAuthenticator.scheme()).thenReturn("Bearer"); var textGenerationService = TextGenerationService.builder() @@ -576,7 +576,7 @@ void should_use_correct_executors() throws Exception { List threadNames = new ArrayList<>(); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); Executor ioExecutor = Executors.newSingleThreadExecutor(r -> new Thread(() -> { threadNames.add(Thread.currentThread().getName()); diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TokenizationServiceTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TokenizationServiceTest.java index defe4baa..d2cf06dd 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TokenizationServiceTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/TokenizationServiceTest.java @@ -40,7 +40,7 @@ public class TokenizationServiceTest extends AbstractWatsonxTest { @BeforeEach void setUp() { when(mockAuthenticator.token()).thenReturn("my-super-token"); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); } @Test @@ -111,7 +111,7 @@ void should_tokenize_text_asynchronously() throws Exception { .build(); try { - var response = tokenizationService.asyncTokenize("Write a tagline for an alumni association: Together we").get(); + var response = tokenizationService.tokenizeAsync("Write a tagline for an alumni association: Together we").get(); JSONAssert.assertEquals(REQUEST, HttpUtils.bodyPublisherToString(mockHttpRequest), true); JSONAssert.assertEquals(RESPONSE, Json.toJson(response), true); } catch (Exception e) { @@ -330,7 +330,7 @@ void should_use_correct_executors() throws Exception { .build(); try { - tokenizationService.asyncTokenize("input") + tokenizationService.tokenizeAsync("input") .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), ioExecutor) .thenRunAsync(() -> threadNames.add(Thread.currentThread().getName()), cpuExecutor) .get(3, TimeUnit.SECONDS); diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceStreamingTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceStreamingTest.java index 58dc0358..dfc67beb 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceStreamingTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceStreamingTest.java @@ -159,7 +159,7 @@ void should_stream_chat_response_in_chunks_correctly() throws Exception { """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -324,7 +324,7 @@ void should_stream_tool_calls_and_capture_partial_and_complete_tool_responses() """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -641,7 +641,7 @@ void should_stream_chat_response_and_handle_required_tool_choice_correctly() thr data: {"id":"chatcmpl-75021362a9edcdacca7976b97cc20f0d","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[],"created":1749766697,"model_version":"4.0.0","created_at":"2025-06-12T22:18:18.661Z","usage":{"completion_tokens":49,"prompt_tokens":374,"total_tokens":423}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -934,7 +934,7 @@ void should_handle_error_in_chat_streaming() throws Exception { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[{"index":0,"finish_reason":null,"delta":{"content":"iao"}}],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.552Z"} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -996,7 +996,7 @@ void should_throw_exception_when_on_complete_handler_fails() throws Exception { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8", "model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[{"index":0,"finish_reason":null,"delta":{"role":"assistant","content":""}}],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.541Z","system":{"warnings":[{"message":"This model is a Non-IBM Product governed by a third-party license that may impose use restrictions and other obligations. By using this model you agree to its terms as identified in the following URL.","id":"disclaimer_warning","more_info":"https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models.html?context=wx"}]}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -1122,7 +1122,7 @@ void should_throw_model_not_supported_exception_for_invalid_model_in_chat_stream "status_code": 404 }"""))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -1179,7 +1179,7 @@ void should_handle_error_event_and_continue_chat_streaming() { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[{"index":0,"finish_reason":null,"delta":{"content":"Hello"}}],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.552Z"} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -1302,7 +1302,7 @@ void should_process_chat_streaming_non_blocking() { var executor = Executors.newCachedThreadPool(); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("my-token")); var chatService = ChatService.builder() .baseUrl("http://localhost:%s".formatted(wireMock.getPort())) @@ -1380,7 +1380,7 @@ void should_retry_async_interceptors_until_success() { "time_limit" : 60000 }"""; - when(mockAuthenticator.asyncToken()) + when(mockAuthenticator.tokenAsync()) .thenReturn( failedFuture(new AuthenticationTokenExpiredException("Failed to authenticate the request due to an expired token", 401, detail))) .thenReturn(completedFuture("my-super-token")); @@ -1516,7 +1516,7 @@ void should_use_correct_executors() throws Exception { .withChunkedDribbleDelay(5, 200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var chatService = ChatService.builder() .baseUrl("http://localhost:%s".formatted(wireMock.getPort())) @@ -1675,7 +1675,7 @@ void should_not_override_assistant_content_in_streaming() { """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -1778,7 +1778,7 @@ void should_call_tool_interceptor_correctly_in_streaming() { """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -1975,7 +1975,7 @@ void should_call_tool_interceptor_only_two_times_in_streaming() { ))); var toolInterceptor = mock(ToolInterceptor.class); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); when(toolInterceptor.intercept(any(InterceptorContext.class), any(CompletedToolCall.class))) .thenReturn(new CompletedToolCall("id_1", 0, ToolCall.of("1", "name_1", "{ \"test_1\": \"1\"}"))) .thenReturn(new CompletedToolCall("id_2", 0, ToolCall.of("2", "name_2", "{ \"test_2\": \"2\"}"))); @@ -2146,7 +2146,7 @@ void should_invoke_tool_with_correct_arguments() { data: {"id":"chatcmpl-a83f98dc7a834a4caf1b48af4392d666","object":"chat.completion.chunk","model_id":"ibm/granite-4-h-small","model":"ibm/granite-4-h-small","choices":[],"created":1764859084,"model_version":"4.0.0","created_at":"2025-12-04T14:38:05.559Z","usage":{"completion_tokens":28,"prompt_tokens":192,"total_tokens":220}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); when(mockAuthenticator.token()).thenReturn("my-super-token"); when(mockAuthenticator.scheme()).thenReturn("Bearer"); @@ -2203,7 +2203,7 @@ public void onError(Throwable error) { @Test void should_process_multiple_tool_calls_from_single_stream_chunk() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() @@ -2263,7 +2263,7 @@ public void onError(Throwable error) {} @Test void should_handle_tool_calls_from_multiple_stream_chunks() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() @@ -2331,7 +2331,7 @@ public void onCompleteToolCall(CompletedToolCall completeToolCall) { @Test void should_invoke_tool_calls_in_parallels() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() @@ -2392,7 +2392,7 @@ public void onCompleteToolCall(CompletedToolCall completeToolCall) { @Test void should_invoke_on_error_during_interceptor_call() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .willReturn(aResponse() .withStatus(200) @@ -2439,7 +2439,7 @@ public void onError(Throwable error) { @Test void should_stream_chat_response_and_process_thinking_and_tool_calls() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() @@ -2644,7 +2644,7 @@ void should_stop_streaming_on_first_error() throws Exception { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[{"index":0,"finish_reason":null,"delta":{"content":"ao"}}],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.552Z"} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -2756,7 +2756,7 @@ void should_use_cached_thread_pool_for_interceptor() { data: {"id":"chatcmpl-cc34b5ea3120fa9e07b18c5125d66602","object":"chat.completion.chunk","model_id":"ibm/granite-4-h-small","model":"ibm/granite-4-h-small","choices":[],"created":1749764735,"model_version":"3.3.0","created_at":"2025-06-12T21:45:35.565Z","usage":{"completion_tokens":49,"prompt_tokens":319,"total_tokens":368}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() @@ -2874,7 +2874,7 @@ void should_use_virtual_thread_for_interceptor() { data: {"id":"chatcmpl-cc34b5ea3120fa9e07b18c5125d66602","object":"chat.completion.chunk","model_id":"ibm/granite-4-h-small","model":"ibm/granite-4-h-small","choices":[],"created":1749764735,"model_version":"3.3.0","created_at":"2025-06-12T21:45:35.565Z","usage":{"completion_tokens":49,"prompt_tokens":319,"total_tokens":368}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -3322,7 +3322,7 @@ public void onPartialThinking(String partialThinking, PartialChatResponse partia @Test void should_not_invoke_onError_when_toolInterceptor_catches_exception() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .willReturn(aResponse() @@ -3384,7 +3384,7 @@ void should_convert_multiple_choices_to_assistant_messages() throws Exception { .withChunkedDribbleDelay(81, 5) .withBody(RESPONSE))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -3475,7 +3475,7 @@ void should_handle_multiple_choices_with_different_finish_reasons() throws Excep .withChunkedDribbleDelay(33, 5) .withBody(RESPONSE))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -3562,7 +3562,7 @@ void should_manage_multiple_choices_with_empty_argument_tools() throws Exception .withChunkedDribbleDelay(6, 5) .withBody(RESPONSE))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -3594,7 +3594,7 @@ void should_manage_multiple_choices_with_tools() throws Exception { .withChunkedDribbleDelay(101, 5) .withBody(RESPONSE))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -3731,7 +3731,7 @@ void should_apply_tool_interceptor_to_multiple_choices_streaming() throws Except .withChunkedDribbleDelay(101, 5) .withBody(RESPONSE))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var interceptorCallCount = new AtomicInteger(0); var completedToolCalls = new ConcurrentHashMap(); @@ -3806,7 +3806,7 @@ public void onCompleteToolCall(CompletedToolCall completedToolCall) { @Test void should_handle_tool_calls_using_tool_registry() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("token")); wireMock.stubFor(post("/ml/v1/text/chat_stream?version=%s".formatted(API_VERSION)) .withHeader("Authorization", equalTo("Bearer token")) .willReturn(aResponse() diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceThinkingTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceThinkingTest.java index 00be5f7a..221dd656 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceThinkingTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/chat/ChatServiceThinkingTest.java @@ -425,7 +425,7 @@ void should_stream_partial_thinking_and_content_correctly() throws Exception { .withChunkedDribbleDelay(159, 200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -567,7 +567,7 @@ void should_handle_streaming_without_thinking_result() throws Exception { data: {"id":"chatcmpl-5d8c131decbb6978cba5df10267aa3ff","object":"chat.completion.chunk","model_id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","model":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8","choices":[],"created":1749736055,"model_version":"4.0.0","created_at":"2025-06-12T13:47:35.564Z","usage":{"completion_tokens":3,"prompt_tokens":38,"total_tokens":41}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -674,7 +674,7 @@ void should_extract_thinking_without_configuration() throws Exception { .withStatus(200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -760,7 +760,7 @@ void should_handle_thinking_efforts() throws Exception { .withStatus(200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -904,7 +904,7 @@ void should_not_extract_thinking_when_disabled() throws Exception { data: {"id":"chatcmpl-3956a62a1e0446b0a1f4115152baf489","object":"chat.completion.chunk","model_id":"openai/gpt-oss-120b-curated","model":"openai/gpt-oss-120b-curated","choices":[],"created":1760258822,"created_at":"2025-10-12T08:47:02.194Z","usage":{"completion_tokens":27,"prompt_tokens":85,"total_tokens":112}} """))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) @@ -988,7 +988,7 @@ void should_extract_thinking_with_builder() throws Exception { .withStatus(200) .withBody(BODY))); - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-token")); var chatService = ChatService.builder() .authenticator(mockAuthenticator) diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DIAMRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DIAMRestClient.java index c989c51c..cdb138a4 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DIAMRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DIAMRestClient.java @@ -21,8 +21,8 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncToken'"); + public CompletableFuture tokenAsync(TokenRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'tokenAsync'"); } public static final class CustomCP4DIAMRestClientBuilderFactory implements CP4DRestClient.CP4DIAMRestClientBuilderFactory { diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DLegacyRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DLegacyRestClient.java index 6ff7d4e2..664c1343 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DLegacyRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DLegacyRestClient.java @@ -21,8 +21,8 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncToken'"); + public CompletableFuture tokenAsync(TokenRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'tokenAsync'"); } public static final class CustomCP4DLegacyRestClientBuilderFactory implements CP4DRestClient.CP4DLegacyRestClientBuilderFactory { diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DZenRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DZenRestClient.java index 7deccb4d..48d555e6 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DZenRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomCP4DZenRestClient.java @@ -21,8 +21,8 @@ public TokenResponse token(TokenRequest request) { } @Override - public CompletableFuture asyncToken(TokenRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncToken'"); + public CompletableFuture tokenAsync(TokenRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'tokenAsync'"); } public static final class CustomCP4DZenRestClientBuilderFactory implements CP4DRestClient.CP4DZenRestClientBuilderFactory { diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomIBMCloudRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomIBMCloudRestClient.java index 885a1850..7b9f8b78 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomIBMCloudRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomIBMCloudRestClient.java @@ -20,8 +20,8 @@ public TokenResponse token(String apiKey, String grantType) { } @Override - public CompletableFuture asyncToken(String apiKey, String grantType) { - throw new UnsupportedOperationException("Unimplemented method 'asyncToken'"); + public CompletableFuture tokenAsync(String apiKey, String grantType) { + throw new UnsupportedOperationException("Unimplemented method 'tokenAsync'"); } public static final class CustomIBMCloudRestClientBuilderFactory implements IBMCloudRestClientBuilderFactory { diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextClassificationRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextClassificationRestClient.java index dd1554c0..cb45e03d 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextClassificationRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextClassificationRestClient.java @@ -37,8 +37,8 @@ public boolean deleteFile(DeleteFileRequest request) { } @Override - public CompletableFuture asyncDeleteFile(DeleteFileRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncDeleteFile'"); + public CompletableFuture deleteFileAsync(DeleteFileRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'deleteFileAsync'"); } @Override diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextExtractionRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextExtractionRestClient.java index 3fb0d244..78488701 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextExtractionRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTextExtractionRestClient.java @@ -23,8 +23,8 @@ public boolean deleteFile(DeleteFileRequest request) { } @Override - public CompletableFuture asyncDeleteFile(DeleteFileRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncDeleteFile'"); + public CompletableFuture deleteFileAsync(DeleteFileRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'deleteFileAsync'"); } @Override diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTokenizationRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTokenizationRestClient.java index 3f14cb5b..a1468617 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTokenizationRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomTokenizationRestClient.java @@ -21,8 +21,8 @@ public TokenizationResponse tokenize(String transactionId, TokenizationRequest r } @Override - public CompletableFuture asyncTokenize(String transactionId, TokenizationRequest request) { - throw new UnsupportedOperationException("Unimplemented method 'asyncTokenize'"); + public CompletableFuture tokenizeAsync(String transactionId, TokenizationRequest request) { + throw new UnsupportedOperationException("Unimplemented method 'tokenizeAsync'"); } public static final class CustomTokenizationRestClientBuilderFactory implements TokenizationRestClientBuilderFactory { diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/TokenizationServiceIT.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/TokenizationServiceIT.java index 4379acc2..df19898f 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/TokenizationServiceIT.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/it/TokenizationServiceIT.java @@ -56,7 +56,7 @@ void should_return_tokens_synchronously_when_text_is_provided() { @Test void should_return_tokens_asynchronously_when_text_is_provided() throws Exception { - var response = tokenizationService.asyncTokenize( + var response = tokenizationService.tokenizeAsync( "Tokenize this!", TokenizationParameters.builder() .returnTokens(true) diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java index 1bf41bb9..67dc431b 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textclassification/TextClassificationTest.java @@ -596,7 +596,7 @@ void should_upload_and_start_classification_using_input_stream() throws Exceptio @Test void should_remove_uploaded_file_after_classification() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); mockServers(true); var file = new File(ClassLoader.getSystemResource("test.pdf").toURI()); @@ -689,7 +689,7 @@ void should_handle_long_running_classification_with_retries() throws Exception { @Test void should_throw_exception_when_classification_timeout_exceeded() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); var JOB = Files.readString(Path.of(ClassLoader.getSystemResource("classification_job.json").toURI())); watsonxServer.stubFor(post("/ml/v1/text/classifications?version=%s".formatted(API_VERSION)) @@ -756,7 +756,7 @@ void should_throw_exception_when_classification_timeout_exceeded() throws Except @Test void should_throw_exception_when_classification_job_fails() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); var JOB = Files.readString(Path.of(ClassLoader.getSystemResource("classification_job.json").toURI())); var JOB_ERROR = Files.readString(Path.of(ClassLoader.getSystemResource("classification_job_error.json").toURI())); var file = new File(ClassLoader.getSystemResource("test.pdf").toURI()); @@ -903,7 +903,7 @@ void should_throw_exception_when_classification_event_not_found() { @MockitoSettings(strictness = Strictness.LENIENT) void should_delete_file() throws Exception { - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); cosServer.resetAll(); cosServer.stubFor(delete("/%s/%s".formatted("my-bucket", "test.pdf")) @@ -941,9 +941,9 @@ void should_delete_file() throws Exception { void should_delete_file_with_custom_api_key() throws Exception { var cosAuthenticator = mock(Authenticator.class); - when(cosAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("custom-token")); + when(cosAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("custom-token")); when(cosAuthenticator.scheme()).thenReturn("Bearer"); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("token")); cosServer.resetAll(); var classificationService = TextClassificationService.builder() diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java index 68ae31ac..6a9a0fe7 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/textprocessing/textextraction/TextExtractionTest.java @@ -1202,7 +1202,7 @@ void should_upload_and_start_extraction_using_input_stream() throws Exception { void should_remove_uploaded_and_output_files_after_extraction() throws Exception { when(mockAuthenticator.token()).thenReturn("my-super-token"); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("my-super-token")); var outputFileName = "myNewOutput.json"; var file = new File(TextExtractionTest.class.getClassLoader().getResource(FILE_NAME).toURI()); @@ -1560,7 +1560,7 @@ void should_throw_exception_when_extraction_timeout_exceeded() { var outputFileName = FILE_NAME.replace(".pdf", ".md"); when(mockAuthenticator.token()).thenReturn("my-super-token"); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("my-super-token")); watsonxServer.stubFor(post("/ml/v1/text/extractions?version=%s".formatted(API_VERSION)) .inScenario("long_response") @@ -1634,7 +1634,7 @@ void should_throw_exception_when_extraction_timeout_exceeded() { void should_throw_exception_when_extraction_job_fails() throws Exception { when(mockAuthenticator.token()).thenReturn("my-super-token"); - when(mockAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("my-super-token")); var outputFileName = FILE_NAME.replace(".pdf", ".md"); var file = new File(TextExtractionTest.class.getClassLoader().getResource(FILE_NAME).toURI()); @@ -1995,7 +1995,7 @@ void should_override_connection_and_bucket_parameters() throws Exception { @Test void should_delete_file_from_cos_with_retry_on_failure() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); cosServer.resetAll(); @@ -2026,14 +2026,14 @@ void should_delete_file_from_cos_with_retry_on_failure() { assertTrue(assertDoesNotThrow(() -> textExtractionService.deleteFile(BUCKET_NAME, FILE_NAME))); cosServer.verify(2, deleteRequestedFor(urlEqualTo("/%s/%s".formatted(BUCKET_NAME, FILE_NAME)))); - verify(mockAuthenticator, times(2)).asyncToken(); + verify(mockAuthenticator, times(2)).tokenAsync(); } @Test void should_delete_file_with_custom_api_key() { var cosAuthenticator = mock(Authenticator.class); - when(cosAuthenticator.asyncToken()).thenReturn(CompletableFuture.completedFuture("custom-token")); + when(cosAuthenticator.tokenAsync()).thenReturn(CompletableFuture.completedFuture("custom-token")); when(cosAuthenticator.scheme()).thenReturn("Bearer"); cosServer.resetAll(); @@ -2060,7 +2060,7 @@ void should_delete_file_with_custom_api_key() { @Test void should_throw_exception_when_deleting_non_existent_file() { - when(mockAuthenticator.asyncToken()).thenReturn(completedFuture("my-super-token")); + when(mockAuthenticator.tokenAsync()).thenReturn(completedFuture("my-super-token")); cosServer.stubFor(delete("/%s/%s".formatted(BUCKET_NAME, FILE_NAME)) .withHeader("Authorization", equalTo("Bearer my-super-token")) @@ -2079,7 +2079,7 @@ void should_throw_exception_when_deleting_non_existent_file() { assertThrows(FileNotFoundException.class, () -> textExtractionService.deleteFile(BUCKET_NAME, FILE_NAME)); cosServer.verify(1, deleteRequestedFor(urlEqualTo("/%s/%s".formatted(BUCKET_NAME, FILE_NAME)))); - verify(mockAuthenticator, times(1)).asyncToken(); + verify(mockAuthenticator, times(1)).tokenAsync(); } @Test From eed6ed9f6b86a562ec8384b363e8fe662f75a3ff Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Mon, 6 Apr 2026 00:08:53 +0200 Subject: [PATCH 5/6] Convert RerankRequest to builder class Changes: - Converted RerankRequest from record to builder class for extensibility - Created RerankPayload record for internal API requests --- .../ai/embedding/EmbeddingRequest.java | 9 +- .../watsonx/ai/rerank/DefaultRestClient.java | 2 +- .../watsonx/ai/rerank/RerankParameters.java | 4 +- .../ibm/watsonx/ai/rerank/RerankPayload.java | 53 +++++++ .../ibm/watsonx/ai/rerank/RerankRequest.java | 145 ++++++++++++++---- .../watsonx/ai/rerank/RerankRestClient.java | 2 +- .../ibm/watsonx/ai/rerank/RerankService.java | 31 +++- .../client/impl/CustomRerankRestClient.java | 4 +- 8 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankPayload.java diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java index 5df137e9..dfa4c67b 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/embedding/EmbeddingRequest.java @@ -4,8 +4,6 @@ */ package com.ibm.watsonx.ai.embedding; -import static java.util.Objects.requireNonNullElse; -import java.util.ArrayList; import java.util.List; /** @@ -26,8 +24,8 @@ * }

*/ public final class EmbeddingRequest { - private List inputs; - private EmbeddingParameters parameters; + private final List inputs; + private final EmbeddingParameters parameters; private EmbeddingRequest(Builder builder) { inputs = builder.inputs; @@ -94,8 +92,7 @@ private Builder() {} * @param inputs the list of input texts to embed */ public Builder inputs(List inputs) { - this.inputs = requireNonNullElse(this.inputs, new ArrayList<>()); - this.inputs.addAll(inputs); + this.inputs = inputs; return this; } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/DefaultRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/DefaultRestClient.java index 49432ade..e4c0b0a0 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/DefaultRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/DefaultRestClient.java @@ -31,7 +31,7 @@ final class DefaultRestClient extends RerankRestClient { } @Override - public RerankResponse rerank(String transactionId, RerankRequest request) { + public RerankResponse rerank(String transactionId, RerankPayload request) { var httpRequest = HttpRequest .newBuilder(URI.create(baseUrl + "/ml/v1/text/rerank?version=%s".formatted(version))) diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankParameters.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankParameters.java index 61f8ed9d..235f0eb8 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankParameters.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankParameters.java @@ -6,8 +6,8 @@ import static java.util.Objects.nonNull; import com.ibm.watsonx.ai.WatsonxParameters.WatsonxCryptoParameters; -import com.ibm.watsonx.ai.rerank.RerankRequest.Parameters; -import com.ibm.watsonx.ai.rerank.RerankRequest.ReturnOptions; +import com.ibm.watsonx.ai.rerank.RerankPayload.Parameters; +import com.ibm.watsonx.ai.rerank.RerankPayload.ReturnOptions; /** * Represents a set of parameters used to control the behavior of a rerank operation. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankPayload.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankPayload.java new file mode 100644 index 00000000..0174593a --- /dev/null +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankPayload.java @@ -0,0 +1,53 @@ +/* + * Copyright 2025 IBM Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.watsonx.ai.rerank; + +import java.util.List; +import com.ibm.watsonx.ai.Crypto; + +/** + * Represents a request to perform text reranking using a specified model. + * + * @param modelId The identifier of the reranking model to use. + * @param inputs The list of input texts to be reranked. + * @param query The query text to rank the inputs against. + * @param spaceId The deployment space identifier. + * @param projectId The project identifier. + * @param parameters Additional parameters for the reranking operation. + * @param crypto Encryption configuration for sensitive data. + */ +public record RerankPayload( + String modelId, + List inputs, + String query, + String spaceId, + String projectId, + Parameters parameters, + Crypto crypto) { + + /** + * Represents a single input text to be reranked. + * + * @param text The input text content. + */ + public record RerankInput(String text) {} + + /** + * Additional parameters for controlling the reranking behavior. + * + * @param truncateInputTokens Maximum number of tokens per input. + * @param returnOptions Options for controlling what data is returned. + */ + public record Parameters(Integer truncateInputTokens, ReturnOptions returnOptions) {} + + /** + * Options for controlling what data is included in the response. + * + * @param topN Number of top results to return. + * @param inputs Whether to include input texts in the response. + * @param query Whether to include the query in the response. + */ + public record ReturnOptions(Integer topN, Boolean inputs, Boolean query) {} +} diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRequest.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRequest.java index cdf0a08a..af975ca0 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRequest.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRequest.java @@ -5,49 +5,136 @@ package com.ibm.watsonx.ai.rerank; import java.util.List; -import com.ibm.watsonx.ai.Crypto; /** - * Represents a request to perform text reranking using a specified model. + * Represents a rerank request. + *

+ * Example usage: * - * @param modelId The identifier of the reranking model to use. - * @param inputs The list of input texts to be reranked. - * @param query The query text to rank the inputs against. - * @param spaceId The deployment space identifier. - * @param projectId The project identifier. - * @param parameters Additional parameters for the reranking operation. - * @param crypto Encryption configuration for sensitive data. + *

{@code
+ * var parameters = RerankParameters.builder()
+ *     .topN(3)
+ *     .returnDocuments(true)
+ *     .build();
+ *
+ * RerankRequest request = RerankRequest.builder()
+ *     .query("What is watsonx.ai?")
+ *     .inputs(List.of("Document 1", "Document 2", "Document 3"))
+ *     .parameters(parameters)
+ *     .build();
+ * }
*/ -public record RerankRequest( - String modelId, - List inputs, - String query, - String spaceId, - String projectId, - Parameters parameters, - Crypto crypto) { +public final class RerankRequest { + private final String query; + private final List inputs; + private final RerankParameters parameters; + + private RerankRequest(Builder builder) { + query = builder.query; + inputs = builder.inputs; + parameters = builder.parameters; + } + + /** + * Returns the query text used for reranking. + * + * @return the query text, or {@code null} if not set + */ + public String query() { + return query; + } /** - * Represents a single input text to be reranked. + * Returns the input texts to be reranked. * - * @param text The input text content. + * @return the list of input texts, or {@code null} if not set */ - public record RerankInput(String text) {} + public List inputs() { + return inputs; + } /** - * Additional parameters for controlling the reranking behavior. + * Returns the rerank parameters. * - * @param truncateInputTokens Maximum number of tokens per input. - * @param returnOptions Options for controlling what data is returned. + * @return the rerank parameters, or {@code null} if not set */ - public record Parameters(Integer truncateInputTokens, ReturnOptions returnOptions) {} + public RerankParameters parameters() { + return parameters; + } /** - * Options for controlling what data is included in the response. + * Returns a new {@link Builder} instance. + *

+ * Example usage: + * + *

{@code
+     * var parameters = RerankParameters.builder()
+     *     .topN(3)
+     *     .returnDocuments(true)
+     *     .build();
+     *
+     * RerankRequest request = RerankRequest.builder()
+     *     .query("What is watsonx.ai?")
+     *     .inputs(List.of("Document 1", "Document 2", "Document 3"))
+     *     .parameters(parameters)
+     *     .build();
+     * }
* - * @param topN Number of top results to return. - * @param inputs Whether to include input texts in the response. - * @param query Whether to include the query in the response. + * @return {@link Builder} instance */ - public record ReturnOptions(Integer topN, Boolean inputs, Boolean query) {} + public static Builder builder() { + return new Builder(); + } + + /** + * Builder class for constructing {@link RerankRequest} instances. + */ + public final static class Builder { + private String query; + private List inputs; + private RerankParameters parameters; + + private Builder() {} + + /** + * Sets the query text used for reranking. + * + * @param query the query text + */ + public Builder query(String query) { + this.query = query; + return this; + } + + /** + * Sets the input texts for the request, replacing any existing inputs. + *

+ * This method completely overwrites the current list of inputs with the provided values. + * + * @param inputs the list of input texts to rerank + */ + public Builder inputs(List inputs) { + this.inputs = inputs; + return this; + } + + /** + * Sets the parameters controlling the rerank model behavior. + * + * @param parameters an {@link RerankParameters} instance + */ + public Builder parameters(RerankParameters parameters) { + this.parameters = parameters; + return this; + } + + /** + * Builds a {@link RerankRequest} instance using the configured parameters. + * + * @return a new instance of {@link RerankRequest} + */ + public RerankRequest build() { + return new RerankRequest(this); + } + } } diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRestClient.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRestClient.java index 97cd7c02..0f2e0bd6 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRestClient.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankRestClient.java @@ -24,7 +24,7 @@ protected RerankRestClient(Builder builder) { * @param request the rerank request payload * @return A {@link RerankResponse} containing the reranked results. */ - public abstract RerankResponse rerank(String transactionId, RerankRequest request); + public abstract RerankResponse rerank(String transactionId, RerankPayload request); /** * Creates a new {@link Builder} using the first available {@link RerankRestClientBuilderFactory} discovered via {@link ServiceLoader}. diff --git a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankService.java b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankService.java index 297bb04f..60038f9a 100644 --- a/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankService.java +++ b/modules/watsonx-ai/src/main/java/com/ibm/watsonx/ai/rerank/RerankService.java @@ -11,8 +11,8 @@ import com.ibm.watsonx.ai.Crypto; import com.ibm.watsonx.ai.WatsonxService.ModelService; import com.ibm.watsonx.ai.core.auth.Authenticator; -import com.ibm.watsonx.ai.rerank.RerankRequest.Parameters; -import com.ibm.watsonx.ai.rerank.RerankRequest.RerankInput; +import com.ibm.watsonx.ai.rerank.RerankPayload.Parameters; +import com.ibm.watsonx.ai.rerank.RerankPayload.RerankInput; /** @@ -79,10 +79,27 @@ public RerankResponse rerank(String query, List inputs) { * @return The {@link RerankResponse} containing the reranked results. */ public RerankResponse rerank(String query, List inputs, RerankParameters parameters) { + return rerank( + RerankRequest.builder() + .query(query) + .inputs(inputs) + .parameters(parameters) + .build()); + } + + /** + * Performs a reranking operation using the specified request. + * + * @param request The rerank request. + * @return The {@link RerankResponse} containing the reranked results. + */ + public RerankResponse rerank(RerankRequest request) { - requireNonNull(query, "Query cannot be null"); - requireNonNull(inputs, "Inputs cannot be null"); + requireNonNull(request, "Request cannot be null"); + requireNonNull(request.query(), "Query cannot be null"); + requireNonNull(request.inputs(), "Inputs cannot be null"); + RerankParameters parameters = request.parameters(); ProjectSpace projectSpace = resolveProjectSpace(parameters); String projectId = projectSpace.projectId(); String spaceId = projectSpace.spaceId(); @@ -98,10 +115,10 @@ public RerankResponse rerank(String query, List inputs, RerankParameters crypto = nonNull(parameters.crypto()) ? new Crypto(parameters.crypto()) : null; } - var rerankRequest = new RerankRequest( + var rerankRequest = new RerankPayload( modelId, - inputs.stream().map(RerankInput::new).toList(), - query, + request.inputs().stream().map(RerankInput::new).toList(), + request.query(), spaceId, projectId, requestParameters, diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomRerankRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomRerankRestClient.java index 0fbbd7ab..9608e490 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomRerankRestClient.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomRerankRestClient.java @@ -4,7 +4,7 @@ */ package com.ibm.watsonx.ai.client.impl; -import com.ibm.watsonx.ai.rerank.RerankRequest; +import com.ibm.watsonx.ai.rerank.RerankPayload; import com.ibm.watsonx.ai.rerank.RerankResponse; import com.ibm.watsonx.ai.rerank.RerankRestClient; @@ -15,7 +15,7 @@ public class CustomRerankRestClient extends RerankRestClient { } @Override - public RerankResponse rerank(String transactionId, RerankRequest request) { + public RerankResponse rerank(String transactionId, RerankPayload request) { throw new UnsupportedOperationException("Unimplemented method 'rerank'"); } From 0e0ff1e29eee4ec9ef227ce7a0ed680ec97591a4 Mon Sep 17 00:00:00 2001 From: andreadimaio Date: Mon, 6 Apr 2026 00:46:38 +0200 Subject: [PATCH 6/6] Add ServiceLoader JUnit tests --- .../com/ibm/watsonx/ai/BatchServiceTest.java | 1 + .../ibm/watsonx/ai/CustomHttpClientTest.java | 9 +++ .../ai/client/CustomRestClientTest.java | 43 ++++++++++--- .../ai/client/impl/CustomBatchRestClient.java | 54 ++++++++++++++++ .../ai/client/impl/CustomFileRestClient.java | 61 +++++++++++++++++++ .../watsonx/ai/utils/ServiceLoaderUtils.java | 8 +++ 6 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomBatchRestClient.java create mode 100644 modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomFileRestClient.java diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java index ee1d202d..9604c94c 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/BatchServiceTest.java @@ -1694,6 +1694,7 @@ void should_batch_chat_responses() { .toList(); var results = batchService.submitChatRequestsAndFetch(chatRequests); + assertDoesNotThrow(() -> Thread.sleep(200)); // Wait for async files deletion to complete assertEquals(3, results.size()); assertEquals("0", results.get(0).customId()); diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java index d42a5dd4..8645a9d6 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/CustomHttpClientTest.java @@ -649,6 +649,11 @@ void should_use_custom_http_client_for_file_service() throws Exception { assertEquals(customClient, getFieldValue(syncHttpClient, "delegate")); assertNotEquals(HttpClientProvider.httpClient(true), getFieldValue(syncHttpClient, "delegate")); assertNotEquals(HttpClientProvider.httpClient(false), getFieldValue(syncHttpClient, "delegate")); + + Object asyncHttpClient = getFieldValue(restclient, "asyncHttpClient"); + assertEquals(customClient, getFieldValue(asyncHttpClient, "delegate")); + assertNotEquals(HttpClientProvider.httpClient(true), getFieldValue(asyncHttpClient, "delegate")); + assertNotEquals(HttpClientProvider.httpClient(false), getFieldValue(asyncHttpClient, "delegate")); } @Test @@ -674,6 +679,10 @@ void should_use_default_http_client_for_file_service() throws Exception { assertNotEquals(customClient, getFieldValue(syncHttpClient, "delegate")); assertEquals(HttpClientProvider.httpClient(verifySsl), getFieldValue(syncHttpClient, "delegate")); + Object asyncHttpClient = getFieldValue(restclient, "asyncHttpClient"); + assertNotEquals(customClient, getFieldValue(asyncHttpClient, "delegate")); + assertEquals(HttpClientProvider.httpClient(verifySsl), getFieldValue(asyncHttpClient, "delegate")); + } catch (Exception e) { fail(e); } diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/CustomRestClientTest.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/CustomRestClientTest.java index 87064dd8..aa839f72 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/CustomRestClientTest.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/CustomRestClientTest.java @@ -8,14 +8,16 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.ibm.watsonx.ai.batch.BatchService; import com.ibm.watsonx.ai.chat.ChatService; +import com.ibm.watsonx.ai.client.impl.CustomBatchRestClient; import com.ibm.watsonx.ai.client.impl.CustomCP4DIAMRestClient; import com.ibm.watsonx.ai.client.impl.CustomCP4DLegacyRestClient; import com.ibm.watsonx.ai.client.impl.CustomCP4DZenRestClient; import com.ibm.watsonx.ai.client.impl.CustomChatRestClient; import com.ibm.watsonx.ai.client.impl.CustomDeploymentRestClient; -import com.ibm.watsonx.ai.client.impl.CustomDetectionRestClient; import com.ibm.watsonx.ai.client.impl.CustomEmbeddingRestClient; +import com.ibm.watsonx.ai.client.impl.CustomFileRestClient; import com.ibm.watsonx.ai.client.impl.CustomFoundationModelRestClient; import com.ibm.watsonx.ai.client.impl.CustomIBMCloudRestClient; import com.ibm.watsonx.ai.client.impl.CustomRerankRestClient; @@ -30,8 +32,8 @@ import com.ibm.watsonx.ai.core.auth.cp4d.CP4DAuthenticator; import com.ibm.watsonx.ai.core.auth.ibmcloud.IBMCloudAuthenticator; import com.ibm.watsonx.ai.deployment.DeploymentService; -import com.ibm.watsonx.ai.detection.DetectionService; import com.ibm.watsonx.ai.embedding.EmbeddingService; +import com.ibm.watsonx.ai.file.FileService; import com.ibm.watsonx.ai.foundationmodel.FoundationModelService; import com.ibm.watsonx.ai.rerank.RerankService; import com.ibm.watsonx.ai.textgeneration.TextGenerationService; @@ -317,19 +319,44 @@ public void should_use_custom_rest_client_when_building_tool_service() throws Ex } @Test - // com.ibm.watsonx.ai.detection.DetectionRestClient$DetectionRestClientBuilderFactory - public void should_use_custom_rest_client_when_building_detection_service() throws Exception { + // com.ibm.watsonx.ai.file.FileRestClient$FileRestClientBuilderFactory + public void should_use_custom_rest_client_when_building_file_service() throws Exception { - DetectionService detectionService = DetectionService.builder() + FileService fileService = FileService.builder() .apiKey("test") .baseUrl("http://localhost") .projectId("project-id") .build(); - Class clazz = DetectionService.class; + Class clazz = FileService.class; var clientField = clazz.getDeclaredField("client"); clientField.setAccessible(true); - var client = clientField.get(detectionService); - assertTrue(client instanceof CustomDetectionRestClient); + var client = clientField.get(fileService); + assertTrue(client instanceof CustomFileRestClient); + } + + @Test + // com.ibm.watsonx.ai.batch.BatchRestClient$BatchRestClientBuilderFactory + public void should_use_custom_rest_client_when_building_batch_service() throws Exception { + + FileService fileService = FileService.builder() + .apiKey("test") + .baseUrl("http://localhost") + .projectId("project-id") + .build(); + + BatchService batchService = BatchService.builder() + .apiKey("test") + .baseUrl("http://localhost") + .projectId("project-id") + .endpoint("/v1/chat/completions") + .fileService(fileService) + .build(); + + Class clazz = BatchService.class; + var clientField = clazz.getDeclaredField("client"); + clientField.setAccessible(true); + var client = clientField.get(batchService); + assertTrue(client instanceof CustomBatchRestClient); } } diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomBatchRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomBatchRestClient.java new file mode 100644 index 00000000..6f40063e --- /dev/null +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomBatchRestClient.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 IBM Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.watsonx.ai.client.impl; + +import com.ibm.watsonx.ai.batch.BatchCancelRequest; +import com.ibm.watsonx.ai.batch.BatchCreateRequest; +import com.ibm.watsonx.ai.batch.BatchData; +import com.ibm.watsonx.ai.batch.BatchListRequest; +import com.ibm.watsonx.ai.batch.BatchListResponse; +import com.ibm.watsonx.ai.batch.BatchRestClient; +import com.ibm.watsonx.ai.batch.BatchRetrieveRequest; + +public class CustomBatchRestClient extends BatchRestClient { + + CustomBatchRestClient(Builder builder) { + super(builder); + } + + @Override + public BatchData submit(BatchCreateRequest batchCreateRequest) { + throw new UnsupportedOperationException("Unimplemented method 'submit'"); + } + + @Override + public BatchListResponse list(BatchListRequest batchListRequest) { + throw new UnsupportedOperationException("Unimplemented method 'list'"); + } + + @Override + public BatchData retrieve(BatchRetrieveRequest batchRetrieveRequest) { + throw new UnsupportedOperationException("Unimplemented method 'retrieve'"); + } + + @Override + public BatchData cancel(BatchCancelRequest batchCancelRequest) { + throw new UnsupportedOperationException("Unimplemented method 'cancel'"); + } + + public static final class CustomBatchRestClientBuilderFactory implements BatchRestClientBuilderFactory { + @Override + public Builder get() { + return new CustomBatchRestClient.Builder(); + } + } + + static final class Builder extends BatchRestClient.Builder { + @Override + public CustomBatchRestClient build() { + return new CustomBatchRestClient(this); + } + } +} diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomFileRestClient.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomFileRestClient.java new file mode 100644 index 00000000..0d0f8c80 --- /dev/null +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/client/impl/CustomFileRestClient.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 IBM Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.watsonx.ai.client.impl; + +import java.util.concurrent.CompletableFuture; +import com.ibm.watsonx.ai.file.FileData; +import com.ibm.watsonx.ai.file.FileDeleteRequest; +import com.ibm.watsonx.ai.file.FileDeleteResponse; +import com.ibm.watsonx.ai.file.FileListRequest; +import com.ibm.watsonx.ai.file.FileListResponse; +import com.ibm.watsonx.ai.file.FileRestClient; +import com.ibm.watsonx.ai.file.FileRetrieveRequest; +import com.ibm.watsonx.ai.file.FileUploadRequest; + +public class CustomFileRestClient extends FileRestClient { + + CustomFileRestClient(Builder builder) { + super(builder); + } + + @Override + public FileData upload(FileUploadRequest fileUploadRequest) { + throw new UnsupportedOperationException("Unimplemented method 'upload'"); + } + + @Override + public FileListResponse list(FileListRequest fileListRequest) { + throw new UnsupportedOperationException("Unimplemented method 'list'"); + } + + @Override + public String retrieve(FileRetrieveRequest fileRetrieveRequest) { + throw new UnsupportedOperationException("Unimplemented method 'retrieve'"); + } + + @Override + public FileDeleteResponse delete(FileDeleteRequest fileDeleteRequest) { + throw new UnsupportedOperationException("Unimplemented method 'delete'"); + } + + @Override + public CompletableFuture deleteAsync(FileDeleteRequest fileDeleteRequest) { + throw new UnsupportedOperationException("Unimplemented method 'deleteAsync'"); + } + + public static final class CustomFileRestClientBuilderFactory implements FileRestClientBuilderFactory { + @Override + public Builder get() { + return new CustomFileRestClient.Builder(); + } + } + + static final class Builder extends FileRestClient.Builder { + @Override + public CustomFileRestClient build() { + return new CustomFileRestClient(this); + } + } +} diff --git a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/utils/ServiceLoaderUtils.java b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/utils/ServiceLoaderUtils.java index 952f96b3..28b3e3d9 100644 --- a/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/utils/ServiceLoaderUtils.java +++ b/modules/watsonx-ai/src/test/java/com/ibm/watsonx/ai/utils/ServiceLoaderUtils.java @@ -86,6 +86,14 @@ public static void setupServiceLoader() throws Exception { "com.ibm.watsonx.ai.detection.DetectionRestClient$DetectionRestClientBuilderFactory", "com.ibm.watsonx.ai.client.impl.CustomDetectionRestClient$CustomDetectionRestClientBuilderFactory"); + createServiceFile(metaInfServices, + "com.ibm.watsonx.ai.file.FileRestClient$FileRestClientBuilderFactory", + "com.ibm.watsonx.ai.client.impl.CustomFileRestClient$CustomFileRestClientBuilderFactory"); + + createServiceFile(metaInfServices, + "com.ibm.watsonx.ai.batch.BatchRestClient$BatchRestClientBuilderFactory", + "com.ibm.watsonx.ai.client.impl.CustomBatchRestClient$CustomBatchRestClientBuilderFactory"); + URLClassLoader tempClassLoader = new URLClassLoader( new URL[] { tempDir.toUri().toURL() }, originalClassLoader