feat: add ocr api (#80)
This commit is contained in:
parent
633b1d323d
commit
59e9a606eb
10 changed files with 379 additions and 2 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ai.z.openapi.service.layoutparsing;
|
||||
|
||||
public interface LayoutParsingService {
|
||||
|
||||
LayoutParsingResponse layoutParsing(LayoutParsingCreateParams request);
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
2
pom.xml
2
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ public class FunctionCallingExample {
|
|||
.parameters(ChatFunctionParameters.builder()
|
||||
.type("object")
|
||||
.properties(properties)
|
||||
.required(Collections.singletonList("location"))
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
|
|
|||
121
samples/src/main/ai.z.openapi.samples/LayoutParsingExample.java
Normal file
121
samples/src/main/ai.z.openapi.samples/LayoutParsingExample.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue