This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new b00a3e98e3c CAMEL-20476 - Camel-AWS-Bedrock: Support Amazon Titan Image Generator G1 model (#13336) b00a3e98e3c is described below commit b00a3e98e3c124c20146a3d80c6ceb323a93072a Author: Andrea Cosentino <anco...@gmail.com> AuthorDate: Wed Feb 28 11:11:32 2024 +0100 CAMEL-20476 - Camel-AWS-Bedrock: Support Amazon Titan Image Generator G1 model (#13336) * CAMEL-20476 - Camel-AWS-Bedrock: Support Amazon Titan Image Generator G1 model Signed-off-by: Andrea Cosentino <anco...@gmail.com> * CAMEL-20476 - Camel-AWS-Bedrock: Support Amazon Titan Image Generator G1 model Signed-off-by: Andrea Cosentino <anco...@gmail.com> --------- Signed-off-by: Andrea Cosentino <anco...@gmail.com> --- .../camel/catalog/components/aws-bedrock.json | 4 +- components/camel-aws/camel-aws-bedrock/pom.xml | 5 ++ .../camel/component/aws2/bedrock/aws-bedrock.json | 4 +- .../component/aws2/bedrock/BedrockModels.java | 3 +- .../component/aws2/bedrock/BedrockOperations.java | 4 +- .../component/aws2/bedrock/BedrockProducer.java | 60 ++++++++++++++++++++++ .../bedrock/integration/BedrockProducerIT.java | 34 ++++++++++++ 7 files changed, 108 insertions(+), 6 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-bedrock.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-bedrock.json index 85c01b88d9a..02b0151a67e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-bedrock.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/aws-bedrock.json @@ -26,7 +26,7 @@ "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" }, "lazyStartProducer": { "index": 1, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] "modelId": { "index": 2, "kind": "property", "displayName": "Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Define the model Id we are going to use" }, - "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "The operation to [...] + "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel", "invokeImageModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description" [...] "overrideEndpoint": { "index": 4, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combina [...] "pojoRequest": { "index": 5, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "profileCredentialsName": { "index": 6, "kind": "property", "displayName": "Profile Credentials Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If using a profile credentials provider, this parameter wi [...] @@ -55,7 +55,7 @@ "properties": { "label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Logical name" }, "modelId": { "index": 1, "kind": "parameter", "displayName": "Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Define the model Id we are going to use" }, - "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "The operation to [...] + "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel", "invokeImageModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description [...] "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combin [...] "pojoRequest": { "index": 4, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "profileCredentialsName": { "index": 5, "kind": "parameter", "displayName": "Profile Credentials Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If using a profile credentials provider, this parameter w [...] diff --git a/components/camel-aws/camel-aws-bedrock/pom.xml b/components/camel-aws/camel-aws-bedrock/pom.xml index 522ceefc47c..40ba03486a3 100644 --- a/components/camel-aws/camel-aws-bedrock/pom.xml +++ b/components/camel-aws/camel-aws-bedrock/pom.xml @@ -66,6 +66,11 @@ <artifactId>camel-test-spring-junit5</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-base64</artifactId> + <scope>test</scope> + </dependency> <dependency> diff --git a/components/camel-aws/camel-aws-bedrock/src/generated/resources/META-INF/org/apache/camel/component/aws2/bedrock/aws-bedrock.json b/components/camel-aws/camel-aws-bedrock/src/generated/resources/META-INF/org/apache/camel/component/aws2/bedrock/aws-bedrock.json index 85c01b88d9a..02b0151a67e 100644 --- a/components/camel-aws/camel-aws-bedrock/src/generated/resources/META-INF/org/apache/camel/component/aws2/bedrock/aws-bedrock.json +++ b/components/camel-aws/camel-aws-bedrock/src/generated/resources/META-INF/org/apache/camel/component/aws2/bedrock/aws-bedrock.json @@ -26,7 +26,7 @@ "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Component configuration" }, "lazyStartProducer": { "index": 1, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] "modelId": { "index": 2, "kind": "property", "displayName": "Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Define the model Id we are going to use" }, - "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "The operation to [...] + "operation": { "index": 3, "kind": "property", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel", "invokeImageModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description" [...] "overrideEndpoint": { "index": 4, "kind": "property", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combina [...] "pojoRequest": { "index": 5, "kind": "property", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "profileCredentialsName": { "index": 6, "kind": "property", "displayName": "Profile Credentials Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If using a profile credentials provider, this parameter wi [...] @@ -55,7 +55,7 @@ "properties": { "label": { "index": 0, "kind": "path", "displayName": "Label", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Logical name" }, "modelId": { "index": 1, "kind": "parameter", "displayName": "Model Id", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Define the model Id we are going to use" }, - "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "The operation to [...] + "operation": { "index": 2, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "object", "javaType": "org.apache.camel.component.aws2.bedrock.BedrockOperations", "enum": [ "invokeTextModel", "invokeImageModel" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description [...] "overrideEndpoint": { "index": 3, "kind": "parameter", "displayName": "Override Endpoint", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "Set the need for overriding the endpoint. This option needs to be used in combin [...] "pojoRequest": { "index": 4, "kind": "parameter", "displayName": "Pojo Request", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If we want to use a POJO request as body or not" }, "profileCredentialsName": { "index": 5, "kind": "parameter", "displayName": "Profile Credentials Name", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.aws2.bedrock.BedrockConfiguration", "configurationField": "configuration", "description": "If using a profile credentials provider, this parameter w [...] diff --git a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java index 25fb7bb0f47..c1cce5e044c 100644 --- a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java +++ b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockModels.java @@ -19,7 +19,8 @@ package org.apache.camel.component.aws2.bedrock; public enum BedrockModels { TITAN_TEXT_EXPRESS_V1("amazon.titan-text-express-v1"), - TITAN_TEXT_LITE_V1("amazon.titan-text-lite-v1"); + TITAN_TEXT_LITE_V1("amazon.titan-text-lite-v1"), + TITAN_IMAGE_GENERATOR_V1("amazon.titan-image-generator-v1"); public final String model; diff --git a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockOperations.java b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockOperations.java index 6b84c2f46b6..e99f0e08f9e 100644 --- a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockOperations.java +++ b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockOperations.java @@ -18,5 +18,7 @@ package org.apache.camel.component.aws2.bedrock; public enum BedrockOperations { - invokeTextModel + invokeTextModel, + + invokeImageModel } diff --git a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockProducer.java b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockProducer.java index 675b7ca571f..990ff1f75e6 100644 --- a/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockProducer.java +++ b/components/camel-aws/camel-aws-bedrock/src/main/java/org/apache/camel/component/aws2/bedrock/BedrockProducer.java @@ -16,6 +16,9 @@ */ package org.apache.camel.component.aws2.bedrock; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; @@ -50,6 +53,9 @@ public class BedrockProducer extends DefaultProducer { case invokeTextModel: invokeTextModel(getEndpoint().getBedrockRuntimeClient(), exchange); break; + case invokeImageModel: + invokeImageModel(getEndpoint().getBedrockRuntimeClient(), exchange); + break; default: throw new IllegalArgumentException("Unsupported operation"); } @@ -124,6 +130,60 @@ public class BedrockProducer extends DefaultProducer { } } + private void invokeImageModel(BedrockRuntimeClient bedrockRuntimeClient, Exchange exchange) throws InvalidPayloadException { + if (getConfiguration().isPojoRequest()) { + Object payload = exchange.getMessage().getMandatoryBody(); + if (payload instanceof InvokeModelRequest) { + InvokeModelResponse result; + try { + result = bedrockRuntimeClient.invokeModel((InvokeModelRequest) payload); + } catch (AwsServiceException ase) { + LOG.trace("Invoke Image Model command returned the error code {}", ase.awsErrorDetails().errorCode()); + throw ase; + } + Message message = getMessageForResponse(exchange); + message.setBody(result); + } + } else { + InvokeModelRequest.Builder builder = InvokeModelRequest.builder(); + if (ObjectHelper.isNotEmpty(exchange.getMessage().getHeader(BedrockConstants.MODEL_CONTENT_TYPE))) { + String contentType = exchange.getIn().getHeader(BedrockConstants.MODEL_CONTENT_TYPE, String.class); + builder.contentType(contentType); + } else { + throw new IllegalArgumentException("Model Content Type must be specified"); + } + if (ObjectHelper.isNotEmpty(exchange.getMessage().getHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE))) { + String acceptContentType = exchange.getIn().getHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, String.class); + builder.accept(acceptContentType); + } else { + throw new IllegalArgumentException("Model Accept Content Type must be specified"); + } + InvokeModelRequest request = builder + .body(SdkBytes.fromUtf8String(String.valueOf(exchange.getMessage().getBody()))) + .modelId(getConfiguration().getModelId()) + .build(); + InvokeModelResponse result; + try { + result = bedrockRuntimeClient.invokeModel(request); + } catch (AwsServiceException ase) { + LOG.trace("Invoke Model command returned the error code {}", ase.awsErrorDetails().errorCode()); + throw ase; + } + Message message = getMessageForResponse(exchange); + try { + setBase64Image(result, message); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + } + + private static void setBase64Image(InvokeModelResponse result, Message message) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonString = mapper.readTree(result.body().asUtf8String()); + message.setBody(jsonString.get("images").get(0)); + } + public static Message getMessageForResponse(final Exchange exchange) { return exchange.getMessage(); } diff --git a/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/integration/BedrockProducerIT.java b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/integration/BedrockProducerIT.java index bd6261e62e1..5fbab759ffd 100644 --- a/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/integration/BedrockProducerIT.java +++ b/components/camel-aws/camel-aws-bedrock/src/test/java/org/apache/camel/component/aws2/bedrock/integration/BedrockProducerIT.java @@ -98,6 +98,33 @@ class BedrockProducerIT extends CamelTestSupport { MockEndpoint.assertIsSatisfied(context); } + @Test + public void testInvokeTitanImageModel() throws InterruptedException { + + result.expectedMessageCount(1); + final Exchange result = template.send("direct:send_titan_image", exchange -> { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode rootNode = mapper.createObjectNode(); + ObjectNode textParameter = mapper.createObjectNode(); + textParameter.put("text", + "A ancient time camel running in the desert"); + rootNode.put("textToImageParams", textParameter); + rootNode.put("taskType", "TEXT_IMAGE"); + ObjectNode childNode = mapper.createObjectNode(); + childNode.put("numberOfImages", 1); + childNode.put("quality", "standard"); + childNode.put("cfgScale", 8).put("height", 512).put("width", 512).put("seed", 0); + + rootNode.put("imageGenerationConfig", childNode); + + exchange.getMessage().setBody(mapper.writer().writeValueAsString(rootNode)); + exchange.getMessage().setHeader(BedrockConstants.MODEL_CONTENT_TYPE, "application/json"); + exchange.getMessage().setHeader(BedrockConstants.MODEL_ACCEPT_CONTENT_TYPE, "application/json"); + }); + + MockEndpoint.assertIsSatisfied(context); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -112,6 +139,13 @@ class BedrockProducerIT extends CamelTestSupport { .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}®ion=us-east-1&operation=invokeTextModel&modelId=" + BedrockModels.TITAN_TEXT_LITE_V1.model) .to(result); + + from("direct:send_titan_image") + .to("aws-bedrock:label?accessKey=RAW({{aws.manual.access.key}})&secretKey=RAW({{aws.manual.secret.key}}®ion=us-east-1&operation=invokeImageModel&modelId=" + + BedrockModels.TITAN_IMAGE_GENERATOR_V1.model) + .unmarshal().base64() + .setHeader("CamelFileName", constant("image.png")).to("file:target/generated_images") + .to(result); } }; }