From 45d37fa2b1dc5096d0ffa867339f4e85f8116b56 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Tue, 7 Apr 2026 17:31:46 -0300 Subject: [PATCH 1/2] feat(dotAI): add Google Vertex AI provider support (chat only) --- dotCMS/pom.xml | 15 +++++++ .../langchain4j/LangChain4jModelFactory.java | 45 ++++++++++++++++--- .../ai/client/langchain4j/ProviderConfig.java | 8 ++-- .../LangChain4jModelFactoryTest.java | 27 +++++++++++ 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/dotCMS/pom.xml b/dotCMS/pom.xml index 667b2f30bd30..8a7d276147b3 100644 --- a/dotCMS/pom.xml +++ b/dotCMS/pom.xml @@ -518,6 +518,21 @@ dev.langchain4j langchain4j-bedrock + + + dev.langchain4j + langchain4j-vertex-ai-gemini + + + org.checkerframework + checker-qual + + + com.google.android + annotations + + + jakarta.inject jakarta.inject-api diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java index 5b43a5c5805b..bb6b092867a1 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java +++ b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java @@ -7,6 +7,7 @@ import dev.langchain4j.model.bedrock.BedrockChatRequestParameters; import dev.langchain4j.model.bedrock.BedrockCohereEmbeddingModel; import dev.langchain4j.model.bedrock.BedrockTitanEmbeddingModel; +import dev.langchain4j.model.vertexai.VertexAiGeminiChatModel; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.embedding.EmbeddingModel; @@ -31,8 +32,8 @@ * To add support for a new provider, add a case to each switch block below. * No other class needs to change. * - *

Supported providers: {@code openai}, {@code azure_openai}, {@code bedrock} - *

Planned: {@code vertex_ai} + *

Supported providers: {@code openai}, {@code azure_openai}, {@code bedrock}, {@code vertex_ai} + *

Note: {@code vertex_ai} supports chat only; embeddings and image are not available via LangChain4J. */ public class LangChain4jModelFactory { @@ -49,7 +50,8 @@ public static ChatModel buildChatModel(final ProviderConfig config) { return build(config, "chat", LangChain4jModelFactory::buildOpenAiChatModel, LangChain4jModelFactory::buildAzureOpenAiChatModel, - LangChain4jModelFactory::buildBedrockChatModel); + LangChain4jModelFactory::buildBedrockChatModel, + LangChain4jModelFactory::buildVertexAiChatModel); } /** @@ -63,7 +65,8 @@ public static EmbeddingModel buildEmbeddingModel(final ProviderConfig config) { return build(config, "embeddings", LangChain4jModelFactory::buildOpenAiEmbeddingModel, LangChain4jModelFactory::buildAzureOpenAiEmbeddingModel, - LangChain4jModelFactory::buildBedrockEmbeddingModel); + LangChain4jModelFactory::buildBedrockEmbeddingModel, + LangChain4jModelFactory::buildVertexAiEmbeddingModel); } /** @@ -77,14 +80,16 @@ public static ImageModel buildImageModel(final ProviderConfig config) { return build(config, "image", LangChain4jModelFactory::buildOpenAiImageModel, LangChain4jModelFactory::buildAzureOpenAiImageModel, - LangChain4jModelFactory::buildBedrockImageModel); + LangChain4jModelFactory::buildBedrockImageModel, + LangChain4jModelFactory::buildVertexAiImageModel); } private static T build(final ProviderConfig config, final String modelType, final Function openAiFn, final Function azureOpenAiFn, - final Function bedrockFn) { + final Function bedrockFn, + final Function vertexAiFn) { if (config == null || config.provider() == null) { throw new IllegalArgumentException("ProviderConfig or provider name is null for model type: " + modelType); } @@ -95,9 +100,11 @@ private static T build(final ProviderConfig config, return azureOpenAiFn.apply(config); case "bedrock": return bedrockFn.apply(config); + case "vertex_ai": + return vertexAiFn.apply(config); default: throw new IllegalArgumentException("Unsupported " + modelType + " provider: " - + config.provider() + ". Supported: openai, azure_openai, bedrock"); + + config.provider() + ". Supported: openai, azure_openai, bedrock, vertex_ai"); } } @@ -240,4 +247,28 @@ private static ImageModel buildBedrockImageModel(final ProviderConfig config) { "Image generation is not supported for Bedrock provider via LangChain4J"); } + // ── Vertex AI builders ──────────────────────────────────────────────────── + + private static ChatModel buildVertexAiChatModel(final ProviderConfig config) { + final VertexAiGeminiChatModel.VertexAiGeminiChatModelBuilder builder = + VertexAiGeminiChatModel.builder() + .project(config.projectId()) + .location(config.location()) + .modelName(config.model()); + if (config.maxRetries() != null) builder.maxRetries(config.maxRetries()); + if (config.temperature() != null) builder.temperature(config.temperature().floatValue()); + if (config.maxTokens() != null) builder.maxOutputTokens(config.maxTokens()); + return builder.build(); + } + + private static EmbeddingModel buildVertexAiEmbeddingModel(final ProviderConfig config) { + throw new UnsupportedOperationException( + "Embeddings are not supported for Vertex AI provider via LangChain4J"); + } + + private static ImageModel buildVertexAiImageModel(final ProviderConfig config) { + throw new UnsupportedOperationException( + "Image generation is not supported for Vertex AI provider via LangChain4J"); + } + } diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/ProviderConfig.java b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/ProviderConfig.java index 0988cf9ad4af..70e74bc51ae5 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/ProviderConfig.java +++ b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/ProviderConfig.java @@ -40,11 +40,13 @@ *

  • {@code embeddingInputType} – Cohere only: {@code search_document} (default) or {@code search_query}
  • * * - *

    Google Vertex AI: + *

    Google Vertex AI (chat only — embeddings and image not supported via LangChain4J): *

    + *

    Auth is handled automatically via Application Default Credentials (ADC). + * No API key is required. */ @Value.Immutable @JsonSerialize(as = ImmutableProviderConfig.class) diff --git a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java index 4ab5a25d9fb9..90650145d7a5 100644 --- a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java +++ b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java @@ -33,6 +33,12 @@ public void test_buildChatModel_bedrock_returnsModel() { assertNotNull(model); } + @Test + public void test_buildChatModel_vertexAi_returnsModel() { + final ChatModel model = LangChain4jModelFactory.buildChatModel(vertexAiConfig("gemini-1.5-pro")); + assertNotNull(model); + } + @Test public void test_buildChatModel_unknownProvider_throws() { final ProviderConfig config = ImmutableProviderConfig.builder() @@ -72,6 +78,12 @@ public void test_buildEmbeddingModel_bedrock_cohere_returnsModel() { assertNotNull(model); } + @Test + public void test_buildEmbeddingModel_vertexAi_throws() { + assertThrows(UnsupportedOperationException.class, + () -> LangChain4jModelFactory.buildEmbeddingModel(vertexAiConfig("text-embedding-004"))); + } + @Test public void test_buildEmbeddingModel_unknownProvider_throws() { final ProviderConfig config = ImmutableProviderConfig.builder() @@ -105,6 +117,12 @@ public void test_buildImageModel_bedrock_throws() { () -> LangChain4jModelFactory.buildImageModel(bedrockConfig("stability.stable-diffusion-xl-v1"))); } + @Test + public void test_buildImageModel_vertexAi_throws() { + assertThrows(UnsupportedOperationException.class, + () -> LangChain4jModelFactory.buildImageModel(vertexAiConfig("imagen-3.0"))); + } + @Test public void test_buildImageModel_unknownProvider_throws() { final ProviderConfig config = ImmutableProviderConfig.builder() @@ -123,6 +141,15 @@ private static ProviderConfig openAiConfig(final String model) { .build(); } + private static ProviderConfig vertexAiConfig(final String model) { + return ImmutableProviderConfig.builder() + .provider("vertex_ai") + .model(model) + .projectId("my-gcp-project") + .location("us-central1") + .build(); + } + private static ProviderConfig bedrockConfig(final String model) { return ImmutableProviderConfig.builder() .provider("bedrock") From 961776e88af85cd13ddab5eeee0e0c296f98e4b2 Mon Sep 17 00:00:00 2001 From: ihoffmann-dot Date: Tue, 21 Apr 2026 20:51:02 -0300 Subject: [PATCH 2/2] fix(ai): vertex validation order, remove CI-unsafe test, align streaming params --- .../ai/client/langchain4j/LangChain4jModelFactory.java | 7 +++++++ .../ai/client/langchain4j/LangChain4jModelFactoryTest.java | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java index e8dcb8120ca9..c7af07b3e8ad 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java +++ b/dotCMS/src/main/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactory.java @@ -126,6 +126,11 @@ private static T build(final ProviderConfig config, validateBedrock(config, modelType); return bedrockFn.apply(config); case "vertex_ai": + // Throw UnsupportedOperationException before validating config fields — + // the operation itself is unsupported regardless of how the config is set. + if (!"chat".equals(modelType)) { + return vertexAiFn.apply(config); + } validateVertexAi(config, modelType); return vertexAiFn.apply(config); default: @@ -369,6 +374,7 @@ private static ChatModel buildVertexAiChatModel(final ProviderConfig config) { if (config.maxRetries() != null) builder.maxRetries(config.maxRetries()); if (config.temperature() != null) builder.temperature(config.temperature().floatValue()); if (config.maxTokens() != null) builder.maxOutputTokens(config.maxTokens()); + // timeout and streaming maxRetries not exposed by VertexAiGemini builders in LangChain4J return builder.build(); } @@ -380,6 +386,7 @@ private static StreamingChatModel buildVertexAiStreamingChatModel(final Provider .modelName(config.model()); if (config.temperature() != null) builder.temperature(config.temperature().floatValue()); if (config.maxTokens() != null) builder.maxOutputTokens(config.maxTokens()); + // maxRetries and timeout not exposed by VertexAiGeminiStreamingChatModel builder in LangChain4J return builder.build(); } diff --git a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java index 2dead0d74abc..64b25b376985 100644 --- a/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java +++ b/dotCMS/src/test/java/com/dotcms/ai/client/langchain4j/LangChain4jModelFactoryTest.java @@ -82,12 +82,6 @@ public void test_buildChatModel_bedrock_missingRegion_throws() { assertThrows(IllegalArgumentException.class, () -> LangChain4jModelFactory.buildChatModel(config)); } - @Test - public void test_buildChatModel_vertexAi_returnsModel() { - final ChatModel model = LangChain4jModelFactory.buildChatModel(vertexAiConfig("gemini-1.5-pro")); - assertNotNull(model); - } - @Test public void test_buildChatModel_vertexAi_missingProjectId_throws() { final ProviderConfig config = ImmutableProviderConfig.builder()