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 2885c7de6b4 CAMEL-22475: Camel LangChain4j Agent: custom tools feature
2885c7de6b4 is described below
commit 2885c7de6b40cd2445f8a553b13017caf7d781f9
Author: Zineb Bendhiba <[email protected]>
AuthorDate: Wed Oct 1 09:54:43 2025 +0200
CAMEL-22475: Camel LangChain4j Agent: custom tools feature
---
.../langchain4j/agent/api/AgentConfiguration.java | 21 +++
.../langchain4j/agent/api/AgentWithMemory.java | 10 +-
.../langchain4j/agent/api/AgentWithoutMemory.java | 12 +-
.../src/main/docs/langchain4j-agent-component.adoc | 128 ++++++++++++++++-
.../integration/LangChain4jAgentCustomToolsIT.java | 150 ++++++++++++++++++++
.../integration/LangChain4jAgentMixedToolsIT.java | 156 +++++++++++++++++++++
.../langchain4j/agent/pojos/CalculatorTool.java | 41 ++++++
.../langchain4j/agent/pojos/StringTool.java | 36 +++++
.../langchain4j/agent/pojos/WeatherTool.java | 39 ++++++
9 files changed, 587 insertions(+), 6 deletions(-)
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
index fcdeaf0f8f8..c0de0e01fe1 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
@@ -57,6 +57,7 @@ public class AgentConfiguration {
private RetrievalAugmentor retrievalAugmentor;
private List<Class<?>> inputGuardrailClasses;
private List<Class<?>> outputGuardrailClasses;
+ private List<Object> customTools; // Custom LangChain4j tools
/**
* Gets the configured chat model.
@@ -265,6 +266,26 @@ public class AgentConfiguration {
.collect(java.util.stream.Collectors.toList());
}
+ /**
+ * Gets the configured custom tools.
+ *
+ * @return the custom tools list, or {@code null} if not configured
+ */
+ public List<Object> getCustomTools() {
+ return customTools;
+ }
+
+ /**
+ * Sets the custom tools for this agent configuration.
+ *
+ * @param customTools the list of tool instances with @Tool methods
+ * @return this configuration instance for method chaining
+ */
+ public AgentConfiguration withCustomTools(List<Object> customTools) {
+ this.customTools = customTools;
+ return this;
+ }
+
/**
* Loads a guardrail class by its fully qualified name using reflection.
*
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
index ba1ad3f6211..cf0bb64e7b6 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java
@@ -39,7 +39,7 @@ public class AgentWithMemory implements Agent {
}
@Override
- public String chat(AiAgentBody aiAgentBody, ToolProvider toolProvider) {
+ public String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider) {
AiAgentWithMemoryService agentService =
createAiAgentService(toolProvider);
return aiAgentBody.getSystemMessage() != null
@@ -48,7 +48,8 @@ public class AgentWithMemory implements Agent {
}
/**
- * Create AI service with a single universal tool that handles multiple
Camel routes and Memory Provider
+ * Create AI service with a single universal tool that handles multiple
Camel routes, Memory Provider, and
+ * additional tools
*/
private AiAgentWithMemoryService createAiAgentService(ToolProvider
toolProvider) {
var builder = AiServices.builder(AiAgentWithMemoryService.class)
@@ -60,6 +61,11 @@ public class AgentWithMemory implements Agent {
builder.toolProvider(toolProvider);
}
+ // Additional custom LangChain4j Tool Instances (objects with @Tool
methods)
+ if (configuration.getCustomTools() != null &&
!configuration.getCustomTools().isEmpty()) {
+ builder.tools(configuration.getCustomTools());
+ }
+
// RAG
if (configuration.getRetrievalAugmentor() != null) {
builder.retrievalAugmentor(configuration.getRetrievalAugmentor());
diff --git
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
index dca18d96d98..b8ca88abee2 100644
---
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
+++
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java
@@ -36,7 +36,7 @@ public class AgentWithoutMemory implements Agent {
}
@Override
- public String chat(AiAgentBody aiAgentBody, ToolProvider toolProvider) {
+ public String chat(AiAgentBody<?> aiAgentBody, ToolProvider toolProvider) {
AiAgentWithoutMemoryService agentService =
createAiAgentService(toolProvider);
return aiAgentBody.getSystemMessage() != null
@@ -45,9 +45,10 @@ public class AgentWithoutMemory implements Agent {
}
/**
- * Create AI service with a single universal tool that handles multiple
Camel routes
+ * Create AI service with a single universal tool that handles multiple
Camel routes and additional tools
*/
- private AiAgentWithoutMemoryService createAiAgentService(ToolProvider
toolProvider) {
+ private AiAgentWithoutMemoryService createAiAgentService(
+ ToolProvider toolProvider) {
var builder = AiServices.builder(AiAgentWithoutMemoryService.class)
.chatModel(configuration.getChatModel());
@@ -56,6 +57,11 @@ public class AgentWithoutMemory implements Agent {
builder.toolProvider(toolProvider);
}
+ // Additional custom LangChain4j Tool Instances (objects with @Tool
methods)
+ if (configuration.getCustomTools() != null &&
!configuration.getCustomTools().isEmpty()) {
+ builder.tools(configuration.getCustomTools());
+ }
+
// RAG
if (configuration.getRetrievalAugmentor() != null) {
builder.retrievalAugmentor(configuration.getRetrievalAugmentor());
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
index 58438c12f7e..668341d8a7a 100644
---
a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
+++
b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc
@@ -35,7 +35,7 @@ The component has been simplified to use only two main
options:
* **agent**: Reference to an `Agent` implementation registered in the Camel
registry
* **tags**: Tags for discovering and calling Camel route tools (optional)
-All other configuration (chat models, memory, RAG, guardrails) is now handled
through the `AgentConfiguration` when creating agents.
+All other configuration (chat models, memory, RAG, guardrails, custom tools)
is now handled through the `AgentConfiguration` when creating agents.
== URI format
@@ -257,6 +257,132 @@ String response =
template.requestBodyAndHeader("direct:chat",
There's no need to add Camel LangChain4j Tools component as a dependency when
using the tools with LangChain4j Agent component.
====
+=== Custom LangChain4j Tools
+
+You can also add custom LangChain4j tools using the `@Tool` annotation. These
tools are passed directly to the agent via the `customTools` configuration
parameter.
+
+==== Creating Custom LangChain4j Tools
+
+Create a class with methods annotated with `@Tool`:
+
+[source,java]
+----
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+
+public class CalculatorTool {
+
+ @Tool("Adds two numbers")
+ public int add(@P("First number") int a, @P("Second number") int b) {
+ return a + b;
+ }
+
+ @Tool("Multiplies two numbers")
+ public int multiply(@P("First number") int a, @P("Second number") int b) {
+ return a * b;
+ }
+
+ @Tool("Gets the square root of a number")
+ public double sqrt(@P("Number") double x) {
+ return Math.sqrt(x);
+ }
+}
+----
+
+==== Using Custom LangChain4j Tools with Agent
+
+Pass your custom tool instances to the agent configuration:
+
+[source,java]
+----
+// Create tool instances
+CalculatorTool calculator = new CalculatorTool();
+WeatherTool weather = new WeatherTool();
+
+// Create agent configuration with custom tools
+AgentConfiguration config = new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withCustomTools(Arrays.asList(calculator, weather));
+
+// Create agent
+Agent agent = new AgentWithoutMemory(config);
+
+// Register agent in Camel context
+context.getRegistry().bind("customToolsAgent", agent);
+----
+
+==== Route Configuration
+
+Use the agent with custom tools in your routes:
+
+[source,java]
+----
+from("direct:chat")
+ .to("langchain4j-agent:assistant?agent=#customToolsAgent")
+ .to("mock:agent-response");
+----
+
+==== Usage Example
+
+[source,java]
+----
+String response = template.requestBody("direct:chat",
+ "Calculate 10 * 5 and tell me the weather in Paris", String.class);
+----
+
+[NOTE]
+====
+Custom LangChain4j tools are executed directly by the LangChain4j framework.
No additional configuration or tool Executor is needed for tool execution.
+====
+
+=== Mixed Tools (Camel Routes + Custom LangChain4j Tools)
+
+You can combine both Camel route tools (via `tags`) and custom LangChain4j
tools (via `customTools`) in the same agent:
+
+[source,java]
+----
+// Define Camel route tools
+from("langchain4j-tools:weatherService?tags=weather&description=Get current
weather information¶meter.location=string")
+ .setBody(constant("{\"weather\": \"sunny\", \"location\": \"Current
Location\"}"));
+
+// Create custom tool instances
+CalculatorTool calculator = new CalculatorTool();
+StringTool stringTool = new StringTool();
+
+// Create agent configuration with both types of tools
+AgentConfiguration config = new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withCustomTools(Arrays.asList(calculator, stringTool));
+
+// Create agent
+Agent agent = new AgentWithMemory(config);
+
+// Register agent in Camel context
+context.getRegistry().bind("mixedToolsAgent", agent);
+----
+
+==== Route Configuration with Mixed Tools
+
+[source,java]
+----
+from("direct:chat")
+ .to("langchain4j-agent:assistant?agent=#mixedToolsAgent&tags=weather")
+ .to("mock:agent-response");
+----
+
+==== Usage Example
+
+[source,java]
+----
+String response = template.requestBody("direct:chat",
+ "Calculate 10 * 5 and tell me the weather in London", String.class);
+----
+
+[NOTE]
+====
+When using mixed tools, Camel route tools are discovered dynamically via the
`tags` parameter, while custom LangChain4j tools are provided statically via
the `customTools` configuration.
+====
+
=== RAG Integration
RAG (Retrieval-Augmented Generation) is supported by configuring a
`RetrievalAugmentor` in the `AgentConfiguration`. Create an agent with RAG
capabilities:
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentCustomToolsIT.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentCustomToolsIT.java
new file mode 100644
index 00000000000..5b085ad3ce7
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentCustomToolsIT.java
@@ -0,0 +1,150 @@
+/*
+ * 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.agent.integration;
+
+import java.util.Arrays;
+import java.util.List;
+
+import dev.langchain4j.model.chat.ChatModel;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.langchain4j.agent.api.Agent;
+import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
+import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory;
+import org.apache.camel.component.langchain4j.agent.pojos.CalculatorTool;
+import org.apache.camel.component.langchain4j.agent.pojos.StringTool;
+import org.apache.camel.component.langchain4j.agent.pojos.WeatherTool;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIf;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Integration test for LangChain4j Agent component with custom tools only (no
Camel route tools).
+ */
+@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings")
+public class LangChain4jAgentCustomToolsIT extends CamelTestSupport {
+
+ private static final String CALCULATION_RESULT = "8";
+ private static final String WEATHER_INFO = "sunny";
+ private static final String WEATHER_TEMP = "22";
+
+ protected ChatModel chatModel;
+
+ @Override
+ protected void setupResources() throws Exception {
+ super.setupResources();
+ chatModel = ModelHelper.loadFromEnv();
+ }
+
+ @Test
+ void testAgentWithAdditionalToolsOnly() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:chat", "What is 5 +
3?", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains(CALCULATION_RESULT) ||
response.contains("eight"),
+ "Response should contain the calculation result from the
calculator tool");
+ }
+
+ @Test
+ void testMultipleToolInstances() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response
+ = template.requestBody("direct:chat", "Calculate 10 * 5 and
tell me the weather in Paris", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains("50") || response.contains("fifty"),
+ "Response should contain the multiplication result");
+ // Weather tool might not be used by the AI, so we make this assertion
more flexible
+ assertTrue(
+ response.toLowerCase().contains(WEATHER_INFO) ||
response.toLowerCase().contains("weather")
+ || response.toLowerCase().contains("paris"),
+ "Response should contain weather information or reference to
weather/Paris");
+ }
+
+ @Test
+ void testWeatherTool() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:chat",
+ "Call the getWeather function for Paris", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+
+ boolean weatherToolUsed = response.contains("Weather in Paris: " +
WEATHER_INFO + ", " + WEATHER_TEMP + "°C") ||
+ response.toLowerCase().contains("sunny") ||
+ response.toLowerCase().contains("22");
+
+ assertTrue(weatherToolUsed,
+ "Response should contain weather information from the weather
tool. Response was: " + response);
+ }
+
+ @Test
+ void testStringManipulationTool() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:chat", "Convert 'hello
world' to uppercase", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains("HELLO WORLD"),
+ "Response should contain the uppercase conversion result");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ // Create LangChain4j tool instances
+ CalculatorTool calculator = new CalculatorTool();
+ WeatherTool weather = new WeatherTool();
+ StringTool stringTool = new StringTool();
+
+ List<Object> customTools = Arrays.asList(calculator, weather,
stringTool);
+
+ // Create agent configuration with custom tools
+ AgentConfiguration config = new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withCustomTools(customTools);
+
+ // Create agent
+ Agent agent = new AgentWithoutMemory(config);
+
+ // Register agent in Camel context
+ this.context.getRegistry().bind("additionalToolsAgent", agent);
+
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // Route with custom tools only
+ from("direct:chat")
+
.to("langchain4j-agent:assistant?agent=#additionalToolsAgent")
+ .to("mock:agent-response");
+ }
+ };
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentMixedToolsIT.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentMixedToolsIT.java
new file mode 100644
index 00000000000..abe135190bc
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentMixedToolsIT.java
@@ -0,0 +1,156 @@
+/*
+ * 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.agent.integration;
+
+import java.util.Arrays;
+import java.util.List;
+
+import dev.langchain4j.model.chat.ChatModel;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.langchain4j.agent.api.Agent;
+import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
+import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory;
+import org.apache.camel.component.langchain4j.agent.pojos.CalculatorTool;
+import org.apache.camel.component.langchain4j.agent.pojos.StringTool;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIf;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Integration test for LangChain4j Agent component mixing Camel route tools
(tags) and custom LangChain4j tools.
+ */
+@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings")
+public class LangChain4jAgentMixedToolsIT extends CamelTestSupport {
+
+ private static final String USER_DATABASE = """
+ {"id": "123", "name": "John Smith", "membership": "Gold",
"rentals": 15, "preferredVehicle": "SUV"}
+ """;
+
+ private static final String USER_DB_NAME = "John Smith";
+ private static final String WEATHER_INFO = "sunny";
+ private static final String CALCULATION_RESULT = "10";
+
+ protected ChatModel chatModel;
+
+ @Override
+ protected void setupResources() throws Exception {
+ super.setupResources();
+ chatModel = ModelHelper.loadFromEnv();
+ }
+
+ @Test
+ void testAgentWithMixedTools() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:mixedTools", "Calculate
7 + 3", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains(CALCULATION_RESULT) ||
response.contains("ten"),
+ "Response should contain the calculation result from the
additional calculator tool");
+ }
+
+ @Test
+ void testAgentWithMultipleTagsAndAdditionalTools() throws
InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:mixedTools",
+ "Calculate 15 * 4 and convert 'hello' to uppercase",
+ String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains("60") || response.contains("sixty"),
+ "Response should contain the multiplication result from
additional tools");
+ assertTrue(response.contains("HELLO"),
+ "Response should contain the uppercase conversion result from
additional tools");
+ }
+
+ @Test
+ void testAgentWithCamelAndAdditionalTools() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:mixedTools",
+ "What is the name of user ID 123 and calculate 5 * 6?",
+ String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.contains(USER_DB_NAME),
+ "Response should contain the user name from the Camel route
tool");
+ assertTrue(response.contains("30") || response.contains("thirty"),
+ "Response should contain the calculation result from the
additional calculator tool");
+ }
+
+ @Test
+ void testAgentWithOnlyCamelRouteTools() throws InterruptedException {
+ MockEndpoint mockEndpoint =
this.context.getEndpoint("mock:agent-response", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+
+ String response = template.requestBody("direct:mixedTools", "What's
the weather in New York?", String.class);
+
+ mockEndpoint.assertIsSatisfied();
+ assertNotNull(response, "AI response should not be null");
+ assertTrue(response.toLowerCase().contains(WEATHER_INFO),
+ "Response should contain weather information from the Camel
route tool");
+
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ // Create LangChain4jtool instances
+ CalculatorTool calculator = new CalculatorTool();
+ StringTool stringTool = new StringTool();
+
+ List<Object> customTools = Arrays.asList(calculator, stringTool);
+
+ // Create agent configuration with custom tools
+ AgentConfiguration config = new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withCustomTools(customTools);
+
+ // Create agent
+ Agent agent = new AgentWithoutMemory(config);
+
+ // Register agent in Camel context
+ this.context.getRegistry().bind("mixedToolsAgent", agent);
+
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ // Route with mixed tools : custom tools (via agent) + camel
routes
+ from("direct:mixedTools")
+
.to("langchain4j-agent:assistant?agent=#mixedToolsAgent&tags=users,weather")
+ .to("mock:agent-response");
+
+ // Tool routes for function calling
+ from("langchain4j-tools:userDb?tags=users&description=Query
user database by user ID¶meter.userId=string")
+ .setBody(constant(USER_DATABASE));
+
+
from("langchain4j-tools:weatherService?tags=weather&description=Get current
weather information¶meter.location=string")
+ .setBody(constant("{\"weather\": \"" + WEATHER_INFO +
"\", \"location\": \"Current Location\"}"));
+ }
+ };
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/CalculatorTool.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/CalculatorTool.java
new file mode 100644
index 00000000000..e64a64af66a
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/CalculatorTool.java
@@ -0,0 +1,41 @@
+/*
+ * 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.agent.pojos;
+
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+
+/**
+ * Calculator tools
+ */
+public class CalculatorTool {
+
+ @Tool("Adds two numbers")
+ public int add(@P("First number") int a, @P("Second number") int b) {
+ return a + b;
+ }
+
+ @Tool("Multiplies two numbers")
+ public int multiply(@P("First number") int a, @P("Second number") int b) {
+ return a * b;
+ }
+
+ @Tool("Gets the square root of a number")
+ public double sqrt(@P("Number") double x) {
+ return Math.sqrt(x);
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/StringTool.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/StringTool.java
new file mode 100644
index 00000000000..7700061d9c9
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/StringTool.java
@@ -0,0 +1,36 @@
+/*
+ * 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.agent.pojos;
+
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+
+/**
+ * String tools
+ */
+public class StringTool {
+
+ @Tool("Converts text to uppercase")
+ public String toUpperCase(@P("Text to convert") String text) {
+ return text.toUpperCase();
+ }
+
+ @Tool("Gets the length of a string")
+ public int getLength(@P("Text") String text) {
+ return text.length();
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/WeatherTool.java
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/WeatherTool.java
new file mode 100644
index 00000000000..f4230b55d46
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/pojos/WeatherTool.java
@@ -0,0 +1,39 @@
+/*
+ * 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.agent.pojos;
+
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+
+/**
+ * Weather LangChain4j tools
+ */
+public class WeatherTool {
+
+ private static final String WEATHER_INFO = "sunny";
+ private static final String WEATHER_TEMP = "22";
+
+ @Tool("Gets weather information for a city")
+ public String getWeather(@P("City name") String city) {
+ return "Weather in " + city + ": " + WEATHER_INFO + ", " +
WEATHER_TEMP + "°C";
+ }
+
+ @Tool("Gets temperature for a city")
+ public int getTemperature(@P("City name") String city) {
+ return Integer.parseInt(WEATHER_TEMP);
+ }
+}