Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public String getTransactionId() {
*/
@SuppressWarnings("unchecked")
public static abstract class Builder<T extends Builder<T>> {
private String projectId;
private String spaceId;
private String transactionId;
protected String projectId;
protected String spaceId;
protected String transactionId;

/**
* Sets the project id.
Expand Down Expand Up @@ -114,7 +114,7 @@ public String getModelId() {
*/
@SuppressWarnings("unchecked")
public static abstract class Builder<T extends Builder<T>> extends WatsonxParameters.Builder<T> {
private String modelId;
protected String modelId;

/**
* Sets the model id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package com.ibm.watsonx.ai;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
import java.net.URI;
Expand All @@ -13,6 +14,7 @@
import com.ibm.watsonx.ai.core.auth.AuthenticationProvider;
import com.ibm.watsonx.ai.core.auth.iam.IAMAuthenticator;
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.foundationmodel.FoundationModelService;
import com.ibm.watsonx.ai.rerank.RerankService;
Expand All @@ -38,6 +40,7 @@
* @see TimeSeriesService
* @see FoundationModelService
* @see ToolService
* @see DetectionService
*/
public abstract class WatsonxService {

Expand Down Expand Up @@ -176,6 +179,9 @@ public AuthenticationProvider getAuthenticationProvider() {
* Abstract base class for watsonx services that require a project or space context.
*/
public static abstract class ProjectService extends WatsonxService {

protected record ProjectSpace(String projectId, String spaceId) {}

protected final String projectId;
protected final String spaceId;

Expand All @@ -188,6 +194,12 @@ protected ProjectService(Builder<?> builder) {
throw new NullPointerException("Either projectId or spaceId must be provided");
}

protected ProjectSpace resolveProjectSpace(WatsonxParameters parameters) {
return nonNull(parameters.getProjectId()) || nonNull(parameters.getSpaceId())
? new ProjectSpace(parameters.getProjectId(), parameters.getSpaceId())
: new ProjectSpace(projectId, spaceId);
}

@SuppressWarnings("unchecked")
protected static abstract class Builder<T extends Builder<T>> extends WatsonxService.Builder<T> {
private String projectId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

import java.util.Map;

/**
* Represents a generic detection request.
*/
abstract class BaseDetectionRequest {
private final Map<String, Map<String, Object>> detectors;
private final String projectId;
private final String spaceId;

protected BaseDetectionRequest(Map<String, Map<String, Object>> detectors, String projectId, String spaceId) {
this.detectors = detectors;
this.projectId = projectId;
this.spaceId = spaceId;
}

public Map<String, Map<String, Object>> getDetectors() {
return detectors;
}

public String getProjectId() {
return projectId;
}

public String getSpaceId() {
return spaceId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

/**
* Represents the response for a generic detection request.
*/
abstract class BaseDetectionResponse {
protected String text;
protected String detectionType;
protected String detection;
protected float score;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public String getDetectionType() {
return detectionType;
}

public void setDetectionType(String detectionType) {
this.detectionType = detectionType;
}

public String getDetection() {
return detection;
}

public void setDetection(String detection) {
this.detection = detection;
}

public float getScore() {
return score;
}

public void setScore(float score) {
this.score = score;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

import static com.ibm.watsonx.ai.core.Json.fromJson;
import static com.ibm.watsonx.ai.core.Json.toJson;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import com.ibm.watsonx.ai.core.factory.HttpClientFactory;
import com.ibm.watsonx.ai.core.http.SyncHttpClient;
import com.ibm.watsonx.ai.core.http.interceptors.LoggerInterceptor.LogMode;
import com.ibm.watsonx.ai.core.spi.json.TypeToken;

/**
* Default implementation of the {@link DetectionRestClient} abstract class.
*/
final class DefaultRestClient extends DetectionRestClient {
private final SyncHttpClient syncHttpClient;

DefaultRestClient(Builder builder) {
super(builder);
requireNonNull(authenticationProvider, "authenticationProvider is mandatory");
syncHttpClient = HttpClientFactory.createSync(authenticationProvider, LogMode.of(logRequests, logResponses));
}

@Override
public DetectionResponse<DetectionTextResponse> detect(String transactionId, TextDetectionContentDetectors request) {

var httpRequest = HttpRequest
.newBuilder(URI.create(baseUrl + "/ml/v1/text/detection?version=%s".formatted(version)))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(BodyPublishers.ofString(toJson(request)))
.timeout(timeout);

if (nonNull(transactionId))
httpRequest.header(TRANSACTION_ID_HEADER, transactionId);

try {

var httpReponse = syncHttpClient.send(httpRequest.build(), BodyHandlers.ofString());
return fromJson(httpReponse.body(), new TypeToken<DetectionResponse<DetectionTextResponse>>() {});

} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}

/**
* Returns a new {@link Builder} instance.
*/
static Builder builder() {
return new Builder();
}

/**
* Builder class for constructing {@link DefaultRestClient} instances with configurable parameters.
*/
public final static class Builder extends DetectionRestClient.Builder {

private Builder() {}

/**
* Builds a {@link DefaultRestClient} instance using the configured parameters.
*
* @return a new instance of {@link DefaultRestClient}
*/
public DefaultRestClient build() {
return new DefaultRestClient(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

import java.util.List;

/**
* Represents the response returned by the Text Detection API.
*/
public record DetectionResponse<T extends BaseDetectionResponse>(List<T> detections) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

import java.util.ServiceLoader;
import java.util.function.Supplier;
import com.ibm.watsonx.ai.WatsonxRestClient;

/**
* Abstraction of a REST client for interacting with the IBM watsonx.ai Text Detection APIs.
*/
public abstract class DetectionRestClient extends WatsonxRestClient {

protected DetectionRestClient(Builder builder) {
super(builder);
}

public abstract DetectionResponse<DetectionTextResponse> detect(String transactionId, TextDetectionContentDetectors request);

/**
* Creates a new {@link Builder} using the first available {@link DetectionRestClientBuilderFactory} discovered via {@link ServiceLoader}.
* <p>
* If no factory is found, falls back to the default {@link DefaultRestClient}.
*/
static DetectionRestClient.Builder builder() {
return ServiceLoader.load(DetectionRestClientBuilderFactory.class).findFirst()
.map(Supplier::get)
.orElse(DefaultRestClient.builder());
}

/**
* Builder abstract class for constructing {@link DetectionRestClient} instances with configurable parameters.
*/
public abstract static class Builder extends WatsonxRestClient.Builder<DetectionRestClient, Builder> {}

/**
* Service Provider Interface for supplying custom {@link Builder} implementations.
* <p>
* This allows frameworks to provide their own client implementations.
*/
public interface DetectionRestClientBuilderFactory extends Supplier<DetectionRestClient.Builder> {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright IBM Corp. 2025 - 2025
* SPDX-License-Identifier: Apache-2.0
*/
package com.ibm.watsonx.ai.detection;

import static java.util.Objects.requireNonNull;
import com.ibm.watsonx.ai.WatsonxService.ProjectService;
import com.ibm.watsonx.ai.core.auth.AuthenticationProvider;

/**
* Service class to interact with IBM watsonx.ai Text Detection APIs.
* <p>
* <b>Example usage:</b>
*
* <pre>{@code
* DetectionService detectionService = DetectionService.builder()
* .baseUrl("https://...") // or use CloudRegion
* .apiKey("my-api-key") // creates an IAM-based AuthenticationProvider
* .projectId("my-project-id")
* .build();
*
* DetectionResponse<DetectionTextResponse> response = detectionService.detect(
* DetectionTextRequest.builder()
* .input("...")
* .detectors(Pii.create(), Hap.builder().threshold(0.3).build())
* .build()
* );
* }</pre>
*
* To use a custom authentication mechanism, configure it explicitly with {@code authenticationProvider(AuthenticationProvider)}.
*
* @see AuthenticationProvider
*/
public final class DetectionService extends ProjectService {

private final DetectionRestClient client;

private DetectionService(Builder builder) {
super(builder);
requireNonNull(builder.getAuthenticationProvider(), "authenticationProvider cannot be null");
client = DetectionRestClient.builder()
.baseUrl(baseUrl)
.version(version)
.logRequests(logRequests)
.logResponses(logResponses)
.timeout(timeout)
.authenticationProvider(builder.getAuthenticationProvider())
.build();
}

public DetectionResponse<DetectionTextResponse> detect(DetectionTextRequest request) {
var projectSpace = resolveProjectSpace(request);
var transactionId = request.getTransactionId();
var textDetectionRequest =
new TextDetectionContentDetectors(request.getInput(), request.getDetectors(), projectSpace.projectId(), projectSpace.spaceId());
return client.detect(transactionId, textDetectionRequest);
}

/**
* Returns a new {@link Builder} instance.
* <p>
* <b>Example usage:</b>
*
* <pre>{@code
* DetectionService detectionService = DetectionService.builder()
* .baseUrl("https://...") // or use CloudRegion
* .apiKey("my-api-key") // creates an IAM-based AuthenticationProvider
* .projectId("my-project-id")
* .build();
*
* DetectionResponse<DetectionTextResponse> response = detectionService.detect(
* DetectionTextRequest.builder()
* .input("...")
* .detectors(Pii.create(), Hap.builder().threshold(0.3).build())
* .build()
* );
* }</pre>
*
* @return {@link Builder} instance.
*/
public static Builder builder() {
return new Builder();
}

/**
* Builder class for constructing {@link DetectionService} instances with configurable parameters.
*/
public final static class Builder extends ProjectService.Builder<Builder> {

private Builder() {}

/**
* Builds a {@link DetectionService} instance using the configured parameters.
*
* @return a new instance of {@link DetectionService}
*/
public DetectionService build() {
return new DetectionService(this);
}
}
}
Loading