-
Notifications
You must be signed in to change notification settings - Fork 53
feat(aws): add Bedrock Knowledge Base connector #6799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
8b228cb
506a5a7
a0ac3f5
947abc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>io.camunda.connector</groupId> | ||
| <artifactId>connector-aws-parent</artifactId> | ||
| <version>8.9.0-SNAPSHOT</version> | ||
| </parent> | ||
|
|
||
| <artifactId>connector-aws-bedrock-knowledgebase</artifactId> | ||
| <name>connector-aws-bedrock-knowledgebase</name> | ||
| <description>Camunda Connector for AWS Bedrock Knowledge Base retrieval</description> | ||
| <packaging>jar</packaging> | ||
|
|
||
| <licenses> | ||
| <license> | ||
| <name>Camunda Self-Managed Free Edition license</name> | ||
| <url> | ||
| https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/ | ||
| </url> | ||
| </license> | ||
| <license> | ||
| <name>Camunda Self-Managed Enterprise Edition license</name> | ||
| </license> | ||
| </licenses> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>io.camunda.connector</groupId> | ||
| <artifactId>connector-aws-base</artifactId> | ||
| <version>${project.version}</version> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>bedrockagentruntime</artifactId> | ||
| <version>${version.aws-sdk2}</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>auth</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>regions</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>sdk-core</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>aws-core</artifactId> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>io.camunda.connector</groupId> | ||
| <artifactId>connector-object-mapper</artifactId> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>io.camunda.connector</groupId> | ||
| <artifactId>element-template-generator-annotations</artifactId> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>io.camunda.connector</groupId> | ||
| <artifactId>element-template-generator-maven-plugin</artifactId> | ||
| <version>${project.version}</version> | ||
| <configuration> | ||
| <connectors> | ||
| <connector> | ||
| <connectorClass> | ||
| io.camunda.connector.aws.bedrock.knowledgebase.BedrockKnowledgeBaseConnectorFunction | ||
| </connectorClass> | ||
| <files> | ||
| <file> | ||
| <templateId>io.camunda.connectors.aws.bedrock.knowledgebase.v1</templateId> | ||
| <templateFileName>aws-bedrock-knowledgebase-outbound-connector.json</templateFileName> | ||
| </file> | ||
| </files> | ||
| <generateHybridTemplates>true</generateHybridTemplates> | ||
| </connector> | ||
| </connectors> | ||
| <versionHistoryEnabled>true</versionHistoryEnabled> | ||
| <includeDependencies> | ||
| <includeDependency>io.camunda.connector:connector-aws-base</includeDependency> | ||
| </includeDependencies> | ||
| </configuration> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
|
|
||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * 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.knowledgebase; | ||
|
|
||
| import io.camunda.connector.api.annotation.OutboundConnector; | ||
| import io.camunda.connector.api.outbound.OutboundConnectorContext; | ||
| import io.camunda.connector.api.outbound.OutboundConnectorFunction; | ||
| import io.camunda.connector.aws.CredentialsProviderSupportV2; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.request.BedrockKnowledgeBaseRequest; | ||
| import io.camunda.connector.generator.java.annotation.ElementTemplate; | ||
| import io.camunda.connector.generator.java.annotation.ElementTemplate.PropertyGroup; | ||
| import io.camunda.connector.jackson.ConnectorsObjectMapperSupplier; | ||
| import java.net.URI; | ||
| import software.amazon.awssdk.regions.Region; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.BedrockAgentRuntimeClient; | ||
|
|
||
| @OutboundConnector( | ||
| name = "AWS Bedrock Knowledge Base", | ||
| inputVariables = { | ||
| "authentication", | ||
| "configuration", | ||
| "knowledgeBaseId", | ||
| "operation", | ||
| "operationDiscriminator" | ||
| }, | ||
| type = "io.camunda:aws-bedrock-knowledgebase:1") | ||
| @ElementTemplate( | ||
| engineVersion = "^8.9", // TODO: update to ^8.10 when available | ||
| id = "io.camunda.connectors.aws.bedrock.knowledgebase.v1", | ||
| name = "AWS Bedrock Knowledge Base Outbound Connector", | ||
| description = "Retrieve relevant documents from an AWS Bedrock Knowledge Base", | ||
| inputDataClass = BedrockKnowledgeBaseRequest.class, | ||
| version = 1, | ||
| propertyGroups = { | ||
| @PropertyGroup(id = "authentication", label = "Authentication"), | ||
| @PropertyGroup(id = "configuration", label = "Configuration"), | ||
| @PropertyGroup(id = "operation", label = "Operation"), | ||
| @PropertyGroup(id = "retrieve", label = "Retrieve from Knowledge Base"), | ||
| }, | ||
| icon = "icon.png") | ||
| public class BedrockKnowledgeBaseConnectorFunction implements OutboundConnectorFunction { | ||
|
|
||
| @Override | ||
| public Object execute(OutboundConnectorContext context) { | ||
| var request = context.bindVariables(BedrockKnowledgeBaseRequest.class); | ||
| try (var client = buildClient(request)) { | ||
| return new BedrockKnowledgeBaseExecutor(client, ConnectorsObjectMapperSupplier.getCopy()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔧 I guess we could avoid creating a new |
||
| .execute(request, context::create); | ||
| } | ||
| } | ||
|
|
||
| private BedrockAgentRuntimeClient buildClient(BedrockKnowledgeBaseRequest request) { | ||
| var builder = | ||
| BedrockAgentRuntimeClient.builder() | ||
| .credentialsProvider(CredentialsProviderSupportV2.credentialsProvider(request)); | ||
| var config = request.getConfiguration(); | ||
| if (config != null && config.region() != null) { | ||
| builder.region(Region.of(config.region())); | ||
| } | ||
| if (config != null && config.endpoint() != null && !config.endpoint().isBlank()) { | ||
| builder.endpointOverride(URI.create(config.endpoint())); | ||
| } | ||
| return builder.build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| /* | ||
| * 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.knowledgebase; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import io.camunda.connector.api.document.Document; | ||
| import io.camunda.connector.api.document.DocumentCreationRequest; | ||
| import io.camunda.connector.api.error.ConnectorException; | ||
| import io.camunda.connector.api.error.ConnectorRetryException; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.request.BedrockKnowledgeBaseRequest; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.request.RetrieveOperation; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.response.BedrockKnowledgeBaseResponse; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.response.KnowledgeBaseRetrievalResult; | ||
| import io.camunda.connector.aws.bedrock.knowledgebase.model.response.RetrievalResultEntry; | ||
| import java.time.Instant; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.BedrockAgentRuntimeClient; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.BedrockAgentRuntimeException; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.KnowledgeBaseRetrievalConfiguration; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.KnowledgeBaseVectorSearchConfiguration; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.RetrieveRequest; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.RetrieveResponse; | ||
| import software.amazon.awssdk.services.bedrockagentruntime.model.ThrottlingException; | ||
|
|
||
| public class BedrockKnowledgeBaseExecutor { | ||
|
|
||
| private final BedrockAgentRuntimeClient client; | ||
| private final ObjectMapper objectMapper; | ||
|
|
||
| public BedrockKnowledgeBaseExecutor(BedrockAgentRuntimeClient client, ObjectMapper objectMapper) { | ||
| this.client = client; | ||
| this.objectMapper = objectMapper; | ||
| } | ||
|
|
||
| public KnowledgeBaseRetrievalResult execute( | ||
| BedrockKnowledgeBaseRequest request, | ||
| Function<DocumentCreationRequest, Document> documentFactory) { | ||
| return switch (request.getOperation()) { | ||
| case RetrieveOperation op -> retrieve(op, request, documentFactory); | ||
| }; | ||
| } | ||
|
|
||
| private KnowledgeBaseRetrievalResult retrieve( | ||
| RetrieveOperation op, | ||
| BedrockKnowledgeBaseRequest request, | ||
| Function<DocumentCreationRequest, Document> documentFactory) { | ||
| try { | ||
| var sdkRequestBuilder = | ||
| RetrieveRequest.builder() | ||
| .knowledgeBaseId(request.getKnowledgeBaseId()) | ||
| .retrievalQuery(q -> q.text(op.query())); | ||
|
|
||
| if (op.numberOfResults() != null) { | ||
| sdkRequestBuilder.retrievalConfiguration( | ||
| KnowledgeBaseRetrievalConfiguration.builder() | ||
| .vectorSearchConfiguration( | ||
| KnowledgeBaseVectorSearchConfiguration.builder() | ||
| .numberOfResults(op.numberOfResults()) | ||
| .build()) | ||
| .build()); | ||
| } | ||
|
|
||
| RetrieveResponse sdkResponse = client.retrieve(sdkRequestBuilder.build()); | ||
|
|
||
| var response = mapSdkResponse(sdkResponse); | ||
| byte[] json = objectMapper.writeValueAsBytes(response); | ||
|
|
||
| Document doc = | ||
| documentFactory.apply( | ||
| DocumentCreationRequest.from(json) | ||
| .contentType("application/json") | ||
| .fileName("kb-retrieval-" + Instant.now().toEpochMilli() + ".json") | ||
| .build()); | ||
|
|
||
| return new KnowledgeBaseRetrievalResult(doc, response.results().size(), response.nextToken()); | ||
|
Comment on lines
+79
to
+86
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔧 The Vector DB connector (which as I understand it does something very similar) returns a list of individual documents (one per chunk) for the retrieve operation. Do you think it would make sense to align this here? Regarding the file name, some integrations don't set a file name at all - maybe worth checking if we actually need it. If we keep it, we could maybe use the element instance key from the connector job context as unique key which we can correlate with the job. |
||
|
|
||
| } catch (ThrottlingException e) { | ||
| throw ConnectorRetryException.builder() | ||
| .errorCode("THROTTLED") | ||
| .message("Bedrock Knowledge Base request was throttled: " + e.getMessage()) | ||
| .cause(e) | ||
| .build(); | ||
| } catch (BedrockAgentRuntimeException e) { | ||
| var errorMsg = | ||
| e.awsErrorDetails() != null ? e.awsErrorDetails().errorMessage() : e.getMessage(); | ||
| throw new ConnectorException( | ||
| "KB_RETRIEVAL_FAILED", "Bedrock Knowledge Base error: " + errorMsg, e); | ||
| } catch (JsonProcessingException e) { | ||
| throw new ConnectorException( | ||
| "SERIALIZATION_ERROR", "Failed to serialize retrieval results to JSON", e); | ||
| } | ||
| } | ||
|
|
||
| private BedrockKnowledgeBaseResponse mapSdkResponse(RetrieveResponse sdkResponse) { | ||
| var results = | ||
| sdkResponse.retrievalResults().stream() | ||
| .map( | ||
| r -> | ||
| new RetrievalResultEntry( | ||
| r.content() != null ? r.content().text() : null, | ||
| r.score(), | ||
| r.location() != null && r.location().s3Location() != null | ||
| ? r.location().s3Location().uri() | ||
| : null, | ||
| r.hasMetadata() | ||
| ? r.metadata().entrySet().stream() | ||
| .collect( | ||
| Collectors.toMap( | ||
| Map.Entry::getKey, | ||
| e -> e.getValue() != null ? e.getValue().toString() : "")) | ||
| : Map.of())) | ||
| .toList(); | ||
|
||
| return new BedrockKnowledgeBaseResponse(results, sdkResponse.nextToken()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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.knowledgebase.model.request; | ||
|
|
||
| import io.camunda.connector.api.annotation.FEEL; | ||
| import io.camunda.connector.aws.model.impl.AwsBaseRequest; | ||
| import io.camunda.connector.generator.java.annotation.TemplateProperty; | ||
| import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyConstraints; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public class BedrockKnowledgeBaseRequest extends AwsBaseRequest { | ||
|
|
||
| @FEEL | ||
| @NotBlank | ||
| @TemplateProperty( | ||
| group = "configuration", | ||
| label = "Knowledge Base ID", | ||
| description = "The ID of the Bedrock Knowledge Base to query.", | ||
| constraints = @PropertyConstraints(notEmpty = true)) | ||
mdii marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private String knowledgeBaseId; | ||
|
|
||
| @Valid @NotNull private KnowledgeBaseOperation operation; | ||
|
|
||
| public String getKnowledgeBaseId() { | ||
| return knowledgeBaseId; | ||
| } | ||
|
|
||
| public void setKnowledgeBaseId(String knowledgeBaseId) { | ||
| this.knowledgeBaseId = knowledgeBaseId; | ||
| } | ||
|
|
||
| public KnowledgeBaseOperation getOperation() { | ||
| return operation; | ||
| } | ||
|
|
||
| public void setOperation(KnowledgeBaseOperation operation) { | ||
| this.operation = operation; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* | ||
| * 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.knowledgebase.model.request; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonSubTypes; | ||
| import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
| import io.camunda.connector.generator.java.annotation.TemplateDiscriminatorProperty; | ||
|
|
||
| @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "operationDiscriminator") | ||
| @JsonSubTypes({ | ||
| @JsonSubTypes.Type(value = RetrieveOperation.class, name = "retrieve"), | ||
| }) | ||
| @TemplateDiscriminatorProperty( | ||
| label = "Operation", | ||
| group = "operation", | ||
| name = "operationDiscriminator", | ||
| defaultValue = "retrieve") | ||
| public sealed interface KnowledgeBaseOperation permits RetrieveOperation {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to declare
operationDiscriminatorhere 🤔 ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we do. I followed a similar approach as the
actionDiscriminatorin the S3 connector.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a difference between how it is done in the S3 connector. Here
operationDiscriminatoris a nested property ofoperationwhereas in the S3 connectoractionDiscriminatoris at the root level. You can try removing it as see if the connector works as before.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
operationDiscriminatordoes no exist anyways. There is anoperation.operationDiscriminatorinstead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct, I've updated it