This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push:
new b386ce31af Add tests and support for langchain4j-agent custom tools
b386ce31af is described below
commit b386ce31af0962464e4980656774b16337a17593
Author: James Netherton <[email protected]>
AuthorDate: Wed Oct 15 07:21:09 2025 +0100
Add tests and support for langchain4j-agent custom tools
---
.../deployment/SupportLangchain4jProcessor.java | 21 ++++++++++++
integration-tests/langchain4j-agent/pom.xml | 4 +++
.../langchain4j/agent/it/AgentProducers.java | 9 +++++
.../agent/it/Langchain4jAgentResource.java | 19 ++++++++++
.../agent/it/Langchain4jAgentRoutes.java | 3 ++
.../langchain4j/agent/it/tool/AdditionTool.java | 40 ++++++++++++++++++++++
.../langchain4j/agent/it/Langchain4jAgentTest.java | 12 +++++++
..._chat-891a47a3-531c-48fd-920c-b8eff546c7ff.json | 24 +++++++++++++
..._chat-a14f44e0-8921-47ab-a69e-cf038e7cb358.json | 24 +++++++++++++
9 files changed, 156 insertions(+)
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportLangchain4jProcessor.java
b/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportLangchain4jProcessor.java
index 8d97bfef69..f7d0e243ff 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportLangchain4jProcessor.java
+++
b/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportLangchain4jProcessor.java
@@ -25,6 +25,7 @@ import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.guardrail.InputGuardrail;
import dev.langchain4j.guardrail.JsonExtractorOutputGuardrail;
import dev.langchain4j.guardrail.OutputGuardrail;
@@ -188,6 +189,26 @@ class SupportLangchain4jProcessor {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(guardrailTypes.toArray(new
String[0])).build());
}
+ @BuildStep
+ void registerCustomToolsForReflection(
+ CombinedIndexBuildItem combinedIndex,
+ BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
+
+ Set<String> customToolClasses = combinedIndex.getIndex()
+ .getAnnotations(Tool.class)
+ .stream()
+ .map(AnnotationInstance::target)
+ .map(AnnotationTarget::asMethod)
+ .map(MethodInfo::declaringClass)
+ .map(ClassInfo::name)
+ .map(DotName::toString)
+ .collect(Collectors.toSet());
+
+
reflectiveClass.produce(ReflectiveClassBuildItem.builder(customToolClasses.toArray(new
String[0]))
+ .methods()
+ .build());
+ }
+
@BuildStep
void
registerLangChain4jNlpTypesForReflection(BuildProducer<ReflectiveClassBuildItem>
reflectiveClass) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(SentenceDetectorFactory.class).build());
diff --git a/integration-tests/langchain4j-agent/pom.xml
b/integration-tests/langchain4j-agent/pom.xml
index af0ee36572..7a4a9b02e7 100644
--- a/integration-tests/langchain4j-agent/pom.xml
+++ b/integration-tests/langchain4j-agent/pom.xml
@@ -47,6 +47,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-resteasy-jackson</artifactId>
+ </dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
diff --git
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/AgentProducers.java
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/AgentProducers.java
index f60dd5ad05..ac1714d6b2 100644
---
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/AgentProducers.java
+++
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/AgentProducers.java
@@ -52,6 +52,7 @@ import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.Validat
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationSuccessInputGuardrail;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationSuccessOutputGuardrail;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.service.TestPojoAiAgent;
+import
org.apache.camel.quarkus.component.langchain4j.agent.it.tool.AdditionTool;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.util.PersistentChatMemoryStore;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -212,4 +213,12 @@ public class AgentProducers {
return new TestPojoAiAgent(new AgentConfiguration()
.withChatModel(chatModel), objectMapper);
}
+
+ @Produces
+ @Identifier("agentWithCustomTools")
+ Agent agentWithCustomTools(@Identifier("ollamaLlama31Model") ChatModel
chatModel) {
+ return new AgentWithoutMemory(new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withCustomTools(List.of(new AdditionTool())));
+ }
}
diff --git
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentResource.java
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentResource.java
index 311d9c1e5d..2edf87679d 100644
---
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentResource.java
+++
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentResource.java
@@ -33,6 +33,7 @@ import org.apache.camel.FluentProducerTemplate;
import org.apache.camel.component.langchain4j.agent.api.AiAgentBody;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationSuccessInputGuardrail;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationSuccessOutputGuardrail;
+import
org.apache.camel.quarkus.component.langchain4j.agent.it.tool.AdditionTool;
import static
org.apache.camel.component.langchain4j.agent.api.Headers.MEMORY_ID;
import static
org.apache.camel.component.langchain4j.agent.api.Headers.SYSTEM_MESSAGE;
@@ -206,4 +207,22 @@ public class Langchain4jAgentResource {
return Response.ok(result.trim()).build();
}
+
+ @Path("/custom/tools")
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response chatWithCustomTools(String userMessage) {
+ try {
+ String result =
producerTemplate.to("direct:agent-with-custom-tools")
+ .withBody(userMessage)
+ .request(String.class);
+
+ return Response.ok(
+ Map.of("result", result.trim(), "toolWasInvoked",
AdditionTool.isToolWasInvoked()))
+ .build();
+ } finally {
+ AdditionTool.reset();
+ }
+ }
}
diff --git
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentRoutes.java
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentRoutes.java
index 3c09c356e6..3d65065444 100644
---
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentRoutes.java
+++
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentRoutes.java
@@ -55,5 +55,8 @@ public class Langchain4jAgentRoutes extends RouteBuilder {
from("langchain4j-tools:userDb?tags=users&description=Query user
database by user ID¶meter.userId=integer")
.setBody().constant("{\"name\": \"" + USER_JOHN + "\", \"id\":
\"123\"}");
+
+ from("direct:agent-with-custom-tools")
+
.to("langchain4j-agent:test-agent-custom-tools?agent=#agentWithCustomTools");
}
}
diff --git
a/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/tool/AdditionTool.java
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/tool/AdditionTool.java
new file mode 100644
index 0000000000..64c2a72c0d
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/tool/AdditionTool.java
@@ -0,0 +1,40 @@
+/*
+ * 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.quarkus.component.langchain4j.agent.it.tool;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+
+public class AdditionTool {
+ private static final AtomicBoolean TOOL_WAS_INVOKED = new
AtomicBoolean(false);
+
+ @Tool("Adds two numbers")
+ public Integer addNumbers(@P("First number") Integer a, @P("Second
number") Integer b) {
+ TOOL_WAS_INVOKED.set(true);
+ return a + b;
+ }
+
+ public static boolean isToolWasInvoked() {
+ return TOOL_WAS_INVOKED.get();
+ }
+
+ public static void reset() {
+ TOOL_WAS_INVOKED.set(false);
+ }
+}
diff --git
a/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTest.java
b/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTest.java
index fce52dbc17..bc73e83ed6 100644
---
a/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTest.java
+++
b/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTest.java
@@ -211,4 +211,16 @@ class Langchain4jAgentTest {
"name", is(USER_JOHN),
"description", notNullValue());
}
+
+ @Test
+ void agentWithCustomTools() {
+ RestAssured.given()
+ .body("Calculate the addition of 10 + 5")
+ .post("/langchain4j-agent/custom/tools")
+ .then()
+ .statusCode(200)
+ .body(
+ "result", containsStringIgnoringCase("15"),
+ "toolWasInvoked", is(true));
+ }
}
diff --git
a/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-891a47a3-531c-48fd-920c-b8eff546c7ff.json
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-891a47a3-531c-48fd-920c-b8eff546c7ff.json
new file mode 100644
index 0000000000..24a597189e
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-891a47a3-531c-48fd-920c-b8eff546c7ff.json
@@ -0,0 +1,24 @@
+{
+ "id" : "891a47a3-531c-48fd-920c-b8eff546c7ff",
+ "name" : "api_chat",
+ "request" : {
+ "url" : "/api/chat",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\n \"model\" : \"llama3.1:latest\",\n \"messages\" :
[ {\n \"role\" : \"user\",\n \"content\" : \"Calculate the addition of 10
+ 5\"\n }, {\n \"role\" : \"assistant\",\n \"tool_calls\" : [ {\n
\"function\" : {\n \"name\" : \"addNumbers\",\n \"arguments\" :
{\n \"arg1\" : 5,\n \"arg0\" : 10\n }\n }\n }
]\n }, {\n \"role\" : \"tool\",\n \"content\" : \"15\"\n } ],\n
\"options\" : {\n \"te [...]
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" :
"{\"model\":\"llama3.1:latest\",\"created_at\":\"2025-10-14T13:43:19.525346678Z\",\"message\":{\"role\":\"assistant\",\"content\":\"The
result of the calculation is
15.\"},\"done_reason\":\"stop\",\"done\":true,\"total_duration\":2396046130,\"load_duration\":18256574,\"prompt_eval_count\":108,\"prompt_eval_duration\":1392355389,\"eval_count\":10,\"eval_duration\":984595879}",
+ "headers" : {
+ "Date" : "Tue, 14 Oct 2025 13:43:19 GMT",
+ "Content-Type" : "application/json; charset=utf-8"
+ }
+ },
+ "uuid" : "891a47a3-531c-48fd-920c-b8eff546c7ff",
+ "persistent" : true,
+ "insertionIndex" : 20
+}
\ No newline at end of file
diff --git
a/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-a14f44e0-8921-47ab-a69e-cf038e7cb358.json
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-a14f44e0-8921-47ab-a69e-cf038e7cb358.json
new file mode 100644
index 0000000000..3d85f6c1cd
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-a14f44e0-8921-47ab-a69e-cf038e7cb358.json
@@ -0,0 +1,24 @@
+{
+ "id" : "a14f44e0-8921-47ab-a69e-cf038e7cb358",
+ "name" : "api_chat",
+ "request" : {
+ "url" : "/api/chat",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\n \"model\" : \"llama3.1:latest\",\n \"messages\" :
[ {\n \"role\" : \"user\",\n \"content\" : \"Calculate the addition of 10
+ 5\"\n } ],\n \"options\" : {\n \"temperature\" : 0.3,\n \"stop\" : [
]\n },\n \"stream\" : false,\n \"tools\" : [ {\n \"type\" :
\"function\",\n \"function\" : {\n \"name\" : \"addNumbers\",\n
\"description\" : \"Adds two numbers\",\n \"parameters\" : {\n
\"type\" : \"object\",\n \"pr [...]
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" :
"{\"model\":\"llama3.1:latest\",\"created_at\":\"2025-10-14T13:43:17.079209926Z\",\"message\":{\"role\":\"assistant\",\"content\":\"\",\"tool_calls\":[{\"function\":{\"name\":\"addNumbers\",\"arguments\":{\"arg0\":10,\"arg1\":5}}}]},\"done_reason\":\"stop\",\"done\":true,\"total_duration\":20461789347,\"load_duration\":13165365726,\"prompt_eval_count\":188,\"prompt_eval_duration\":4650715215,\"eval_count\":25,\"eval_duration\":2644447381}",
+ "headers" : {
+ "Date" : "Tue, 14 Oct 2025 13:43:17 GMT",
+ "Content-Type" : "application/json; charset=utf-8"
+ }
+ },
+ "uuid" : "a14f44e0-8921-47ab-a69e-cf038e7cb358",
+ "persistent" : true,
+ "insertionIndex" : 21
+}
\ No newline at end of file