feat: add ocr api (#80)

This commit is contained in:
Tomsun28 2026-02-03 00:01:24 +08:00 committed by GitHub
parent 633b1d323d
commit 59e9a606eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 379 additions and 2 deletions

View file

@ -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.

View file

@ -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<LayoutParsingResult> layoutParsing(@Body LayoutParsingCreateParams request);
}

View file

@ -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<LayoutParsingCreateParams> {
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;
}

View file

@ -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<LayoutParsingResult> {
private int code;
private String msg;
private boolean success;
private LayoutParsingResult data;
private ChatError error;
}

View file

@ -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<List<LayoutDetail>> layoutDetails;
@JsonProperty("layout_visualization")
private List<String> 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<Integer> 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<Integer> convertBbox2d(Object bbox2dRaw) {
List<Integer> 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<PageInfo> pages;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PageInfo {
private Integer width;
private Integer height;
}
}

View file

@ -0,0 +1,7 @@
package ai.z.openapi.service.layoutparsing;
public interface LayoutParsingService {
LayoutParsingResponse layoutParsing(LayoutParsingCreateParams request);
}

View file

@ -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<LayoutParsingCreateParams, LayoutParsingResult> 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");
}
}
}

View file

@ -45,7 +45,7 @@
</scm>
<properties>
<revision>0.3.2</revision>
<revision>0.3.3</revision>
<java.version>8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View file

@ -49,7 +49,6 @@ public class FunctionCallingExample {
.parameters(ChatFunctionParameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("location"))
.build())
.build())
.build();

View file

@ -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());
}
}
}
}
}