This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch CAMEL-20560 in repository https://gitbox.apache.org/repos/asf/camel.git
commit b3d453b4ed3a4d19bc5bf3195f8ccd80f63ca075 Author: Andrea Cosentino <[email protected]> AuthorDate: Wed Mar 13 10:30:23 2024 +0100 CAMEL-20560 - Camel-AWS-Bedrock: Support Anthropic models Signed-off-by: Andrea Cosentino <[email protected]> --- .../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}}®ion=us-east-1&operation=invokeTextModel&modelId=" + .to("aws-bedrock:label?useDefaultCredentialsProvider=true®ion=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}}®ion=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}}®ion=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}}®ion=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}}®ion=us-east-1&operation=invokeTextModel&modelId=" + + BedrockModels.ANTROPHIC_CLAUDE_V3.model) + .log("Completions: ${body}") + .to(result); } }; }
