diff --git a/bundle/default-bundle/src/test/resources/application.properties b/bundle/default-bundle/src/test/resources/application.properties index e19ac4d124..59a53a29f3 100644 --- a/bundle/default-bundle/src/test/resources/application.properties +++ b/bundle/default-bundle/src/test/resources/application.properties @@ -10,12 +10,11 @@ camunda.client.zeebe.gateway-url=http://localhost:26500 camunda.client.zeebe.defaults.max-jobs-active=32 camunda.client.zeebe.defaults.worker-threads=10 camunda.client.zeebe.defaults.stream-enabled=true -camunda.client.zeebe.rest-address=http://localhost:8080 camunda.client.mode=self-managed # Config for use with docker-compose.yml -camunda.client.zeebe.rest-address=http://localhost:8080 +camunda.client.zeebe.rest-address=http://localhost:8088 camunda.client.auth.client-id=connectors camunda.client.auth.client-secret=XALaRPl5qwTEItdwCMiPS62nVpKs7dL7 camunda.client.auth.oidc-type=keycloak diff --git a/connectors/aws/aws-bedrock/element-templates/aws-bedrock-outbound-connector.json b/connectors/aws/aws-bedrock/element-templates/aws-bedrock-outbound-connector.json index a359fca740..913c04c0ae 100644 --- a/connectors/aws/aws-bedrock/element-templates/aws-bedrock-outbound-connector.json +++ b/connectors/aws/aws-bedrock/element-templates/aws-bedrock-outbound-connector.json @@ -3,6 +3,9 @@ "name" : "AWS BedRock Outbound Connector", "id" : "io.camunda.connectors.aws.bedrock.v1", "description" : "Execute bedrock requests", + "metadata" : { + "keywords" : [ ] + }, "documentationRef" : "https://docs.camunda.io/docs/", "version" : 1, "category" : { @@ -186,6 +189,23 @@ "type" : "simple" }, "type" : "String" + }, { + "id" : "data.messagesHistory", + "label" : "Message History", + "description" : "Specify the message history, when previous context is needed", + "optional" : true, + "feel" : "required", + "group" : "converse", + "binding" : { + "name" : "data.messagesHistory", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "action", + "equals" : "converse", + "type" : "simple" + }, + "type" : "String" }, { "id" : "data.modelId1", "label" : "Model ID", @@ -207,7 +227,7 @@ }, "type" : "String" }, { - "id" : "data.nextMessage", + "id" : "data.newMessage", "label" : "New Message", "description" : "Specify the next message", "optional" : false, @@ -217,7 +237,7 @@ "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.nextMessage", + "name" : "data.newMessage", "type" : "zeebe:input" }, "condition" : { @@ -227,14 +247,13 @@ }, "type" : "String" }, { - "id" : "data.messages", - "label" : "Message History", - "description" : "Specify the message history, when previous context is needed", + "id" : "data.maxTokens", + "label" : "Max token returned", "optional" : true, - "feel" : "required", + "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.messages", + "name" : "data.maxTokens", "type" : "zeebe:input" }, "condition" : { @@ -244,13 +263,13 @@ }, "type" : "String" }, { - "id" : "data.maxTokens", - "label" : "Max token returned", + "id" : "data.temperature", + "label" : "Temperature", "optional" : true, "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.maxTokens", + "name" : "data.temperature", "type" : "zeebe:input" }, "condition" : { @@ -260,13 +279,13 @@ }, "type" : "String" }, { - "id" : "data.temperature", - "label" : "Temperature", + "id" : "data.topP", + "label" : "top P", "optional" : true, "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.temperature", + "name" : "data.topP", "type" : "zeebe:input" }, "condition" : { @@ -276,13 +295,13 @@ }, "type" : "String" }, { - "id" : "data.topP", - "label" : "top P", + "id" : "data.newDocuments", + "label" : "documents", "optional" : true, - "feel" : "optional", + "feel" : "required", "group" : "converse", "binding" : { - "name" : "data.topP", + "name" : "data.newDocuments", "type" : "zeebe:input" }, "condition" : { diff --git a/connectors/aws/aws-bedrock/element-templates/hybrid/aws-bedrock-outbound-connector-hybrid.json b/connectors/aws/aws-bedrock/element-templates/hybrid/aws-bedrock-outbound-connector-hybrid.json index fa1b2876a6..839ba4d725 100644 --- a/connectors/aws/aws-bedrock/element-templates/hybrid/aws-bedrock-outbound-connector-hybrid.json +++ b/connectors/aws/aws-bedrock/element-templates/hybrid/aws-bedrock-outbound-connector-hybrid.json @@ -3,6 +3,9 @@ "name" : "Hybrid AWS BedRock Outbound Connector", "id" : "io.camunda.connectors.aws.bedrock.v1-hybrid", "description" : "Execute bedrock requests", + "metadata" : { + "keywords" : [ ] + }, "documentationRef" : "https://docs.camunda.io/docs/", "version" : 1, "category" : { @@ -191,6 +194,23 @@ "type" : "simple" }, "type" : "String" + }, { + "id" : "data.messagesHistory", + "label" : "Message History", + "description" : "Specify the message history, when previous context is needed", + "optional" : true, + "feel" : "required", + "group" : "converse", + "binding" : { + "name" : "data.messagesHistory", + "type" : "zeebe:input" + }, + "condition" : { + "property" : "action", + "equals" : "converse", + "type" : "simple" + }, + "type" : "String" }, { "id" : "data.modelId1", "label" : "Model ID", @@ -212,7 +232,7 @@ }, "type" : "String" }, { - "id" : "data.nextMessage", + "id" : "data.newMessage", "label" : "New Message", "description" : "Specify the next message", "optional" : false, @@ -222,7 +242,7 @@ "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.nextMessage", + "name" : "data.newMessage", "type" : "zeebe:input" }, "condition" : { @@ -232,14 +252,13 @@ }, "type" : "String" }, { - "id" : "data.messages", - "label" : "Message History", - "description" : "Specify the message history, when previous context is needed", + "id" : "data.maxTokens", + "label" : "Max token returned", "optional" : true, - "feel" : "required", + "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.messages", + "name" : "data.maxTokens", "type" : "zeebe:input" }, "condition" : { @@ -249,13 +268,13 @@ }, "type" : "String" }, { - "id" : "data.maxTokens", - "label" : "Max token returned", + "id" : "data.temperature", + "label" : "Temperature", "optional" : true, "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.maxTokens", + "name" : "data.temperature", "type" : "zeebe:input" }, "condition" : { @@ -265,13 +284,13 @@ }, "type" : "String" }, { - "id" : "data.temperature", - "label" : "Temperature", + "id" : "data.topP", + "label" : "top P", "optional" : true, "feel" : "optional", "group" : "converse", "binding" : { - "name" : "data.temperature", + "name" : "data.topP", "type" : "zeebe:input" }, "condition" : { @@ -281,13 +300,13 @@ }, "type" : "String" }, { - "id" : "data.topP", - "label" : "top P", + "id" : "data.newDocuments", + "label" : "documents", "optional" : true, - "feel" : "optional", + "feel" : "required", "group" : "converse", "binding" : { - "name" : "data.topP", + "name" : "data.newDocuments", "type" : "zeebe:input" }, "condition" : { diff --git a/connectors/aws/aws-bedrock/pom.xml b/connectors/aws/aws-bedrock/pom.xml index eb40dcc1aa..706e6df02f 100644 --- a/connectors/aws/aws-bedrock/pom.xml +++ b/connectors/aws/aws-bedrock/pom.xml @@ -37,6 +37,12 @@ bedrockruntime ${version.software-aws-java-sdk} + + + org.apache.tika + tika-core + 2.0.0 + diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/core/BedrockExecutor.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/core/BedrockExecutor.java index ddf4d4f64c..8edcd71801 100644 --- a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/core/BedrockExecutor.java +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/core/BedrockExecutor.java @@ -9,7 +9,6 @@ import io.camunda.connector.aws.CredentialsProviderSupportV2; import io.camunda.connector.aws.ObjectMapperSupplier; import io.camunda.connector.aws.bedrock.model.BedrockRequest; -import io.camunda.connector.aws.bedrock.model.BedrockResponse; import io.camunda.connector.aws.bedrock.model.RequestData; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; @@ -33,7 +32,7 @@ public static BedrockExecutor create(BedrockRequest bedrockRequest) { bedrockRequest.getData()); } - public BedrockResponse execute() { + public Object execute() { return this.requestData.execute(bedrockRuntimeClient, ObjectMapperSupplier.getMapperInstance()); } } diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapper.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapper.java new file mode 100644 index 0000000000..e77d457294 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapper.java @@ -0,0 +1,72 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import io.camunda.connector.aws.bedrock.model.BedrockContent; +import io.camunda.document.Document; +import java.util.List; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.DocumentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ImageBlock; + +public class BedrockContentMapper { + + private final DocumentMapper documentMapper; + + public BedrockContentMapper(DocumentMapper documentMapper) { + this.documentMapper = documentMapper; + } + + public BedrockContent messageToBedrockContent(String message) { + return new BedrockContent(message); + } + + public List documentsToBedrockContent(List documents) { + if (documents == null) { + return List.of(); + } + return documents.stream().map(this::documentToBedrockContent).toList(); + } + + public BedrockContent documentToBedrockContent(Document document) { + return new BedrockContent(document); + } + + /* + * The current implementation supports ContentBlock containing only text. + * */ + public List mapToBedrockContent(List contentBlocks) { + return contentBlocks.stream().map(ContentBlock::text).map(BedrockContent::new).toList(); + } + + public List mapToContentBlocks(List contentBlocks) { + return contentBlocks.stream() + .map( + content -> { + String text = content.getText(); + if (text != null) { + return mapToContentBlock(text); + } + var document = content.getDocument(); + var docBlock = documentMapper.mapToFileBlock(document); + return mapToContentBlock(docBlock); + }) + .toList(); + } + + private ContentBlock mapToContentBlock(Object content) { + return switch (content) { + case String s -> ContentBlock.fromText(s); + case ImageBlock imageBlock -> ContentBlock.fromImage(imageBlock); + default -> ContentBlock.fromDocument((DocumentBlock) content); + }; + } + + public DocumentMapper getDocumentMapper() { + return documentMapper; + } +} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapper.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapper.java new file mode 100644 index 0000000000..61541121f7 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapper.java @@ -0,0 +1,76 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import io.camunda.connector.aws.bedrock.util.FileUtil; +import io.camunda.document.Document; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tika.mime.MimeTypeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.SdkPojo; +import software.amazon.awssdk.services.bedrockruntime.model.*; + +public class DocumentMapper { + + public static final String UNSUPPORTED_DOC_TYPE_MSG = "Unsupported document type: "; + public static final String UNSUPPORTED_CONTENT_TYPE_MSG = "Unsupported document content type: "; + private static final Logger LOGGER = LoggerFactory.getLogger(DocumentMapper.class); + + public DocumentMapper() {} + + public SdkPojo mapToFileBlock(Document document) { + Pair nameTypePair = + FileUtil.defineNameAndType(document.metadata().getFileName()); + String fileName = nameTypePair.getLeft(); + String fileType = nameTypePair.getRight(); + String contentType = document.metadata().getContentType(); + + try { + fileType = fileType.isEmpty() ? FileUtil.defineType(contentType) : fileType; + } catch (MimeTypeException e) { + String errorMsg = UNSUPPORTED_CONTENT_TYPE_MSG + contentType; + LOGGER.debug(errorMsg); + throw new RuntimeException(errorMsg, e); + } + + var bytes = document.asByteArray(); + var imageFormat = ImageFormat.fromValue(fileType); + + if (!imageFormat.equals(ImageFormat.UNKNOWN_TO_SDK_VERSION)) { + return mapToImag(bytes, imageFormat); + } + + var documentFormat = DocumentFormat.fromValue(fileType); + + if (!documentFormat.equals(DocumentFormat.UNKNOWN_TO_SDK_VERSION)) { + return mapToDocument(bytes, documentFormat, fileName); + } + + String unsupportedDocumentMsg = UNSUPPORTED_DOC_TYPE_MSG + fileName; + + LOGGER.debug(unsupportedDocumentMsg); + throw new IllegalArgumentException(unsupportedDocumentMsg); + } + + private ImageBlock mapToImag(byte[] bytes, ImageFormat imageFormat) { + return ImageBlock.builder() + .source(ImageSource.builder().bytes(SdkBytes.fromByteArray(bytes)).build()) + .format(imageFormat) + .build(); + } + + private DocumentBlock mapToDocument( + byte[] bytes, DocumentFormat documentFormat, String fileName) { + return DocumentBlock.builder() + .source(DocumentSource.builder().bytes(SdkBytes.fromByteArray(bytes)).build()) + .format(documentFormat) + .name(fileName) + .build(); + } +} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/MessageMapper.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/MessageMapper.java new file mode 100644 index 0000000000..3702a64926 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/mapper/MessageMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import io.camunda.connector.aws.bedrock.model.BedrockContent; +import io.camunda.connector.aws.bedrock.model.BedrockMessage; +import java.util.List; +import software.amazon.awssdk.services.bedrockruntime.model.Message; + +public class MessageMapper { + + private final BedrockContentMapper bedrockContentMapper; + + public MessageMapper(BedrockContentMapper bedrockContentMapper) { + this.bedrockContentMapper = bedrockContentMapper; + } + + public List mapToMessages(List bedrockMessages) { + if (bedrockMessages != null) { + return bedrockMessages.stream().map(this::mapToMessage).toList(); + } + return List.of(); + } + + public Message mapToMessage(BedrockMessage bedrockMessage) { + return Message.builder() + .content(bedrockContentMapper.mapToContentBlocks(bedrockMessage.getContentList())) + .role(bedrockMessage.getRole()) + .build(); + } + + public BedrockMessage mapToBedrockMessage(Message message) { + List bedrockContents = + bedrockContentMapper.mapToBedrockContent(message.content()); + return new BedrockMessage(message.roleAsString(), bedrockContents); + } + + public BedrockContentMapper getBedrockContentMapper() { + return bedrockContentMapper; + } +} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockContent.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockContent.java new file mode 100644 index 0000000000..973aaa2d67 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockContent.java @@ -0,0 +1,59 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.camunda.document.Document; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BedrockContent { + private String text; + + private Document document; + + public BedrockContent(String text) { + this.text = text; + } + + public BedrockContent(Document document) { + this.document = document; + } + + public BedrockContent() {} + + public @Valid @NotBlank String getText() { + return text; + } + + public void setText(@Valid @NotBlank String text) { + this.text = text; + } + + public Document getDocument() { + return document; + } + + public void setDocument(Document document) { + this.document = document; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BedrockContent that = (BedrockContent) o; + return Objects.equals(text, that.text) && Objects.equals(document, that.document); + } + + @Override + public int hashCode() { + return Objects.hash(text, document); + } +} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockMessage.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockMessage.java new file mode 100644 index 0000000000..66fda29727 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/BedrockMessage.java @@ -0,0 +1,53 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.model; + +import java.util.List; +import java.util.Objects; + +public class BedrockMessage { + + private String role; + + private List contentList; + + public BedrockMessage(String role, List contentList) { + this.role = role; + this.contentList = contentList; + } + + public BedrockMessage() {} + + public String getRole() { + return role; + } + + public List getContentList() { + return contentList; + } + + public void setRole(String role) { + this.role = role; + } + + public void setContentList(List contentList) { + this.contentList = contentList; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BedrockMessage that = (BedrockMessage) o; + return Objects.equals(role, that.role) && Objects.equals(contentList, that.contentList); + } + + @Override + public int hashCode() { + return Objects.hash(role, contentList); + } +} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseData.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseData.java index 63c9b92062..4acb3d5b4f 100644 --- a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseData.java +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseData.java @@ -9,6 +9,9 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.ObjectMapper; +import io.camunda.connector.aws.bedrock.mapper.BedrockContentMapper; +import io.camunda.connector.aws.bedrock.mapper.DocumentMapper; +import io.camunda.connector.aws.bedrock.mapper.MessageMapper; import io.camunda.connector.generator.dsl.Property; import io.camunda.connector.generator.java.annotation.TemplateProperty; import io.camunda.connector.generator.java.annotation.TemplateSubType; @@ -20,14 +23,23 @@ import java.util.List; import java.util.Objects; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; -import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; -import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; -import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; -import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.*; @TemplateSubType(id = "converse", label = "Converse") public final class ConverseData implements RequestData { + @TemplateProperty( + label = "Message History", + group = "converse", + id = "data.messagesHistory", + description = "Specify the message history, when previous context is needed", + feel = Property.FeelMode.required, + optional = true, + binding = @TemplateProperty.PropertyBinding(name = "data.messagesHistory")) + @Valid + @JsonSetter(nulls = Nulls.SKIP) + private List messagesHistory = new ArrayList<>(); + @TemplateProperty( label = "Model ID", group = "converse", @@ -43,25 +55,13 @@ public final class ConverseData implements RequestData { @TemplateProperty( label = "New Message", group = "converse", - id = "data.nextMessage", + id = "data.newMessage", description = "Specify the next message", feel = Property.FeelMode.optional, - binding = @TemplateProperty.PropertyBinding(name = "data.nextMessage")) + binding = @TemplateProperty.PropertyBinding(name = "data.newMessage")) @Valid @NotBlank - private String nextMessage; - - @TemplateProperty( - label = "Message History", - group = "converse", - id = "data.messages", - description = "Specify the message history, when previous context is needed", - feel = Property.FeelMode.required, - optional = true, - binding = @TemplateProperty.PropertyBinding(name = "data.messages")) - @Valid - @JsonSetter(nulls = Nulls.SKIP) - private List messages = new ArrayList<>(); + private String newMessage; @TemplateProperty( label = "Max token returned", @@ -91,29 +91,23 @@ public final class ConverseData implements RequestData { private Float topP = 0.9f; @TemplateProperty( - label = "documents", - group = "converse", - id = "data.documents", - feel = Property.FeelMode.required, - optional = true, - binding = @TemplateProperty.PropertyBinding(name = "data.documents") - ) - private List documents; + label = "documents", + group = "converse", + id = "data.newDocuments", + feel = Property.FeelMode.required, + optional = true, + binding = @TemplateProperty.PropertyBinding(name = "data.newDocuments")) + private List newDocuments; @Override - public BedrockResponse execute( + public List execute( BedrockRuntimeClient bedrockRuntimeClient, ObjectMapper mapperInstance) { - this.messages.add(new PreviousMessage(this.nextMessage, ConversationRole.USER.name())); - Message.Builder messageBuilder = Message.builder(); - List messages = - this.messages.stream() - .map( - message -> - messageBuilder - .role(ConversationRole.valueOf(message.role())) - .content(ContentBlock.fromText(message.message())) - .build()) - .toList(); + var messageMapper = createMessageMapper(); + var bedrockContentMapper = messageMapper.getBedrockContentMapper(); + this.messagesHistory.add(prepareNewBedrockMessage(bedrockContentMapper)); + + List messages = messageMapper.mapToMessages(this.messagesHistory); + ConverseResponse converseResponse = bedrockRuntimeClient.converse( builder -> @@ -127,9 +121,25 @@ public BedrockResponse execute( .maxTokens(this.maxTokens) .topP(this.topP) .build())); - String newMessage = converseResponse.output().message().content().getFirst().text(); - this.messages.add(new PreviousMessage(newMessage, ConversationRole.ASSISTANT.name())); - return new ConverseWrapperResponse(this.messages, newMessage); + + var responseMessage = converseResponse.output().message(); + this.messagesHistory.add(messageMapper.mapToBedrockMessage(responseMessage)); + + return messagesHistory; + } + + private BedrockMessage prepareNewBedrockMessage(BedrockContentMapper bedrockContentMapper) { + String user = ConversationRole.USER.toString(); + List contentList = + new ArrayList<>(bedrockContentMapper.documentsToBedrockContent(this.newDocuments)); + contentList.add(bedrockContentMapper.messageToBedrockContent(this.newMessage)); + + return new BedrockMessage(user, contentList); + } + + private MessageMapper createMessageMapper() { + var bedrockContentMapper = new BedrockContentMapper(new DocumentMapper()); + return new MessageMapper(bedrockContentMapper); } @Override @@ -137,29 +147,27 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConverseData that = (ConverseData) o; - return Objects.equals(modelId, that.modelId) - && Objects.equals(nextMessage, that.nextMessage) - && Objects.equals(messages, that.messages) + return Objects.equals(messagesHistory, that.messagesHistory) + && Objects.equals(modelId, that.modelId) + && Objects.equals(newMessage, that.newMessage) && Objects.equals(maxTokens, that.maxTokens) && Objects.equals(temperature, that.temperature) - && Objects.equals(topP, that.topP); + && Objects.equals(topP, that.topP) + && Objects.equals(newDocuments, that.newDocuments); } @Override public int hashCode() { - return Objects.hash(modelId, nextMessage, messages, maxTokens, temperature, topP); + return Objects.hash( + messagesHistory, modelId, newMessage, maxTokens, temperature, topP, newDocuments); } public void setModelId(@Valid @NotNull String modelId) { this.modelId = modelId; } - public void setNextMessage(@Valid @NotBlank String nextMessage) { - this.nextMessage = nextMessage; - } - - public void setMessages(@Valid List messages) { - this.messages = messages; + public void setNewMessage(@Valid @NotBlank String newMessage) { + this.newMessage = newMessage; } public void setMaxTokens(Integer maxTokens) { @@ -174,6 +182,22 @@ public void setTopP(Float topP) { this.topP = topP; } + public List getNewDocuments() { + return newDocuments; + } + + public void setNewDocuments(List newDocuments) { + this.newDocuments = newDocuments; + } + + public List getMessagesHistory() { + return this.messagesHistory; + } + + public void setMessagesHistory(List messagesHistory) { + this.messagesHistory = messagesHistory; + } + @Override public String toString() { return "ConverseData{" diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseWrapperResponse.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseWrapperResponse.java deleted file mode 100644 index 97eabc2cf2..0000000000 --- a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/ConverseWrapperResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.aws.bedrock.model; - -import java.util.List; - -public record ConverseWrapperResponse(List messageHistory, String newMessage) - implements BedrockResponse {} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/PreviousMessage.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/PreviousMessage.java deleted file mode 100644 index 1c6c0aacfb..0000000000 --- a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/PreviousMessage.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH - * under one or more contributor license agreements. Licensed under a proprietary license. - * See the License.txt file for more information. You may not use this file - * except in compliance with the proprietary license. - */ -package io.camunda.connector.aws.bedrock.model; - -public record PreviousMessage(String message, String role) {} diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/RequestData.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/RequestData.java index 5b2e1edb79..7d028524ea 100644 --- a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/RequestData.java +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/model/RequestData.java @@ -18,5 +18,5 @@ defaultValue = "invokeModel") @TemplateSubType(id = "action", label = "Action") public sealed interface RequestData permits InvokeModelData, ConverseData { - BedrockResponse execute(BedrockRuntimeClient bedrockRuntimeClient, ObjectMapper mapperInstance); + Object execute(BedrockRuntimeClient bedrockRuntimeClient, ObjectMapper mapperInstance); } diff --git a/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/util/FileUtil.java b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/util/FileUtil.java new file mode 100644 index 0000000000..e691a156c6 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/main/java/io/camunda/connector/aws/bedrock/util/FileUtil.java @@ -0,0 +1,37 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.util; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; + +public final class FileUtil { + + private FileUtil() {} + + public static Pair defineNameAndType(String fileName) { + String separator = "."; + int separatorIndex = fileName.lastIndexOf(separator); + + if (separatorIndex == -1) { + return Pair.of(fileName, ""); + } + + String name = fileName.substring(0, separatorIndex); + String type = fileName.substring(separatorIndex + 1); + return Pair.of(name, type); + } + + public static String defineType(String contentType) throws MimeTypeException { + MimeTypes mimeTypes = MimeTypes.getDefaultMimeTypes(); + String extension = mimeTypes.forName(contentType).getExtension(); + + int dotIndex = extension.indexOf('.'); + return dotIndex == 0 ? extension.substring(1) : extension; + } +} diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/BaseTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/BaseTest.java index 1a3138a855..858282ec70 100644 --- a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/BaseTest.java +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/BaseTest.java @@ -20,6 +20,8 @@ public class BaseTest { + protected static final ObjectMapper mapper = ConnectorsObjectMapperSupplier.getCopy(); + public static Stream loadInvokeModelVariables() { try { return loadTestCasesFromResourceFile( @@ -37,12 +39,15 @@ public static Stream loadConverseVariables() { } } + public static T readData(String path, Class type) throws IOException { + final String cases = readString(new File(path).toPath(), UTF_8); + return mapper.readValue(cases, type); + } + @SuppressWarnings("unchecked") protected static Stream loadTestCasesFromResourceFile(final String fileWithTestCasesUri) throws IOException { - final String cases = readString(new File(fileWithTestCasesUri).toPath(), UTF_8); - final ObjectMapper mapper = ConnectorsObjectMapperSupplier.getCopy(); - var array = mapper.readValue(cases, ArrayList.class); + var array = readData(fileWithTestCasesUri, ArrayList.class); return array.stream() .map( value -> { diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapperTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapperTest.java new file mode 100644 index 0000000000..0e6d7d3327 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/BedrockContentMapperTest.java @@ -0,0 +1,128 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import static io.camunda.connector.aws.bedrock.BaseTest.readData; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.camunda.connector.aws.bedrock.model.BedrockContent; +import io.camunda.connector.document.annotation.jackson.DocumentReferenceModel; +import io.camunda.document.CamundaDocument; +import io.camunda.document.Document; +import io.camunda.document.reference.DocumentReference; +import io.camunda.document.store.CamundaDocumentStore; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.DocumentBlock; + +@ExtendWith(MockitoExtension.class) +class BedrockContentMapperTest { + + @Mock private DocumentMapper documentMapper; + + @InjectMocks private BedrockContentMapper bedrockContentMapper; + + @Test + void messageToBedrockContent() { + String msg = "Hello World!"; + var bedrockContent = bedrockContentMapper.messageToBedrockContent(msg); + + assertThat(bedrockContent.getText()).isEqualTo(msg); + assertThat(bedrockContent.getDocument()).isNull(); + } + + @Test + void documentsToBedrockContent() throws IOException { + String path1 = "src/test/resources/converse/text-document.json"; + String path2 = "src/test/resources/converse/image-document.json"; + var doc1 = prepareDocument(path1); + var doc2 = prepareDocument(path2); + + List expectedContent = + List.of(new BedrockContent(doc1), new BedrockContent(doc2)); + List contentResult = + bedrockContentMapper.documentsToBedrockContent(List.of(doc1, doc2)); + + assertThat(contentResult).isEqualTo(expectedContent); + } + + @Test + void documentToBedrockContent() throws IOException { + String path = "src/test/resources/converse/text-document.json"; + var document = prepareDocument(path); + + var bedrockContent = bedrockContentMapper.documentToBedrockContent(document); + + assertThat(bedrockContent.getText()).isNull(); + assertThat(bedrockContent.getDocument()).isEqualTo(document); + } + + @Test + void mapToBedrockContent() { + String msg = "Hello World!"; + var contentBlock = ContentBlock.fromText(msg); + + List result = bedrockContentMapper.mapToBedrockContent(List.of(contentBlock)); + + assertThat(result).hasSize(1); + assertThat(result.getFirst().getText()).isEqualTo(msg); + } + + @Test + void mapToContentBlocks() throws IOException { + String msg = "Hello World!"; + var textBedrockContent = new BedrockContent(msg); + + String path = "src/test/resources/converse/text-document.json"; + var documentReference = mock(DocumentReference.CamundaDocumentReference.class); + var documentStore = mock(CamundaDocumentStore.class); + var document = prepareDocument(path, documentReference, documentStore); + + var byteInput = new ByteArrayInputStream(new byte[0]); + when(documentStore.getDocumentContent(any())).thenReturn(byteInput); + + var docContent = new BedrockContent(document); + + when(documentMapper.mapToFileBlock(any(Document.class))).thenCallRealMethod(); + + List result = + bedrockContentMapper.mapToContentBlocks(List.of(textBedrockContent, docContent)); + + DocumentBlock documentBlock = (DocumentBlock) documentMapper.mapToFileBlock(document); + ContentBlock documentContent = ContentBlock.fromDocument(documentBlock); + + ContentBlock textContent = ContentBlock.fromText(msg); + + List expected = List.of(textContent, documentContent); + + assertThat(result).isEqualTo(expected); + } + + private Document prepareDocument(String path) throws IOException { + var documentReference = mock(DocumentReference.CamundaDocumentReference.class); + var documentStore = mock(CamundaDocumentStore.class); + + return prepareDocument(path, documentReference, documentStore); + } + + private Document prepareDocument( + String path, DocumentReference.CamundaDocumentReference docRef, CamundaDocumentStore docStore) + throws IOException { + var docMetadata = readData(path, DocumentReferenceModel.CamundaDocumentMetadataModel.class); + return new CamundaDocument(docMetadata, docRef, docStore); + } +} diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapperTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapperTest.java new file mode 100644 index 0000000000..1dca9b4895 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/DocumentMapperTest.java @@ -0,0 +1,102 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import static io.camunda.connector.aws.bedrock.BaseTest.readData; +import static io.camunda.connector.aws.bedrock.mapper.DocumentMapper.UNSUPPORTED_DOC_TYPE_MSG; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import io.camunda.connector.document.annotation.jackson.DocumentReferenceModel; +import io.camunda.document.CamundaDocument; +import io.camunda.document.Document; +import io.camunda.document.reference.DocumentReference; +import io.camunda.document.store.CamundaDocumentStore; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.bedrockruntime.model.*; + +@ExtendWith(MockitoExtension.class) +class DocumentMapperTest { + + private final DocumentMapper documentMapper = new DocumentMapper(); + @Mock private DocumentReference.CamundaDocumentReference documentReference; + @Mock private CamundaDocumentStore documentStore; + + @Test + void mapToFileBlockShouldCreateDocumentBlock() throws IOException { + String path = "src/test/resources/converse/text-document.json"; + var document = prepareDocument(path); + + var byteInput = new ByteArrayInputStream(new byte[0]); + when(documentStore.getDocumentContent(any())).thenReturn(byteInput); + + String fileName = + ((DocumentReferenceModel.CamundaDocumentMetadataModel) document.metadata()).fileName(); + var expectedDocBlock = + DocumentBlock.builder() + .source( + DocumentSource.builder() + .bytes(SdkBytes.fromByteArray(byteInput.readAllBytes())) + .build()) + .format(DocumentFormat.TXT) + .name(fileName.split("\\.")[0]) + .build(); + + var documentBlockResult = documentMapper.mapToFileBlock(document); + + assertThat(documentBlockResult).isEqualTo(expectedDocBlock); + } + + @Test + void mapToFileShouldCreateImageBlock() throws IOException { + String path = "src/test/resources/converse/image-document.json"; + var document = prepareDocument(path); + + var byteInput = new ByteArrayInputStream(new byte[0]); + when(documentStore.getDocumentContent(any())).thenReturn(byteInput); + + var imageBlockResult = documentMapper.mapToFileBlock(document); + + var expectedImageBlock = + ImageBlock.builder() + .source( + ImageSource.builder() + .bytes(SdkBytes.fromByteArray(byteInput.readAllBytes())) + .build()) + .format(ImageFormat.PNG) + .build(); + + assertThat(imageBlockResult).isEqualTo(expectedImageBlock); + } + + @Test + void mapToFileWithWithUnknownFileTypeShouldThrowException() throws IOException { + String path = "src/test/resources/converse/unsupported-document.json"; + var document = prepareDocument(path); + + var byteInput = new ByteArrayInputStream(new byte[0]); + when(documentStore.getDocumentContent(any())).thenReturn(byteInput); + + var ex = + assertThrows(IllegalArgumentException.class, () -> documentMapper.mapToFileBlock(document)); + + assertThat(ex).hasMessageContaining(UNSUPPORTED_DOC_TYPE_MSG); + } + + private Document prepareDocument(String path) throws IOException { + var docMetadata = readData(path, DocumentReferenceModel.CamundaDocumentMetadataModel.class); + return new CamundaDocument(docMetadata, documentReference, documentStore); + } +} diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/MessageMapperTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/MessageMapperTest.java new file mode 100644 index 0000000000..1d09c14364 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/mapper/MessageMapperTest.java @@ -0,0 +1,68 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.mapper; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.camunda.connector.aws.bedrock.model.BedrockMessage; +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.Message; + +class MessageMapperTest { + + private final BedrockContentMapper bedrockContentMapper = + new BedrockContentMapper(new DocumentMapper()); + private final MessageMapper messageMapper = new MessageMapper(bedrockContentMapper); + + @Test + void mapToMessages() { + String msg = "Hello World!"; + String role = "user"; + var bedrockContent = bedrockContentMapper.messageToBedrockContent(msg); + + List messagesResult = + messageMapper.mapToMessages(List.of(new BedrockMessage(role, List.of(bedrockContent)))); + + List messagesExpected = + List.of( + Message.builder() + .content(bedrockContentMapper.mapToContentBlocks(List.of(bedrockContent))) + .role(role) + .build()); + + assertThat(messagesResult).isEqualTo(messagesExpected); + } + + @Test + void mapToMessage() { + String msg = "Hello World!"; + String role = "user"; + var bedrockContent = bedrockContentMapper.messageToBedrockContent(msg); + + var message = messageMapper.mapToMessage(new BedrockMessage(role, List.of(bedrockContent))); + + assertThat(message.role().toString()).isEqualTo(role); + assertThat(message.content()).isEqualTo(List.of(ContentBlock.fromText(msg))); + } + + @Test + void mapToBedrockMessage() { + String msg = "Hello World!"; + String role = "user"; + + Message message = Message.builder().content(ContentBlock.fromText(msg)).role(role).build(); + + var bedrockMessageResult = messageMapper.mapToBedrockMessage(message); + + var bedrockMessageExpected = + new BedrockMessage(role, List.of(bedrockContentMapper.messageToBedrockContent(msg))); + + assertThat(bedrockMessageResult).isIn(bedrockMessageExpected); + } +} diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/model/ConverseDataTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/model/ConverseDataTest.java index f20b0f54a5..5c4aaa44ca 100644 --- a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/model/ConverseDataTest.java +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/model/ConverseDataTest.java @@ -6,6 +6,7 @@ */ package io.camunda.connector.aws.bedrock.model; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -14,12 +15,12 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; -import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.Message; class ConverseDataTest { @@ -33,20 +34,26 @@ void execute_success() { ConverseData converseData = new ConverseData(); converseData.setModelId("random-model-id"); - List previousMessages = new ArrayList<>(); - previousMessages.add(new PreviousMessage("Hey", ConversationRole.USER.name())); - previousMessages.add(new PreviousMessage("How are you?", ConversationRole.ASSISTANT.name())); - converseData.setMessages(previousMessages); - converseData.setNextMessage("I am good thanks, and you?"); + List previousMessages = new ArrayList<>(); + previousMessages.add( + new BedrockMessage("assistant", List.of(new BedrockContent("Hey, How are you?")))); + + converseData.setMessagesHistory(previousMessages); + converseData.setNewMessage("I am good thanks, and you?"); when(bedrockRuntimeClient.converse(any(Consumer.class))).thenReturn(converseResponse); - when(converseResponse.output().message().content().getFirst().text()) - .thenReturn("I am also good"); - BedrockResponse bedrockResponse = converseData.execute(bedrockRuntimeClient, mapper); - - Assertions.assertInstanceOf(ConverseWrapperResponse.class, bedrockResponse); - Assertions.assertEquals( - "I am also good", - ((ConverseWrapperResponse) bedrockResponse).messageHistory().getLast().message()); + Message response = + Message.builder() + .role("assistant") + .content(ContentBlock.fromText("I am also good")) + .build(); + + when(converseResponse.output().message()).thenReturn(response); + + List result = converseData.execute(bedrockRuntimeClient, mapper); + + assertThat(result.size()).isEqualTo(3); + assertThat(result.get(2).getContentList()) + .isEqualTo(List.of(new BedrockContent("I am also good"))); } } diff --git a/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/util/FileUtilTest.java b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/util/FileUtilTest.java new file mode 100644 index 0000000000..77eb3ebd87 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/java/io/camunda/connector/aws/bedrock/util/FileUtilTest.java @@ -0,0 +1,45 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. Licensed under a proprietary license. + * See the License.txt file for more information. You may not use this file + * except in compliance with the proprietary license. + */ +package io.camunda.connector.aws.bedrock.util; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tika.mime.MimeTypeException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class FileUtilTest { + + @ParameterizedTest + @CsvSource({"test-file, txt", "test.file, pdf"}) + void defineNameAndType(String fileName, String fileExtension) { + String fullFileName = fileName + "." + fileExtension; + + Pair result = FileUtil.defineNameAndType(fullFileName); + + assertThat(result.getLeft()).isEqualTo(fileName); + assertThat(result.getRight()).isEqualTo(fileExtension); + } + + @Test + void defineNameAndTypeWhenFileWithoutExtensionShouldReturnOnlyName() { + String fileName = "test-file"; + + Pair result = FileUtil.defineNameAndType(fileName); + + assertThat(result.getLeft()).isEqualTo(fileName); + assertThat(result.getRight()).isEmpty(); + } + + @Test + void defineType() throws MimeTypeException { + String result = FileUtil.defineType("text/plain"); + assertThat(result).isEqualTo("txt"); + } +} diff --git a/connectors/aws/aws-bedrock/src/test/resources/converse/converseExample.json b/connectors/aws/aws-bedrock/src/test/resources/converse/converseExample.json index b17a2a2967..c69a58ed77 100644 --- a/connectors/aws/aws-bedrock/src/test/resources/converse/converseExample.json +++ b/connectors/aws/aws-bedrock/src/test/resources/converse/converseExample.json @@ -2,11 +2,11 @@ { "data":{ "modelId":"amazon.titan-text-premier-v1:0", - "nextMessage":"ok", - "messages":[ - {"message":"Hey", "role":"USER"}, {"message":"Hello there! How can I assist you today?", "role":"ASSISTANT"}, - {"message":"You feel good ?", "role":"USER"}, { - "message":"Thank you for asking! As an AI, I don't have personal feelings or experiences, but I'm always here to help with any questions or information you may need. How can I assist you today?", + "newMessage":"ok", + "messagesHistory":[ + {"text":"Hey", "role":"USER"}, {"text":"Hello there! How can I assist you today?", "role":"ASSISTANT"}, + {"text":"You feel good ?", "role":"USER"}, { + "text":"Thank you for asking! As an AI, I don't have personal feelings or experiences, but I'm always here to help with any questions or information you may need. How can I assist you today?", "role":"ASSISTANT" } ] diff --git a/connectors/aws/aws-bedrock/src/test/resources/converse/image-document.json b/connectors/aws/aws-bedrock/src/test/resources/converse/image-document.json new file mode 100644 index 0000000000..ab5815b59c --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/resources/converse/image-document.json @@ -0,0 +1,6 @@ +{ + "contentType": "image/png", + "fileName": "iphone-image.png", + "size": 49213, + "customProperties": {} +} \ No newline at end of file diff --git a/connectors/aws/aws-bedrock/src/test/resources/converse/text-document.json b/connectors/aws/aws-bedrock/src/test/resources/converse/text-document.json new file mode 100644 index 0000000000..9ba89a96d7 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/resources/converse/text-document.json @@ -0,0 +1,6 @@ +{ + "contentType": "text/plain", + "fileName": "test.txt", + "size": 4, + "customProperties": {} +} \ No newline at end of file diff --git a/connectors/aws/aws-bedrock/src/test/resources/converse/unsupported-document.json b/connectors/aws/aws-bedrock/src/test/resources/converse/unsupported-document.json new file mode 100644 index 0000000000..79228ebac4 --- /dev/null +++ b/connectors/aws/aws-bedrock/src/test/resources/converse/unsupported-document.json @@ -0,0 +1,6 @@ +{ + "contentType": "application/trgx", + "fileName": "test", + "size": 4, + "customProperties": {} +} \ No newline at end of file diff --git a/connectors/aws/aws-comprehend/element-templates/aws-comprehend-outbound-connector.json b/connectors/aws/aws-comprehend/element-templates/aws-comprehend-outbound-connector.json index 08a5f007a3..b7de67c47e 100644 --- a/connectors/aws/aws-comprehend/element-templates/aws-comprehend-outbound-connector.json +++ b/connectors/aws/aws-comprehend/element-templates/aws-comprehend-outbound-connector.json @@ -3,6 +3,9 @@ "name" : "AWS Comprehend Outbound Connector", "id" : "io.camunda.connectors.AWSCOMPREHEND.v1", "description" : "Execute Comprehend models", + "metadata" : { + "keywords" : [ ] + }, "documentationRef" : "https://docs.camunda.io/docs/next/components/connectors/out-of-the-box-connectors/amazon-comprehend/", "version" : 1, "category" : { diff --git a/connectors/aws/aws-comprehend/element-templates/hybrid/aws-comprehend-outbound-connector-hybrid.json b/connectors/aws/aws-comprehend/element-templates/hybrid/aws-comprehend-outbound-connector-hybrid.json index a6375c305b..7ccb618154 100644 --- a/connectors/aws/aws-comprehend/element-templates/hybrid/aws-comprehend-outbound-connector-hybrid.json +++ b/connectors/aws/aws-comprehend/element-templates/hybrid/aws-comprehend-outbound-connector-hybrid.json @@ -3,6 +3,9 @@ "name" : "Hybrid AWS Comprehend Outbound Connector", "id" : "io.camunda.connectors.AWSCOMPREHEND.v1-hybrid", "description" : "Execute Comprehend models", + "metadata" : { + "keywords" : [ ] + }, "documentationRef" : "https://docs.camunda.io/docs/next/components/connectors/out-of-the-box-connectors/amazon-comprehend/", "version" : 1, "category" : { diff --git a/connectors/http/rest/README.md b/connectors/http/rest/README.md index 542b3ae280..00393ca7f4 100644 --- a/connectors/http/rest/README.md +++ b/connectors/http/rest/README.md @@ -1,13 +1,9 @@ ![REST Outbound Connector connector icon](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAxOCAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE3LjAzMzUgOC45OTk5N0MxNy4wMzM1IDEzLjQ0NzUgMTMuNDI4MSAxNy4wNTI5IDguOTgwNjUgMTcuMDUyOUM0LjUzMzE2IDE3LjA1MjkgMC45Mjc3NjUgMTMuNDQ3NSAwLjkyNzc2NSA4Ljk5OTk3QzAuOTI3NzY1IDQuNTUyNDggNC41MzMxNiAwLjk0NzA4MyA4Ljk4MDY1IDAuOTQ3MDgzQzEzLjQyODEgMC45NDcwODMgMTcuMDMzNSA0LjU1MjQ4IDE3LjAzMzUgOC45OTk5N1oiIGZpbGw9IiM1MDU1NjIiLz4KPHBhdGggZD0iTTQuOTMxMjYgMTQuMTU3MUw2Ljc4MTA2IDMuNzE0NzFIMTAuMTM3NUMxMS4xOTE3IDMuNzE0NzEgMTEuOTgyNCAzLjk4MzIzIDEyLjUwOTUgNC41MjAyN0MxMy4wNDY1IDUuMDQ3MzYgMTMuMzE1IDUuNzMzNTggMTMuMzE1IDYuNTc4OTJDMTMuMzE1IDcuNDQ0MTQgMTMuMDcxNCA4LjE1NTIyIDEyLjU4NDEgOC43MTIxNUMxMi4xMDY3IDkuMjU5MTMgMTEuNDU1MyA5LjYzNzA1IDEwLjYyOTggOS44NDU5TDEyLjA2MTkgMTQuMTU3MUgxMC4zMzE1TDkuMDMzNjQgMTAuMDI0OUg3LjI0MzUxTDYuNTEyNTQgMTQuMTU3MUg0LjkzMTI2Wk03LjQ5NzExIDguNTkyODFIOS4yNDI0OEM5Ljk5ODMyIDguNTkyODEgMTAuNTkwMSA4LjQyMzc0IDExLjAxNzcgOC4wODU2MUMxMS40NTUzIDcuNzM3NTMgMTEuNjc0MSA3LjI2NTEzIDExLjY3NDEgNi42Njg0MkMxMS42NzQxIDYuMTkxMDYgMTEuNTI0OSA1LjgxODExIDExLjIyNjUgNS41NDk1OUMxMC45MjgyIDUuMjcxMTMgMTAuNDU1OCA1LjEzMTkgOS44MDkzNiA1LjEzMTlIOC4xMDg3NEw3LjQ5NzExIDguNTkyODFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K) - # REST Outbound Connector - Invoke REST API - # Camunda HTTP JSON Connector -Find the user documentation in -our [Camunda](https://docs.camunda.io/docs/components/connectors/protocol/rest/). +Find the user documentation in our [Camunda](https://docs.camunda.io/docs/components/connectors/protocol/rest/). ## Build @@ -120,13 +116,13 @@ The response will contain the status code, the headers and the body of the respo "method": "post", "url": "https://youroauthclientdomainname.eu.auth0.com/oauth/token", "authentication": { - "oauthTokenEndpoint": "{{secrets.OAUTH_TOKEN_ENDPOINT_KEY}}", + "oauthTokenEndpoint":"{{secrets.OAUTH_TOKEN_ENDPOINT_KEY}}", "scopes": "read:clients read:users", - "audience": "{{secrets.AUDIENCE_KEY}}", - "clientId": "{{secrets.CLIENT_ID_KEY}}", - "clientSecret": "{{secrets.CLIENT_SECRET_KEY}}", + "audience":"{{secrets.AUDIENCE_KEY}}", + "clientId":"{{secrets.CLIENT_ID_KEY}}", + "clientSecret":"{{secrets.CLIENT_SECRET_KEY}}", "type": "oauth-client-credentials-flow", - "clientAuthentication": "{{secrets.CLIENT_AUTHENTICATION_KEY}}" + "clientAuthentication":"{{secrets.CLIENT_AUTHENTICATION_KEY}}" } } ``` @@ -135,24 +131,21 @@ The response will contain the status code, the headers and the body of the respo ```json { - "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlUtN2N6WG1sMzljUFNfUnlQQkNMWCJ9.kjhwfjkhfejkrhfbwjkfbhetcetc", - "scope": "read:clients create:users", - "expires_in": 86400, - "token_type": "Bearer" + "access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlUtN2N6WG1sMzljUFNfUnlQQkNMWCJ9.kjhwfjkhfejkrhfbwjkfbhetcetc", + "scope":"read:clients create:users", + "expires_in":86400, + "token_type":"Bearer" } ``` - ### Error codes -The Connector will fail on any non-2XX HTTP status code in the response. This error status code will be passed on as -error code, e.g. "404". +The Connector will fail on any non-2XX HTTP status code in the response. This error status code will be passed on as error code, e.g. "404". ## :lock: Use proxy-mechanism > :warning: Proxy mode is currently only supported in Camunda 8 SaaS environment. -You can configure the HTTP JSON Connector to do any outgoing HTTP call via a proxy. This proxy should be effectively -also an HTTP JSON Connector +You can configure the HTTP JSON Connector to do any outgoing HTTP call via a proxy. This proxy should be effectively also an HTTP JSON Connector running in a different environment. For example, you can build the following runtime architecture: @@ -162,12 +155,10 @@ For example, you can build the following runtime architecture: [ Camunda Network, e.g. K8S ] [ Separate network, e.g. Google Function ] ``` -Now, any call via the Http Connector will be just forwarded to a specified hardcoded URL. And this proxy does the real -call then. +Now, any call via the Http Connector will be just forwarded to a specified hardcoded URL. And this proxy does the real call then. This avoids that you could reach internal endpoints in your Camunda network (e.g. the current Kubernetes cluster). -Just set the following property to enable proxy mode for the connector, e.g. in application.properties when using the -Spring-based runtime: +Just set the following property to enable proxy mode for the connector, e.g. in application.properties when using the Spring-based runtime: ```properties camunda.connector.http.proxy.url=https://someUrl/ @@ -187,8 +178,7 @@ GOOGLE_APPLICATION_CREDENTIALS=... ### :lock: Test the Connector locally with Google Cloud Function as a proxy -Run the [:lock:connector-proxy-saas](https://github.com/camunda/connector-proxy-saas) project locally as described in -its [:lock:README](https://github.com/camunda/connector-proxy-saas#usage). +Run the [:lock:connector-proxy-saas](https://github.com/camunda/connector-proxy-saas) project locally as described in its [:lock:README](https://github.com/camunda/connector-proxy-saas#usage). Set the specific property or environment variable to enable proxy mode as described above. @@ -200,53 +190,48 @@ The generic HTTP JSON Connector element template can be found in the [element-templates/http-json-connector.json](element-templates/http-json-connector.json) file. Additional Connector templates based on the HTTP JSON Connector: - - [Automation Anywhere Connector](../automation-anywhere) - [Blue Prism Connector](../blue-prism) - [UiPath Connector](../uipath) -## Properties +## Properties | Name | Type | Required | Description | Example | -|--------|----------|----------|-------------|----------------| +| ------ | -------- | -------- | ----------- | -------------- | | Method | Dropdown | Yes | | ```{ }``` | | URL | String | Yes | | ```"string"``` | - ## Result - The following json structure will be returned by the Connector and can be used in the result expression. ```json { - "body": { - "order": { - "id": "123", - "total": "100.00€" + "body" : { + "order" : { + "id" : "123", + "total" : "100.00€" } }, - "headers": { - "Content-Type": "application/json" + "headers" : { + "Content-Type" : "application/json" }, - "status": 200 + "status" : 200 } ``` The body can be accessed via FEEL: - ```json = body.order.id ``` - leading to the following result - ```json "123" ``` -| Connector Info | | -|-------------------------|------------------------| -| Type | io.camunda:http-json:1 | -| Version | 8 | -| Supported element types | | + +| Connector Info | | +| --- | --- | +| Type | io.camunda:http-json:1 | +| Version | 8 | +| Supported element types | |