Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2a989cb
langchain4j tools
Milozmih Aug 4, 2026
d6a79e8
implementation of langchain4j websearch + tools
Milozmih Aug 20, 2026
0af5788
CamelLangChain4jAgentSystemMessage implemented
Milozmih Aug 20, 2026
63b5585
Revert "CamelLangChain4jAgentSystemMessage implemented"
Milozmih Aug 21, 2026
9eb6154
agentic websearch
Milozmih Aug 26, 2026
74827c4
naming change
Milozmih Aug 31, 2026
e9fdabd
update graphql component
skin27 Jul 27, 2026
71e45f1
moved ai connections to its own package
skin27 Jul 31, 2026
e1a85dc
added x-descriptors on openai-action kamelet
brunovg Jul 28, 2026
5734800
apply final corrections and improvements
brunovg Jul 28, 2026
689fe1f
soap action kamelet - smart option with true by default
brunovg Jul 30, 2026
72c8c6f
Update if action and router
skin27 Aug 2, 2026
e4216de
Add mustache kamelet
skin27 Aug 4, 2026
842aeaf
update ErrorRegistry
skin27 Aug 4, 2026
aa69ef7
decrypt accessToken on initializeOAuth2EmailAuthenticator
brunovg Aug 5, 2026
f078a69
use authentication on elasticsearch requests when username and passwo…
brunovg Aug 5, 2026
4077e24
added basic authentication on StartupManager if any of the dependency…
brunovg Aug 5, 2026
77b0b79
do not use cleanRaw to fetch encrypted accessToken
brunovg Aug 6, 2026
93539d5
update doWhile kamelet
skin27 Aug 5, 2026
5900f12
add loop action and dowhile action
skin27 Aug 5, 2026
b90366b
update loop/dowhile kamelets
skin27 Aug 6, 2026
8f913d6
update dependencies
skin27 Aug 10, 2026
b107d75
Add metadata to groprepository and add custom tenantrepository
skin27 Aug 7, 2026
96eb5a4
add tenant to errorRegistry
skin27 Aug 10, 2026
89119f6
update dependencies
skin27 Aug 11, 2026
f70411c
update Spring AI connection
skin27 Aug 11, 2026
7a0b7e3
Add privateKeyUri to sftp enrich kamelet
skin27 Aug 12, 2026
59eafac
Add out to the ftpenrich and ftpsenrich kamelets
skin27 Aug 12, 2026
b4621ac
workaround for sftp enrich
skin27 Aug 14, 2026
435c260
update dependencies / SSL cleanup
skin27 Aug 17, 2026
6c19160
update flowlinks/queues
skin27 Aug 18, 2026
5d7ed96
Add reset flow statsistics endpoints
skin27 Aug 19, 2026
dd934e9
Port fix to set PreviousFlowId/PreviousFlowVersion
skin27 Aug 28, 2026
52d479b
rename kamelet setoauth2token to oauth2token
skin27 Aug 28, 2026
763905f
langchain4j tools
Milozmih Aug 4, 2026
d8e3b95
implementation of langchain4j websearch + tools
Milozmih Aug 20, 2026
3c3c005
CamelLangChain4jAgentSystemMessage implemented
Milozmih Aug 20, 2026
8fc146c
Revert "CamelLangChain4jAgentSystemMessage implemented"
Milozmih Aug 21, 2026
f032e4d
added what has been lost when resolving conflicts
Milozmih Aug 31, 2026
df43190
dependency errors fixed
Milozmih Sep 1, 2026
8c7b198
dependency fixes
Milozmih Sep 1, 2026
260f9ca
Merge branch 'next' into 667-langchain4j-tools
Milozmih Sep 1, 2026
aec17a8
override with latest kamelet content
Milozmih Sep 1, 2026
7d27a07
change versions
Milozmih Sep 1, 2026
5ca549c
added new sink addition
Milozmih Sep 1, 2026
f4adfad
small order fix
Milozmih Sep 1, 2026
a15c6a3
working with springai, but Jackson 3 bug with google GenAI
Milozmih Sep 2, 2026
a3544bc
git feedback applied
Milozmih Sep 3, 2026
23c8012
Merge branch 'next' into 667-langchain4j-tools
Milozmih Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion camelBase/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,23 @@
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-log</artifactId>
<artifactId>camel-langchain4j-agent</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ai-tool</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-langchain4j-tools</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-langchain4j-web-search</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-mustache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
Expand Down
6 changes: 5 additions & 1 deletion commonBase/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,11 @@
<artifactId>langchain4j-google-ai-gemini</artifactId>
<version>${langchain4j.version}</version>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-web-search-engine-tavily</artifactId>
<version>${langchain4j-tavily.version}</version>
</dependency>
<!-- spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.assimbly.dil.blocks.connections.ai.LangChain4jAgentConnection;
import org.assimbly.dil.blocks.connections.ai.LangChain4jConnection;
import org.assimbly.dil.blocks.connections.ai.LangChain4jWebSearchConnection;
import org.assimbly.dil.blocks.connections.ai.SpringAiConnection;
import org.assimbly.dil.blocks.connections.auth.BasicAuthentication;
import org.assimbly.dil.blocks.connections.auth.MutualSSL;
Expand Down Expand Up @@ -73,10 +74,10 @@ private void startConnection() throws Exception{
case "rabbitmq", "spring-rabbitmq" ->
new RabbitMQConnection(context, decryptedProperties, connectionId, "spring-rabbitmq").start();

case "springai" ->
case "springaichat" ->
new SpringAiConnection(context, decryptedProperties, connectionId).start();

case "langchain4j" ->
case "langchain4jchat" ->
new LangChain4jConnection(context, decryptedProperties, connectionId).start();

case "ibmq" ->
Expand All @@ -94,9 +95,12 @@ private void startConnection() throws Exception{
case "imaps" ->
log.debug("Imaps connection will be configured on the component");

case "langchain4j-agent" ->
case "langchain4jagent" ->
new LangChain4jAgentConnection(context, decryptedProperties, connectionId).start();

case "langchain4j-web-search" ->
new LangChain4jWebSearchConnection(context, decryptedProperties, connectionId).start();

default -> throw new IllegalArgumentException("Connection parameters for connection " + connectionType + " are not implemented");

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
import dev.langchain4j.web.search.WebSearchEngine;
import dev.langchain4j.web.search.WebSearchTool;
import dev.langchain4j.web.search.tavily.TavilyWebSearchEngine;
import java.time.Duration;
import java.util.List;

public class LangChain4jAgentConnection {

Expand All @@ -25,6 +29,8 @@ public class LangChain4jAgentConnection {
private String apiKey;
private String modelName;
private String timeout;
private String webSearchApiKey;
private String maxMessages;

public LangChain4jAgentConnection(CamelContext context, EncryptableProperties properties, String connectionId) {
this.context = context;
Expand All @@ -47,6 +53,11 @@ private void setFields() {
apiKey = properties.getProperty("connection." + connectionId + ".apikey");
modelName = properties.getProperty("connection." + connectionId + ".modelname");
timeout = properties.getProperty("connection." + connectionId + ".timeout");
webSearchApiKey = properties.getProperty("connection." + connectionId + ".websearchapikey");
if (webSearchApiKey == null || webSearchApiKey.isEmpty()) {
webSearchApiKey = properties.getProperty("connection." + connectionId + ".tavilyapikey");
}
maxMessages = properties.getProperty("connection." + connectionId + ".maxmessages");
}

private boolean checkConnection() {
Expand All @@ -67,7 +78,7 @@ private void setConnection() {
apiKey != null ? apiKey.length() : 0,
apiKey != null && apiKey.length() >= 5 ? apiKey.substring(0, 5) : "N/A");

String resolvedModel = (modelName != null && !modelName.isEmpty()) ? modelName : "gemini-2.5-flash";
String resolvedModel = (modelName != null && !modelName.isEmpty()) ? modelName : "gemini-3.6-flash";
long resolvedTimeout = 10;
if (timeout != null && !timeout.isEmpty()) {
try {
Expand All @@ -77,23 +88,58 @@ private void setConnection() {
}
}

int resolvedMaxMessages = 100;
if (maxMessages != null && !maxMessages.isEmpty()) {
try {
resolvedMaxMessages = Integer.parseInt(maxMessages);
} catch (NumberFormatException e) {
log.warn("Invalid maxMessages value '{}', using default 100", maxMessages);
}
}

ChatModel chatModel = GoogleAiGeminiChatModel.builder()
.apiKey(apiKey)
.modelName(resolvedModel)
.timeout(Duration.ofSeconds(resolvedTimeout))
.returnThinking(true)
.sendThinking(true)
.build();

final int finalMaxMessages = resolvedMaxMessages;
InMemoryChatMemoryStore chatMemoryStore = new InMemoryChatMemoryStore();
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(100)
.chatMemoryStore(chatMemoryStore)
.build();
java.util.Map<Object, dev.langchain4j.memory.ChatMemory> memories = java.util.Collections.synchronizedMap(
new java.util.LinkedHashMap<Object, dev.langchain4j.memory.ChatMemory>(100, 0.75f, true) {
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<Object, dev.langchain4j.memory.ChatMemory> eldest) {
return size() > 1000;
}
}
);
ChatMemoryProvider chatMemoryProvider = memoryId -> {
synchronized (memories) {
return memories.computeIfAbsent(memoryId, id ->
MessageWindowChatMemory.builder()
.id(id)
.maxMessages(finalMaxMessages)
.chatMemoryStore(chatMemoryStore)
.build()
);
}
};

AgentConfiguration config = new AgentConfiguration()
.withChatModel(chatModel)
.withChatMemoryProvider(chatMemoryProvider);

if (webSearchApiKey != null && !webSearchApiKey.isEmpty()) {
log.info("Attaching Tavily WebSearchTool to LangChain4j Agent with connection id={}", connectionId);
WebSearchEngine webSearchEngine = TavilyWebSearchEngine.builder()
.apiKey(webSearchApiKey)
.build();
WebSearchTool webSearchTool = WebSearchTool.from(webSearchEngine);
config.withCustomTools(List.of(webSearchTool));
}

Agent agent = new AgentWithMemory(config);

context.getRegistry().bind(connectionId, agent);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.assimbly.dil.blocks.connections.ai;

import org.apache.camel.CamelContext;
import org.jasypt.properties.EncryptableProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dev.langchain4j.web.search.WebSearchEngine;
import dev.langchain4j.web.search.tavily.TavilyWebSearchEngine;

public class LangChain4jWebSearchConnection {

protected Logger log = LoggerFactory.getLogger(getClass());

private final CamelContext context;
private final EncryptableProperties properties;
private final String connectionId;

private String apiKey;

public LangChain4jWebSearchConnection(CamelContext context, EncryptableProperties properties, String connectionId) {
this.context = context;
this.properties = properties;
this.connectionId = connectionId;
}

public void start() {
setFields();

if (checkConnection()) {
log.info("Creating new LangChain4j Web Search connection with id={}", connectionId);
setConnection();
} else {
log.info("Reuse LangChain4j Web Search connection with id={}", connectionId);
}
}

private void setFields() {
apiKey = properties.getProperty("connection." + connectionId + ".apikey");
}

private boolean checkConnection() {
Object isRegistered = context.getRegistry().lookupByName(connectionId);
if (isRegistered != null) {
return false;
}

if (apiKey == null || apiKey.isEmpty()) {
throw new IllegalArgumentException("LangChain4j Web Search connection parameters are invalid. apikey is required");
}

return true;
}

private void setConnection() {
log.info("Setting up Tavily WebSearchEngine connection with id={}", connectionId);

WebSearchEngine webSearchEngine = TavilyWebSearchEngine.builder()
.apiKey(apiKey)
.build();

context.getRegistry().bind(connectionId, webSearchEngine);
log.info("Successfully bound WebSearchEngine bean with id={} to the Camel registry", connectionId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ private void createTemplateId(String uri,String type){
}

if(templateExists(templateName)){
templateId = templateName;
templateId = resolveTemplateName(templateName);
}else if(uri.startsWith("block")){
String componentName = path;
componentName = componentName.toLowerCase();
Expand All @@ -474,7 +474,30 @@ private void createTemplateId(String uri,String type){

private boolean templateExists(String templateName) {
String fullTemplateName = templateName + ".kamelet.yaml";
return CustomKameletCatalog.getNames().contains(fullTemplateName);
if (CustomKameletCatalog.getNames().contains(fullTemplateName)) {
return true;
}
String targetStripped = fullTemplateName.replace("-", "");
for (String name : CustomKameletCatalog.getNames()) {
if (name.replace("-", "").equalsIgnoreCase(targetStripped)) {
return true;
}
}
return false;
}

private String resolveTemplateName(String templateName) {
String fullTemplateName = templateName + ".kamelet.yaml";
if (CustomKameletCatalog.getNames().contains(fullTemplateName)) {
return templateName;
}
String targetStripped = fullTemplateName.replace("-", "");
for (String name : CustomKameletCatalog.getNames()) {
if (name.replace("-", "").equalsIgnoreCase(targetStripped)) {
return name.substring(0, name.indexOf(".kamelet.yaml"));
}
}
return templateName;
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: "camel.apache.org/v1"
kind: "Kamelet"
metadata:
name: "langchain4j-tool-weather-source"
labels:
camel.apache.org/kamelet.type: "source"
spec:
definition:
title: "LangChain4j Weather Tool"
description: "Returns a placeholder weather forecast for a given city."
type: "object"
properties:
city:
title: "City"
description: "Name of the city to get weather for"
type: "string"
default: ""
dependencies:
- "camel:ai-tool"
template:
route:
from:
uri: "ai-tool:weather?description=Returns+a+placeholder+weather+forecast+for+a+given+city&tags=weather"
steps:
- setBody:
simple: "Weather for ${header.city} is sunny with 25°C temperature."
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
apiVersion: "camel.apache.org/v1"
kind: "Kamelet"
metadata:
name: "langchain4j-web-search-action"
labels:
camel.apache.org/kamelet.type: "action"

spec:
definition:
title: "langchain4j-web-search action"
description: "Web Search component for LangChain4j"
type: "object"
properties:
in:
title: "Source Endpoint"
type: "string"
default: "kamelet:source"
out:
title: "Sink Endpoint"
type: "string"
default: "kamelet:sink"
sink:
title: "Sink"
type: "boolean"
default: false
routeId:
title: "Route ID"
type: "string"
routeConfigurationId:
title: "Route Configuration ID"
type: "string"
default: "0"
searchId:
title: "Search ID"
type: "string"
default: "search"
webSearchEngine:
title: "Web Search Engine Bean Name"
description: "Name of the WebSearchEngine bean in the Camel registry (e.g. #webSearchEngine)"
type: "string"
default: "#webSearchEngine"
maxResults:
title: "Max Results"
description: "Maximum number of search results to return"
type: "integer"
default: 1
resultType:
title: "Result Type"
description: "Result type (CONTENT, SNIPPET, or LANGCHAIN4J_WEB_SEARCH_ORGANIC_RESULT)"
type: "string"
default: "SNIPPET"

dependencies:
- "camel:kamelet"
- "camel:langchain4j-web-search"

template:
route:
routeConfigurationId: "{{routeConfigurationId}}"
from:
uri: "{{in}}"
steps:
- step:
id: "{{routeId}}"
steps:
- convertBodyTo:
type: String
- to:
uri: "langchain4j-web-search:{{searchId}}?webSearchEngine={{webSearchEngine}}&maxResults={{maxResults}}&resultType={{resultType}}"
- to:
disabled: "{{sink}}"
uri: "{{out}}"

Loading