diff --git a/integration-tests/openai/src/main/java/org/acme/example/openai/QuarkusRestApiResource.java b/integration-tests/openai/src/main/java/org/acme/example/openai/QuarkusRestApiResource.java index 88004d18d..f95029cdc 100644 --- a/integration-tests/openai/src/main/java/org/acme/example/openai/QuarkusRestApiResource.java +++ b/integration-tests/openai/src/main/java/org/acme/example/openai/QuarkusRestApiResource.java @@ -36,6 +36,7 @@ import dev.ai4j.openai4j.completion.CompletionChoice; import dev.ai4j.openai4j.completion.CompletionResponse; import dev.ai4j.openai4j.embedding.EmbeddingResponse; +import io.netty.util.internal.StringUtil; import io.quarkiverse.langchain4j.openai.OpenAiRestApi; import io.quarkiverse.langchain4j.openai.runtime.config.LangChain4jOpenAiConfig; import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; @@ -56,7 +57,7 @@ public QuarkusRestApiResource(LangChain4jOpenAiConfig runtimeConfig) this.restApi = QuarkusRestClientBuilder.newBuilder() .baseUri(new URI(openAiConfig.baseUrl())) .build(OpenAiRestApi.class); - this.token = openAiConfig.apiKey(); + this.token = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); this.organizationId = openAiConfig.organizationId().orElse(null); } diff --git a/integration-tests/openai/src/main/java/org/acme/example/openai/chat/QuarkusOpenAiClientChatResource.java b/integration-tests/openai/src/main/java/org/acme/example/openai/chat/QuarkusOpenAiClientChatResource.java index ce4182ed6..63bf03da4 100644 --- a/integration-tests/openai/src/main/java/org/acme/example/openai/chat/QuarkusOpenAiClientChatResource.java +++ b/integration-tests/openai/src/main/java/org/acme/example/openai/chat/QuarkusOpenAiClientChatResource.java @@ -10,6 +10,7 @@ import dev.ai4j.openai4j.chat.AssistantMessage; import dev.ai4j.openai4j.chat.ChatCompletionChoice; import dev.ai4j.openai4j.chat.Delta; +import io.netty.util.internal.StringUtil; import io.quarkiverse.langchain4j.openai.QuarkusOpenAiClient; import io.quarkiverse.langchain4j.openai.runtime.config.LangChain4jOpenAiConfig; import io.smallrye.mutiny.Multi; @@ -21,7 +22,7 @@ public class QuarkusOpenAiClientChatResource { private final QuarkusOpenAiClient quarkusOpenAiClient; public QuarkusOpenAiClientChatResource(LangChain4jOpenAiConfig runtimeConfig) { - String token = runtimeConfig.defaultConfig().apiKey(); + String token = runtimeConfig.defaultConfig().apiKey().orElse(StringUtil.EMPTY_STRING); String baseUrl = runtimeConfig.defaultConfig().baseUrl(); quarkusOpenAiClient = QuarkusOpenAiClient.builder().openAiApiKey(token).baseUrl(baseUrl).build(); } diff --git a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/OpenAiRecorder.java b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/OpenAiRecorder.java index d8294e6a5..f5c3f99fe 100644 --- a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/OpenAiRecorder.java +++ b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/OpenAiRecorder.java @@ -20,6 +20,7 @@ import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.model.openai.OpenAiModerationModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; +import io.netty.util.internal.StringUtil; import io.quarkiverse.langchain4j.openai.QuarkusOpenAiClient; import io.quarkiverse.langchain4j.openai.QuarkusOpenAiImageModel; import io.quarkiverse.langchain4j.openai.runtime.config.ChatModelConfig; @@ -42,8 +43,8 @@ public Supplier chatModel(LangChain4jOpenAiConfig runtimeConf LangChain4jOpenAiConfig.OpenAiConfig openAiConfig = correspondingOpenAiConfig(runtimeConfig, configName); if (openAiConfig.enableIntegration()) { - String apiKey = openAiConfig.apiKey(); - if (DUMMY_KEY.equals(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { + String apiKey = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); + if (StringUtil.isNullOrEmpty(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { throw new ConfigValidationException(createApiKeyConfigProblems(configName)); } ChatModelConfig chatModelConfig = openAiConfig.chatModel(); @@ -87,7 +88,7 @@ public Supplier streamingChatModel(LangChain4jOpenAi LangChain4jOpenAiConfig.OpenAiConfig openAiConfig = correspondingOpenAiConfig(runtimeConfig, configName); if (openAiConfig.enableIntegration()) { - String apiKey = openAiConfig.apiKey(); + String apiKey = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); if (DUMMY_KEY.equals(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { throw new ConfigValidationException(createApiKeyConfigProblems(configName)); } @@ -131,7 +132,7 @@ public Supplier embeddingModel(LangChain4jOpenAiConfig runtimeCo LangChain4jOpenAiConfig.OpenAiConfig openAiConfig = correspondingOpenAiConfig(runtimeConfig, configName); if (openAiConfig.enableIntegration()) { - String apiKey = openAiConfig.apiKey(); + String apiKey = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); if (DUMMY_KEY.equals(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { throw new ConfigValidationException(createApiKeyConfigProblems(configName)); } @@ -171,7 +172,7 @@ public Supplier moderationModel(LangChain4jOpenAiConfig runtime LangChain4jOpenAiConfig.OpenAiConfig openAiConfig = correspondingOpenAiConfig(runtimeConfig, configName); if (openAiConfig.enableIntegration()) { - String apiKey = openAiConfig.apiKey(); + String apiKey = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); if (DUMMY_KEY.equals(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { throw new ConfigValidationException(createApiKeyConfigProblems(configName)); } @@ -207,7 +208,7 @@ public Supplier imageModel(LangChain4jOpenAiConfig runtimeConfig, St LangChain4jOpenAiConfig.OpenAiConfig openAiConfig = correspondingOpenAiConfig(runtimeConfig, configName); if (openAiConfig.enableIntegration()) { - String apiKey = openAiConfig.apiKey(); + String apiKey = openAiConfig.apiKey().orElse(StringUtil.EMPTY_STRING); if (DUMMY_KEY.equals(apiKey) && OPENAI_BASE_URL.equals(openAiConfig.baseUrl())) { throw new ConfigValidationException(createApiKeyConfigProblems(configName)); } diff --git a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/config/LangChain4jOpenAiConfig.java b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/config/LangChain4jOpenAiConfig.java index dbf39c46b..8bcf23306 100644 --- a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/config/LangChain4jOpenAiConfig.java +++ b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/config/LangChain4jOpenAiConfig.java @@ -47,8 +47,8 @@ interface OpenAiConfig { /** * OpenAI API key */ - @WithDefault("dummy") // TODO: this should be Optional but Smallrye Config doesn't like it... - String apiKey(); + + Optional apiKey(); /** * OpenAI Organization ID (https://platform.openai.com/docs/api-reference/organization-optional) diff --git a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiImagesJsonRPCService.java b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiImagesJsonRPCService.java index 83cae2a71..d0dfd4f5f 100644 --- a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiImagesJsonRPCService.java +++ b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiImagesJsonRPCService.java @@ -7,8 +7,10 @@ import dev.langchain4j.data.image.Image; import dev.langchain4j.model.image.ImageModel; +import io.netty.util.internal.StringUtil; import io.quarkiverse.langchain4j.openai.QuarkusOpenAiImageModel; import io.quarkiverse.langchain4j.openai.runtime.config.LangChain4jOpenAiConfig; +import io.quarkiverse.langchain4j.openai.runtime.utils.ValidationUtil; import io.quarkiverse.langchain4j.runtime.NamedConfigUtil; import io.vertx.core.json.JsonObject; @@ -18,16 +20,12 @@ public class OpenAiImagesJsonRPCService { LangChain4jOpenAiConfig config; public JsonObject generate(String configuration, String modelName, String size, String prompt, String quality) { - if (NamedConfigUtil.isDefault(configuration) && config.defaultConfig().apiKey().equals("dummy")) { - // for non-default providers, we assume that Quarkus has verified by now that the api key is set - throw new RuntimeException("OpenAI API key is not configured. " + - "Please specify the key in the `quarkus.langchain4j.openai.api-key` configuration property."); - } + ValidationUtil.isValidDefaultConfig(configuration, config); LangChain4jOpenAiConfig.OpenAiConfig clientConfig = NamedConfigUtil.isDefault(configuration) ? config.defaultConfig() : config.namedConfig().get(configuration); ImageModel model = QuarkusOpenAiImageModel.builder() .baseUrl(clientConfig.baseUrl()) - .apiKey(clientConfig.apiKey()) + .apiKey(clientConfig.apiKey().orElse(StringUtil.EMPTY_STRING)) .timeout(clientConfig.timeout().orElse(Duration.ofSeconds(10))) .user(clientConfig.imageModel().user()) .maxRetries(clientConfig.maxRetries()) diff --git a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiModerationModelsJsonRPCService.java b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiModerationModelsJsonRPCService.java index 769ff4d55..37c5853e5 100644 --- a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiModerationModelsJsonRPCService.java +++ b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/devui/OpenAiModerationModelsJsonRPCService.java @@ -11,7 +11,9 @@ import dev.ai4j.openai4j.moderation.ModerationResponse; import dev.ai4j.openai4j.moderation.ModerationResult; import dev.langchain4j.internal.RetryUtils; +import io.netty.util.internal.StringUtil; import io.quarkiverse.langchain4j.openai.runtime.config.LangChain4jOpenAiConfig; +import io.quarkiverse.langchain4j.openai.runtime.utils.ValidationUtil; import io.quarkiverse.langchain4j.runtime.NamedConfigUtil; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -22,14 +24,11 @@ public class OpenAiModerationModelsJsonRPCService { LangChain4jOpenAiConfig config; public JsonObject moderate(String configuration, String modelName, String prompt) { - if (NamedConfigUtil.isDefault(configuration) && config.defaultConfig().apiKey().equals("dummy")) { - // for non-default configurations, we assume that Quarkus has verified by now that the api key is set - throw new RuntimeException("OpenAI API key is not configured. " + - "Please specify the key in the `quarkus.langchain4j.openai.api-key` configuration property."); - } + ValidationUtil.isValidDefaultConfig(configuration, config); LangChain4jOpenAiConfig.OpenAiConfig clientConfig = NamedConfigUtil.isDefault(configuration) ? config.defaultConfig() : config.namedConfig().get(configuration); - OpenAiClient client = OpenAiClient.builder().openAiApiKey(clientConfig.apiKey()).baseUrl(clientConfig.baseUrl()) + OpenAiClient client = OpenAiClient.builder().openAiApiKey(clientConfig.apiKey().orElse(StringUtil.EMPTY_STRING)) + .baseUrl(clientConfig.baseUrl()) .callTimeout(clientConfig.timeout().orElse(Duration.ofSeconds(10))) .connectTimeout(clientConfig.timeout().orElse(Duration.ofSeconds(10))) .readTimeout(clientConfig.timeout().orElse(Duration.ofSeconds(10))) diff --git a/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/utils/ValidationUtil.java b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/utils/ValidationUtil.java new file mode 100644 index 000000000..b27791007 --- /dev/null +++ b/model-providers/openai/openai-vanilla/runtime/src/main/java/io/quarkiverse/langchain4j/openai/runtime/utils/ValidationUtil.java @@ -0,0 +1,24 @@ +package io.quarkiverse.langchain4j.openai.runtime.utils; + +import io.netty.util.internal.StringUtil; +import io.quarkiverse.langchain4j.openai.runtime.config.LangChain4jOpenAiConfig; +import io.quarkiverse.langchain4j.runtime.NamedConfigUtil; + +public class ValidationUtil { + + private static final String OPENAI_BASE_URL = "https://api.openai.com/v1/"; + + public static void isValidDefaultConfig(String configuration, + LangChain4jOpenAiConfig config) { + String defaultAPiKey = config.defaultConfig().apiKey().orElse(StringUtil.EMPTY_STRING); + if (NamedConfigUtil.isDefault(configuration) && StringUtil.isNullOrEmpty(defaultAPiKey) + && OPENAI_BASE_URL.equals(config.defaultConfig().baseUrl())) { + // for non-default providers, we assume that Quarkus has verified by now that the api key is set + throw new RuntimeException("OpenAI API key is not configured. " + + "Please specify the key in the `quarkus.langchain4j.openai.api-key` configuration property."); + } + } + + private ValidationUtil() { + } +}