Skip to content

CAMEL-23238 - Camel-Google-VertexAI: Implement streaming, image generation, embeddings, multimodal, and streamRawPredict operations#22220

Merged
oscerd merged 3 commits intomainfrom
CAMEL-23238
Mar 24, 2026
Merged

CAMEL-23238 - Camel-Google-VertexAI: Implement streaming, image generation, embeddings, multimodal, and streamRawPredict operations#22220
oscerd merged 3 commits intomainfrom
CAMEL-23238

Conversation

@oscerd
Copy link
Contributor

@oscerd oscerd commented Mar 24, 2026

Description

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

  • If this is a large change, bug fix, or code improvement, I checked there is a JIRA issue filed for the change (usually before you start working on it).

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.
  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.

oscerd added 2 commits March 24, 2026 09:59
…ation, embeddings, multimodal, and streamRawPredict operations

Implement all 5 placeholder operations in GoogleVertexAIProducer:
- generateChatStreaming: uses generateContentStream for streaming Gemini responses
- generateImage: uses generateImages for Imagen model image generation
- generateEmbeddings: uses embedContent for single/batch text embeddings
- generateMultimodal: uses Content/Part for text+image/video/audio input
- streamRawPredict: uses streamRawPredictCallable for streaming partner models

Added 10 new header constants for operation-specific parameters.
Added 7 unit tests and 5 integration tests.
Updated component documentation with examples for all new operations.

Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
…ation, embeddings, multimodal, and streamRawPredict operations

Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@github-actions
Copy link
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using build-all, build-dependents, skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-google/camel-google-vertexai
  • dsl/camel-endpointdsl
All tested modules (72 modules)
  • Camel :: All Components Sync point
  • Camel :: All Components Sync point [pom]
  • Camel :: Assembly
  • Camel :: Assembly [pom]
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated) [maven-plugin]
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated) SUCCESS [ 1.806 s]
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Catalog [jar]
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Report Maven Plugin [maven-plugin]
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Camel Route Parser [jar]
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Console [jar]
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Dummy Component [jar]
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Lucene (deprecated) [jar]
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Maven [jar]
  • Camel :: Catalog :: Suggest
  • Camel :: Catalog :: Suggest [jar]
  • Camel :: Component DSL
  • Camel :: Component DSL [jar]
  • Camel :: Coverage
  • Camel :: Coverage [pom]
  • Camel :: Docs
  • Camel :: Docs [pom]
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL [jar]
  • Camel :: Endpoint DSL :: Support
  • Camel :: Endpoint DSL :: Support [jar]
  • Camel :: Google :: Vertex AI
  • Camel :: Google :: Vertex AI [jar]
  • Camel :: Integration Tests
  • Camel :: Integration Tests [jar]
  • Camel :: JBang :: Core
  • Camel :: JBang :: Core [jar]
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: Integration tests [jar]
  • Camel :: JBang :: MCP
  • Camel :: JBang :: MCP [jar]
  • Camel :: JBang :: Main
  • Camel :: JBang :: Main [jar]
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Edit [jar]
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Generate [jar]
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: Kubernetes [jar]
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: Route Parser [jar]
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Testing [jar]
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: JBang :: Plugin :: Validate [jar]
  • Camel :: Kamelet Main
  • Camel :: Kamelet Main [jar]
  • Camel :: Launcher
  • Camel :: Launcher [jar]
  • Camel :: Launcher :: Container
  • Camel :: Launcher :: Container [pom]
  • Camel :: YAML DSL
  • Camel :: YAML DSL [jar]
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Deserializers [jar]
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Maven Plugins [maven-plugin]
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator [jar]
  • Camel :: YAML DSL :: Validator Maven Plugin
  • Camel :: YAML DSL :: Validator Maven Plugin [maven-plugin]

Copy link
Contributor

@gnodet gnodet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Overall good work implementing the 5 missing operations. The documentation and integration tests are appreciated. However, there are several issues that need to be addressed before merging:

Critical Issues

  1. Duplicate constant: STREAMING_CHUNK_COUNT (line 91) and the new CHUNK_COUNT both resolve to the same header name CamelGoogleVertexAIChunkCount. This causes issues in the generated metadata (index 20 is missing in the generated JSON). One of them should be removed.

  2. Dead code in generateChatStreaming: The if ("chunks".equals(streamOutputMode)) / else branches do exactly the same thing. Either implement differentiated behavior for "chunks" mode or remove the branching.

  3. ServerStream not closed in streamRawPredict: Unlike generateChatStreaming which properly uses try-with-resources on ResponseStream, the streamRawPredict method iterates ServerStream without closing it.

Minor Issues

  1. Inconsistent @Metadata label on GENERATED_IMAGES: Uses "generateImage" while all other new constants use "producer generateImage".
  2. Silently swallowed exceptions in extractAnthropicStreamingTextContent: The catch (Exception e) block should at least log at TRACE level.
  3. Unchecked cast without validation in generateEmbeddings: (List<String>) body could fail with confusing ClassCastException.
  4. generateMultimodal body fallback logic is hard to follow: The condition is complex, consider simplifying.
  5. Hardcoded us-east5 fallback: Should be documented or made consistent with the existing rawPredict method.

@Metadata(label = "producer generateChatStreaming",
description = "The number of streaming chunks received", javaType = "Integer")
public static final String CHUNK_COUNT = "CamelGoogleVertexAIChunkCount";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

This new CHUNK_COUNT constant has the same header value (CamelGoogleVertexAIChunkCount) as the existing STREAMING_CHUNK_COUNT on line 91 (visible just above in this diff). This creates a duplicate constant for the same header name, which causes issues in the generated metadata (index 20 is skipped in the generated JSON).

Since STREAMING_CHUNK_COUNT was introduced in the same development cycle and hasn't been released yet, I'd suggest removing STREAMING_CHUNK_COUNT entirely and keeping only CHUNK_COUNT. If it was already released, deprecate STREAMING_CHUNK_COUNT and point to CHUNK_COUNT.

public static final String IMAGE_ASPECT_RATIO = "CamelGoogleVertexAIImageAspectRatio";

@Metadata(label = "generateImage",
description = "The generated images from an image generation operation",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Inconsistent @Metadata label: this uses "generateImage" while all other new constants use "producer <operationName>" (e.g., "producer generateImage", "producer generateEmbeddings"). Should be "producer generateImage" for consistency.

This also shows up in the generated JSON where the group/label is "generateImage" without the "producer" prefix.

Message message = getMessageForResponse(exchange);

if ("chunks".equals(streamOutputMode)) {
// In chunks mode, return the full accumulated text but include chunk count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Both branches of this if/else do exactly the same thing: message.setBody(fullText.toString()). The comment says "In chunks mode, return the full accumulated text but include chunk count" but the default mode also returns the same thing.

Either:

  • Implement differentiated behavior (e.g., chunks mode returns List<String> of individual chunks, or uses Camel streaming/reactive patterns), or
  • Remove the streamOutputMode check entirely since it's dead code.

As-is, this is misleading — users will set streamOutputMode=chunks expecting different behavior but get the exact same result.

if (body instanceof List) {
// Batch embeddings
List<String> texts = (List<String>) body;
response = client.models.embedContent(modelId, texts, embedConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Unchecked cast (List<String>) body — if the body is a List of non-String objects (e.g., List<Integer>), this will compile fine but throw a confusing ClassCastException later when the SDK tries to use the elements. Consider:

List<?> list = (List<?>) body;
List<String> texts = list.stream()
    .map(Object::toString)
    .collect(Collectors.toList());

or at least validate the element types.


// If body is byte[] and no media header was set, treat body as media
if (parts.isEmpty() || (prompt != null && mediaData == null && gcsUri == null)) {
Object body = exchange.getIn().getBody();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

This condition is hard to follow:

if (parts.isEmpty() || (prompt != null && mediaData == null && gcsUri == null))

The second clause means "prompt was given but no media headers were set" — so we check if body can serve as media. But if parts.isEmpty() is true, it means no prompt AND no media headers, so we also check body as a fallback.

This would be clearer split into explicit cases:

// If no media was provided via headers, check if body contains media
if (mediaData == null && gcsUri == null) {
    Object body = exchange.getIn().getBody();
    if (body instanceof byte[] bodyBytes && mediaMimeType != null) {
        parts.add(Part.fromBytes(bodyBytes, mediaMimeType));
    } else if (body instanceof String bodyStr && prompt == null) {
        parts.add(Part.fromText(bodyStr));
    }
}

String location = config.getLocation();
if ("global".equalsIgnoreCase(location)) {
location = "us-east5";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Hardcoding "us-east5" as a fallback for "global" location is fragile. This behavior should be documented (either in the code with a comment explaining why, or in the component docs). Is there a reason us-central1 isn't used as the default like elsewhere? Partner models may have different regional availability, but the choice should be transparent to the user.

.build();

ServerStream<HttpBody> stream = predictionClient.streamRawPredictCallable().call(request);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Unlike generateChatStreaming() which properly uses try-with-resources on ResponseStream, the ServerStream<HttpBody> here is never closed. If the iteration is interrupted (e.g., by an exception during append), the underlying gRPC stream won't be cleaned up.

Consider wrapping in a try block or calling stream.cancel() in a finally block.

}
} catch (Exception e) {
// Skip non-JSON or unparseable lines
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code on behalf of Guillaume Nodet

Silently swallowing all exceptions with an empty catch block. If a line starts with data: but contains malformed JSON, this will be silently ignored which could lead to truncated text output with no indication of the problem.

At minimum, add LOG.trace("Skipping unparseable SSE line: {}", jsonStr, e); so there's some visibility when debugging.

…ation, embeddings, multimodal, and streamRawPredict operations

Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd
Copy link
Contributor Author

oscerd commented Mar 24, 2026

It should be ok now.

@oscerd oscerd merged commit 74e7cf5 into main Mar 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants