Skip to content

Commit

Permalink
SAS-715: upgraded to Mx 10.12.4, upgraded all our assets to latest MP…
Browse files Browse the repository at this point in the history
… version.
  • Loading branch information
liamsommer-mx committed Aug 28, 2024
1 parent c44d40b commit 6fac6e1
Show file tree
Hide file tree
Showing 46 changed files with 1,570 additions and 1,404 deletions.
Binary file modified AI Bot Starter App.mpr
Binary file not shown.
145 changes: 109 additions & 36 deletions javasource/amazonbedrockconnector/actions/Converse.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock;
import software.amazon.awssdk.services.bedrockruntime.model.ConverseOutput;
import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse;
import software.amazon.awssdk.services.bedrockruntime.model.DocumentBlock;
import software.amazon.awssdk.services.bedrockruntime.model.DocumentSource;
import software.amazon.awssdk.services.bedrockruntime.model.ImageBlock;
import software.amazon.awssdk.services.bedrockruntime.model.ImageSource;
import software.amazon.awssdk.services.bedrockruntime.model.InferenceConfiguration;
Expand Down Expand Up @@ -147,6 +149,13 @@ public java.lang.String toString()
private static final MxLogger LOGGER = new MxLogger(Converse.class);
private static final ObjectMapper MAPPER = new ObjectMapper();

/*
* The name attribute of a document that is sent to the model.
* This field is vulnerable to prompt injections, because the model might inadvertently interpret it as instructions
* Therefore, it is recommended to use a hardcoded name.
*/
private static final String DOC_NAME = "user document";

//Request Mapping

// Main method to build AWS Request
Expand Down Expand Up @@ -335,27 +344,57 @@ private Message getAwsMessage(genaicommons.proxies.Message mxMsg, List<genaicomm

List<ContentBlock> contentBlockList = new ArrayList<>();

// Case 1: Message contains images.
if (hasImages(mxMsg)) {
LOGGER.debug("Message with Image found");
// Check if a message content is present and add as additional text content.
// Case 1: Message has a FileCollection with FileContent(s).
if (hasFiles(mxMsg)) {
LOGGER.debug("Message with Files found");

// Check if a message content is present it can be added as a separate text content.
if (mxMsg.getContent() != null && !mxMsg.getContent().isBlank()) {
ContentBlock textContent = getTextContent(mxMsg.getContent());
contentBlockList.add(textContent);
}

// Adding image content for each file
List<FileContent> images = getImages(mxMsg);
for (FileContent image : images) {
// Count for documents in message
// Used for unique document name
int j = 0;

// Adding file content for each file
List<FileContent> files = getFiles(mxMsg);
for (FileContent file : files) {

// Adding additional text content if TextContent attribute contains content
if (image.getTextContent() != null && !image.getTextContent().isBlank()) {
ContentBlock imgTextContent = getTextContent(image.getTextContent());
if (file.getTextContent() != null && !file.getTextContent().isBlank()) {
ContentBlock imgTextContent = getTextContent(file.getTextContent());
contentBlockList.add(imgTextContent);
}
ContentBlock imageContent = getImageContent(image);
if (imageContent != null) {
contentBlockList.add(imageContent);

// Checking if file is image or document
// Then creating the corresponding content block types for it
ENUM_FileType fileType = file.getFileType();
if (fileType != null) {

switch (fileType) {
case image: {
ContentBlock imageContentBlock = getImageContent(file);
if (imageContentBlock != null) {
contentBlockList.add(imageContentBlock);
}
break;
}
case document: {
ContentBlock documentContentBlock = getDocumentContent(file, i, j);
contentBlockList.add(documentContentBlock);
j++;
break;
}
default:
LOGGER.warn("Unsupported FileContent FileType found in request.");
break;
}
} else {
LOGGER.error("FileContent with empty FileType found in request.");
}

}

// Case 2: After a Function Call, a Tool Result message is being sent
Expand Down Expand Up @@ -404,7 +443,7 @@ private Message getAwsMessage(genaicommons.proxies.Message mxMsg, List<genaicomm
}

// Check if the message has images
private boolean hasImages(genaicommons.proxies.Message mxMsg) throws CoreException {
private boolean hasFiles(genaicommons.proxies.Message mxMsg) throws CoreException {
FileCollection fileCol = mxMsg.getMessage_FileCollection();
if (fileCol == null) {
return false;
Expand All @@ -415,19 +454,48 @@ private boolean hasImages(genaicommons.proxies.Message mxMsg) throws CoreExcepti
return false;
}

List<FileContent> images = filterFileContentByFileType(fileContents, ENUM_FileType.image);
return images.size() > 0;
return true;
}

// Helper to filter FileContent objects
private List<FileContent> filterFileContentByFileType(List<FileContent> fileContent, ENUM_FileType type) {
return fileContent.stream().filter(fc -> fc.getFileType() == type).collect(Collectors.toList());
private ContentBlock getDocumentContent(FileContent doc, int i, int j) {
// Creating document content block
// Using fixed name because this field is vulnerable to prompt injection
// source is fileContent attribute as byte[] from base64 string
String format = getFileExtension(doc);
String name = String.format("%s-%s-%s", DOC_NAME, i, j);
DocumentSource source = getDocSource(doc);

DocumentBlock docBlock = DocumentBlock.builder()
.format(format)
.name(name)
.source(source)
.build();

return ContentBlock.builder()
.document(docBlock)
.build();
}

private String getFileExtension(FileContent fc) {
String extension = fc.getFileExtension();
if (extension == null || extension.isBlank()) {
LOGGER.error("FileContent with empty FileExtension found in request.");
return null;
}

return extension;
}

private DocumentSource getDocSource(FileContent doc) {
byte[] bytes = Base64.getDecoder().decode(doc.getFileContent());
var builder = DocumentSource.builder()
.bytes(SdkBytes.fromByteArray(bytes));
return builder.build();
}

// Helper to get all Image FileContent objects from a message
private List<FileContent> getImages(genaicommons.proxies.Message mxMsg) throws CoreException {
List<FileContent> fileContents = mxMsg.getMessage_FileCollection().getFileCollection_FileContent();
return filterFileContentByFileType(fileContents, ENUM_FileType.image);
private List<FileContent> getFiles(genaicommons.proxies.Message mxMsg) throws CoreException {
return mxMsg.getMessage_FileCollection().getFileCollection_FileContent();
}

// Creating a Content Block with text
Expand Down Expand Up @@ -465,7 +533,7 @@ private ContentBlock getImageContentBlock(String format, byte[] bytes) {
}

private ContentBlock getImageContentBase64(FileContent mxImage) {
String format = getImageFormat(mxImage.getMediaType());
String format = getImageExtension(mxImage);
byte[] bytes = Base64.getDecoder().decode(mxImage.getFileContent());

return getImageContentBlock(format, bytes);
Expand All @@ -481,8 +549,8 @@ private ContentBlock getImageContentURI(FileContent mxImage) throws URISyntaxExc
byte[] imageBytes = IOUtils.toByteArray(is);

String format;
if (mxImage.getMediaType() != null && !mxImage.getMediaType().isBlank()) {
format = getImageFormat(mxImage.getMediaType());
if (mxImage.getFileExtension() != null && !mxImage.getFileExtension().isBlank()) {
format = getFileExtension(mxImage);
} else {
format = getFormatFromURL(url);
}
Expand All @@ -491,20 +559,19 @@ private ContentBlock getImageContentURI(FileContent mxImage) throws URISyntaxExc
}
}

// Image Format from mediaType attribute
// Bedrock accetps "jpeg", not "jpp"
private String getImageFormat(String mediaType) {
String format = mediaType.substring(mediaType.indexOf("/") + 1);
if (format.equals("jpg")) {
format = "jpeg";
private String getImageExtension(FileContent fc) {
String extension = getFileExtension(fc);
if (extension != null && extension.equals("jpg")) {
extension = "jpeg";
}
return format;
return extension;
}

// Image Format from URL
private String getFormatFromURL(URL url) throws MalformedURLException {
String file = url.getFile();
String format = file.substring(file.lastIndexOf('.') + 1);
String format = file.substring(file.lastIndexOf(".") + 1);
if (format.equals("jpg")) {
format= "jpeg";
}
Expand All @@ -531,7 +598,7 @@ private ContentBlock getToolUseContent(ToolCall mxToolCall) throws JsonMappingEx
.toolUseId(mxToolCall.getToolCallId());

if (mxToolCall.getArguments() == null || mxToolCall.getArguments().isBlank()) {
builder.input(Document.mapBuilder().putString("", "").build());
builder.input(Document.mapBuilder().build());

} else {
// Arguments JSON must be build by Document.mapBuilder()
Expand Down Expand Up @@ -621,7 +688,7 @@ private software.amazon.awssdk.services.bedrockruntime.model.Tool getAwsTool(Too
.name(mxTool.getName())
.description(mxTool.getDescription())
.inputSchema(getToolInputSchema(mxTool));

return software.amazon.awssdk.services.bedrockruntime.model.Tool.builder().toolSpec(toolSpecBuilder.build()).build();
}

Expand All @@ -631,7 +698,13 @@ private ToolInputSchema getToolInputSchema(Tool mxTool) throws JsonProcessingExc
Function function = (Function) mxTool;
String inputParamName = FunctionMappingImpl.getFirstInputParamName(function.getMicroflow());
if (inputParamName == null) {
return null;
LOGGER.debug("Function Microflow without input parameter");

Document json = Document.mapBuilder()
.putString("type", "object")
.build();

return ToolInputSchema.builder().json(json).build();
}
// Must be created using Document.mapBuilder()
// Constructing the JSON in a different way causes errors
Expand Down Expand Up @@ -833,8 +906,8 @@ private void setMessageToolUseContent(List<ToolCall> toolCallList, ToolUseBlock
// Getting requested input parameters and storing them as Json string
private String getInputParamsJson(Document awsDoc) throws JsonProcessingException {
if (!awsDoc.isMap() || awsDoc.asMap().isEmpty()) {
LOGGER.error("No Input Schema for Tool Use received.");
return "{}";
LOGGER.debug("Tool without parameter called");
return null;
}
// Returned map always has only one value because Function microflows have single parameter
Map.Entry<String, Document> entry = awsDoc.asMap().entrySet().iterator().next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class AmazonBedrockClient {
private static final MxLogger LOGGER = new MxLogger(AmazonBedrockClient.class);

//TODO Replace X.Y.Z below with correct version nr and delete this line in rc-branch
private static final String AWS_HEADER_VALUE = "Mendix-Bedrock-5.0.1";
private static final String AWS_HEADER_VALUE = "Mendix-Bedrock-5.2.0";

public static BedrockClient getBedrockClient(Credentials credentials, ENUM_Region region, AbstractRequest request) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class AWSBuilderConfigurator<BuilderT extends AwsSyncClientBuilder<Builde
private static final MxLogger LOGGER = new MxLogger(AWSBuilderConfigurator.class);

//TODO Replace X.Y.Z below with correct version nr and delete this line in rc-branch
private static final String AWS_HEADER_VALUE = "Mendix-Authentication-3.1.1";
private static final String AWS_HEADER_VALUE = "Mendix-Authentication-3.1.2";

private AbstractRequest abstractRequest;
private ENUM_Region region;
Expand Down
Binary file modified modules/GenAICommons.mxmodule
Binary file not shown.
3 changes: 2 additions & 1 deletion modules/javasource/genaicommons/proxies/ENUM_FileType.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

public enum ENUM_FileType
{
image(new java.lang.String[][] { new java.lang.String[] { "en_US", "Image" } });
image(new java.lang.String[][] { new java.lang.String[] { "en_US", "Image" } }),
document(new java.lang.String[][] { new java.lang.String[] { "en_US", "Document" } });

private final java.util.Map<java.lang.String, java.lang.String> captions;

Expand Down
30 changes: 15 additions & 15 deletions modules/javasource/genaicommons/proxies/FileContent.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public enum MemberNames
ContentType("ContentType"),
FileType("FileType"),
TextContent("TextContent"),
MediaType("MediaType");
FileExtension("FileExtension");

private final java.lang.String metaName;

Expand Down Expand Up @@ -245,39 +245,39 @@ public final void setTextContent(com.mendix.systemwideinterfaces.core.IContext c
}

/**
* @return value of MediaType
* @return value of FileExtension
*/
public final java.lang.String getMediaType()
public final java.lang.String getFileExtension()
{
return getMediaType(getContext());
return getFileExtension(getContext());
}

/**
* @param context
* @return value of MediaType
* @return value of FileExtension
*/
public final java.lang.String getMediaType(com.mendix.systemwideinterfaces.core.IContext context)
public final java.lang.String getFileExtension(com.mendix.systemwideinterfaces.core.IContext context)
{
return (java.lang.String) getMendixObject().getValue(context, MemberNames.MediaType.toString());
return (java.lang.String) getMendixObject().getValue(context, MemberNames.FileExtension.toString());
}

/**
* Set value of MediaType
* @param mediatype
* Set value of FileExtension
* @param fileextension
*/
public final void setMediaType(java.lang.String mediatype)
public final void setFileExtension(java.lang.String fileextension)
{
setMediaType(getContext(), mediatype);
setFileExtension(getContext(), fileextension);
}

/**
* Set value of MediaType
* Set value of FileExtension
* @param context
* @param mediatype
* @param fileextension
*/
public final void setMediaType(com.mendix.systemwideinterfaces.core.IContext context, java.lang.String mediatype)
public final void setFileExtension(com.mendix.systemwideinterfaces.core.IContext context, java.lang.String fileextension)
{
getMendixObject().setValue(context, MemberNames.MediaType.toString(), mediatype);
getMendixObject().setValue(context, MemberNames.FileExtension.toString(), fileextension);
}

@java.lang.Override
Expand Down
Loading

0 comments on commit 6fac6e1

Please sign in to comment.