You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2024/03/13 10:11:45 UTC

(camel) branch main updated: CAMEL-20560 - Camel-AWS-Bedrock: Support Anthropic models (#13462)

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

acosentino 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 1a171ac0e6b CAMEL-20560 - Camel-AWS-Bedrock: Support Anthropic models (#13462)
1a171ac0e6b is described below

commit 1a171ac0e6bd494b31434f770b14b1312b84149e
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Wed Mar 13 11:11:39 2024 +0100

    CAMEL-20560 - Camel-AWS-Bedrock: Support Anthropic models (#13462)
    
    Signed-off-by: Andrea Cosentino <an...@gmail.com>
---
 .../component/aws2/bedrock/BedrockModels.java      |   5 +-
 .../aws2/bedrock/runtime/BedrockProducer.java      |  26 ++++
 .../runtime/integration/BedrockProducerIT.java     | 150 ++++++++++++++++++++-
 3 files changed, 174 insertions(+), 7 deletions(-)

diff --git a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java
index ef994e22ff5..d8d9bf561a9 100644
--- a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java
+++ b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java
@@ -25,8 +25,9 @@ public enum BedrockModels {
     JURASSIC2_ULTRA("ai21.j2-ultra-v1"),
     JURASSIC2_MID("ai21.j2-mid-v1"),
     ANTROPHIC_CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"),
-    ANTROPHIC_CLAUDE_INSTANT_V2("anthropic.claude-v2"),
-    ANTROPHIC_CLAUDE_INSTANT_V2_1("anthropic.claude-v2:1");
+    ANTROPHIC_CLAUDE_V2("anthropic.claude-v2"),
+    ANTROPHIC_CLAUDE_V2_1("anthropic.claude-v2:1"),
+    ANTROPHIC_CLAUDE_V3("anthropic.claude-3-sonnet-20240229-v1:0");
 
     public final String model;
 
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 e79c7138234..e553972684f 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
@@ -242,6 +242,20 @@ public class BedrockProducer extends DefaultProducer {
                     throw new RuntimeException(e);
                 }
             }
+            case "anthropic.claude-instant-v1", "anthropic.claude-v2", "anthropic.claude-v2:1" -> {
+                try {
+                    setAnthropicText(result, message);
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            case "anthropic.claude-3-sonnet-20240229-v1:0" -> {
+                try {
+                    setAnthropicV3Text(result, message);
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
             default -> throw new IllegalStateException("Unexpected value: " + getConfiguration().getModelId());
         }
     }
@@ -256,6 +270,18 @@ public class BedrockProducer extends DefaultProducer {
         message.setBody(jsonString.get("completions"));
     }
 
+    private void setAnthropicText(InvokeModelResponse result, Message message) throws JsonProcessingException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonString = mapper.readTree(result.body().asUtf8String());
+        message.setBody(jsonString.get("completion"));
+    }
+
+    private void setAnthropicV3Text(InvokeModelResponse result, Message message) throws JsonProcessingException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonString = mapper.readTree(result.body().asUtf8String());
+        message.setBody(jsonString);
+    }
+
     public static Message getMessageForResponse(final Exchange exchange) {
         return exchange.getMessage();
     }
diff --git a/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/integration/BedrockProducerIT.java b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/integration/BedrockProducerIT.java
index aa2044461e3..bfe93173df8 100644
--- a/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/integration/BedrockProducerIT.java
+++ b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/runtime/integration/BedrockProducerIT.java
@@ -17,10 +17,7 @@
 package org.apache.camel.component.aws2.bedrock.runtime.integration;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.IntNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.node.*;
 import org.apache.camel.EndpointInject;
 import org.apache.camel.Exchange;
 import org.apache.camel.ProducerTemplate;
@@ -220,6 +217,125 @@ class BedrockProducerIT extends CamelTestSupport {
         MockEndpoint.assertIsSatisfied(context);
     }
 
+    @Test
+    public void testInvokeAnthropicV1Model() throws InterruptedException {
+
+        result.expectedMessageCount(1);
+        final Exchange result = template.send("direct:send_anthropic_v1_model", exchange -> {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode rootNode = mapper.createObjectNode();
+            rootNode.putIfAbsent("prompt",
+                    new TextNode("Human: Can you tell the history of Mayflower? \\n\\Assistant:"));
+
+            ArrayNode stopSequences = mapper.createArrayNode();
+            stopSequences.add("Human:");
+            rootNode.putIfAbsent("max_tokens_to_sample", new IntNode(300));
+            rootNode.putIfAbsent("stop_sequences", stopSequences);
+            rootNode.putIfAbsent("temperature", new DoubleNode(0.5));
+            rootNode.putIfAbsent("top_p", new IntNode(1));
+            rootNode.putIfAbsent("top_k", new IntNode(250));
+            rootNode.putIfAbsent("anthropic_version", new TextNode("bedrock-2023-05-31"));
+
+            exchange.getMessage().setBody(mapper.writer().writeValueAsString(rootNode));
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE, "application/json");
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, "application/json");
+        });
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    public void testInvokeAnthropicV2Model() throws InterruptedException {
+
+        result.expectedMessageCount(1);
+        final Exchange result = template.send("direct:send_anthropic_v2_model", exchange -> {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode rootNode = mapper.createObjectNode();
+            rootNode.putIfAbsent("prompt",
+                    new TextNode("Human: Can you tell the history of Mayflower? \\n\\Assistant:"));
+
+            ArrayNode stopSequences = mapper.createArrayNode();
+            stopSequences.add("Human:");
+            rootNode.putIfAbsent("max_tokens_to_sample", new IntNode(300));
+            rootNode.putIfAbsent("stop_sequences", stopSequences);
+            rootNode.putIfAbsent("temperature", new DoubleNode(0.5));
+            rootNode.putIfAbsent("top_p", new IntNode(1));
+            rootNode.putIfAbsent("top_k", new IntNode(250));
+            rootNode.putIfAbsent("anthropic_version", new TextNode("bedrock-2023-05-31"));
+
+            exchange.getMessage().setBody(mapper.writer().writeValueAsString(rootNode));
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE, "application/json");
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, "application/json");
+        });
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    public void testInvokeAnthropicV21Model() throws InterruptedException {
+
+        result.expectedMessageCount(1);
+        final Exchange result = template.send("direct:send_anthropic_v21_model", exchange -> {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode rootNode = mapper.createObjectNode();
+            rootNode.putIfAbsent("prompt",
+                    new TextNode("Human: Can you tell the history of Mayflower? \\n\\Assistant:"));
+
+            ArrayNode stopSequences = mapper.createArrayNode();
+            stopSequences.add("Human:");
+            rootNode.putIfAbsent("max_tokens_to_sample", new IntNode(300));
+            rootNode.putIfAbsent("stop_sequences", stopSequences);
+            rootNode.putIfAbsent("temperature", new DoubleNode(0.5));
+            rootNode.putIfAbsent("top_p", new IntNode(1));
+            rootNode.putIfAbsent("top_k", new IntNode(250));
+            rootNode.putIfAbsent("anthropic_version", new TextNode("bedrock-2023-05-31"));
+
+            exchange.getMessage().setBody(mapper.writer().writeValueAsString(rootNode));
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE, "application/json");
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, "application/json");
+        });
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    public void testInvokeAnthropicV3Model() throws InterruptedException {
+
+        result.expectedMessageCount(1);
+        final Exchange result = template.send("direct:send_anthropic_v3_model", exchange -> {
+            ObjectMapper mapper = new ObjectMapper();
+            ObjectNode rootNode = mapper.createObjectNode();
+
+            ArrayNode messages = mapper.createArrayNode();
+
+            ObjectNode element = mapper.createObjectNode();
+            element.putIfAbsent("role", new TextNode("user"));
+
+            ArrayNode content = mapper.createArrayNode();
+
+            ObjectNode textContent = mapper.createObjectNode();
+
+            textContent.putIfAbsent("type", new TextNode("text"));
+            textContent.putIfAbsent("text", new TextNode("Can you tell the history of Mayflower?"));
+
+            content.add(textContent);
+
+            element.putIfAbsent("content", content);
+
+            messages.add(element);
+
+            rootNode.putIfAbsent("messages", messages);
+            rootNode.putIfAbsent("max_tokens", new IntNode(1000));
+            rootNode.putIfAbsent("anthropic_version", new TextNode("bedrock-2023-05-31"));
+
+            exchange.getMessage().setBody(mapper.writer().writeValueAsString(rootNode));
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE, "application/json");
+            exchange.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, "application/json");
+        });
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -257,12 +373,36 @@ class BedrockProducerIT extends CamelTestSupport {
                         .to(result);
 
                 from("direct:send_jurassic2_mid_model")
-                        .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}&region=us-east-1&operation=invokeTextModel&modelId="
+                        .to("aws-bedrock:label?useDefaultCredentialsProvider=true&region=us-east-1&operation=invokeTextModel&modelId="
                             + BedrockModels.JURASSIC2_MID.model)
                         .split(body())
                         .transform().jq(".data.text")
                         .log("Completions: ${body}")
                         .to(result);
+
+                from("direct:send_anthropic_v1_model")
+                        .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}&region=us-east-1&operation=invokeTextModel&modelId="
+                            + BedrockModels.ANTROPHIC_CLAUDE_INSTANT_V1.model)
+                        .log("${body}")
+                        .to(result);
+
+                from("direct:send_anthropic_v2_model")
+                        .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}&region=us-east-1&operation=invokeTextModel&modelId="
+                            + BedrockModels.ANTROPHIC_CLAUDE_V2.model)
+                        .log("${body}")
+                        .to(result);
+
+                from("direct:send_anthropic_v21_model")
+                        .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}&region=us-east-1&operation=invokeTextModel&modelId="
+                            + BedrockModels.ANTROPHIC_CLAUDE_V2_1.model)
+                        .log("${body}")
+                        .to(result);
+
+                from("direct:send_anthropic_v3_model")
+                        .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}&region=us-east-1&operation=invokeTextModel&modelId="
+                            + BedrockModels.ANTROPHIC_CLAUDE_V3.model)
+                        .log("Completions: ${body}")
+                        .to(result);
             }
         };
     }