diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..48dce43 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,32 @@ +name: 🐞 Bug Report +description: File a bug report +title: "[Bug]: " +type: "Bug" +body: + - type: markdown + attributes: + value: | + Thanks for stopping by to let us know something could be better! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us what you expected to happen and how to reproduce the issue. + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/THUDM/z-ai-sdk-java/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..026fa12 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,40 @@ +name: 💡 Feature Request +description: Suggest an idea for this repository +title: "[Feat]: " +type: "Feature" +body: + - type: markdown + attributes: + value: | + Thanks for stopping by to let us know something could be better! + - type: textarea + id: problem + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. + placeholder: Ex. I'm always frustrated when [...] + - type: textarea + id: describe + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/THUDM/z-ai-sdk-java/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..21b8587 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +# Description + +Thank you for opening a Pull Request! +Before submitting your PR, there are a few things you can do to make sure it goes smoothly: + +- [ ] Follow the [`CONTRIBUTING` Guide](https://github.com/THUDM/z-ai-sdk-java/blob/main/CONTRIBUTING.md). +- [ ] Make your Pull Request title in the specification. +- [ ] Ensure the tests pass (Run `mvn clean test` from the repository root) +- [ ] Appropriate docs were updated (if necessary) + +Fixes # diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7ab0acc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build and Test +on: + pull_request: + branches: + - main + push: + branches: + - 'action*' + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + + - name: Validate Java Format + run: mvn spring-javaformat:validate + + - name: Build with Maven + run: mvn -B package diff --git a/.github/workflows/lint-pr.yaml b/.github/workflows/lint-pr.yaml new file mode 100644 index 0000000..1b8a44e --- /dev/null +++ b/.github/workflows/lint-pr.yaml @@ -0,0 +1,27 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - reopened + +jobs: + lint-pr: + name: Validate PR title + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + subjectPattern: ^[a-zA-Z0-9!-_@#=*`.|+,\s]+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pr title "{title}" didn't match the + configured pattern. Can only chars, numbers and symbols !-_@#=*`|+. \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..508fa21 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Publish package to the Maven Central Repository +on: + release: + types: [created] + workflow_dispatch: + +jobs: + pre-build: + uses: + ./.github/workflows/build.yml + publish-release: + needs: + - pre-build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Maven Central Repository + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + - name: Publish package to Maven Central + run: mvn --batch-mode clean deploy -P release + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.gitignore b/.gitignore index 524f096..792704b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,45 @@ -# Compiled class file -*.class +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** -# Log file -*.log +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache -# BlueJ files -*.ctxt +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +### mvn release:prepare temp files +/*/release.properties +/*/pom.xml.releaseBackup +/*/pom.xml.versionsBackup +/release.properties +/pom.xml.releaseBackup +/pom.xml.versionsBackup +.flattened-pom.xml -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* +### VS Code ### +.vscode/ +logs/data.log +logs/error.log +logs/ +app.pid +/data/ diff --git a/.springjavaformatconfig b/.springjavaformatconfig new file mode 100644 index 0000000..6d408bb --- /dev/null +++ b/.springjavaformatconfig @@ -0,0 +1 @@ +java-baseline=8 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..29ee511 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,828 @@ +# Z.ai SDK Java - Architecture Documentation + +## 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. + +## Architecture Components + +### 1. Core Client Architecture + +#### ZaiClient +The main client class that serves as the entry point for all AI services: + +```java +public class ZaiClient extends AbstractClientBaseService { + // Service instances + private ChatService chatService; + private AgentService agentService; + private EmbeddingService embeddingService; + // ... other services + + // Constructor + public ZaiClient(ZaiConfig config) { + // Initialize HTTP client and Retrofit + } + + // Service accessors + public synchronized ChatService chat() { /* ... */ } + public synchronized AgentService agents() { /* ... */ } + // ... other service accessors +} +``` + +#### Base Request and Response Models + +**ClientRequest Interface**: Base interface for all service requests +```java +public interface ClientRequest { + // Marker interface for type safety +} +``` + +**ClientResponse Interface**: Base interface for all service responses +```java +public interface ClientResponse { + 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 extends ClientResponse { + void setFlowable(Flowable stream); +} +``` + +### 2. Configuration Management + +#### ZaiConfig +Main configuration class that contains all SDK settings: + +```java +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ZaiConfig { + private String baseUrl; + private String apiSecretKey; + private String apiKey; + private String apiSecret; + private int expireMillis = 30 * 60 * 1000; // 30 minutes + private String alg = "HS256"; + private boolean disableTokenCache; + + // Connection pool 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; + private int writeTimeout; + private TimeUnit timeOutTimeUnit; + + private String source_channel; +} +``` + +#### Configuration Features + +1. **Authentication Settings** + - API secret key in format `{apiKey}.{apiSecret}` + - JWT token expiration and algorithm configuration + - Token caching control + +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: + +```java +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 +public class ChatServiceImpl implements ChatService { + private final ZaiClient zAiClient; + private final ChatApi chatApi; + + public ChatServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + 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); + } + } +} +``` + +## Usage Examples + +### Basic Configuration + +```java +// Simple configuration with API secret key +ZaiConfig config = new ZaiConfig("your.api.key.your.api.secret"); +ZaiClient client = new ZaiClient(config); + +// Or using separate API key and secret +ZaiConfig config = new ZaiConfig("your.api.key", "your.api.secret"); +ZaiClient client = new ZaiClient(config); +``` + +### 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 + +```java +ZaiConfig config = ZaiConfig.builder() + .apiSecretKey("your.api.key.your.api.secret") + .baseUrl("https://custom.api.endpoint") + .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 + +```java +// Get service instance +ChatService chatService = client.chat(); + +// Create chat request +ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model("glm-4") + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessage.Role.USER) + .content("Hello, world!") + .build() + )) + .stream(false) // Set to true for streaming + .temperature(0.7f) + .maxTokens(1024) + .build(); + +// Execute request +try { + 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()); +} +``` + +### Streaming Usage + +```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 +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); + } + } + }, + error -> System.err.println("\nStream error: " + error.getMessage()), + () -> System.out.println("\nStream completed") + ); +} 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: + +```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 { + private String model; + private List messages; + private Boolean stream; + private Float temperature; + @JsonProperty("max_tokens") + private Integer maxTokens; + private List stop; + private List tools; + // ... other fields +} +``` + +### Response Structure +All responses implement `ClientResponse` or `FlowableClientResponse`: + +```java +@Data +public class ChatCompletionResponse implements FlowableClientResponse { + private int code; + private String msg; + private boolean success; + private ModelData data; + private Flowable flowable; // For streaming responses + private ChatError error; +} +``` + +## Error Handling + +### Response Error Handling + +```java +ChatCompletionResponse response = chatService.createChatCompletion(request); + +// 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) { + 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"); + } + } + 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 { + @Override + public CustomResponse execute(CustomRequest request) throws Exception { + // Implementation + } + + @Override + public CompletableFuture executeAsync(CustomRequest request) { + // Implementation + } + + @Override + public Flowable 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. \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b01039c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,91 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + +Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the +Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Z.ai Open Source Strategy team. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b8ec2a6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# How to contribute + +We'd love to accept your patches and contributions to this project. + +## Contribution process + +### Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +### Contributor Guide + +You may follow these steps to contribute: + +1. **Fork the official repository.** This will create a copy of the official repository in your own account. +2. **Sync the branches.** This will ensure that your copy of the repository is up-to-date with the latest changes from the official repository. +3. **Work on your forked repository's feature branch.** This is where you will make your changes to the code. +4. **Commit your updates on your forked repository's feature branch.** This will save your changes to your copy of the repository. +5. **Submit a pull request to the official repository's main branch.** This will request that your changes be merged into the official repository. +6. **Resolve any linting errors.** This will ensure that your changes are formatted correctly. + +Here are some additional things to keep in mind during the process: + +- **Test your changes.** Before you submit a pull request, make sure that your changes work as expected. +- **Be patient.** It may take some time for your pull request to be reviewed and merged. + + +### Environment Setup +For running unit tests, set up your environment variables with your API credentials: + +```bash +export ZAI_BASE_URL=https://api.z.ai/api/paas/v4/ # Default ZAI API endpoint +export ZAI_API_KEY=your_api_key_here # Replace with your actual API key +``` + +> ⚠️ **Note**: Running tests will consume a small amount of tokens from your API account. + +### Dependencies + +This SDK uses the following core dependencies: + +| Library | Version | +|---------|----------| +| OkHttp | 3.14.9 | +| Java JWT | 4.2.2 | +| Jackson | 2.11.3 | +| Retrofit2 | 2.9.0 | + + +Have Fun! +--- diff --git a/LICENSE b/LICENSE index ef00698..09d03ab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Z.ai & THUKEG +Copyright (c) Z.ai, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a50fa2f..92bfd97 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,368 @@ -# z-ai-sdk-java -Java SDK for z.ai +# Z.ai Open Platform Java SDK + +[![Maven Central](https://img.shields.io/maven-central/v/ai.z/z-ai-sdk.svg)](https://search.maven.org/artifact/ai.z/z-ai-sdk) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Java](https://img.shields.io/badge/java-1.8%2B-orange.svg)](https://www.oracle.com/java/) + +[中文文档](README_CN.md) | English + +**Not yet released.** + +The official Java SDK for Z.ai and ZHIPU AI platforms, providing a unified interface to access powerful AI capabilities including chat completion, embeddings, image generation, audio processing, and more. + +## ✨ Features + +- 🚀 **Type-safe API**: All interfaces are fully type-encapsulated, no need to consult API documentation +- 🔧 **Easy Integration**: Simple and intuitive API design for quick integration +- ⚡ **High Performance**: Built with modern Java libraries for optimal performance +- 🛡️ **Secure**: Built-in authentication and token management +- 📦 **Lightweight**: Minimal dependencies for easy project integration + +## 📦 Installation + +### Requirements +- Java 1.8 or higher +- Maven or Gradle +- Not supported on Android platform + +### Maven +Add the following dependency to your `pom.xml`: + +```xml + + ai.z + z-ai-sdk + 0.0.1 + +``` + +### Gradle +Add the following dependency to your `build.gradle` (for Groovy DSL): + +```groovy +dependencies { + implementation 'ai.z:z-ai-sdk:0.0.1' +} +``` + +Or `build.gradle.kts` (for Kotlin DSL): + +```kotlin +dependencies { + implementation("ai.z:z-ai-sdk:0.0.1") +} +``` + +### 📋 Dependencies + +This SDK uses the following core dependencies: + +| Library | Version | +|---------|----------| +| OkHttp | 4.12.0 | +| Java JWT | 4.4.0 | +| Jackson | 2.17.2 | +| Retrofit2 | 2.11.0 | +| RxJava | 3.1.8 | +| SLF4J | 2.0.16 | + +## 🚀 Quick Start + +### Basic Usage + +1. **Create a ZaiClient** with your API credentials +2. **Access services** through the client +3. **Call API methods** with typed parameters + +```java +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.model.*; +import ai.z.openapi.core.Constants; + +// Create client with API key, recommend export the ENV api-key +// export ZAI_API_KEY=your.api.key +ZaiClient client = ZaiClient.builder().build(); + +// Or set the api-key by code +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .build(); + +// Or create client for specific platform +ZaiClient zhipuClient = ZaiClient.ofZHIPU("your.api.key.your.api.secret").build(); +``` + +### Client Configuration + +The SDK provides a flexible builder pattern for customizing your client: + +```java +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .baseUrl("https://api.z.ai/api/paas/v4/") + .enableTokenCache() + .tokenExpire(3600000) // 1 hour + .connectionPool(10, 5, TimeUnit.MINUTES) + .build(); +``` + +## 💡 Examples + +### Chat Completion + +```java +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.model.*; +import ai.z.openapi.core.Constants; +import java.util.Arrays; + +// Create client +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .build(); + +// Create chat request +ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("Hello, how are you?") + .build() + )) + .stream(false) + .temperature(0.7f) + .maxTokens(1024) + .build(); + +// Execute request +ChatCompletionResponse response = client.chat().createChatCompletion(request); + +if (response.isSuccess()) { + String content = response.getData().getChoices().get(0).getMessage().getContent(); + System.out.println("Response: " + content); +} else { + System.err.println("Error: " + response.getMsg()); +} +``` + +### Streaming Chat + +```java +// Create streaming request +ChatCompletionCreateParams streamRequest = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("Tell me a story") + .build() + )) + .stream(true) // Enable streaming + .build(); + +// Execute streaming request +ChatCompletionResponse response = client.chat().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); + } + } + }, + error -> System.err.println("\nStream error: " + error.getMessage()), + () -> System.out.println("\nStream completed") + ); +} +``` + +### Function Calling + +```java +// Define function +ChatTool weatherTool = ChatTool.builder() + .type(ChatToolType.FUNCTION.value()) + .function(ChatFunction.builder() + .name("get_weather") + .description("Get current weather for a location") + .parameters(ChatFunctionParameters.builder() + .type("object") + .properties(Map.of( + "location", Map.of( + "type", "string", + "description", "City name" + ) + )) + .required(Arrays.asList("location")) + .build()) + .build()) + .build(); + +// Create request with function +ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("What's the weather like in Beijing?") + .build() + )) + .tools(Arrays.asList(weatherTool)) + .toolChoice("auto") + .build(); + +ChatCompletionResponse response = client.chat().createChatCompletion(request); +``` + +### Embeddings + +```java +import ai.z.openapi.service.embedding.*; + +// Create embedding request +EmbeddingCreateParams request = EmbeddingCreateParams.builder() + .model(Constants.ModelEmbedding3) + .input(Arrays.asList("Hello world", "How are you?")) + .build(); + +// Execute request +EmbeddingResponse response = client.embeddings().create(request); + +if (response.isSuccess()) { + response.getData().getData().forEach(embedding -> { + System.out.println("Embedding: " + embedding.getEmbedding()); + }); +} +``` + +### Image Generation + +```java +import ai.z.openapi.service.image.*; + +// Create image generation request +CreateImageRequest request = CreateImageRequest.builder() + .model(Constants.ModelCogView3Plus) + .prompt("A beautiful sunset over mountains") + .size("1024x1024") + .quality("standard") + .n(1) + .build(); + +// Execute request +ImageResponse response = client.images().generate(request); + +if (response.isSuccess()) { + response.getData().getData().forEach(image -> { + System.out.println("Image URL: " + image.getUrl()); + }); +} +``` + +### Spring Boot Integration + +```java +@RestController +public class AIController { + + private final ZaiClient zaiClient; + + public AIController() { + this.zaiClient = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .enableTokenCache() + .build(); + } + + @PostMapping("/chat") + public ResponseEntity chat(@RequestBody ChatRequest request) { + ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content(request.getMessage()) + .build() + )) + .build(); + + ChatCompletionResponse response = zaiClient.chat().createChatCompletion(params); + + if (response.isSuccess()) { + String content = response.getData().getChoices().get(0).getMessage().getContent(); + return ResponseEntity.ok(content); + } else { + return ResponseEntity.badRequest().body(response.getMsg()); + } + } +} +``` + +## 🔧 Available Services + +The ZaiClient provides access to comprehensive AI services: + +| Service | Description | Key Features | +|---------|-------------|-------------| +| **Chat** | Text generation and conversation | Streaming, function calling, async support | +| **Embeddings** | Text embeddings generation | Multiple embedding models | +| **Images** | Image generation and processing | CogView models, various sizes | +| **Audio** | Speech synthesis and recognition | Text-to-speech, speech-to-text | +| **Files** | File management and processing | Upload, download, batch processing | +| **Assistants** | AI assistant management | Create, configure, and manage assistants | +| **Agents** | Agent-based completions | Specialized agent interactions | +| **Knowledge** | Knowledge base operations | Document indexing and retrieval | +| **Fine-tuning** | Model customization | Train custom models | +| **Batch** | Batch processing | Bulk operations | +| **Web Search** | Web search integration | Real-time web information | +| **Videos** | Video processing | Video analysis and generation | + +## 🎯 Supported Models + +### Text Generation +- `glm-4-plus` - Enhanced GLM-4 with improved capabilities +- `glm-4` - Standard GLM-4 model +- `glm-4-air` - Lightweight version for speed +- `glm-4-flash` - Ultra-fast response model +- `glm-4-long` - Optimized for long-context conversations +- `glm-4-voice` - Specialized for voice interactions + +### Vision Models +- `glm-4v-plus` - Enhanced vision model +- `glm-4v` - Standard vision model + +### Image Generation +- `cogview-3-plus` - Enhanced image generation +- `cogview-3` - Standard image generation + +### Embeddings +- `embedding-3` - Latest embedding model +- `embedding-2` - Previous generation embedding + +### Specialized +- `charglm-3` - Character interaction model +- `cogtts` - Text-to-speech model + +## 📈 Release Notes + +For detailed release notes and version history, please see [Release-Note.md](Release-Note.md). + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 🤝 Contributing + +We welcome contributions! Please feel free to submit a Pull Request. + +## 📞 Support + +For questions and support: +- Visit [Z.ai Platform](https://z.ai/) +- Visit [ZHIPU AI Open Platform](http://open.bigmodel.cn/) +- Check our [Architecture Documentation](ARCHITECTURE.md) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..daee846 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,396 @@ +# Z.ai Open Platform Java SDK + +[![Maven Central](https://img.shields.io/maven-central/v/ai.z/z-ai-sdk.svg)](https://search.maven.org/artifact/ai.z/z-ai-sdk) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Java](https://img.shields.io/badge/java-1.8%2B-orange.svg)](https://www.oracle.com/java/) + +[English Readme](README.md) + +Z.ai AI 平台官方 Java SDK,提供统一接口访问强大的AI能力,包括对话补全、向量嵌入、图像生成、音频处理等功能。 + +## ✨ 特性 + +- 🚀 **类型安全**: 所有接口完全类型封装,无需查阅API文档即可完成接入 +- 🔧 **简单易用**: 简洁直观的API设计,快速上手 +- ⚡ **高性能**: 基于现代Java库构建,性能优异 +- 🛡️ **安全可靠**: 内置身份验证和令牌管理 +- 📦 **轻量级**: 最小化依赖,易于项目集成 + +## 📦 安装 + +### 环境要求 +- Java 1.8 或更高版本 +- Maven 或 Gradle +- 尚不支持在 Android 平台运行 + +### Maven 依赖 +在您的 `pom.xml` 中添加以下依赖: + +```xml + + ai.z + z-ai-sdk + 0.0.1 + +``` + +### Gradle 依赖 +在您的 `build.gradle` 中添加以下依赖(适用于 Groovy DSL): + +```groovy +dependencies { + implementation 'ai.z:z-ai-sdk:0.0.1' +} +``` + +或 `build.gradle.kts`(适用于 Kotlin DSL): + +```kotlin +dependencies { + implementation("ai.z:z-ai-sdk:0.0.1") +} +``` + +### 📋 核心依赖 + +本SDK使用以下核心依赖库: + +| 依赖库 | 版本 | +|--------|------| +| OkHttp | 4.12.0 | +| Java JWT | 4.4.0 | +| Jackson | 2.17.2 | +| Retrofit2 | 2.11.0 | +| RxJava | 3.1.8 | +| SLF4J | 2.0.16 | + +## 🚀 快速开始 + +### 基本用法 + +1. **使用API凭证创建ZaiClient** +2. **通过客户端访问服务** +3. **使用类型化参数调用API方法** + +```java +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.model.*; +import ai.z.openapi.core.Constants; + +// 创建客户端 推荐使用环境变量设置API凭证 +// export ZAI_API_KEY=your.api.key +ZaiClient client = ZaiClient.builder().build(); + +// 或代码设置凭证 +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .build(); + +// 或为特定平台创建客户端 +ZaiClient zhipuClient = ZaiClient.ofZHIPU("your.api.key.your.api.secret").build(); +``` + +### 客户端配置 + +SDK提供了灵活的构建器模式来自定义您的客户端: + +```java +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .baseUrl("https://api.z.ai/api/paas/v4/") + .enableTokenCache() + .tokenExpire(3600000) // 1小时 + .connectionPool(10, 5, TimeUnit.MINUTES) + .build(); +``` + +## 💡 使用示例 + +### 对话补全 + +```java +import ai.z.openapi.ZaiClient; +import ai.z.openapi.service.model.*; +import ai.z.openapi.core.Constants; +import java.util.Arrays; + +// 创建客户端 +ZaiClient client = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .build(); + +// 创建对话请求 +ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("你好,你怎么样?") + .build() + )) + .stream(false) + .temperature(0.7f) + .maxTokens(1024) + .build(); + +// 执行请求 +ChatCompletionResponse response = client.chat().createChatCompletion(request); + +if (response.isSuccess()) { + String content = response.getData().getChoices().get(0).getMessage().getContent(); + System.out.println("回复: " + content); +} else { + System.err.println("错误: " + response.getMsg()); +} +``` + +### 流式对话 + +```java +// 创建流式请求 +ChatCompletionCreateParams streamRequest = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("给我讲个故事") + .build() + )) + .stream(true) // 启用流式 + .build(); + +// 执行流式请求 +ChatCompletionResponse response = client.chat().createChatCompletion(streamRequest); + +if (response.isSuccess() && response.getFlowable() != null) { + response.getFlowable().subscribe( + data -> { + // 处理流式数据块 + 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("\n流式错误: " + error.getMessage()), + () -> System.out.println("\n流式完成") + ); +} +``` + +### 函数调用 + +```java +// 定义函数 +ChatTool weatherTool = ChatTool.builder() + .type(ChatToolType.FUNCTION.value()) + .function(ChatFunction.builder() + .name("get_weather") + .description("获取指定地点的当前天气") + .parameters(ChatFunctionParameters.builder() + .type("object") + .properties(Map.of( + "location", Map.of( + "type", "string", + "description", "城市名称" + ) + )) + .required(Arrays.asList("location")) + .build()) + .build()) + .build(); + +// 创建带函数的请求 +ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("北京的天气怎么样?") + .build() + )) + .tools(Arrays.asList(weatherTool)) + .toolChoice("auto") + .build(); + +ChatCompletionResponse response = client.chat().createChatCompletion(request); +``` + +### 向量嵌入 + +```java +import ai.z.openapi.service.embedding.*; + +// 创建嵌入请求 +EmbeddingCreateParams request = EmbeddingCreateParams.builder() + .model(Constants.ModelEmbedding3) + .input(Arrays.asList("你好世界", "你好吗?")) + .build(); + +// 执行请求 +EmbeddingResponse response = client.embeddings().create(request); + +if (response.isSuccess()) { + response.getData().getData().forEach(embedding -> { + System.out.println("嵌入向量: " + embedding.getEmbedding()); + }); +} +``` + +### 图像生成 + +```java +import ai.z.openapi.service.image.*; + +// 创建图像生成请求 +CreateImageRequest request = CreateImageRequest.builder() + .model(Constants.ModelCogView3Plus) + .prompt("山间美丽的日落") + .size("1024x1024") + .quality("standard") + .n(1) + .build(); + +// 执行请求 +ImageResponse response = client.images().generate(request); + +if (response.isSuccess()) { + response.getData().getData().forEach(image -> { + System.out.println("图像URL: " + image.getUrl()); + }); +} +``` + +### Spring Boot 集成 + +```java +@RestController +public class AIController { + + private final ZaiClient zaiClient; + + public AIController() { + this.zaiClient = ZaiClient.builder() + .apiKey("your.api.key.your.api.secret") + .enableTokenCache() + .build(); + } + + @PostMapping("/chat") + public ResponseEntity chat(@RequestBody ChatRequest request) { + ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content(request.getMessage()) + .build() + )) + .build(); + + ChatCompletionResponse response = zaiClient.chat().createChatCompletion(params); + + if (response.isSuccess()) { + String content = response.getData().getChoices().get(0).getMessage().getContent(); + return ResponseEntity.ok(content); + } else { + return ResponseEntity.badRequest().body(response.getMsg()); + } + } +} +``` + +## 🔧 可用服务 + +### Chat 服务 +- 对话补全(同步/异步) +- 流式对话 +- 函数调用 +- 多模态对话(文本+图像) + +### Embeddings 服务 +- 文本向量化 +- 批量嵌入 +- 多种嵌入模型 + +### Images 服务 +- 文本到图像生成 +- 图像编辑 +- 图像变体 + +### Audio 服务 +- 语音转文本 +- 文本转语音 +- 语音翻译 + +### Files 服务 +- 文件上传 +- 文件管理 +- 文件检索 + +### Assistants 服务 +- AI助手创建 +- 助手管理 +- 对话线程 + +### Agents 服务 +- 智能代理 +- 工作流管理 +- 任务执行 + +### Knowledge 服务 +- 知识库管理 +- 文档处理 +- 知识检索 + +### Batch 服务 +- 批量处理 +- 异步任务 +- 结果管理 + +## 🤖 支持的模型 + +### 文本生成模型 +- `glm-4-plus` - 最新的GLM-4 Plus模型 +- `glm-4-0520` - GLM-4标准版 +- `glm-4-long` - 长文本处理版本 +- `glm-4-airx` - 轻量级版本 +- `glm-4-air` - 快速响应版本 +- `glm-4-flashx` - 超快响应版本 +- `glm-4-flash` - 闪电版本 + +### 视觉模型 +- `glm-4v-plus` - 多模态理解模型 +- `glm-4v` - 视觉理解模型 + +### 图像生成模型 +- `cogview-3-plus` - 高质量图像生成 +- `cogview-3` - 标准图像生成 + +### 嵌入模型 +- `embedding-3` - 最新嵌入模型 +- `embedding-2` - 标准嵌入模型 + +### 专业模型 +- `charglm-3` - 角色扮演模型 +- `emohaa` - 情感分析模型 + +## 📈 版本更新 + +详细的版本更新记录和历史信息,请查看 [Release-Note.md](Release-Note.md)。 + +## 📄 许可证 + +本项目基于 MIT 许可证开源 - 详情请查看 [LICENSE](LICENSE) 文件。 + +## 🤝 贡献 + +欢迎贡献代码!请随时提交 Pull Request。 + +## 📞 支持 + +如有问题和技术支持: +- Visit [Z.ai Platform](https://z.ai/) +- Visit [ZHIPU AI Open Platform](http://open.bigmodel.cn/) +- Check our [Architecture Documentation](ARCHITECTURE.md) diff --git a/Release-Note.md b/Release-Note.md new file mode 100644 index 0000000..f1df9cf --- /dev/null +++ b/Release-Note.md @@ -0,0 +1,4 @@ +# Release Notes + +### 0.0.1 +- ✅ Initial the Z.ai sdk refer from ZHIPU sdk \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..aa36953 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,237 @@ + + + 4.0.0 + + ai.z.openapi + z-ai-sdk-parent + ${revision} + ../pom.xml + + z-ai-sdk + + jar + + Java sdk for Z.ai open api + Java SDK for Z.ai Open Platform API + https://z.ai + + + MIT License + https://www.opensource.org/licenses/mit-license.php + repo + + + + + Z.ai + Z.ai + user_feedback@z.ai + https://z.ai/model-api + Z.ai + https://z.ai + + architect + developer + + Asia/Shanghai + + + + scm:git:https://github.com/THUDM/z-ai-sdk-java.git + scm:git:https://github.com/THUDM/z-ai-sdk-java.git + HEAD + https://github.com/THUDM/z-ai-sdk-java + + + + 8 + 8 + UTF-8 + 2.0.11 + 3.14.9 + 2.11.3 + 2.9.0 + 3.1.8 + 4.2.2 + 1.18.32 + 2.9.0 + 5.10.2 + 1.19.8 + 5.15.0 + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + pom + test + + + + + com.squareup.okhttp3 + okhttp-sse + ${okhttp.version} + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.squareup.okhttp3 + logging-interceptor + ${okhttp.version} + + + io.reactivex.rxjava3 + rxjava + ${rxjava.version} + + + com.squareup.retrofit2 + adapter-rxjava2 + ${retrofit2.version} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit2.version} + + + + + com.auth0 + java-jwt + ${jwt.version} + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + org.apache.tika + tika-core + ${tika.version} + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.test.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.test.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.test.version} + test + + + + + org.testcontainers + testcontainers + ${testcontainer.version} + test + + + org.testcontainers + mockserver + ${testcontainer.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainer.version} + test + + + + + org.mock-server + mockserver-netty + ${mockserver.version} + test + + + org.mock-server + mockserver-client-java + ${mockserver.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + + diff --git a/core/src/main/java/ai/z/openapi/ZaiClient.java b/core/src/main/java/ai/z/openapi/ZaiClient.java new file mode 100644 index 0000000..abe9ccc --- /dev/null +++ b/core/src/main/java/ai/z/openapi/ZaiClient.java @@ -0,0 +1,646 @@ +package ai.z.openapi; + +import ai.z.openapi.service.AbstractClientBaseService; +import ai.z.openapi.service.model.ChatError; +import ai.z.openapi.service.model.ZAiHttpException; +import ai.z.openapi.service.chat.ChatService; +import ai.z.openapi.service.chat.ChatServiceImpl; +import ai.z.openapi.service.agents.AgentService; +import ai.z.openapi.service.agents.AgentServiceImpl; +import ai.z.openapi.service.embedding.EmbeddingService; +import ai.z.openapi.service.embedding.EmbeddingServiceImpl; +import ai.z.openapi.service.file.FileService; +import ai.z.openapi.service.file.FileServiceImpl; +import ai.z.openapi.service.audio.AudioService; +import ai.z.openapi.service.audio.AudioServiceImpl; +import ai.z.openapi.service.image.ImageService; +import ai.z.openapi.service.image.ImageServiceImpl; +import ai.z.openapi.service.batches.BatchService; +import ai.z.openapi.service.batches.BatchServiceImpl; +import ai.z.openapi.service.fine_turning.FineTuningService; +import ai.z.openapi.service.fine_turning.FineTuningServiceImpl; +import ai.z.openapi.service.web_search.WebSearchService; +import ai.z.openapi.service.web_search.WebSearchServiceImpl; +import ai.z.openapi.service.videos.VideosService; +import ai.z.openapi.service.videos.VideosServiceImpl; +import ai.z.openapi.service.knowledge.KnowledgeService; +import ai.z.openapi.service.knowledge.KnowledgeServiceImpl; +import ai.z.openapi.service.document.DocumentService; +import ai.z.openapi.service.document.DocumentServiceImpl; +import ai.z.openapi.service.assistant.AssistantService; +import ai.z.openapi.service.assistant.AssistantServiceImpl; +import ai.z.openapi.core.config.ZaiConfig; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.core.model.FlowableClientResponse; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.OkHttps; +import ai.z.openapi.utils.RequestSupplier; +import ai.z.openapi.utils.StringUtils; +import io.reactivex.Flowable; +import io.reactivex.Single; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.TimeUnit; + +import static ai.z.openapi.core.Constants.ZHIPU_AI_BASE_URL; +import static ai.z.openapi.core.Constants.Z_AI_BASE_URL; + +/** + * ZaiClient is the main entry point for interacting with the Z.ai API. This client + * provides access to various AI services including chat, embeddings, file operations, + * audio processing, image generation, and more. + * + *

+ * The client supports both Z.ai and ZHIPU AI endpoints and provides thread-safe lazy + * initialization of services. + *

+ * + *

+ * Example usage: + *

+ *
{@code
+ * ZaiClient client = new ZaiClient.Builder("your-api-key")
+ *     .networkConfig(30, 10, 30, 30, TimeUnit.SECONDS)
+ *     .build();
+ *
+ * ChatService chatService = client.chat();
+ * // Use the chat service...
+ *
+ * client.close(); // Don't forget to close when done
+ * }
+ * + */ +public class ZaiClient extends AbstractClientBaseService { + + /** Logger instance for this class */ + private static final Logger logger = LoggerFactory.getLogger(ZaiClient.class); + + /** HTTP client for making network requests */ + private final OkHttpClient httpClient; + + /** Retrofit instance for API communication */ + private final Retrofit retrofit; + + // Service instances - lazily initialized for thread safety and performance + /** Chat service for conversational AI operations */ + private ChatService chatService; + + /** Agent service for AI agent management */ + private AgentService agentService; + + /** Embedding service for text embeddings */ + private EmbeddingService embeddingService; + + /** File service for file operations */ + private FileService fileService; + + /** Audio service for audio processing */ + private AudioService audioService; + + /** Image service for image generation and processing */ + private ImageService imageService; + + /** Batch service for batch processing operations */ + private BatchService batchService; + + /** Fine-tuning service for model customization */ + private FineTuningService fineTuningService; + + /** Web search service for internet search capabilities */ + private WebSearchService webSearchService; + + /** Videos service for video processing */ + private VideosService videosService; + + /** Knowledge service for knowledge base operations */ + private KnowledgeService knowledgeService; + + /** Document service for document processing */ + private DocumentService documentService; + + /** Assistant service for AI assistant functionality */ + private AssistantService assistantService; + + /** + * Constructs a new ZaiClient with the specified configuration. By default, this + * client uses the Z.ai OpenAPI endpoint. + * @param config the configuration object containing API keys, timeouts, and other + * settings + * @throws IllegalArgumentException if config is null or invalid + */ + public ZaiClient(ZaiConfig config) { + this.httpClient = OkHttps.create(config); + this.retrofit = new Retrofit.Builder() + .baseUrl(StringUtils.isEmpty(config.getBaseUrl()) ? Z_AI_BASE_URL : config.getBaseUrl()) + .client(httpClient) + .addConverterFactory(JacksonConverterFactory.create(mapper)) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + } + + // ==================== Service Accessor Methods ==================== + // All service methods use lazy initialization with synchronization for thread safety + + /** + * Returns the chat service for conversational AI operations. This service handles + * chat completions, streaming conversations, and related functionality. + * @return the ChatService instance (lazily initialized) + */ + public synchronized ChatService chat() { + if (chatService == null) { + this.chatService = new ChatServiceImpl(this); + } + return chatService; + } + + /** + * Returns the agent service for AI agent management. This service handles agent + * creation, configuration, and execution. + * @return the AgentService instance (lazily initialized) + */ + public synchronized AgentService agents() { + if (agentService == null) { + this.agentService = new AgentServiceImpl(this); + } + return agentService; + } + + /** + * Returns the embedding service for text embeddings. This service converts text into + * numerical vector representations. + * @return the EmbeddingService instance (lazily initialized) + */ + public synchronized EmbeddingService embeddings() { + if (embeddingService == null) { + this.embeddingService = new EmbeddingServiceImpl(this); + } + return embeddingService; + } + + /** + * Returns the file service for file operations. This service handles file uploads, + * downloads, and management. + * @return the FileService instance (lazily initialized) + */ + public synchronized FileService files() { + if (fileService == null) { + this.fileService = new FileServiceImpl(this); + } + return fileService; + } + + /** + * Returns the audio service for audio processing. This service handles + * speech-to-text, text-to-speech, and audio analysis. + * @return the AudioService instance (lazily initialized) + */ + public synchronized AudioService audio() { + if (audioService == null) { + this.audioService = new AudioServiceImpl(this); + } + return audioService; + } + + /** + * Returns the image service for image generation and processing. This service handles + * image creation, editing, and analysis. + * @return the ImageService instance (lazily initialized) + */ + public synchronized ImageService images() { + if (imageService == null) { + this.imageService = new ImageServiceImpl(this); + } + return imageService; + } + + /** + * Returns the batch service for batch processing operations. This service handles + * large-scale batch processing of requests. + * @return the BatchService instance (lazily initialized) + */ + public synchronized BatchService batches() { + if (batchService == null) { + this.batchService = new BatchServiceImpl(this); + } + return batchService; + } + + /** + * Returns the fine-tuning service for model customization. This service handles + * training custom models on user data. + * @return the FineTuningService instance (lazily initialized) + */ + public synchronized FineTuningService fineTuning() { + if (fineTuningService == null) { + this.fineTuningService = new FineTuningServiceImpl(this); + } + return fineTuningService; + } + + /** + * Returns the web search service for internet search capabilities. This service + * provides AI-powered web search functionality. + * @return the WebSearchService instance (lazily initialized) + */ + public synchronized WebSearchService webSearch() { + if (webSearchService == null) { + this.webSearchService = new WebSearchServiceImpl(this); + } + return webSearchService; + } + + /** + * Returns the videos service for video processing. This service handles video + * analysis, generation, and manipulation. + * @return the VideosService instance (lazily initialized) + */ + public synchronized VideosService videos() { + if (videosService == null) { + this.videosService = new VideosServiceImpl(this); + } + return videosService; + } + + /** + * Returns the knowledge service for knowledge base operations. This service manages + * knowledge bases and retrieval operations. + * @return the KnowledgeService instance (lazily initialized) + */ + public synchronized KnowledgeService knowledge() { + if (knowledgeService == null) { + this.knowledgeService = new KnowledgeServiceImpl(this); + } + return knowledgeService; + } + + /** + * Returns the document service for document processing. This service handles document + * parsing, analysis, and manipulation. + * @return the DocumentService instance (lazily initialized) + */ + public synchronized DocumentService documents() { + if (documentService == null) { + this.documentService = new DocumentServiceImpl(this); + } + return documentService; + } + + /** + * Returns the assistant service for AI assistant functionality. This service provides + * advanced AI assistant capabilities. + * @return the AssistantService instance (lazily initialized) + */ + public synchronized AssistantService assistants() { + if (assistantService == null) { + this.assistantService = new AssistantServiceImpl(this); + } + return assistantService; + } + + // ==================== Utility Methods ==================== + + /** + * Returns the underlying Retrofit instance used for API communication. This method is + * primarily intended for advanced users who need direct access to the Retrofit client + * for custom API calls. + * @return the Retrofit instance + */ + public Retrofit retrofit() { + return retrofit; + } + + /** + * Closes the ZAi client and releases all associated resources. This method shuts down + * the HTTP client's connection pool and executor service. After calling this method, + * the client should not be used for further requests. + * + *

+ * Important: Always call this method when you're done with the + * client to prevent resource leaks. + *

+ */ + public void close() { + if (httpClient != null) { + httpClient.dispatcher().executorService().shutdown(); + } + } + + // ==================== Core Request Execution Methods ==================== + + /** + * Executes a synchronous API request and returns the response. This method handles + * the complete request lifecycle including error handling and response wrapping. + * @param the type of data expected in the response + * @param the type of parameters for the request + * @param the type of client request + * @param the type of client response + * @param request the client request containing parameters + * @param requestSupplier the supplier that creates the actual API call + * @param tRespClass the class of the response type + * @return the wrapped response containing either success data or error information + */ + @Override + public , TResp extends ClientResponse> TResp executeRequest( + TReq request, RequestSupplier requestSupplier, Class tRespClass) { + Single apiCall = requestSupplier.get((Param) request); + + TResp tResp = convertToClientResponse(tRespClass); + try { + // Execute the API call synchronously + Data response = execute(apiCall); + tResp.setCode(200); + tResp.setMsg("Call successful"); + tResp.setData(response); + tResp.setSuccess(true); + } + catch (ZAiHttpException e) { + logger.error("API request failed with business error", e); + tResp.setCode(e.statusCode); + tResp.setMsg("Business error"); + tResp.setSuccess(false); + ChatError chatError = new ChatError(); + chatError.setCode(Integer.parseInt(e.code)); + chatError.setMessage(e.getMessage()); + tResp.setError(chatError); + } + return tResp; + } + + /** + * Executes a streaming API request and returns a response containing a Flowable + * stream. This method is used for requests that return data as a continuous stream, + * such as chat completions with streaming enabled. + * @param the type of data expected in each stream element + * @param the type of parameters for the request + * @param the type of client request + * @param the type of flowable client response + * @param request the client request containing parameters + * @param requestSupplier the supplier that creates the actual streaming API call + * @param tRespClass the class of the response type + * @param tDataClass the class of the data type for stream elements + * @return the wrapped response containing either a success stream or error + * information + */ + @Override + @SuppressWarnings("unchecked") + public , TResp extends FlowableClientResponse> TResp streamRequest( + TReq request, FlowableRequestSupplier> requestSupplier, + Class tRespClass, Class tDataClass) { + retrofit2.Call apiCall = requestSupplier.get((Param) request); + + TResp tResp = convertToClientResponse(tRespClass); + try { + // Create a streaming response using the provided API call + Flowable stream = stream(apiCall, tDataClass); + tResp.setCode(200); + tResp.setMsg("Stream initialized successfully"); + tResp.setSuccess(true); + tResp.setFlowable(stream); + } + catch (ZAiHttpException e) { + logger.error("Streaming API request failed with business error", e); + tResp.setCode(e.statusCode); + tResp.setMsg("Business error"); + tResp.setSuccess(false); + ChatError chatError = new ChatError(); + chatError.setCode(Integer.parseInt(e.code)); + chatError.setMessage(e.getMessage()); + tResp.setError(chatError); + } + return tResp; + } + + /** + * Creates a new instance of the specified response class using reflection. This + * helper method is used to instantiate response objects dynamically. + * @param the type of data contained in the response + * @param the type of client response + * @param tRespClass the class of the response type to instantiate + * @return a new instance of the response class + * @throws RuntimeException if the response object cannot be created + */ + private > TResp convertToClientResponse(Class tRespClass) { + try { + return tRespClass.getDeclaredConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException("Failed to create response object of type: " + tRespClass.getSimpleName(), e); + } + } + + // ==================== Builder Pattern Implementation ==================== + + /** + * Builder class for creating ZaiClient instances with custom configurations. This + * builder provides a fluent API for setting up the client with various options + * including authentication, network settings, and connection pooling. + * + *

+ * Example usage: + *

+ *
{@code
+	 * ZaiClient client = new ZaiClient.Builder("your-api-key")
+	 *     .networkConfig(30, 10, 30, 30, TimeUnit.SECONDS)
+	 *     .connectionPool(10, 5, TimeUnit.MINUTES)
+	 *     .enableTokenCache()
+	 *     .build();
+	 * }
+ */ + public static final class Builder { + + /** Configuration object that accumulates all builder settings */ + private final ZaiConfig config = new ZaiConfig(); + + public Builder() { + } + + /** + * Creates a new builder with the specified API key. + * @param apiKey the API key for authentication + * @throws IllegalArgumentException if apiKey is null or empty + */ + public Builder(String apiKey) { + if (apiKey == null || apiKey.trim().isEmpty()) { + throw new IllegalArgumentException("API key cannot be null or empty"); + } + config.setApiKey(apiKey); + } + + /** + * Creates a new builder with a custom base URL and API secret key. + * @param baseUrl the custom base URL for the API endpoint + * @param apiKey the API secret key for authentication + * @throws IllegalArgumentException if any parameter is null or empty + */ + public Builder(String baseUrl, String apiKey) { + if (baseUrl == null || baseUrl.trim().isEmpty()) { + throw new IllegalArgumentException("Base URL cannot be null or empty"); + } + if (apiKey == null || apiKey.trim().isEmpty()) { + throw new IllegalArgumentException("API secret key cannot be null or empty"); + } + config.setBaseUrl(baseUrl); + config.setApiKey(apiKey); + } + + /** + * Config the api service base url + * @param baseUrl base url + * @return this Builder instance for method chaining + */ + public Builder baseUrl(String baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty()) { + throw new IllegalArgumentException("Base URL cannot be null or empty"); + } + config.setBaseUrl(baseUrl); + return this; + } + + /** + * Config the apikey + * @param apiKey api key + * @return this Builder instance for method chaining + */ + public Builder apiKey(String apiKey) { + if (apiKey == null || apiKey.trim().isEmpty()) { + throw new IllegalArgumentException("API secret key cannot be null or empty"); + } + config.setApiKey(apiKey); + return this; + } + + /** + * Use the ZHIPU AI base url + * @return this Builder instance for method chaining + */ + public Builder ofZHIPU() { + config.setBaseUrl(ZHIPU_AI_BASE_URL); + return this; + } + + /** + * Use the Z AI base url + * @return this Builder instance for method chaining + */ + public Builder ofZAI() { + config.setBaseUrl(Z_AI_BASE_URL); + return this; + } + + /** + * Disables token caching, forcing the client to use API keys for direct requests. + * This is the default behavior. + * @return this Builder instance for method chaining + */ + public Builder disableTokenCache() { + config.setDisableTokenCache(true); + return this; + } + + /** + * Enables token caching, allowing the client to use access tokens for requests. + * This can improve performance by reducing authentication overhead. + * @return this Builder instance for method chaining + */ + public Builder enableTokenCache() { + config.setDisableTokenCache(false); + return this; + } + + /** + * Configures the HTTP connection pool settings. + * @param maxIdleConnections maximum number of idle connections to keep in the + * pool + * @param keepAliveDuration how long to keep idle connections alive + * @param timeUnit the time unit for the keep alive duration + * @return this Builder instance for method chaining + */ + public Builder connectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { + config.setConnectionPoolMaxIdleConnections(maxIdleConnections); + config.setConnectionPoolKeepAliveDuration(keepAliveDuration); + config.setConnectionPoolTimeUnit(timeUnit); + return this; + } + + /** + * Sets the token expiration time in milliseconds. + * @param expireMillis the token expiration time in milliseconds + * @return this Builder instance for method chaining + */ + public Builder tokenExpire(int expireMillis) { + config.setExpireMillis(expireMillis); + return this; + } + + /** + * Configures network request timeout settings. + * @param requestTimeOut the overall request timeout (see + * {@link OkHttpClient.Builder#callTimeout(long, TimeUnit)}) + * @param connectTimeout the connection timeout (see + * {@link OkHttpClient.Builder#connectTimeout(long, TimeUnit)}) + * @param readTimeout the read timeout (see + * {@link OkHttpClient.Builder#readTimeout(long, TimeUnit)}) + * @param writeTimeout the write timeout (see + * {@link OkHttpClient.Builder#writeTimeout(long, TimeUnit)}) + * @param timeUnit the time unit for all timeout values + * @return this Builder instance for method chaining + */ + public Builder networkConfig(int requestTimeOut, int connectTimeout, int readTimeout, int writeTimeout, + TimeUnit timeUnit) { + config.setRequestTimeOut(requestTimeOut); + config.setConnectTimeout(connectTimeout); + config.setReadTimeout(readTimeout); + config.setWriteTimeout(writeTimeout); + config.setTimeOutTimeUnit(timeUnit); + return this; + } + + /** + * Builds and returns a new ZaiClient instance with the configured settings. + * @return a new ZaiClient instance + * @throws IllegalStateException if the configuration is invalid + */ + public ZaiClient build() { + return new ZaiClient(config); + } + + } + + // ==================== Static Factory Methods ==================== + + /** + * Creates a new Builder instance for constructing ZaiClient. + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a ZaiClient configured for ZHIPU AI platform with the specified API key. + * @param apiKey the API key for authentication + * @return a new ZaiClient instance configured for ZHIPU AI + * @throws IllegalArgumentException if apiKey is null or empty + */ + public static Builder ofZHIPU(String apiKey) { + return new Builder().apiKey(apiKey).ofZHIPU(); + } + + /** + * Creates a ZaiClient configured for ZHIPU AI platform with the specified API key. + * @return a new ZaiClient instance configured for ZHIPU AI + * @throws IllegalArgumentException if apiKey is null or empty + */ + public static Builder ofZHIPU() { + return new Builder().ofZHIPU(); + } + +} diff --git a/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java b/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java new file mode 100644 index 0000000..efe0dcf --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/agents/AgentsApi.java @@ -0,0 +1,25 @@ +package ai.z.openapi.api.agents; + +import ai.z.openapi.service.agents.AgentAsyncResultRetrieveParams; +import ai.z.openapi.service.agents.AgentsCompletionRequest; +import ai.z.openapi.service.model.ModelData; +import io.reactivex.Single; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; +import retrofit2.http.Streaming; + +public interface AgentsApi { + + @Streaming + @POST("v1/agents") + Call agentsCompletionStream(@Body AgentsCompletionRequest request); + + @POST("v1/agents") + Single agentsCompletionSync(@Body AgentsCompletionRequest request); + + @POST("v1/agents/async-result") + Single queryAgentsAsyncResult(@Body AgentAsyncResultRetrieveParams request); + +} diff --git a/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java b/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java new file mode 100644 index 0000000..2690cba --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/assistant/AssistantApi.java @@ -0,0 +1,31 @@ +package ai.z.openapi.api.assistant; + +import ai.z.openapi.service.assistant.AssistantCompletion; +import ai.z.openapi.service.assistant.AssistantParameters; +import ai.z.openapi.service.assistant.conversation.ConversationUsageListStatus; +import ai.z.openapi.service.assistant.query_support.AssistantSupportStatus; +import ai.z.openapi.service.assistant.conversation.ConversationParameters; +import ai.z.openapi.service.assistant.query_support.QuerySupportParams; +import io.reactivex.Single; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; +import retrofit2.http.Streaming; + +public interface AssistantApi { + + @Streaming + @POST("assistant") + Call assistantCompletionStream(@Body AssistantParameters request); + + @POST("assistant") + Single assistantCompletion(@Body AssistantParameters request); + + @POST("assistant/list") + Single querySupport(@Body QuerySupportParams request); + + @POST("assistant/conversation/list") + Single queryConversationUsage(@Body ConversationParameters request); + +} diff --git a/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java b/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java new file mode 100644 index 0000000..f8c05fb --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/audio/AudioApi.java @@ -0,0 +1,50 @@ +package ai.z.openapi.api.audio; + +import ai.z.openapi.service.audio.AudioSpeechRequest; +import ai.z.openapi.service.model.ModelData; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; +import retrofit2.http.PartMap; +import retrofit2.http.Streaming; + +import java.util.Map; + +public interface AudioApi { + + /** + * TTS interface (Text to speech) + * @param request + * @return + */ + @POST("audio/speech") + Single audioSpeech(@Body AudioSpeechRequest request); + + /** + * Voice cloning interface + * @param request + * @return + */ + @Multipart + @POST("audio/customization") + Single audioCustomization(@PartMap Map request, + @Part MultipartBody.Part voiceData); + + @Streaming + @POST("audio/transcriptions") + @Multipart + Call audioTranscriptionsStream(@PartMap Map request, + @Part MultipartBody.Part file); + + @POST("audio/transcriptions") + @Multipart + Single audioTranscriptions(@PartMap Map request, @Part MultipartBody.Part file); + +} diff --git a/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java b/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java new file mode 100644 index 0000000..3456d74 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/batches/BatchesApi.java @@ -0,0 +1,27 @@ +package ai.z.openapi.api.batches; + +import ai.z.openapi.service.batches.Batch; +import ai.z.openapi.service.batches.BatchCreateParams; +import ai.z.openapi.service.batches.BatchPage; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface BatchesApi { + + @POST("batches") + Single batchesCreate(@Body BatchCreateParams batchCreateParams); + + @GET("batches/{batch_id}") + Single batchesRetrieve(@Path("batch_id") String batchId); + + @GET("batches") + Single batchesList(@Query("after") String after, @Query("limit") Integer limit); + + @POST("batches/{batch_id}/cancel") + Single batchesCancel(@Path("batch_id") String batchId); + +} diff --git a/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java b/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java new file mode 100644 index 0000000..c89b208 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java @@ -0,0 +1,29 @@ +package ai.z.openapi.api.chat; + +import ai.z.openapi.service.model.ChatCompletionCreateParams; +import ai.z.openapi.service.model.ModelData; +import io.reactivex.Single; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Streaming; + +public interface ChatApi { + + @Streaming + @POST("chat/completions") + Call createChatCompletionStream(@Body ChatCompletionCreateParams request); + + @POST("async/chat/completions") + Single createChatCompletionAsync(@Body ChatCompletionCreateParams request); + + @POST("chat/completions") + Single createChatCompletion(@Body ChatCompletionCreateParams request); + + @GET("async-result/{id}") + Single queryAsyncResult(@Path("id") String id); + +} diff --git a/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java b/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java new file mode 100644 index 0000000..e41334b --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/embedding/EmbeddingApi.java @@ -0,0 +1,14 @@ +package ai.z.openapi.api.embedding; + +import ai.z.openapi.service.embedding.EmbeddingCreateParams; +import ai.z.openapi.service.embedding.EmbeddingResult; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface EmbeddingApi { + + @POST("embeddings") + Single createEmbeddings(@Body EmbeddingCreateParams request); + +} diff --git a/core/src/main/java/ai/z/openapi/api/file/FileApi.java b/core/src/main/java/ai/z/openapi/api/file/FileApi.java new file mode 100644 index 0000000..a439c24 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/file/FileApi.java @@ -0,0 +1,37 @@ +package ai.z.openapi.api.file; + +import ai.z.openapi.service.file.File; +import ai.z.openapi.service.file.FileDeleted; +import ai.z.openapi.service.file.QueryFileResult; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; +import retrofit2.http.Streaming; + +public interface FileApi { + + @POST("files") + Single uploadFile(@Body MultipartBody multipartBody); + + @GET("files/{file_id}") + Single retrieveFile(@Path("file_id") String fileId); + + @DELETE("files/{file_id}") + Single deletedFile(@Path("file_id") String fileId); + + @GET("files") + Single queryFileList(@Query("after") String after, @Query("purpose") String purpose, + @Query("order") String order, @Query("limit") Integer limit); + + @Streaming + @GET("files/{file_id}/content") + Call fileContent(@Path("file_id") String fileId); + +} diff --git a/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java b/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java new file mode 100644 index 0000000..ec3f9a4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/fine_tuning/FineTuningApi.java @@ -0,0 +1,42 @@ +package ai.z.openapi.api.fine_tuning; + +import ai.z.openapi.service.fine_turning.FineTunedModelsStatus; +import ai.z.openapi.service.fine_turning.FineTuningEvent; +import ai.z.openapi.service.fine_turning.FineTuningJob; +import ai.z.openapi.service.fine_turning.FineTuningJobRequest; +import ai.z.openapi.service.fine_turning.PersonalFineTuningJob; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface FineTuningApi { + + @POST("fine_tuning/jobs") + Single createFineTuningJob(@Body FineTuningJobRequest request); + + @GET("fine_tuning/jobs/{fine_tuning_job_id}/events") + Single listFineTuningJobEvents(@Path("fine_tuning_job_id") String fineTuningJobId, + @Query("limit") Integer limit, @Query("after") String after); + + @GET("fine_tuning/jobs/{fine_tuning_job_id}") + Single retrieveFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId, + @Query("limit") Integer limit, @Query("after") String after); + + @GET("fine_tuning/jobs") + Single queryPersonalFineTuningJobs(@Query("limit") Integer limit, + @Query("after") String after); + + @POST("fine_tuning/jobs/{fine_tuning_job_id}/cancel") + Single cancelFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + + @DELETE("fine_tuning/jobs/{fine_tuning_job_id}") + Single deleteFineTuningJob(@Path("fine_tuning_job_id") String fineTuningJobId); + + @DELETE("fine_tuning/fine_tuned_models/{fine_tuned_model}") + Single deleteFineTuningModel(@Path("fine_tuned_model") String fineTunedModel); + +} diff --git a/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java b/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java new file mode 100644 index 0000000..45121be --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/images/ImagesApi.java @@ -0,0 +1,14 @@ +package ai.z.openapi.api.images; + +import ai.z.openapi.service.image.CreateImageRequest; +import ai.z.openapi.service.image.ImageResult; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface ImagesApi { + + @POST("images/generations") + Single createImage(@Body CreateImageRequest request); + +} diff --git a/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java b/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java new file mode 100644 index 0000000..139df43 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/knowledge/KnowledgeApi.java @@ -0,0 +1,36 @@ +package ai.z.openapi.api.knowledge; + +import ai.z.openapi.service.knowledge.KnowledgeBaseParams; +import ai.z.openapi.service.knowledge.KnowledgeInfo; +import ai.z.openapi.service.knowledge.KnowledgePage; +import ai.z.openapi.service.knowledge.KnowledgeUsed; +import io.reactivex.Single; + +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface KnowledgeApi { + + @POST("knowledge") + Single knowledgeCreate(@Body KnowledgeBaseParams knowledgeBaseParams); + + @PUT("knowledge/{knowledge_id}") + Single> knowledgeModify(@Path("knowledge_id") String knowledge_id, + @Body KnowledgeBaseParams knowledgeBaseParams); + + @GET("knowledge") + Single knowledgeQuery(@Query("page") Integer page, @Query("size") Integer size); + + @DELETE("knowledge/{knowledge_id}") + Single> knowledgeDelete(@Path("knowledge_id") String knowledge_id); + + @GET("knowledge/capacity") + Single knowledgeUsed(); + +} diff --git a/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java b/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java new file mode 100644 index 0000000..f50b029 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/knowledge/document/DocumentApi.java @@ -0,0 +1,37 @@ +package ai.z.openapi.api.knowledge.document; + +import ai.z.openapi.service.knowledge.document.DocumentData; +import ai.z.openapi.service.knowledge.document.DocumentEditParams; +import ai.z.openapi.service.knowledge.document.DocumentObject; +import ai.z.openapi.service.knowledge.document.DocumentPage; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface DocumentApi { + + @POST("files") + Single createDocument(@Body MultipartBody document); + + @PUT("document/{document_id}") + Single> modifyDocument(@Path("document_id") String documentId, + @Body DocumentEditParams documentEditParams); + + @DELETE("document/{document_id}") + Single> deleteDocument(@Path("document_id") String documentId); + + @GET("files") + Single queryDocumentList(@Query("knowledge_id") String knowledgeId, @Query("purpose") String purpose, + @Query("page") Integer page, @Query("limit") Integer limit, @Query("order") String order); + + @GET("document/{document_id}") + Single retrieveDocument(@Path("document_id") String documentId); + +} diff --git a/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java b/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java new file mode 100644 index 0000000..a2b9683 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/tools/ToolsApi.java @@ -0,0 +1,21 @@ +package ai.z.openapi.api.tools; + +import ai.z.openapi.service.tools.WebSearchParamsRequest; +import ai.z.openapi.service.tools.WebSearchPro; +import io.reactivex.Single; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; +import retrofit2.http.Streaming; + +public interface ToolsApi { + + @Streaming + @POST("tools") + Call webSearchStreaming(@Body WebSearchParamsRequest request); + + @POST("tools") + Single webSearch(@Body WebSearchParamsRequest request); + +} diff --git a/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java b/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java new file mode 100644 index 0000000..3327a2f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/videos/VideosApi.java @@ -0,0 +1,19 @@ +package ai.z.openapi.api.videos; + +import ai.z.openapi.service.videos.VideoCreateParams; +import ai.z.openapi.service.videos.VideoObject; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface VideosApi { + + @POST("videos/generations") + Single videoGenerations(@Body VideoCreateParams request); + + @GET("async-result/{id}") + Single videoGenerationsResult(@Path("id") String id); + +} diff --git a/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java b/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java new file mode 100644 index 0000000..f1728ea --- /dev/null +++ b/core/src/main/java/ai/z/openapi/api/web_search/WebSearchApi.java @@ -0,0 +1,14 @@ +package ai.z.openapi.api.web_search; + +import ai.z.openapi.service.web_search.WebSearchDTO; +import ai.z.openapi.service.web_search.WebSearchRequest; +import io.reactivex.Single; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface WebSearchApi { + + @POST("web_search") + Single webSearch(@Body WebSearchRequest request); + +} diff --git a/core/src/main/java/ai/z/openapi/core/Constants.java b/core/src/main/java/ai/z/openapi/core/Constants.java new file mode 100644 index 0000000..e9a29f4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/Constants.java @@ -0,0 +1,155 @@ +package ai.z.openapi.core; + +/** + * Constants class containing all the configuration values and model identifiers used + * throughout the Z.AI OpenAPI SDK. + * + * This class provides centralized access to: - API base URLs - Model identifiers for + * different AI capabilities - Invocation method constants + * + * @author Z.AI SDK Team + * @since 1.0.0 + */ +public final class Constants { + + // Private constructor to prevent instantiation + private Constants() { + throw new UnsupportedOperationException("Constants class cannot be instantiated"); + } + + // ============================================================================= + // API Configuration + // ============================================================================= + + /** + * Base URL for the ZHIPU AI OpenAPI service. All API requests will be made to + * endpoints under this base URL. + */ + public static final String ZHIPU_AI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4/"; + + /** + * Base URL for the Z.AI OpenAPI service. All API requests will be made to endpoints + * under this base URL. + */ + public static final String Z_AI_BASE_URL = "https://api.z.ai/api/paas/v4/"; + + // ============================================================================= + // Text Generation Models + // ============================================================================= + + /** + * GLM-4 Plus model - Enhanced version with improved capabilities. + */ + public static final String ModelChatGLM4Plus = "glm-4-plus"; + + /** + * GLM-4 Air model - Lightweight version optimized for speed. + */ + public static final String ModelChatGLM4Air = "glm-4-air"; + + /** + * GLM-4 Flash model - Ultra-fast response model. + */ + public static final String ModelChatGLM4Flash = "glm-4-flash"; + + /** + * GLM-4 standard model - Balanced performance and capability. + */ + public static final String ModelChatGLM4 = "glm-4"; + + /** + * GLM-4 model version 0520 - Specific version release. + */ + public static final String ModelChatGLM40520 = "glm-4-0520"; + + /** + * GLM-4 AirX model - Extended Air model with additional features. + */ + public static final String ModelChatGLM4Airx = "glm-4-airx"; + + /** + * GLM-4 Long model - Optimized for long-context conversations. + */ + public static final String ModelChatGLMLong = "glm-4-long"; + + /** + * GLM-4 Voice model - Specialized for voice-related tasks. + */ + public static final String ModelChatGLM4Voice = "glm-4-voice"; + + // ============================================================================= + // Vision Models (Image Understanding) + // ============================================================================= + + /** + * GLM-4V Plus model - Enhanced vision model for image understanding. + */ + public static final String ModelChatGLM4VPlus = "glm-4v-plus"; + + /** + * GLM-4V standard model - Standard vision model for image analysis. + */ + public static final String ModelChatGLM4V = "glm-4v"; + + // ============================================================================= + // Image Generation Models + // ============================================================================= + + /** + * CogView-3 Plus model - Enhanced image generation capabilities. + */ + public static final String ModelCogView3Plus = "cogview-3-plus"; + + /** + * CogView-3 standard model - Standard image generation model. + */ + public static final String ModelCogView = "cogview-3"; + + // ============================================================================= + // Embedding Models + // ============================================================================= + + /** + * Embedding model version 2 - Text embedding generation. + */ + public static final String ModelEmbedding2 = "embedding-2"; + + /** + * Embedding model version 3 - Latest text embedding generation. + */ + public static final String ModelEmbedding3 = "embedding-3"; + + // ============================================================================= + // Specialized Models + // ============================================================================= + + /** + * CharGLM-3 model - Anthropomorphic character interaction model. + */ + public static final String ModelCharGLM3 = "charglm-3"; + + /** + * CogTTS model - Text-to-Speech synthesis model. + */ + public static final String ModelTTS = "cogtts"; + + // ============================================================================= + // API Invocation Methods + // ============================================================================= + + /** + * Asynchronous invocation method - For non-blocking API calls. + */ + public static final String INVOKE_METHOD_ASYNC = "async-invoke"; + + /** + * Server-Sent Events invocation method - For streaming responses. + */ + public static final String INVOKE_METHOD_SSE = "sse-invoke"; + + /** + * Standard synchronous invocation method - For blocking API calls. + */ + public static final String INVOKE_METHOD = "invoke"; + +} diff --git a/core/src/main/java/ai/z/openapi/core/cache/ICache.java b/core/src/main/java/ai/z/openapi/core/cache/ICache.java new file mode 100644 index 0000000..c130839 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/cache/ICache.java @@ -0,0 +1,27 @@ +package ai.z.openapi.core.cache; + +import java.util.concurrent.TimeUnit; + +/** + * Token cache interface with default LocalCache implementation. Can be replaced with + * distributed cache (e.g., Redis) as needed. + */ +public interface ICache { + + /** + * Retrieves cached value by key. + * @param key the cache key + * @return cached value or empty string if not found or expired + */ + String get(String key); + + /** + * Sets cache value with expiration time. + * @param key the cache key + * @param value the value to cache + * @param expire expiration duration + * @param timeUnit time unit for expiration + */ + void set(String key, String value, int expire, TimeUnit timeUnit); + +} diff --git a/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java b/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java new file mode 100644 index 0000000..ea73457 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/cache/LocalCache.java @@ -0,0 +1,81 @@ +package ai.z.openapi.core.cache; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Thread-safe local cache implementation using ConcurrentHashMap. Provides basic caching + * functionality with expiration support. + */ +public class LocalCache implements ICache { + + private static final Logger log = LoggerFactory.getLogger(LocalCache.class); + + private static final ConcurrentMap CACHE = new ConcurrentHashMap<>(8); + + /** + * Private constructor to prevent direct instantiation. + */ + private LocalCache() { + } + + /** + * Gets singleton instance of LocalCache. + * @return LocalCache instance + */ + public static LocalCache getInstance() { + return Inner.LOCAL_CACHE; + } + + @Override + public String get(String key) { + Value v = LocalCache.CACHE.get(key); + if (v == null || new Date().after(v.end)) { + return ""; + } + + log.debug("Retrieved key: {}, time left: {}s", key, (v.end.getTime() - new Date().getTime()) / 1000); + return v.value; + } + + @Override + public void set(String key, String value, int expire, TimeUnit timeUnit) { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.SECOND, (int) timeUnit.toSeconds(expire)); + Value v = new Value(value, calendar.getTime()); + log.debug("Cached key: {}, expire time: {}", key, calendar.getTime()); + LocalCache.CACHE.put(key, v); + } + + /** + * Internal value wrapper with expiration time. + */ + private static class Value { + + final String value; + + final Date end; + + public Value(String value, Date time) { + this.value = value; + this.end = time; + } + + } + + /** + * Singleton holder pattern for thread-safe lazy initialization. + */ + private static class Inner { + + private static final LocalCache LOCAL_CACHE = new LocalCache(); + + } + +} diff --git a/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java b/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java new file mode 100644 index 0000000..8405bf9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java @@ -0,0 +1,400 @@ +package ai.z.openapi.core.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.concurrent.TimeUnit; + +import static ai.z.openapi.core.Constants.Z_AI_BASE_URL; + +/** + * Configuration class for ZAI SDK containing API credentials, JWT settings, HTTP client + * configurations, and cache settings. Supports reading configuration values from + * environment variables with memory values taking priority. + */ +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ZaiConfig { + + // Environment variable names + private static final String ENV_BASE_URL = "ZAI_BASE_URL"; + + private static final String ENV_API_KEY = "ZAI_API_KEY"; + + private static final String ENV_EXPIRE_MILLIS = "ZAI_EXPIRE_MILLIS"; + + private static final String ENV_ALG = "ZAI_ALG"; + + private static final String ENV_DISABLE_TOKEN_CACHE = "ZAI_DISABLE_TOKEN_CACHE"; + + private static final String ENV_CONNECTION_POOL_MAX_IDLE = "ZAI_CONNECTION_POOL_MAX_IDLE"; + + private static final String ENV_CONNECTION_POOL_KEEP_ALIVE = "ZAI_CONNECTION_POOL_KEEP_ALIVE"; + + private static final String ENV_REQUEST_TIMEOUT = "ZAI_REQUEST_TIMEOUT"; + + private static final String ENV_CONNECT_TIMEOUT = "ZAI_CONNECT_TIMEOUT"; + + private static final String ENV_READ_TIMEOUT = "ZAI_READ_TIMEOUT"; + + private static final String ENV_WRITE_TIMEOUT = "ZAI_WRITE_TIMEOUT"; + + /** + * Base URL for API endpoints. + */ + private String baseUrl; + + /** + * Combined API secret key in format: {apiId}.{apiSecret} + */ + private String apiKey; + + /** + * API id component. + */ + private String apiId; + + /** + * API secret component. + */ + private String apiSecret; + + /** + * JWT token expiration time in milliseconds (default: 30 minutes). + */ + private int expireMillis = 30 * 60 * 1000; + + /** + * JWT encryption algorithm (default: HS256). + */ + private String alg = "HS256"; + + /** + * Flag to disable token caching. + */ + private boolean disableTokenCache; + + /** + * Maximum number of idle connections in the connection pool. + */ + private int connectionPoolMaxIdleConnections = 5; + + /** + * Keep alive duration for connections in the pool (in seconds). + */ + private long connectionPoolKeepAliveDuration = 1; + + /** + * Time unit for connection pool keep alive duration. + */ + private TimeUnit connectionPoolTimeUnit = TimeUnit.SECONDS; + + /** + * Request timeout in specified time unit. + */ + private int requestTimeOut = 300; + + /** + * Connection timeout in specified time unit. + */ + private int connectTimeout = 100; + + /** + * Read timeout in specified time unit. + */ + private int readTimeout = 100; + + /** + * Write timeout in specified time unit. + */ + private int writeTimeout = 100; + + /** + * Time unit for timeout configurations. + */ + private TimeUnit timeOutTimeUnit = TimeUnit.SECONDS; + + /** + * Source channel identifier for request tracking. + */ + private String source_channel = "java-sdk"; + + /** + * Constructor with combined API secret key. + * @param apiKey combined secret key in format {apiKey}.{apiSecret} + * @throws RuntimeException if apiSecretKey format is invalid + */ + public ZaiConfig(String apiKey) { + this.apiKey = apiKey; + String[] arrStr = apiKey.split("\\."); + if (arrStr.length != 2) { + throw new RuntimeException("invalid apiSecretKey"); + } + this.apiId = arrStr[0]; + this.apiSecret = arrStr[1]; + } + + /** + * Sets API key and parses it into id and secret components. + * @param apiKey combined secret key in format {apiId}.{apiSecret} + * @throws RuntimeException if apiSecretKey format is invalid + */ + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + String[] arrStr = apiKey.split("\\."); + if (arrStr.length != 2) { + throw new RuntimeException("invalid api Key"); + } + this.apiId = arrStr[0]; + this.apiSecret = arrStr[1]; + } + + /** + * Gets base URL with system property and environment variable fallback. + */ + public String getBaseUrl() { + if (baseUrl != null) { + return baseUrl; + } + String propValue = System.getProperty(ENV_BASE_URL); + propValue = propValue != null ? propValue : System.getenv(ENV_BASE_URL); + return propValue != null ? propValue : Z_AI_BASE_URL; + } + + /** + * Gets API secret key with system property and environment variable fallback. + */ + public String getApiKey() { + if (apiKey != null && !apiKey.isEmpty()) { + return apiKey; + } + String propValue = System.getProperty(ENV_API_KEY); + String value = propValue != null ? propValue : System.getenv(ENV_API_KEY); + if (value != null && !value.isEmpty()) { + // Parse value and set components + this.apiKey = value; + String[] arrStr = value.split("\\."); + if (arrStr.length == 2) { + this.apiId = arrStr[0]; + this.apiSecret = arrStr[1]; + } + return value; + } + return apiKey; + } + + /** + * Gets API key with system property and environment variable fallback. + */ + public String getApiId() { + if (apiId != null && !apiId.isEmpty()) { + return apiId; + } + getApiKey(); + return apiId; + } + + /** + * Gets API secret with system property and environment variable fallback. + */ + public String getApiSecret() { + if (apiSecret != null && !apiSecret.isEmpty()) { + return apiSecret; + } + getApiKey(); + return apiSecret; + } + + /** + * Gets expire millis with system property and environment variable fallback. + */ + public int getExpireMillis() { + if (expireMillis != 30 * 60 * 1000) { // If not default value + return expireMillis; + } + String propValue = System.getProperty(ENV_EXPIRE_MILLIS); + String value = propValue != null ? propValue : System.getenv(ENV_EXPIRE_MILLIS); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return expireMillis; + } + + /** + * Gets algorithm with system property and environment variable fallback. + */ + public String getAlg() { + if (!"HS256".equals(alg)) { + return alg; + } + String propValue = System.getProperty(ENV_ALG); + String value = propValue != null ? propValue : System.getenv(ENV_ALG); + return value != null ? value : alg; + } + + /** + * Gets disable token cache flag with system property and environment variable + * fallback. + */ + public boolean isDisableTokenCache() { + if (disableTokenCache) { + return true; + } + String propValue = System.getProperty(ENV_DISABLE_TOKEN_CACHE); + String value = propValue != null ? propValue : System.getenv(ENV_DISABLE_TOKEN_CACHE); + return Boolean.parseBoolean(value); + } + + /** + * Gets connection pool max idle connections with system property and environment + * variable fallback. + */ + public int getConnectionPoolMaxIdleConnections() { + if (connectionPoolMaxIdleConnections != 5) { // If not default value + return connectionPoolMaxIdleConnections; + } + String propValue = System.getProperty(ENV_CONNECTION_POOL_MAX_IDLE); + String value = propValue != null ? propValue : System.getenv(ENV_CONNECTION_POOL_MAX_IDLE); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return connectionPoolMaxIdleConnections; + } + + /** + * Gets connection pool keep alive duration with system property and environment + * variable fallback. + */ + public long getConnectionPoolKeepAliveDuration() { + if (connectionPoolKeepAliveDuration != 1) { // If not default value + return connectionPoolKeepAliveDuration; + } + String propValue = System.getProperty(ENV_CONNECTION_POOL_KEEP_ALIVE); + String value = propValue != null ? propValue : System.getenv(ENV_CONNECTION_POOL_KEEP_ALIVE); + if (value != null) { + try { + return Long.parseLong(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return connectionPoolKeepAliveDuration; + } + + /** + * Gets connection pool time unit (always returns the set value or default). + */ + public TimeUnit getConnectionPoolTimeUnit() { + return connectionPoolTimeUnit != null ? connectionPoolTimeUnit : TimeUnit.SECONDS; + } + + /** + * Gets request timeout with system property and environment variable fallback. + */ + public int getRequestTimeOut() { + if (requestTimeOut != 300) { + return requestTimeOut; + } + String propValue = System.getProperty(ENV_REQUEST_TIMEOUT); + String value = propValue != null ? propValue : System.getenv(ENV_REQUEST_TIMEOUT); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return requestTimeOut; + } + + /** + * Gets connect timeout with system property and environment variable fallback. + */ + public int getConnectTimeout() { + if (connectTimeout != 100) { + return connectTimeout; + } + String propValue = System.getProperty(ENV_CONNECT_TIMEOUT); + String value = propValue != null ? propValue : System.getenv(ENV_CONNECT_TIMEOUT); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return connectTimeout; + } + + /** + * Gets read timeout with system property and environment variable fallback. + */ + public int getReadTimeout() { + if (readTimeout != 100) { + return readTimeout; + } + String propValue = System.getProperty(ENV_READ_TIMEOUT); + String value = propValue != null ? propValue : System.getenv(ENV_READ_TIMEOUT); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return readTimeout; + } + + /** + * Gets write timeout with system property and environment variable fallback. + */ + public int getWriteTimeout() { + if (writeTimeout != 100) { + return writeTimeout; + } + String propValue = System.getProperty(ENV_WRITE_TIMEOUT); + String value = propValue != null ? propValue : System.getenv(ENV_WRITE_TIMEOUT); + if (value != null) { + try { + return Integer.parseInt(value); + } + catch (NumberFormatException e) { + // Return default value if parsing fails + } + } + return writeTimeout; + } + + /** + * Gets timeout time unit (always returns the set value or null). + */ + public TimeUnit getTimeOutTimeUnit() { + return timeOutTimeUnit; + } + + /** + * Gets source channel with system property. + */ + public String getSource_channel() { + return source_channel; + } + +} diff --git a/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java b/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java new file mode 100644 index 0000000..601f8d0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/model/ClientRequest.java @@ -0,0 +1,5 @@ +package ai.z.openapi.core.model; + +public interface ClientRequest { + +} diff --git a/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java b/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java new file mode 100644 index 0000000..8b0ef37 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/model/ClientResponse.java @@ -0,0 +1,19 @@ +package ai.z.openapi.core.model; + +import ai.z.openapi.service.model.ChatError; + +public interface ClientResponse { + + T getData(); + + void setData(T data); + + void setCode(int code); + + void setMsg(String msg); + + void setSuccess(boolean b); + + void setError(ChatError chatError); + +} diff --git a/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java b/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java new file mode 100644 index 0000000..d995477 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/model/FlowableClientResponse.java @@ -0,0 +1,19 @@ +package ai.z.openapi.core.model; + +import io.reactivex.Flowable; + +/** + * Client response interface with reactive stream support. Extends ClientResponse to + * provide Flowable stream functionality. + * + * @param response data type + */ +public interface FlowableClientResponse extends ClientResponse { + + /** + * Sets the reactive stream for this response. + * @param stream Flowable stream containing response data + */ + void setFlowable(Flowable stream); + +} diff --git a/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java b/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java new file mode 100644 index 0000000..e2e3537 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/response/HttpxBinaryResponseContent.java @@ -0,0 +1,156 @@ +package ai.z.openapi.core.response; + +import java.io.Closeable; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Objects; +import okhttp3.ResponseBody; +import okio.BufferedSource; + +public class HttpxBinaryResponseContent implements Closeable { + + private final retrofit2.Response response; + + public HttpxBinaryResponseContent(retrofit2.Response response) { + if (response == null || response.body() == null) { + throw new IllegalArgumentException("Response or ResponseBody cannot be null"); + } + this.response = response; + } + + public byte[] getContent() throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + try (BufferedSource source = response.body().source()) { + return source.readByteArray(); + } + } + + public String getText() throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + try (BufferedSource source = response.body().source()) { + return source.readUtf8(); + } + } + + public String getEncoding() { + return response.body() != null && response.body().contentType() != null + ? Objects.requireNonNull(Objects.requireNonNull(response.body().contentType()).charset()).toString() + : null; + } + + public Iterator iterBytes(int chunkSize) throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + return new Iterator() { + final BufferedSource source = response.body().source(); + + final byte[] buffer = new byte[chunkSize]; + + boolean hasMore = true; + + @Override + public boolean hasNext() { + try { + if (source.exhausted()) { + hasMore = false; + } + } + catch (IOException e) { + hasMore = false; + } + return hasMore; + } + + @Override + public byte[] next() { + try { + source.read(buffer); + } + catch (IOException e) { + // Handle the exception + } + return buffer; + } + }; + } + + public Iterator iterText(int chunkSize) throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + return new Iterator() { + final BufferedSource source = response.body().source(); + + final byte[] buffer = new byte[chunkSize]; + + boolean hasMore = true; + + @Override + public boolean hasNext() { + try { + if (source.exhausted()) { + hasMore = false; + } + } + catch (IOException e) { + hasMore = false; + } + return hasMore; + } + + @Override + public String next() { + try { + int bytesRead = source.read(buffer); + return new String(buffer, 0, bytesRead); + } + catch (IOException e) { + // Handle the exception + } + return ""; + } + }; + } + + public void writeToFile(String file) throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + + try (BufferedSource source = response.body().source(); FileOutputStream fos = new FileOutputStream(file)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = source.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } + } + + public void streamToFile(String file, int chunkSize) throws IOException { + if (response.body() == null) { + throw new IOException("ResponseBody is null"); + } + + try (BufferedSource source = response.body().source(); FileOutputStream fos = new FileOutputStream(file)) { + byte[] buffer = new byte[chunkSize]; + int bytesRead; + while ((bytesRead = source.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } + } + + @Override + public void close() throws IOException { + if (response.body() != null) { + response.body().close(); + } + } + +} diff --git a/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java b/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java new file mode 100644 index 0000000..322da34 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java @@ -0,0 +1,47 @@ +package ai.z.openapi.core.token; + +import ai.z.openapi.core.config.ZaiConfig; +import ai.z.openapi.utils.StringUtils; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.Objects; + +/** + * OkHttp Interceptor that adds an authorization token header + */ +public class AuthenticationInterceptor implements Interceptor { + + private final ZaiConfig config; + + public AuthenticationInterceptor(ZaiConfig config) { + Objects.requireNonNull(config.getApiKey(), "Z.ai token required"); + this.config = config; + } + + @Override + public Response intercept(Chain chain) throws IOException { + String accessToken; + if (this.config.isDisableTokenCache()) { + accessToken = this.config.getApiKey(); + } + else { + TokenManager tokenManager = GlobalTokenManager.getTokenManagerV4(); + accessToken = tokenManager.getToken(this.config); + } + String source_channel = "java-sdk"; + if (StringUtils.isNotEmpty(config.getSource_channel())) { + source_channel = config.getSource_channel(); + } + Request request = chain.request() + .newBuilder() + .header("Authorization", "Bearer " + accessToken) + .header("x-source-channel", source_channel) + .header("Accept-Language", "en-US,en") + .build(); + return chain.proceed(request); + } + +} diff --git a/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java b/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java new file mode 100644 index 0000000..05a6b63 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/token/GlobalTokenManager.java @@ -0,0 +1,32 @@ +package ai.z.openapi.core.token; + +import ai.z.openapi.core.cache.LocalCache; + +/** + * Global token manager providing singleton access to TokenManager instance. Uses + * LocalCache as default cache implementation. + */ +public class GlobalTokenManager { + + /** + * Global TokenManager instance with LocalCache. + */ + private static volatile TokenManager globalTokenManager = new TokenManager(LocalCache.getInstance()); + + /** + * Gets the global TokenManager instance. + * @return TokenManager instance + */ + public static TokenManager getTokenManagerV4() { + return globalTokenManager; + } + + /** + * Sets custom TokenManager implementation. + * @param tokenManager custom TokenManager instance + */ + public static void setTokenManager(TokenManager tokenManager) { + globalTokenManager = tokenManager; + } + +} diff --git a/core/src/main/java/ai/z/openapi/core/token/TokenManager.java b/core/src/main/java/ai/z/openapi/core/token/TokenManager.java new file mode 100644 index 0000000..33a24f9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/core/token/TokenManager.java @@ -0,0 +1,100 @@ +package ai.z.openapi.core.token; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import ai.z.openapi.core.config.ZaiConfig; +import ai.z.openapi.core.cache.ICache; +import ai.z.openapi.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * JWT token manager for handling token generation and caching. + */ +public class TokenManager { + + private static final Logger logger = LoggerFactory.getLogger(TokenManager.class); + + private final ICache cache; + + private static final String TOKEN_KEY_PREFIX = "zai_oapi_token"; + + /** + * Additional delay time (5 minutes) to prevent token expiration issues. + */ + private static final Long DELAY_EXPIRE_TIME = 5 * 60 * 1000L; + + /** + * Constructs TokenManager with specified cache implementation. + * @param cache cache implementation for token storage + */ + public TokenManager(ICache cache) { + this.cache = cache; + } + + /** + * Gets valid JWT token, either from cache or generates new one. + * @param config ZAI configuration containing API credentials + * @return valid JWT token + */ + public String getToken(ZaiConfig config) { + String tokenCacheKey = genTokenCacheKey(config.getApiId()); + String cacheToken = cache.get(tokenCacheKey); + if (StringUtils.isNotEmpty(cacheToken)) { + return cacheToken; + } + String newToken = createJwt(config); + cache.set(tokenCacheKey, newToken, config.getExpireMillis(), TimeUnit.MILLISECONDS); + return newToken; + } + + /** + * Creates JWT token using HMAC256 algorithm. + * @param config ZAI configuration + * @return JWT token string or null if creation fails + */ + private static String createJwt(ZaiConfig config) { + Algorithm alg; + String algId = config.getAlg(); + if ("HS256".equals(algId)) { + try { + alg = Algorithm.HMAC256(config.getApiSecret().getBytes(StandardCharsets.UTF_8)); + } + catch (Exception e) { + logger.error("Failed to create HMAC256 algorithm", e); + return null; + } + } + else { + // Currently only HS256 is supported + logger.error("Algorithm: {} not supported", algId); + return null; + } + + Map payload = new HashMap<>(); + // here the api_key is the apiId + payload.put("api_key", config.getApiId()); + payload.put("exp", System.currentTimeMillis() + config.getExpireMillis() + DELAY_EXPIRE_TIME); + payload.put("timestamp", Calendar.getInstance().getTimeInMillis()); + Map headerClaims = new HashMap<>(); + headerClaims.put("alg", "HS256"); + headerClaims.put("sign_type", "SIGN"); + return JWT.create().withPayload(payload).withHeader(headerClaims).sign(alg); + } + + /** + * Generates cache key for token storage. + * @param apiKey API key + * @return formatted cache key + */ + private String genTokenCacheKey(String apiKey) { + return String.format("%s-%s", TOKEN_KEY_PREFIX, apiKey); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java b/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java new file mode 100644 index 0000000..27c6864 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/AbstractClientBaseService.java @@ -0,0 +1,147 @@ +package ai.z.openapi.service; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.core.model.FlowableClientResponse; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.model.ResponseBodyCallback; +import ai.z.openapi.service.model.SSE; +import ai.z.openapi.service.model.ZAiError; +import ai.z.openapi.service.model.ZAiHttpException; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.RequestSupplier; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.reactivex.BackpressureStrategy; +import io.reactivex.Flowable; +import io.reactivex.Single; +import okhttp3.ResponseBody; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import retrofit2.Call; +import retrofit2.HttpException; +import retrofit2.Response; + +import java.io.IOException; + +/** + * Abstract base service class that provides common functionality for API client + * implementations. This class handles request execution, response processing, and error + * handling for both synchronous and streaming API calls. + */ +public abstract class AbstractClientBaseService { + + protected final static Logger logger = LoggerFactory.getLogger(AbstractClientBaseService.class); + + protected static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper(); + + /** + * Executes a synchronous API request. + * @param the type of data returned by the API + * @param the type of parameters sent to the API + * @param the type of the request object + * @param the type of the response object + * @param request the request object containing parameters + * @param requestSupplier the supplier that creates the API call + * @param tRespClass the class of the response type + * @return the response object containing the API result + */ + public abstract , TResp extends ClientResponse> TResp executeRequest( + TReq request, RequestSupplier requestSupplier, Class tRespClass); + + /** + * Executes a streaming API request that returns a continuous stream of data. + * @param the type of data returned by the API stream + * @param the type of parameters sent to the API + * @param the type of the request object + * @param the type of the streaming response object + * @param request the request object containing parameters + * @param requestSupplier the supplier that creates the streaming API call + * @param tRespClass the class of the response type + * @param tDataClass the class of the data type in the stream + * @return the streaming response object containing the API result stream + */ + public abstract , TResp extends FlowableClientResponse> TResp streamRequest( + TReq request, FlowableRequestSupplier> requestSupplier, Class tRespClass, + Class tDataClass); + + /** + * Executes a Single API call synchronously and handles errors. + * @param the type of the response + * @param apiCall the Single API call to execute + * @return the response from the API call + * @throws ZAiHttpException if an HTTP error occurs with a parseable error body + * @throws HttpException if an HTTP error occurs that cannot be parsed + */ + public static T execute(Single apiCall) { + try { + T response = apiCall.blockingGet(); + + // Check status code if the response is a Response type object + if (response instanceof Response) { + handleResponse((Response) response); + } + + return response; + } + catch (HttpException e) { + logger.error("HTTP exception: {}", e.getMessage()); + try { + if (e.response() == null || e.response().errorBody() == null) { + throw e; + } + String errorBody = e.response().errorBody().string(); + + ZAiError error = mapper.readValue(errorBody, ZAiError.class); + + throw new ZAiHttpException(error, e, e.code()); + } + catch (IOException ex) { + // couldn't parse ZAiError error + throw e; + } + } + } + + /** + * Handles HTTP response and throws exception if not successful. + * @param response the HTTP response to handle + * @throws HttpException if the response is not successful + */ + private static void handleResponse(Response response) { + if (!response.isSuccessful()) { + throw new HttpException(response); + } + } + + /** + * Creates a streaming Flowable that maps SSE data to the specified class type. + * @param the type to map the SSE data to + * @param apiCall the API call that returns streaming data + * @param cl the class to map the SSE data to + * @return a Flowable of the specified type + */ + public Flowable stream(retrofit2.Call apiCall, Class cl) { + return stream(apiCall).map(sse -> mapper.readValue(sse.getData(), cl)); + } + + /** + * Creates a streaming Flowable of SSE events without emitting done events. + * @param apiCall the API call that returns streaming data + * @return a Flowable of SSE events + */ + public static Flowable stream(retrofit2.Call apiCall) { + return stream(apiCall, false); + } + + /** + * Creates a streaming Flowable of SSE events with optional done event emission. + * @param apiCall the API call that returns streaming data + * @param emitDone whether to emit done events + * @return a Flowable of SSE events + */ + public static Flowable stream(retrofit2.Call apiCall, boolean emitDone) { + return Flowable.create(emitter -> apiCall.enqueue(new ResponseBodyCallback(emitter, emitDone)), + BackpressureStrategy.BUFFER); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/CommonRequest.java b/core/src/main/java/ai/z/openapi/service/CommonRequest.java new file mode 100644 index 0000000..71a703f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/CommonRequest.java @@ -0,0 +1,38 @@ +package ai.z.openapi.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.Map; + +/** + * Common request base class with extra JSON fields. Other ZAI request classes should + * extend this class to inherit common request parameters. + */ + +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +@Data +public class CommonRequest { + + /** + * Request ID provided by the client, must be unique. Used to distinguish each + * request. If not provided by the client, the platform will generate one by default. + */ + @JsonProperty("request_id") + private String requestId; + + /** + * A unique identifier representing your end-user, which will help ZAI to monitor and + * detect abuse. + */ + @JsonProperty("user_id") + private String userId; + + private Map extraJson; + +} diff --git a/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java b/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java new file mode 100644 index 0000000..5d80dc5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/agents/AgentAsyncResultRetrieveParams.java @@ -0,0 +1,43 @@ +package ai.z.openapi.service.agents; + +import ai.z.openapi.core.model.ClientRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parameters for retrieving agent asynchronous task results. This class contains the + * necessary parameters to query the result of an agent's async task. + */ +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AgentAsyncResultRetrieveParams implements ClientRequest> { + + /** + * The ID of the agent async task to retrieve + */ + @JsonProperty("task_id") + private String taskId; + + /** + * The agent ID associated with the async task + */ + @JsonProperty("agent_id") + private String agentId; + + /** + * Optional request ID for tracking + */ + @JsonProperty("request_id") + private String requestId; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/agents/AgentService.java b/core/src/main/java/ai/z/openapi/service/agents/AgentService.java new file mode 100644 index 0000000..62e0a0a --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/agents/AgentService.java @@ -0,0 +1,27 @@ +package ai.z.openapi.service.agents; + +import ai.z.openapi.service.model.ChatCompletionResponse; +import ai.z.openapi.service.model.ModelData; +import io.reactivex.Single; + +/** + * Agent completion service interface + */ +public interface AgentService { + + /** + * Creates an agent completion, either streaming or non-streaming based on the request + * configuration. + * @param request the agents completion request + * @return ChatCompletionResponse containing the agent completion result + */ + ChatCompletionResponse createAgentCompletion(AgentsCompletionRequest request); + + /** + * Retrieves the result of an asynchronous agent operation. + * @param request the query request for the async agent result + * @return Single containing the async agent operation result + */ + Single retrieveAgentAsyncResult(AgentAsyncResultRetrieveParams request); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/agents/AgentServiceImpl.java b/core/src/main/java/ai/z/openapi/service/agents/AgentServiceImpl.java new file mode 100644 index 0000000..6225497 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/agents/AgentServiceImpl.java @@ -0,0 +1,52 @@ +package ai.z.openapi.service.agents; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.agents.AgentsApi; +import ai.z.openapi.service.model.ChatCompletionResponse; +import ai.z.openapi.service.model.ModelData; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.RequestSupplier; +import io.reactivex.Single; +import okhttp3.ResponseBody; + +/** + * Agent completion service implementation + */ +public class AgentServiceImpl implements AgentService { + + private final ZaiClient zAiClient; + + private final AgentsApi agentsApi; + + public AgentServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.agentsApi = zAiClient.retrofit().create(AgentsApi.class); + } + + @Override + public ChatCompletionResponse createAgentCompletion(AgentsCompletionRequest request) { + if (request.getStream()) { + return streamAgentCompletion(request); + } + else { + return syncAgentCompletion(request); + } + } + + @Override + public Single retrieveAgentAsyncResult(AgentAsyncResultRetrieveParams request) { + return agentsApi.queryAgentsAsyncResult(request); + } + + private ChatCompletionResponse streamAgentCompletion(AgentsCompletionRequest request) { + FlowableRequestSupplier> supplier = agentsApi::agentsCompletionStream; + ; + return this.zAiClient.streamRequest(request, supplier, ChatCompletionResponse.class, ModelData.class); + } + + private ChatCompletionResponse syncAgentCompletion(AgentsCompletionRequest request) { + RequestSupplier supplier = agentsApi::agentsCompletionSync; + return this.zAiClient.executeRequest(request, supplier, ChatCompletionResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/agents/AgentsCompletionRequest.java b/core/src/main/java/ai/z/openapi/service/agents/AgentsCompletionRequest.java new file mode 100644 index 0000000..69fe15a --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/agents/AgentsCompletionRequest.java @@ -0,0 +1,60 @@ +package ai.z.openapi.service.agents; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.ChatMessage; +import ai.z.openapi.service.model.SensitiveWordCheckRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Request parameters for agent completion API calls. This class contains all the + * necessary parameters to initiate an agent completion request, including agent ID, + * messages, streaming options, and custom variables. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AgentsCompletionRequest extends CommonRequest implements ClientRequest { + + /** + * Agent ID + */ + @JsonProperty("agent_id") + private String agent_id; + + /** + * Message body + */ + private List messages; + + /** + * Synchronous call: false, SSE call: true + */ + private Boolean stream; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + + /** + * Agent business fields + * @return + */ + @JsonProperty("custom_variables") + private ObjectNode custom_variables; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantApiResponse.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantApiResponse.java new file mode 100644 index 0000000..a88e345 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantApiResponse.java @@ -0,0 +1,62 @@ +package ai.z.openapi.service.assistant; + +import ai.z.openapi.core.model.FlowableClientResponse; +import ai.z.openapi.service.model.ChatError; +import io.reactivex.Flowable; +import lombok.Data; + +/** + * Response wrapper for Assistant API calls that supports both synchronous and streaming + * responses. This class implements FlowableClientResponse to handle streaming assistant + * completions. + */ +@Data +public class AssistantApiResponse implements FlowableClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates whether the request was successful. + */ + private boolean success; + + /** + * The assistant completion data for synchronous responses. + */ + private AssistantCompletion data; + + /** + * The flowable stream for streaming responses. + */ + private Flowable flowable; + + /** + * Error information if the request failed. + */ + private ChatError error; + + /** + * Default constructor. + */ + public AssistantApiResponse() { + } + + /** + * Constructor with code and message. + * @param code the response code + * @param msg the response message + */ + public AssistantApiResponse(int code, String msg) { + this.code = code; + this.msg = msg; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantAttachments.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantAttachments.java new file mode 100644 index 0000000..95d3c6e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantAttachments.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents an attachment with a file ID. + */ +public class AssistantAttachments { + + /** + * The ID of the file attachment. + */ + @JsonProperty("file_id") + private String fileId; + + // Getters and Setters + + public String getFileId() { + return fileId; + } + + public void setFileId(String fileId) { + this.fileId = fileId; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantChoice.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantChoice.java new file mode 100644 index 0000000..d16c80e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantChoice.java @@ -0,0 +1,138 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.assistant.message.MessageContent; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.assistant.AssistantChoiceDeserializer; + +import java.util.Iterator; +import java.util.Map; + +/** + * Represents an assistant's choice output in conversation responses. This class contains + * the response index, message content, finish reason, and metadata. + */ +@JsonDeserialize(using = AssistantChoiceDeserializer.class) +public class AssistantChoice extends ObjectNode { + + /** + * Result index. + */ + @JsonProperty("index") + private int index; + + /** + * Current conversation output message content. + */ + @JsonProperty("delta") + private MessageContent delta; + + /** + * Reason for inference completion: - stop: inference naturally ended or triggered + * stop words - sensitive: model inference content was blocked by security review - + * network_error: model inference service exception + */ + @JsonProperty("finish_reason") + private String finishReason; + + /** + * Metadata, extension field. + */ + @JsonProperty("metadata") + private Map metadata; + + public AssistantChoice() { + super(JsonNodeFactory.instance); + } + + public AssistantChoice(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + if (objectNode == null) { + return; + } + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.has("index")) { + this.setIndex(objectNode.get("index").asInt()); + } + else { + this.setIndex(0); + } + if (objectNode.has("delta")) { + MessageContent delta = objectMapper.convertValue(objectNode.get("delta"), MessageContent.class); + this.setDelta(delta); + } + else { + this.setDelta(null); + } + if (objectNode.has("finish_reason")) { + this.setFinishReason(objectNode.get("finish_reason").asText()); + + } + else { + this.setFinishReason(null); + } + if (objectNode.has("metadata")) { + Map metadata = objectMapper.convertValue(objectNode.get("metadata"), + new TypeReference>() { + }); + + this.setMetadata(metadata); + } + else { + this.setMetadata(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + this.put("index", index); + } + + public MessageContent getDelta() { + return delta; + } + + public void setDelta(MessageContent delta) { + this.delta = delta; + this.putPOJO("delta", delta); + } + + public String getFinishReason() { + return finishReason; + } + + public void setFinishReason(String finishReason) { + this.finishReason = finishReason; + this.put("finish_reason", finishReason); + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + this.putPOJO("metadata", metadata); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantCompletion.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantCompletion.java new file mode 100644 index 0000000..65b37c5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantCompletion.java @@ -0,0 +1,254 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.assistant.AssistantCompletionDeserializer; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * This class represents the completion data returned by an assistant. + */ +@JsonDeserialize(using = AssistantCompletionDeserializer.class) +public class AssistantCompletion extends ObjectNode { + + /** + * Request ID + */ + @JsonProperty("id") + private String id; + + /** + * Conversation ID + */ + @JsonProperty("conversation_id") + private String conversationId; + + /** + * Assistant ID + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * Request creation time, Unix timestamp + */ + @JsonProperty("created") + private int created; + + /** + * Return status, including: `completed` indicates generation finished, `in_progress` + * indicates generating, `failed` indicates generation exception + */ + @JsonProperty("status") + private String status; + + /** + * Error information + */ + @JsonProperty("last_error") + private ErrorInfo lastError; + + /** + * Incremental return information + */ + @JsonProperty("choices") + private List choices; + + /** + * Metadata, extension field + */ + @JsonProperty("metadata") + private Map metadata; + + /** + * Token count statistics + */ + @JsonProperty("usage") + private CompletionUsage usage; + + public AssistantCompletion() { + super(JsonNodeFactory.instance); + } + + public AssistantCompletion(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.has("id")) { + this.setId(objectNode.get("id").asText()); + } + else { + this.setId(null); + } + + if (objectNode.has("conversation_id")) { + this.setConversationId(objectNode.get("conversation_id").asText()); + } + else { + this.setConversationId(null); + } + + if (objectNode.has("assistant_id")) { + this.setAssistantId(objectNode.get("assistant_id").asText()); + } + else { + this.setAssistantId(null); + } + + if (objectNode.has("created")) { + this.setCreated(objectNode.get("created").asInt()); + } + else { + this.setCreated(0); + } + + if (objectNode.has("status")) { + this.setStatus(objectNode.get("status").asText()); + + } + else { + this.setStatus(null); + } + + if (objectNode.has("last_error")) { + this.setLastError(objectMapper.convertValue(objectNode.get("last_error"), ErrorInfo.class)); + } + else { + this.setLastError(null); + } + + if (objectNode.has("choices")) { + List choices1 = objectMapper.convertValue(objectNode.get("choices"), + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + + this.setChoices(choices1); + + } + else { + this.setChoices(null); + } + + if (objectNode.has("metadata")) { + this.setMetadata(objectMapper.convertValue(objectNode.get("metadata"), Map.class)); + } + else { + this.setMetadata(null); + } + + if (objectNode.has("usage")) { + this.setUsage(objectMapper.convertValue(objectNode.get("usage"), CompletionUsage.class)); + } + else { + this.setUsage(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + this.put("id", id); + } + + public String getConversationId() { + return conversationId; + } + + public void setConversationId(String conversationId) { + this.conversationId = conversationId; + this.put("conversation_id", conversationId); + } + + public String getAssistantId() { + return assistantId; + } + + public void setAssistantId(String assistantId) { + this.assistantId = assistantId; + this.put("assistant_id", assistantId); + } + + public int getCreated() { + return created; + } + + public void setCreated(int created) { + this.created = created; + this.put("created", created); + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + this.put("status", status); + } + + public ErrorInfo getLastError() { + return lastError; + } + + public void setLastError(ErrorInfo lastError) { + this.lastError = lastError; + this.putPOJO("last_error", lastError); + } + + public List getChoices() { + return choices; + } + + public void setChoices(List choices) { + this.choices = choices; + ArrayNode jsonNodes = this.putArray("choices"); + if (choices == null) { + jsonNodes.removeAll(); + } + else { + + for (AssistantChoice choice : choices) { + jsonNodes.add(choice); + } + } + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + this.putPOJO("metadata", metadata); + } + + public CompletionUsage getUsage() { + return usage; + } + + public void setUsage(CompletionUsage usage) { + this.usage = usage; + this.putPOJO("usage", usage); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantExtraParameters.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantExtraParameters.java new file mode 100644 index 0000000..28c4a1c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantExtraParameters.java @@ -0,0 +1,23 @@ +package ai.z.openapi.service.assistant; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Extra parameters for assistant configuration. This class contains additional optional + * parameters that can be used to customize assistant behavior and functionality. + */ +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantExtraParameters { + + /** + * Translation agent parameters + */ + private TranslateParameters translate; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantParameters.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantParameters.java new file mode 100644 index 0000000..a0c07da --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantParameters.java @@ -0,0 +1,71 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class represents the parameters for an assistant, including optional fields. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AssistantParameters extends CommonRequest implements ClientRequest { + + /** + * The ID of the assistant. + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * The conversation ID. If not provided, a new conversation is created. + */ + @JsonProperty("conversation_id") + private String conversationId; + + /** + * The name of the model, default is 'GLM-4-Assistant'. + */ + @JsonProperty("model") + private String model; + + /** + * Whether to support streaming SSE, should be set to True. + */ + @JsonProperty("stream") + private boolean stream; + + /** + * The list of conversation messages. + */ + @JsonProperty("messages") + private List messages; + + /** + * The list of file attachments for the conversation, optional. + */ + @JsonProperty("attachments") + private List attachments; + + /** + * Metadata or additional fields, optional. + */ + @JsonProperty("metadata") + private Map metadata; + + @JsonProperty("extra_parameters") + private AssistantExtraParameters extraParameters; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantService.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantService.java new file mode 100644 index 0000000..b6f81d7 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantService.java @@ -0,0 +1,43 @@ +package ai.z.openapi.service.assistant; + +import ai.z.openapi.service.assistant.AssistantParameters; +import ai.z.openapi.service.assistant.AssistantApiResponse; +import ai.z.openapi.service.assistant.conversation.ConversationParameters; +import ai.z.openapi.service.assistant.conversation.ConversationUsageListResponse; +import ai.z.openapi.service.assistant.query_support.AssistantSupportResponse; +import ai.z.openapi.service.assistant.query_support.QuerySupportParams; + +/** + * Assistant service interface + */ +public interface AssistantService { + + /** + * Creates a streaming assistant completion. + * @param request the assistant completion request + * @return AssistantApiResponse containing the completion result + */ + AssistantApiResponse assistantCompletionStream(AssistantParameters request); + + /** + * Creates a non-streaming assistant completion. + * @param request the assistant completion request + * @return AssistantApiResponse containing the completion result + */ + AssistantApiResponse assistantCompletion(AssistantParameters request); + + /** + * Queries assistant support status. + * @param request the query support request + * @return AssistantSupportResponse containing the support information + */ + AssistantSupportResponse querySupport(QuerySupportParams request); + + /** + * Queries conversation usage information. + * @param request the conversation parameters + * @return ConversationUsageListResponse containing the usage information + */ + ConversationUsageListResponse queryConversationUsage(ConversationParameters request); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/assistant/AssistantServiceImpl.java b/core/src/main/java/ai/z/openapi/service/assistant/AssistantServiceImpl.java new file mode 100644 index 0000000..2a36689 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/AssistantServiceImpl.java @@ -0,0 +1,53 @@ +package ai.z.openapi.service.assistant; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.assistant.AssistantApi; +import ai.z.openapi.service.assistant.conversation.ConversationParameters; +import ai.z.openapi.service.assistant.conversation.ConversationUsageListResponse; +import ai.z.openapi.service.assistant.conversation.ConversationUsageListStatus; +import ai.z.openapi.service.assistant.query_support.AssistantSupportResponse; +import ai.z.openapi.service.assistant.query_support.AssistantSupportStatus; +import ai.z.openapi.service.assistant.query_support.QuerySupportParams; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.RequestSupplier; +import okhttp3.ResponseBody; + +/** + * Implementation of AssistantService + */ +public class AssistantServiceImpl implements AssistantService { + + private final ZaiClient zAiClient; + + private final AssistantApi assistantApi; + + public AssistantServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.assistantApi = zAiClient.retrofit().create(AssistantApi.class); + } + + @Override + public AssistantApiResponse assistantCompletionStream(AssistantParameters request) { + FlowableRequestSupplier> supplier = assistantApi::assistantCompletionStream; + return zAiClient.streamRequest(request, supplier, AssistantApiResponse.class, AssistantCompletion.class); + } + + @Override + public AssistantApiResponse assistantCompletion(AssistantParameters request) { + RequestSupplier supplier = assistantApi::assistantCompletion; + return zAiClient.executeRequest(request, supplier, AssistantApiResponse.class); + } + + @Override + public AssistantSupportResponse querySupport(QuerySupportParams request) { + RequestSupplier supplier = assistantApi::querySupport; + return zAiClient.executeRequest(request, supplier, AssistantSupportResponse.class); + } + + @Override + public ConversationUsageListResponse queryConversationUsage(ConversationParameters request) { + RequestSupplier supplier = assistantApi::queryConversationUsage; + return zAiClient.executeRequest(request, supplier, ConversationUsageListResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/assistant/CompletionUsage.java b/core/src/main/java/ai/z/openapi/service/assistant/CompletionUsage.java new file mode 100644 index 0000000..25c5006 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/CompletionUsage.java @@ -0,0 +1,101 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.assistant.CompletionUsageDeserializer; + +import java.util.Iterator; + +/** + * This class represents the usage statistics for a completion. + */ +@JsonDeserialize(using = CompletionUsageDeserializer.class) +public class CompletionUsage extends ObjectNode { + + /** + * Number of tokens in the input (prompt). + */ + @JsonProperty("prompt_tokens") + private int promptTokens; + + /** + * Number of tokens in the output (completion). + */ + @JsonProperty("completion_tokens") + private int completionTokens; + + /** + * Total number of tokens used. + */ + @JsonProperty("total_tokens") + private int totalTokens; + + public CompletionUsage() { + super(JsonNodeFactory.instance); + } + + public CompletionUsage(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("prompt_tokens") != null) { + this.setPromptTokens(objectNode.get("prompt_tokens").asInt()); + } + else { + this.setPromptTokens(0); + } + if (objectNode.get("completion_tokens") != null) { + this.setCompletionTokens(objectNode.get("completion_tokens").asInt()); + } + else { + this.setCompletionTokens(0); + } + if (objectNode.get("total_tokens") != null) { + this.setTotalTokens(objectNode.get("total_tokens").asInt()); + } + else { + this.setTotalTokens(0); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public int getPromptTokens() { + return promptTokens; + } + + public void setPromptTokens(int promptTokens) { + this.promptTokens = promptTokens; + this.put("prompt_tokens", promptTokens); + } + + public int getCompletionTokens() { + return completionTokens; + } + + public void setCompletionTokens(int completionTokens) { + this.completionTokens = completionTokens; + this.put("completion_tokens", completionTokens); + } + + public int getTotalTokens() { + return totalTokens; + } + + public void setTotalTokens(int totalTokens) { + this.totalTokens = totalTokens; + this.put("total_tokens", totalTokens); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/ConversationMessage.java b/core/src/main/java/ai/z/openapi/service/assistant/ConversationMessage.java new file mode 100644 index 0000000..936ce34 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/ConversationMessage.java @@ -0,0 +1,34 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + * This class represents a conversation message body. + */ +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ConversationMessage { + + /** + * The role of the user input, e.g., 'user'. + */ + @JsonProperty("role") + private String role; + + /** + * The content of the conversation message. + */ + @JsonProperty("content") + private List content; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/ErrorInfo.java b/core/src/main/java/ai/z/openapi/service/assistant/ErrorInfo.java new file mode 100644 index 0000000..ae7fad4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/ErrorInfo.java @@ -0,0 +1,80 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.assistant.ErrorInfoDeserializer; + +import java.util.Iterator; + +/** + * This class represents error information. + */ +@JsonDeserialize(using = ErrorInfoDeserializer.class) +public class ErrorInfo extends ObjectNode { + + /** + * Error code. + */ + @JsonProperty("code") + private String code; + + /** + * Error message. + */ + @JsonProperty("message") + private String message; + + public ErrorInfo() { + super(JsonNodeFactory.instance); + } + + public ErrorInfo(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + + if (objectNode.get("code") != null) { + this.setCode(objectNode.get("code").asText()); + } + else { + this.setCode(null); + } + if (objectNode.get("message") != null) { + this.setMessage(objectNode.get("message").asText()); + } + else { + this.setMessage(null); + } + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + // Getters and Setters + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + this.put("code", code); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + this.put("message", message); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/MessageTextContent.java b/core/src/main/java/ai/z/openapi/service/assistant/MessageTextContent.java new file mode 100644 index 0000000..70ad537 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/MessageTextContent.java @@ -0,0 +1,33 @@ +package ai.z.openapi.service.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * This class represents the text content of a message. Currently supports only type = + * text. + */ +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class MessageTextContent { + + /** + * The type of the message content, currently only "text" is supported. + */ + @JsonProperty("type") + private String type; + + /** + * The text content of the message. + */ + @JsonProperty("text") + private String text; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/TranslateParameters.java b/core/src/main/java/ai/z/openapi/service/assistant/TranslateParameters.java new file mode 100644 index 0000000..21a840f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/TranslateParameters.java @@ -0,0 +1,28 @@ +package ai.z.openapi.service.assistant; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Parameters for translation operations. This class contains the source and target + * language settings for translation requests. + */ +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class TranslateParameters { + + /** + * Source language for translation. + */ + private String from; + + /** + * Target language for translation. + */ + private String to; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationParameters.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationParameters.java new file mode 100644 index 0000000..dd8d99d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationParameters.java @@ -0,0 +1,40 @@ +package ai.z.openapi.service.assistant.conversation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * This class represents the parameters for a conversation, including pagination. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ConversationParameters extends CommonRequest implements ClientRequest { + + /** + * The Assistant ID. + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * The current page number for pagination. + */ + @JsonProperty("page") + private int page; + + /** + * The number of items per page for pagination. + */ + @JsonProperty("page_size") + private int pageSize; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsage.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsage.java new file mode 100644 index 0000000..6a2fff5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsage.java @@ -0,0 +1,44 @@ +package ai.z.openapi.service.assistant.conversation; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * This class represents the usage data for a specific conversation. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationUsage { + + /** + * The conversation ID. + */ + @JsonProperty("id") + private String id; + + /** + * The Assistant ID. + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * The creation time of the conversation. + */ + @JsonProperty("create_time") + private String createTime; + + /** + * The last update time of the conversation. + */ + @JsonProperty("update_time") + private String updateTime; + + /** + * The usage statistics for the conversation. + */ + @JsonProperty("usage") + private Usage usage; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageList.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageList.java new file mode 100644 index 0000000..42aecd4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageList.java @@ -0,0 +1,34 @@ +package ai.z.openapi.service.assistant.conversation; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * This class represents a list of conversation usage data. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationUsageList { + + /** + * The Assistant ID. + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * Whether there are more pages of results. + */ + @JsonProperty("has_more") + private boolean hasMore; + + /** + * The list of conversation usage data. + */ + @JsonProperty("conversation_list") + private List conversationList; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListResponse.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListResponse.java new file mode 100644 index 0000000..faeac90 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListResponse.java @@ -0,0 +1,47 @@ +package ai.z.openapi.service.assistant.conversation; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Response for conversation usage list API calls. This class contains the response data + * for retrieving conversation usage statistics. + */ +@Data +public class ConversationUsageListResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates if the request was successful. + */ + private boolean success; + + /** + * The conversation usage list data. + */ + private ConversationUsageListStatus data; + + /** + * Error information if the request failed. + */ + private ChatError error; + + public ConversationUsageListResponse() { + } + + public ConversationUsageListResponse(int code, String msg) { + this.code = code; + this.msg = msg; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListStatus.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListStatus.java new file mode 100644 index 0000000..5454a15 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/ConversationUsageListStatus.java @@ -0,0 +1,32 @@ +package ai.z.openapi.service.assistant.conversation; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * This class represents the response containing a list of conversation usage data. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConversationUsageListStatus { + + /** + * The response code. + */ + @JsonProperty("code") + private int code; + + /** + * The response message. + */ + @JsonProperty("msg") + private String msg; + + /** + * The data containing the conversation usage list. + */ + @JsonProperty("data") + private ConversationUsageList data; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/conversation/Usage.java b/core/src/main/java/ai/z/openapi/service/assistant/conversation/Usage.java new file mode 100644 index 0000000..ac76183 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/conversation/Usage.java @@ -0,0 +1,32 @@ +package ai.z.openapi.service.assistant.conversation; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * This class represents the usage statistics for a conversation. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Usage { + + /** + * The number of tokens in the user's input. + */ + @JsonProperty("prompt_tokens") + private int promptTokens; + + /** + * The number of tokens in the model's input. + */ + @JsonProperty("completion_tokens") + private int completionTokens; + + /** + * The total number of tokens. + */ + @JsonProperty("total_tokens") + private int totalTokens; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/MessageContent.java b/core/src/main/java/ai/z/openapi/service/assistant/message/MessageContent.java new file mode 100644 index 0000000..cf1c3dc --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/MessageContent.java @@ -0,0 +1,13 @@ +package ai.z.openapi.service.assistant.message; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import ai.z.openapi.service.deserialize.JsonTypeMapping; +import ai.z.openapi.service.deserialize.assistant.message.MessageContentDeserializer; + +@JsonTypeMapping({ ToolsDeltaBlock.class, TextContentBlock.class }) +@JsonDeserialize(using = MessageContentDeserializer.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class MessageContent { + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/TextContentBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/TextContentBlock.java new file mode 100644 index 0000000..7d4b9a4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/TextContentBlock.java @@ -0,0 +1,44 @@ +package ai.z.openapi.service.assistant.message; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.service.deserialize.JsonTypeField; + +/** + * This class represents a block of text content in a conversation. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("content") +public class TextContentBlock extends MessageContent { + + /** + * The content of the text block. + */ + @JsonProperty("content") + private String content; + + /** + * The role of the speaker, default is "assistant". + */ + @JsonProperty("role") + private String role = "assistant"; + + // Getters and Setters + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/ToolsDeltaBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/ToolsDeltaBlock.java new file mode 100644 index 0000000..5e2bdf6 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/ToolsDeltaBlock.java @@ -0,0 +1,47 @@ +package ai.z.openapi.service.assistant.message; + +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 java.util.List; + +/** + * This class represents a block of tool call data in a conversation. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("tool_calls") +public class ToolsDeltaBlock extends MessageContent { + + /** + * A list of tool call types. + */ + @JsonProperty("tool_calls") + private List toolCalls; + + /** + * The role of the speaker, default is "tool". + */ + @JsonProperty("role") + private String role = "tool"; + + // Getters and Setters + + public List getToolCalls() { + return toolCalls; + } + + public void setToolCalls(List toolCalls) { + this.toolCalls = toolCalls; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/ToolsType.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/ToolsType.java new file mode 100644 index 0000000..40b4eab --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/ToolsType.java @@ -0,0 +1,19 @@ +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 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) +public abstract class ToolsType { + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreter.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreter.java new file mode 100644 index 0000000..b7ac8cd --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreter.java @@ -0,0 +1,41 @@ +package ai.z.openapi.service.assistant.message.tools.code_interpreter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * This class represents a code interpreter that executes code and returns the results. + */ +public class CodeInterpreter { + + /** + * The generated code snippet that is input to the code sandbox. + */ + @JsonProperty("input") + private String input; + + /** + * The output results after the code execution. + */ + @JsonProperty("outputs") + private List outputs; + + // Getters and Setters + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public List getOutputs() { + return outputs; + } + + public void setOutputs(List outputs) { + this.outputs = outputs; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolBlock.java new file mode 100644 index 0000000..5d9327f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolBlock.java @@ -0,0 +1,45 @@ +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; + +/** + * This class represents a block of code tool data. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("code_interpreter") +public class CodeInterpreterToolBlock extends ToolsType { + + /** + * The code interpreter object. + */ + @JsonProperty("code_interpreter") + private CodeInterpreter codeInterpreter; + + /** + * The type of tool being called, always "code_interpreter". + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolOutput.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolOutput.java new file mode 100644 index 0000000..38152f0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/code_interpreter/CodeInterpreterToolOutput.java @@ -0,0 +1,54 @@ +package ai.z.openapi.service.assistant.message.tools.code_interpreter; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents the output result of a code tool. + */ +public class CodeInterpreterToolOutput { + + /** + * The type of output, currently only "logs". + */ + @JsonProperty("type") + private String type; + + /** + * The log results from the code execution. + */ + @JsonProperty("logs") + private String logs; + + /** + * Error message if any occurred during code execution. + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingTool.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingTool.java new file mode 100644 index 0000000..a18ab67 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingTool.java @@ -0,0 +1,41 @@ +package ai.z.openapi.service.assistant.message.tools.drawing_tool; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * The input string that was used to generate the drawing. + */ +public class DrawingTool { + + /** + * The input string that was used to generate the drawing. + */ + @JsonProperty("input") + private String input; + + /** + * A list of outputs generated by the drawing tool. + */ + @JsonProperty("outputs") + private List outputs; + + // Getters and Setters + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public List getOutputs() { + return outputs; + } + + public void setOutputs(List outputs) { + this.outputs = outputs; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolBlock.java new file mode 100644 index 0000000..9a0d536 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolBlock.java @@ -0,0 +1,45 @@ +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; + +/** + * This class represents a block of drawing tool data. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("drawing_tool") +public class DrawingToolBlock extends ToolsType { + + /** + * The drawing tool object that contains input and outputs. + */ + @JsonProperty("drawing_tool") + private DrawingTool drawingTool; + + /** + * The type of tool being called, always "drawing_tool". + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolOutput.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolOutput.java new file mode 100644 index 0000000..5d060a0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/drawing_tool/DrawingToolOutput.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.assistant.message.tools.drawing_tool; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents the output of a drawing tool, containing the generated image. + */ +public class DrawingToolOutput { + + /** + * The generated image in a string format. + */ + @JsonProperty("image") + private String image; + + // Getters and Setters + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionTool.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionTool.java new file mode 100644 index 0000000..810bc20 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionTool.java @@ -0,0 +1,58 @@ +package ai.z.openapi.service.assistant.message.tools.function; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.List; + +/** + * This class represents a function tool with a name, arguments, and outputs. + */ +public class FunctionTool { + + /** + * The name of the function tool. + */ + @JsonProperty("name") + private String name; + + /** + * 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 + + /** + * A list of outputs generated by the function tool. + */ + @JsonProperty("outputs") + private List 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 getOutputs() { + return outputs; + } + + public void setOutputs(List outputs) { + this.outputs = outputs; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolBlock.java new file mode 100644 index 0000000..5345ef9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolBlock.java @@ -0,0 +1,45 @@ +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; + +/** + * This class represents a block of function tool data. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("function") +public class FunctionToolBlock extends ToolsType { + + /** + * The function tool object that contains the name, arguments, and outputs. + */ + @JsonProperty("function") + private FunctionTool function; + + /** + * The type of tool being called, always "function". + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolOutput.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolOutput.java new file mode 100644 index 0000000..b2ab2ab --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/function/FunctionToolOutput.java @@ -0,0 +1,24 @@ +package ai.z.openapi.service.assistant.message.tools.function; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents the output of a function tool, containing the generated content. + */ +public class FunctionToolOutput { + + /** + * The generated content as a string. + */ + @JsonProperty("content") + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalTool.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalTool.java new file mode 100644 index 0000000..b14fb42 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalTool.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.assistant.message.tools.retrieval; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * This class represents the outputs of a retrieval tool. + */ +public class RetrievalTool { + + /** + * A list of text snippets and their respective document names retrieved from the + * knowledge base. + */ + @JsonProperty("outputs") + private List outputs; + + public List getOutputs() { + return outputs; + } + + public void setOutputs(List outputs) { + this.outputs = outputs; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolBlock.java new file mode 100644 index 0000000..77b3586 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolBlock.java @@ -0,0 +1,44 @@ +package ai.z.openapi.service.assistant.message.tools.retrieval; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.service.deserialize.JsonTypeField; + +/** + * This class represents a block for invoking the retrieval tool. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("retrieval") +public class RetrievalToolBlock { + + /** + * An instance of the RetrievalTool class containing the retrieval outputs. + */ + @JsonProperty("retrieval") + private RetrievalTool retrieval; + + /** + * The type of tool being used, always set to "retrieval". + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolOutput.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolOutput.java new file mode 100644 index 0000000..b70ce33 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/retrieval/RetrievalToolOutput.java @@ -0,0 +1,41 @@ +package ai.z.openapi.service.assistant.message.tools.retrieval; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents the output of a retrieval tool. + */ +public class RetrievalToolOutput { + + /** + * The text snippet retrieved from the knowledge base. + */ + @JsonProperty("text") + private String text; + + /** + * The name of the document from which the text snippet was retrieved, returned only + * in intelligent configuration. + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowser.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowser.java new file mode 100644 index 0000000..51e39c4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowser.java @@ -0,0 +1,41 @@ +package ai.z.openapi.service.assistant.message.tools.web_browser; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * This class represents the input and outputs of a web browser search. + */ +public class WebBrowser { + + /** + * The input query for the web browser search. + */ + @JsonProperty("input") + private String input; + + /** + * A list of search results returned by the web browser. + */ + @JsonProperty("outputs") + private List outputs; + + // Getters and Setters + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public List getOutputs() { + return outputs; + } + + public void setOutputs(List outputs) { + this.outputs = outputs; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserOutput.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserOutput.java new file mode 100644 index 0000000..1368b70 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserOutput.java @@ -0,0 +1,68 @@ +package ai.z.openapi.service.assistant.message.tools.web_browser; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class represents the output of a web browser search result. + */ +public class WebBrowserOutput { + + /** + * The title of the search result. + */ + @JsonProperty("title") + private String title; + + /** + * The URL link to the search result's webpage. + */ + @JsonProperty("link") + private String link; + + /** + * The textual content extracted from the search result. + */ + @JsonProperty("content") + private String content; + + /** + * Any error message encountered during the search or retrieval process. + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserToolBlock.java b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserToolBlock.java new file mode 100644 index 0000000..e20d28f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/message/tools/web_browser/WebBrowserToolBlock.java @@ -0,0 +1,45 @@ +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; + +/** + * This class represents a block for invoking the web browser tool. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeField("web_browser") +public class WebBrowserToolBlock extends ToolsType { + + /** + * An instance of the WebBrowser class containing the search input and outputs. + */ + @JsonProperty("web_browser") + private WebBrowser webBrowser; + + /** + * The type of tool being used, always set to "web_browser". + */ + @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; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupport.java b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupport.java new file mode 100644 index 0000000..5082e17 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupport.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.assistant.query_support; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * This class represents the details of an assistant. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class AssistantSupport { + + /** + * The Assistant ID, used for assistant conversations. + */ + @JsonProperty("assistant_id") + private String assistantId; + + /** + * The creation time of the assistant. + */ + @JsonProperty("created_at") + private String createdAt; + + /** + * The last update time of the assistant. + */ + @JsonProperty("updated_at") + private String updatedAt; + + /** + * The name of the assistant. + */ + @JsonProperty("name") + private String name; + + /** + * The avatar of the assistant. + */ + @JsonProperty("avatar") + private String avatar; + + /** + * The description of the assistant. + */ + @JsonProperty("description") + private String description; + + /** + * The status of the assistant, currently only "publish". + */ + @JsonProperty("status") + private String status; + + /** + * The list of tools supported by the assistant. + */ + @JsonProperty("tools") + private List tools; + + /** + * The list of recommended prompts to start the assistant. + */ + @JsonProperty("starter_prompts") + private List starterPrompts; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportResponse.java b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportResponse.java new file mode 100644 index 0000000..1c09f4c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportResponse.java @@ -0,0 +1,47 @@ +package ai.z.openapi.service.assistant.query_support; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Response for assistant support query API calls. This class contains the response data + * for checking assistant support status. + */ +@Data +public class AssistantSupportResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates if the request was successful. + */ + private boolean success; + + /** + * The assistant support status data. + */ + private AssistantSupportStatus data; + + /** + * Error information if the request failed. + */ + private ChatError error; + + public AssistantSupportResponse() { + } + + public AssistantSupportResponse(int code, String msg) { + this.code = code; + this.msg = msg; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportStatus.java b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportStatus.java new file mode 100644 index 0000000..39aed27 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/query_support/AssistantSupportStatus.java @@ -0,0 +1,34 @@ +package ai.z.openapi.service.assistant.query_support; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * This class represents the response containing a list of assistant supports. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class AssistantSupportStatus { + + /** + * The response code. + */ + @JsonProperty("code") + private Integer code; + + /** + * The response message. + */ + @JsonProperty("msg") + private String msg; + + /** + * The list of assistant supports. + */ + @JsonProperty("data") + private List data; + +} diff --git a/core/src/main/java/ai/z/openapi/service/assistant/query_support/QuerySupportParams.java b/core/src/main/java/ai/z/openapi/service/assistant/query_support/QuerySupportParams.java new file mode 100644 index 0000000..33fdd9c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/assistant/query_support/QuerySupportParams.java @@ -0,0 +1,31 @@ +package ai.z.openapi.service.assistant.query_support; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + * Parameters for querying assistant support status. This class contains the parameters + * needed to query the support status of specific assistants. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QuerySupportParams extends CommonRequest implements ClientRequest { + + /** + * List of assistant IDs to query support status for. + */ + @JsonProperty("assistant_id_list") + private List assistantIdList; + +} diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationApiResponse.java b/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationApiResponse.java new file mode 100644 index 0000000..41eeca8 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationApiResponse.java @@ -0,0 +1,42 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +import java.io.File; + +/** + * Response wrapper for audio customization API calls. This class contains the response + * data for audio customization requests, including the generated audio file and status + * information. + */ +@Data +public class AudioCustomizationApiResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates whether the request was successful. + */ + private boolean success; + + /** + * The generated audio file. + */ + private File data; + + /** + * Error information if the request failed. + */ + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationRequest.java b/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationRequest.java new file mode 100644 index 0000000..03f51ca --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioCustomizationRequest.java @@ -0,0 +1,63 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.SensitiveWordCheckRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * Request parameters for audio customization API calls. This class contains all the + * necessary parameters for generating customized audio, including input text, model + * selection, voice cloning data, and response format options. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AudioCustomizationRequest extends CommonRequest implements ClientRequest { + + /** + * Text to generate audio from + */ + private String input; + + /** + * Model code to call + */ + private String model; + + /** + * Text description of the original audio to clone + */ + @JsonProperty("voice_text") + private String voiceText; + + /** + * Original audio file to clone + */ + @JsonProperty("voice_data") + private File voiceData; + + /** + * Audio response format + */ + @JsonProperty("response_format") + private String responseFormat; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + +} diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioService.java b/core/src/main/java/ai/z/openapi/service/audio/AudioService.java new file mode 100644 index 0000000..86e6258 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioService.java @@ -0,0 +1,31 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.service.model.ChatCompletionResponse; + +/** + * Audio service interface + */ +public interface AudioService { + + /** + * Creates speech from text using text-to-speech. + * @param request the speech generation request + * @return AudioSpeechApiResponse containing the generated speech + */ + AudioSpeechApiResponse createSpeech(AudioSpeechRequest request); + + /** + * Creates customized speech with specific voice characteristics. + * @param request the speech customization request + * @return AudioCustomizationApiResponse containing the customized speech result + */ + AudioCustomizationApiResponse createCustomSpeech(AudioCustomizationRequest request); + + /** + * Creates audio transcriptions from audio files. + * @param request the transcription request + * @return ChatCompletionResponse containing the transcription result + */ + ChatCompletionResponse createTranscription(AudioTranscriptionsRequest request); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioServiceImpl.java b/core/src/main/java/ai/z/openapi/service/audio/AudioServiceImpl.java new file mode 100644 index 0000000..4609d08 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioServiceImpl.java @@ -0,0 +1,213 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.audio.AudioApi; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.model.ChatCompletionResponse; +import ai.z.openapi.service.model.ModelData; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.RequestSupplier; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.reactivex.Single; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import org.apache.tika.Tika; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Audio service implementation + */ +@Slf4j +public class AudioServiceImpl implements AudioService { + + protected static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper(); + + private final ZaiClient zAiClient; + + private final AudioApi audioApi; + + public AudioServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.audioApi = zAiClient.retrofit().create(AudioApi.class); + } + + @Override + public AudioSpeechApiResponse createSpeech(AudioSpeechRequest request) { + RequestSupplier supplier = (params) -> { + try { + Single responseBody = audioApi.audioSpeech(params); + Path tempDirectory = Files.createTempFile("audio_speech" + UUID.randomUUID(), ".wav"); + java.io.File file = tempDirectory.toFile(); + writeResponseBodyToFile(responseBody.blockingGet(), file); + return Single.just(file); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }; + return this.zAiClient.executeRequest(request, supplier, AudioSpeechApiResponse.class); + } + + @Override + public AudioCustomizationApiResponse createCustomSpeech(AudioCustomizationRequest request) { + RequestSupplier supplier = (params) -> { + try { + java.io.File voiceFile = params.getVoiceData(); + RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), voiceFile); + MultipartBody.Part voiceData = MultipartBody.Part.createFormData("voice_data", voiceFile.getName(), + requestFile); + + Map requestMap = new HashMap<>(); + if (params.getInput() != null) { + requestMap.put("input", RequestBody.create(MediaType.parse("text/plain"), params.getInput())); + } + if (params.getModel() != null) { + requestMap.put("model", RequestBody.create(MediaType.parse("text/plain"), params.getModel())); + } + if (params.getVoiceText() != null) { + requestMap.put("voice_text", + RequestBody.create(MediaType.parse("text/plain"), params.getVoiceText())); + } + if (params.getResponseFormat() != null) { + requestMap.put("response_format", + RequestBody.create(MediaType.parse("text/plain"), params.getResponseFormat())); + } + if (params.getSensitiveWordCheck() != null) { + try { + String sensitiveWordCheckJson = mapper.writeValueAsString(params.getSensitiveWordCheck()); + requestMap.put("sensitive_word_check", + RequestBody.create(MediaType.parse("application/json"), sensitiveWordCheckJson)); + } + catch (Exception e) { + log.error("Error serializing sensitive_word_check: {}", e.getMessage(), e); + } + } + if (params.getRequestId() != null) { + requestMap.put("request_id", + RequestBody.create(MediaType.parse("text/plain"), params.getRequestId())); + } + if (params.getUserId() != null) { + requestMap.put("user_id", RequestBody.create(MediaType.parse("text/plain"), params.getUserId())); + } + + Single responseBody = audioApi.audioCustomization(requestMap, voiceData); + Path tempDirectory = Files.createTempFile("audio_customization" + UUID.randomUUID(), ".wav"); + java.io.File file = tempDirectory.toFile(); + writeResponseBodyToFile(responseBody.blockingGet(), file); + return Single.just(file); + } + catch (IOException e) { + log.error("Error create custom speak: {}", e.getMessage(), e); + throw new RuntimeException(e); + } + }; + return this.zAiClient.executeRequest(request, supplier, AudioCustomizationApiResponse.class); + } + + @Override + public ChatCompletionResponse createTranscription(AudioTranscriptionsRequest request) { + if (request.getStream()) { + return createTranscriptionStream(request); + } + else { + return createTranscriptionSync(request); + } + } + + private ChatCompletionResponse createTranscriptionStream(AudioTranscriptionsRequest request) { + FlowableRequestSupplier> supplier = params -> { + try { + java.io.File file = params.getFile(); + Tika tika = new Tika(); + String contentType = tika.detect(file); + RequestBody requestFile = RequestBody.create(MediaType.parse(contentType), file); + MultipartBody.Part fileData = MultipartBody.Part.createFormData("file", file.getName(), requestFile); + + Map requestMap = new HashMap<>(); + if (params.getModel() != null) { + requestMap.put("model", RequestBody.create(MediaType.parse("text/plain"), params.getModel())); + } + if (params.getStream() != null) { + requestMap.put("stream", + RequestBody.create(MediaType.parse("text/plain"), params.getStream().toString())); + } + if (params.getRequestId() != null) { + requestMap.put("request_id", + RequestBody.create(MediaType.parse("text/plain"), params.getRequestId())); + } + if (params.getUserId() != null) { + requestMap.put("user_id", RequestBody.create(MediaType.parse("text/plain"), params.getUserId())); + } + + return audioApi.audioTranscriptionsStream(requestMap, fileData); + } + catch (IOException e) { + log.error("Error create transcription: {}", e.getMessage(), e); + throw new RuntimeException(e); + } + }; + return this.zAiClient.streamRequest(request, supplier, ChatCompletionResponse.class, ModelData.class); + } + + private ChatCompletionResponse createTranscriptionSync(AudioTranscriptionsRequest request) { + RequestSupplier supplier = (params) -> { + try { + java.io.File file = params.getFile(); + Tika tika = new Tika(); + String contentType = tika.detect(file); + RequestBody requestFile = RequestBody.create(MediaType.parse(contentType), file); + MultipartBody.Part fileData = MultipartBody.Part.createFormData("file", file.getName(), requestFile); + + Map requestMap = new HashMap<>(); + if (params.getModel() != null) { + requestMap.put("model", RequestBody.create(MediaType.parse("text/plain"), params.getModel())); + } + if (params.getStream() != null) { + requestMap.put("stream", + RequestBody.create(MediaType.parse("text/plain"), params.getStream().toString())); + } + if (params.getRequestId() != null) { + requestMap.put("request_id", + RequestBody.create(MediaType.parse("text/plain"), params.getRequestId())); + } + if (params.getUserId() != null) { + requestMap.put("user_id", RequestBody.create(MediaType.parse("text/plain"), params.getUserId())); + } + + return audioApi.audioTranscriptions(requestMap, fileData); + } + catch (IOException e) { + log.error("Error create transcription: {}", e.getMessage(), e); + throw new RuntimeException(e); + } + }; + return this.zAiClient.executeRequest(request, supplier, ChatCompletionResponse.class); + } + + private void writeResponseBodyToFile(ResponseBody body, java.io.File file) { + try (InputStream inputStream = body.byteStream(); + OutputStream outputStream = Files.newOutputStream(file.toPath())) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + outputStream.flush(); + } + catch (IOException e) { + log.error("writeResponseBodyToFile error,msg:{}", e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechApiResponse.java b/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechApiResponse.java new file mode 100644 index 0000000..f7cffbc --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechApiResponse.java @@ -0,0 +1,22 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +import java.io.File; + +@Data +public class AudioSpeechApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private File data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechRequest.java b/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechRequest.java new file mode 100644 index 0000000..db7cede --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioSpeechRequest.java @@ -0,0 +1,50 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.SensitiveWordCheckRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AudioSpeechRequest extends CommonRequest implements ClientRequest { + + /** + * Model code to call + */ + private String model; + + /** + * Text to generate speech from + */ + private String input; + + /** + * Voice tone for speech generation + */ + private String voice; + + /** + * Format of the generated speech file + */ + @JsonProperty("response_format") + private String responseFormat; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + +} diff --git a/core/src/main/java/ai/z/openapi/service/audio/AudioTranscriptionsRequest.java b/core/src/main/java/ai/z/openapi/service/audio/AudioTranscriptionsRequest.java new file mode 100644 index 0000000..f5ec9b2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/audio/AudioTranscriptionsRequest.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.audio; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.SensitiveWordCheckRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AudioTranscriptionsRequest extends CommonRequest implements ClientRequest { + + /** + * Model code to call + */ + private String model; + + /** + * Synchronous call: false, SSE call: true + */ + private Boolean stream; + + private File file; + + /** + * Sampling temperature, controls output randomness, must be positive Range: + * (0.0,1.0], cannot equal 0, default value is 0.95 Higher values make output more + * random and creative; lower values make output more stable or deterministic It's + * recommended to adjust either top_p or temperature parameter based on your use case, + * but not both simultaneously + */ + private Float temperature; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + +} diff --git a/core/src/main/java/ai/z/openapi/service/batches/Batch.java b/core/src/main/java/ai/z/openapi/service/batches/Batch.java new file mode 100644 index 0000000..937dcd9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/Batch.java @@ -0,0 +1,113 @@ +package ai.z.openapi.service.batches; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class Batch { + + @JsonProperty("id") + private String id; + + @JsonProperty("completion_window") + private String completionWindow; + + @JsonProperty("created_at") + private long createdAt; + + @JsonProperty("endpoint") + private String endpoint; + + @JsonProperty("input_file_id") + private String inputFileId; + + @JsonProperty("object") + private String object; + + @JsonProperty("status") + private String status; + + @JsonProperty("cancelled_at") + private Long cancelledAt; + + @JsonProperty("cancelling_at") + private Long cancellingAt; + + @JsonProperty("completed_at") + private Long completedAt; + + @JsonProperty("error_file_id") + private String errorFileId; + + @JsonProperty("errors") + private Errors errors; + + @JsonProperty("expired_at") + private Long expiredAt; + + @JsonProperty("expires_at") + private Long expiresAt; + + @JsonProperty("failed_at") + private Long failedAt; + + @JsonProperty("finalizing_at") + private Long finalizingAt; + + @JsonProperty("in_progress_at") + private Long inProgressAt; + + @JsonProperty("metadata") + private Object metadata; + + @JsonProperty("output_file_id") + private String outputFileId; + + @JsonProperty("request_counts") + private BatchRequestCounts requestCounts; + +} + +@Data +class Errors { + + @JsonProperty("data") + private List data; + + @JsonProperty("object") + private String object; + +} + +@Data +class BatchRequestCounts { + + @JsonProperty("completed") + private int completed; + + @JsonProperty("failed") + private int failed; + + @JsonProperty("total") + private int total; + +} + +@Data +class BatchError { + + @JsonProperty("code") + private String code; + + @JsonProperty("line") + private Long line; + + @JsonProperty("message") + private String message; + + @JsonProperty("param") + private String param; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchCreateParams.java b/core/src/main/java/ai/z/openapi/service/batches/BatchCreateParams.java new file mode 100644 index 0000000..3dc62d2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchCreateParams.java @@ -0,0 +1,56 @@ +package ai.z.openapi.service.batches; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import java.util.Map; + +/** + * Parameters for creating batch processing requests. This class contains all the + * necessary parameters to create a batch job, including completion window, endpoint, + * input file, and metadata. + */ +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@Data +public class BatchCreateParams implements ClientRequest { + + /** + * The time frame within which the batch should be processed. + */ + @JsonProperty("completion_window") + private String completionWindow; + + /** + * The API endpoint to be used for batch processing. + */ + @JsonProperty("endpoint") + private String endpoint; + + /** + * The ID of the uploaded file containing batch requests. Must be the ID of the + * uploaded file. + */ + @JsonProperty("input_file_id") + private String inputFileId; + + /** + * Optional custom metadata for the batch job. + */ + @JsonProperty("metadata") + private Map metadata; + + public BatchCreateParams() { + } + + public BatchCreateParams(String completionWindow, String endpoint, String inputFileId, + Map metadata) { + this.completionWindow = completionWindow; + this.endpoint = endpoint; + this.inputFileId = inputFileId; + this.metadata = metadata; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchPage.java b/core/src/main/java/ai/z/openapi/service/batches/BatchPage.java new file mode 100644 index 0000000..6247438 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchPage.java @@ -0,0 +1,16 @@ +package ai.z.openapi.service.batches; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class BatchPage { + + private String object; + + private List data; + +} diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchRequest.java b/core/src/main/java/ai/z/openapi/service/batches/BatchRequest.java new file mode 100644 index 0000000..edc9d1d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchRequest.java @@ -0,0 +1,19 @@ +package ai.z.openapi.service.batches; + +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class BatchRequest implements ClientRequest { + + private String batchId; + +} diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchResponse.java b/core/src/main/java/ai/z/openapi/service/batches/BatchResponse.java new file mode 100644 index 0000000..5e87fa5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.batches; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class BatchResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private Batch data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchService.java b/core/src/main/java/ai/z/openapi/service/batches/BatchService.java new file mode 100644 index 0000000..69be046 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchService.java @@ -0,0 +1,52 @@ +package ai.z.openapi.service.batches; + +import ai.z.openapi.service.file.QueryBatchRequest; + +/** + * Batch service interface + */ +public interface BatchService { + + /** + * Creates a batch request. + * @param batchCreateParams the batch creation parameters + * @return BatchResponse containing the created batch details + */ + BatchResponse createBatch(BatchCreateParams batchCreateParams); + + /** + * Retrieves a specific batch. + * @param request the batch request containing the batch ID + * @return BatchResponse containing the batch details + */ + BatchResponse retrieveBatch(BatchRequest request); + + /** + * Retrieves a specific batch by ID. + * @param batchId the ID of the batch to retrieve + * @return BatchResponse containing the batch details + */ + BatchResponse retrieveBatch(String batchId); + + /** + * Lists all batches. + * @param queryBatchRequest the query parameters for listing batches + * @return QueryBatchResponse containing the list of batches + */ + QueryBatchResponse listBatches(QueryBatchRequest queryBatchRequest); + + /** + * Cancels a batch request. + * @param request the batch request containing the batch ID + * @return BatchResponse containing the cancellation result + */ + BatchResponse cancelBatch(BatchRequest request); + + /** + * Cancels a batch request by ID. + * @param batchId the ID of the batch to cancel + * @return BatchResponse containing the cancellation result + */ + BatchResponse cancelBatch(String batchId); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/batches/BatchServiceImpl.java b/core/src/main/java/ai/z/openapi/service/batches/BatchServiceImpl.java new file mode 100644 index 0000000..c781d7d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/BatchServiceImpl.java @@ -0,0 +1,59 @@ +package ai.z.openapi.service.batches; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.batches.BatchesApi; +import ai.z.openapi.service.file.QueryBatchRequest; +import ai.z.openapi.utils.RequestSupplier; + +/** + * Batch service implementation + */ +public class BatchServiceImpl implements BatchService { + + private final ZaiClient zAiClient; + + private final BatchesApi batchesApi; + + public BatchServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.batchesApi = zAiClient.retrofit().create(BatchesApi.class); + } + + @Override + public BatchResponse createBatch(BatchCreateParams batchCreateParams) { + RequestSupplier supplier = batchesApi::batchesCreate; + return this.zAiClient.executeRequest(batchCreateParams, supplier, BatchResponse.class); + } + + @Override + public BatchResponse retrieveBatch(BatchRequest request) { + RequestSupplier supplier = (params) -> batchesApi.batchesRetrieve(params.getBatchId()); + return this.zAiClient.executeRequest(request, supplier, BatchResponse.class); + } + + @Override + public BatchResponse retrieveBatch(String batchId) { + BatchRequest request = BatchRequest.builder().batchId(batchId).build(); + return retrieveBatch(request); + } + + @Override + public QueryBatchResponse listBatches(QueryBatchRequest queryBatchRequest) { + RequestSupplier supplier = (params) -> batchesApi.batchesList(params.getAfter(), + params.getLimit()); + return this.zAiClient.executeRequest(queryBatchRequest, supplier, QueryBatchResponse.class); + } + + @Override + public BatchResponse cancelBatch(BatchRequest request) { + RequestSupplier supplier = (params) -> batchesApi.batchesCancel(params.getBatchId()); + return this.zAiClient.executeRequest(request, supplier, BatchResponse.class); + } + + @Override + public BatchResponse cancelBatch(String batchId) { + BatchRequest request = BatchRequest.builder().batchId(batchId).build(); + return cancelBatch(request); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/batches/QueryBatchResponse.java b/core/src/main/java/ai/z/openapi/service/batches/QueryBatchResponse.java new file mode 100644 index 0000000..e24ac54 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/batches/QueryBatchResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.batches; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryBatchResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private BatchPage data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/chat/ChatService.java b/core/src/main/java/ai/z/openapi/service/chat/ChatService.java new file mode 100644 index 0000000..f382290 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/chat/ChatService.java @@ -0,0 +1,35 @@ +package ai.z.openapi.service.chat; + +import ai.z.openapi.service.model.ChatCompletionCreateParams; +import ai.z.openapi.service.model.ChatCompletionResponse; +import ai.z.openapi.service.model.AsyncResultRetrieveParams; +import ai.z.openapi.service.model.QueryModelResultResponse; + +/** + * Chat completion service interface + */ +public interface ChatService { + + /** + * Creates a chat completion, either streaming or non-streaming based on the request + * configuration. + * @param request the chat completion request + * @return ChatCompletionResponse containing the completion result + */ + ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request); + + /** + * Creates an asynchronous chat completion. + * @param request the chat completion request + * @return ChatCompletionResponse containing the async completion result + */ + ChatCompletionResponse asyncChatCompletion(ChatCompletionCreateParams request); + + /** + * Retrieves the result of an asynchronous model operation. + * @param request the query request for the async result + * @return QueryModelResultResponse containing the async operation result + */ + QueryModelResultResponse retrieveAsyncResult(AsyncResultRetrieveParams request); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/chat/ChatServiceImpl.java b/core/src/main/java/ai/z/openapi/service/chat/ChatServiceImpl.java new file mode 100644 index 0000000..cc58763 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/chat/ChatServiceImpl.java @@ -0,0 +1,81 @@ +package ai.z.openapi.service.chat; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.chat.ChatApi; +import ai.z.openapi.service.model.ChatCompletionCreateParams; +import ai.z.openapi.service.model.ChatCompletionResponse; +import ai.z.openapi.service.model.AsyncResultRetrieveParams; +import ai.z.openapi.service.model.QueryModelResultResponse; +import ai.z.openapi.service.model.ModelData; +import ai.z.openapi.utils.FlowableRequestSupplier; +import ai.z.openapi.utils.RequestSupplier; +import ai.z.openapi.utils.StringUtils; +import okhttp3.ResponseBody; + +/** + * Chat completion service implementation + */ +public class ChatServiceImpl implements ChatService { + + private final ZaiClient zAiClient; + + private final ChatApi chatApi; + + public ChatServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.chatApi = this.zAiClient.retrofit().create(ChatApi.class); + } + + @Override + public ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request) { + String paramMsg = validateParams(request); + if (StringUtils.isNotEmpty(paramMsg)) { + return new ChatCompletionResponse(-100, String.format("invalid param: %s", paramMsg)); + } + if (request.getStream()) { + return streamChatCompletion(request); + } + else { + return syncChatCompletion(request); + } + } + + @Override + public ChatCompletionResponse asyncChatCompletion(ChatCompletionCreateParams request) { + RequestSupplier supplier = chatApi::createChatCompletionAsync; + return this.zAiClient.executeRequest(request, supplier, ChatCompletionResponse.class); + } + + @Override + public QueryModelResultResponse retrieveAsyncResult(AsyncResultRetrieveParams request) { + RequestSupplier supplier = (params) -> chatApi + .queryAsyncResult(params.getTaskId()); + // Handle response + return this.zAiClient.executeRequest(request, supplier, QueryModelResultResponse.class); + } + + private ChatCompletionResponse streamChatCompletion(ChatCompletionCreateParams request) { + FlowableRequestSupplier> supplier = chatApi::createChatCompletionStream; + return this.zAiClient.streamRequest(request, supplier, ChatCompletionResponse.class, ModelData.class); + } + + private ChatCompletionResponse syncChatCompletion(ChatCompletionCreateParams request) { + RequestSupplier supplier = chatApi::createChatCompletion; + // Handle response + return this.zAiClient.executeRequest(request, supplier, ChatCompletionResponse.class); + } + + private String validateParams(ChatCompletionCreateParams request) { + if (request == null) { + return "request can not be null"; + } + if (request.getMessages() == null || request.getMessages().isEmpty()) { + return "message can not be empty"; + } + if (request.getModel() == null) { + return "model can not be empty"; + } + return null; + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/BaseNodeDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/BaseNodeDeserializer.java new file mode 100644 index 0000000..fd6a14e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/BaseNodeDeserializer.java @@ -0,0 +1,461 @@ +package ai.z.openapi.service.deserialize; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.util.RawValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Base class for all actual {@link JsonNode} deserializer implementations + */ +public abstract class BaseNodeDeserializer extends StdDeserializer { + + protected final Boolean _supportsUpdates; + + public BaseNodeDeserializer(Class vc, Boolean supportsUpdates) { + super(vc); + _supportsUpdates = supportsUpdates; + } + + @Override + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) + throws IOException { + // Output can be as JSON Object, Array or scalar: no way to know a priori: + return typeDeserializer.deserializeTypedFromAny(p, ctxt); + } + + /* + * 07-Nov-2014, tatu: When investigating [databind#604], realized that it makes sense + * to also mark this is cachable, since lookup not exactly free, and since it's not + * uncommon to "read anything" + */ + @Override + public boolean isCachable() { + return true; + } + + @Override // since 2.9 + public Boolean supportsUpdate(DeserializationConfig config) { + return _supportsUpdates; + } + + /* + * /********************************************************** /* Overridable methods + * /********************************************************** + */ + + /** + * Method called when there is a duplicate value for a field. By default we don't + * care, and the last value is used. Can be overridden to provide alternate handling, + * such as throwing an exception, or choosing different strategy for combining values + * or choosing which one to keep. + * @param fieldName Name of the field for which duplicate value was found + * @param objectNode Object node that contains values + * @param oldValue Value that existed for the object node before newValue was added + * @param newValue Newly added value just added to the object node + */ + protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory, + String fieldName, ObjectNode objectNode, JsonNode oldValue, JsonNode newValue) + throws JsonProcessingException { + // [databind#237]: Report an error if asked to do so: + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)) { + // 11-Sep-2019, tatu: Can not pass "property name" because we may be + // missing enclosing JSON content context... + // ctxt.reportPropertyInputMismatch(JsonNode.class, fieldName, + ctxt.reportInputMismatch(JsonNode.class, + "Duplicate field '%s' for `ObjectNode`: not allowed when `DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY` enabled", + fieldName); + } + } + + /* + * /********************************************************** /* Helper methods + * /********************************************************** + */ + + /** + * Method called to deserialize Object node instance when there is no existing node to + * modify. + */ + protected final ObjectNode deserializeObject(JsonParser p, DeserializationContext ctxt, + final JsonNodeFactory nodeFactory) throws IOException { + final ObjectNode node = nodeFactory.objectNode(); + String key = p.nextFieldName(); + for (; key != null; key = p.nextFieldName()) { + JsonNode value; + JsonToken t = p.nextToken(); + if (t == null) { // can this ever occur? + t = JsonToken.NOT_AVAILABLE; // can this ever occur? + } + switch (t.id()) { + case JsonTokenId.ID_START_OBJECT: + value = deserializeObject(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_START_ARRAY: + value = deserializeArray(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_EMBEDDED_OBJECT: + value = _fromEmbedded(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_STRING: + value = nodeFactory.textNode(p.getText()); + break; + case JsonTokenId.ID_NUMBER_INT: + value = _fromInt(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_TRUE: + value = nodeFactory.booleanNode(true); + break; + case JsonTokenId.ID_FALSE: + value = nodeFactory.booleanNode(false); + break; + case JsonTokenId.ID_NULL: + value = nodeFactory.nullNode(); + break; + default: + value = deserializeAny(p, ctxt, nodeFactory); + } + JsonNode old = node.replace(key, value); + if (old != null) { + _handleDuplicateField(p, ctxt, nodeFactory, key, node, old, value); + } + } + return node; + } + + /** + * Alternate deserialization method used when parser already points to first + * FIELD_NAME and not START_OBJECT. + * + * @since 2.9 + */ + protected final ObjectNode deserializeObjectAtName(JsonParser p, DeserializationContext ctxt, + final JsonNodeFactory nodeFactory) throws IOException { + final ObjectNode node = nodeFactory.objectNode(); + String key = p.getCurrentName(); + for (; key != null; key = p.nextFieldName()) { + JsonNode value; + JsonToken t = p.nextToken(); + if (t == null) { // can this ever occur? + t = JsonToken.NOT_AVAILABLE; // can this ever occur? + } + switch (t.id()) { + case JsonTokenId.ID_START_OBJECT: + value = deserializeObject(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_START_ARRAY: + value = deserializeArray(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_EMBEDDED_OBJECT: + value = _fromEmbedded(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_STRING: + value = nodeFactory.textNode(p.getText()); + break; + case JsonTokenId.ID_NUMBER_INT: + value = _fromInt(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_TRUE: + value = nodeFactory.booleanNode(true); + break; + case JsonTokenId.ID_FALSE: + value = nodeFactory.booleanNode(false); + break; + case JsonTokenId.ID_NULL: + value = nodeFactory.nullNode(); + break; + default: + value = deserializeAny(p, ctxt, nodeFactory); + } + JsonNode old = node.replace(key, value); + if (old != null) { + _handleDuplicateField(p, ctxt, nodeFactory, key, node, old, value); + } + } + return node; + } + + /** + * Alternate deserialization method that is to update existing {@link ObjectNode} if + * possible. + * + * @since 2.9 + */ + protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt, final ObjectNode node) + throws IOException { + String key; + if (p.isExpectedStartObjectToken()) { + key = p.nextFieldName(); + } + else { + if (!p.hasToken(JsonToken.FIELD_NAME)) { + return deserialize(p, ctxt); + } + key = p.getCurrentName(); + } + for (; key != null; key = p.nextFieldName()) { + // If not, fall through to regular handling + JsonToken t = p.nextToken(); + + // First: see if we can merge things: + JsonNode old = node.get(key); + if (old != null) { + if (old instanceof ObjectNode) { + JsonNode newValue = updateObject(p, ctxt, (ObjectNode) old); + if (newValue != old) { + node.set(key, newValue); + } + continue; + } + if (old instanceof ArrayNode) { + JsonNode newValue = updateArray(p, ctxt, (ArrayNode) old); + if (newValue != old) { + node.set(key, newValue); + } + continue; + } + } + if (t == null) { // can this ever occur? + t = JsonToken.NOT_AVAILABLE; + } + JsonNode value; + JsonNodeFactory nodeFactory = ctxt.getNodeFactory(); + switch (t.id()) { + case JsonTokenId.ID_START_OBJECT: + value = deserializeObject(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_START_ARRAY: + value = deserializeArray(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_EMBEDDED_OBJECT: + value = _fromEmbedded(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_STRING: + value = nodeFactory.textNode(p.getText()); + break; + case JsonTokenId.ID_NUMBER_INT: + value = _fromInt(p, ctxt, nodeFactory); + break; + case JsonTokenId.ID_TRUE: + value = nodeFactory.booleanNode(true); + break; + case JsonTokenId.ID_FALSE: + value = nodeFactory.booleanNode(false); + break; + case JsonTokenId.ID_NULL: + value = nodeFactory.nullNode(); + break; + default: + value = deserializeAny(p, ctxt, nodeFactory); + } + if (old != null) { + _handleDuplicateField(p, ctxt, nodeFactory, key, node, old, value); + } + node.set(key, value); + } + return node; + } + + protected final ArrayNode deserializeArray(JsonParser p, DeserializationContext ctxt, + final JsonNodeFactory nodeFactory) throws IOException { + ArrayNode node = nodeFactory.arrayNode(); + while (true) { + JsonToken t = p.nextToken(); + switch (t.id()) { + case JsonTokenId.ID_START_OBJECT: + node.add(deserializeObject(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_START_ARRAY: + node.add(deserializeArray(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_END_ARRAY: + return node; + case JsonTokenId.ID_EMBEDDED_OBJECT: + node.add(_fromEmbedded(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_STRING: + node.add(nodeFactory.textNode(p.getText())); + break; + case JsonTokenId.ID_NUMBER_INT: + node.add(_fromInt(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_TRUE: + node.add(nodeFactory.booleanNode(true)); + break; + case JsonTokenId.ID_FALSE: + node.add(nodeFactory.booleanNode(false)); + break; + case JsonTokenId.ID_NULL: + node.add(nodeFactory.nullNode()); + break; + default: + node.add(deserializeAny(p, ctxt, nodeFactory)); + break; + } + } + } + + /** + * Alternate deserialization method that is to update existing {@link ObjectNode} if + * possible. + * + * @since 2.9 + */ + protected final JsonNode updateArray(JsonParser p, DeserializationContext ctxt, final ArrayNode node) + throws IOException { + final JsonNodeFactory nodeFactory = ctxt.getNodeFactory(); + while (true) { + JsonToken t = p.nextToken(); + switch (t.id()) { + case JsonTokenId.ID_START_OBJECT: + node.add(deserializeObject(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_START_ARRAY: + node.add(deserializeArray(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_END_ARRAY: + return node; + case JsonTokenId.ID_EMBEDDED_OBJECT: + node.add(_fromEmbedded(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_STRING: + node.add(nodeFactory.textNode(p.getText())); + break; + case JsonTokenId.ID_NUMBER_INT: + node.add(_fromInt(p, ctxt, nodeFactory)); + break; + case JsonTokenId.ID_TRUE: + node.add(nodeFactory.booleanNode(true)); + break; + case JsonTokenId.ID_FALSE: + node.add(nodeFactory.booleanNode(false)); + break; + case JsonTokenId.ID_NULL: + node.add(nodeFactory.nullNode()); + break; + default: + node.add(deserializeAny(p, ctxt, nodeFactory)); + break; + } + } + } + + protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctxt, + final JsonNodeFactory nodeFactory) throws IOException { + switch (p.currentTokenId()) { + case JsonTokenId.ID_END_OBJECT: // for empty JSON Objects we may point to + // this? + return nodeFactory.objectNode(); + case JsonTokenId.ID_FIELD_NAME: + return deserializeObjectAtName(p, ctxt, nodeFactory); + case JsonTokenId.ID_EMBEDDED_OBJECT: + return _fromEmbedded(p, ctxt, nodeFactory); + case JsonTokenId.ID_STRING: + return nodeFactory.textNode(p.getText()); + case JsonTokenId.ID_NUMBER_INT: + return _fromInt(p, ctxt, nodeFactory); + case JsonTokenId.ID_NUMBER_FLOAT: + return _fromFloat(p, ctxt, nodeFactory); + case JsonTokenId.ID_TRUE: + return nodeFactory.booleanNode(true); + case JsonTokenId.ID_FALSE: + return nodeFactory.booleanNode(false); + case JsonTokenId.ID_NULL: + return nodeFactory.nullNode(); + + /* + * Caller checks for these, should not get here ever case + * JsonTokenId.ID_START_OBJECT: return deserializeObject(p, ctxt, + * nodeFactory); case JsonTokenId.ID_START_ARRAY: return deserializeArray(p, + * ctxt, nodeFactory); + */ + + // These states cannot be mapped; input stream is + // off by an event or two + + // case END_OBJECT: + // case END_ARRAY: + default: + } + return (JsonNode) ctxt.handleUnexpectedToken(handledType(), p); + } + + protected final JsonNode _fromInt(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory) + throws IOException { + JsonParser.NumberType nt; + int feats = ctxt.getDeserializationFeatures(); + if ((feats & F_MASK_INT_COERCIONS) != 0) { + if (DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.enabledIn(feats)) { + nt = JsonParser.NumberType.BIG_INTEGER; + } + else if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) { + nt = JsonParser.NumberType.LONG; + } + else { + nt = p.getNumberType(); + } + } + else { + nt = p.getNumberType(); + } + if (nt == JsonParser.NumberType.INT) { + return nodeFactory.numberNode(p.getIntValue()); + } + if (nt == JsonParser.NumberType.LONG) { + return nodeFactory.numberNode(p.getLongValue()); + } + return nodeFactory.numberNode(p.getBigIntegerValue()); + } + + protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt, final JsonNodeFactory nodeFactory) + throws IOException { + JsonParser.NumberType nt = p.getNumberType(); + if (nt == JsonParser.NumberType.BIG_DECIMAL) { + return nodeFactory.numberNode(p.getDecimalValue()); + } + if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { + // 20-May-2016, tatu: As per [databind#1028], need to be careful + // (note: JDK 1.8 would have `Double.isFinite()`) + if (p.isNaN()) { + return nodeFactory.numberNode(p.getDoubleValue()); + } + return nodeFactory.numberNode(p.getDecimalValue()); + } + if (nt == JsonParser.NumberType.FLOAT) { + return nodeFactory.numberNode(p.getFloatValue()); + } + return nodeFactory.numberNode(p.getDoubleValue()); + } + + protected final JsonNode _fromEmbedded(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory) + throws IOException { + Object ob = p.getEmbeddedObject(); + if (ob == null) { // should this occur? + return nodeFactory.nullNode(); + } + Class type = ob.getClass(); + if (type == byte[].class) { // most common special case + return nodeFactory.binaryNode((byte[]) ob); + } + // [databind#743]: Don't forget RawValue + if (ob instanceof RawValue) { + return nodeFactory.rawValueNode((RawValue) ob); + } + if (ob instanceof JsonNode) { + // [databind#433]: but could also be a JsonNode hiding in there! + return (JsonNode) ob; + } + // any other special handling needed? + return nodeFactory.pojoNode(ob); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ChatFunctionCallDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ChatFunctionCallDeserializer.java new file mode 100644 index 0000000..6967435 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ChatFunctionCallDeserializer.java @@ -0,0 +1,71 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.ChatFunctionCall; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ChatFunctionCall} from any JSON + * content, using appropriate {@link ChatFunctionCall} type. + */ +public class ChatFunctionCallDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for + * {@link ChatFunctionCallDeserializer}. Only used for types other than JSON Object + * and Array. + */ + private final static ChatFunctionCallDeserializer instance = new ChatFunctionCallDeserializer(); + + public ChatFunctionCallDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(ChatFunctionCall.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public ChatFunctionCall getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public ChatFunctionCall deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ChatFunctionCall(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ChatMessageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ChatMessageDeserializer.java new file mode 100644 index 0000000..fcbaec0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ChatMessageDeserializer.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.ChatMessage; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ChatMessage} from any JSON content, + * using appropriate {@link ChatMessage} type. + */ +public class ChatMessageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for {@link ChatMessageDeserializer}. + * Only used for types other than JSON Object and Array. + */ + private final static ChatMessageDeserializer instance = new ChatMessageDeserializer(); + + public ChatMessageDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(ChatMessage.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public ChatMessage getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public ChatMessage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ChatMessage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ChoiceDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ChoiceDeserializer.java new file mode 100644 index 0000000..f5e22e4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ChoiceDeserializer.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.Choice; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link Choice} from any JSON content, using + * appropriate {@link Choice} type. + */ +public class ChoiceDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for {@link ChoiceDeserializer}. Only + * used for types other than JSON Object and Array. + */ + private final static ChoiceDeserializer instance = new ChoiceDeserializer(); + + public ChoiceDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(Choice.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public Choice getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public Choice deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new Choice(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/CodeGeexContextDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/CodeGeexContextDeserializer.java new file mode 100644 index 0000000..ade7986 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/CodeGeexContextDeserializer.java @@ -0,0 +1,42 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.params.CodeGeexContext; + +import java.io.IOException; + +public class CodeGeexContextDeserializer extends BaseNodeDeserializer { + + private static final CodeGeexContextDeserializer instance = new CodeGeexContextDeserializer(); + + public CodeGeexContextDeserializer() { + super(CodeGeexContext.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public CodeGeexContext getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public CodeGeexContext deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new CodeGeexContext(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/DeltaDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/DeltaDeserializer.java new file mode 100644 index 0000000..f0585e9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/DeltaDeserializer.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.Delta; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link Delta} from any JSON content, using + * appropriate {@link Delta} type. + */ +public class DeltaDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for {@link DeltaDeserializer}. Only used + * for types other than JSON Object and Array. + */ + private final static DeltaDeserializer instance = new DeltaDeserializer(); + + public DeltaDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(Delta.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public Delta getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public Delta deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new Delta(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeField.java b/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeField.java new file mode 100644 index 0000000..c03c863 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeField.java @@ -0,0 +1,11 @@ +package ai.z.openapi.service.deserialize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface JsonTypeField { + + String value(); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeMapping.java b/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeMapping.java new file mode 100644 index 0000000..70bff0b --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/JsonTypeMapping.java @@ -0,0 +1,11 @@ +package ai.z.openapi.service.deserialize; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface JsonTypeMapping { + + Class[] value(); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/MessageDeserializeFactory.java b/core/src/main/java/ai/z/openapi/service/deserialize/MessageDeserializeFactory.java new file mode 100644 index 0000000..676d8ad --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/MessageDeserializeFactory.java @@ -0,0 +1,126 @@ +package ai.z.openapi.service.deserialize; + +import ai.z.openapi.service.deserialize.knowledge.document.DocumentDataDeserializer; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentDataFailInfoDeserializer; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentFailedInfoDeserializer; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentObjectDeserializer; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentPageDeserializer; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentSuccessInfoDeserializer; +import ai.z.openapi.service.deserialize.tools.ChoiceDeltaDeserializer; +import ai.z.openapi.service.deserialize.tools.ChoiceDeltaToolCallDeserializer; +import ai.z.openapi.service.deserialize.tools.SearchChatMessageDeserializer; +import ai.z.openapi.service.deserialize.tools.SearchIntentDeserializer; +import ai.z.openapi.service.deserialize.tools.SearchRecommendDeserializer; +import ai.z.openapi.service.deserialize.tools.SearchResultDeserializer; +import ai.z.openapi.service.deserialize.tools.WebSearchChoiceDeserializer; +import ai.z.openapi.service.deserialize.tools.WebSearchMessageDeserializer; +import ai.z.openapi.service.deserialize.tools.WebSearchMessageToolCallDeserializer; +import ai.z.openapi.service.deserialize.tools.WebSearchProDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentData; +import ai.z.openapi.service.knowledge.document.DocumentDataFailInfo; +import ai.z.openapi.service.knowledge.document.DocumentFailedInfo; +import ai.z.openapi.service.knowledge.document.DocumentObject; +import ai.z.openapi.service.knowledge.document.DocumentPage; +import ai.z.openapi.service.knowledge.document.DocumentSuccessInfo; +import ai.z.openapi.service.model.ChatFunctionCall; +import ai.z.openapi.service.model.ChatMessage; +import ai.z.openapi.service.model.Choice; +import ai.z.openapi.service.model.Delta; +import ai.z.openapi.service.model.ModelData; +import ai.z.openapi.service.model.ToolCalls; +import ai.z.openapi.service.tools.ChoiceDelta; +import ai.z.openapi.service.tools.ChoiceDeltaToolCall; +import ai.z.openapi.service.tools.SearchChatMessage; +import ai.z.openapi.service.tools.SearchIntent; +import ai.z.openapi.service.tools.SearchRecommend; +import ai.z.openapi.service.tools.SearchResult; +import ai.z.openapi.service.tools.WebSearchChoice; +import ai.z.openapi.service.tools.WebSearchMessage; +import ai.z.openapi.service.tools.WebSearchMessageToolCall; +import ai.z.openapi.service.tools.WebSearchPro; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.module.SimpleModule; +import ai.z.openapi.service.assistant.AssistantChoice; +import ai.z.openapi.service.assistant.AssistantCompletion; +import ai.z.openapi.service.assistant.CompletionUsage; +import ai.z.openapi.service.assistant.ErrorInfo; +import ai.z.openapi.service.deserialize.assistant.AssistantChoiceDeserializer; +import ai.z.openapi.service.deserialize.assistant.AssistantCompletionDeserializer; +import ai.z.openapi.service.deserialize.assistant.CompletionUsageDeserializer; +import ai.z.openapi.service.deserialize.assistant.ErrorInfoDeserializer; +import ai.z.openapi.service.deserialize.embedding.EmbeddingDeserializer; +import ai.z.openapi.service.deserialize.embedding.EmbeddingResultDeserializer; +import ai.z.openapi.service.deserialize.image.ImageDeserializer; +import ai.z.openapi.service.deserialize.image.ImageResultDeserializer; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeInfoDeserializer; +import ai.z.openapi.service.deserialize.knowledge.KnowledgePageDeserializer; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeStatisticsDeserializer; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeUsedDeserializer; +import ai.z.openapi.service.deserialize.videos.VideoObjectDeserializer; +import ai.z.openapi.service.deserialize.videos.VideoResultDeserializer; +import ai.z.openapi.service.embedding.Embedding; +import ai.z.openapi.service.embedding.EmbeddingResult; +import ai.z.openapi.service.image.Image; +import ai.z.openapi.service.image.ImageResult; +import ai.z.openapi.service.knowledge.KnowledgeInfo; +import ai.z.openapi.service.knowledge.KnowledgePage; +import ai.z.openapi.service.knowledge.KnowledgeStatistics; +import ai.z.openapi.service.knowledge.KnowledgeUsed; +import ai.z.openapi.service.model.params.CodeGeexContext; +import ai.z.openapi.service.videos.VideoObject; +import ai.z.openapi.service.videos.VideoResult; + +public class MessageDeserializeFactory { + + public static ObjectMapper defaultObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + SimpleModule module = new SimpleModule(); + + module.addDeserializer(ModelData.class, new ModelDataDeserializer()); + module.addDeserializer(Choice.class, new ChoiceDeserializer()); + module.addDeserializer(ChatMessage.class, new ChatMessageDeserializer()); + module.addDeserializer(Delta.class, new DeltaDeserializer()); + module.addDeserializer(ToolCalls.class, new ToolCallsDeserializer()); + module.addDeserializer(ChatFunctionCall.class, new ChatFunctionCallDeserializer()); + module.addDeserializer(CodeGeexContext.class, new CodeGeexContextDeserializer()); + module.addDeserializer(ChoiceDelta.class, new ChoiceDeltaDeserializer()); + module.addDeserializer(ChoiceDeltaToolCall.class, new ChoiceDeltaToolCallDeserializer()); + module.addDeserializer(SearchChatMessage.class, new SearchChatMessageDeserializer()); + module.addDeserializer(SearchIntent.class, new SearchIntentDeserializer()); + module.addDeserializer(SearchRecommend.class, new SearchRecommendDeserializer()); + module.addDeserializer(SearchResult.class, new SearchResultDeserializer()); + module.addDeserializer(WebSearchChoice.class, new WebSearchChoiceDeserializer()); + module.addDeserializer(WebSearchMessage.class, new WebSearchMessageDeserializer()); + module.addDeserializer(WebSearchMessageToolCall.class, new WebSearchMessageToolCallDeserializer()); + module.addDeserializer(WebSearchPro.class, new WebSearchProDeserializer()); + module.addDeserializer(VideoResult.class, new VideoResultDeserializer()); + module.addDeserializer(VideoObject.class, new VideoObjectDeserializer()); + module.addDeserializer(Image.class, new ImageDeserializer()); + module.addDeserializer(ImageResult.class, new ImageResultDeserializer()); + module.addDeserializer(KnowledgeInfo.class, new KnowledgeInfoDeserializer()); + module.addDeserializer(KnowledgeUsed.class, new KnowledgeUsedDeserializer()); + module.addDeserializer(KnowledgeStatistics.class, new KnowledgeStatisticsDeserializer()); + module.addDeserializer(KnowledgePage.class, new KnowledgePageDeserializer()); + module.addDeserializer(DocumentFailedInfo.class, new DocumentFailedInfoDeserializer()); + module.addDeserializer(DocumentObject.class, new DocumentObjectDeserializer()); + module.addDeserializer(DocumentSuccessInfo.class, new DocumentSuccessInfoDeserializer()); + module.addDeserializer(DocumentData.class, new DocumentDataDeserializer()); + module.addDeserializer(DocumentDataFailInfo.class, new DocumentDataFailInfoDeserializer()); + module.addDeserializer(DocumentPage.class, new DocumentPageDeserializer()); + module.addDeserializer(EmbeddingResult.class, new EmbeddingResultDeserializer()); + module.addDeserializer(Embedding.class, new EmbeddingDeserializer()); + module.addDeserializer(KnowledgeInfo.class, new KnowledgeInfoDeserializer()); + module.addDeserializer(AssistantChoice.class, new AssistantChoiceDeserializer()); + module.addDeserializer(AssistantCompletion.class, new AssistantCompletionDeserializer()); + module.addDeserializer(CompletionUsage.class, new CompletionUsageDeserializer()); + module.addDeserializer(ErrorInfo.class, new ErrorInfoDeserializer()); + mapper.registerModule(module); + + return mapper; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ModelDataDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ModelDataDeserializer.java new file mode 100644 index 0000000..525c077 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ModelDataDeserializer.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.ModelData; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ModelData} from any JSON content, using + * appropriate {@link ModelData} type. + */ +public class ModelDataDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for {@link ModelDataDeserializer}. Only + * used for types other than JSON Object and Array. + */ + private final static ModelDataDeserializer instance = new ModelDataDeserializer(); + + public ModelDataDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(ModelData.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public ModelData getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public ModelData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ModelData(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ObjectDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ObjectDeserializer.java new file mode 100644 index 0000000..a49d4c8 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ObjectDeserializer.java @@ -0,0 +1,54 @@ +package ai.z.openapi.service.deserialize; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public final class ObjectDeserializer extends BaseNodeDeserializer { + + private static final long serialVersionUID = 1L; + + private final static ObjectDeserializer _instance = new ObjectDeserializer(); + + private ObjectDeserializer() { + super(ObjectNode.class, true); + } + + public static ObjectDeserializer getInstance() { + return _instance; + } + + @Override + public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.isExpectedStartObjectToken()) { + return deserializeObject(p, ctxt, ctxt.getNodeFactory()); + } + if (p.hasToken(JsonToken.FIELD_NAME)) { + return deserializeObjectAtName(p, ctxt, ctxt.getNodeFactory()); + } + // 23-Sep-2015, tatu: Ugh. We may also be given END_OBJECT (similar to + // FIELD_NAME), + // if caller has advanced to the first token of Object, but for empty Object + if (p.hasToken(JsonToken.END_OBJECT)) { + return ctxt.getNodeFactory().objectNode(); + } + return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p); + } + + /** + * Variant needed to support both root-level `updateValue()` and merging. + * + * @since 2.9 + */ + @Override + public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt, ObjectNode node) throws IOException { + if (p.isExpectedStartObjectToken() || p.hasToken(JsonToken.FIELD_NAME)) { + return (ObjectNode) updateObject(p, ctxt, (ObjectNode) node); + } + return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/ToolCallsDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/ToolCallsDeserializer.java new file mode 100644 index 0000000..6f4528d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/ToolCallsDeserializer.java @@ -0,0 +1,70 @@ +package ai.z.openapi.service.deserialize; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.model.ToolCalls; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ToolCalls} from any JSON content, using + * appropriate {@link ToolCalls} type. + */ +public class ToolCallsDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Singleton instance of generic deserializer for {@link ToolCallsDeserializer}. Only + * used for types other than JSON Object and Array. + */ + private final static ToolCallsDeserializer instance = new ToolCallsDeserializer(); + + public ToolCallsDeserializer() { + // `null` means that explicit "merge" is honored and may or may not work, but + // that per-type and global defaults do not enable merging. This because + // some node types (Object, Array) do support, others don't. + super(ToolCalls.class, null); + } + + /** + * Factory method for accessing deserializer for specific node type + */ + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + // For others, generic one works fine + return instance; + } + + /* + * /********************************************************** /* Actual deserializer + * implementations /********************************************************** + */ + + @Override + public ToolCalls getNullValue(DeserializationContext ctxt) { + return null; + } + + /** + * Implementation that will produce types of any JSON nodes; not just one deserializer + * is registered to handle (in case of more specialized handler). Overridden by typed + * sub-classes for more thorough checking + */ + @Override + public ToolCalls deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ToolCalls(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantChoiceDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantChoiceDeserializer.java new file mode 100644 index 0000000..49e8df2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantChoiceDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.assistant; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.assistant.AssistantChoice; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link AssistantChoice} from any JSON content, + * using appropriate {@link AssistantChoice} type. + */ +public class AssistantChoiceDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static AssistantChoiceDeserializer instance = new AssistantChoiceDeserializer(); + + public AssistantChoiceDeserializer() { + super(AssistantChoice.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public AssistantChoice getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public AssistantChoice deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new AssistantChoice(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantCompletionDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantCompletionDeserializer.java new file mode 100644 index 0000000..3473cea --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/AssistantCompletionDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.assistant; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.assistant.AssistantCompletion; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link AssistantCompletion} from any JSON + * content, using appropriate {@link AssistantCompletion} type. + */ +public class AssistantCompletionDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static AssistantCompletionDeserializer instance = new AssistantCompletionDeserializer(); + + public AssistantCompletionDeserializer() { + super(AssistantCompletion.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public AssistantCompletion getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public AssistantCompletion deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new AssistantCompletion(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/CompletionUsageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/CompletionUsageDeserializer.java new file mode 100644 index 0000000..bcba6ba --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/CompletionUsageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.assistant; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.assistant.CompletionUsage; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link CompletionUsage} from any JSON content, + * using appropriate {@link CompletionUsage} type. + */ +public class CompletionUsageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static CompletionUsageDeserializer instance = new CompletionUsageDeserializer(); + + public CompletionUsageDeserializer() { + super(CompletionUsage.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public CompletionUsage getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public CompletionUsage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new CompletionUsage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/ErrorInfoDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/ErrorInfoDeserializer.java new file mode 100644 index 0000000..52eebb2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/ErrorInfoDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.assistant; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.assistant.ErrorInfo; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ErrorInfo} from any JSON content, using + * appropriate {@link ErrorInfo} type. + */ +public class ErrorInfoDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static ErrorInfoDeserializer instance = new ErrorInfoDeserializer(); + + public ErrorInfoDeserializer() { + super(ErrorInfo.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public ErrorInfo getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public ErrorInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ErrorInfo(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/MessageContentDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/MessageContentDeserializer.java new file mode 100644 index 0000000..f3076b2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/MessageContentDeserializer.java @@ -0,0 +1,140 @@ +package ai.z.openapi.service.deserialize.assistant.message; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import ai.z.openapi.service.assistant.message.MessageContent; +import ai.z.openapi.service.assistant.message.TextContentBlock; +import ai.z.openapi.service.assistant.message.tools.ToolsType; +import ai.z.openapi.service.deserialize.JsonTypeField; +import ai.z.openapi.service.deserialize.JsonTypeMapping; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.List; + +public class MessageContentDeserializer extends JsonDeserializer { + + @Override + public MessageContent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + + // Get JsonTypeMapping annotation from MessageContent class + JsonTypeMapping mapping = MessageContent.class.getAnnotation(JsonTypeMapping.class); + if (mapping == null) { + throw new IllegalStateException("Missing JsonTypeMapping annotation on MessageContent class"); + } + + // Iterate through classes defined in annotation to determine suitable class based + // on annotation or static method values + for (Class clazz : mapping.value()) { + JsonTypeField typeField = clazz.getAnnotation(JsonTypeField.class); + + if (typeField != null && node.has(typeField.value())) { + try { + // Create instance of the class + Object obj = clazz.getDeclaredConstructor().newInstance(); + + // Use reflection to manually set field values + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + // field.getName() gets the value from JsonProperty annotation + // above + JsonProperty annotation = field.getAnnotation(JsonProperty.class); + String name = null; + if (annotation == null) { + name = field.getName(); + } + else { + name = annotation.value(); + } + if (node.has(name)) { + // Set values based on field type, assuming fields are + // primitive types or strings + if (field.getType().equals(String.class)) { + field.set(obj, node.get(name).asText()); + } + else if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { + field.set(obj, node.get(name).asInt()); + } + else if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) { + field.set(obj, node.get(name).asBoolean()); + } + else if (List.class.isAssignableFrom(field.getType())) { + // Check if generic type is ToolsType + ParameterizedType listType = (ParameterizedType) field.getGenericType(); + Class listGenericType = (Class) listType.getActualTypeArguments()[0]; + + if (listGenericType.equals(ToolsType.class)) { + // Deserialize to List + List list = new ObjectMapper().readerForListOf(ToolsType.class) + .readValue(node.get(name)); + field.set(obj, list); + } + else { + throw new IllegalArgumentException( + "Unsupported generic list type: " + listGenericType); + } + } + else { + throw new IllegalArgumentException("Unsupported field type: " + field.getType()); + } + + } + } + + return (MessageContent) obj; + } + catch (Exception e) { + throw new RuntimeException("Error while creating instance of " + clazz.getName(), e); + } + } + } + + // Create instance of the class + try { + MessageContent obj = TextContentBlock.class.getDeclaredConstructor().newInstance(); + + // Use reflection to manually set field values + for (Field field : TextContentBlock.class.getDeclaredFields()) { + field.setAccessible(true); + // field.getName() gets the value from JsonProperty annotation above + JsonProperty annotation = field.getAnnotation(JsonProperty.class); + String name = null; + if (annotation == null) { + name = field.getName(); + } + else { + name = annotation.value(); + } + if (node.has(name)) { + // Set values based on field type, assuming fields are primitive types + // or strings + if (field.getType().equals(String.class)) { + field.set(obj, node.get(name).asText()); + } + else if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { + field.set(obj, node.get(name).asInt()); + } + else if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) { + field.set(obj, node.get(name).asBoolean()); + + } + else { + // For other types, use ObjectMapper for direct conversion + Object o = new ObjectMapper().treeToValue(node.get(name), field.getType()); + field.set(obj, o); + } + } + } + + return obj; + } + catch (Exception e) { + throw new RuntimeException("Error while creating instance of " + TextContentBlock.class.getName(), e); + } + + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/tools/ToolsTypeDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/tools/ToolsTypeDeserializer.java new file mode 100644 index 0000000..d82b58d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/assistant/message/tools/ToolsTypeDeserializer.java @@ -0,0 +1,80 @@ +package ai.z.openapi.service.deserialize.assistant.message.tools; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import ai.z.openapi.service.assistant.message.tools.ToolsType; +import ai.z.openapi.service.deserialize.JsonTypeField; +import ai.z.openapi.service.deserialize.JsonTypeMapping; + +import java.io.IOException; +import java.lang.reflect.Field; + +public class ToolsTypeDeserializer extends JsonDeserializer { + + @Override + public ToolsType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + + // Get JsonTypeMapping annotation from MessageContent class + JsonTypeMapping mapping = ToolsType.class.getAnnotation(JsonTypeMapping.class); + if (mapping == null) { + throw new IllegalStateException("Missing JsonTypeMapping annotation on MessageContent class"); + } + + // Iterate through classes defined in annotation to determine suitable class based + // on annotation or static method values + for (Class clazz : mapping.value()) { + JsonTypeField typeField = clazz.getAnnotation(JsonTypeField.class); + + if (typeField != null && node.has(typeField.value())) { + try { + // Create instance of the class + Object obj = clazz.getDeclaredConstructor().newInstance(); + + // Use reflection to manually set field values + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + // field.getName() gets the value from JsonProperty annotation + // above + JsonProperty annotation = field.getAnnotation(JsonProperty.class); + String name = null; + if (annotation == null) { + name = field.getName(); + } + else { + name = annotation.value(); + } + if (node.has(name)) { + // Set values based on field type, assuming fields are + // primitive types or strings + if (field.getType().equals(String.class)) { + field.set(obj, node.get(name).asText()); + } + else if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) { + field.set(obj, node.get(name).asInt()); + } + else if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) { + field.set(obj, node.get(name).asBoolean()); + + } + else { + // For other types, use ObjectMapper for direct conversion + Object o = new ObjectMapper().treeToValue(node.get(name), field.getType()); + field.set(obj, o); + } + } + } + + return (ToolsType) obj; + } + catch (Exception e) { + throw new RuntimeException("Error while creating instance of " + clazz.getName(), e); + } + } + } + + throw new IllegalArgumentException("Cannot determine type for JSON: " + node.toString()); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingDeserializer.java new file mode 100644 index 0000000..f757b77 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingDeserializer.java @@ -0,0 +1,52 @@ + +package ai.z.openapi.service.deserialize.embedding; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.embedding.Embedding; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link Embedding} from any JSON content, using + * appropriate {@link Embedding} type. + */ +public class EmbeddingDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static EmbeddingDeserializer instance = new EmbeddingDeserializer(); + + public EmbeddingDeserializer() { + super(Embedding.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public Embedding getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public Embedding deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new Embedding(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingResultDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingResultDeserializer.java new file mode 100644 index 0000000..dd5ad82 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/embedding/EmbeddingResultDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.embedding; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.embedding.EmbeddingResult; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link EmbeddingResult} from any JSON content, + * using appropriate {@link EmbeddingResult} type. + */ +public class EmbeddingResultDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static EmbeddingResultDeserializer instance = new EmbeddingResultDeserializer(); + + public EmbeddingResultDeserializer() { + super(EmbeddingResult.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public EmbeddingResult getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public EmbeddingResult deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new EmbeddingResult(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageDeserializer.java new file mode 100644 index 0000000..7cd15e7 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.image; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.image.Image; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link Image} from any JSON content, using + * appropriate {@link Image} type. + */ +public class ImageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static ImageDeserializer instance = new ImageDeserializer(); + + public ImageDeserializer() { + super(Image.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public Image getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public Image deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new Image(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageResultDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageResultDeserializer.java new file mode 100644 index 0000000..583166e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/image/ImageResultDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.image; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.image.ImageResult; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ImageResult} from any JSON content, + * using appropriate {@link ImageResult} type. + */ +public class ImageResultDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static ImageResultDeserializer instance = new ImageResultDeserializer(); + + public ImageResultDeserializer() { + super(ImageResult.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public ImageResult getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public ImageResult deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ImageResult(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeInfoDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeInfoDeserializer.java new file mode 100644 index 0000000..fd00a3f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeInfoDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.KnowledgeInfo; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link KnowledgeInfo} from any JSON content, + * using appropriate {@link KnowledgeInfo} type. + */ +public class KnowledgeInfoDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static KnowledgeInfoDeserializer instance = new KnowledgeInfoDeserializer(); + + public KnowledgeInfoDeserializer() { + super(KnowledgeInfo.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public KnowledgeInfo getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public KnowledgeInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new KnowledgeInfo(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgePageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgePageDeserializer.java new file mode 100644 index 0000000..e64a669 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgePageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.KnowledgePage; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link KnowledgePage} from any JSON content, + * using appropriate {@link KnowledgePage} type. + */ +public class KnowledgePageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static KnowledgePageDeserializer instance = new KnowledgePageDeserializer(); + + public KnowledgePageDeserializer() { + super(KnowledgePage.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public KnowledgePage getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public KnowledgePage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new KnowledgePage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeStatisticsDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeStatisticsDeserializer.java new file mode 100644 index 0000000..f741a84 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeStatisticsDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.KnowledgeStatistics; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link KnowledgeStatistics} from any JSON + * content, using appropriate {@link KnowledgeStatistics} type. + */ +public class KnowledgeStatisticsDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static KnowledgeStatisticsDeserializer instance = new KnowledgeStatisticsDeserializer(); + + public KnowledgeStatisticsDeserializer() { + super(KnowledgeStatistics.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public KnowledgeStatistics getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public KnowledgeStatistics deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new KnowledgeStatistics(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeUsedDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeUsedDeserializer.java new file mode 100644 index 0000000..b64cfc5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/KnowledgeUsedDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.KnowledgeUsed; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link KnowledgeUsed} from any JSON content, + * using appropriate {@link KnowledgeUsed} type. + */ +public class KnowledgeUsedDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static KnowledgeUsedDeserializer instance = new KnowledgeUsedDeserializer(); + + public KnowledgeUsedDeserializer() { + super(KnowledgeUsed.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public KnowledgeUsed getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public KnowledgeUsed deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new KnowledgeUsed(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataDeserializer.java new file mode 100644 index 0000000..7dd4f80 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentData; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentData} from any JSON content, + * using appropriate {@link DocumentData} type. + */ +public class DocumentDataDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentDataDeserializer instance = new DocumentDataDeserializer(); + + public DocumentDataDeserializer() { + super(DocumentData.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentData getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentData(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataFailInfoDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataFailInfoDeserializer.java new file mode 100644 index 0000000..adf3ffc --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentDataFailInfoDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentDataFailInfo; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentDataFailInfo} from any JSON + * content, using appropriate {@link DocumentDataFailInfo} type. + */ +public class DocumentDataFailInfoDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentDataFailInfoDeserializer instance = new DocumentDataFailInfoDeserializer(); + + public DocumentDataFailInfoDeserializer() { + super(DocumentDataFailInfo.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentDataFailInfo getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentDataFailInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentDataFailInfo(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentFailedInfoDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentFailedInfoDeserializer.java new file mode 100644 index 0000000..d537ca5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentFailedInfoDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentFailedInfo; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentFailedInfo} from any JSON + * content, using appropriate {@link DocumentFailedInfo} type. + */ +public class DocumentFailedInfoDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentFailedInfoDeserializer instance = new DocumentFailedInfoDeserializer(); + + public DocumentFailedInfoDeserializer() { + super(DocumentFailedInfo.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentFailedInfo getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentFailedInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentFailedInfo(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentObjectDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentObjectDeserializer.java new file mode 100644 index 0000000..32c9131 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentObjectDeserializer.java @@ -0,0 +1,52 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentObject; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentObject} from any JSON content, + * using appropriate {@link DocumentObject} type. + */ + +public class DocumentObjectDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentObjectDeserializer instance = new DocumentObjectDeserializer(); + + public DocumentObjectDeserializer() { + super(DocumentObject.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentObject getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentObject(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentPageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentPageDeserializer.java new file mode 100644 index 0000000..a36aef2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentPageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentPage; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentPage} from any JSON content, + * using appropriate {@link DocumentPage} type. + */ +public class DocumentPageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentPageDeserializer instance = new DocumentPageDeserializer(); + + public DocumentPageDeserializer() { + super(DocumentPage.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentPage getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentPage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentPage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentSuccessInfoDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentSuccessInfoDeserializer.java new file mode 100644 index 0000000..d3e8791 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/knowledge/document/DocumentSuccessInfoDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.knowledge.document; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.knowledge.document.DocumentSuccessInfo; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link DocumentSuccessInfo} from any JSON + * content, using appropriate {@link DocumentSuccessInfo} type. + */ +public class DocumentSuccessInfoDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static DocumentSuccessInfoDeserializer instance = new DocumentSuccessInfoDeserializer(); + + public DocumentSuccessInfoDeserializer() { + super(DocumentSuccessInfo.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public DocumentSuccessInfo getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public DocumentSuccessInfo deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new DocumentSuccessInfo(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaDeserializer.java new file mode 100644 index 0000000..f7e56e5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.ChoiceDelta; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ChoiceDelta} from any JSON content, + * using appropriate {@link ChoiceDelta} type. + */ +public class ChoiceDeltaDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static ChoiceDeltaDeserializer instance = new ChoiceDeltaDeserializer(); + + public ChoiceDeltaDeserializer() { + super(ChoiceDelta.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public ChoiceDelta getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public ChoiceDelta deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ChoiceDelta(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaToolCallDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaToolCallDeserializer.java new file mode 100644 index 0000000..7394946 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/ChoiceDeltaToolCallDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.ChoiceDeltaToolCall; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link ChoiceDeltaToolCall} from any JSON + * content, using appropriate {@link ChoiceDeltaToolCall} type. + */ +public class ChoiceDeltaToolCallDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static ChoiceDeltaToolCallDeserializer instance = new ChoiceDeltaToolCallDeserializer(); + + public ChoiceDeltaToolCallDeserializer() { + super(ChoiceDeltaToolCall.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public ChoiceDeltaToolCall getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public ChoiceDeltaToolCall deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new ChoiceDeltaToolCall(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchChatMessageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchChatMessageDeserializer.java new file mode 100644 index 0000000..65de688 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchChatMessageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.SearchChatMessage; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link SearchChatMessage} from any JSON + * content, using appropriate {@link SearchChatMessage} type. + */ +public class SearchChatMessageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static SearchChatMessageDeserializer instance = new SearchChatMessageDeserializer(); + + public SearchChatMessageDeserializer() { + super(SearchChatMessage.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public SearchChatMessage getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public SearchChatMessage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new SearchChatMessage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchIntentDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchIntentDeserializer.java new file mode 100644 index 0000000..806267a --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchIntentDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.SearchIntent; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link SearchIntent} from any JSON content, + * using appropriate {@link SearchIntent} type. + */ +public class SearchIntentDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static SearchIntentDeserializer instance = new SearchIntentDeserializer(); + + public SearchIntentDeserializer() { + super(SearchIntent.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public SearchIntent getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public SearchIntent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new SearchIntent(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchRecommendDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchRecommendDeserializer.java new file mode 100644 index 0000000..5b79bc9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchRecommendDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.SearchRecommend; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link SearchRecommend} from any JSON content, + * using appropriate {@link SearchRecommend} type. + */ +public class SearchRecommendDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static SearchRecommendDeserializer instance = new SearchRecommendDeserializer(); + + public SearchRecommendDeserializer() { + super(SearchRecommend.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public SearchRecommend getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public SearchRecommend deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new SearchRecommend(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchResultDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchResultDeserializer.java new file mode 100644 index 0000000..d8c8baf --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/SearchResultDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.SearchResult; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link SearchResult} from any JSON content, + * using appropriate {@link SearchResult} type. + */ +public class SearchResultDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static SearchResultDeserializer instance = new SearchResultDeserializer(); + + public SearchResultDeserializer() { + super(SearchResult.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public SearchResult getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public SearchResult deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new SearchResult(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchChoiceDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchChoiceDeserializer.java new file mode 100644 index 0000000..37ee3a9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchChoiceDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.WebSearchChoice; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link WebSearchChoice} from any JSON content, + * using appropriate {@link WebSearchChoice} type. + */ +public class WebSearchChoiceDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static WebSearchChoiceDeserializer instance = new WebSearchChoiceDeserializer(); + + public WebSearchChoiceDeserializer() { + super(WebSearchChoice.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public WebSearchChoice getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public WebSearchChoice deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new WebSearchChoice(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageDeserializer.java new file mode 100644 index 0000000..878a6bd --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.WebSearchMessage; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link WebSearchMessage} from any JSON + * content, using appropriate {@link WebSearchMessage} type. + */ +public class WebSearchMessageDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static WebSearchMessageDeserializer instance = new WebSearchMessageDeserializer(); + + public WebSearchMessageDeserializer() { + super(WebSearchMessage.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public WebSearchMessage getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public WebSearchMessage deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new WebSearchMessage(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageToolCallDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageToolCallDeserializer.java new file mode 100644 index 0000000..4d04fc0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchMessageToolCallDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.WebSearchMessageToolCall; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link WebSearchMessageToolCall} from any JSON + * content, using appropriate {@link WebSearchMessageToolCall} type. + */ +public class WebSearchMessageToolCallDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static WebSearchMessageToolCallDeserializer instance = new WebSearchMessageToolCallDeserializer(); + + public WebSearchMessageToolCallDeserializer() { + super(WebSearchMessageToolCall.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public WebSearchMessageToolCall getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public WebSearchMessageToolCall deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new WebSearchMessageToolCall(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchProDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchProDeserializer.java new file mode 100644 index 0000000..c4d6c4f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/tools/WebSearchProDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.tools; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.tools.WebSearchPro; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link WebSearchPro} from any JSON content, + * using appropriate {@link WebSearchPro} type. + */ +public class WebSearchProDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static WebSearchProDeserializer instance = new WebSearchProDeserializer(); + + public WebSearchProDeserializer() { + super(WebSearchPro.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public WebSearchPro getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public WebSearchPro deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new WebSearchPro(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoObjectDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoObjectDeserializer.java new file mode 100644 index 0000000..ffcef63 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoObjectDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.videos; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.videos.VideoObject; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link VideoObject} from any JSON content, + * using appropriate {@link VideoObject} type. + */ +public class VideoObjectDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static VideoObjectDeserializer instance = new VideoObjectDeserializer(); + + public VideoObjectDeserializer() { + super(VideoObject.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public VideoObject getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public VideoObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new VideoObject(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoResultDeserializer.java b/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoResultDeserializer.java new file mode 100644 index 0000000..af0cb1f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/deserialize/videos/VideoResultDeserializer.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.deserialize.videos; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.BaseNodeDeserializer; +import ai.z.openapi.service.deserialize.ObjectDeserializer; +import ai.z.openapi.service.videos.VideoResult; + +import java.io.IOException; + +/** + * Deserializer that can build instances of {@link VideoResult} from any JSON content, + * using appropriate {@link VideoResult} type. + */ +public class VideoResultDeserializer extends BaseNodeDeserializer { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final static VideoResultDeserializer instance = new VideoResultDeserializer(); + + public VideoResultDeserializer() { + super(VideoResult.class, null); + } + + public static JsonDeserializer getDeserializer(Class nodeClass) { + if (nodeClass == ObjectNode.class) { + return ObjectDeserializer.getInstance(); + } + return instance; + } + + @Override + public VideoResult getNullValue(DeserializationContext ctxt) { + return null; + } + + @Override + public VideoResult deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentTokenId() == JsonTokenId.ID_START_OBJECT) { + ObjectNode jsonNodes = deserializeObject(p, ctxt, ctxt.getNodeFactory()); + return new VideoResult(jsonNodes); + } + return null; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/document/DocumentService.java b/core/src/main/java/ai/z/openapi/service/document/DocumentService.java new file mode 100644 index 0000000..366a775 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/document/DocumentService.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.document; + +import ai.z.openapi.service.knowledge.document.DocumentCreateParams; +import ai.z.openapi.service.knowledge.document.DocumentEditParams; +import ai.z.openapi.service.knowledge.document.QueryDocumentRequest; +import ai.z.openapi.service.knowledge.document.DocumentDataResponse; +import ai.z.openapi.service.knowledge.document.DocumentEditResponse; +import ai.z.openapi.service.knowledge.document.DocumentObjectResponse; +import ai.z.openapi.service.knowledge.document.QueryDocumentApiResponse; + +/** + * Document service interface + */ +public interface DocumentService { + + /** + * Creates a new document. + * @param request the document creation request + * @return DocumentDataResponse containing the creation result + */ + DocumentObjectResponse createDocument(DocumentCreateParams request); + + /** + * Modifies an existing document. + * @param request the document modification request + * @return DocumentEditResponse containing the modification result + */ + DocumentEditResponse modifyDocument(DocumentEditParams request); + + /** + * Deletes a document. + * @param documentId the document ID to delete + * @return DocumentObjectResponse containing the deletion result + */ + DocumentEditResponse deleteDocument(String documentId); + + /** + * Lists documents. + * @param request the query request + * @return QueryDocumentApiResponse containing the document list + */ + QueryDocumentApiResponse listDocuments(QueryDocumentRequest request); + + /** + * Retrieves a specific document. + * @param documentId the document ID to retrieve + * @return DocumentDataResponse containing the document details + */ + DocumentDataResponse retrieveDocument(String documentId); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/document/DocumentServiceImpl.java b/core/src/main/java/ai/z/openapi/service/document/DocumentServiceImpl.java new file mode 100644 index 0000000..3790a5b --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/document/DocumentServiceImpl.java @@ -0,0 +1,144 @@ +package ai.z.openapi.service.document; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.knowledge.document.DocumentApi; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.knowledge.document.DocumentCreateParams; +import ai.z.openapi.service.knowledge.document.DocumentData; +import ai.z.openapi.service.knowledge.document.DocumentDataResponse; +import ai.z.openapi.service.knowledge.document.DocumentEditParams; +import ai.z.openapi.service.knowledge.document.DocumentEditResponse; +import ai.z.openapi.service.knowledge.document.DocumentObject; +import ai.z.openapi.service.knowledge.document.DocumentObjectResponse; +import ai.z.openapi.service.knowledge.document.DocumentPage; +import ai.z.openapi.service.knowledge.document.QueryDocumentRequest; +import ai.z.openapi.service.knowledge.document.QueryDocumentApiResponse; +import ai.z.openapi.utils.RequestSupplier; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import retrofit2.Response; + +import java.util.Date; + +/** + * Implementation of DocumentService + */ +@Slf4j +public class DocumentServiceImpl implements DocumentService { + + private final ZaiClient zAiClient; + + private final DocumentApi documentApi; + + private final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper(); + + public DocumentServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.documentApi = zAiClient.retrofit().create(DocumentApi.class); + } + + @Override + public DocumentObjectResponse createDocument(DocumentCreateParams request) { + // Only one of getUploadDetail and getFilePath can exist + if (request.getUploadDetail() != null && request.getFilePath() != null) { + throw new RuntimeException("Only one of upload detail and file path can exist"); + } + RequestSupplier supplier = (params) -> { + // Convert DocumentCreateParams to MultipartBody + MultipartBody.Builder formBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + try { + if (params.getFilePath() != null) { + java.io.File file = new java.io.File(params.getFilePath()); + if (!file.exists()) { + throw new RuntimeException("file not found"); + } + MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), + RequestBody.create(MediaType.parse("application/octet-stream"), file)); + formBodyBuilder.addPart(filePart); + } + if (params.getUploadDetail() != null) { + formBodyBuilder.addFormDataPart("upload_detail", null, RequestBody.create( + MediaType.parse("application/json"), mapper.writeValueAsString(params.getUploadDetail()))); + } + formBodyBuilder.addFormDataPart("knowledge_id", params.getKnowledgeId()); + if (params.getSentenceSize() != null) { + + formBodyBuilder.addFormDataPart("sentence_size", String.valueOf(params.getSentenceSize())); + } + formBodyBuilder.addFormDataPart("purpose", params.getPurpose()); + + if (params.getCustomSeparator() != null) { + + formBodyBuilder.addFormDataPart("custom_separator", null, + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(params.getCustomSeparator()))); + } + + if (params.getExtraJson() != null) { + for (String s : params.getExtraJson().keySet()) { + if (params.getExtraJson().get(s) instanceof String + || params.getExtraJson().get(s) instanceof Number + || params.getExtraJson().get(s) instanceof Boolean + || params.getExtraJson().get(s) instanceof Character) { + formBodyBuilder.addFormDataPart(s, params.getExtraJson().get(s).toString()); + } + else if (params.getExtraJson().get(s) instanceof Date) { + Date date = (Date) params.getExtraJson().get(s); + formBodyBuilder.addFormDataPart(s, String.valueOf(date.getTime())); + } + else { + + formBodyBuilder.addFormDataPart(s, null, + RequestBody.create(MediaType.parse("application/json"), + mapper.writeValueAsString(params.getExtraJson().get(s)))); + + } + + } + } + } + catch (Exception e) { + log.error(e.getMessage(), e); + } + + MultipartBody multipartBody = formBodyBuilder.build(); + return documentApi.createDocument(multipartBody); + }; + return zAiClient.executeRequest(request, supplier, DocumentObjectResponse.class); + } + + @Override + public DocumentEditResponse modifyDocument(DocumentEditParams request) { + RequestSupplier> supplier = (params) -> documentApi + .modifyDocument(params.getId(), params); + return zAiClient.executeRequest(request, supplier, DocumentEditResponse.class); + } + + @Override + public DocumentEditResponse deleteDocument(String documentId) { + DocumentEditParams params = new DocumentEditParams(); + params.setId(documentId); + RequestSupplier> supplier = (params1) -> documentApi + .deleteDocument(params1.getId()); + return zAiClient.executeRequest(params, supplier, DocumentEditResponse.class); + } + + @Override + public QueryDocumentApiResponse listDocuments(QueryDocumentRequest request) { + RequestSupplier supplier = (params) -> documentApi.queryDocumentList( + params.getKnowledgeId(), params.getPurpose(), params.getPage(), params.getLimit(), params.getOrder()); + return zAiClient.executeRequest(request, supplier, QueryDocumentApiResponse.class); + } + + @Override + public DocumentDataResponse retrieveDocument(String documentId) { + DocumentEditParams params = new DocumentEditParams(); + params.setId(documentId); + RequestSupplier supplier = (id) -> documentApi.retrieveDocument(id.getId()); + return zAiClient.executeRequest(params, supplier, DocumentDataResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/embedding/Embedding.java b/core/src/main/java/ai/z/openapi/service/embedding/Embedding.java new file mode 100644 index 0000000..2c0c3e2 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/Embedding.java @@ -0,0 +1,97 @@ +package ai.z.openapi.service.embedding; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.embedding.EmbeddingDeserializer; +import lombok.Getter; + +import java.util.Iterator; +import java.util.List; + +/** + * Represents an embedding returned by the embedding api + */ + +@JsonDeserialize(using = EmbeddingDeserializer.class) +public class Embedding extends ObjectNode { + + /** + * The type of object returned, should be "embedding" + */ + @Getter + String object; + + /** + * The embedding vector + */ + @Getter + List embedding; + + /** + * The position of this embedding in the list + */ + @Getter + Integer index; + + public Embedding() { + super(JsonNodeFactory.instance); + } + + public Embedding(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("object") != null) { + this.setObject(objectNode.get("object").asText()); + } + else { + this.setObject(null); + } + if (objectNode.get("index") != null) { + this.setIndex(objectNode.get("index").asInt()); + } + else { + this.setIndex(null); + } + + if (objectNode.get("embedding") != null) { + List embedding = objectMapper.convertValue(objectNode.get("embedding"), + new TypeReference>() { + }); + this.setEmbedding(embedding); + } + else { + this.setEmbedding(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public void setIndex(Integer index) { + this.index = index; + this.put("index", index); + } + + public void setObject(String object) { + this.object = object; + this.put("object", object); + } + + public void setEmbedding(List embedding) throws IllegalArgumentException { + if (embedding == null || embedding.isEmpty()) { + throw new IllegalArgumentException("Embedding cannot be null or empty"); + } + this.embedding = embedding; + this.putPOJO("embedding", embedding); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingCreateParams.java b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingCreateParams.java new file mode 100644 index 0000000..d868e38 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingCreateParams.java @@ -0,0 +1,53 @@ +package ai.z.openapi.service.embedding; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Creates an embedding vector representing the input text. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class EmbeddingCreateParams extends CommonRequest implements ClientRequest { + + /** + * The name of the model to use. Required if using the new v1/embeddings endpoint. + */ + private String model; + + /** + * Input text to get embeddings for, encoded as a string or array of tokens. To get + * embeddings for multiple inputs in a single request, pass an array of strings or + * array of token arrays. Each input must not exceed 2048 tokens in length. + *

+ * Unless you are embedding code, we suggest replacing newlines (\n) in your input + * with a single space, as we have observed inferior results when newlines are + * present. + */ + + private Object input; + + private Integer dimensions; + + public void setInput(String input) { + this.input = input; + } + + public void setInput(List input) throws IllegalArgumentException { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("Input cannot be null or empty"); + } + + this.input = input; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResponse.java b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResponse.java new file mode 100644 index 0000000..3ef74ed --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResponse.java @@ -0,0 +1,39 @@ +package ai.z.openapi.service.embedding; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Response wrapper for embedding API calls. Contains the result of text embedding + * operations along with status information. + */ +@Data +public class EmbeddingResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates whether the request was successful. + */ + private boolean success; + + /** + * The embedding result data. + */ + private EmbeddingResult data; + + /** + * Error information if the request failed. + */ + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResult.java b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResult.java new file mode 100644 index 0000000..8a7eb1d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingResult.java @@ -0,0 +1,106 @@ +package ai.z.openapi.service.embedding; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.embedding.EmbeddingResultDeserializer; +import ai.z.openapi.service.model.Usage; +import lombok.Getter; + +import java.util.Iterator; +import java.util.List; + +/** + * An object containing a response from the answer api + */ +@Getter +@JsonDeserialize(using = EmbeddingResultDeserializer.class) +public class EmbeddingResult extends ObjectNode { + + /** + * The GLMmodel used for generating embeddings + */ + String model; + + /** + * The type of object returned, should be "list" + */ + String object; + + /** + * A list of the calculated embeddings + */ + List data; + + /** + * The API usage for this request + */ + Usage usage; + + public EmbeddingResult() { + super(JsonNodeFactory.instance); + } + + public EmbeddingResult(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("model") != null) { + this.setModel(objectNode.get("model").asText()); + } + else { + this.setModel(null); + } + if (objectNode.get("object") != null) { + this.setObject(objectNode.get("object").asText()); + } + else { + this.setObject(null); + } + if (objectNode.get("data") != null) { + List data1 = objectMapper.convertValue(objectNode.get("data"), + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + this.setData(data1); + } + else { + this.setData(null); + } + if (objectNode.get("usage") != null) { + this.setUsage(objectMapper.convertValue(objectNode.get("usage"), Usage.class)); + } + else { + this.setUsage(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public void setModel(String model) { + this.model = model; + this.put("model", model); + } + + public void setObject(String object) { + this.object = object; + this.put("object", object); + } + + public void setData(List data) { + this.data = data; + this.putPOJO("data", data); + } + + public void setUsage(Usage usage) { + this.usage = usage; + this.putPOJO("usage", usage); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingService.java b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingService.java new file mode 100644 index 0000000..c41d1e8 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingService.java @@ -0,0 +1,15 @@ +package ai.z.openapi.service.embedding; + +/** + * Embedding service interface + */ +public interface EmbeddingService { + + /** + * Creates embeddings for the given input text. + * @param request the embeddings request + * @return EmbeddingResponse containing the embedding vectors + */ + EmbeddingResponse createEmbeddings(EmbeddingCreateParams request); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingServiceImpl.java b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingServiceImpl.java new file mode 100644 index 0000000..9dd451d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/embedding/EmbeddingServiceImpl.java @@ -0,0 +1,27 @@ +package ai.z.openapi.service.embedding; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.embedding.EmbeddingApi; +import ai.z.openapi.utils.RequestSupplier; + +/** + * Embedding service implementation + */ +public class EmbeddingServiceImpl implements EmbeddingService { + + private final ZaiClient zAiClient; + + private final EmbeddingApi embeddingApi; + + public EmbeddingServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.embeddingApi = zAiClient.retrofit().create(EmbeddingApi.class); + } + + @Override + public EmbeddingResponse createEmbeddings(EmbeddingCreateParams request) { + RequestSupplier supplier = embeddingApi::createEmbeddings; + return this.zAiClient.executeRequest(request, supplier, EmbeddingResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/file/File.java b/core/src/main/java/ai/z/openapi/service/file/File.java new file mode 100644 index 0000000..20a39e1 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/File.java @@ -0,0 +1,60 @@ +package ai.z.openapi.service.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * A file uploaded to ZAi + * + */ +@Data +public class File { + + /** + * The unique id of this file. + */ + String id; + + /** + * The type of object returned, should be "file". + */ + String object; + + /** + * File size in bytes. + */ + Long bytes; + + /** + * The creation time in epoch seconds. + */ + @JsonProperty("created_at") + Long createdAt; + + /** + * The name of the file. + */ + String filename; + + /** + * Description of the file's purpose. + */ + String purpose; + + /** + * The current status of the file, which can be either uploaded, processed, pending, + * error, deleting or deleted. + */ + String status; + + /** + * Additional details about the status of the file. If the file is in the error state, + * this will include a message describing the error. + */ + @JsonProperty("status_details") + String statusDetails; + + ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/FileApiResponse.java b/core/src/main/java/ai/z/openapi/service/file/FileApiResponse.java new file mode 100644 index 0000000..69f632b --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class FileApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private File data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/FileDelResponse.java b/core/src/main/java/ai/z/openapi/service/file/FileDelResponse.java new file mode 100644 index 0000000..0e21855 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileDelResponse.java @@ -0,0 +1,16 @@ +package ai.z.openapi.service.file; + +import lombok.Data; + +@Data +public class FileDelResponse { + + private int code; + + private String msg; + + private boolean success; + + private FileDeleted data; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/FileDeleted.java b/core/src/main/java/ai/z/openapi/service/file/FileDeleted.java new file mode 100644 index 0000000..bab9ddd --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileDeleted.java @@ -0,0 +1,33 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Represents the result of a file deletion operation. Contains information about whether + * the file was successfully deleted. + */ +@Data +public class FileDeleted { + + /** + * The ID of the deleted file. + */ + private String id; + + /** + * Indicates whether the file was successfully deleted. + */ + private boolean deleted; + + /** + * The object type, always "file". + */ + private final String object = "file"; + + /** + * Error information if the deletion failed. + */ + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/FileListParams.java b/core/src/main/java/ai/z/openapi/service/file/FileListParams.java new file mode 100644 index 0000000..692927c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileListParams.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + * Query file list + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FileListParams extends CommonRequest implements ClientRequest { + + private String purpose; + + private Integer limit; + + private String after; + + private String order; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/FileService.java b/core/src/main/java/ai/z/openapi/service/file/FileService.java new file mode 100644 index 0000000..a680c11 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileService.java @@ -0,0 +1,34 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.response.HttpxBinaryResponseContent; +import java.io.IOException; + +/** + * File service interface + */ +public interface FileService { + + /** + * Uploads a file to the server. + * @param request the file upload request + * @return FileApiResponse containing the upload result + */ + FileApiResponse uploadFile(FileUploadParams request); + + /** + * Lists all files. + * @param queryFilesRequest FileListParams containing the query parameters for listing + * files + * @return QueryFileApiResponse containing the list of files + */ + QueryFileApiResponse listFiles(FileListParams queryFilesRequest); + + /** + * Retrieves the content of a specific file. + * @param fileId the ID of the file to retrieve + * @return HttpxBinaryResponseContent containing the file content + * @throws IOException if an I/O error occurs + */ + HttpxBinaryResponseContent retrieveFileContent(String fileId) throws IOException; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/file/FileServiceImpl.java b/core/src/main/java/ai/z/openapi/service/file/FileServiceImpl.java new file mode 100644 index 0000000..6bdc153 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileServiceImpl.java @@ -0,0 +1,86 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.file.FileApi; +import ai.z.openapi.utils.RequestSupplier; +import ai.z.openapi.core.response.HttpxBinaryResponseContent; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Response; +import java.io.IOException; +import java.util.Date; + +/** + * File service implementation + */ +public class FileServiceImpl implements FileService { + + private final ZaiClient zAiClient; + + private final FileApi fileApi; + + public FileServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.fileApi = zAiClient.retrofit().create(FileApi.class); + } + + @Override + public FileApiResponse uploadFile(FileUploadParams request) { + RequestSupplier supplier = (params) -> { + try { + java.io.File file = new java.io.File(params.getFilePath()); + if (!file.exists()) { + throw new RuntimeException("file not found"); + } + MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), + RequestBody.create(MediaType.parse("application/octet-stream"), file)); + MultipartBody.Builder formBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + formBodyBuilder.addPart(filePart); + formBodyBuilder.addFormDataPart("purpose", params.getPurpose()); + if (params.getExtraJson() != null) { + for (String s : params.getExtraJson().keySet()) { + if (params.getExtraJson().get(s) instanceof String + || params.getExtraJson().get(s) instanceof Number + || params.getExtraJson().get(s) instanceof Boolean + || params.getExtraJson().get(s) instanceof Character) { + formBodyBuilder.addFormDataPart(s, params.getExtraJson().get(s).toString()); + } + else if (params.getExtraJson().get(s) instanceof Date) { + Date date = (Date) params.getExtraJson().get(s); + formBodyBuilder.addFormDataPart(s, String.valueOf(date.getTime())); + } + } + } + MultipartBody multipartBody = formBodyBuilder.build(); + return fileApi.uploadFile(multipartBody); + } + catch (Exception e) { + throw new RuntimeException(e); + } + }; + return this.zAiClient.executeRequest(request, supplier, FileApiResponse.class); + } + + @Override + public QueryFileApiResponse listFiles(FileListParams queryFilesRequest) { + RequestSupplier supplier = (params) -> fileApi.queryFileList(params.getAfter(), + params.getPurpose(), params.getOrder(), params.getLimit()); + return this.zAiClient.executeRequest(queryFilesRequest, supplier, QueryFileApiResponse.class); + } + + @Override + public HttpxBinaryResponseContent retrieveFileContent(String fileId) throws IOException { + return fileWrapper(fileApi.fileContent(fileId)); + } + + private HttpxBinaryResponseContent fileWrapper(retrofit2.Call response) throws IOException { + Response execute = response.execute(); + if (!execute.isSuccessful() || execute.body() == null) { + throw new IOException("Failed to get the file content"); + } + return new HttpxBinaryResponseContent(execute); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/file/FileUploadParams.java b/core/src/main/java/ai/z/openapi/service/file/FileUploadParams.java new file mode 100644 index 0000000..bf90693 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/FileUploadParams.java @@ -0,0 +1,29 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + * Create file upload request + * + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FileUploadParams extends CommonRequest implements ClientRequest { + + /** + * The purpose of the file + */ + private String purpose; + + /** + * local file + */ + private String filePath; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/QueryBatchRequest.java b/core/src/main/java/ai/z/openapi/service/file/QueryBatchRequest.java new file mode 100644 index 0000000..8505c02 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/QueryBatchRequest.java @@ -0,0 +1,24 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QueryBatchRequest implements ClientRequest { + + private Integer limit; + + private String after; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/QueryFileApiResponse.java b/core/src/main/java/ai/z/openapi/service/file/QueryFileApiResponse.java new file mode 100644 index 0000000..e6cdb16 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/QueryFileApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryFileApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private QueryFileResult data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/file/QueryFileResult.java b/core/src/main/java/ai/z/openapi/service/file/QueryFileResult.java new file mode 100644 index 0000000..cef0735 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/QueryFileResult.java @@ -0,0 +1,17 @@ +package ai.z.openapi.service.file; + +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +import java.util.List; + +@Data +public class QueryFileResult { + + private String object; + + private List data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/CreateFineTuningJobApiResponse.java b/core/src/main/java/ai/z/openapi/service/fine_turning/CreateFineTuningJobApiResponse.java new file mode 100644 index 0000000..5cfc8a7 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/CreateFineTuningJobApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class CreateFineTuningJobApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private FineTuningJob data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatus.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatus.java new file mode 100644 index 0000000..b56276c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatus.java @@ -0,0 +1,31 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Represents the status of a fine-tuned model. This class contains information about + * model deletion status and identification. + */ +@Data +public class FineTunedModelsStatus { + + /** + * Request ID. + */ + @JsonProperty("request_id") + private String requestId; + + /** + * Model name. + */ + @JsonProperty("model_name") + private String modelName; + + /** + * Deletion status: deleting (in progress), deleted (completed). + */ + @JsonProperty("delete_status") + private String deleteStatus; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatusResponse.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatusResponse.java new file mode 100644 index 0000000..d570813 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTunedModelsStatusResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class FineTunedModelsStatusResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private FineTunedModelsStatus data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEvent.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEvent.java new file mode 100644 index 0000000..23f30fa --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEvent.java @@ -0,0 +1,30 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.service.model.ChatError; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * An object representing an event in the lifecycle of a fine-tuning job + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) // Ignore unknown properties +public class FineTuningEvent { + + private String object; + + @JsonProperty("has_more") + private Boolean hasMore; + + private List data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventData.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventData.java new file mode 100644 index 0000000..c59727d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventData.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) // Ignore unknown properties +public class FineTuningEventData { + + /** + * The type of object returned, should be "fine-tuneing.job.event". + */ + private String object; + + /** + * The ID of the fine-tuning event. + */ + private String id; + + /** + * The creation time in epoch seconds. + */ + @JsonProperty("created_at") + private Long createdAt; + + /** + * The log level of this message. + */ + private String level; + + /** + * The event message. + */ + private String message; + + /** + * The type of event, i.e. "message" + */ + private String type; + + /** + * The data of the event. + */ + private FineTuningEventMetric data; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventMetric.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventMetric.java new file mode 100644 index 0000000..0622b46 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningEventMetric.java @@ -0,0 +1,48 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) // Ignore unknown properties +public class FineTuningEventMetric { + + @JsonProperty("epoch") + private String epoch; + + @JsonProperty("current_steps") + private Integer currentSteps; + + @JsonProperty("total_steps") + private Integer totalSteps; + + @JsonProperty("elapsed_time") + private String elapsedTime; + + @JsonProperty("remaining_time") + private String remainingTime; + + @JsonProperty("trained_tokens") + private Integer trainedTokens; + + @JsonProperty("loss") + private Float loss; + + @JsonProperty("eval_loss") + private Float evalLoss; + + @JsonProperty("acc") + private Float acc; + + @JsonProperty("eval_acc") + private Float evalAcc; + + @JsonProperty("learning_rate") + private Float learningRate; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJob.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJob.java new file mode 100644 index 0000000..0696c02 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJob.java @@ -0,0 +1,98 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +import java.util.List; + +/** + * Fine-tuning job + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FineTuningJob { + + /** + * The object identifier, which can be referenced in the API endpoints. + */ + String id; + + /** + * The object type, which is always "fine_tuning.job". + */ + String object; + + /** + * The unix timestamp for when the fine-tuning job was created. + */ + @JsonProperty("created_at") + Long createdAt; + + /** + * The unix timestamp for when the fine-tuning job was finished. + */ + @JsonProperty("finished_at") + Long finishedAt; + + /** + * The base model that is being fine-tuned. + */ + String model; + + /** + * The name of the fine-tuned model that is being created. Can be null if no + * fine-tuned model is created yet. + */ + @JsonProperty("fine_tuned_model") + String fine_tuned_model; + + /** + * The organization that owns the fine-tuning job. + */ + @JsonProperty("organization_id") + String organizationId; + + /** + * The current status of the fine-tuning job. Can be either created, pending, running, + * succeeded, failed, or cancelled. + */ + String status; + + /** + * The hyperparameters used for the fine-tuning job. See the fine-tuning guide for + * more details. + */ + Hyperparameters hyperparameters; + + /** + * The file ID used for training. + */ + @JsonProperty("training_file") + String training_file; + + /** + * The file ID used for validation. Can be null if validation is not used. + */ + @JsonProperty("validation_file") + String validation_file; + + /** + * The compiled results files for the fine-tuning job. + */ + @JsonProperty("result_files") + List result_files; + + /** + * The total number of billable tokens processed by this fine-tuning job. + */ + @JsonProperty("trained_tokens") + Integer trainedTokens; + + @JsonProperty("request_id") + String requestId; + + private ChatError error; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobIdRequest.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobIdRequest.java new file mode 100644 index 0000000..f2e09c0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobIdRequest.java @@ -0,0 +1,24 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FineTuningJobIdRequest implements ClientRequest { + + @JsonProperty("job_id") + private String jobId; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobModelRequest.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobModelRequest.java new file mode 100644 index 0000000..6d2902e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobModelRequest.java @@ -0,0 +1,24 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FineTuningJobModelRequest implements ClientRequest { + + @JsonProperty("fine_tuned_model") + private String fineTunedModel; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobRequest.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobRequest.java new file mode 100644 index 0000000..cafda78 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningJobRequest.java @@ -0,0 +1,50 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import lombok.*; + +/** + * ClientRequest to create a fine tuning job + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class FineTuningJobRequest implements ClientRequest { + + /** + * The ID of an uploaded file that contains training data. + */ + @NonNull + @JsonProperty("training_file") + String training_file; + + /** + * The ID of an uploaded file that contains validation data. Optional. + */ + @JsonProperty("validation_file") + String validationFile; + + /** + * The name of the model to fine-tune. + */ + @NonNull + String model; + + /** + * The hyperparameters used for the fine-tuning job. + */ + Hyperparameters hyperparameters; + + /** + * A string of up to 40 characters that will be added to your fine-tuned model name. + */ + String suffix; + + /** + * Client request ID + */ + private String requestId; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningService.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningService.java new file mode 100644 index 0000000..ebea77d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningService.java @@ -0,0 +1,60 @@ +package ai.z.openapi.service.fine_turning; + +/** + * Fine-tuning service interface + */ +public interface FineTuningService { + + /** + * Creates a fine-tuning job. + * @param request the fine-tuning job creation request + * @return CreateFineTuningJobApiResponse containing the created job details + */ + CreateFineTuningJobApiResponse createFineTuningJob(FineTuningJobRequest request); + + /** + * Lists events for a fine-tuning job. + * @param queryFineTuningJobRequest the request parameters for listing events + * @return QueryFineTuningEventApiResponse containing the list of events + */ + QueryFineTuningEventApiResponse listFineTuningJobEvents(QueryFineTuningJobRequest queryFineTuningJobRequest); + + /** + * Retrieves a specific fine-tuning job. + * @param queryFineTuningJobRequest the request parameters for retrieving the job + * @return QueryFineTuningJobApiResponse containing the job details + */ + QueryFineTuningJobApiResponse retrieveFineTuningJob(QueryFineTuningJobRequest queryFineTuningJobRequest); + + /** + * Lists personal fine-tuning jobs. + * @param queryPersonalFineTuningJobRequest the request parameters for listing + * personal jobs + * @return QueryPersonalFineTuningJobApiResponse containing the list of personal + * fine-tuning jobs + */ + QueryPersonalFineTuningJobApiResponse listPersonalFineTuningJobs( + QueryPersonalFineTuningJobRequest queryPersonalFineTuningJobRequest); + + /** + * Cancels a fine-tuning job. + * @param fineTuningJobIdRequest the request containing the job ID to cancel + * @return QueryFineTuningJobApiResponse containing the cancellation result + */ + QueryFineTuningJobApiResponse cancelFineTuningJob(FineTuningJobIdRequest fineTuningJobIdRequest); + + /** + * Deletes a fine-tuning job. + * @param fineTuningJobIdRequest the request containing the job ID to delete + * @return QueryFineTuningJobApiResponse containing the deletion result + */ + QueryFineTuningJobApiResponse deleteFineTuningJob(FineTuningJobIdRequest fineTuningJobIdRequest); + + /** + * Deletes a fine-tuned model. + * @param fineTuningJobModelRequest the request containing the model to delete + * @return FineTunedModelsStatusResponse containing the deletion status + */ + FineTunedModelsStatusResponse deleteFineTunedModel(FineTuningJobModelRequest fineTuningJobModelRequest); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningServiceImpl.java b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningServiceImpl.java new file mode 100644 index 0000000..f9481fa --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/FineTuningServiceImpl.java @@ -0,0 +1,73 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.fine_tuning.FineTuningApi; +import ai.z.openapi.utils.RequestSupplier; + +/** + * Fine-tuning service implementation + */ +public class FineTuningServiceImpl implements FineTuningService { + + private final ZaiClient zAiClient; + + private final FineTuningApi fineTuningApi; + + public FineTuningServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.fineTuningApi = zAiClient.retrofit().create(FineTuningApi.class); + } + + @Override + public CreateFineTuningJobApiResponse createFineTuningJob(FineTuningJobRequest request) { + RequestSupplier supplier = fineTuningApi::createFineTuningJob; + return this.zAiClient.executeRequest(request, supplier, CreateFineTuningJobApiResponse.class); + } + + @Override + public QueryFineTuningEventApiResponse listFineTuningJobEvents( + QueryFineTuningJobRequest queryFineTuningJobRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .listFineTuningJobEvents(params.getJobId(), params.getLimit(), params.getAfter()); + return this.zAiClient.executeRequest(queryFineTuningJobRequest, supplier, + QueryFineTuningEventApiResponse.class); + } + + @Override + public QueryFineTuningJobApiResponse retrieveFineTuningJob(QueryFineTuningJobRequest queryFineTuningJobRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .retrieveFineTuningJob(params.getJobId(), params.getLimit(), params.getAfter()); + return this.zAiClient.executeRequest(queryFineTuningJobRequest, supplier, QueryFineTuningJobApiResponse.class); + } + + @Override + public QueryPersonalFineTuningJobApiResponse listPersonalFineTuningJobs( + QueryPersonalFineTuningJobRequest queryPersonalFineTuningJobRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .queryPersonalFineTuningJobs(params.getLimit(), params.getAfter()); + return this.zAiClient.executeRequest(queryPersonalFineTuningJobRequest, supplier, + QueryPersonalFineTuningJobApiResponse.class); + } + + @Override + public QueryFineTuningJobApiResponse cancelFineTuningJob(FineTuningJobIdRequest fineTuningJobIdRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .cancelFineTuningJob(params.getJobId()); + return this.zAiClient.executeRequest(fineTuningJobIdRequest, supplier, QueryFineTuningJobApiResponse.class); + } + + @Override + public QueryFineTuningJobApiResponse deleteFineTuningJob(FineTuningJobIdRequest fineTuningJobIdRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .deleteFineTuningJob(params.getJobId()); + return this.zAiClient.executeRequest(fineTuningJobIdRequest, supplier, QueryFineTuningJobApiResponse.class); + } + + @Override + public FineTunedModelsStatusResponse deleteFineTunedModel(FineTuningJobModelRequest fineTuningJobModelRequest) { + RequestSupplier supplier = (params) -> fineTuningApi + .deleteFineTuningModel(params.getFineTunedModel()); + return this.zAiClient.executeRequest(fineTuningJobModelRequest, supplier, FineTunedModelsStatusResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/Hyperparameters.java b/core/src/main/java/ai/z/openapi/service/fine_turning/Hyperparameters.java new file mode 100644 index 0000000..f25965f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/Hyperparameters.java @@ -0,0 +1,27 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Hyperparameters for a fine-tuning job + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Hyperparameters { + + /** + * The number of epochs to train the model for. An epoch refers to one full cycle + * through the training dataset. "Auto" decides the optimal number of epochs based on + * the size of the dataset. If setting the number manually, we support any number + * between 1 and 50 epochs. + */ + @JsonProperty("n_epochs") + Integer nEpochs; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/PersonalFineTuningJob.java b/core/src/main/java/ai/z/openapi/service/fine_turning/PersonalFineTuningJob.java new file mode 100644 index 0000000..869e9b0 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/PersonalFineTuningJob.java @@ -0,0 +1,19 @@ +package ai.z.openapi.service.fine_turning; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * Fine-tuning job + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PersonalFineTuningJob { + + String object; + + private List data; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningEventApiResponse.java b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningEventApiResponse.java new file mode 100644 index 0000000..aa97168 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningEventApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryFineTuningEventApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private FineTuningEvent data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobApiResponse.java b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobApiResponse.java new file mode 100644 index 0000000..fc9e43f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryFineTuningJobApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private FineTuningJob data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobRequest.java b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobRequest.java new file mode 100644 index 0000000..5da6df6 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryFineTuningJobRequest.java @@ -0,0 +1,29 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * ClientRequest to create a fine tuning job + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QueryFineTuningJobRequest implements ClientRequest { + + @JsonProperty("job_id") + private String jobId; + + private Integer limit; + + private String after; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobApiResponse.java b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobApiResponse.java new file mode 100644 index 0000000..1061bcb --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryPersonalFineTuningJobApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private PersonalFineTuningJob data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobRequest.java b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobRequest.java new file mode 100644 index 0000000..70f9509 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/fine_turning/QueryPersonalFineTuningJobRequest.java @@ -0,0 +1,22 @@ +package ai.z.openapi.service.fine_turning; + +import ai.z.openapi.core.model.ClientRequest; +import lombok.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * ClientRequest to create a fine tuning job + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QueryPersonalFineTuningJobRequest implements ClientRequest { + + private Integer limit; + + private String after; + +} diff --git a/core/src/main/java/ai/z/openapi/service/image/CreateImageRequest.java b/core/src/main/java/ai/z/openapi/service/image/CreateImageRequest.java new file mode 100644 index 0000000..30279b9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/CreateImageRequest.java @@ -0,0 +1,44 @@ +package ai.z.openapi.service.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.SensitiveWordCheckRequest; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + * A request for ZAi to create an image based on a prompt All fields except prompt are + * optional + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class CreateImageRequest extends CommonRequest implements ClientRequest { + + /** + * A text description of the desired image(s). The maximum length is 1000 characters + * for dall-e-2 and 4000 characters for dall-e-3. + */ + @NonNull + private String prompt; + + /** + * The model to use for image generation. Defaults to "dall-e-2". + */ + private String model; + + /** + * The size of the image to generate. Defaults to "256x256". + */ + private String size; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + +} diff --git a/core/src/main/java/ai/z/openapi/service/image/Image.java b/core/src/main/java/ai/z/openapi/service/image/Image.java new file mode 100644 index 0000000..c121f57 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/Image.java @@ -0,0 +1,91 @@ +package ai.z.openapi.service.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.image.ImageDeserializer; +import lombok.Getter; + +import java.util.Iterator; + +/** + * An object containing either a URL or a base 64 encoded image. + */ + +@Getter +@JsonDeserialize(using = ImageDeserializer.class) +public class Image extends ObjectNode { + + /** + * The URL where the image can be accessed. + */ + @JsonProperty("url") + String url; + + /** + * Base64 encoded image string. + */ + @JsonProperty("b64_json") + String b64Json; + + /** + * The prompt that was used to generate the image, if there was any revision to the + * prompt. + */ + @JsonProperty("revised_prompt") + String revisedPrompt; + + public Image() { + super(JsonNodeFactory.instance); + } + + public Image(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("url") != null) { + this.setUrl(objectNode.get("url").asText()); + } + else { + this.setUrl(null); + } + if (objectNode.get("b64_json") != null) { + this.setB64Json(objectNode.get("b64_json").asText()); + } + else { + this.setB64Json(null); + } + if (objectNode.get("revised_prompt") != null) { + this.setRevisedPrompt(objectNode.get("revised_prompt").asText()); + } + else { + this.setRevisedPrompt(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public void setUrl(String url) { + this.url = url; + this.put("url", url); + } + + public void setB64Json(String b64Json) { + this.b64Json = b64Json; + this.put("b64_json", b64Json); + } + + public void setRevisedPrompt(String revisedPrompt) { + this.revisedPrompt = revisedPrompt; + this.put("revised_prompt", revisedPrompt); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/image/ImageResponse.java b/core/src/main/java/ai/z/openapi/service/image/ImageResponse.java new file mode 100644 index 0000000..3809235 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/ImageResponse.java @@ -0,0 +1,39 @@ +package ai.z.openapi.service.image; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +/** + * Response wrapper for image generation API calls. Contains the result of image + * generation operations along with status information. + */ +@Data +public class ImageResponse implements ClientResponse { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates whether the request was successful. + */ + private boolean success; + + /** + * The image generation result data. + */ + private ImageResult data; + + /** + * Error information if the request failed. + */ + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/image/ImageResult.java b/core/src/main/java/ai/z/openapi/service/image/ImageResult.java new file mode 100644 index 0000000..3d1be0e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/ImageResult.java @@ -0,0 +1,106 @@ +package ai.z.openapi.service.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.image.ImageResultDeserializer; +import lombok.Getter; + +import java.util.Iterator; +import java.util.List; + +/** + * An object with a list of image results. + */ + +@Getter +@JsonDeserialize(using = ImageResultDeserializer.class) +public class ImageResult extends ObjectNode { + + /** + * The creation time in epoch seconds. + */ + @JsonProperty("created") + Long created; + + /** + * List of image results. + */ + List data; + + /** + * Task number submitted by user in client request or task number generated by + * platform + */ + @JsonProperty("request_id") + private String requestId; + + public ImageResult() { + super(JsonNodeFactory.instance); + } + + public ImageResult(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("created") != null) { + this.setCreated(objectNode.get("created").asLong()); + } + else { + this.setCreated(null); + } + if (objectNode.get("data") != null) { + List data = objectMapper.convertValue(objectNode.get("data"), new TypeReference>() { + }); + + this.setData(data); + } + else { + this.setData(null); + } + + if (objectNode.get("request_id") != null) { + this.setRequestId(objectNode.get("request_id").asText()); + } + else { + this.setRequestId(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public void setCreated(Long created) { + this.created = created; + this.put("created", created); + } + + public void setData(List data) { + this.data = data; + ArrayNode jsonNodes = this.putArray("data"); + if (data == null) { + jsonNodes.removeAll(); + } + else { + + for (Image image : data) { + jsonNodes.add(image); + } + } + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + this.put("request_id", requestId); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/image/ImageService.java b/core/src/main/java/ai/z/openapi/service/image/ImageService.java new file mode 100644 index 0000000..bc53d24 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/ImageService.java @@ -0,0 +1,15 @@ +package ai.z.openapi.service.image; + +/** + * Image service interface + */ +public interface ImageService { + + /** + * Creates an image based on the provided generation request. + * @param createImageRequest the image generation request + * @return ImageResponse containing the generated image result + */ + ImageResponse createImage(CreateImageRequest createImageRequest); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/image/ImageServiceImpl.java b/core/src/main/java/ai/z/openapi/service/image/ImageServiceImpl.java new file mode 100644 index 0000000..6e9c518 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/image/ImageServiceImpl.java @@ -0,0 +1,27 @@ +package ai.z.openapi.service.image; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.images.ImagesApi; +import ai.z.openapi.utils.RequestSupplier; + +/** + * Image service implementation + */ +public class ImageServiceImpl implements ImageService { + + private final ZaiClient zAiClient; + + private final ImagesApi imagesApi; + + public ImageServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.imagesApi = zAiClient.retrofit().create(ImagesApi.class); + } + + @Override + public ImageResponse createImage(CreateImageRequest createImageRequest) { + RequestSupplier supplier = imagesApi::createImage; + return this.zAiClient.executeRequest(createImageRequest, supplier, ImageResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeBaseParams.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeBaseParams.java new file mode 100644 index 0000000..ed84ae7 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeBaseParams.java @@ -0,0 +1,53 @@ +package ai.z.openapi.service.knowledge; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class KnowledgeBaseParams implements ClientRequest { + + /** + * Knowledge base parameter type definition + *

+ * Attributes: embedding_id (int): Vector model ID bound to the knowledge base name + * (String): Knowledge base name, limited to 100 characters customer_identifier + * (String): User identifier, within 32 characters description (String): Knowledge + * base description, limited to 500 characters background (String): Background color + * icon (String): Knowledge base icon bucket_id (String): Bucket ID, limited to 32 + * characters + */ + + @JsonProperty("embedding_id") + private int embeddingId; + + @JsonProperty("name") + private String name; + + @JsonProperty("knowledge_id") + private String knowledgeId; + + @JsonProperty("customer_identifier") + private String customerIdentifier; + + @JsonProperty("description") + private String description; + + @JsonProperty("background") + private String background; + + @JsonProperty("icon") + private String icon; + + @JsonProperty("bucket_id") + private String bucketId; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeEditResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeEditResponse.java new file mode 100644 index 0000000..beef251 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeEditResponse.java @@ -0,0 +1,40 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; +import retrofit2.Response; + +/** + * Response for knowledge base edit operations. This class contains the response data for + * knowledge base modification requests. + */ +@Data +public class KnowledgeEditResponse implements ClientResponse> { + + /** + * Response status code. + */ + private int code; + + /** + * Response message. + */ + private String msg; + + /** + * Indicates if the edit operation was successful. + */ + private boolean success; + + /** + * The response data (typically void for edit operations). + */ + private Response data; + + /** + * Error information if the edit operation failed. + */ + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeInfo.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeInfo.java new file mode 100644 index 0000000..8bbfed6 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeInfo.java @@ -0,0 +1,205 @@ +package ai.z.openapi.service.knowledge; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeInfoDeserializer; + +import java.util.Iterator; + +/** + * This class represents the knowledge base information. + */ +@JsonDeserialize(using = KnowledgeInfoDeserializer.class) +public class KnowledgeInfo extends ObjectNode { + + /** + * Knowledge base unique ID + */ + @JsonProperty("id") + private String id; + + /** + * Vector model bound to the knowledge base + */ + @JsonProperty("embedding_id") + private String embeddingId; + + /** + * Knowledge base name, limited to 100 characters + */ + @JsonProperty("name") + private String name; + + /** + * User identifier, within 32 characters + */ + @JsonProperty("customer_identifier") + private String customerIdentifier; + + /** + * Knowledge base description, limited to 500 characters + */ + @JsonProperty("description") + private String description; + + /** + * Background color: 'blue', 'red', 'orange', 'purple', 'sky' + */ + @JsonProperty("background") + private String background; + + /** + * Knowledge base icon: question: question mark, book: book, seal: seal, wrench: + * wrench, tag: tag, horn: horn, house: house + */ + @JsonProperty("icon") + private String icon; + + /** + * Bucket ID, limited to 32 characters + */ + @JsonProperty("bucket_id") + private String bucketId; + + public KnowledgeInfo() { + super(JsonNodeFactory.instance); + } + + public KnowledgeInfo(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + if (objectNode.has("id")) { + this.setId(objectNode.get("id").asText()); + } + else { + this.setId(null); + } + if (objectNode.has("embedding_id")) { + this.setEmbeddingId(objectNode.get("embedding_id").asText()); + } + else { + this.setEmbeddingId(null); + } + if (objectNode.has("name")) { + this.setName(objectNode.get("name").asText()); + } + else { + this.setName(null); + } + + if (objectNode.has("customer_identifier")) { + this.setCustomerIdentifier(objectNode.get("customer_identifier").asText()); + } + else { + this.setCustomerIdentifier(null); + } + if (objectNode.has("description")) { + this.setDescription(objectNode.get("description").asText()); + } + else { + this.setDescription(null); + } + if (objectNode.has("background")) { + this.setBackground(objectNode.get("background").asText()); + } + else { + this.setBackground(null); + } + if (objectNode.has("icon")) { + this.setIcon(objectNode.get("icon").asText()); + } + else { + this.setIcon(null); + } + if (objectNode.has("bucket_id")) { + this.setBucketId(objectNode.get("bucket_id").asText()); + } + else { + this.setBucketId(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + // Getters and Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + this.put("id", id); + } + + public String getEmbeddingId() { + return embeddingId; + } + + public void setEmbeddingId(String embeddingId) { + this.embeddingId = embeddingId; + this.put("embedding_id", embeddingId); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + this.put("name", name); + } + + public String getCustomerIdentifier() { + return customerIdentifier; + } + + public void setCustomerIdentifier(String customerIdentifier) { + this.customerIdentifier = customerIdentifier; + this.put("customer_identifier", customerIdentifier); + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + this.put("description", description); + } + + public String getBackground() { + return background; + } + + public void setBackground(String background) { + this.background = background; + this.put("background", background); + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + this.put("icon", icon); + } + + public String getBucketId() { + return bucketId; + } + + public void setBucketId(String bucketId) { + this.bucketId = bucketId; + this.put("bucket_id", bucketId); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgePage.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgePage.java new file mode 100644 index 0000000..b33cd9f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgePage.java @@ -0,0 +1,97 @@ +package ai.z.openapi.service.knowledge; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.knowledge.KnowledgePageDeserializer; + +import java.util.Iterator; +import java.util.List; + +/** + * This class represents a page of knowledge base information. + */ +@JsonDeserialize(using = KnowledgePageDeserializer.class) +public class KnowledgePage extends ObjectNode { + + /** + * Knowledge base information list + */ + @JsonProperty("list") + private List list; + + /** + * Total count + */ + @JsonProperty("total") + private Integer total; + + public KnowledgePage() { + super(JsonNodeFactory.instance); + } + + public KnowledgePage(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("list") != null) { + List list = objectMapper.convertValue(objectNode.get("list"), + new TypeReference>() { + }); + + this.setList(list); + } + else { + this.setList(null); + } + if (objectNode.get("total") != null) { + this.setTotal(objectNode.get("total").asInt()); + } + else { + this.setTotal(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + // Getters and Setters + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + ArrayNode jsonNodes = this.putArray("list"); + if (list == null) { + jsonNodes.removeAll(); + } + else { + + for (KnowledgeInfo item : list) { + jsonNodes.add(item); + } + } + + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + this.put("total", total); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeResponse.java new file mode 100644 index 0000000..aa2771c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class KnowledgeResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private KnowledgeInfo data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeService.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeService.java new file mode 100644 index 0000000..c879859 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeService.java @@ -0,0 +1,50 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.service.knowledge.KnowledgeBaseParams; +import ai.z.openapi.service.knowledge.KnowledgeEditResponse; +import ai.z.openapi.service.knowledge.QueryKnowledgeApiResponse; +import ai.z.openapi.service.knowledge.KnowledgeResponse; +import ai.z.openapi.service.knowledge.KnowledgeUsedResponse; +import ai.z.openapi.service.knowledge.QueryKnowledgeRequest; +import retrofit2.Response; + +/** + * Knowledge service interface + */ +public interface KnowledgeService { + + /** + * Creates a new knowledge base. + * @param request the knowledge creation request + * @return KnowledgeResponse containing the creation result + */ + KnowledgeResponse createKnowledge(KnowledgeBaseParams request); + + /** + * Modifies an existing knowledge base. + * @param request the knowledge modification request + * @return KnowledgeEditResponse containing the modification result + */ + KnowledgeEditResponse modifyKnowledge(KnowledgeBaseParams request); + + /** + * Queries knowledge. + * @param request the knowledge query request + * @return QueryKnowledgeApiResponse containing the query result + */ + QueryKnowledgeApiResponse queryKnowledge(QueryKnowledgeRequest request); + + /** + * Deletes a knowledge base. + * @param knowledgeId the knowledge ID to delete + * @return KnowledgeResponse containing the deletion result + */ + KnowledgeEditResponse deleteKnowledge(String knowledgeId); + + /** + * Checks if knowledge is used. + * @return KnowledgeUsedResponse containing the usage status + */ + KnowledgeUsedResponse checkKnowledgeUsed(); + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeServiceImpl.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeServiceImpl.java new file mode 100644 index 0000000..a1fbdce --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeServiceImpl.java @@ -0,0 +1,57 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.ZaiClient; +import ai.z.openapi.api.knowledge.KnowledgeApi; +import ai.z.openapi.service.model.AsyncResultRetrieveParams; +import ai.z.openapi.utils.RequestSupplier; +import retrofit2.Response; + +/** + * Knowledge service implementation + */ +public class KnowledgeServiceImpl implements KnowledgeService { + + private final ZaiClient zAiClient; + + private final KnowledgeApi knowledgeApi; + + public KnowledgeServiceImpl(ZaiClient zAiClient) { + this.zAiClient = zAiClient; + this.knowledgeApi = this.zAiClient.retrofit().create(KnowledgeApi.class); + } + + @Override + public KnowledgeResponse createKnowledge(KnowledgeBaseParams request) { + RequestSupplier supplier = knowledgeApi::knowledgeCreate; + return this.zAiClient.executeRequest(request, supplier, KnowledgeResponse.class); + } + + @Override + public KnowledgeEditResponse modifyKnowledge(KnowledgeBaseParams request) { + RequestSupplier> supplier = (params) -> knowledgeApi + .knowledgeModify(params.getKnowledgeId(), params); + return zAiClient.executeRequest(request, supplier, KnowledgeEditResponse.class); + } + + @Override + public QueryKnowledgeApiResponse queryKnowledge(QueryKnowledgeRequest request) { + RequestSupplier supplier = (params) -> knowledgeApi + .knowledgeQuery(params.getPage(), params.getSize()); + return zAiClient.executeRequest(request, supplier, QueryKnowledgeApiResponse.class); + } + + @Override + public KnowledgeEditResponse deleteKnowledge(String knowledgeId) { + AsyncResultRetrieveParams params = new AsyncResultRetrieveParams(knowledgeId); + RequestSupplier> supplier = (params1) -> knowledgeApi + .knowledgeDelete(params1.getTaskId()); + return zAiClient.executeRequest(params, supplier, KnowledgeEditResponse.class); + } + + @Override + public KnowledgeUsedResponse checkKnowledgeUsed() { + RequestSupplier supplier = (a) -> knowledgeApi.knowledgeUsed(); + return zAiClient.executeRequest(null, supplier, KnowledgeUsedResponse.class); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeStatistics.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeStatistics.java new file mode 100644 index 0000000..409a19d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeStatistics.java @@ -0,0 +1,76 @@ +package ai.z.openapi.service.knowledge; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeStatisticsDeserializer; + +import java.util.Iterator; + +/** + * This class represents the usage statistics of the knowledge base. + */ +@JsonDeserialize(using = KnowledgeStatisticsDeserializer.class) +public class KnowledgeStatistics extends ObjectNode { + + /** + * Statistical word count + */ + @JsonProperty("word_num") + private Integer wordNum; + + /** + * Length + */ + @JsonProperty("length") + private Integer length; + + public KnowledgeStatistics() { + super(JsonNodeFactory.instance); + } + + public KnowledgeStatistics(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + if (objectNode.has("word_num")) { + this.setWordNum(objectNode.get("word_num").asInt()); + } + else { + this.setWordNum(null); + } + if (objectNode.has("length")) { + this.setLength(objectNode.get("length").asInt()); + } + else { + this.setLength(null); + } + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + // Getters and Setters + + public Integer getWordNum() { + return wordNum; + } + + public void setWordNum(Integer wordNum) { + this.wordNum = wordNum; + this.put("word_num", wordNum); + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + this.put("length", length); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsed.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsed.java new file mode 100644 index 0000000..9998e82 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsed.java @@ -0,0 +1,80 @@ +package ai.z.openapi.service.knowledge; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.knowledge.KnowledgeUsedDeserializer; + +import java.util.Iterator; + +/** + * This class represents the usage information of the knowledge base. + */ +@JsonDeserialize(using = KnowledgeUsedDeserializer.class) +public class KnowledgeUsed extends ObjectNode { + + /** + * Used amount + */ + @JsonProperty("used") + private KnowledgeStatistics used; + + /** + * Total amount of knowledge base + */ + @JsonProperty("total") + private KnowledgeStatistics total; + + public KnowledgeUsed() { + super(JsonNodeFactory.instance); + } + + public KnowledgeUsed(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.has("used")) { + this.setUsed(objectMapper.convertValue(objectNode.get("used"), KnowledgeStatistics.class)); + } + else { + this.setUsed(null); + } + if (objectNode.has("total")) { + this.setTotal(objectMapper.convertValue(objectNode.get("total"), KnowledgeStatistics.class)); + } + else { + this.setTotal(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + // Getters and Setters + + public KnowledgeStatistics getUsed() { + return used; + } + + public void setUsed(KnowledgeStatistics used) { + this.used = used; + this.set("used", used); + } + + public KnowledgeStatistics getTotal() { + return total; + } + + public void setTotal(KnowledgeStatistics total) { + this.total = total; + this.set("total", total); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsedResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsedResponse.java new file mode 100644 index 0000000..eb4da94 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/KnowledgeUsedResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class KnowledgeUsedResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private KnowledgeUsed data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeApiResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeApiResponse.java new file mode 100644 index 0000000..391d577 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryKnowledgeApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private KnowledgePage data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeRequest.java b/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeRequest.java new file mode 100644 index 0000000..e70d294 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/QueryKnowledgeRequest.java @@ -0,0 +1,24 @@ +package ai.z.openapi.service.knowledge; + +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QueryKnowledgeRequest implements ClientRequest { + + private Integer page; + + private Integer size; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentCreateParams.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentCreateParams.java new file mode 100644 index 0000000..ef8b9c5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentCreateParams.java @@ -0,0 +1,67 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +/** + * This class represents the parameters required for file creation and upload. + */ +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class DocumentCreateParams extends CommonRequest implements ClientRequest { + + /** + * local file + */ + private String filePath; + + /** + * File and upload_detail are mutually exclusive and one is required. + */ + @JsonProperty("upload_detail") + private List uploadDetail; + + /** + * The purpose of uploading the file. Supported values: "fine-tune", "retrieval", + * "batch". + *

    + *
  • For "retrieval", the supported file types are Doc, Docx, PDF, Xlsx, and URL, + * and the maximum file size is 5MB.
  • + *
  • For "fine-tune", the supported file type is .jsonl, and the maximum file size + * is 100MB.
  • + *
+ */ + @JsonProperty("purpose") + private String purpose; + + /** + * Custom separator list. When the purpose is "retrieval" and the file type is pdf, + * url, or docx, upload with the default slicing rule as `\n`. + */ + @JsonProperty("custom_separator") + private List customSeparator; + + /** + * Knowledge Base ID. Required when the file upload purpose is "retrieval". + */ + @JsonProperty("knowledge_id") + private String knowledgeId; + + /** + * Sentence size. Required when the file upload purpose is "retrieval". + */ + @JsonProperty("sentence_size") + private Integer sentenceSize; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentData.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentData.java new file mode 100644 index 0000000..60e40fa --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentData.java @@ -0,0 +1,245 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentDataDeserializer; + +import java.util.Iterator; +import java.util.List; + +/** + * This class represents the document data, including metadata and processing status. + */ +@JsonDeserialize(using = DocumentDataDeserializer.class) +public class DocumentData extends ObjectNode { + + /** + * Knowledge unique ID + */ + @JsonProperty("id") + private String id; + + /** + * Segmentation rules + */ + @JsonProperty("custom_separator") + private List customSeparator; + + /** + * Segment size + */ + @JsonProperty("sentence_size") + private String sentenceSize; + + /** + * File size (bytes) + */ + @JsonProperty("length") + private Integer length; + + /** + * File word count + */ + @JsonProperty("word_num") + private Integer wordNum; + + /** + * File name + */ + @JsonProperty("name") + private String name; + + /** + * File download link + */ + @JsonProperty("url") + private String url; + + /** + * Vectorization status 0: Vectorizing 1: Vectorization completed 2: Vectorization + * failed + */ + @JsonProperty("embedding_stat") + private Integer embeddingStat; + + /** + * Failure reason, present when vectorization fails + */ + @JsonProperty("failInfo") + private DocumentDataFailInfo failInfo; + + public DocumentData() { + super(JsonNodeFactory.instance); + } + + public DocumentData(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode == null) { + return; + } + if (objectNode.get("id") != null) { + this.setId(objectNode.get("id").asText()); + } + else { + this.setId(null); + } + if (objectNode.get("custom_separator") != null) { + List customSeparator = objectNode.findValuesAsText("custom_separator"); + this.setCustomSeparator(customSeparator); + } + else { + this.setCustomSeparator(null); + } + if (objectNode.get("sentence_size") != null) { + this.setSentenceSize(objectNode.get("sentence_size").asText()); + } + else { + this.setSentenceSize(null); + } + if (objectNode.get("length") != null) { + this.setLength(objectNode.get("length").asInt()); + } + else { + this.setLength(null); + } + if (objectNode.get("word_num") != null) { + this.setWordNum(objectNode.get("word_num").asInt()); + } + else { + this.setWordNum(null); + } + if (objectNode.get("name") != null) { + this.setName(objectNode.get("name").asText()); + } + else { + this.setName(null); + } + if (objectNode.get("url") != null) { + this.setUrl(objectNode.get("url").asText()); + } + else { + this.setUrl(null); + } + if (objectNode.get("embedding_stat") != null) { + this.setEmbeddingStat(objectNode.get("embedding_stat").asInt()); + } + else { + this.setEmbeddingStat(null); + } + + if (objectNode.get("failInfo") != null) { + DocumentDataFailInfo failInfo = objectMapper.convertValue(objectNode.get("failInfo"), + DocumentDataFailInfo.class); + this.setFailInfo(failInfo); + } + else { + this.setFailInfo(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + this.put("id", id); + } + + public List getCustomSeparator() { + return customSeparator; + } + + public void setCustomSeparator(List customSeparator) { + this.customSeparator = customSeparator; + ArrayNode jsonNodes = this.putArray("customSeparator"); + if (customSeparator == null) { + jsonNodes.removeAll(); + } + else { + for (String item : customSeparator) { + jsonNodes.add(item); + } + } + } + + public String getSentenceSize() { + return sentenceSize; + } + + public void setSentenceSize(String sentenceSize) { + this.sentenceSize = sentenceSize; + this.put("sentenceSize", sentenceSize); + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + this.put("length", length); + } + + public Integer getWordNum() { + return wordNum; + } + + public void setWordNum(Integer wordNum) { + this.wordNum = wordNum; + this.put("wordNum", wordNum); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + this.put("name", name); + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + this.put("url", url); + } + + public Integer getEmbeddingStat() { + return embeddingStat; + } + + public void setEmbeddingStat(Integer embeddingStat) { + this.embeddingStat = embeddingStat; + this.put("embeddingStat", embeddingStat); + } + + public DocumentDataFailInfo getFailInfo() { + return failInfo; + } + + public void setFailInfo(DocumentDataFailInfo failInfo) { + this.failInfo = failInfo; + this.putPOJO("failInfo", failInfo); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataFailInfo.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataFailInfo.java new file mode 100644 index 0000000..4ffd90d --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataFailInfo.java @@ -0,0 +1,80 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentDataFailInfoDeserializer; + +import java.util.Iterator; + +/** + * This class represents the failure information of document data processing. + */ +@JsonDeserialize(using = DocumentDataFailInfoDeserializer.class) +public class DocumentDataFailInfo extends ObjectNode { + + /** + * Failure code 10001: Knowledge unavailable, knowledge base space has reached the + * limit 10002: Knowledge unavailable, knowledge base space has reached the limit + * (word count exceeds limit) + */ + @JsonProperty("embedding_code") + private Integer embeddingCode; + + /** + * Failure reason + */ + @JsonProperty("embedding_msg") + private String embeddingMsg; + + public DocumentDataFailInfo() { + super(JsonNodeFactory.instance); + } + + public DocumentDataFailInfo(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + if (objectNode.get("embedding_code") != null) { + this.setEmbeddingCode(objectNode.get("embedding_code").asInt()); + } + else { + this.setEmbeddingCode(null); + } + + if (objectNode.get("embedding_msg") != null) { + this.setEmbeddingMsg(objectNode.get("embedding_msg").asText()); + + } + else { + this.setEmbeddingMsg(null); + } + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public Integer getEmbeddingCode() { + return embeddingCode; + } + + public void setEmbeddingCode(Integer embeddingCode) { + this.embeddingCode = embeddingCode; + this.put("embedding_code", embeddingCode); + } + + public String getEmbeddingMsg() { + return embeddingMsg; + } + + public void setEmbeddingMsg(String embeddingMsg) { + this.embeddingMsg = embeddingMsg; + this.put("embedding_msg", embeddingMsg); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataResponse.java new file mode 100644 index 0000000..d35d6bb --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentDataResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge.document; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class DocumentDataResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private DocumentData data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditParams.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditParams.java new file mode 100644 index 0000000..1852f6f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditParams.java @@ -0,0 +1,118 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; +import java.util.Map; + +/** + * This class represents the parameters for editing a document in the knowledge base. + */ +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class DocumentEditParams implements ClientRequest { + + /** + * Knowledge ID + */ + @JsonProperty("id") + private String id; + + /** + * Knowledge type: + *
    + *
  • 1: Article knowledge: supports pdf, url, docx
  • + *
  • 2: Q&A knowledge-document: supports pdf, url, docx
  • + *
  • 3: Q&A knowledge-table: supports xlsx
  • + *
  • 4: Product library-table: supports xlsx
  • + *
  • 5: Custom: supports pdf, url, docx
  • + *
+ */ + @JsonProperty("knowledge_type") + private Integer knowledgeType; + + /** + * Chunk rules when knowledge type is custom (knowledge_type=5), default \n + */ + @JsonProperty("custom_separator") + private List customSeparator; + + /** + * Chunk word count when knowledge type is custom (knowledge_type=5), range: 20-2000, + * default 300 + */ + @JsonProperty("sentence_size") + private Integer sentenceSize; + + /** + * Callback URL + */ + @JsonProperty("callback_url") + private String callbackUrl; + + /** + * Headers to carry during callback + */ + @JsonProperty("callback_header") + private Map callbackHeader; + + // Getters and Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getKnowledgeType() { + return knowledgeType; + } + + public void setKnowledgeType(Integer knowledgeType) { + this.knowledgeType = knowledgeType; + } + + public List getCustomSeparator() { + return customSeparator; + } + + public void setCustomSeparator(List customSeparator) { + this.customSeparator = customSeparator; + } + + public Integer getSentenceSize() { + return sentenceSize; + } + + public void setSentenceSize(Integer sentenceSize) { + this.sentenceSize = sentenceSize; + } + + public String getCallbackUrl() { + return callbackUrl; + } + + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + public Map getCallbackHeader() { + return callbackHeader; + } + + public void setCallbackHeader(Map callbackHeader) { + this.callbackHeader = callbackHeader; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditResponse.java new file mode 100644 index 0000000..40ea76f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentEditResponse.java @@ -0,0 +1,21 @@ +package ai.z.openapi.service.knowledge.document; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; +import retrofit2.Response; + +@Data +public class DocumentEditResponse implements ClientResponse> { + + private int code; + + private String msg; + + private boolean success; + + private Response data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentFailedInfo.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentFailedInfo.java new file mode 100644 index 0000000..b66aba8 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentFailedInfo.java @@ -0,0 +1,104 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentFailedInfoDeserializer; + +import java.util.Iterator; + +/** + * This class represents the information of a failed document upload. + */ +@JsonDeserialize(using = DocumentFailedInfoDeserializer.class) +public class DocumentFailedInfo extends ObjectNode { + + /** + * Reason for upload failure, including: unsupported file format, file size exceeds + * limit, knowledge base capacity is full, capacity limit is 500,000 words. + */ + @JsonProperty("failReason") + private String failReason; + + /** + * File name + */ + @JsonProperty("filename") + private String filename; + + /** + * Knowledge base ID + */ + @JsonProperty("documentId") + private String documentId; + + public DocumentFailedInfo() { + super(JsonNodeFactory.instance); + } + + public DocumentFailedInfo(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + if (objectNode == null) { + return; + } + + if (objectNode.has("failReason")) { + this.setFailReason(objectNode.get("failReason").asText()); + } + else { + this.setFailReason(null); + } + if (objectNode.has("filename")) { + this.setFilename(objectNode.get("filename").asText()); + } + else { + this.setFilename(null); + } + + if (objectNode.has("documentId")) { + this.setDocumentId(objectNode.get("documentId").asText()); + } + else { + this.setDocumentId(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + + } + // Getters and Setters + + public String getFailReason() { + return failReason; + } + + public void setFailReason(String failReason) { + this.failReason = failReason; + this.put("failReason", failReason); + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + this.put("filename", filename); + } + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + this.put("documentId", documentId); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObject.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObject.java new file mode 100644 index 0000000..f2099d9 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObject.java @@ -0,0 +1,108 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentObjectDeserializer; + +import java.util.Iterator; +import java.util.List; + +/** + * This class represents the document information including success and failure details. + */ +@JsonDeserialize(using = DocumentObjectDeserializer.class) +public class DocumentObject extends ObjectNode { + + /** + * Information of successfully uploaded files + */ + @JsonProperty("successInfos") + private List successInfos; + + /** + * Information of failed uploaded files + */ + @JsonProperty("failedInfos") + private List failedInfos; + + public DocumentObject() { + super(JsonNodeFactory.instance); + } + + public DocumentObject(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("successInfos") != null) { + List successInfos = objectMapper.convertValue(objectNode.get("successInfos"), + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + + this.setSuccessInfos(successInfos); + } + else { + this.setSuccessInfos(null); + } + if (objectNode.get("failedInfos") != null) { + List failedInfos = objectMapper.convertValue(objectNode.get("failedInfos"), + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + + this.setFailedInfos(failedInfos); + } + else { + this.setFailedInfos(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public List getSuccessInfos() { + return successInfos; + } + + public void setSuccessInfos(List successInfos) { + this.successInfos = successInfos; + ArrayNode jsonNodes = this.putArray("successInfos"); + if (successInfos == null) { + jsonNodes.removeAll(); + } + else { + + for (DocumentSuccessInfo successInfo : successInfos) { + jsonNodes.add(successInfo); + } + } + } + + public List getFailedInfos() { + return failedInfos; + } + + public void setFailedInfos(List failedInfos) { + this.failedInfos = failedInfos; + ArrayNode jsonNodes = this.putArray("failedInfos"); + if (successInfos == null) { + jsonNodes.removeAll(); + } + else { + + for (DocumentFailedInfo failedInfo : failedInfos) { + jsonNodes.add(failedInfo); + } + } + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObjectResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObjectResponse.java new file mode 100644 index 0000000..6ae6307 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentObjectResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge.document; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class DocumentObjectResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private DocumentObject data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentPage.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentPage.java new file mode 100644 index 0000000..d02a10a --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentPage.java @@ -0,0 +1,96 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentPageDeserializer; + +import java.util.Iterator; +import java.util.List; + +/** + * This class represents a page of document data, including a list of document entries and + * the object type. + */ +@JsonDeserialize(using = DocumentPageDeserializer.class) +public class DocumentPage extends ObjectNode { + + /** + * List of document data entries. + */ + @JsonProperty("list") + private List list; + + /** + * The object type. + */ + @JsonProperty("object") + private String object; + + public DocumentPage() { + super(JsonNodeFactory.instance); + } + + public DocumentPage(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("list") != null) { + List list = objectMapper.convertValue(objectNode.get("list"), + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + + this.setList(list); + } + else { + this.setList(null); + } + if (objectNode.get("object") != null) { + this.setObject(objectNode.get("object").asText()); + } + else { + this.setObject(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + // Getters and Setters + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + ArrayNode jsonNodes = this.putArray("list"); + if (list == null) { + jsonNodes.removeAll(); + } + else { + + for (DocumentData documentData : list) { + jsonNodes.add(documentData); + } + } + } + + public String getObject() { + return object; + } + + public void setObject(String object) { + this.object = object; + this.put("object", object); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentSuccessInfo.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentSuccessInfo.java new file mode 100644 index 0000000..3e72f89 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/DocumentSuccessInfo.java @@ -0,0 +1,77 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.knowledge.document.DocumentSuccessInfoDeserializer; + +import java.util.Iterator; + +/** + * This class represents the information of a successfully uploaded document. + */ +@JsonDeserialize(using = DocumentSuccessInfoDeserializer.class) +public class DocumentSuccessInfo extends ObjectNode { + + /** + * File ID + */ + @JsonProperty("documentId") + private String documentId; + + /** + * File name + */ + @JsonProperty("filename") + private String filename; + + public DocumentSuccessInfo() { + super(JsonNodeFactory.instance); + } + + public DocumentSuccessInfo(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + if (objectNode == null) { + return; + } + if (objectNode.has("documentId")) { + this.setDocumentId(objectNode.get("documentId").asText()); + } + else { + this.setDocumentId(null); + } + if (objectNode.has("filename")) { + this.setFilename(objectNode.get("filename").asText()); + } + else { + this.setFilename(null); + } + Iterator fieldNames = objectNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + this.put("documentId", documentId); + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + this.put("filename", filename); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentApiResponse.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentApiResponse.java new file mode 100644 index 0000000..65c3c7f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentApiResponse.java @@ -0,0 +1,20 @@ +package ai.z.openapi.service.knowledge.document; + +import ai.z.openapi.core.model.ClientResponse; +import ai.z.openapi.service.model.ChatError; +import lombok.Data; + +@Data +public class QueryDocumentApiResponse implements ClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private DocumentPage data; + + private ChatError error; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentRequest.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentRequest.java new file mode 100644 index 0000000..5c3567b --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/QueryDocumentRequest.java @@ -0,0 +1,32 @@ +package ai.z.openapi.service.knowledge.document; + +import ai.z.openapi.core.model.ClientRequest; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.Map; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class QueryDocumentRequest implements ClientRequest { + + @JsonProperty("knowledge_id") + private String knowledgeId; + + private String purpose; + + private Integer page; + + private Integer limit; + + private String order; + +} diff --git a/core/src/main/java/ai/z/openapi/service/knowledge/document/UploadDetail.java b/core/src/main/java/ai/z/openapi/service/knowledge/document/UploadDetail.java new file mode 100644 index 0000000..1ac5677 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/knowledge/document/UploadDetail.java @@ -0,0 +1,112 @@ +package ai.z.openapi.service.knowledge.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +/** + * This class represents the details required for uploading a file to the knowledge base. + */ +public class UploadDetail { + + /** + * URL of the file to be uploaded. + */ + @JsonProperty("url") + private String url; + + /** + * Knowledge type identifier. + */ + @JsonProperty("knowledge_type") + private int knowledgeType; + + /** + * Optional file name. + */ + @JsonProperty("file_name") + private String fileName; + + /** + * Optional sentence size for processing. + */ + @JsonProperty("sentence_size") + private Integer sentenceSize; + + /** + * Optional list of custom separators. + */ + @JsonProperty("custom_separator") + private List customSeparator; + + /** + * Optional callback URL for notifications. + */ + @JsonProperty("callback_url") + private String callbackUrl; + + /** + * Optional callback headers for the callback request. + */ + @JsonProperty("callback_header") + private Map callbackHeader; + + // Getters and Setters + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getKnowledgeType() { + return knowledgeType; + } + + public void setKnowledgeType(int knowledgeType) { + this.knowledgeType = knowledgeType; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Integer getSentenceSize() { + return sentenceSize; + } + + public void setSentenceSize(Integer sentenceSize) { + this.sentenceSize = sentenceSize; + } + + public List getCustomSeparator() { + return customSeparator; + } + + public void setCustomSeparator(List customSeparator) { + this.customSeparator = customSeparator; + } + + public String getCallbackUrl() { + return callbackUrl; + } + + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + public Map getCallbackHeader() { + return callbackHeader; + } + + public void setCallbackHeader(Map callbackHeader) { + this.callbackHeader = callbackHeader; + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/model/AsyncResultRetrieveParams.java b/core/src/main/java/ai/z/openapi/service/model/AsyncResultRetrieveParams.java new file mode 100644 index 0000000..c75090f --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/AsyncResultRetrieveParams.java @@ -0,0 +1,19 @@ +package ai.z.openapi.service.model; + +import ai.z.openapi.core.model.ClientRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@EqualsAndHashCode(callSuper = false) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AsyncResultRetrieveParams implements ClientRequest { + + private String taskId; + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/Audio.java b/core/src/main/java/ai/z/openapi/service/model/Audio.java new file mode 100644 index 0000000..03c499c --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/Audio.java @@ -0,0 +1,14 @@ +package ai.z.openapi.service.model; + +import lombok.Data; + +@Data +public class Audio { + + private String id; + + private String data; + + private Long expires_at; + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatCompletionCreateParams.java b/core/src/main/java/ai/z/openapi/service/model/ChatCompletionCreateParams.java new file mode 100644 index 0000000..098ac22 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatCompletionCreateParams.java @@ -0,0 +1,110 @@ +package ai.z.openapi.service.model; + +import ai.z.openapi.core.model.ClientRequest; +import ai.z.openapi.service.CommonRequest; +import ai.z.openapi.service.model.params.CodeGeexExtra; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.experimental.SuperBuilder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ChatCompletionCreateParams extends CommonRequest implements ClientRequest { + + /** + * Model code to call + */ + private String model; + + /** + * When calling the language model, pass the current conversation information list as + * prompt input to the model, in JSON array format like {"role": "user", "content": + * "hello"}; possible message types include System message, User message, Assistant + * message and Tool message. See message field description below + */ + private List messages; + + /** + * When do_sample is true, sampling strategy is enabled; when do_sample is false, + * sampling strategies temperature and top_p will not take effect + */ + @JsonProperty("do_sample") + private Boolean doSample; + + /** + * Synchronous call: false, SSE call: true + */ + private Boolean stream; + + /** + * Sampling temperature, controls output randomness, must be positive Range: + * (0.0,1.0], cannot equal 0, default value is 0.95 Higher values make output more + * random and creative; lower values make output more stable or deterministic It's + * recommended to adjust either top_p or temperature parameter based on your use case, + * but not both simultaneously + */ + private Float temperature; + + /** + * Another method for temperature sampling, called nucleus sampling Range: (0.0, 1.0) + * open interval, cannot equal 0 or 1, default value is 0.7 Model considers results + * with top_p probability mass tokens For example: 0.1 means the model decoder only + * considers tokens from the top 10% probability candidate set It's recommended to + * adjust either top_p or temperature parameter based on your use case, but not both + * simultaneously + */ + @JsonProperty("top_p") + private Float topP; + + /** + * Maximum output tokens for the model + */ + @JsonProperty("max_tokens") + private Integer maxTokens; + + /** + * Model will stop generating when encountering characters specified by stop, + * currently only supports single stop word format + */ + private List stop; + + /** + * Sensitive word detection control + */ + @JsonProperty("sensitive_word_check") + private SensitiveWordCheckRequest sensitiveWordCheck; + + /** + * List of tools available for model invocation The tools field will count tokens and + * is also subject to token length limitations: 1. The sum of the length of the last + * message in messages, system message length, and functions field length cannot + * exceed 4000 tokens. Otherwise, the conversation cannot be completed. 2. When the + * total length of messages exceeds the token limit, the system will sequentially + * forget the earliest historical conversations up to 4000 tokens + */ + private List tools; + + /** + * Conversation metadata + */ + private ChatMeta meta; + + /** + * Conversation metadata + */ + private CodeGeexExtra extra; + + /** + * Specify calling a specific function + */ + @JsonProperty("tool_choice") + private Object toolChoice; + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatCompletionResponse.java b/core/src/main/java/ai/z/openapi/service/model/ChatCompletionResponse.java new file mode 100644 index 0000000..141a632 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatCompletionResponse.java @@ -0,0 +1,30 @@ +package ai.z.openapi.service.model; + +import ai.z.openapi.core.model.FlowableClientResponse; +import io.reactivex.Flowable; +import lombok.Data; + +@Data +public class ChatCompletionResponse implements FlowableClientResponse { + + private int code; + + private String msg; + + private boolean success; + + private ModelData data; + + private Flowable flowable; + + private ChatError error; + + public ChatCompletionResponse() { + } + + public ChatCompletionResponse(int code, String msg) { + this.code = code; + this.msg = msg; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatError.java b/core/src/main/java/ai/z/openapi/service/model/ChatError.java new file mode 100644 index 0000000..fc6a3f5 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatError.java @@ -0,0 +1,26 @@ +package ai.z.openapi.service.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Represents an error that occurred during API operations. Contains error code and + * descriptive message for troubleshooting. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChatError { + + /** + * Error code indicating the type of error. + */ + private Integer code; + + /** + * Descriptive error message. + */ + private String message; + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatFunction.java b/core/src/main/java/ai/z/openapi/service/model/ChatFunction.java new file mode 100644 index 0000000..1b225d7 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatFunction.java @@ -0,0 +1,95 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; + +import java.util.List; +import java.util.Map; + +@Getter +public class ChatFunction extends ObjectNode { + + private String name; + + private String description; + + private ChatFunctionParameters parameters; + + private List required; + + public ChatFunction() { + super(JsonNodeFactory.instance); + } + + public ChatFunction(JsonNodeFactory nc, Map kids) { + super(nc, kids); + } + + public void setName(String name) { + this.name = name; + this.put("name", name); + } + + public void setDescription(String description) { + this.description = description; + this.put("description", description); + } + + public void setParameters(ChatFunctionParameters parameters) { + this.parameters = parameters; + this.set("parameters", parameters); + } + + public void setRequired(List required) { + this.required = required; + this.putPOJO("required", required); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + private String description; + + private ChatFunctionParameters parameters; + + private List required; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder parameters(ChatFunctionParameters parameters) { + this.parameters = parameters; + return this; + } + + public Builder required(List required) { + this.required = required; + return this; + } + + public ChatFunction build() { + ChatFunction chatFunction = new ChatFunction(); + chatFunction.setName(name); + chatFunction.setDescription(description); + chatFunction.setParameters(parameters); + chatFunction.setRequired(required); + return chatFunction; + } + + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatFunctionCall.java b/core/src/main/java/ai/z/openapi/service/model/ChatFunctionCall.java new file mode 100644 index 0000000..577f391 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatFunctionCall.java @@ -0,0 +1,76 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.ChatFunctionCallDeserializer; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import lombok.Getter; + +import java.util.Iterator; + +/** + * Represents a function call generated by the chat model. This class contains the + * function name and arguments that the model wants to invoke. + */ +@Getter +@JsonDeserialize(using = ChatFunctionCallDeserializer.class) +public class ChatFunctionCall extends ObjectNode { + + /** + * Name of the function that the model wants to call. + */ + String name; + + /** + * JSON-formatted function call arguments generated by the model. Note that the JSON + * generated by the model is not always valid and may contain parameters not defined + * in the function schema. Please validate the arguments in your code before calling + * the function. + */ + JsonNode arguments; + + public ChatFunctionCall() { + super(JsonNodeFactory.instance); + } + + public ChatFunctionCall(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("name") != null) { + this.setName(objectNode.get("name").asText()); + } + else { + this.setName(null); + } + if (objectNode.get("arguments") != null) { + this.setArguments(objectNode.get("arguments")); + } + else { + this.setArguments(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + + } + + public void setName(String name) { + this.name = name; + this.put("name", name); + } + + public void setArguments(JsonNode arguments) { + this.arguments = arguments; + this.set("arguments", arguments); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatFunctionParameters.java b/core/src/main/java/ai/z/openapi/service/model/ChatFunctionParameters.java new file mode 100644 index 0000000..366c844 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatFunctionParameters.java @@ -0,0 +1,64 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Getter +public class ChatFunctionParameters extends ObjectNode { + + private String type; + + private JsonNode properties; + + private List required; + + public ChatFunctionParameters() { + super(JsonNodeFactory.instance); + } + + public ChatFunctionParameters(JsonNodeFactory nc, Map kids) { + super(nc, kids); + } + + public void setType(String type) { + this.type = type; + this.put("type", type); + } + + public void setProperties(Object properties) { + if (properties instanceof JsonNode) { + this.properties = (JsonNode) properties; + } + else if (properties instanceof Map) { + Map map = (Map) properties; + JsonNode jsonNode = JsonNodeFactory.instance.objectNode(); + map.forEach((k, v) -> { + if (v instanceof JsonNode) { + ((ObjectNode) jsonNode).set(k, (JsonNode) v); + } + else { + ((ObjectNode) jsonNode).putPOJO(k, v); + } + }); + this.properties = jsonNode; + } + else { + throw new IllegalArgumentException("properties must be a Map or JsonNode"); + } + this.putPOJO("properties", properties); + } + + public void setRequired(List required) { + this.required = required; + this.putPOJO("required", required); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatMessage.java b/core/src/main/java/ai/z/openapi/service/model/ChatMessage.java new file mode 100644 index 0000000..673ce00 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatMessage.java @@ -0,0 +1,131 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.ChatMessageDeserializer; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import lombok.*; + +import java.util.Iterator; +import java.util.List; + +@Getter +@JsonDeserialize(using = ChatMessageDeserializer.class) +public class ChatMessage extends ObjectNode { + + private String role; + + private Object content; + + private Audio audio; + + private String name; + + @JsonProperty("tool_calls") + private List tool_calls; + + private String tool_call_id; + + public ChatMessage() { + super(JsonNodeFactory.instance); + } + + public ChatMessage(String role, Object content) { + + super(JsonNodeFactory.instance); + this.setRole(role); + this.setContent(content); + } + + public ChatMessage(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("role") != null) { + this.setRole(objectNode.get("role").asText()); + } + else { + this.setRole(null); + } + if (objectNode.get("content") != null) { + this.setContent(objectNode.get("content").asText()); + } + else { + this.setContent(null); + } + if (objectNode.get("name") != null) { + this.setName(objectNode.get("name").asText()); + } + else { + this.setName(null); + } + if (objectNode.get("tool_calls") != null) { + this.setTool_calls(objectMapper.convertValue(objectNode.get("tool_calls"), + new com.fasterxml.jackson.core.type.TypeReference>() { + })); + } + else { + this.setTool_calls(null); + } + if (objectNode.get("tool_call_id") != null) { + this.setTool_call_id(objectNode.get("tool_call_id").asText()); + } + else { + this.setTool_call_id(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + + } + + public void setRole(String role) { + this.role = role; + this.put("role", role); + } + + public void setContent(Object content) { + this.content = content; + this.putPOJO("content", content); + } + + public void setName(String name) { + this.name = name; + this.put("name", name); + } + + public void setTool_calls(List tool_calls) { + this.tool_calls = tool_calls; + ArrayNode toolCalls = this.putArray("tool_calls"); + if (tool_calls == null) { + toolCalls.removeAll(); + } + else { + + for (ToolCalls toolCall : tool_calls) { + toolCalls.add(toolCall); + } + } + } + + public void setTool_call_id(String tool_call_id) { + this.tool_call_id = tool_call_id; + this.put("tool_call_id", tool_call_id); + } + + public void setAudio(Audio audio) { + this.audio = audio; + this.putPOJO("audio", audio); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatMessageAccumulator.java b/core/src/main/java/ai/z/openapi/service/model/ChatMessageAccumulator.java new file mode 100644 index 0000000..4be3e9e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatMessageAccumulator.java @@ -0,0 +1,57 @@ +package ai.z.openapi.service.model; + +/** + * Class that accumulates chat messages and provides utility methods for handling message + * chunks and function calls within a chat stream. This class is immutable. + * + * @author [Your Name] + */ +public class ChatMessageAccumulator { + + private final ChatMessage accumulatedMessage; + + private final Delta delta; + + private final Choice choice; + + private final Usage usage; + + private final Long created; + + private final String id; + + public ChatMessageAccumulator(Delta delta, ChatMessage accumulatedMessage, Choice choice, Usage usage, Long created, + String id) { + this.delta = delta; + this.accumulatedMessage = accumulatedMessage; + this.choice = choice; + this.usage = usage; + this.created = created; + this.id = id; + } + + public Delta getDelta() { + return delta; + } + + public ChatMessage getAccumulatedMessage() { + return accumulatedMessage; + } + + public Choice getChoice() { + return choice; + } + + public Usage getUsage() { + return usage; + } + + public String getId() { + return id; + } + + public Long getCreated() { + return created; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatMessageRole.java b/core/src/main/java/ai/z/openapi/service/model/ChatMessageRole.java new file mode 100644 index 0000000..c0bfca4 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatMessageRole.java @@ -0,0 +1,17 @@ +package ai.z.openapi.service.model; + +public enum ChatMessageRole { + + SYSTEM("system"), USER("user"), ASSISTANT("assistant"), FUNCTION("function"); + + private final String value; + + ChatMessageRole(final String value) { + this.value = value; + } + + public String value() { + return value; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatMeta.java b/core/src/main/java/ai/z/openapi/service/model/ChatMeta.java new file mode 100644 index 0000000..82ed64e --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatMeta.java @@ -0,0 +1,33 @@ +package ai.z.openapi.service.model; + +import lombok.Data; + +/** + * Meta information for hyper-realistic conversations. Contains role and user information + * data where: - user_info: user information - bot_info: role/bot information - bot_name: + * role/bot name - user_name: user name + */ +@Data +public class ChatMeta { + + /** + * User information. + */ + private String user_info; + + /** + * Bot/role information. + */ + private String bot_info; + + /** + * Bot/role name. + */ + private String bot_name; + + /** + * User name. + */ + private String user_name; + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatTool.java b/core/src/main/java/ai/z/openapi/service/model/ChatTool.java new file mode 100644 index 0000000..f3772e8 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatTool.java @@ -0,0 +1,51 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.*; + +import java.util.Map; + +@Getter +public class ChatTool extends ObjectNode { + + private String type; + + private ChatFunction function; + + private Retrieval retrieval; + + @JsonProperty("web_search") + private WebSearch web_search; + + public ChatTool() { + super(JsonNodeFactory.instance); + } + + public ChatTool(JsonNodeFactory nc, Map kids) { + super(nc, kids); + } + + public void setType(String type) { + this.type = type; + this.put("type", type); + } + + public void setFunction(ChatFunction function) { + this.function = function; + this.putPOJO("function", function); + } + + public void setRetrieval(Retrieval retrieval) { + this.retrieval = retrieval; + this.putPOJO("retrieval", retrieval); + } + + public void setWeb_search(WebSearch web_search) { + this.web_search = web_search; + this.putPOJO("web_search", web_search); + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatToolType.java b/core/src/main/java/ai/z/openapi/service/model/ChatToolType.java new file mode 100644 index 0000000..4ce0692 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatToolType.java @@ -0,0 +1,21 @@ +package ai.z.openapi.service.model; + +public enum ChatToolType { + + WEB_SEARCH("web_search"), + + RETRIEVAL("retrieval"), + + FUNCTION("function"); + + private final String value; + + ChatToolType(final String value) { + this.value = value; + } + + public String value() { + return value; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/Choice.java b/core/src/main/java/ai/z/openapi/service/model/Choice.java new file mode 100644 index 0000000..6bcec95 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/Choice.java @@ -0,0 +1,93 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.ChoiceDeserializer; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import lombok.Getter; + +import java.util.Iterator; + +@Getter +@JsonDeserialize(using = ChoiceDeserializer.class) +public class Choice extends ObjectNode { + + @JsonProperty("finish_reason") + private String finishReason; + + @JsonProperty("index") + private Long index; + + @JsonProperty("message") + private ChatMessage message; + + @JsonProperty("delta") + private Delta delta; + + public Choice() { + super(JsonNodeFactory.instance); + } + + public Choice(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("finish_reason") != null) { + this.setFinishReason(objectNode.get("finish_reason").asText()); + } + else { + this.setFinishReason(null); + } + if (objectNode.get("index") != null) { + this.setIndex(objectNode.get("index").asLong()); + } + else { + this.setIndex(null); + } + if (objectNode.get("message") != null) { + this.setMessage(objectMapper.convertValue(objectNode.get("message"), ChatMessage.class)); + } + else { + this.setMessage(null); + } + if (objectNode.get("delta") != null) { + this.setDelta(objectMapper.convertValue(objectNode.get("delta"), Delta.class)); + } + else { + this.setDelta(null); + } + + Iterator fieldNames = objectNode.fieldNames(); + + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + + JsonNode field = objectNode.get(fieldName); + this.set(fieldName, field); + } + } + + public void setFinishReason(String finishReason) { + this.finishReason = finishReason; + this.put("finish_reason", finishReason); + } + + public void setIndex(Long index) { + this.index = index; + this.put("index", index); + } + + public void setMessage(ChatMessage message) { + this.message = message; + this.putPOJO("message", message); + } + + public void setDelta(Delta delta) { + this.delta = delta; + this.putPOJO("delta", delta); + } + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/service/model/Delta.java b/core/src/main/java/ai/z/openapi/service/model/Delta.java new file mode 100644 index 0000000..034fd02 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/Delta.java @@ -0,0 +1,102 @@ +package ai.z.openapi.service.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import ai.z.openapi.service.deserialize.DeltaDeserializer; +import ai.z.openapi.service.deserialize.MessageDeserializeFactory; +import lombok.*; + +import java.util.Iterator; +import java.util.List; + +@Getter +@JsonDeserialize(using = DeltaDeserializer.class) +public class Delta extends ObjectNode { + + private String role; + + private String content; + + @Setter + private Audio audio; + + @JsonProperty("tool_calls") + private List tool_calls; + + public Delta() { + super(JsonNodeFactory.instance); + } + + public Delta(ObjectNode objectNode) { + super(JsonNodeFactory.instance); + ObjectMapper objectMapper = MessageDeserializeFactory.defaultObjectMapper(); + if (objectNode.get("role") != null) { + this.setRole(objectNode.get("role").asText()); + } + else { + this.setRole(null); + } + if (objectNode.get("content") != null) { + this.setContent(objectNode.get("content").asText()); + } + else { + this.setContent(null); + } + if (objectNode.get("tool_calls") != null) { + this.setTool_calls( + objectMapper.convertValue(objectNode.get("tool_calls"), new TypeReference>() { + })); + } + else { + this.setTool_calls(null); + } + if (objectNode.get("audio") != null) { + Audio audio = objectMapper.convertValue(objectNode.get("audio"), new TypeReference