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 1bbcbdfbcf Add tests and support for langchain4j-agent MCP tools
1bbcbdfbcf is described below
commit 1bbcbdfbcf550a396d93a596e54118a192733128
Author: James Netherton <[email protected]>
AuthorDate: Mon Nov 24 07:13:28 2025 +0000
Add tests and support for langchain4j-agent MCP tools
---
.github/reclaim-disk-space.sh | 2 +-
.../deployment/Langchain4jAgentProcessor.java | 25 ++++++++++++++++++++
integration-tests/langchain4j-agent/README.adoc | 2 ++
.../langchain4j/agent/it/AgentProducers.java | 27 ++++++++++++++++++++++
.../agent/it/Langchain4jAgentResource.java | 17 ++++++++++++++
.../agent/it/Langchain4jAgentRoutes.java | 11 +++++++++
.../langchain4j/agent/it/util/ProcessUtils.java | 16 ++++++-------
.../src/main/resources/application.properties | 3 ++-
.../langchain4j/agent/it/Langchain4jAgentTest.java | 26 ++++++++++++++++++++-
...urce.java => Langchain4jAgentTestResource.java} | 25 +++++++++++++++++++-
..._chat-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json | 24 +++++++++++++++++++
..._chat-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json | 24 +++++++++++++++++++
..._chat-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json | 24 +++++++++++++++++++
13 files changed, 214 insertions(+), 12 deletions(-)
diff --git a/.github/reclaim-disk-space.sh b/.github/reclaim-disk-space.sh
index d475e8e650..60e9a6cdad 100755
--- a/.github/reclaim-disk-space.sh
+++ b/.github/reclaim-disk-space.sh
@@ -29,7 +29,7 @@ sudo rm -rf /opt/ghc \
rm -rf /usr/local/.ghcup \
rm -rf /usr/local/go \
rm -rf /usr/local/lib/android \
- rm -rf /usr/local/lib/node_modules \
+ rm -rf /usr/local/lib/node_modules/parcel \
rm -rf /usr/local/share/boost \
rm -rf /usr/local/share/powershell \
rm -rf /usr/share/dotnet \
diff --git
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
index 0f3e510c35..05cb21d36c 100644
---
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
+++
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
@@ -16,8 +16,16 @@
*/
package org.apache.camel.quarkus.component.langchain4j.agent.deployment;
+import java.util.Set;
+import java.util.stream.Collectors;
+
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
class Langchain4jAgentProcessor {
private static final String FEATURE = "camel-langchain4j-agent";
@@ -26,4 +34,21 @@ class Langchain4jAgentProcessor {
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
+
+ @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
+ ReflectiveClassBuildItem registerForReflection(CombinedIndexBuildItem
combinedIndex) {
+ Set<String> mcpProtocolClasses = combinedIndex.getIndex()
+ .getClassesInPackage("dev.langchain4j.mcp.client.protocol")
+ .stream()
+ .map(ClassInfo::asClass)
+ .map(ClassInfo::name)
+ .map(DotName::toString)
+ .collect(Collectors.toSet());
+
+ return ReflectiveClassBuildItem
+ .builder(mcpProtocolClasses.toArray(new String[0]))
+ .fields(true)
+ .methods(true)
+ .build();
+ }
}
diff --git a/integration-tests/langchain4j-agent/README.adoc
b/integration-tests/langchain4j-agent/README.adoc
index dd78b36b2a..17c762c412 100644
--- a/integration-tests/langchain4j-agent/README.adoc
+++ b/integration-tests/langchain4j-agent/README.adoc
@@ -4,6 +4,8 @@ By default, the Langchain4j-agent integration tests use
WireMock to stub Ollama
To run the `camel-quarkus-langchain4j-agent` integration tests against the
real API, you need a Ollama instance running with the `orca-mini` &
`granite4:3b` models downloaded.
+The MCP client tests require https://nodejs.org/[Node.js] to be installed on
the test host.
+
When Ollama is running, set the following environment variables:
[source,shell]
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 5815b7bc76..a3d9bdff67 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
@@ -27,6 +27,8 @@ import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.mcp.client.DefaultMcpClient;
+import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
@@ -54,6 +56,7 @@ import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.Validat
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.apache.camel.quarkus.component.langchain4j.agent.it.util.ProcessUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import static java.time.Duration.ofSeconds;
@@ -63,6 +66,9 @@ public class AgentProducers {
@ConfigProperty(name = "langchain4j.ollama.base-url")
String baseUrl;
+ @ConfigProperty(name = "nodejs.installed")
+ boolean isNodeJSInstaled;
+
@Produces
@Identifier("ollamaOrcaMiniModel")
ChatModel ollamaOrcaMiniModel() {
@@ -221,4 +227,25 @@ public class AgentProducers {
.withChatModel(chatModel)
.withCustomTools(List.of(new AdditionTool())));
}
+
+ @Produces
+ @Identifier("agentWithMcpClient")
+ Agent agentWithMcpClient(@Identifier("granite4Model") ChatModel chatModel)
{
+ if (isNodeJSInstaled) {
+ return new AgentWithoutMemory(new AgentConfiguration()
+ .withChatModel(chatModel)
+ .withMcpClient(new DefaultMcpClient.Builder()
+ .transport(new StdioMcpTransport.Builder()
+
.command(List.of(ProcessUtils.getNpxExecutable(), "-y",
+
"@modelcontextprotocol/server-everything"))
+ .logEvents(true)
+ .build())
+ .build())
+ .withMcpToolProviderFilter((mcpClient, toolSpecification)
-> {
+ String toolName =
toolSpecification.name().toLowerCase();
+ return toolName.contains("add") ||
toolName.contains("echo") || toolName.contains("long");
+ }));
+ }
+ return null;
+ }
}
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 2edf87679d..d8f928b382 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
@@ -26,6 +26,7 @@ import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.camel.Exchange;
@@ -225,4 +226,20 @@ public class Langchain4jAgentResource {
AdditionTool.reset();
}
}
+
+ @Path("/mcp/client")
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(value = { MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
+ public Response chatWithMcpClient(String userMessage) {
+ String result = producerTemplate.to("direct:agent-with-mcp-client")
+ .withBody(userMessage)
+ .request(String.class);
+
+ String contentType = userMessage.contains("JSON") ?
MediaType.APPLICATION_JSON : MediaType.TEXT_PLAIN;
+
+ return Response.ok(result.trim())
+ .header(HttpHeaders.CONTENT_TYPE, contentType)
+ .build();
+ }
}
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 3d65065444..f1de1bd9df 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
@@ -16,11 +16,17 @@
*/
package org.apache.camel.quarkus.component.langchain4j.agent.it;
+import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.builder.RouteBuilder;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+@ApplicationScoped
public class Langchain4jAgentRoutes extends RouteBuilder {
public static final String USER_JOHN = "John Doe";
+ @ConfigProperty(name = "nodejs.installed")
+ boolean isNodeJSInstaled;
+
@Override
public void configure() throws Exception {
from("direct:simple-agent")
@@ -58,5 +64,10 @@ public class Langchain4jAgentRoutes extends RouteBuilder {
from("direct:agent-with-custom-tools")
.to("langchain4j-agent:test-agent-custom-tools?agent=#agentWithCustomTools");
+
+ if (isNodeJSInstaled) {
+ from("direct:agent-with-mcp-client")
+
.to("langchain4j-agent:test-agent-with-mcp-client?agent=#agentWithMcpClient");
+ }
}
}
diff --git
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java
similarity index 68%
copy from
extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
copy to
integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java
index 0f3e510c35..5ba35eca06 100644
---
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
+++
b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java
@@ -14,16 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.langchain4j.agent.deployment;
+package org.apache.camel.quarkus.component.langchain4j.agent.it.util;
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.smallrye.common.os.OS;
-class Langchain4jAgentProcessor {
- private static final String FEATURE = "camel-langchain4j-agent";
+public final class ProcessUtils {
+ private ProcessUtils() {
+ // Utility class
+ }
- @BuildStep
- FeatureBuildItem feature() {
- return new FeatureBuildItem(FEATURE);
+ public static String getNpxExecutable() {
+ return OS.current().equals(OS.WINDOWS) ? "npx.cmd" : "npx";
}
}
diff --git
a/integration-tests/langchain4j-agent/src/main/resources/application.properties
b/integration-tests/langchain4j-agent/src/main/resources/application.properties
index 38bd39fb72..63b00b4664 100644
---
a/integration-tests/langchain4j-agent/src/main/resources/application.properties
+++
b/integration-tests/langchain4j-agent/src/main/resources/application.properties
@@ -14,4 +14,5 @@
## See the License for the specific language governing permissions and
## limitations under the License.
## ---------------------------------------------------------------------------
-quarkus.native.resources.includes=rag/*
\ No newline at end of file
+quarkus.native.resources.includes=rag/*
+quarkus.http.test-timeout=120S
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 175f5bff35..b68dc89859 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
@@ -21,10 +21,13 @@ import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationFailureInputGuardrail;
import
org.apache.camel.quarkus.component.langchain4j.agent.it.guardrail.ValidationFailureOutputGuardrail;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static
org.apache.camel.quarkus.component.langchain4j.agent.it.Langchain4jAgentRoutes.USER_JOHN;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.is;
@@ -33,7 +36,7 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
@ExtendWith(Langchain4jTestWatcher.class)
-@QuarkusTestResource(OllamaTestResource.class)
+@QuarkusTestResource(Langchain4jAgentTestResource.class)
@QuarkusTest
class Langchain4jAgentTest {
static final String TEST_USER_MESSAGE_SIMPLE = "What is Apache Camel?";
@@ -223,4 +226,25 @@ class Langchain4jAgentTest {
"result", containsStringIgnoringCase("15"),
"toolWasInvoked", is(true));
}
+
+ @Test
+ void agentWithMcpClient() {
+ boolean isNodeJSInstalled =
ConfigProvider.getConfig().getValue("nodejs.installed", boolean.class);
+ Assumptions.assumeTrue(isNodeJSInstalled, "Node.js is not installed");
+
+ RestAssured.given()
+ .body("Please list your available tools. You MUST respond
using ONLY valid JSON with tool names as an array. DO NOT add explanations. DO
NOT add comments. DO NOT wrap in markdown.")
+ .post("/langchain4j-agent/mcp/client")
+ .then()
+ .statusCode(200)
+ .body(".", containsInAnyOrder("add", "echo",
"longRunningOperation"));
+
+ RestAssured.given()
+ .body("Use your available tools to perform a long running
operation for 2 seconds with 2 steps. DO NOT use any markdown formatting in the
response.")
+ .post("/langchain4j-agent/mcp/client")
+ .then()
+ .statusCode(200)
+ .body(containsStringIgnoringCase(
+ "operation was executed successfully for a duration of
2 seconds divided into 2 steps"));
+ }
}
diff --git
a/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/OllamaTestResource.java
b/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTestResource.java
similarity index 77%
rename from
integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/OllamaTestResource.java
rename to
integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTestResource.java
index ee56621035..ab750a8612 100644
---
a/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/OllamaTestResource.java
+++
b/integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/Langchain4jAgentTestResource.java
@@ -22,11 +22,14 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import
org.apache.camel.quarkus.component.langchain4j.agent.it.util.ProcessUtils;
import
org.apache.camel.quarkus.test.wiremock.WireMockTestResourceLifecycleManager;
+import org.junit.jupiter.api.condition.OS;
-public class OllamaTestResource extends WireMockTestResourceLifecycleManager {
+public class Langchain4jAgentTestResource extends
WireMockTestResourceLifecycleManager {
private static final String OLLAMA_ENV_URL = "LANGCHAIN4J_OLLAMA_BASE_URL";
@Override
@@ -35,6 +38,7 @@ public class OllamaTestResource extends
WireMockTestResourceLifecycleManager {
String wiremockUrl = properties.get("wiremock.url");
String url = wiremockUrl != null ? wiremockUrl :
getRecordTargetBaseUrl();
properties.put("langchain4j.ollama.base-url", url);
+ properties.put("nodejs.installed",
isNodeJSInstallationExists().toString());
return properties;
}
@@ -86,4 +90,23 @@ public class OllamaTestResource extends
WireMockTestResourceLifecycleManager {
Langchain4jTestWatcher.reset();
}
}
+
+ private Boolean isNodeJSInstallationExists() {
+ try {
+ // TODO: Suppress MCP tests in GitHub Actions for windows -
https://github.com/apache/camel-quarkus/issues/8007
+ if (OS.current().equals(OS.WINDOWS) && System.getenv("CI") !=
null) {
+ return false;
+ }
+
+ Process process = new ProcessBuilder()
+ .command(ProcessUtils.getNpxExecutable(), "--version")
+ .start();
+
+ process.waitFor(10, TimeUnit.SECONDS);
+ return process.exitValue() == 0;
+ } catch (Exception e) {
+ LOG.error("Failed detecting Node.js", e);
+ }
+ return false;
+ }
}
diff --git
a/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json
new file mode 100644
index 0000000000..3952c172a5
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json
@@ -0,0 +1,24 @@
+{
+ "id" : "31a8eb4e-6382-41f3-af6e-d0bd0c223779",
+ "name" : "api_chat",
+ "request" : {
+ "url" : "/api/chat",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\n \"model\" : \"granite4:3b\",\n \"messages\" : [
{\n \"role\" : \"user\",\n \"content\" : \"Use your available tools to
perform a long running operation for 2 seconds with 2 steps. DO NOT use any
markdown formatting in the response.\"\n }, {\n \"role\" : \"assistant\",\n
\"tool_calls\" : [ {\n \"function\" : {\n \"name\" :
\"longRunningOperation\",\n \"arguments\" : {\n \"duration\" :
2,\n \"steps\" : 2\n [...]
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" :
"{\"model\":\"granite4:3b\",\"created_at\":\"2025-11-21T13:34:24.262165232Z\",\"message\":{\"role\":\"assistant\",\"content\":\"The
long running operation was executed successfully for a duration of 2 seconds
divided into 2 steps. No further actions are
required.\"},\"done\":true,\"done_reason\":\"stop\",\"total_duration\":1950113721,\"load_duration\":58493280,\"prompt_eval_count\":372,\"prompt_eval_duration\":501168238,\"eval_count\":27,\"eval_duration\":1373362750}",
+ "headers" : {
+ "Date" : "Fri, 21 Nov 2025 13:34:24 GMT",
+ "Content-Type" : "application/json; charset=utf-8"
+ }
+ },
+ "uuid" : "31a8eb4e-6382-41f3-af6e-d0bd0c223779",
+ "persistent" : true,
+ "insertionIndex" : 22
+}
\ No newline at end of file
diff --git
a/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json
new file mode 100644
index 0000000000..a4f1b7d6eb
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json
@@ -0,0 +1,24 @@
+{
+ "id" : "40955a71-2ef8-4abf-a9e9-9e225590ff3d",
+ "name" : "api_chat",
+ "request" : {
+ "url" : "/api/chat",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\n \"model\" : \"granite4:3b\",\n \"messages\" : [
{\n \"role\" : \"user\",\n \"content\" : \"Use your available tools to
perform a long running operation for 2 seconds with 2 steps. DO NOT use any
markdown formatting in the response.\"\n } ],\n \"options\" : {\n
\"temperature\" : 0.3,\n \"stop\" : [ ]\n },\n \"stream\" : false,\n
\"tools\" : [ {\n \"type\" : \"function\",\n \"function\" : {\n
\"name\" : \"echo\",\n \"description [...]
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" :
"{\"model\":\"granite4:3b\",\"created_at\":\"2025-11-21T13:34:12.261633323Z\",\"message\":{\"role\":\"assistant\",\"content\":\"\",\"tool_calls\":[{\"id\":\"call_nwsbgoqi\",\"function\":{\"index\":0,\"name\":\"longRunningOperation\",\"arguments\":{\"duration\":2,\"steps\":2}}}]},\"done\":true,\"done_reason\":\"stop\",\"total_duration\":2284427024,\"load_duration\":53618252,\"prompt_eval_count\":319,\"prompt_eval_duration\":429089470,\"eval_count\":35,\"eval_duration\":1782178545}",
+ "headers" : {
+ "Date" : "Fri, 21 Nov 2025 13:34:12 GMT",
+ "Content-Type" : "application/json; charset=utf-8"
+ }
+ },
+ "uuid" : "40955a71-2ef8-4abf-a9e9-9e225590ff3d",
+ "persistent" : true,
+ "insertionIndex" : 23
+}
\ No newline at end of file
diff --git
a/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json
new file mode 100644
index 0000000000..db86ad647e
--- /dev/null
+++
b/integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json
@@ -0,0 +1,24 @@
+{
+ "id" : "ffafaf8e-0801-4877-bd4f-1a86a7798e5a",
+ "name" : "api_chat",
+ "request" : {
+ "url" : "/api/chat",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "equalToJson" : "{\n \"model\" : \"granite4:3b\",\n \"messages\" : [
{\n \"role\" : \"user\",\n \"content\" : \"Please list your available
tools. You MUST respond using ONLY valid JSON with tool names as an array. DO
NOT add explanations. DO NOT add comments. DO NOT wrap in markdown.\"\n } ],\n
\"options\" : {\n \"temperature\" : 0.3,\n \"stop\" : [ ]\n },\n
\"stream\" : false,\n \"tools\" : [ {\n \"type\" : \"function\",\n
\"function\" : {\n \"name\" [...]
+ "ignoreArrayOrder" : true,
+ "ignoreExtraElements" : false
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" :
"{\"model\":\"granite4:3b\",\"created_at\":\"2025-11-21T13:34:09.765306544Z\",\"message\":{\"role\":\"assistant\",\"content\":\"[\\n
\\\"echo\\\",\\n \\\"longRunningOperation\\\",\\n
\\\"add\\\"\\n]\"},\"done\":true,\"done_reason\":\"stop\",\"total_duration\":6360023744,\"load_duration\":1473869578,\"prompt_eval_count\":326,\"prompt_eval_duration\":4030858505,\"eval_count\":17,\"eval_duration\":843507830}",
+ "headers" : {
+ "Date" : "Fri, 21 Nov 2025 13:34:09 GMT",
+ "Content-Type" : "application/json; charset=utf-8"
+ }
+ },
+ "uuid" : "ffafaf8e-0801-4877-bd4f-1a86a7798e5a",
+ "persistent" : true,
+ "insertionIndex" : 24
+}
\ No newline at end of file