diff --git a/core/src/main/java/ai/z/openapi/AbstractAiClient.java b/core/src/main/java/ai/z/openapi/AbstractAiClient.java index 9a819cd..e4ec63e 100644 --- a/core/src/main/java/ai/z/openapi/AbstractAiClient.java +++ b/core/src/main/java/ai/z/openapi/AbstractAiClient.java @@ -25,6 +25,8 @@ import ai.z.openapi.service.assistant.AssistantServiceImpl; import ai.z.openapi.service.voiceclone.VoiceCloneService; import ai.z.openapi.service.voiceclone.VoiceCloneServiceImpl; +import ai.z.openapi.service.moderations.ModerationService; +import ai.z.openapi.service.moderations.ModerationServiceImpl; import ai.z.openapi.core.config.ZaiConfig; import ai.z.openapi.core.model.BiFlowableClientResponse; import ai.z.openapi.core.model.ClientRequest; @@ -104,6 +106,9 @@ public abstract class AbstractAiClient extends AbstractClientBaseService { /** Voice clone service for voice cloning operations */ private VoiceCloneService voiceCloneService; + /** Moderation service for content safety detection */ + private ModerationService moderationService; + /** * Constructs a new AbstractAiClient with the specified configuration. * @param config the configuration object containing API keys, timeouts, and other @@ -256,6 +261,18 @@ public synchronized VoiceCloneService voiceClone() { return voiceCloneService; } + /** + * Returns the moderation service for content safety detection. This service handles + * content moderation for text, image, video, and audio inputs. + * @return the ModerationService instance (lazily initialized) + */ + public synchronized ModerationService moderations() { + if (moderationService == null) { + this.moderationService = new ModerationServiceImpl(this); + } + return moderationService; + } + // ==================== Utility Methods ==================== /** diff --git a/core/src/main/java/ai/z/openapi/api/moderations/ModerationApi.java b/core/src/main/java/ai/z/openapi/api/moderations/ModerationApi.java new file mode 100644 index 0000000..a4007f5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/moderations/ModerationApi.java @@ -0,0 +1,34 @@ +package ai.z.openapi.api.moderations; + +import ai.z.openapi.service.moderations.ModerationCreateParams; +import ai.z.openapi.service.model.ModelData; +import io.reactivex.rxjava3.core.Single; +import retrofit2.http.Body; +import retrofit2.http.POST; + +/** + * Moderation API for content safety detection + * Provides content moderation capabilities for text, image, audio, and video formats + * Accurately identifies risky content including adult content, violence, illegal content, etc. + * Returns structured moderation results including content type, risk type, and specific risk segments + * Supports pricing: Text: 1.2 yuan/10k times, Image: 4 yuan/10k times, Audio: 0.18 yuan/hour, Video: 0.72 yuan/hour + */ +public interface ModerationApi { + + /** + * Create a content moderation request for safety detection + * Analyzes content for potential risks including adult content, violence, illegal activities + * Supports multiple content types: text strings, images, audio, and video files + * Returns detailed risk assessment with structured results and specific risk segments + * + * @param request Moderation parameters including content to analyze (text string or multimedia object) + * Text: maximum 2000 characters + * Images: less than 10M, minimum resolution 20x20, maximum 6000x6000 + * Video: recommended duration 30 seconds + * Audio: recommended duration 60 seconds + * @return Moderation response with risk level (PASS/REVIEW/REJECT), content type, + * risk types, and processing time information + */ + @POST("moderations") + Single createModeration(@Body ModerationCreateParams request); +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationCreateParams.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationCreateParams.java new file mode 100644 index 0000000..b809697 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationCreateParams.java @@ -0,0 +1,29 @@ +package ai.z.openapi.service.moderations; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + * Parameters for creating a moderation request to check content safety. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ModerationCreateParams extends CommonRequest implements ClientRequest { + + /** + * The model to use for moderation. Currently supports "glm-4-content-safety". + */ + private String model; + + /** + * The input content to moderate. Can be text, image, video, or audio. + */ + private List input; +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationInput.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationInput.java new file mode 100644 index 0000000..b4e663f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationInput.java @@ -0,0 +1,100 @@ +package ai.z.openapi.service.moderations; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Base class for moderation input content + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ModerationInput { + + /** + * Type of content being moderated + * Possible values: "text", "image_url", "video_url", "audio_url" + */ + private String type; + + /** + * Text content for text moderation + */ + private String text; + + /** + * Image URL configuration for image moderation + */ + @JsonProperty("image_url") + private MediaUrl imageUrl; + + /** + * Video URL configuration for video moderation + */ + @JsonProperty("video_url") + private MediaUrl videoUrl; + + /** + * Audio URL configuration for audio moderation + */ + @JsonProperty("audio_url") + private MediaUrl audioUrl; + + /** + * Helper method to create text input + */ + public static ModerationInput text(String text) { + return ModerationInput.builder() + .type("text") + .text(text) + .build(); + } + + /** + * Helper method to create image input + */ + public static ModerationInput image(String url) { + return ModerationInput.builder() + .type("image_url") + .imageUrl(MediaUrl.builder().url(url).build()) + .build(); + } + + /** + * Helper method to create video input + */ + public static ModerationInput video(String url) { + return ModerationInput.builder() + .type("video_url") + .videoUrl(MediaUrl.builder().url(url).build()) + .build(); + } + + /** + * Helper method to create audio input + */ + public static ModerationInput audio(String url) { + return ModerationInput.builder() + .type("audio_url") + .audioUrl(MediaUrl.builder().url(url).build()) + .build(); + } + + /** + * Media URL configuration + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MediaUrl { + /** + * URL of the media file + */ + private String url; + } +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationResponse.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationResponse.java new file mode 100644 index 0000000..9163801 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationResponse.java @@ -0,0 +1,38 @@ +package ai.z.openapi.service.moderations; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Response wrapper for moderation API calls. Contains the result of content moderation + * operations along with status information. + */ +@Data +public class ModerationResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates whether the request was successful. + */ + private boolean success; + + /** + * The moderation result data. + */ + private ModerationResult data; + + /** + * Error information if the request failed. + */ + private ChatError error; +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationResult.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationResult.java new file mode 100644 index 0000000..08fc119 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationResult.java @@ -0,0 +1,90 @@ +package ai.z.openapi.service.moderations; + +import ai.z.openapi.service.model.Usage; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Result data from the moderation API containing safety analysis results. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ModerationResult { + + /** + * Unique request identifier for tracking. + */ + private String requestId; + + /** + * Processing time in milliseconds. + */ + private Long processedTime; + + /** + * List of moderation results for each input item. + */ + private List resultList; + + /** + * Token usage information for the request. + */ + private Usage usage; + + /** + * Individual moderation result for a single input item. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ModerationItem { + + /** + * Type of content being moderated (text, image, video, audio). + */ + private String contentType; + + /** + * Risk level assessment: "low", "medium", "high". + */ + private String riskLevel; + + /** + * Specific type of risk detected (e.g., "violence", "sexual", "hate"). + */ + private String riskType; + + /** + * Confidence score for the moderation result (0.0 to 1.0). + */ + private Double confidence; + + /** + * Additional details about the moderation result. + */ + private String details; + + /** + * Check if the content is flagged as unsafe. + * @return true if risk level is medium or high + */ + public boolean isFlagged() { + return "medium".equalsIgnoreCase(riskLevel) || "high".equalsIgnoreCase(riskLevel); + } + + /** + * Check if the content is safe. + * @return true if risk level is low + */ + public boolean isSafe() { + return "low".equalsIgnoreCase(riskLevel); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationService.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationService.java new file mode 100644 index 0000000..1ef0309 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationService.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.moderations; + +/** + * Moderation service for content safety detection + * Provides content moderation capabilities for text, image, audio, and video formats + * Accurately identifies risky content including adult content, violence, illegal content, etc. + * Returns structured moderation results including content type, risk type, and specific risk segments + */ +public interface ModerationService { + + /** + * Create a content moderation request for safety detection + * Analyzes content for potential risks including adult content, violence, illegal activities + * Supports multiple content types: text strings, images, audio, and video files + * Returns detailed risk assessment with structured results and specific risk segments + * + * @param request Moderation parameters including content to analyze (text string or multimedia object) + * Text: maximum 2000 characters + * Images: less than 10M, minimum resolution 20x20, maximum 6000x6000 + * Video: recommended duration 30 seconds + * Audio: recommended duration 60 seconds + * @return Moderation response with risk level (PASS/REVIEW/REJECT), content type, + * risk types, and processing time information + */ + ModerationResponse createModeration(ModerationCreateParams request); +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/moderations/ModerationServiceImpl.java b/core/src/main/java/ai/z/openapi/service/moderations/ModerationServiceImpl.java new file mode 100644 index 0000000..2fa387c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/moderations/ModerationServiceImpl.java @@ -0,0 +1,27 @@ +package ai.z.openapi.service.moderations; + +import ai.z.openapi.AbstractAiClient; +import ai.z.openapi.api.moderations.ModerationApi; +import ai.z.openapi.service.model.ModelData; +import ai.z.openapi.utils.RequestSupplier; + +/** + * Implementation of ModerationService + */ +public class ModerationServiceImpl implements ModerationService { + + private final AbstractAiClient zAiClient; + + private final ModerationApi moderationApi; + + public ModerationServiceImpl(AbstractAiClient zAiClient) { + this.zAiClient = zAiClient; + this.moderationApi = zAiClient.retrofit().create(ModerationApi.class); + } + + @Override + public ModerationResponse createModeration(ModerationCreateParams request) { + RequestSupplier supplier = moderationApi::createModeration; + return this.zAiClient.executeRequest(request, supplier, ModerationResponse.class); + } +} \ No newline at end of file diff --git a/samples/src/main/ai.z.openapi.samples/ModerationExample.java b/samples/src/main/ai.z.openapi.samples/ModerationExample.java new file mode 100644 index 0000000..9bab549 --- /dev/null +++ b/samples/src/main/ai.z.openapi.samples/ModerationExample.java @@ -0,0 +1,173 @@ +package ai.z.openapi.samples; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.moderations.*; + +import java.util.Arrays; +import java.util.List; + +/** + * Moderation Example + * Demonstrates how to use ZaiClient to moderate content for safety + */ +public class ModerationExample { + + public static void main(String[] args) { + // Create client + ZaiClient client = ZaiClient.builder().build(); + + // Example 1: Text moderation + System.out.println("=== Text Moderation Example ==="); + moderateText(client); + + // Example 2: Image moderation + System.out.println("\n=== Image Moderation Example ==="); + moderateImage(client); + + // Example 3: Mixed content moderation + System.out.println("\n=== Mixed Content Moderation Example ==="); + moderateMixedContent(client); + } + + private static void moderateText(ZaiClient client) { + // Create text moderation inputs + List inputs = Arrays.asList( + ModerationInput.text("This is a normal message about technology."), + ModerationInput.text("Hello, how are you today?"), + ModerationInput.text("Let's discuss artificial intelligence and machine learning.") + ); + + // Create moderation request + ModerationCreateParams request = ModerationCreateParams.builder() + .model("glm-4-content-safety") + .input(inputs) + .build(); + + try { + // Execute request + ModerationResponse response = client.moderations().createModeration(request); + + if (response.isSuccess()) { + System.out.println("Text moderation completed successfully:"); + System.out.println("Request ID: " + response.getData().getRequestId()); + System.out.println("Processing time: " + response.getData().getProcessedTime() + "ms"); + + response.getData().getResultList().forEach(item -> { + System.out.println("\nContent Type: " + item.getContentType()); + System.out.println("Risk Level: " + item.getRiskLevel()); + System.out.println("Risk Type: " + item.getRiskType()); + System.out.println("Is Safe: " + item.isSafe()); + System.out.println("Is Flagged: " + item.isFlagged()); + if (item.getConfidence() != null) { + System.out.println("Confidence: " + String.format("%.2f", item.getConfidence())); + } + }); + } else { + System.err.println("Error: Text moderation failed: " + response.getMsg()); + if (response.getError() != null) { + System.err.println("Error details: " + response.getError().getMessage()); + } + } + } catch (Exception e) { + System.err.println("Text moderation exception: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void moderateImage(ZaiClient client) { + // Create image moderation input + List inputs = Arrays.asList( + ModerationInput.image("https://example.com/sample-image.jpg"), + ModerationInput.image("https://example.com/another-image.png") + ); + + // Create moderation request + ModerationCreateParams request = ModerationCreateParams.builder() + .model("glm-4-content-safety") + .input(inputs) + .build(); + + try { + // Execute request + ModerationResponse response = client.moderations().createModeration(request); + + if (response.isSuccess()) { + System.out.println("Image moderation completed successfully:"); + System.out.println("Request ID: " + response.getData().getRequestId()); + + response.getData().getResultList().forEach(item -> { + System.out.println("\nContent Type: " + item.getContentType()); + System.out.println("Risk Level: " + item.getRiskLevel()); + System.out.println("Status: " + (item.isSafe() ? "SAFE" : "FLAGGED")); + if (item.getRiskType() != null) { + System.out.println("Risk Type: " + item.getRiskType()); + } + }); + } else { + System.err.println("Error: Image moderation failed: " + response.getMsg()); + } + } catch (Exception e) { + System.err.println("Image moderation exception: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void moderateMixedContent(ZaiClient client) { + // Create mixed content moderation inputs + List inputs = Arrays.asList( + ModerationInput.text("This is a text message to be moderated."), + ModerationInput.image("https://example.com/image-to-check.jpg"), + ModerationInput.video("https://example.com/video-sample.mp4"), + ModerationInput.audio("https://example.com/audio-sample.mp3") + ); + + // Create moderation request + ModerationCreateParams request = ModerationCreateParams.builder() + .model("glm-4-content-safety") + .input(inputs) + .build(); + + try { + // Execute request + ModerationResponse response = client.moderations().createModeration(request); + + if (response.isSuccess()) { + System.out.println("Mixed content moderation completed successfully:"); + System.out.println("Total items processed: " + response.getData().getResultList().size()); + + response.getData().getResultList().forEach(item -> { + System.out.println("\n--- Moderation Result ---"); + System.out.println("Content Type: " + item.getContentType()); + System.out.println("Risk Assessment: " + item.getRiskLevel()); + System.out.println("Safety Status: " + (item.isSafe() ? "✓ SAFE" : "⚠ FLAGGED")); + + if (item.isFlagged()) { + System.out.println("Risk Category: " + item.getRiskType()); + if (item.getDetails() != null) { + System.out.println("Details: " + item.getDetails()); + } + } + }); + + // Summary statistics + long safeCount = response.getData().getResultList().stream() + .mapToLong(item -> item.isSafe() ? 1 : 0) + .sum(); + long flaggedCount = response.getData().getResultList().size() - safeCount; + + System.out.println("\n--- Summary ---"); + System.out.println("Safe items: " + safeCount); + System.out.println("Flagged items: " + flaggedCount); + + if (response.getData().getUsage() != null) { + System.out.println("Total tokens used: " + response.getData().getUsage().getTotalTokens()); + } + } else { + System.err.println("Error: Mixed content moderation failed: " + response.getMsg()); + } + } catch (Exception e) { + System.err.println("Mixed content moderation exception: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file