Skip to content

Commit b0f2e80

Browse files
authored
feat: add web reader api (#63)
1 parent b3ba8ec commit b0f2e80

File tree

15 files changed

+405
-9
lines changed

15 files changed

+405
-9
lines changed

core/src/main/java/ai/z/openapi/AbstractAiClient.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import ai.z.openapi.service.batches.BatchServiceImpl;
2222
import ai.z.openapi.service.web_search.WebSearchService;
2323
import ai.z.openapi.service.web_search.WebSearchServiceImpl;
24+
import ai.z.openapi.service.web_reader.WebReaderService;
25+
import ai.z.openapi.service.web_reader.WebReaderServiceImpl;
2426
import ai.z.openapi.service.videos.VideosService;
2527
import ai.z.openapi.service.videos.VideosServiceImpl;
2628
import ai.z.openapi.service.assistant.AssistantService;
@@ -99,6 +101,9 @@ public abstract class AbstractAiClient extends AbstractClientBaseService {
99101
/** Web search service for internet search capabilities */
100102
private WebSearchService webSearchService;
101103

104+
/** Web reader service for parsing web pages */
105+
private WebReaderService webReaderService;
106+
102107
/** Videos service for video processing */
103108
private VideosService videosService;
104109

@@ -230,6 +235,18 @@ public synchronized WebSearchService webSearch() {
230235
return webSearchService;
231236
}
232237

238+
/**
239+
* Returns the web reader service for parsing web pages. This service reads and
240+
* extracts content, metadata, images, and links from URLs.
241+
* @return the WebReaderService instance (lazily initialized)
242+
*/
243+
public synchronized WebReaderService webReader() {
244+
if (webReaderService == null) {
245+
this.webReaderService = new WebReaderServiceImpl(this);
246+
}
247+
return webReaderService;
248+
}
249+
233250
/**
234251
* Returns the videos service for video processing. This service handles video
235252
* analysis, generation, and manipulation.
@@ -601,7 +618,7 @@ public B tokenExpire(int expireMillis) {
601618

602619
/**
603620
* Configures network request timeout settings.
604-
* @param requestTimeOut the overall request timeout
621+
* @param requestTimeOut the overall request timeout, 0 is no timeout
605622
* @param connectTimeout the connection timeout
606623
* @param readTimeout the read timeout
607624
* @param writeTimeout the write timeout

core/src/main/java/ai/z/openapi/ZhipuAiClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static Builder builder() {
8787
* </p>
8888
* <pre>{@code
8989
* ZhipuAiClient client = new ZhipuAiClient.Builder("your-api-key")
90-
* .networkConfig(30, 10, 30, 30, TimeUnit.SECONDS)
90+
* .networkConfig(0, 10, 30, 30, TimeUnit.SECONDS)
9191
* .connectionPool(10, 5, TimeUnit.MINUTES)
9292
* .enableTokenCache()
9393
* .build();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ai.z.openapi.api.web_reader;
2+
3+
import ai.z.openapi.service.web_reader.WebReaderRequest;
4+
import ai.z.openapi.service.web_reader.WebReaderResult;
5+
import io.reactivex.rxjava3.core.Single;
6+
import retrofit2.http.Body;
7+
import retrofit2.http.POST;
8+
9+
/**
10+
* Web Reader API for reading and parsing web page content.
11+
*/
12+
public interface WebReaderApi {
13+
14+
/**
15+
* Read and parse content from a given URL.
16+
* @param request reader parameters including url and options
17+
* @return parsed content and metadata
18+
*/
19+
@POST("reader")
20+
Single<WebReaderResult> reader(@Body WebReaderRequest request);
21+
22+
}

core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public class ZaiConfig {
9797

9898
/**
9999
* Request timeout in specified time unit. The whole timeout for complete calls, is
100-
* the okhttp call timeout.
100+
* the okhttp call timeout. The 0 value means no timeout.
101101
*/
102102
private Integer requestTimeOut;
103103

core/src/main/java/ai/z/openapi/service/audio/AudioServiceImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
import com.fasterxml.jackson.databind.ObjectMapper;
99
import com.fasterxml.jackson.databind.node.ObjectNode;
1010
import io.reactivex.rxjava3.core.Single;
11-
import lombok.extern.slf4j.Slf4j;
1211
import okhttp3.MediaType;
1312
import okhttp3.MultipartBody;
1413
import okhttp3.RequestBody;
1514
import okhttp3.ResponseBody;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
1617

1718
import java.io.IOException;
1819
import java.io.InputStream;
@@ -26,9 +27,10 @@
2627
/**
2728
* Audio service implementation
2829
*/
29-
@Slf4j
3030
public class AudioServiceImpl implements AudioService {
3131

32+
private static final Logger log = LoggerFactory.getLogger(AudioServiceImpl.class);
33+
3234
protected static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper();
3335

3436
private final AbstractAiClient zAiClient;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package ai.z.openapi.service.web_reader;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import ai.z.openapi.core.model.ClientRequest;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
import lombok.experimental.SuperBuilder;
9+
import java.net.URI;
10+
import java.net.URISyntaxException;
11+
12+
@SuperBuilder
13+
@NoArgsConstructor
14+
@AllArgsConstructor
15+
@Data
16+
public class WebReaderRequest implements ClientRequest<WebReaderRequest> {
17+
18+
/**
19+
* The target URL to read and parse content from.
20+
*/
21+
@JsonProperty("url")
22+
private String url;
23+
24+
/**
25+
* Unique request identifier, used for tracing.
26+
*/
27+
@JsonProperty("request_id")
28+
private String requestId;
29+
30+
/**
31+
* User ID associated with the request.
32+
*/
33+
@JsonProperty("user_id")
34+
private String userId;
35+
36+
/**
37+
* Timeout in seconds for the reader operation.
38+
*/
39+
@JsonProperty("timeout")
40+
private Integer timeout;
41+
42+
/**
43+
* Whether to bypass cache when reading.
44+
*/
45+
@JsonProperty("no_cache")
46+
private Boolean noCache;
47+
48+
/**
49+
* Return format of the reader output, e.g., markdown or plain.
50+
*/
51+
@JsonProperty("return_format")
52+
private String returnFormat;
53+
54+
/**
55+
* Whether to retain image placeholders in the content.
56+
*/
57+
@JsonProperty("retain_images")
58+
private Boolean retainImages;
59+
60+
/**
61+
* Whether to disable GitHub-Flavored Markdown processing.
62+
*/
63+
@JsonProperty("no_gfm")
64+
private Boolean noGfm;
65+
66+
/**
67+
* Whether to keep image data URLs inline.
68+
*/
69+
@JsonProperty("keep_img_data_url")
70+
private Boolean keepImgDataUrl;
71+
72+
/**
73+
* Whether to include images summary in the result.
74+
*/
75+
@JsonProperty("with_images_summary")
76+
private Boolean withImagesSummary;
77+
78+
/**
79+
* Whether to include links summary in the result.
80+
*/
81+
@JsonProperty("with_links_summary")
82+
private Boolean withLinksSummary;
83+
84+
/**
85+
* Validate request fields that require constraints. Ensures {@code url} is non-empty
86+
* and a syntactically valid HTTP/HTTPS URL.
87+
* @throws IllegalArgumentException if validation fails
88+
*/
89+
public void validate() {
90+
if (url == null || url.trim().isEmpty()) {
91+
throw new IllegalArgumentException("request url cannot be null or empty");
92+
}
93+
String normalized = url.trim();
94+
try {
95+
URI initial = new URI(normalized);
96+
URI candidate = initial;
97+
String scheme = initial.getScheme();
98+
if (scheme == null) {
99+
String candidateStr = normalized.startsWith("//") ? ("https:" + normalized) : ("https://" + normalized);
100+
candidate = new URI(candidateStr);
101+
scheme = candidate.getScheme();
102+
}
103+
if (!("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))) {
104+
throw new IllegalArgumentException("request url must use http or https");
105+
}
106+
if (candidate.getHost() == null || candidate.getHost().trim().isEmpty()) {
107+
throw new IllegalArgumentException("request url must contain a valid host");
108+
}
109+
}
110+
catch (URISyntaxException ex) {
111+
throw new IllegalArgumentException("request url is invalid: " + ex.getMessage());
112+
}
113+
}
114+
115+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ai.z.openapi.service.web_reader;
2+
3+
import ai.z.openapi.core.model.ClientResponse;
4+
import ai.z.openapi.service.model.ChatError;
5+
import lombok.Data;
6+
7+
@Data
8+
public class WebReaderResponse implements ClientResponse<WebReaderResult> {
9+
10+
private int code;
11+
12+
private String msg;
13+
14+
private boolean success;
15+
16+
private WebReaderResult data;
17+
18+
private ChatError error;
19+
20+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ai.z.openapi.service.web_reader;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import lombok.Data;
7+
8+
import java.util.Map;
9+
10+
@Data
11+
@JsonIgnoreProperties(ignoreUnknown = true)
12+
public class WebReaderResult {
13+
14+
@JsonProperty("reader_result")
15+
private ReaderData readerResult;
16+
17+
@Data
18+
@JsonIgnoreProperties(ignoreUnknown = true)
19+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
20+
public static class ReaderData {
21+
22+
private Map<String, String> images;
23+
24+
private Map<String, String> links;
25+
26+
private String title;
27+
28+
private String description;
29+
30+
private String url;
31+
32+
private String content;
33+
34+
private String publishedTime;
35+
36+
private Map<String, Object> metadata;
37+
38+
private Map<String, Object> external;
39+
40+
}
41+
42+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ai.z.openapi.service.web_reader;
2+
3+
/**
4+
* Web reader service interface
5+
*/
6+
public interface WebReaderService {
7+
8+
/**
9+
* Creates a web reader request to parse a URL.
10+
* @param request the web reader request
11+
* @return WebReaderResponse containing the reader result
12+
*/
13+
WebReaderResponse createWebReader(WebReaderRequest request);
14+
15+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ai.z.openapi.service.web_reader;
2+
3+
import ai.z.openapi.AbstractAiClient;
4+
import ai.z.openapi.api.web_reader.WebReaderApi;
5+
import ai.z.openapi.utils.RequestSupplier;
6+
7+
/**
8+
* Web reader service implementation
9+
*/
10+
public class WebReaderServiceImpl implements WebReaderService {
11+
12+
private final AbstractAiClient zAiClient;
13+
14+
private final WebReaderApi webReaderApi;
15+
16+
public WebReaderServiceImpl(AbstractAiClient zAiClient) {
17+
this.zAiClient = zAiClient;
18+
this.webReaderApi = zAiClient.retrofit().create(WebReaderApi.class);
19+
}
20+
21+
@Override
22+
public WebReaderResponse createWebReader(WebReaderRequest request) {
23+
if (request == null) {
24+
throw new IllegalArgumentException("request cannot be null");
25+
}
26+
request.validate();
27+
RequestSupplier<WebReaderRequest, WebReaderResult> supplier = webReaderApi::reader;
28+
return this.zAiClient.executeRequest(request, supplier, WebReaderResponse.class);
29+
}
30+
31+
}

0 commit comments

Comments
 (0)