diff --git a/core/src/main/java/ai/z/openapi/AbstractAiClient.java b/core/src/main/java/ai/z/openapi/AbstractAiClient.java index b6de2d5..46612e1 100644 --- a/core/src/main/java/ai/z/openapi/AbstractAiClient.java +++ b/core/src/main/java/ai/z/openapi/AbstractAiClient.java @@ -27,6 +27,8 @@ import ai.z.openapi.service.web_reader.WebReaderService; import ai.z.openapi.service.web_reader.WebReaderServiceImpl; import ai.z.openapi.service.videos.VideosService; import ai.z.openapi.service.videos.VideosServiceImpl; +import ai.z.openapi.service.layoutparsing.LayoutParsingService; +import ai.z.openapi.service.layoutparsing.LayoutParsingServiceImpl; import ai.z.openapi.service.assistant.AssistantService; import ai.z.openapi.service.assistant.AssistantServiceImpl; import ai.z.openapi.service.voiceclone.VoiceCloneService; @@ -121,6 +123,9 @@ public abstract class AbstractAiClient extends AbstractClientBaseService { /** HandWriting service for handwritingOcrService operations */ private HandwritingOcrService handwritingOcrService; + /** Layout parsing service for layout_parsing operations */ + private LayoutParsingService layoutParsingService; + /** Moderation service for content safety detection */ private ModerationService moderationService; @@ -307,6 +312,13 @@ public abstract class AbstractAiClient extends AbstractClientBaseService { return handwritingOcrService; } + public synchronized LayoutParsingService layoutParsing() { + if (layoutParsingService == null) { + this.layoutParsingService = new LayoutParsingServiceImpl(this); + } + return layoutParsingService; + } + /** * Returns the moderation service for content safety detection. This service handles * content moderation for text, image, video, and audio inputs. diff --git a/core/src/main/java/ai/z/openapi/api/layoutparsing/LayoutParsingApi.java b/core/src/main/java/ai/z/openapi/api/layoutparsing/LayoutParsingApi.java new file mode 100644 index 0000000..f0405be --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/layoutparsing/LayoutParsingApi.java @@ -0,0 +1,14 @@ +package ai.z.openapi.api.layoutparsing; + +import ai.z.openapi.service.layoutparsing.LayoutParsingCreateParams; +import ai.z.openapi.service.layoutparsing.LayoutParsingResult; +import io.reactivex.rxjava3.core.Single; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface LayoutParsingApi { + + @POST("layout_parsing") + Single layoutParsing(@Body LayoutParsingCreateParams request); + +} diff --git a/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingCreateParams.java b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingCreateParams.java new file mode 100644 index 0000000..60ccf2e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingCreateParams.java @@ -0,0 +1,35 @@ +package ai.z.openapi.service.layoutparsing; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class LayoutParsingCreateParams extends CommonRequest implements ClientRequest { + + private String model; + + private String file; + + @JsonProperty("return_crop_images") + private Boolean returnCropImages; + + @JsonProperty("need_layout_visualization") + private Boolean needLayoutVisualization; + + @JsonProperty("start_page_id") + private Integer startPageId; + + @JsonProperty("end_page_id") + private Integer endPageId; + +} diff --git a/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResponse.java b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResponse.java new file mode 100644 index 0000000..f77c33c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.layoutparsing; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class LayoutParsingResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private LayoutParsingResult data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResult.java b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResult.java new file mode 100644 index 0000000..c19974d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingResult.java @@ -0,0 +1,132 @@ +package ai.z.openapi.service.layoutparsing; + +import ai.z.openapi.service.model.Usage; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class LayoutParsingResult { + + private String id; + + private Long created; + + private String model; + + @JsonProperty("md_results") + private String mdResults; + + @JsonProperty("layout_details") + private List> layoutDetails; + + @JsonProperty("layout_visualization") + private List layoutVisualization; + + @JsonProperty("data_info") + private DataInfo dataInfo; + + @JsonProperty("request_id") + private String requestId; + + private Usage usage; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class LayoutDetail { + + private Integer index; + + private String label; + + @JsonProperty("bbox_2d") + private Object bbox2dRaw; + + private List bbox2d; + + private String content; + + private Integer height; + + private Integer width; + + @JsonProperty("bbox_2d") + public void setBbox2dRaw(Object bbox2dRaw) { + this.bbox2dRaw = bbox2dRaw; + this.bbox2d = convertBbox2d(bbox2dRaw); + } + + private List convertBbox2d(Object bbox2dRaw) { + List result = new ArrayList<>(); + if (bbox2dRaw == null) { + return result; + } + + try { + if (bbox2dRaw instanceof List) { + List rawList = (List) bbox2dRaw; + if (!rawList.isEmpty() && rawList.get(0) instanceof List) { + // Handle nested array: [[x1, y1, x2, y2]] + List innerList = (List) rawList.get(0); + for (Object item : innerList) { + if (item instanceof Number) { + result.add(((Number) item).intValue()); + } + } + } + else { + // Handle flat array: [x1, y1, x2, y2] + for (Object item : rawList) { + if (item instanceof Number) { + result.add(((Number) item).intValue()); + } + } + } + } + } + catch (Exception e) { + // Log error if needed, but return empty list to avoid breaking the + // parsing + } + + return result; + } + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DataInfo { + + @JsonProperty("num_pages") + private Integer numPages; + + private List pages; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PageInfo { + + private Integer width; + + private Integer height; + + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingService.java b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingService.java new file mode 100644 index 0000000..548d4fb --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingService.java @@ -0,0 +1,7 @@ +package ai.z.openapi.service.layoutparsing; + +public interface LayoutParsingService { + + LayoutParsingResponse layoutParsing(LayoutParsingCreateParams request); + +} diff --git a/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingServiceImpl.java b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingServiceImpl.java new file mode 100644 index 0000000..4fefca4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/layoutparsing/LayoutParsingServiceImpl.java @@ -0,0 +1,37 @@ +package ai.z.openapi.service.layoutparsing; + +import ai.z.openapi.AbstractAiClient; +import ai.z.openapi.api.layoutparsing.LayoutParsingApi; +import ai.z.openapi.utils.RequestSupplier; + +public class LayoutParsingServiceImpl implements LayoutParsingService { + + private final AbstractAiClient zAiClient; + + private final LayoutParsingApi layoutParsingApi; + + public LayoutParsingServiceImpl(AbstractAiClient zAiClient) { + this.zAiClient = zAiClient; + this.layoutParsingApi = zAiClient.retrofit().create(LayoutParsingApi.class); + } + + @Override + public LayoutParsingResponse layoutParsing(LayoutParsingCreateParams request) { + validateParams(request); + RequestSupplier supplier = layoutParsingApi::layoutParsing; + return this.zAiClient.executeRequest(request, supplier, LayoutParsingResponse.class); + } + + private void validateParams(LayoutParsingCreateParams request) { + if (request == null) { + throw new IllegalArgumentException("request cannot be null"); + } + if (request.getModel() == null) { + throw new IllegalArgumentException("model cannot be null"); + } + if (request.getFile() == null) { + throw new IllegalArgumentException("file cannot be null"); + } + } + +} diff --git a/pom.xml b/pom.xml index a2c175b..1265fad 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ - 0.3.2 + 0.3.3 8 UTF-8 UTF-8 diff --git a/samples/src/main/ai.z.openapi.samples/FunctionCallingExample.java b/samples/src/main/ai.z.openapi.samples/FunctionCallingExample.java index f2f4b9a..3d44c45 100644 --- a/samples/src/main/ai.z.openapi.samples/FunctionCallingExample.java +++ b/samples/src/main/ai.z.openapi.samples/FunctionCallingExample.java @@ -49,7 +49,6 @@ public class FunctionCallingExample { .parameters(ChatFunctionParameters.builder() .type("object") .properties(properties) - .required(Collections.singletonList("location")) .build()) .build()) .build(); diff --git a/samples/src/main/ai.z.openapi.samples/LayoutParsingExample.java b/samples/src/main/ai.z.openapi.samples/LayoutParsingExample.java new file mode 100644 index 0000000..9b45d64 --- /dev/null +++ b/samples/src/main/ai.z.openapi.samples/LayoutParsingExample.java @@ -0,0 +1,121 @@ +package ai.z.openapi.samples; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.layoutparsing.LayoutParsingCreateParams; +import ai.z.openapi.service.layoutparsing.LayoutParsingResponse; +import ai.z.openapi.service.layoutparsing.LayoutParsingResult; + +public class LayoutParsingExample { + + public static void main(String[] args) { + // Create client, recommended to set API Key via environment variable + // export ZAI_API_KEY=your.api_key + // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient.builder().ofZHIPU().build() + + // You can also set the API Key directly in the code for testing + ZaiClient client = ZaiClient.builder().ofZAI().build(); + + try { + System.out.println("=== Layout Parsing Example ==="); + + // Example 1: Using URL + String imageUrl = "https://cdn.bigmodel.cn/static/platform/images/trialcenter/example/visual_img1.jpeg"; + LayoutParsingResponse response = layoutParsingExample(client, "glm-ocr", imageUrl); + printResult(response); + + } catch (Exception e) { + System.err.println("Exception occurred: " + e.getMessage()); + e.printStackTrace(); + } finally { + client.close(); + } + } + + /** + * Example: Perform layout parsing on an image or PDF + * @param client ZaiClient instance + * @param model Model name, e.g., "glm-ocr" + * @param file Image URL or base64 string + * @param userId Optional user ID for tracking + * @param requestId Optional request ID for tracking + * @return LayoutParsingResponse object + */ + private static LayoutParsingResponse layoutParsingExample(ZaiClient client, String model, String file) { + if (model == null || model.trim().isEmpty()) { + System.err.println("Model cannot be null or empty."); + return null; + } + if (file == null || file.trim().isEmpty()) { + System.err.println("File (URL or base64) cannot be null or empty."); + return null; + } + try { + LayoutParsingCreateParams params = LayoutParsingCreateParams.builder() + .model(model) + .file(file) + .build(); + + System.out.println("Request parameters:"); + System.out.println(" model: " + model); + System.out.println(" file: " + (file.length() > 80 ? file.substring(0, 80) + "..." : file)); + System.out.println(); + + System.out.println("Calling layout parsing API..."); + return client.layoutParsing().layoutParsing(params); + } + catch (Exception e) { + System.err.println("Layout parsing task error: " + e.getMessage()); + } + // Return null indicates failure + return null; + } + + private static void printResult(LayoutParsingResponse response) { + if (response == null) { + System.out.println("No response received."); + return; + } + System.out.println("Response status: " + (response.isSuccess() ? "SUCCESS" : "FAILED")); + System.out.println("Code: " + response.getCode()); + System.out.println("Message: " + response.getMsg()); + if (!response.isSuccess() && response.getError() != null) { + System.out.println("Error: " + response.getError()); + } + if (response.getData() != null) { + LayoutParsingResult data = response.getData(); + System.out.println("Task ID: " + data.getId()); + System.out.println("Created: " + data.getCreated()); + System.out.println("Model: " + data.getModel()); + System.out.println("Request ID: " + data.getRequestId()); + System.out.println("Markdown results length: " + (data.getMdResults() != null ? data.getMdResults().length() : 0)); + if (data.getLayoutDetails() != null) { + System.out.println("Layout pages count: " + data.getLayoutDetails().size()); + data.getLayoutDetails().stream().limit(1).forEach(page -> { + System.out.println("First page blocks count: " + (page != null ? page.size() : 0)); + if (page != null) { + page.stream().limit(3).forEach(detail -> { + System.out.println( + " - index: " + detail.getIndex() + ", label: " + detail.getLabel() + ", bbox: " + detail.getBbox2d()); + }); + } + }); + } + if (data.getDataInfo() != null) { + System.out.println("Data info:"); + System.out.println(" num_pages: " + data.getDataInfo().getNumPages()); + if (data.getDataInfo().getPages() != null && !data.getDataInfo().getPages().isEmpty()) { + System.out.println(" first page size: " + data.getDataInfo().getPages().get(0).getWidth() + "x" + data.getDataInfo().getPages().get(0).getHeight()); + } + } + if (data.getUsage() != null) { + System.out.println("Usage:"); + System.out.println(" prompt_tokens: " + data.getUsage().getPromptTokens()); + System.out.println(" completion_tokens: " + data.getUsage().getCompletionTokens()); + System.out.println(" total_tokens: " + data.getUsage().getTotalTokens()); + if (data.getUsage().getPromptTokensDetails() != null) { + System.out.println(" prompt_tokens_details.cached_tokens: " + data.getUsage().getPromptTokensDetails().getCachedTokens()); + } + } + } + } +}