refactor: use jackson subtype instead of custom deserializer (#4)

* refactor: use jackson subtype instead of custom deserializer

* refactor: use jackson subtype instead of custom deserializer
This commit is contained in:
tomsun28 2025-07-14 11:48:03 +08:00 committed by GitHub
parent 4745bbdead
commit 71ae71b778
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 254 additions and 1007 deletions

View file

@ -2,67 +2,47 @@
## Overview
The Z.ai SDK Java provides a service-oriented architecture that offers clean separation of concerns, comprehensive configuration management, and support for both synchronous and streaming operations. The SDK is built around a client-service pattern with reactive programming support.
The Z.ai SDK for Java provides a robust, service-oriented architecture designed for seamless integration with Z.ai's suite of AI services. It features a clean separation of concerns, comprehensive configuration management, and robust support for synchronous, asynchronous, and streaming operations. The SDK is built on a client-service pattern and leverages reactive programming with RxJava for handling streams.
## Architecture Components
## Core Components
### 1. Core Client Architecture
### 1. Main Client: `ZaiClient`
#### ZaiClient
The main client class that serves as the entry point for all AI services:
The `ZaiClient` class is the central entry point for all interactions with the Z.ai API. It manages service instances and handles the underlying HTTP communication.
```java
public class ZaiClient extends AbstractClientBaseService {
// Service instances
// Service instances (lazily initialized)
private ChatService chatService;
private AgentService agentService;
private EmbeddingService embeddingService;
// ... other services
private FileService fileService;
private AudioService audioService;
private ImageService imageService;
private BatchService batchService;
private FineTuningService fineTuningService;
private WebSearchService webSearchService;
private VideosService videosService;
private KnowledgeService knowledgeService;
private DocumentService documentService;
private AssistantService assistantService;
// Constructor
public ZaiClient(ZaiConfig config) {
// Initialize HTTP client and Retrofit
// Initializes OkHttpClient and Retrofit
}
// Service accessors
// Public, thread-safe service accessors
public synchronized ChatService chat() { /* ... */ }
public synchronized AgentService agents() { /* ... */ }
public synchronized EmbeddingService embeddings() { /* ... */ }
// ... other service accessors
}
```
#### Base Request and Response Models
### 2. Configuration: `ZaiConfig`
**ClientRequest Interface**: Base interface for all service requests
```java
public interface ClientRequest<T> {
// Marker interface for type safety
}
```
**ClientResponse Interface**: Base interface for all service responses
```java
public interface ClientResponse<T> {
T getData();
void setData(T data);
void setCode(int code);
void setMsg(String msg);
void setSuccess(boolean success);
void setError(ChatError error);
}
```
**FlowableClientResponse Interface**: Extended interface for streaming responses
```java
public interface FlowableClientResponse<T> extends ClientResponse<T> {
void setFlowable(Flowable<T> stream);
}
```
### 2. Configuration Management
#### ZaiConfig
Main configuration class that contains all SDK settings:
Configuration is managed through the `ZaiConfig` class, which uses a builder pattern for easy and flexible setup.
```java
@Data
@ -70,20 +50,18 @@ Main configuration class that contains all SDK settings:
@NoArgsConstructor
@AllArgsConstructor
public class ZaiConfig {
private String baseUrl;
private String apiSecretKey;
private String baseUrl; // API endpoint URL
private String apiSecretKey; // In format {apiKey}.{apiSecret}
private String apiKey;
private String apiSecret;
private int expireMillis = 30 * 60 * 1000; // 30 minutes
private String alg = "HS256";
private boolean disableTokenCache;
private int expireMillis = 30 * 60 * 1000; // JWT token expiration
private String alg = "HS256"; // JWT algorithm
private boolean disableTokenCache; // Control for token caching
// Connection pool settings
// Network settings
private int connectionPoolMaxIdleConnections = 5;
private long connectionPoolKeepAliveDuration = 1;
private TimeUnit connectionPoolTimeUnit = TimeUnit.SECONDS;
// Timeout settings
private int requestTimeOut;
private int connectTimeout;
private int readTimeout;
@ -94,51 +72,24 @@ public class ZaiConfig {
}
```
#### Configuration Features
**Key Configuration Features**:
- **Authentication**: Supports API key/secret and JWT-based authentication with configurable token caching.
- **Networking**: Fine-grained control over connection pooling and request timeouts.
- **Extensibility**: The builder pattern allows for easy addition of new configuration options.
1. **Authentication Settings**
- API secret key in format `{apiKey}.{apiSecret}`
- JWT token expiration and algorithm configuration
- Token caching control
### 3. Service Abstractions
2. **Network Configuration**
- Base URL for API endpoints
- Connection pool settings (max idle connections, keep-alive duration)
- Timeout configurations (request, connect, read, write)
3. **Token Management**
- JWT token generation and caching
- Configurable expiration times
- Option to disable token caching for direct API key usage
### 3. Service Implementations
#### ChatService
Provides chat completion functionality with support for synchronous, asynchronous, and streaming operations:
Services are defined by interfaces (e.g., `ChatService`, `EmbeddingService`) and implemented in corresponding `ServiceImpl` classes. This promotes a consistent, interface-driven design.
```java
// Example: ChatService interface
public interface ChatService {
/**
* Creates a chat completion, either streaming or non-streaming based on the request configuration.
*/
ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request);
/**
* Creates an asynchronous chat completion.
*/
ChatCompletionResponse asyncChatCompletion(ChatCompletionCreateParams request);
/**
* Retrieves the result of an asynchronous model operation.
*/
QueryModelResultResponse retrieveAsyncResult(AsyncResultRetrieveParams request);
}
```
#### Service Implementation Pattern
All services follow a consistent implementation pattern:
```java
// Implementation pattern
public class ChatServiceImpl implements ChatService {
private final ZaiClient zAiClient;
private final ChatApi chatApi;
@ -148,681 +99,122 @@ public class ChatServiceImpl implements ChatService {
this.chatApi = this.zAiClient.retrofit().create(ChatApi.class);
}
@Override
public ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request) {
// Parameter validation
String paramMsg = validateParams(request);
if (StringUtils.isNotEmpty(paramMsg)) {
return new ChatCompletionResponse(-100, String.format("invalid param: %s", paramMsg));
}
// Route to streaming or synchronous execution
if (request.getStream()) {
return streamChatCompletion(request);
} else {
return syncChatCompletion(request);
}
}
// Method implementations...
}
```
## Usage Examples
### 4. Request/Response Models
### Basic Configuration
- **`ClientRequest<T>`**: A marker interface for all request objects, ensuring type safety.
- **`ClientResponse<T>`**: A standard interface for all synchronous responses, providing access to data, status, and error information.
- **`FlowableClientResponse<T>`**: An extension for streaming responses, providing a `Flowable<T>` for reactive stream handling.
```java
// Simple configuration with API secret key
ZaiConfig config = new ZaiConfig("your.api.key.your.api.secret");
ZaiClient client = new ZaiClient(config);
## Usage Patterns
// Or using separate API key and secret
ZaiConfig config = new ZaiConfig("your.api.key", "your.api.secret");
ZaiClient client = new ZaiClient(config);
```
### Client Instantiation
### Builder Pattern Configuration
```java
// Using the Builder pattern for advanced configuration
ZaiClient client = new ZaiClient.Builder("your.api.key.your.api.secret")
.enableTokenCache()
.networkConfig(
300, // request timeout
100, // connect timeout
100, // read timeout
100, // write timeout
TimeUnit.SECONDS
)
.connectionPool(
10, // max idle connections
5, // keep alive duration
TimeUnit.MINUTES
)
.tokenExpire(3600000) // 1 hour in milliseconds
.build();
```
### Custom Configuration with ZaiConfig
**Using the `ZaiConfig` builder (Recommended):**
```java
ZaiConfig config = ZaiConfig.builder()
.apiSecretKey("your.api.key.your.api.secret")
.baseUrl("https://custom.api.endpoint")
.baseUrl("https://open.bigmodel.cn/")
.requestTimeOut(60)
.connectTimeout(30)
.readTimeout(30)
.writeTimeout(30)
.timeOutTimeUnit(TimeUnit.SECONDS)
.disableTokenCache(false)
.expireMillis(7200000) // 2 hours
.connectionPoolMaxIdleConnections(10)
.connectionPoolKeepAliveDuration(5)
.connectionPoolTimeUnit(TimeUnit.MINUTES)
.build();
ZaiClient client = new ZaiClient(config);
```
### Service Usage
### Service Interaction
Access services directly from the `ZaiClient` instance.
```java
// Get service instance
// Get the chat service
ChatService chatService = client.chat();
// Create chat request
// Build a request
ChatCompletionCreateParams request = ChatCompletionCreateParams.builder()
.model("glm-4")
.messages(Arrays.asList(
ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content("Hello, world!")
.build()
.messages(Collections.singletonList(
ChatMessage.builder().role(ChatMessage.Role.USER).content("Hello!").build()
))
.stream(false) // Set to true for streaming
.temperature(0.7f)
.maxTokens(1024)
.build();
// Execute request
try {
ChatCompletionResponse response = chatService.createChatCompletion(request);
// Execute the request
ChatCompletionResponse response = chatService.createChatCompletion(request);
if (response.isSuccess()) {
ModelData data = response.getData();
if (data != null && data.getChoices() != null && !data.getChoices().isEmpty()) {
String content = data.getChoices().get(0).getMessage().getContent();
System.out.println("Response: " + content);
}
} else {
System.err.println("Error: " + response.getMsg());
if (response.getError() != null) {
System.err.println("Error details: " + response.getError().getMessage());
}
}
} catch (Exception e) {
System.err.println("Request failed: " + e.getMessage());
if (response.isSuccess()) {
System.out.println("Response: " + response.getData().getChoices().get(0).getMessage().getContent());
} else {
System.err.println("Error: " + response.getError().getMessage());
}
```
### Streaming Usage
### Streaming Operations
For streaming, set `stream(true)` in the request and subscribe to the `Flowable`.
```java
// Create streaming request
ChatCompletionCreateParams streamRequest = ChatCompletionCreateParams.builder()
.model("glm-4")
.messages(Arrays.asList(
ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content("Tell me a story")
.build()
))
.stream(true) // Enable streaming
.temperature(0.7f)
.maxTokens(1024)
.build();
// Execute streaming request
ChatCompletionCreateParams streamRequest = request.toBuilder().stream(true).build();
ChatCompletionResponse response = chatService.createChatCompletion(streamRequest);
if (response.isSuccess() && response.getFlowable() != null) {
response.getFlowable().subscribe(
data -> {
// Handle streaming chunk
if (data.getChoices() != null && !data.getChoices().isEmpty()) {
String content = data.getChoices().get(0).getDelta().getContent();
if (content != null) {
System.out.print(content);
}
}
},
data -> System.out.print(data.getChoices().get(0).getDelta().getContent()),
error -> System.err.println("\nStream error: " + error.getMessage()),
() -> System.out.println("\nStream completed")
() -> System.out.println("\nStream complete.")
);
} else {
System.err.println("Failed to start streaming: " + response.getMsg());
}
```
### Asynchronous Usage
```java
// Create async request
ChatCompletionCreateParams asyncRequest = ChatCompletionCreateParams.builder()
.model("glm-4")
.messages(Arrays.asList(
ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content("Generate a long document")
.build()
))
.build();
// Execute async request
ChatCompletionResponse asyncResponse = chatService.asyncChatCompletion(asyncRequest);
if (asyncResponse.isSuccess()) {
String taskId = asyncResponse.getData().getTaskId();
System.out.println("Async task started with ID: " + taskId);
// Poll for results
AsyncResultRetrieveParams retrieveParams = new AsyncResultRetrieveParams();
retrieveParams.setId(taskId);
QueryModelResultResponse result = chatService.retrieveAsyncResult(retrieveParams);
// Handle result...
}
```
## Available Services
The ZaiClient provides access to multiple AI services:
The `ZaiClient` provides access to the following services:
```java
ZaiClient client = new ZaiClient(config);
// Chat completion service
ChatService chatService = client.chat();
// Agent service for agent-based completions
AgentService agentService = client.agents();
// Embedding service for text embeddings
EmbeddingService embeddingService = client.embeddings();
// File management service
FileService fileService = client.files();
// Audio processing service
AudioService audioService = client.audio();
// Image generation service
ImageService imageService = client.images();
// Batch processing service
BatchService batchService = client.batches();
// Fine-tuning service
FineTuningService fineTuningService = client.fineTuning();
// Web search service
WebSearchService webSearchService = client.webSearch();
// Video processing service
VideosService videosService = client.videos();
// Knowledge base service
KnowledgeService knowledgeService = client.knowledge();
// Document management service
DocumentService documentService = client.documents();
// Assistant service
AssistantService assistantService = client.assistants();
```
## Request and Response Models
### Common Request Structure
All requests extend `CommonRequest` which provides common fields:
```java
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class CommonRequest {
@JsonProperty("request_id")
private String requestId;
@JsonProperty("user_id")
private String userId;
// Additional common fields...
}
```
### Chat Request Example
```java
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ChatCompletionCreateParams extends CommonRequest implements ClientRequest<ChatCompletionCreateParams> {
private String model;
private List<ChatMessage> messages;
private Boolean stream;
private Float temperature;
@JsonProperty("max_tokens")
private Integer maxTokens;
private List<String> stop;
private List<ChatTool> tools;
// ... other fields
}
```
### Response Structure
All responses implement `ClientResponse` or `FlowableClientResponse`:
```java
@Data
public class ChatCompletionResponse implements FlowableClientResponse<ModelData> {
private int code;
private String msg;
private boolean success;
private ModelData data;
private Flowable<ModelData> flowable; // For streaming responses
private ChatError error;
}
```
- `chat()`: Chat completion and conversational AI.
- `agents()`: Agent-based completions.
- `embeddings()`: Text embedding generation.
- `files()`: File management (upload, download, delete).
- `audio()`: Audio processing (speech-to-text, text-to-speech).
- `images()`: Image generation.
- `batches()`: Batch processing for large-scale jobs.
- `fineTuning()`: Model fine-tuning and management.
- `webSearch()`: Integrated web search capabilities.
- `videos()`: Video processing tasks.
- `knowledge()`: Knowledge base management.
- `documents()`: Document processing and analysis.
- `assistants()`: AI assistant functionalities.
## Error Handling
### Response Error Handling
The SDK provides two primary mechanisms for error handling:
```java
ChatCompletionResponse response = chatService.createChatCompletion(request);
1. **Response-Level Errors**: Check `response.isSuccess()` and use `response.getError()` to get detailed error information from the API.
// Check if the request was successful
if (!response.isSuccess()) {
int errorCode = response.getCode();
String errorMessage = response.getMsg();
System.err.println("Request failed with code: " + errorCode);
System.err.println("Error message: " + errorMessage);
// Check for detailed error information
if (response.getError() != null) {
```java
if (!response.isSuccess()) {
ChatError error = response.getError();
System.err.println("Error code: " + error.getCode());
System.err.println("Error details: " + error.getMessage());
// Handle specific error types
switch (errorCode) {
case 400:
System.err.println("Bad request - check your parameters");
break;
case 401:
System.err.println("Authentication failed - check your API key");
break;
case 429:
System.err.println("Rate limit exceeded - please retry later");
break;
case 500:
System.err.println("Server error - please try again");
break;
default:
System.err.println("Unexpected error occurred");
System.err.printf("API Error: [%s] %s%n", error.getCode(), error.getMessage());
}
```
2. **Exception Handling**: Use a `try-catch` block to handle network issues or unexpected client-side problems, such as `ZAiHttpException`.
```java
try {
// API call
} catch (ZAiHttpException e) {
System.err.printf("HTTP Error: %d - %s%n", e.statusCode, e.getMessage());
} catch (Exception e) {
System.err.println("An unexpected error occurred: " + e.getMessage());
}
return;
}
// Process successful response
ModelData data = response.getData();
if (data != null) {
// Handle successful response data
System.out.println("Request completed successfully");
}
```
### Exception Handling
```java
try {
ChatCompletionResponse response = chatService.createChatCompletion(request);
// Process response...
} catch (ZAiHttpException e) {
// Handle HTTP-specific errors
System.err.println("HTTP Error: " + e.getMessage());
System.err.println("Status Code: " + e.statusCode);
System.err.println("Error Code: " + e.code);
} catch (Exception e) {
// Handle other exceptions
System.err.println("Unexpected error: " + e.getMessage());
e.printStackTrace();
}
```
## Extension Points
### Custom Service Implementation
```java
public class CustomService implements AIService<CustomRequest, CustomResponse> {
@Override
public CustomResponse execute(CustomRequest request) throws Exception {
// Implementation
}
@Override
public CompletableFuture<CustomResponse> executeAsync(CustomRequest request) {
// Implementation
}
@Override
public Flowable<CustomResponse> executeStream(CustomRequest request) throws Exception {
// Implementation
}
@Override
public void validateRequest(CustomRequest request) throws IllegalArgumentException {
// Validation logic
}
@Override
public String getServiceType() {
return "CUSTOM_SERVICE";
}
}
// Register custom service
client.registerService("CUSTOM_SERVICE", new CustomService());
```
### Custom Configuration
```java
// Extend configuration for custom needs
ZaiConfiguration customConfig = ZaiConfigurationBuilder.newBuilder()
.apiSecretKey("your.api.key")
.baseUrl("https://custom.endpoint")
// Add custom settings
.build();
// Add custom metadata to configuration
customConfig.getAuth().addMetadata("customAuth", "value");
customConfig.getNetwork().addMetadata("customNetwork", "value");
```
```
## Best Practices
### Configuration Management
```java
// Use builder pattern for configuration
ZaiConfig config = ZaiConfig.builder()
.apiSecretKey("your.api.key.your.api.secret")
.baseUrl("https://open.bigmodel.cn/")
.requestTimeOut(60)
.connectTimeout(30)
.readTimeout(30)
.writeTimeout(30)
.timeOutTimeUnit(TimeUnit.SECONDS)
.disableTokenCache(false)
.expireMillis(3600000) // 1 hour
.connectionPoolMaxIdleConnections(10)
.connectionPoolKeepAliveDuration(5)
.connectionPoolTimeUnit(TimeUnit.MINUTES)
.build();
ZaiClient client = new ZaiClient(config);
```
### Request Building
```java
// Use builder pattern for creating requests
ChatCompletionCreateParams request = ChatCompletionCreateParams.builder()
.model("glm-4")
.messages(Arrays.asList(
ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content("Hello, world!")
.build()
))
.temperature(0.7f)
.maxTokens(1000)
.stream(false)
.build();
```
### Error Handling
```java
// Comprehensive error handling
ChatCompletionResponse response = chatService.createChatCompletion(request);
if (!response.isSuccess()) {
System.err.println("Request failed: " + response.getMsg());
if (response.getError() != null) {
System.err.println("Error details: " + response.getError().getMessage());
}
return;
}
// Process successful response
ModelData data = response.getData();
if (data != null && data.getChoices() != null && !data.getChoices().isEmpty()) {
String content = data.getChoices().get(0).getMessage().getContent();
System.out.println("Response: " + content);
}
```
### Streaming Best Practices
```java
// Handle streaming responses properly
ChatCompletionCreateParams streamRequest = request.toBuilder()
.stream(true)
.build();
ChatCompletionResponse response = chatService.createChatCompletion(streamRequest);
if (response.isSuccess() && response.getFlowable() != null) {
response.getFlowable()
.observeOn(Schedulers.io())
.subscribe(
data -> {
// Process each streaming chunk
if (data.getChoices() != null && !data.getChoices().isEmpty()) {
String content = data.getChoices().get(0).getDelta().getContent();
if (content != null) {
System.out.print(content);
}
}
},
error -> {
System.err.println("Streaming error: " + error.getMessage());
},
() -> {
System.out.println("\nStreaming completed");
}
);
}
```
### Resource Management
```java
// Properly manage client lifecycle
try {
ZaiClient client = new ZaiClient(config);
ChatService chatService = client.chat();
// Use the service...
ChatCompletionResponse response = chatService.createChatCompletion(request);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
} finally {
// Clean up resources if needed
}
```
### Asynchronous Processing
```java
// Use async operations for long-running tasks
ChatCompletionResponse asyncResponse = chatService.asyncChatCompletion(request);
if (asyncResponse.isSuccess()) {
String taskId = asyncResponse.getData().getTaskId();
// Poll for results
AsyncResultRetrieveParams retrieveParams = new AsyncResultRetrieveParams();
retrieveParams.setId(taskId);
// Implement polling logic with backoff
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // Wait before polling
return chatService.retrieveAsyncResult(retrieveParams);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}).thenAccept(result -> {
// Handle result
if (result.isSuccess()) {
System.out.println("Async task completed");
}
});
}
```
### Security Best Practices
1. **API Key Management**: Store API keys securely, never hardcode them
2. **Token Caching**: Enable token caching to reduce authentication overhead
3. **Request Validation**: Always validate input parameters
4. **Error Logging**: Log errors but never log sensitive information
5. **Timeout Configuration**: Set appropriate timeouts to prevent hanging requests
6. **Connection Pooling**: Configure connection pools for optimal performance
7. **Rate Limiting**: Implement client-side rate limiting to respect API limits
## Migration Guide
### Upgrading to Latest Version
This guide helps you migrate from older versions of the Z-AI SDK to the current architecture.
#### Key Changes in Current Version
1. **Unified Client Architecture**: All services are now accessed through `ZaiClient`
2. **Improved Configuration**: `ZaiConfig` with builder pattern for better flexibility
3. **Standardized Request/Response**: All requests implement `ClientRequest`, responses implement `ClientResponse`
4. **Enhanced Streaming**: Better support for streaming responses with `FlowableClientResponse`
5. **Comprehensive Service Coverage**: Support for Chat, Agents, Embeddings, Files, Audio, Images, and more
#### Configuration Migration
```java
// If you were using basic configuration
// Old approach (if applicable)
String apiKey = "your-api-key";
String apiSecret = "your-api-secret";
// New approach
ZaiConfig config = ZaiConfig.builder()
.apiKey(apiKey)
.apiSecret(apiSecret)
.baseUrl("https://open.bigmodel.cn/")
.enableTokenCache(true)
.tokenExpiredSeconds(3600)
.build();
ZaiClient client = new ZaiClient(config);
```
#### Service Usage Migration
```java
// Modern service usage
ChatService chatService = client.chat();
EmbeddingService embeddingService = client.embeddings();
FileService fileService = client.files();
AudioService audioService = client.audio();
ImageService imageService = client.images();
// ... and more services
```
#### Request Building Migration
```java
// Use builder pattern for all requests
ChatCompletionCreateParams chatRequest = ChatCompletionCreateParams.builder()
.model("glm-4")
.messages(Arrays.asList(
ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content("Hello, world!")
.build()
))
.temperature(0.7f)
.maxTokens(1000)
.build();
```
#### Response Handling Migration
```java
// Standardized response handling
ChatCompletionResponse response = chatService.createChatCompletion(chatRequest);
if (response.isSuccess()) {
ModelData data = response.getData();
// Process successful response
} else {
System.err.println("Error: " + response.getMsg());
if (response.getError() != null) {
System.err.println("Details: " + response.getError().getMessage());
}
}
```
#### Streaming Migration
```java
// Enhanced streaming support
ChatCompletionCreateParams streamRequest = chatRequest.toBuilder()
.stream(true)
.build();
ChatCompletionResponse streamResponse = chatService.createChatCompletion(streamRequest);
if (streamResponse.isSuccess() && streamResponse.getFlowable() != null) {
streamResponse.getFlowable()
.subscribe(
data -> {
// Process streaming data
},
error -> {
// Handle streaming errors
},
() -> {
// Streaming completed
}
);
}
```
### Best Practices for Migration
1. **Update Dependencies**: Ensure you're using the latest version of the SDK
2. **Review Configuration**: Update your configuration to use `ZaiConfig.builder()`
3. **Update Service Access**: Use `ZaiClient` to access all services
4. **Standardize Error Handling**: Use the new response structure for error handling
5. **Test Thoroughly**: Test all functionality after migration
6. **Update Documentation**: Update your internal documentation to reflect the new patterns
This architecture provides a solid foundation for future enhancements while maintaining backward compatibility where possible.
- **Singleton Client**: For most applications, create a single `ZaiClient` instance and share it to leverage connection pooling.
- **Use Builders**: Always use the builder pattern for creating `ZaiConfig` and request objects.
- **Resource Management**: While `ZaiClient` does not require explicit closing for resource management in typical use cases, ensure your application shuts down gracefully.
- **Secure Key Management**: Store API keys securely using environment variables or a secrets management system. Do not hardcode them in your source code.

View file

@ -49,6 +49,25 @@ This SDK uses the following core dependencies:
| Jackson | 2.11.3 |
| Retrofit2 | 2.9.0 |
### Code Format
use `mvn spring-javaformat:apply` to format code.
### Run Test
1. limit inner test
```shell
mvn test
```
2. integration test
```shell
export ZAI_API_KEY=your.api.key
mvn test
```
Have Fun!
---

View file

@ -1,19 +1,21 @@
package ai.z.openapi.service.assistant.message.tools;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import ai.z.openapi.service.assistant.message.tools.code_interpreter.CodeInterpreterToolBlock;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import ai.z.openapi.service.assistant.message.tools.drawing_tool.DrawingToolBlock;
import ai.z.openapi.service.assistant.message.tools.function.FunctionToolBlock;
import ai.z.openapi.service.assistant.message.tools.retrieval.RetrievalToolBlock;
import ai.z.openapi.service.assistant.message.tools.web_browser.WebBrowserToolBlock;
import ai.z.openapi.service.deserialize.JsonTypeMapping;
import ai.z.openapi.service.deserialize.assistant.message.tools.ToolsTypeDeserializer;
@JsonTypeMapping({ WebBrowserToolBlock.class, RetrievalToolBlock.class, FunctionToolBlock.class, DrawingToolBlock.class,
CodeInterpreterToolBlock.class, })
@JsonDeserialize(using = ToolsTypeDeserializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({ @JsonSubTypes.Type(value = WebBrowserToolBlock.class, name = "web_browser"),
@JsonSubTypes.Type(value = RetrievalToolBlock.class, name = "retrieval"),
@JsonSubTypes.Type(value = FunctionToolBlock.class, name = "function"),
@JsonSubTypes.Type(value = CodeInterpreterToolBlock.class, name = "code_interpreter"),
@JsonSubTypes.Type(value = DrawingToolBlock.class, name = "drawing_tool") })
public abstract class ToolsType {
}

View file

@ -1,11 +1,19 @@
package ai.z.openapi.service.assistant.message.tools.code_interpreter;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* This class represents a code interpreter that executes code and returns the results.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeInterpreter {
/**
@ -20,22 +28,4 @@ public class CodeInterpreter {
@JsonProperty("outputs")
private List<CodeInterpreterToolOutput> outputs;
// Getters and Setters
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public List<CodeInterpreterToolOutput> getOutputs() {
return outputs;
}
public void setOutputs(List<CodeInterpreterToolOutput> outputs) {
this.outputs = outputs;
}
}

View file

@ -3,13 +3,21 @@ package ai.z.openapi.service.assistant.message.tools.code_interpreter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ai.z.openapi.service.assistant.message.tools.ToolsType;
import ai.z.openapi.service.deserialize.JsonTypeField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* This class represents a block of code tool data.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeField("code_interpreter")
public class CodeInterpreterToolBlock extends ToolsType {
/**
@ -24,22 +32,4 @@ public class CodeInterpreterToolBlock extends ToolsType {
@JsonProperty("type")
private String type = "code_interpreter";
// Getters and Setters
public CodeInterpreter getCodeInterpreter() {
return codeInterpreter;
}
public void setCodeInterpreter(CodeInterpreter codeInterpreter) {
this.codeInterpreter = codeInterpreter;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -1,10 +1,16 @@
package ai.z.openapi.service.assistant.message.tools.code_interpreter;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class represents the output result of a code tool.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeInterpreterToolOutput {
/**
@ -25,30 +31,4 @@ public class CodeInterpreterToolOutput {
@JsonProperty("error_msg")
private String errorMsg;
// Getters and Setters
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLogs() {
return logs;
}
public void setLogs(String logs) {
this.logs = logs;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}

View file

@ -1,11 +1,18 @@
package ai.z.openapi.service.assistant.message.tools.drawing_tool;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* The input string that was used to generate the drawing.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DrawingTool {
/**
@ -20,22 +27,4 @@ public class DrawingTool {
@JsonProperty("outputs")
private List<DrawingToolOutput> outputs;
// Getters and Setters
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public List<DrawingToolOutput> getOutputs() {
return outputs;
}
public void setOutputs(List<DrawingToolOutput> outputs) {
this.outputs = outputs;
}
}

View file

@ -3,13 +3,21 @@ package ai.z.openapi.service.assistant.message.tools.drawing_tool;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ai.z.openapi.service.assistant.message.tools.ToolsType;
import ai.z.openapi.service.deserialize.JsonTypeField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* This class represents a block of drawing tool data.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeField("drawing_tool")
public class DrawingToolBlock extends ToolsType {
/**
@ -24,22 +32,4 @@ public class DrawingToolBlock extends ToolsType {
@JsonProperty("type")
private String type = "drawing_tool";
// Getters and Setters
public DrawingTool getDrawingTool() {
return drawingTool;
}
public void setDrawingTool(DrawingTool drawingTool) {
this.drawingTool = drawingTool;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -1,10 +1,16 @@
package ai.z.openapi.service.assistant.message.tools.drawing_tool;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class represents the output of a drawing tool, containing the generated image.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DrawingToolOutput {
/**
@ -13,14 +19,4 @@ public class DrawingToolOutput {
@JsonProperty("image")
private String image;
// Getters and Setters
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
}

View file

@ -2,12 +2,18 @@ package ai.z.openapi.service.assistant.message.tools.function;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* This class represents a function tool with a name, arguments, and outputs.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FunctionTool {
/**
@ -20,8 +26,7 @@ public class FunctionTool {
* The arguments for the function tool, which can be a string or a dictionary.
*/
@JsonProperty("arguments")
private JsonNode arguments; // Union type in Java can be represented by Object, and
// deserialization handles it accordingly
private JsonNode arguments;
/**
* A list of outputs generated by the function tool.
@ -29,30 +34,4 @@ public class FunctionTool {
@JsonProperty("outputs")
private List<FunctionToolOutput> outputs;
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JsonNode getArguments() {
return arguments;
}
public void setArguments(JsonNode arguments) {
this.arguments = arguments;
}
public List<FunctionToolOutput> getOutputs() {
return outputs;
}
public void setOutputs(List<FunctionToolOutput> outputs) {
this.outputs = outputs;
}
}

View file

@ -3,13 +3,21 @@ package ai.z.openapi.service.assistant.message.tools.function;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ai.z.openapi.service.assistant.message.tools.ToolsType;
import ai.z.openapi.service.deserialize.JsonTypeField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* This class represents a block of function tool data.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeField("function")
public class FunctionToolBlock extends ToolsType {
/**
@ -24,22 +32,4 @@ public class FunctionToolBlock extends ToolsType {
@JsonProperty("type")
private String type = "function";
// Getters and Setters
public FunctionTool getFunction() {
return function;
}
public void setFunction(FunctionTool function) {
this.function = function;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -1,10 +1,16 @@
package ai.z.openapi.service.assistant.message.tools.function;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class represents the output of a function tool, containing the generated content.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FunctionToolOutput {
/**
@ -13,12 +19,4 @@ public class FunctionToolOutput {
@JsonProperty("content")
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View file

@ -1,11 +1,20 @@
package ai.z.openapi.service.assistant.message.tools.retrieval;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* This class represents the outputs of a retrieval tool.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RetrievalTool {
/**
@ -15,12 +24,4 @@ public class RetrievalTool {
@JsonProperty("outputs")
private List<RetrievalToolOutput> outputs;
public List<RetrievalToolOutput> getOutputs() {
return outputs;
}
public void setOutputs(List<RetrievalToolOutput> outputs) {
this.outputs = outputs;
}
}

View file

@ -1,15 +1,23 @@
package ai.z.openapi.service.assistant.message.tools.retrieval;
import ai.z.openapi.service.assistant.message.tools.ToolsType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import ai.z.openapi.service.deserialize.JsonTypeField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* This class represents a block for invoking the retrieval tool.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeField("retrieval")
public class RetrievalToolBlock {
public class RetrievalToolBlock extends ToolsType {
/**
* An instance of the RetrievalTool class containing the retrieval outputs.
@ -23,22 +31,4 @@ public class RetrievalToolBlock {
@JsonProperty("type")
private String type = "retrieval";
// Getters and Setters
public RetrievalTool getRetrieval() {
return retrieval;
}
public void setRetrieval(RetrievalTool retrieval) {
this.retrieval = retrieval;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -1,10 +1,18 @@
package ai.z.openapi.service.assistant.message.tools.retrieval;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class represents the output of a retrieval tool.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RetrievalToolOutput {
/**
@ -20,22 +28,4 @@ public class RetrievalToolOutput {
@JsonProperty("document")
private String document;
// Getters and Setters
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getDocument() {
return document;
}
public void setDocument(String document) {
this.document = document;
}
}

View file

@ -1,11 +1,18 @@
package ai.z.openapi.service.assistant.message.tools.web_browser;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* This class represents the input and outputs of a web browser search.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WebBrowser {
/**
@ -20,22 +27,4 @@ public class WebBrowser {
@JsonProperty("outputs")
private List<WebBrowserOutput> outputs;
// Getters and Setters
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public List<WebBrowserOutput> getOutputs() {
return outputs;
}
public void setOutputs(List<WebBrowserOutput> outputs) {
this.outputs = outputs;
}
}

View file

@ -1,10 +1,16 @@
package ai.z.openapi.service.assistant.message.tools.web_browser;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class represents the output of a web browser search result.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WebBrowserOutput {
/**
@ -31,38 +37,4 @@ public class WebBrowserOutput {
@JsonProperty("error_msg")
private String errorMsg;
// Getters and Setters
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}

View file

@ -3,13 +3,21 @@ package ai.z.openapi.service.assistant.message.tools.web_browser;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ai.z.openapi.service.assistant.message.tools.ToolsType;
import ai.z.openapi.service.deserialize.JsonTypeField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* This class represents a block for invoking the web browser tool.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeField("web_browser")
public class WebBrowserToolBlock extends ToolsType {
/**
@ -24,22 +32,4 @@ public class WebBrowserToolBlock extends ToolsType {
@JsonProperty("type")
private String type = "web_browser";
// Getters and Setters
public WebBrowser getWebBrowser() {
return webBrowser;
}
public void setWebBrowser(WebBrowser webBrowser) {
this.webBrowser = webBrowser;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}