This is an automated email from the ASF dual-hosted git repository.
oscerd 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 016eea194a2a CAMEL-23463: camel-aws-bedrock - reject non-text models
in invokeTextModel with a clear error (#24252)
016eea194a2a is described below
commit 016eea194a2aa39bb675414628958f86eb16e4c0
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jun 25 18:20:49 2026 +0200
CAMEL-23463: camel-aws-bedrock - reject non-text models in invokeTextModel
with a clear error (#24252)
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../src/main/docs/aws-bedrock-component.adoc | 6 +
.../aws2/bedrock/runtime/BedrockProducer.java | 36 +++++
...BedrockInvokeTextModelUnsupportedModelTest.java | 156 +++++++++++++++++++++
3 files changed, 198 insertions(+)
diff --git
a/components/camel-aws/camel-aws-bedrock/src/main/docs/aws-bedrock-component.adoc
b/components/camel-aws/camel-aws-bedrock/src/main/docs/aws-bedrock-component.adoc
index 98a778ca7f7e..14af8e96e6c5 100644
---
a/components/camel-aws/camel-aws-bedrock/src/main/docs/aws-bedrock-component.adoc
+++
b/components/camel-aws/camel-aws-bedrock/src/main/docs/aws-bedrock-component.adoc
@@ -83,6 +83,12 @@ Camel-AWS Bedrock component provides the following operation
on the producer sid
- converseStream
- applyGuardrail
+The operation must match the model type. Text-generation models are used with
`invokeTextModel`, image-generation
+models (for example `amazon.titan-image-generator-v1`,
`amazon.nova-canvas-v1:0`, the `stability.*` models) with
+`invokeImageModel`, and embedding models (for example
`amazon.titan-embed-text-v1`, `cohere.embed-english-v3`) with
+`invokeEmbeddingsModel`. Invoking `invokeTextModel` with an image, embedding,
rerank or video/speech model fails fast
+with an `IllegalArgumentException` that names the model and points to the
operation to use instead.
+
=== Streaming Support
The streaming operations (`invokeTextModelStreaming`,
`invokeImageModelStreaming`, `invokeEmbeddingsModelStreaming`) enable real-time
streaming of model responses, providing lower latency and better user
experience for interactive applications.
diff --git
a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockProducer.java
b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockProducer.java
index 1c40bc6cc730..d5e2ffa7f5a2 100644
---
a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockProducer.java
+++
b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockProducer.java
@@ -379,6 +379,42 @@ public class BedrockProducer extends DefaultProducer {
}
break;
+ // Image generation models — use the invokeImageModel operation
instead
+ case "amazon.titan-image-generator-v1":
+ case "amazon.titan-image-generator-v2:0":
+ case "amazon.nova-canvas-v1:0":
+ case "stability.sd3-5-large-v1:0":
+ case "stability.stable-image-control-sketch-v1:0":
+ case "stability.stable-image-control-structure-v1:0":
+ case "stability.stable-image-core-v1:1":
+ throw new IllegalArgumentException(
+ "Model " + modelId
+ + " is an image generation
model and cannot be used with the invokeTextModel operation. Use the
invokeImageModel operation instead.");
+
+ // Embedding models — use the invokeEmbeddingsModel operation
instead
+ case "amazon.titan-embed-text-v1":
+ case "amazon.titan-embed-image-v1":
+ case "cohere.embed-english-v3":
+ case "cohere.embed-multilingual-v3":
+ throw new IllegalArgumentException(
+ "Model " + modelId
+ + " is an embedding model
and cannot be used with the invokeTextModel operation. Use the
invokeEmbeddingsModel operation instead.");
+
+ // Rerank models — not supported by the invokeTextModel operation
+ case "amazon.rerank-v1:0":
+ case "cohere.rerank-v3-5:0":
+ throw new IllegalArgumentException(
+ "Model " + modelId
+ + " is a rerank model and
cannot be used with the invokeTextModel operation.");
+
+ // Video and speech models — not supported by the invokeTextModel
operation
+ case "amazon.nova-reel-v1:0":
+ case "amazon.nova-reel-v1:1":
+ case "amazon.nova-sonic-v1:0":
+ throw new IllegalArgumentException(
+ "Model " + modelId
+ + " is a video/speech
generation model and cannot be used with the invokeTextModel operation.");
+
default:
throw new IllegalStateException("Unexpected model: " +
modelId);
}
diff --git
a/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockInvokeTextModelUnsupportedModelTest.java
b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockInvokeTextModelUnsupportedModelTest.java
new file mode 100644
index 000000000000..63e0bfcf34bf
--- /dev/null
+++
b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/BedrockInvokeTextModelUnsupportedModelTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.aws2.bedrock.runtime;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.component.aws2.bedrock.BedrockModels;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.SimpleRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
+import software.amazon.awssdk.services.bedrockruntime.model.InvokeModelRequest;
+import
software.amazon.awssdk.services.bedrockruntime.model.InvokeModelResponse;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
+
+/**
+ * Unit tests asserting that the {@code invokeTextModel} operation rejects
image, embedding, rerank and video/speech
+ * models with a clear, actionable {@link IllegalArgumentException} instead of
the confusing
+ * {@code IllegalStateException("Unexpected model: ...")} that these models —
all advertised in
+ * {@code BedrockConfiguration.modelId}'s {@code @UriParam(enums=...)} —
previously produced (CAMEL-23463). Uses a
+ * mocked {@link BedrockRuntimeClient}, so no AWS credentials or network
access are required.
+ */
+@ExtendWith(MockitoExtension.class)
+public class BedrockInvokeTextModelUnsupportedModelTest {
+
+ @Mock
+ private BedrockRuntimeClient client;
+
+ private CamelContext camelContext;
+ private ProducerTemplate template;
+
+ @BeforeEach
+ public void setup() {
+ lenient().when(client.invokeModel(any(InvokeModelRequest.class)))
+
.thenReturn(InvokeModelResponse.builder().body(SdkBytes.fromUtf8String("{}")).build());
+
+ SimpleRegistry registry = new SimpleRegistry();
+ registry.bind("bedrockClient", client);
+ camelContext = new DefaultCamelContext(registry);
+ camelContext.start();
+ template = camelContext.createProducerTemplate();
+ }
+
+ @AfterEach
+ public void teardown() {
+ if (template != null) {
+ template.stop();
+ }
+ if (camelContext != null) {
+ camelContext.stop();
+ }
+ }
+
+ static Stream<Arguments> nonTextModels() {
+ return Stream.of(
+ // Image generation models -> invokeImageModel
+ Arguments.of(BedrockModels.TITAN_IMAGE_GENERATOR_V1.model,
"invokeImageModel"),
+ Arguments.of(BedrockModels.TITAN_IMAGE_GENERATOR_V2.model,
"invokeImageModel"),
+ Arguments.of(BedrockModels.NOVA_CANVAS_V1.model,
"invokeImageModel"),
+ Arguments.of(BedrockModels.STABLE_DIFFUSION_3_5_LARGE.model,
"invokeImageModel"),
+ Arguments.of(BedrockModels.STABLE_IMAGE_CONTROL_SKETCH.model,
"invokeImageModel"),
+
Arguments.of(BedrockModels.STABLE_IMAGE_CONTROL_STRUCTURE.model,
"invokeImageModel"),
+ Arguments.of(BedrockModels.STABLE_IMAGE_CORE.model,
"invokeImageModel"),
+ // Embedding models -> invokeEmbeddingsModel
+ Arguments.of(BedrockModels.TITAN_EMBEDDINGS_G1.model,
"invokeEmbeddingsModel"),
+
Arguments.of(BedrockModels.TITAN_MULTIMODAL_EMBEDDINGS_G1.model,
"invokeEmbeddingsModel"),
+ Arguments.of(BedrockModels.COHERE_EMBED_ENGLISH_V3.model,
"invokeEmbeddingsModel"),
+ Arguments.of(BedrockModels.COHERE_EMBED_MULTILINGUAL_V3.model,
"invokeEmbeddingsModel"),
+ // Rerank models -> no dedicated operation, just reject clearly
+ Arguments.of(BedrockModels.RERANK_V1.model, null),
+ Arguments.of(BedrockModels.COHERE_RERANK_V3_5.model, null),
+ // Video / speech models -> no dedicated operation, just
reject clearly
+ Arguments.of(BedrockModels.NOVA_REEL_V1.model, null),
+ Arguments.of(BedrockModels.NOVA_REEL_V1_1.model, null),
+ Arguments.of(BedrockModels.NOVA_SONIC_V1.model, null));
+ }
+
+ @ParameterizedTest
+ @MethodSource("nonTextModels")
+ public void invokeTextModelRejectsNonTextModelWithActionableMessage(String
modelId, String expectedOperationHint) {
+ Exchange exchange = template.send(endpointUri(modelId), ex -> {
+ ex.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE,
"application/json");
+
ex.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE,
"application/json");
+ ex.getMessage().setBody("{}");
+ });
+
+ Throwable cause = exchange.getException();
+ assertNotNull(cause, "invokeTextModel should fail for non-text model "
+ modelId);
+ assertInstanceOf(IllegalArgumentException.class, cause,
+ "Expected a clear IllegalArgumentException for " + modelId + "
but was: " + cause);
+ assertTrue(cause.getMessage().contains(modelId),
+ "Error message should name the offending model — was: " +
cause.getMessage());
+ assertTrue(cause.getMessage().contains("invokeTextModel"),
+ "Error message should mention the invokeTextModel operation —
was: " + cause.getMessage());
+ if (expectedOperationHint != null) {
+ assertTrue(cause.getMessage().contains(expectedOperationHint),
+ "Error message should point to the " +
expectedOperationHint + " operation — was: " + cause.getMessage());
+ }
+ }
+
+ @Test
+ public void invokeTextModelStillWorksForTextModel() {
+ Exchange exchange =
template.send(endpointUri(BedrockModels.TITAN_TEXT_EXPRESS_V1.model), ex -> {
+ ex.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE,
"application/json");
+
ex.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE,
"application/json");
+ ex.getMessage().setBody("{}");
+ });
+
+ assertNull(exchange.getException(), "A text model must not be rejected
by invokeTextModel");
+ assertEquals("{}", exchange.getMessage().getBody(String.class),
+ "Titan text response should be returned as-is");
+ }
+
+ private static String endpointUri(String modelId) {
+ return "aws-bedrock://label"
+ + "?bedrockRuntimeClient=#bedrockClient"
+ + "&operation=invokeTextModel"
+ + "&modelId=" + modelId
+ + "®ion=us-east-1"
+ + "&accessKey=unused"
+ + "&secretKey=unused";
+ }
+}