diff --git a/.gitignore b/.gitignore index 792704b..fe42722 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** +.DS_Store ### STS ### .apt_generated diff --git a/core/pom.xml b/core/pom.xml index cb6d4e3..45159ca 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -49,7 +49,7 @@ UTF-8 2.0.11 3.14.9 - 2.11.3 + 2.18.3 2.9.0 3.1.8 4.2.2 diff --git a/core/src/main/java/ai/z/openapi/AbstractAiClient.java b/core/src/main/java/ai/z/openapi/AbstractAiClient.java index 59dfce9..ae240d4 100644 --- a/core/src/main/java/ai/z/openapi/AbstractAiClient.java +++ b/core/src/main/java/ai/z/openapi/AbstractAiClient.java @@ -42,6 +42,7 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; import java.lang.reflect.InvocationTargetException; +import java.util.Map; import java.util.concurrent.TimeUnit; /** @@ -477,6 +478,19 @@ public abstract class AbstractAiClient extends AbstractClientBaseService { return self(); } + /** + * Config the custom headers + * @param customHeaders custom headers + * @return this Builder instance for method chaining + */ + public B customHeaders(Map customHeaders) { + if (customHeaders == null || customHeaders.isEmpty()) { + throw new IllegalArgumentException("Custom headers cannot be null or empty"); + } + config.setCustomHeaders(customHeaders); + return self(); + } + /** * Disables token caching, forcing the client to use API keys for direct requests. * This is the default behavior. 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 index ab80b4b..0359bea 100644 --- a/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java +++ b/core/src/main/java/ai/z/openapi/api/chat/ChatApi.java @@ -7,10 +7,13 @@ import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; +import retrofit2.http.HeaderMap; import retrofit2.http.POST; import retrofit2.http.Path; import retrofit2.http.Streaming; +import java.util.Map; + /** * Chat Completions API for advanced GLM-4 series models Provides synchronous, * asynchronous, and streaming chat completion capabilities Supports complex reasoning, @@ -36,6 +39,23 @@ public interface ChatApi { @POST("chat/completions") Call createChatCompletionStream(@Body ChatCompletionCreateParams request); + /** + * Create a streaming chat completion with custom headers support Returns response + * content incrementally through Server-Sent Events (SSE) for immediate user feedback + * Optimized for interactive applications requiring low latency and progressive + * content delivery Supports all GLM-4 models with configurable streaming parameters + * and custom HTTP headers + * @param request Chat completion parameters including model selection, messages, and + * streaming settings + * @param headers Custom HTTP headers to be added to the request + * @return Streaming response body with incremental content, usage statistics, and + * completion indicators + */ + @Streaming + @POST("chat/completions") + Call createChatCompletionStream(@Body ChatCompletionCreateParams request, + @HeaderMap Map headers); + /** * Create an asynchronous chat completion for long-running tasks Submits the request * and returns immediately with a task ID for later result retrieval Ideal for complex @@ -64,6 +84,22 @@ public interface ChatApi { @POST("chat/completions") Single createChatCompletion(@Body ChatCompletionCreateParams request); + /** + * Create a synchronous chat completion with custom headers support Waits for the + * GLM-4 model to complete execution and returns the final result with custom HTTP + * headers Supports complex reasoning, tool calling, function execution, and + * multi-modal understanding Features advanced capabilities like web search + * integration, code interpretation, and image analysis + * @param request Chat completion parameters including model selection, conversation + * messages, generation settings, tools configuration, and response format + * @param headers Custom HTTP headers to be added to the request + * @return Complete chat completion response with generated content, usage statistics, + * tool call results, and reasoning traces + */ + @POST("chat/completions") + Single createChatCompletion(@Body ChatCompletionCreateParams request, + @HeaderMap Map headers); + /** * Query the result of an asynchronous chat completion task Retrieves the completion * result or current status using the task ID from async request Provides detailed diff --git a/core/src/main/java/ai/z/openapi/core/Constants.java b/core/src/main/java/ai/z/openapi/core/Constants.java index 34aeef6..886a103 100644 --- a/core/src/main/java/ai/z/openapi/core/Constants.java +++ b/core/src/main/java/ai/z/openapi/core/Constants.java @@ -260,6 +260,11 @@ public final class Constants { */ public static final String ModelCogVideoX2 = "cogvideox-2"; + /** + * CogVideoX-3 model + */ + public static final String ModelCogVideoX3 = "cogvideox-3"; + /** * Vidu Q1 Text model - High-performance video generation from text input. Supports * general and anime styles. 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 index ad6b8f0..b2f7d08 100644 --- a/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java +++ b/core/src/main/java/ai/z/openapi/core/config/ZaiConfig.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.NoArgsConstructor; import lombok.Setter; +import java.util.Map; import java.util.concurrent.TimeUnit; import static ai.z.openapi.core.Constants.Z_AI_BASE_URL; @@ -63,6 +64,11 @@ public class ZaiConfig { */ private String apiSecret; + /** + * Custom Http Request Headers + */ + private Map customHeaders; + /** * JWT token expiration time in milliseconds (default: 30 minutes). */ @@ -408,4 +414,12 @@ public class ZaiConfig { return source_channel; } + /** + * Get custom headers + * @return + */ + public Map getCustomHeaders() { + return customHeaders; + } + } diff --git a/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java b/core/src/main/java/ai/z/openapi/core/token/HttpRequestInterceptor.java similarity index 65% rename from core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java rename to core/src/main/java/ai/z/openapi/core/token/HttpRequestInterceptor.java index 322da34..ba3f550 100644 --- a/core/src/main/java/ai/z/openapi/core/token/AuthenticationInterceptor.java +++ b/core/src/main/java/ai/z/openapi/core/token/HttpRequestInterceptor.java @@ -7,16 +7,17 @@ import okhttp3.Request; import okhttp3.Response; import java.io.IOException; +import java.util.Map; import java.util.Objects; /** * OkHttp Interceptor that adds an authorization token header */ -public class AuthenticationInterceptor implements Interceptor { +public class HttpRequestInterceptor implements Interceptor { private final ZaiConfig config; - public AuthenticationInterceptor(ZaiConfig config) { + public HttpRequestInterceptor(ZaiConfig config) { Objects.requireNonNull(config.getApiKey(), "Z.ai token required"); this.config = config; } @@ -31,17 +32,22 @@ public class AuthenticationInterceptor implements Interceptor { TokenManager tokenManager = GlobalTokenManager.getTokenManagerV4(); accessToken = tokenManager.getToken(this.config); } - String source_channel = "java-sdk"; + String source_channel = "z-ai-sdk-java"; if (StringUtils.isNotEmpty(config.getSource_channel())) { source_channel = config.getSource_channel(); } - Request request = chain.request() + Request.Builder 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); + .header("Zai-SDK-Ver", "0.0.2") + .header("Accept-Language", "en-US,en"); + if (Objects.nonNull(config.getCustomHeaders())) { + for (Map.Entry entry : config.getCustomHeaders().entrySet()) { + request.addHeader(entry.getKey(), entry.getValue()); + } + } + return chain.proceed(request.build()); } } 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 index f382290..cf1ed62 100644 --- a/core/src/main/java/ai/z/openapi/service/chat/ChatService.java +++ b/core/src/main/java/ai/z/openapi/service/chat/ChatService.java @@ -5,6 +5,8 @@ import ai.z.openapi.service.model.ChatCompletionResponse; import ai.z.openapi.service.model.AsyncResultRetrieveParams; import ai.z.openapi.service.model.QueryModelResultResponse; +import java.util.Map; + /** * Chat completion service interface */ @@ -32,4 +34,13 @@ public interface ChatService { */ QueryModelResultResponse retrieveAsyncResult(AsyncResultRetrieveParams request); + /** + * Creates a chat completion with custom headers support. This method allows passing + * custom HTTP headers along with the chat completion request. + * @param request the chat completion request parameters + * @param customHeaders custom HTTP headers to be added to the request + * @return ChatCompletionResponse containing the completion result + */ + ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request, Map customHeaders); + } \ 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 index 273c747..6c29a2d 100644 --- a/core/src/main/java/ai/z/openapi/service/chat/ChatServiceImpl.java +++ b/core/src/main/java/ai/z/openapi/service/chat/ChatServiceImpl.java @@ -7,11 +7,12 @@ 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.service.model.ChatRequestWithHeaders; import ai.z.openapi.utils.FlowableRequestSupplier; import ai.z.openapi.utils.RequestSupplier; -import ai.z.openapi.utils.StringUtils; import okhttp3.ResponseBody; +import java.util.Map; import java.util.Objects; /** @@ -64,6 +65,38 @@ public class ChatServiceImpl implements ChatService { return this.zAiClient.executeRequest(request, supplier, ChatCompletionResponse.class); } + @Override + public ChatCompletionResponse createChatCompletion(ChatCompletionCreateParams request, + Map customHeaders) { + if (Objects.isNull(customHeaders)) { + throw new IllegalArgumentException("customHeaders can not be null"); + } + validateParams(request); + if (Objects.nonNull(request.getStream()) && request.getStream()) { + return streamChatCompletionWithHeaders(request, customHeaders); + } + else { + return syncChatCompletionWithHeaders(request, customHeaders); + } + } + + private ChatCompletionResponse streamChatCompletionWithHeaders(ChatCompletionCreateParams request, + Map customHeaders) { + ChatRequestWithHeaders requestWithHeaders = new ChatRequestWithHeaders(request, customHeaders); + FlowableRequestSupplier> supplier = (wrapper) -> chatApi + .createChatCompletionStream(wrapper.getRequest(), wrapper.getCustomHeaders()); + return this.zAiClient.streamRequest(requestWithHeaders, supplier, ChatCompletionResponse.class, + ModelData.class); + } + + private ChatCompletionResponse syncChatCompletionWithHeaders(ChatCompletionCreateParams request, + Map customHeaders) { + ChatRequestWithHeaders requestWithHeaders = new ChatRequestWithHeaders(request, customHeaders); + RequestSupplier supplier = (wrapper) -> chatApi + .createChatCompletion(wrapper.getRequest(), wrapper.getCustomHeaders()); + return this.zAiClient.executeRequest(requestWithHeaders, supplier, ChatCompletionResponse.class); + } + private void validateParams(ChatCompletionCreateParams request) { if (request == null) { throw new IllegalArgumentException("request cannot be null"); diff --git a/core/src/main/java/ai/z/openapi/service/file/UploadFilePurpose.java b/core/src/main/java/ai/z/openapi/service/file/UploadFilePurpose.java new file mode 100644 index 0000000..c674394 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/file/UploadFilePurpose.java @@ -0,0 +1,17 @@ +package ai.z.openapi.service.file; + +public enum UploadFilePurpose { + + BATCH("batch"), FILE_EXTRACT("file-extract"), CODE_INTERPRETER("code-interpreter"), AGENT("agent"); + + private final String value; + + UploadFilePurpose(final String value) { + this.value = value; + } + + public String value() { + return value; + } + +} diff --git a/core/src/main/java/ai/z/openapi/service/model/ChatRequestWithHeaders.java b/core/src/main/java/ai/z/openapi/service/model/ChatRequestWithHeaders.java new file mode 100644 index 0000000..ccb3045 --- /dev/null +++ b/core/src/main/java/ai/z/openapi/service/model/ChatRequestWithHeaders.java @@ -0,0 +1,32 @@ +package ai.z.openapi.service.model; + +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 java.util.Map; + +/** + * Chat request wrapper that includes custom headers support. This class wraps the + * original ChatCompletionCreateParams and adds custom headers functionality. + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChatRequestWithHeaders extends CommonRequest implements ClientRequest { + + /** + * The original chat completion request parameters + */ + private ChatCompletionCreateParams request; + + /** + * Custom headers to be added to the HTTP request + */ + private Map customHeaders; + +} \ No newline at end of file diff --git a/core/src/main/java/ai/z/openapi/utils/OkHttps.java b/core/src/main/java/ai/z/openapi/utils/OkHttps.java index 4d48348..37b253a 100644 --- a/core/src/main/java/ai/z/openapi/utils/OkHttps.java +++ b/core/src/main/java/ai/z/openapi/utils/OkHttps.java @@ -1,7 +1,7 @@ package ai.z.openapi.utils; import ai.z.openapi.core.config.ZaiConfig; -import ai.z.openapi.core.token.AuthenticationInterceptor; +import ai.z.openapi.core.token.HttpRequestInterceptor; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; @@ -42,7 +42,7 @@ public final class OkHttps { throw new IllegalArgumentException("Configuration cannot be null"); } - OkHttpClient.Builder builder = new OkHttpClient.Builder().addInterceptor(new AuthenticationInterceptor(config)); + OkHttpClient.Builder builder = new OkHttpClient.Builder().addInterceptor(new HttpRequestInterceptor(config)); // Configure timeouts configureTimeouts(builder, config); diff --git a/core/src/test/java/ai/z/openapi/service/assistant/AssistantServiceTest.java b/core/src/test/java/ai/z/openapi/service/assistant/AssistantServiceTest.java index e6eea8b..eaf8f71 100644 --- a/core/src/test/java/ai/z/openapi/service/assistant/AssistantServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/assistant/AssistantServiceTest.java @@ -61,44 +61,6 @@ public class AssistantServiceTest { "AssistantService should be an instance of AssistantServiceImpl"); } - // As of 2025-07-23, no models support synchronous completion - // @Test - // @DisplayName("Test Synchronous Assistant Completion - Basic Functionality") - // @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") - // void testSyncAssistantCompletion() throws JsonProcessingException { - // // Prepare test data - // MessageTextContent textContent = MessageTextContent.builder() - // .text("Hello, please introduce yourself") - // .type("text") - // .build(); - - // ConversationMessage message = ConversationMessage.builder() - // .role("user") - // .content(Collections.singletonList(textContent)) - // .build(); - - // String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); - - // AssistantParameters request = AssistantParameters.builder() - // .model(Constants.ModelChatGLM4Assistant) - // .assistantId(TEST_ASSISTANT_ID) - // .stream(false) - // .messages(Collections.singletonList(message)) - // .requestId(requestId) - // .build(); - - // // Execute test - // AssistantApiResponse response = assistantService.assistantCompletion(request); - - // // Verify results - // assertNotNull(response, "Response should not be null"); - // assertTrue(response.isSuccess(), "Response should be successful"); - // assertNotNull(response.getData(), "Response data should not be null"); - // assertNull(response.getError(), "Response error should be null"); - // logger.info("Synchronous assistant completion response: {}", - // mapper.writeValueAsString(response)); - // } - @Test @DisplayName("Test Stream Assistant Completion") @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") @@ -188,13 +150,17 @@ public class AssistantServiceTest { @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") void testQueryConversationUsage() { // Prepare test data - ConversationParameters request = ConversationParameters.builder().assistantId(TEST_ASSISTANT_ID).build(); + ConversationParameters request = ConversationParameters.builder() + .assistantId(TEST_ASSISTANT_ID) + .page(1) + .pageSize(5) + .build(); // Execute test ConversationUsageListResponse response = assistantService.queryConversationUsage(request); // Verify results - assertNotNull(response, "Response should not be null"); + assertNotNull(response.getData(), "Response should not be null"); logger.info("Query conversation usage response: {}", response); } @@ -216,7 +182,7 @@ public class AssistantServiceTest { .messages(Collections.singletonList(message)) .build(); - AssistantApiResponse response = assistantService.assistantCompletion(request); + AssistantApiResponse response = assistantService.assistantCompletionStream(request); // Should handle error gracefully assertNotNull(response, "Response should not be null even for invalid assistant ID"); @@ -258,45 +224,29 @@ public class AssistantServiceTest { AssistantParameters request = AssistantParameters.builder() .assistantId(TEST_ASSISTANT_ID) - .stream(false) + .stream(true) .messages(messages) .requestId(requestId) .build(); - AssistantApiResponse response = assistantService.assistantCompletion(request); - + AssistantApiResponse response = assistantService.assistantCompletionStream(request); + response.getFlowable().doOnNext(accumulator -> { + if (accumulator.getChoices() != null && !accumulator.getChoices().isEmpty()) { + MessageContent delta = accumulator.getChoices().get(0).getDelta(); + if (delta != null) { + try { + logger.info("MessageContent: {}", mapper.writeValueAsString(delta)); + } + catch (JsonProcessingException e) { + logger.error("Error processing message content", e); + } + } + } + }) + .doOnComplete(() -> logger.info("Stream response completed, received messages")) + .doOnError(throwable -> logger.error("Stream error: {}", throwable.getMessage())) + .blockingSubscribe(); assertNotNull(response, "Multi-turn conversation response should not be null"); - logger.info("Multi-turn conversation response: {}", mapper.writeValueAsString(response)); - } - - @Test - @DisplayName("Test Assistant with Translation Parameters") - @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") - void testAssistantWithTranslation() throws JsonProcessingException { - // This test is based on the existing - // TestAssistantClientApiService.testTranslateAssistantCompletion - MessageTextContent textContent = MessageTextContent.builder().text("Hello there").type("text").build(); - - ConversationMessage message = ConversationMessage.builder() - .role("user") - .content(Collections.singletonList(textContent)) - .build(); - - String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); - - AssistantParameters request = AssistantParameters.builder() - .assistantId("9996ijk789lmn012o345p999") // Translation assistant ID - .stream(false) - .messages(Collections.singletonList(message)) - .requestId(requestId) - .build(); - - // Execute test - AssistantApiResponse response = assistantService.assistantCompletion(request); - - // Verify results - assertNotNull(response, "Translation response should not be null"); - logger.info("Translation assistant response: {}", mapper.writeValueAsString(response)); } } diff --git a/core/src/test/java/ai/z/openapi/service/audio/AudioServiceTest.java b/core/src/test/java/ai/z/openapi/service/audio/AudioServiceTest.java index f527c67..d70d574 100644 --- a/core/src/test/java/ai/z/openapi/service/audio/AudioServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/audio/AudioServiceTest.java @@ -303,7 +303,7 @@ public class AudioServiceTest { @DisplayName("Should transcribe different audio formats successfully") @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") void shouldTranscribeDifferentAudioFormatsSuccessfully() throws JsonProcessingException { - String[] audioFiles = { "asr.wav", "asr.webm" }; + String[] audioFiles = { "asr.wav", "asr.mp3" }; for (String audioFile : audioFiles) { String requestId = String.format(REQUEST_ID_TEMPLATE + "-%s", System.currentTimeMillis(), audioFile); diff --git a/core/src/test/java/ai/z/openapi/service/chat/ChatServiceTest.java b/core/src/test/java/ai/z/openapi/service/chat/ChatServiceTest.java index 088a20d..3cb32a6 100644 --- a/core/src/test/java/ai/z/openapi/service/chat/ChatServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/chat/ChatServiceTest.java @@ -508,4 +508,187 @@ public class ChatServiceTest { logger.info("CodeGeex code completion test completed"); } + @Test + @DisplayName("Test Synchronous Chat Completion with Custom Headers") + @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") + void testSyncChatCompletionWithCustomHeaders() throws JsonProcessingException { + // Prepare test data + List messages = new ArrayList<>(); + ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Hello, please introduce yourself"); + messages.add(userMessage); + + String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); + + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.FALSE) + .messages(messages) + .requestId(requestId) + .build(); + + // Prepare custom headers + Map customHeaders = new HashMap<>(); + customHeaders.put("X-Custom-User-ID", "test-user-123"); + customHeaders.put("X-Request-Source", "junit-test"); + customHeaders.put("Session-Id", "session-" + System.currentTimeMillis()); + + // Execute test + ChatCompletionResponse response = chatService.createChatCompletion(request, customHeaders); + + // Verify results + assertNotNull(response, "Response should not be null"); + assertTrue(response.isSuccess(), "Response should be successful"); + assertNotNull(response.getData(), "Response data should not be null"); + assertEquals(requestId, response.getData().getRequestId(), "Request ID should match"); + assertNotNull(response.getData().getChoices(), "Response data should not be null"); + assertFalse(response.getData().getChoices().isEmpty(), "Response data should not be empty"); + assertNull(response.getError(), "Response error should be null"); + logger.info("Synchronous chat completion with custom headers response: {}", + mapper.writeValueAsString(response)); + } + + @Test + @DisplayName("Test Stream Chat Completion with Custom Headers") + @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") + void testStreamChatCompletionWithCustomHeaders() throws JsonProcessingException { + // Prepare test data + List messages = new ArrayList<>(); + ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), + "Please write a short poem about spring"); + messages.add(userMessage); + + String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); + + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.TRUE) + .messages(messages) + .requestId(requestId) + .temperature(0.7F) + .maxTokens(100) + .build(); + + // Prepare custom headers + Map customHeaders = new HashMap<>(); + customHeaders.put("X-Custom-User-ID", "stream-test-user-456"); + customHeaders.put("X-Request-Source", "junit-stream-test"); + customHeaders.put("X-Stream-Mode", "enabled"); + customHeaders.put("Content-Type", "application/json"); + + // Execute test + ChatCompletionResponse response = chatService.createChatCompletion(request, customHeaders); + + // Verify results + assertNotNull(response, "Response should not be null"); + assertTrue(response.isSuccess(), "Response should be successful"); + + // Test stream data processing + AtomicInteger messageCount = new AtomicInteger(0); + AtomicBoolean isFirst = new AtomicBoolean(true); + + response.getFlowable().doOnNext(modelData -> { + if (isFirst.getAndSet(false)) { + logger.info("Starting to receive stream response with custom headers:"); + } + if (modelData.getChoices() != null && !modelData.getChoices().isEmpty()) { + Choice choice = modelData.getChoices().get(0); + if (choice.getDelta() != null && choice.getDelta().getContent() != null) { + logger.info("Received content: {}", choice.getDelta().getContent()); + messageCount.incrementAndGet(); + } + } + }) + .doOnComplete(() -> logger.info( + "Stream response with custom headers completed, received {} messages in total", messageCount.get())) + .blockingSubscribe(); + + assertTrue(messageCount.get() > 0, "Should receive at least one message"); + + logger.info("Stream chat completion with custom headers test completed"); + } + + @Test + @DisplayName("Test Custom Headers Validation - Null Headers") + void testCustomHeadersValidation_NullHeaders() { + // Prepare test data + List messages = new ArrayList<>(); + ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Hello"); + messages.add(userMessage); + + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.FALSE) + .messages(messages) + .build(); + + // Test with null custom headers + assertThrows(IllegalArgumentException.class, () -> { + chatService.createChatCompletion(request, null); + }, "Null custom headers should throw IllegalArgumentException"); + } + + @Test + @DisplayName("Test Custom Headers with Empty Map") + @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") + void testCustomHeadersWithEmptyMap() throws JsonProcessingException { + // Prepare test data + List messages = new ArrayList<>(); + ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Hello"); + messages.add(userMessage); + + String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); + + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.FALSE) + .messages(messages) + .requestId(requestId) + .build(); + + // Test with empty custom headers map + Map emptyHeaders = new HashMap<>(); + ChatCompletionResponse response = chatService.createChatCompletion(request, emptyHeaders); + + // Verify results + assertNotNull(response, "Response should not be null"); + assertTrue(response.isSuccess(), "Response should be successful"); + assertNotNull(response.getData(), "Response data should not be null"); + logger.info("Chat completion with empty custom headers response: {}", mapper.writeValueAsString(response)); + } + + @Test + @DisplayName("Test Custom Headers with Special Characters") + @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") + void testCustomHeadersWithSpecialCharacters() throws JsonProcessingException { + // Prepare test data + List messages = new ArrayList<>(); + ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Hello"); + messages.add(userMessage); + + String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); + + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4) + .stream(Boolean.FALSE) + .messages(messages) + .requestId(requestId) + .build(); + + // Prepare custom headers with special characters + Map customHeaders = new HashMap<>(); + customHeaders.put("X-User-Agent", "ZAI-SDK/1.0 (Java; Test)"); + customHeaders.put("X-Client-Version", "v1.2.3-beta"); + customHeaders.put("X-Request-Timestamp", String.valueOf(System.currentTimeMillis())); + customHeaders.put("X-Trace-ID", "trace-" + UUID.randomUUID()); + + // Execute test + ChatCompletionResponse response = chatService.createChatCompletion(request, customHeaders); + + // Verify results + assertNotNull(response, "Response should not be null"); + assertTrue(response.isSuccess(), "Response should be successful"); + assertNotNull(response.getData(), "Response data should not be null"); + logger.info("Chat completion with special character headers response: {}", mapper.writeValueAsString(response)); + } + } diff --git a/core/src/test/java/ai/z/openapi/service/chat/ChatServiceWithHeadersIntegrationTest.java b/core/src/test/java/ai/z/openapi/service/chat/ChatServiceWithHeadersIntegrationTest.java new file mode 100644 index 0000000..ffc8d91 --- /dev/null +++ b/core/src/test/java/ai/z/openapi/service/chat/ChatServiceWithHeadersIntegrationTest.java @@ -0,0 +1,120 @@ +package ai.z.openapi.service.chat; + +import ai.z.openapi.service.model.ChatCompletionCreateParams; +import ai.z.openapi.service.model.ChatMessage; +import ai.z.openapi.service.model.ChatMessageRole; +import ai.z.openapi.service.model.ChatRequestWithHeaders; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit test for ChatRequestWithHeaders wrapper functionality. Tests the wrapper class + * used for custom headers in chat requests. + */ +class ChatServiceWithHeadersIntegrationTest { + + @Test + void testChatRequestWithHeaders_StreamingRequest() { + // Arrange + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model("glm-4-flash") + .messages(Arrays + .asList(ChatMessage.builder().role(ChatMessageRole.USER.value()).content("Hello, world!").build())) + .stream(true) + .build(); + + Map customHeaders = new HashMap<>(); + customHeaders.put("X-Custom-User-ID", "user123"); + customHeaders.put("X-Request-Source", "test"); + + // Act + ChatRequestWithHeaders wrapper = new ChatRequestWithHeaders(request, customHeaders); + + // Assert + assertNotNull(wrapper); + assertNotNull(wrapper.getRequest()); + assertNotNull(wrapper.getCustomHeaders()); + assertEquals(request, wrapper.getRequest()); + assertEquals(customHeaders, wrapper.getCustomHeaders()); + assertEquals(2, wrapper.getCustomHeaders().size()); + assertEquals("user123", wrapper.getCustomHeaders().get("X-Custom-User-ID")); + assertEquals("test", wrapper.getCustomHeaders().get("X-Request-Source")); + } + + @Test + void testChatRequestWithHeaders_NonStreamingRequest() { + // Arrange + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model("glm-4-flash") + .messages(Arrays + .asList(ChatMessage.builder().role(ChatMessageRole.USER.value()).content("What is AI?").build())) + .stream(false) // Non-streaming request + .build(); + + Map customHeaders = new HashMap<>(); + customHeaders.put("X-Custom-User-ID", "user456"); + customHeaders.put("X-Request-Source", "test-sync"); + + // Act + ChatRequestWithHeaders wrapper = new ChatRequestWithHeaders(request, customHeaders); + + // Assert + assertNotNull(wrapper); + assertNotNull(wrapper.getRequest()); + assertNotNull(wrapper.getCustomHeaders()); + assertEquals(request, wrapper.getRequest()); + assertEquals(customHeaders, wrapper.getCustomHeaders()); + assertFalse(wrapper.getRequest().getStream()); // Verify non-streaming + assertEquals("user456", wrapper.getCustomHeaders().get("X-Custom-User-ID")); + assertEquals("test-sync", wrapper.getCustomHeaders().get("X-Request-Source")); + } + + @Test + void testChatRequestWithHeaders_NullHeaders() { + // Arrange + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model("glm-4-flash") + .messages(Arrays + .asList(ChatMessage.builder().role(ChatMessageRole.USER.value()).content("Test message").build())) + .stream(false) + .build(); + + // Act + ChatRequestWithHeaders wrapper = new ChatRequestWithHeaders(request, null); + + // Assert + assertNotNull(wrapper); + assertNotNull(wrapper.getRequest()); + assertEquals(request, wrapper.getRequest()); + // Custom headers are converted to empty map when null + assertNull(wrapper.getCustomHeaders()); + } + + @Test + void testChatRequestWithHeaders_ToString() { + // Arrange + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model("glm-4-flash") + .messages(Arrays + .asList(ChatMessage.builder().role(ChatMessageRole.USER.value()).content("Test message").build())) + .stream(false) + .build(); + + Map headers = new HashMap<>(); + headers.put("X-Test", "value"); + + // Act + ChatRequestWithHeaders wrapper = new ChatRequestWithHeaders(request, headers); + String toStringResult = wrapper.toString(); + + // Assert + assertNotNull(toStringResult); + assertTrue(toStringResult.contains("ChatRequestWithHeaders")); + } + +} \ No newline at end of file diff --git a/core/src/test/java/ai/z/openapi/service/file/FileServiceTest.java b/core/src/test/java/ai/z/openapi/service/file/FileServiceTest.java index 53e94cb..a18dd46 100644 --- a/core/src/test/java/ai/z/openapi/service/file/FileServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/file/FileServiceTest.java @@ -37,13 +37,6 @@ public class FileServiceTest { // Request ID template private static final String REQUEST_ID_TEMPLATE = "file-test-%d"; - // Test file purposes - private static final String PURPOSE_FINE_TUNE = "fine-tune"; - - private static final String PURPOSE_ASSISTANTS = "assistants"; - - private static final String PURPOSE_BATCH = "batch"; - @BeforeEach void setUp() { ZaiConfig zaiConfig = new ZaiConfig(); @@ -74,7 +67,7 @@ public class FileServiceTest { FileUploadParams request = FileUploadParams.builder() .filePath(tempFile.toString()) - .purpose(PURPOSE_ASSISTANTS) + .purpose(UploadFilePurpose.AGENT.value()) .requestId(requestId) .build(); @@ -87,7 +80,7 @@ public class FileServiceTest { assertNotNull(response.getData(), "Response data should not be null"); assertNotNull(response.getData().getId(), "File ID should not be null"); assertEquals("file", response.getData().getObject(), "Object type should be 'file'"); - assertEquals(PURPOSE_ASSISTANTS, response.getData().getPurpose(), "Purpose should match"); + assertEquals(UploadFilePurpose.AGENT.value(), response.getData().getPurpose(), "Purpose should match"); assertNotNull(response.getData().getFilename(), "Filename should not be null"); assertNotNull(response.getData().getBytes(), "File size should not be null"); assertTrue(response.getData().getBytes() > 0, "File size should be greater than 0"); @@ -119,7 +112,7 @@ public class FileServiceTest { FileUploadParams request = FileUploadParams.builder() .filePath(tempFile.toString()) - .purpose(PURPOSE_FINE_TUNE) + .purpose(UploadFilePurpose.FILE_EXTRACT.value()) .requestId(requestId) .extraJson(extraJson) .build(); @@ -131,7 +124,8 @@ public class FileServiceTest { assertNotNull(response, "Response should not be null"); assertTrue(response.isSuccess(), "Response should be successful"); assertNotNull(response.getData(), "Response data should not be null"); - assertEquals(PURPOSE_FINE_TUNE, response.getData().getPurpose(), "Purpose should match"); + assertEquals(UploadFilePurpose.FILE_EXTRACT.value(), response.getData().getPurpose(), + "Purpose should match"); assertNull(response.getError(), "Response error should be null"); logger.info("File upload with extra JSON response: {}", mapper.writeValueAsString(response)); @@ -149,7 +143,7 @@ public class FileServiceTest { FileUploadParams request = FileUploadParams.builder() .filePath("/non/existent/file.txt") - .purpose(PURPOSE_ASSISTANTS) + .purpose(UploadFilePurpose.AGENT.value()) .requestId(requestId) .build(); @@ -187,7 +181,7 @@ public class FileServiceTest { String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); FileListParams request = FileListParams.builder() - .purpose(PURPOSE_ASSISTANTS) + .purpose(UploadFilePurpose.AGENT.value()) .limit(5) .order("asc") .requestId(requestId) @@ -205,7 +199,8 @@ public class FileServiceTest { // Verify purpose filter if files exist if (response.getData().getData() != null && !response.getData().getData().isEmpty()) { response.getData().getData().forEach(file -> { - assertEquals(PURPOSE_ASSISTANTS, file.getPurpose(), "All files should have the specified purpose"); + assertEquals(UploadFilePurpose.AGENT.value(), file.getPurpose(), + "All files should have the specified purpose"); }); } @@ -269,7 +264,7 @@ public class FileServiceTest { FileUploadParams uploadRequest = FileUploadParams.builder() .filePath(tempFile.toString()) - .purpose(PURPOSE_ASSISTANTS) + .purpose(UploadFilePurpose.FILE_EXTRACT.value()) .requestId(requestId) .build(); diff --git a/core/src/test/java/ai/z/openapi/service/image/ImageServiceTest.java b/core/src/test/java/ai/z/openapi/service/image/ImageServiceTest.java index f905018..d19a549 100644 --- a/core/src/test/java/ai/z/openapi/service/image/ImageServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/image/ImageServiceTest.java @@ -110,13 +110,13 @@ public class ImageServiceTest { @DisplayName("Test Different Image Sizes") @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") void testDifferentImageSizes() throws JsonProcessingException { - String[] sizes = { "256x256", "512x512", "1024x1024" }; + String[] sizes = { "512x512", "1024x1024" }; for (String size : sizes) { String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); CreateImageRequest request = CreateImageRequest.builder() - .model(Constants.ModelCogView3Plus) + .model(Constants.ModelCogView4) .prompt("A simple geometric pattern") .size(size) .requestId(requestId) @@ -124,7 +124,8 @@ public class ImageServiceTest { ImageResponse response = imageService.createImage(request); - assertNotNull(response, "Response should not be null for size: " + size); + assertNotNull(response.getData(), "Response should not be null for size: " + size); + assertEquals(200, response.getCode()); logger.info("Size {} response: {}", size, mapper.writeValueAsString(response)); } } diff --git a/core/src/test/java/ai/z/openapi/service/videos/VideosServiceTest.java b/core/src/test/java/ai/z/openapi/service/videos/VideosServiceTest.java index 24ed764..c7f843f 100644 --- a/core/src/test/java/ai/z/openapi/service/videos/VideosServiceTest.java +++ b/core/src/test/java/ai/z/openapi/service/videos/VideosServiceTest.java @@ -1,6 +1,7 @@ package ai.z.openapi.service.videos; import ai.z.openapi.ZaiClient; +import ai.z.openapi.core.Constants; import ai.z.openapi.core.config.ZaiConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,11 +36,6 @@ public class VideosServiceTest { // Request ID template private static final String REQUEST_ID_TEMPLATE = "video-test-%d"; - // Video model constants - private static final String MODEL_COGVIDEOX = "cogvideox"; - - private static final String MODEL_COGVIDEO3 = "cogvideo-3"; - @BeforeEach void setUp() { ZaiConfig zaiConfig = new ZaiConfig(); @@ -67,7 +63,7 @@ public class VideosServiceTest { String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); VideoCreateParams request = VideoCreateParams.builder() - .model(MODEL_COGVIDEOX) + .model(Constants.ModelCogVideoX3) .prompt("A beautiful sunset over the ocean with waves gently crashing on the shore") .requestId(requestId) .withAudio(Boolean.TRUE) @@ -80,6 +76,7 @@ public class VideosServiceTest { // Verify results assertNotNull(response, "Response should not be null"); + assertEquals(200, response.getCode()); assertTrue(response.isSuccess(), "Response should be successful"); assertNotNull(response.getData(), "Response data should not be null"); assertNotNull(response.getData().getId(), "Response data ID should not be null"); @@ -95,7 +92,7 @@ public class VideosServiceTest { String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); VideoCreateParams request = VideoCreateParams.builder() - .model(MODEL_COGVIDEOX) + .model(Constants.ModelCogVideoX3) .prompt("A person walking in a beautiful garden") .requestId(requestId) .build(); @@ -114,6 +111,7 @@ public class VideosServiceTest { // Verify result response assertNotNull(resultResponse, "Result response should not be null"); + assertEquals(200, resultResponse.getCode()); assertNotNull(resultResponse.getData(), "Result response data should not be null"); assertNotNull(resultResponse.getData().getId(), "Result response task ID should not be null"); logger.info("Video generation result: taskId={}, response={}", taskId, @@ -138,7 +136,7 @@ public class VideosServiceTest { } @ParameterizedTest - @ValueSource(strings = { MODEL_COGVIDEOX, MODEL_COGVIDEO3 }) + @ValueSource(strings = { Constants.ModelCogVideoX2, Constants.ModelCogVideoX3 }) @DisplayName("Test Different Video Models") @EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$") void testDifferentModels(String model) throws JsonProcessingException { @@ -153,6 +151,7 @@ public class VideosServiceTest { VideosResponse response = videosService.videoGenerations(request); assertNotNull(response, "Response should not be null"); + assertEquals(200, response.getCode()); logger.info("Model {} response: {}", model, mapper.writeValueAsString(response)); } @@ -177,7 +176,7 @@ public class VideosServiceTest { @Test @DisplayName("Test Parameter Validation - Empty Prompt") void testValidation_EmptyPrompt() { - VideoCreateParams request = VideoCreateParams.builder().model(MODEL_COGVIDEOX).prompt("").build(); + VideoCreateParams request = VideoCreateParams.builder().model(Constants.ModelCogVideoX3).prompt("").build(); assertThrows(IllegalArgumentException.class, () -> { videosService.videoGenerations(request); @@ -202,17 +201,18 @@ public class VideosServiceTest { Base64.Encoder encoder = Base64.getEncoder(); String imageUrl = encoder.encodeToString(bytes); VideoCreateParams request = VideoCreateParams.builder() - .model(MODEL_COGVIDEOX) + .model(Constants.ModelCogVideoX3) .prompt("Transform this image into a dynamic video scene") .imageUrl(imageUrl) .requestId(requestId) .withAudio(Boolean.FALSE) - .duration(3) + .duration(5) .build(); VideosResponse response = videosService.videoGenerations(request); assertNotNull(response, "Response should not be null"); + assertEquals(200, response.getCode()); logger.info("Video generation with image response: {}", mapper.writeValueAsString(response)); } @@ -223,19 +223,20 @@ public class VideosServiceTest { String requestId = String.format(REQUEST_ID_TEMPLATE, System.currentTimeMillis()); VideoCreateParams request = VideoCreateParams.builder() - .model(MODEL_COGVIDEOX) + .model(Constants.ModelCogVideoX3) .prompt("A futuristic city with flying cars and neon lights") .requestId(requestId) - .quality("high") + .quality("speed") .withAudio(Boolean.TRUE) .size("1280x720") - .duration(10) + .duration(5) .fps(30) .build(); VideosResponse response = videosService.videoGenerations(request); assertNotNull(response, "Response should not be null"); + assertEquals(200, response.getCode()); logger.info("Video generation with custom settings response: {}", mapper.writeValueAsString(response)); } diff --git a/core/src/test/resources/asr.mp3 b/core/src/test/resources/asr.mp3 new file mode 100644 index 0000000..ff9b04d Binary files /dev/null and b/core/src/test/resources/asr.mp3 differ diff --git a/samples/src/main/ai.z.openapi.samples/AgentExample.java b/samples/src/main/ai.z.openapi.samples/AgentExample.java index 80c4b73..f13a124 100644 --- a/samples/src/main/ai.z.openapi.samples/AgentExample.java +++ b/samples/src/main/ai.z.openapi.samples/AgentExample.java @@ -21,7 +21,7 @@ public class AgentExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); diff --git a/samples/src/main/ai.z.openapi.samples/AgentVideoExample.java b/samples/src/main/ai.z.openapi.samples/AgentVideoExample.java index ae22243..2902937 100644 --- a/samples/src/main/ai.z.openapi.samples/AgentVideoExample.java +++ b/samples/src/main/ai.z.openapi.samples/AgentVideoExample.java @@ -21,7 +21,7 @@ public class AgentVideoExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); diff --git a/samples/src/main/ai.z.openapi.samples/ChatAsyncCompletionExample.java b/samples/src/main/ai.z.openapi.samples/ChatAsyncCompletionExample.java index 307aa17..6b2ff83 100644 --- a/samples/src/main/ai.z.openapi.samples/ChatAsyncCompletionExample.java +++ b/samples/src/main/ai.z.openapi.samples/ChatAsyncCompletionExample.java @@ -19,13 +19,13 @@ public class ChatAsyncCompletionExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZhipuAiClient client = ZhipuAiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Create chat request diff --git a/samples/src/main/ai.z.openapi.samples/ChatCompletionExample.java b/samples/src/main/ai.z.openapi.samples/ChatCompletionExample.java index 00cfd75..41e4723 100644 --- a/samples/src/main/ai.z.openapi.samples/ChatCompletionExample.java +++ b/samples/src/main/ai.z.openapi.samples/ChatCompletionExample.java @@ -14,13 +14,13 @@ public class ChatCompletionExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZhipuAiClient client = ZhipuAiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Create chat request diff --git a/samples/src/main/ai.z.openapi.samples/ChatCompletionWithCustomHeadersExample.java b/samples/src/main/ai.z.openapi.samples/ChatCompletionWithCustomHeadersExample.java new file mode 100644 index 0000000..dc3f917 --- /dev/null +++ b/samples/src/main/ai.z.openapi.samples/ChatCompletionWithCustomHeadersExample.java @@ -0,0 +1,90 @@ +package ai.z.openapi.samples; + +import ai.z.openapi.ZhipuAiClient; +import ai.z.openapi.service.model.*; +import ai.z.openapi.core.Constants; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Chat Completion with Custom Headers Example + * Demonstrates how to use ZaiClient for chat conversations with custom HTTP headers + */ +public class ChatCompletionWithCustomHeadersExample { + + public static void main(String[] args) { + // Create client, recommended to set API Key via environment variable + // export ZAI_API_KEY=your.api_key + // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient + ZhipuAiClient client = ZhipuAiClient.builder().build(); + + // Create chat request + ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4_5) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("Hello, how are you?") + .build() + )) + .stream(true) // Enable streaming for custom headers support + .temperature(0.7f) + .maxTokens(1024) + .build(); + + // Create custom headers + Map customHeaders = new HashMap<>(); + customHeaders.put("X-Custom-User-ID", "user123"); + customHeaders.put("X-Request-Source", "mobile-app"); + customHeaders.put("Session-Id", "session-abc-123"); + + try { + // Execute request with custom headers + // This works for both streaming and non-streaming requests + ChatCompletionResponse response = client.chat().createChatCompletion(request, customHeaders); + + // Example for non-streaming request with custom headers + ChatCompletionCreateParams nonStreamingRequest = ChatCompletionCreateParams.builder() + .model(Constants.ModelChatGLM4_5) + .messages(Arrays.asList( + ChatMessage.builder() + .role(ChatMessageRole.USER.value()) + .content("What is artificial intelligence?") + .build() + )) + .stream(false) // Explicitly set to false for non-streaming + .temperature(0.7f) + .maxTokens(1024) + .build(); + + ChatCompletionResponse nonStreamingResponse = client.chat() + .createChatCompletion(nonStreamingRequest, customHeaders); + + System.out.println("Non-streaming response: " + nonStreamingResponse.getData()); + + if (response.isSuccess() && response.getFlowable() != null) { + System.out.println("Streaming response with custom headers:"); + 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("Error: " + response.getMsg()); + } + } catch (Exception e) { + System.err.println("Exception occurred: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/samples/src/main/ai.z.openapi.samples/ClientConfigurationExample.java b/samples/src/main/ai.z.openapi.samples/ClientConfigurationExample.java index 905c474..4abe954 100644 --- a/samples/src/main/ai.z.openapi.samples/ClientConfigurationExample.java +++ b/samples/src/main/ai.z.openapi.samples/ClientConfigurationExample.java @@ -1,6 +1,10 @@ package ai.z.openapi.samples; import ai.z.openapi.ZaiClient; +import ai.z.openapi.ZhipuAiClient; + +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; /** @@ -12,14 +16,17 @@ public class ClientConfigurationExample { public static void main(String[] args) { System.out.println("=== Basic Configuration Example ==="); - ZaiClient basicClient = ZaiClient.builder().build(); + ZaiClient basicClient = ZaiClient.builder().apiKey("xxx.xxx").build(); System.out.println("✓ Basic client created successfully"); // Complete configuration example System.out.println("\n=== Complete Configuration Example ==="); + Map customHeaders = new HashMap<>(); + customHeaders.put("Session-Id", "custom-session-id-xx"); ZaiClient advancedClient = ZaiClient.builder() - .apiKey("your.api.key") + .apiKey("your.api_key") .baseUrl("https://api.z.ai/api/paas/v4/") + .customHeaders(customHeaders) .enableTokenCache() .tokenExpire(3600000) // 1 hour .connectionPool(10, 5, TimeUnit.MINUTES) @@ -28,14 +35,25 @@ public class ClientConfigurationExample { // ZHIPU platform specific client System.out.println("\n=== ZHIPU Platform Specific Configuration ==="); - ZaiClient zhipuClient = ZaiClient.ofZHIPU("your.api.key").build(); + ZaiClient zhipuClient = ZaiClient.ofZHIPU("your.api_key").build(); System.out.println("✓ ZHIPU platform client created successfully"); - + + ZhipuAiClient zhipuAiClient = ZhipuAiClient.builder() + .apiKey("your.api_key") + .baseUrl("https://api.z.ai/api/paas/v4/") + .customHeaders(customHeaders) + .enableTokenCache() + .tokenExpire(3600000) // 1 hour + .connectionPool(10, 5, TimeUnit.MINUTES) + .build(); + System.out.println("✓ ZHIPU platform client created successfully"); + // Custom configuration example System.out.println("\n=== Custom Configuration Example ==="); ZaiClient customClient = ZaiClient.builder() - .apiKey("your.api.key") + .apiKey("your.api_key") .baseUrl("https://custom.api.endpoint/") + .customHeaders(customHeaders) .enableTokenCache() .tokenExpire(7200000) .connectionPool(20, 10, TimeUnit.MINUTES) diff --git a/samples/src/main/ai.z.openapi.samples/CogVideoX3Example.java b/samples/src/main/ai.z.openapi.samples/CogVideoX3Example.java index 77ec566..d41a4be 100644 --- a/samples/src/main/ai.z.openapi.samples/CogVideoX3Example.java +++ b/samples/src/main/ai.z.openapi.samples/CogVideoX3Example.java @@ -16,13 +16,13 @@ public class CogVideoX3Example { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Video generation examples diff --git a/samples/src/main/ai.z.openapi.samples/CogVideoXExample.java b/samples/src/main/ai.z.openapi.samples/CogVideoXExample.java index 6a532a2..913aec5 100644 --- a/samples/src/main/ai.z.openapi.samples/CogVideoXExample.java +++ b/samples/src/main/ai.z.openapi.samples/CogVideoXExample.java @@ -13,13 +13,13 @@ public class CogVideoXExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Basic Video Generation diff --git a/samples/src/main/ai.z.openapi.samples/GLM41VThinkingExample.java b/samples/src/main/ai.z.openapi.samples/GLM41VThinkingExample.java index 7c8c635..70280cd 100644 --- a/samples/src/main/ai.z.openapi.samples/GLM41VThinkingExample.java +++ b/samples/src/main/ai.z.openapi.samples/GLM41VThinkingExample.java @@ -16,7 +16,7 @@ public class GLM41VThinkingExample { // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder() - .apiKey("your.api.key") + .apiKey("your.api_key") .build(); ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() diff --git a/samples/src/main/ai.z.openapi.samples/GLM4VPlusExample.java b/samples/src/main/ai.z.openapi.samples/GLM4VPlusExample.java index f40c3bc..b0ac710 100644 --- a/samples/src/main/ai.z.openapi.samples/GLM4VPlusExample.java +++ b/samples/src/main/ai.z.openapi.samples/GLM4VPlusExample.java @@ -15,7 +15,7 @@ public class GLM4VPlusExample { public static void main(String[] args) { // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient - ZaiClient client = ZaiClient.builder().apiKey("your.api.key").build(); + ZaiClient client = ZaiClient.builder().apiKey("your.api_key").build(); ChatCompletionCreateParams request = ChatCompletionCreateParams.builder() .model("glm-4v-plus-0111") diff --git a/samples/src/main/ai.z.openapi.samples/ViduAspectVideoExample.java b/samples/src/main/ai.z.openapi.samples/ViduAspectVideoExample.java index 52a9c12..5d09478 100644 --- a/samples/src/main/ai.z.openapi.samples/ViduAspectVideoExample.java +++ b/samples/src/main/ai.z.openapi.samples/ViduAspectVideoExample.java @@ -15,7 +15,7 @@ public class ViduAspectVideoExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); diff --git a/samples/src/main/ai.z.openapi.samples/ViduImageToVideoExample.java b/samples/src/main/ai.z.openapi.samples/ViduImageToVideoExample.java index c08a23f..ad6ec8a 100644 --- a/samples/src/main/ai.z.openapi.samples/ViduImageToVideoExample.java +++ b/samples/src/main/ai.z.openapi.samples/ViduImageToVideoExample.java @@ -13,7 +13,7 @@ public class ViduImageToVideoExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); diff --git a/samples/src/main/ai.z.openapi.samples/ViduStartEndVideoExample.java b/samples/src/main/ai.z.openapi.samples/ViduStartEndVideoExample.java index 3bdf339..006fc8e 100644 --- a/samples/src/main/ai.z.openapi.samples/ViduStartEndVideoExample.java +++ b/samples/src/main/ai.z.openapi.samples/ViduStartEndVideoExample.java @@ -15,7 +15,7 @@ public class ViduStartEndVideoExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); diff --git a/samples/src/main/ai.z.openapi.samples/ViduTextToVideoExample.java b/samples/src/main/ai.z.openapi.samples/ViduTextToVideoExample.java index 0d9ca4c..2a219ff 100644 --- a/samples/src/main/ai.z.openapi.samples/ViduTextToVideoExample.java +++ b/samples/src/main/ai.z.openapi.samples/ViduTextToVideoExample.java @@ -13,13 +13,13 @@ public class ViduTextToVideoExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Example: Generate video from text using Vidu diff --git a/samples/src/main/ai.z.openapi.samples/WebSearchExample.java b/samples/src/main/ai.z.openapi.samples/WebSearchExample.java index ee8088c..8db3ede 100644 --- a/samples/src/main/ai.z.openapi.samples/WebSearchExample.java +++ b/samples/src/main/ai.z.openapi.samples/WebSearchExample.java @@ -23,13 +23,13 @@ public class WebSearchExample { public static void main(String[] args) { // Create client, recommended to set API Key via environment variable - // export ZAI_API_KEY=your.api.key + // export ZAI_API_KEY=your.api_key // for Z.ai use the `ZaiClient`, for Zhipu AI use the ZhipuAiClient ZaiClient client = ZaiClient.builder().build(); // Or set API Key via code // ZaiClient client = ZaiClient.builder() - // .apiKey("your.api.key") + // .apiKey("your.api_key") // .build(); // Example 1: Basic Web Search