This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a2ab828025e8 CAMEL-24345: camel-google-vertexai - apply the 
streamOutputMode and jsonMode options
a2ab828025e8 is described below

commit a2ab828025e8e94a8fcf27364a9be1cc58faeb75
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Aug 6 13:42:14 2026 +0200

    CAMEL-24345: camel-google-vertexai - apply the streamOutputMode and 
jsonMode options
    
    Both options were declared and documented but never read. 
streamOutputMode=chunks
    now produces a List<String> with one element per streamed chunk instead of 
the
    concatenated text; the default complete is unchanged. jsonMode=true sets the
    response MIME type to application/json. Also documents generateChat and
    generateCode as aliases of generateText, and fixes a NullPointerException in
    buildRawPredictRequestBody when no body is set.
    
    Closes #25362
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../camel-google/camel-google-vertexai/pom.xml     |  5 ++
 .../google/vertexai/GoogleVertexAIOperations.java  |  6 +-
 .../google/vertexai/GoogleVertexAIProducer.java    | 34 ++++++---
 .../GoogleVertexAIProducerOptionsTest.java         | 81 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    | 12 ++++
 5 files changed, 128 insertions(+), 10 deletions(-)

diff --git a/components/camel-google/camel-google-vertexai/pom.xml 
b/components/camel-google/camel-google-vertexai/pom.xml
index a5ef89f507c8..d24191d022b4 100644
--- a/components/camel-google/camel-google-vertexai/pom.xml
+++ b/components/camel-google/camel-google-vertexai/pom.xml
@@ -103,5 +103,10 @@
             <artifactId>camel-test-junit6</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIOperations.java
 
b/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIOperations.java
index 3644c852e615..429d3d791d20 100644
--- 
a/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIOperations.java
+++ 
b/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIOperations.java
@@ -30,7 +30,8 @@ public enum GoogleVertexAIOperations {
     generateText,
 
     /**
-     * Generate chat response using Gemini models with conversation history.
+     * Generate a chat response using Gemini models. Alias of {@link 
#generateText}: the request is built from the same
+     * prompt and configuration, the operation name only documents the intent 
of the route.
      */
     generateChat,
 
@@ -50,7 +51,8 @@ public enum GoogleVertexAIOperations {
     generateEmbeddings,
 
     /**
-     * Generate code using Gemini or code-specialized models.
+     * Generate code using Gemini or code-specialized models. Alias of {@link 
#generateText}: the model is selected with
+     * the modelId option, the operation name only documents the intent of the 
route.
      */
     generateCode,
 
diff --git 
a/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducer.java
 
b/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducer.java
index 25fabca07a4d..5416933feceb 100644
--- 
a/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducer.java
+++ 
b/components/camel-google/camel-google-vertexai/src/main/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducer.java
@@ -51,6 +51,8 @@ public class GoogleVertexAIProducer extends DefaultProducer {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(GoogleVertexAIProducer.class);
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    private static final String CHUNKS_OUTPUT_MODE = "chunks";
+    private static final String JSON_MIME_TYPE = "application/json";
 
     private final GoogleVertexAIEndpoint endpoint;
 
@@ -154,22 +156,25 @@ public class GoogleVertexAIProducer extends 
DefaultProducer {
         try (ResponseStream<GenerateContentResponse> stream
                 = client.models.generateContentStream(modelId, prompt, 
config)) {
 
-            StringBuilder fullText = new StringBuilder();
-            int chunkCount = 0;
+            List<String> chunks = new ArrayList<>();
             GenerateContentResponse lastResponse = null;
 
             for (GenerateContentResponse chunk : stream) {
-                chunkCount++;
                 lastResponse = chunk;
                 String chunkText = chunk.text();
                 if (chunkText != null) {
-                    fullText.append(chunkText);
+                    chunks.add(chunkText);
                 }
             }
 
             Message message = getMessageForResponse(exchange);
-            message.setBody(fullText.toString());
-            message.setHeader(GoogleVertexAIConstants.CHUNK_COUNT, chunkCount);
+            if 
(CHUNKS_OUTPUT_MODE.equalsIgnoreCase(determineStreamOutputMode(exchange))) {
+                // one element per chunk, so the route can split them
+                message.setBody(chunks);
+            } else {
+                message.setBody(String.join("", chunks));
+            }
+            message.setHeader(GoogleVertexAIConstants.CHUNK_COUNT, 
chunks.size());
 
             if (lastResponse != null) {
                 setMetadataHeaders(exchange, lastResponse);
@@ -177,6 +182,14 @@ public class GoogleVertexAIProducer extends 
DefaultProducer {
         }
     }
 
+    String determineStreamOutputMode(Exchange exchange) {
+        String mode = 
exchange.getIn().getHeader(GoogleVertexAIConstants.STREAM_OUTPUT_MODE, 
String.class);
+        if (mode == null) {
+            mode = endpoint.getConfiguration().getStreamOutputMode();
+        }
+        return mode;
+    }
+
     private void generateImage(Exchange exchange) throws Exception {
         String prompt = getPrompt(exchange);
 
@@ -505,7 +518,8 @@ public class GoogleVertexAIProducer extends DefaultProducer 
{
         }
 
         throw new IllegalArgumentException(
-                "Request body must be a JSON String, Map, or plain text 
prompt. Got: " + body.getClass().getName());
+                "Request body must be a JSON String, Map, or plain text 
prompt. Got: "
+                                           + (body == null ? "no body" : 
body.getClass().getName()));
     }
 
     /**
@@ -630,7 +644,7 @@ public class GoogleVertexAIProducer extends DefaultProducer 
{
         return prompt;
     }
 
-    private GenerateContentConfig buildConfig(Exchange exchange) {
+    GenerateContentConfig buildConfig(Exchange exchange) {
         GoogleVertexAIConfiguration config = endpoint.getConfiguration();
 
         GenerateContentConfig.Builder configBuilder = 
GenerateContentConfig.builder();
@@ -676,6 +690,10 @@ public class GoogleVertexAIProducer extends 
DefaultProducer {
             configBuilder.candidateCount(candidateCount);
         }
 
+        if (config.isJsonMode()) {
+            configBuilder.responseMimeType(JSON_MIME_TYPE);
+        }
+
         return configBuilder.build();
     }
 
diff --git 
a/components/camel-google/camel-google-vertexai/src/test/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducerOptionsTest.java
 
b/components/camel-google/camel-google-vertexai/src/test/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducerOptionsTest.java
new file mode 100644
index 000000000000..e3e7c74de94f
--- /dev/null
+++ 
b/components/camel-google/camel-google-vertexai/src/test/java/org/apache/camel/component/google/vertexai/GoogleVertexAIProducerOptionsTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.vertexai;
+
+import com.google.genai.types.GenerateContentConfig;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies that the producer applies the options that describe the shape of 
the request and of the response.
+ */
+class GoogleVertexAIProducerOptionsTest {
+
+    private static final String BASE = 
"google-vertexai:my-project:us-central1:gemini-2.0-flash";
+
+    private DefaultCamelContext context;
+
+    @AfterEach
+    void tearDown() {
+        if (context != null) {
+            context.stop();
+        }
+    }
+
+    private GoogleVertexAIProducer producer(String query) throws Exception {
+        context = new DefaultCamelContext();
+        context.start();
+        GoogleVertexAIEndpoint endpoint = context.getEndpoint(BASE + query, 
GoogleVertexAIEndpoint.class);
+        return new GoogleVertexAIProducer(endpoint);
+    }
+
+    @Test
+    void jsonModeAsksTheModelForJson() throws Exception {
+        GenerateContentConfig config = 
producer("?jsonMode=true").buildConfig(new DefaultExchange(context));
+
+        assertThat(config.responseMimeType()).contains("application/json");
+    }
+
+    @Test
+    void jsonModeIsOffByDefault() throws Exception {
+        GenerateContentConfig config = producer("").buildConfig(new 
DefaultExchange(context));
+
+        assertThat(config.responseMimeType()).isEmpty();
+    }
+
+    @Test
+    void theStreamOutputModeComesFromTheConfigurationOrTheHeader() throws 
Exception {
+        GoogleVertexAIProducer producer = producer("?streamOutputMode=chunks");
+
+        Exchange exchange = new DefaultExchange(context);
+        
assertThat(producer.determineStreamOutputMode(exchange)).isEqualTo("chunks");
+
+        // the header wins over the endpoint option
+        exchange.getIn().setHeader(GoogleVertexAIConstants.STREAM_OUTPUT_MODE, 
"complete");
+        
assertThat(producer.determineStreamOutputMode(exchange)).isEqualTo("complete");
+    }
+
+    @Test
+    void theStreamOutputModeDefaultsToComplete() throws Exception {
+        assertThat(producer("").determineStreamOutputMode(new 
DefaultExchange(context))).isEqualTo("complete");
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index caed74bf9189..c79eee332eb9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -1722,3 +1722,15 @@ Because the Gmail API does not return the parsed 
`payload` for the `RAW` format,
 and `...MessageId` headers — those values are part of the RFC 2822 content 
that is now in the body.
 `CamelGoogleMailStreamId`, `...ThreadId` and `...LabelIds` are still set. 
Routes that need the parsed
 headers should keep the default `raw=false`.
+
+=== camel-google-vertexai - the streamOutputMode and jsonMode options are now 
applied
+
+Both options were declared and documented but never read, so they had no 
effect. They now do what
+they say:
+
+* `streamOutputMode=chunks` makes `generateChatStreaming` produce a 
`List<String>`, one element per
+  streamed chunk, instead of the concatenated text. The default `complete` is 
unchanged, so routes
+  that never set the option keep receiving a `String`. The value can also be 
set per message with the
+  `CamelGoogleVertexAIStreamOutputMode` header.
+* `jsonMode=true` sets the response MIME type of the request to 
`application/json`, so the model is
+  asked to answer with JSON. The default `false` leaves the request untouched.

Reply via email to