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
+               + "&region=us-east-1"
+               + "&accessKey=unused"
+               + "&secretKey=unused";
+    }
+}

Reply via email to