This is an automated email from the ASF dual-hosted git repository.

orpiske 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 9b112b746aa CAMEL-20968 : Camel LangChain4j : add the RAG feature
9b112b746aa is described below

commit 9b112b746aa9ba860147c03a0018e2672d7c14ae
Author: Zineb Bendhiba <[email protected]>
AuthorDate: Tue Aug 6 16:35:25 2024 +0200

    CAMEL-20968 : Camel LangChain4j : add the RAG feature
---
 .../camel/catalog/components/langchain4j-chat.json |   3 +-
 .../langchain4j/chat/langchain4j-chat.json         |   3 +-
 .../src/main/docs/langchain4j-chat-component.adoc  |  61 ++++++++
 .../langchain4j/chat/LangChain4jChat.java          |   3 +
 .../langchain4j/chat/LangChain4jChatProducer.java  |  66 ++++++---
 .../chat/rag/LangChain4jRagAggregatorStrategy.java |  59 ++++++++
 .../langchain4j.chat/LangChain4jChatIT.java        | 154 ++++++++++++++++++++-
 .../LangChain4jRagAggregatorTest.java              | 121 ++++++++++++++++
 .../langchain4j.chat/OllamaTestSupport.java        |   2 +-
 .../dsl/LangChain4jChatEndpointBuilderFactory.java |  12 ++
 10 files changed, 461 insertions(+), 23 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
index b75b00e3d06..9ce394ac4b7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-chat.json
@@ -30,7 +30,8 @@
     "chatModel": { "index": 4, "kind": "property", "displayName": "Chat 
Model", "group": "advanced", "label": "advanced", "required": false, "type": 
"object", "javaType": "dev.langchain4j.model.chat.ChatLanguageModel", 
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, 
"configurationClass": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatConfiguration", 
"configurationField": "configuration", "description": "Chat Language Model of 
type dev.langchai [...]
   },
   "headers": {
-    "CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The prompt Template.", "constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#PROMPT_TEMPLATE"
 }
+    "CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The prompt Template.", "constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#PROMPT_TEMPLATE"
 },
+    "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Augmented Data for RAG", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#AUGMENTED_DATA"
 }
   },
   "properties": {
     "chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The id" },
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
 
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
index b75b00e3d06..9ce394ac4b7 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/chat/langchain4j-chat.json
@@ -30,7 +30,8 @@
     "chatModel": { "index": 4, "kind": "property", "displayName": "Chat 
Model", "group": "advanced", "label": "advanced", "required": false, "type": 
"object", "javaType": "dev.langchain4j.model.chat.ChatLanguageModel", 
"deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, 
"configurationClass": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChatConfiguration", 
"configurationField": "configuration", "description": "Chat Language Model of 
type dev.langchai [...]
   },
   "headers": {
-    "CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The prompt Template.", "constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#PROMPT_TEMPLATE"
 }
+    "CamelLangChain4jChatPromptTemplate": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The prompt Template.", "constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#PROMPT_TEMPLATE"
 },
+    "CamelLangChain4jChatAugmentedData": { "index": 1, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Augmented Data for RAG", 
"constantName": 
"org.apache.camel.component.langchain4j.chat.LangChain4jChat$Headers#AUGMENTED_DATA"
 }
   },
   "properties": {
     "chatId": { "index": 0, "kind": "path", "displayName": "Chat Id", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The id" },
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
 
b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
index aa64d7ec8ad..e791fae0389 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc
@@ -180,3 +180,64 @@ List<ChatMessage> messages = new ArrayList<>();
 
         Exchange message = 
fluentTemplate.to("direct:test").withBody(messages).request(Exchange.class);
 ----
+
+== RAG (Retrieval Augmented Generation)
+Use the RAG feature to enrich exchanges with data retrieved from any type of 
Camel endpoint. The feature is compatible with all LangChain4 Chat operations 
and is ideal for orchestrating the RAG workflow, utilizing the extensive 
library of components and Enterprise Integration Patterns (EIPs) available in 
Apache Camel.
+
+There are two ways for utilizing the RAG feature:
+
+=== Using RAG with Content Enricher and LangChain4jRagAggregatorStrategy
+Enrich the exchange by retrieving a list of strings using any Camel producer. 
The `LangChain4jRagAggregatorStrategy` is specifically designed to augment data 
within LangChain4j chat producers.
+
+Example of usage:
+[source, java]
+----
+// Create an instance of the RAG aggregator strategy
+LangChain4jRagAggregatorStrategy aggregatorStrategy = new 
LangChain4jRagAggregatorStrategy();
+
+from("direct:test")
+     .enrich("direct:rag", aggregatorStrategy)
+     .to("langchain4j-chat:test1?chatOperation=CHAT_SIMPLE_MESSAGE");
+
+  from("direct:rag")
+          .process(exchange -> {
+                List<String> augmentedData = List.of("data 1", "data 2" );
+                exchange.getIn().setBody(augmentedData);
+              });
+----
+
+[NOTE]
+====
+This method leverages a separate Camel route to fetch and process the 
augmented data.
+====
+
+It is possible to enrich the message from multiple sources within the same 
exchange.
+
+Example of usage:
+[source, java]
+----
+// Create an instance of the RAG aggregator strategy
+LangChain4jRagAggregatorStrategy aggregatorStrategy = new 
LangChain4jRagAggregatorStrategy();
+
+from("direct:test")
+     .enrich("direct:rag-from-source-1", aggregatorStrategy)
+     .enrich("direct:rag-from-source-2", aggregatorStrategy)
+     .to("langchain4j-chat:test1?chatOperation=CHAT_SIMPLE_MESSAGE");
+----
+
+=== Using RAG with Header
+Directly add augmented data into the header. This method is particularly 
efficient for straightforward use cases where the augmented data is predefined 
or static.
+You must add augmented data as a List of `dev.langchain4j.rag.content.Content` 
directly inside the header `CamelLangChain4jChatAugmentedData`.
+
+Example of usage:
+[source, java]
+----
+import dev.langchain4j.rag.content.Content;
+
+...
+
+Content augmentedContent = new Content("data test");
+List<Content> contents = List.of(augmentedContent);
+
+String response = template.requestBodyAndHeader("direct:send-multiple", 
messages, LangChain4jChat.Headers.AUGMENTED_DATA , contents, String.class);
+----
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChat.java
 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChat.java
index 7c2a13a5cbb..f08a08d2e76 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChat.java
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChat.java
@@ -28,5 +28,8 @@ public class LangChain4jChat {
     public static class Headers {
         @Metadata(description = "The prompt Template.", javaType = "String")
         public static final String PROMPT_TEMPLATE = 
"CamelLangChain4jChatPromptTemplate";
+
+        @Metadata(description = "Augmented Data for RAG", javaType = "String")
+        public static final String AUGMENTED_DATA = 
"CamelLangChain4jChatAugmentedData";
     }
 }
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
index 455fbe9450d..153eb6f4cf6 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java
@@ -27,10 +27,14 @@ import dev.langchain4j.agent.tool.ToolSpecification;
 import dev.langchain4j.data.message.AiMessage;
 import dev.langchain4j.data.message.ChatMessage;
 import dev.langchain4j.data.message.ToolExecutionResultMessage;
+import dev.langchain4j.data.message.UserMessage;
 import dev.langchain4j.model.chat.ChatLanguageModel;
 import dev.langchain4j.model.input.Prompt;
 import dev.langchain4j.model.input.PromptTemplate;
 import dev.langchain4j.model.output.Response;
+import dev.langchain4j.rag.content.Content;
+import dev.langchain4j.rag.content.injector.ContentInjector;
+import dev.langchain4j.rag.content.injector.DefaultContentInjector;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.NoSuchHeaderException;
@@ -39,6 +43,8 @@ import 
org.apache.camel.component.langchain4j.chat.tool.CamelToolSpecification;
 import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.ObjectHelper;
 
+import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.Headers.AUGMENTED_DATA;
+
 public class LangChain4jChatProducer extends DefaultProducer {
 
     private final LangChain4jChatEndpoint endpoint;
@@ -76,19 +82,19 @@ public class LangChain4jChatProducer extends 
DefaultProducer {
 
         Map<String, Object> variables = (Map<String, Object>) 
exchange.getIn().getMandatoryBody(Map.class);
 
-        var response = sendWithPromptTemplate(promptTemplate, variables);
+        var response = sendWithPromptTemplate(promptTemplate, variables, 
exchange);
 
         populateResponse(response, exchange);
     }
 
     private void processSingleMessage(Exchange exchange) throws 
InvalidPayloadException {
+        // Retrieve the mandatory body from the exchange
         final var message = exchange.getIn().getMandatoryBody();
 
-        if (message instanceof String text) {
-            populateResponse(sendMessage(text), exchange);
-        } else if (message instanceof ChatMessage chatMessage) {
-            populateResponse(sendChatMessage(chatMessage), exchange);
-        }
+        // Use pattern matching with instanceof to streamline type checks and 
assignments
+        ChatMessage userMessage = (message instanceof String) ? new 
UserMessage((String) message) : (ChatMessage) message;
+
+        populateResponse(sendChatMessage(userMessage, exchange), exchange);
 
     }
 
@@ -110,24 +116,36 @@ public class LangChain4jChatProducer extends 
DefaultProducer {
     }
 
     /**
-     * Send one simple message
+     * Send a ChatMessage
      *
-     * @param  message
+     * @param  chatMessage
      * @return
      */
-    public String sendMessage(String message) {
-        return this.chatLanguageModel.generate(message);
+    private String sendChatMessage(ChatMessage chatMessage, Exchange exchange) 
{
+        var augmentedChatMessage = addAugmentedData(chatMessage, exchange);
+
+        Response<AiMessage> response = 
this.chatLanguageModel.generate(augmentedChatMessage);
+        return extractAiResponse(response);
     }
 
     /**
-     * Send a ChatMessage
+     * Augment the message for RAG if the header is specified
      *
-     * @param  chatMessage
-     * @return
+     * @param chatMessage
+     * @param exchange
      */
-    private String sendChatMessage(ChatMessage chatMessage) {
-        Response<AiMessage> response = 
this.chatLanguageModel.generate(chatMessage);
-        return extractAiResponse(response);
+    private ChatMessage addAugmentedData(ChatMessage chatMessage, Exchange 
exchange) {
+        // check if there's any augmented data
+        List<Content> augmentedData = 
exchange.getIn().getHeader(AUGMENTED_DATA, List.class);
+
+        // inject data
+        if (augmentedData != null && augmentedData.size() != 0) {
+            ContentInjector contentInjector = new DefaultContentInjector();
+            // inject with new List of Contents
+            return contentInjector.inject(augmentedData, chatMessage);
+        } else {
+            return chatMessage;
+        }
     }
 
     /**
@@ -140,6 +158,18 @@ public class LangChain4jChatProducer extends 
DefaultProducer {
         LangChain4jChatEndpoint langChain4jChatEndpoint = 
(LangChain4jChatEndpoint) getEndpoint();
 
         Response<AiMessage> response;
+
+        // Check if the last message is a UserMessage and if there's a need to 
augment the message for RAG
+        int size = chatMessages.size();
+        if (size != 0) {
+            int lastIndex = size - 1;
+            ChatMessage lastUserMessage = chatMessages.get(lastIndex);
+            if (lastUserMessage instanceof UserMessage) {
+                chatMessages.set(lastIndex, addAugmentedData(lastUserMessage, 
exchange));
+            }
+
+        }
+
         if 
(CamelToolExecutorCache.getInstance().getTools().containsKey(langChain4jChatEndpoint.getChatId()))
 {
             List<ToolSpecification> toolSpecifications = 
CamelToolExecutorCache.getInstance().getTools()
                     .get(langChain4jChatEndpoint.getChatId()).stream()
@@ -191,10 +221,10 @@ public class LangChain4jChatProducer extends 
DefaultProducer {
         return message == null ? null : message.text();
     }
 
-    public String sendWithPromptTemplate(String promptTemplate, Map<String, 
Object> variables) {
+    public String sendWithPromptTemplate(String promptTemplate, Map<String, 
Object> variables, Exchange exchange) {
         PromptTemplate template = PromptTemplate.from(promptTemplate);
         Prompt prompt = template.apply(variables);
-        return this.sendMessage(prompt.text());
+        return this.sendChatMessage(new UserMessage(prompt.text()), exchange);
     }
 
 }
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/rag/LangChain4jRagAggregatorStrategy.java
 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/rag/LangChain4jRagAggregatorStrategy.java
new file mode 100644
index 00000000000..b046a48996f
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/rag/LangChain4jRagAggregatorStrategy.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.chat.rag;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import dev.langchain4j.rag.content.Content;
+import org.apache.camel.AggregationStrategy;
+import org.apache.camel.Exchange;
+
+import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.Headers.AUGMENTED_DATA;
+
+public class LangChain4jRagAggregatorStrategy implements AggregationStrategy {
+    @Override
+    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
+        // In theory old exchange shouldn't be null
+        if (oldExchange == null) {
+            return newExchange;
+        }
+
+        // check that we got new Augmented Data
+        Optional<List<String>> newAugmentedData = 
Optional.ofNullable(newExchange.getIn().getBody(List.class));
+        if (newAugmentedData.isEmpty()) {
+            return oldExchange;
+        }
+
+        // create a list of contents from the retrieved Strings
+        List<Content> newContents = newAugmentedData.get().stream()
+                .map(Content::new)
+                .collect(Collectors.toList());
+
+        // Get or create the augmented data list from the old exchange
+        List<Content> augmentedData = 
Optional.ofNullable(oldExchange.getIn().getHeader(AUGMENTED_DATA, List.class))
+                .orElse(new ArrayList<Content>());
+        augmentedData.addAll(newContents);
+
+        // add the retrieved data in the body, langchain4j-chat will know it 
has to add inside the body
+        oldExchange.getIn().setHeader(AUGMENTED_DATA, augmentedData);
+
+        return oldExchange;
+    }
+}
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jChatIT.java
 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jChatIT.java
index ead521f9b0f..a48fa8f66e4 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jChatIT.java
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jChatIT.java
@@ -20,27 +20,39 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import dev.langchain4j.data.message.AiMessage;
 import dev.langchain4j.data.message.ChatMessage;
 import dev.langchain4j.data.message.SystemMessage;
 import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.rag.content.Content;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.NoSuchHeaderException;
 import org.apache.camel.builder.RouteBuilder;
+import 
org.apache.camel.component.langchain4j.chat.rag.LangChain4jRagAggregatorStrategy;
 import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
 
+import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.Headers.AUGMENTED_DATA;
+import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.Headers.PROMPT_TEMPLATE;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @DisabledIfSystemProperty(named = "ci.env.name", matches = ".*", 
disabledReason = "Requires too much network resources")
 public class LangChain4jChatIT extends OllamaTestSupport {
 
+    final static String AUGMENTEG_DATA_FOR_RAG
+            = "Sweden's Armand Duplantis set a new world record of 6.25m after 
winning gold in the men's pole vault at Paris 2024 Olympics.";
+    final static String QUESTION_FOR_RAG = "Who got the gold medal in pole 
vault at Paris 2024?";
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         this.context.getRegistry().bind("chatModel", chatLanguageModel);
+        LangChain4jRagAggregatorStrategy aggregatorStrategy = new 
LangChain4jRagAggregatorStrategy();
 
         return new RouteBuilder() {
             public void configure() {
@@ -72,6 +84,25 @@ public class LangChain4jChatIT extends OllamaTestSupport {
                         .end()
                         .to("mock:response");
 
+                from("direct:send-with-rag")
+                        .enrich("direct:add-augmented-data", 
aggregatorStrategy)
+                        .to("direct:send-simple-message");
+
+                from("direct:send-message-prompt-enrich")
+                        .enrich("direct:add-augmented-data", 
aggregatorStrategy)
+                        .to("direct:send-message-prompt");
+
+                from("direct:send-multiple-enrich")
+                        .enrich("direct:add-augmented-data", 
aggregatorStrategy)
+                        .to("direct:send-multiple");
+
+                from("direct:add-augmented-data")
+                        .process(exchange -> {
+                            List<String> augmentedData = List.of(
+                                    AUGMENTEG_DATA_FOR_RAG);
+                            exchange.getIn().setBody(augmentedData);
+                        });
+
             }
         };
     }
@@ -121,7 +152,7 @@ public class LangChain4jChatIT extends OllamaTestSupport {
         variables.put("ingredients", "potato, tomato, feta, olive oil");
 
         String response = 
template.requestBodyAndHeader("direct:send-message-prompt", variables,
-                LangChain4jChat.Headers.PROMPT_TEMPLATE, promptTemplate, 
String.class);
+                PROMPT_TEMPLATE, promptTemplate, String.class);
         mockEndpoint.assertIsSatisfied();
 
         assertTrue(response.contains("potato"));
@@ -153,7 +184,7 @@ public class LangChain4jChatIT extends OllamaTestSupport {
         var promptTemplate = "Create a recipe for a {{dishType}} with the 
following ingredients: {{ingredients}}";
 
         template.sendBodyAndHeader("direct:send-message-prompt", null,
-                LangChain4jChat.Headers.PROMPT_TEMPLATE, promptTemplate);
+                PROMPT_TEMPLATE, promptTemplate);
         mockEndpoint.assertIsSatisfied();
     }
 
@@ -187,4 +218,123 @@ public class LangChain4jChatIT extends OllamaTestSupport {
         mockEndpoint.assertIsSatisfied();
     }
 
+    @Test
+    @Timeout(value = 2, unit = TimeUnit.MINUTES)
+    void testSimpleMessageWithEnrichRAG() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        var response = template.requestBody("direct:send-with-rag", 
QUESTION_FOR_RAG,
+                String.class);
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+        mockEndpoint.assertIsSatisfied();
+    }
+
+    @Test
+    @Timeout(value = 60, unit = TimeUnit.SECONDS)
+    void testSimpleMessageWithHeaderhRAG() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        Content augmentedContent = new Content(AUGMENTEG_DATA_FOR_RAG);
+
+        List<Content> contents = List.of(augmentedContent);
+
+        var response = 
template.requestBodyAndHeader("direct:send-simple-message", QUESTION_FOR_RAG,
+                AUGMENTED_DATA, contents, String.class);
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+        mockEndpoint.assertIsSatisfied();
+    }
+
+    @Test
+    void testSendMessageWithPromptRagEnrich() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        var promptTemplate = " Who got the gold medal in {{field}} at: 
{{competition}}";
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("field", "pole vault");
+        variables.put("competition", "Paris 2024");
+
+        String response = 
template.requestBodyAndHeader("direct:send-message-prompt-enrich", variables,
+                PROMPT_TEMPLATE, promptTemplate, String.class);
+        mockEndpoint.assertIsSatisfied();
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+    }
+
+    @Test
+    void testSendMessageWithPromptRagHeader() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        var promptTemplate = " Who got the gold medal in {{field}} at: 
{{competition}}";
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("field", "pole vault");
+        variables.put("competition", "Paris 2024");
+
+        Content augmentedContent = new Content(AUGMENTEG_DATA_FOR_RAG);
+
+        List<Content> contents = List.of(augmentedContent);
+
+        Map<String, Object> headerValues = new HashMap<>();
+        headerValues.put(PROMPT_TEMPLATE, promptTemplate);
+        headerValues.put(AUGMENTED_DATA, contents);
+
+        String response = 
template.requestBodyAndHeaders("direct:send-message-prompt", variables,
+                headerValues, String.class);
+        mockEndpoint.assertIsSatisfied();
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+    }
+
+    @Test
+    void testSendMultipleMessagesRagEnrich() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        List<ChatMessage> messages = new ArrayList<>();
+        messages.add(new SystemMessage("You are asked to provide names for 
athletes that won medals at Paris 2024 Olympics."));
+        messages.add(new UserMessage("Hello, my name is Karen."));
+        messages.add(new AiMessage("Hello Karen, how can I help you?"));
+        messages.add(new UserMessage(QUESTION_FOR_RAG));
+
+        String response = template.requestBody("direct:send-multiple-enrich", 
messages, String.class);
+        mockEndpoint.assertIsSatisfied();
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+    }
+
+    @Test
+    void testSendMultipleMessagesRagHeader() throws InterruptedException {
+        MockEndpoint mockEndpoint = this.context.getEndpoint("mock:response", 
MockEndpoint.class);
+        mockEndpoint.expectedMessageCount(1);
+
+        List<ChatMessage> messages = new ArrayList<>();
+        messages.add(new SystemMessage("You are asked to provide names for 
athletes that won medals at Paris 2024 Olympics."));
+        messages.add(new UserMessage("Hello, my name is Karen."));
+        messages.add(new AiMessage("Hello Karen, how can I help you?"));
+        messages.add(new UserMessage(QUESTION_FOR_RAG));
+
+        Content augmentedContent = new Content(AUGMENTEG_DATA_FOR_RAG);
+        List<Content> contents = List.of(augmentedContent);
+
+        String response
+                = template.requestBodyAndHeader("direct:send-multiple", 
messages, AUGMENTED_DATA, contents, String.class);
+        mockEndpoint.assertIsSatisfied();
+
+        // this test could change if using an LLM updated after results of 
Olympics 2024
+        assertTrue(response.toLowerCase().contains("armand duplantis"));
+
+    }
+
 }
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jRagAggregatorTest.java
 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jRagAggregatorTest.java
new file mode 100644
index 00000000000..76e22716a67
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jRagAggregatorTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.chat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.langchain4j.rag.content.Content;
+import org.apache.camel.Exchange;
+import 
org.apache.camel.component.langchain4j.chat.rag.LangChain4jRagAggregatorStrategy;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static 
org.apache.camel.component.langchain4j.chat.LangChain4jChat.Headers.AUGMENTED_DATA;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class LangChain4jRagAggregatorTest {
+
+    private LangChain4jRagAggregatorStrategy aggregator;
+    private Exchange oldExchange;
+    private Exchange newExchange;
+
+    @BeforeEach
+    void setUp() {
+        aggregator = new LangChain4jRagAggregatorStrategy();
+        oldExchange = new DefaultExchange(new DefaultCamelContext());
+        newExchange = new DefaultExchange(new DefaultCamelContext());
+    }
+
+    @Test
+    void testAggregateWithNoNewData() {
+        Exchange result = aggregator.aggregate(oldExchange, newExchange);
+        assertEquals(oldExchange, result);
+    }
+
+    @Test
+    void testAggregateWithNewData() {
+
+        // setting a prompt in the old Exchange
+        oldExchange.getIn().setBody("Prompt Test");
+
+        // setting augmented data in the new Exchange
+        List<String> newData = List.of("data1", "data2");
+        newExchange.getIn().setBody(newData);
+
+        Exchange result = aggregator.aggregate(oldExchange, newExchange);
+
+        List<Content> contents = result.getIn().getHeader(AUGMENTED_DATA, 
List.class);
+        String prompt = result.getIn().getBody(String.class);
+
+        assertNotNull("The body should contain the old body", prompt);
+        assertEquals("Prompt Test", prompt);
+
+        assertNotNull("The old exchange should contain now the enriched data 
in type of List of Content", contents);
+        assertEquals(2, contents.size());
+
+        assertTrue("The first content item should match one of the new data 
entries.",
+                newData.contains(contents.get(0).textSegment().text()));
+        assertTrue("The second content item should match one of the new data 
entries.",
+                newData.contains(contents.get(1).textSegment().text()));
+    }
+
+    @Test
+    void testAggregateWithExistingAndNewData() {
+
+        // setting a prompt in the old Exchange
+        oldExchange.getIn().setBody("Prompt Test");
+
+        // setting a content in the old exchange
+        Content oldContent = new Content("Old data");
+        List<Content> contents = new ArrayList<>();
+        contents.add(oldContent);
+        oldExchange.getIn().setHeader(AUGMENTED_DATA, contents);
+
+        // setting augmented data in the new Exchange
+        List<String> newData = List.of("data1", "data2");
+        newExchange.getIn().setBody(newData);
+
+        Exchange result = aggregator.aggregate(oldExchange, newExchange);
+
+        contents = result.getIn().getHeader(AUGMENTED_DATA, List.class);
+        String prompt = result.getIn().getBody(String.class);
+
+        assertNotNull("The body should contain the old body", prompt);
+        assertEquals("Prompt Test", prompt);
+
+        assertNotNull("The old exchange should contain now the enriched data 
in type of List of Content", contents);
+        assertEquals(3, contents.size());
+
+        assertEquals("The first content item should match the old content", 
"Old data", contents.get(0).textSegment().text());
+        assertTrue("The second content item should match one of the new data 
entries.",
+                newData.contains(contents.get(1).textSegment().text()));
+        assertTrue("The third content item should match one of the new data 
entries.",
+                newData.contains(contents.get(2).textSegment().text()));
+    }
+
+    @Test
+    void testOldExchangeIsNull() {
+        newExchange.getMessage().setHeader(AUGMENTED_DATA, "Additional data");
+        Exchange result = aggregator.aggregate(null, newExchange);
+        assertEquals(newExchange, result);
+    }
+}
diff --git 
a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/OllamaTestSupport.java
 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/OllamaTestSupport.java
index f19e44c5df2..f23197a8ec8 100644
--- 
a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/OllamaTestSupport.java
+++ 
b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/OllamaTestSupport.java
@@ -44,7 +44,7 @@ public class OllamaTestSupport extends CamelTestSupport {
                 .baseUrl(OLLAMA.getBaseUrl())
                 .modelName(OLLAMA.getModel())
                 .temperature(0.3)
-                .timeout(ofSeconds(3000))
+                .timeout(ofSeconds(60))
                 .build();
     }
 }
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
index cfab07c4891..db74cdad193 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jChatEndpointBuilderFactory.java
@@ -252,6 +252,18 @@ public interface LangChain4jChatEndpointBuilderFactory {
         public String langChain4jChatPromptTemplate() {
             return "CamelLangChain4jChatPromptTemplate";
         }
+        /**
+         * Augmented Data for RAG.
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code LangChain4jChatAugmentedData}.
+         */
+        public String langChain4jChatAugmentedData() {
+            return "CamelLangChain4jChatAugmentedData";
+        }
     }
     static LangChain4jChatEndpointBuilder endpointBuilder(String 
componentName, String path) {
         class LangChain4jChatEndpointBuilderImpl extends 
AbstractEndpointBuilder implements LangChain4jChatEndpointBuilder, 
AdvancedLangChain4jChatEndpointBuilder {


Reply via email to