From e540cc2f716bbab115e05c929ddef7ccc7a94b47 Mon Sep 17 00:00:00 2001 From: James Netherton Date: Mon, 24 Nov 2025 07:13:28 +0000 Subject: [PATCH] Add tests and support for langchain4j-agent MCP tools --- .github/reclaim-disk-space.sh | 2 +- .../deployment/Langchain4jAgentProcessor.java | 25 ++++++++++++++++ .../langchain4j-agent/README.adoc | 2 ++ .../langchain4j/agent/it/AgentProducers.java | 27 +++++++++++++++++ .../agent/it/Langchain4jAgentResource.java | 17 +++++++++++ .../agent/it/Langchain4jAgentRoutes.java | 11 +++++++ .../agent/it/util/ProcessUtils.java | 29 +++++++++++++++++++ .../src/main/resources/application.properties | 3 +- .../agent/it/Langchain4jAgentTest.java | 26 ++++++++++++++++- ...java => Langchain4jAgentTestResource.java} | 25 +++++++++++++++- ...-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json | 24 +++++++++++++++ ...-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json | 24 +++++++++++++++ ...-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json | 24 +++++++++++++++ 13 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java rename integration-tests/langchain4j-agent/src/test/java/org/apache/camel/quarkus/component/langchain4j/agent/it/{OllamaTestResource.java => Langchain4jAgentTestResource.java} (77%) create mode 100644 integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-31a8eb4e-6382-41f3-af6e-d0bd0c223779.json create mode 100644 integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-40955a71-2ef8-4abf-a9e9-9e225590ff3d.json create mode 100644 integration-tests/langchain4j-agent/src/test/resources/mappings/api_chat-ffafaf8e-0801-4877-bd4f-1a86a7798e5a.json diff --git a/.github/reclaim-disk-space.sh b/.github/reclaim-disk-space.sh index d475e8e65038..60e9a6cdad55 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 0f3e510c350e..05cb21d36c17 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 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 dd78b36b2ad7..17c762c4123b 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 5815b7bc76a2..a3d9bdff6755 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.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.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 @@ Agent agentWithCustomTools(@Identifier("granite4Model") ChatModel chatModel) { .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 2edf87679daa..d8f928b382ee 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.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 Response chatWithCustomTools(String userMessage) { 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 3d650654448a..f1de1bd9df76 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 void configure() throws Exception { 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/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java new file mode 100644 index 000000000000..5ba35eca06c5 --- /dev/null +++ b/integration-tests/langchain4j-agent/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/it/util/ProcessUtils.java @@ -0,0 +1,29 @@ +/* + * 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.util; + +import io.smallrye.common.os.OS; + +public final class ProcessUtils { + private ProcessUtils() { + // Utility class + } + + 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 38bd39fb724f..63b00b466477 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 175f5bff3552..b68dc898597a 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.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.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 @@ void agentWithCustomTools() { "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 ee5662103594..ab750a8612c7 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.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 Map start() { 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 void stop() { 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 000000000000..3952c172a5f9 --- /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 }\n }\n } ]\n }, {\n \"role\" : \"tool\",\n \"content\" : \"Long running operation completed. Duration: 2 seconds, Steps: 2.\"\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\" : \"Echoes back the input\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"message\" : {\n \"type\" : \"string\",\n \"description\" : \"Message to echo\"\n }\n },\n \"required\" : [ \"message\" ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"longRunningOperation\",\n \"description\" : \"Demonstrates a long running operation with progress updates\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"duration\" : {\n \"type\" : \"number\",\n \"description\" : \"Duration of the operation in seconds\"\n },\n \"steps\" : {\n \"type\" : \"number\",\n \"description\" : \"Number of steps in the operation\"\n }\n },\n \"required\" : [ ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"add\",\n \"description\" : \"Adds two numbers\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"a\" : {\n \"type\" : \"number\",\n \"description\" : \"First number\"\n },\n \"b\" : {\n \"type\" : \"number\",\n \"description\" : \"Second number\"\n }\n },\n \"required\" : [ \"a\", \"b\" ]\n }\n }\n } ]\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 000000000000..a4f1b7d6eb7e --- /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\" : \"Echoes back the input\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"message\" : {\n \"type\" : \"string\",\n \"description\" : \"Message to echo\"\n }\n },\n \"required\" : [ \"message\" ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"longRunningOperation\",\n \"description\" : \"Demonstrates a long running operation with progress updates\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"duration\" : {\n \"type\" : \"number\",\n \"description\" : \"Duration of the operation in seconds\"\n },\n \"steps\" : {\n \"type\" : \"number\",\n \"description\" : \"Number of steps in the operation\"\n }\n },\n \"required\" : [ ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"add\",\n \"description\" : \"Adds two numbers\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"a\" : {\n \"type\" : \"number\",\n \"description\" : \"First number\"\n },\n \"b\" : {\n \"type\" : \"number\",\n \"description\" : \"Second number\"\n }\n },\n \"required\" : [ \"a\", \"b\" ]\n }\n }\n } ]\n}", + "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 000000000000..db86ad647e5a --- /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\" : \"echo\",\n \"description\" : \"Echoes back the input\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"message\" : {\n \"type\" : \"string\",\n \"description\" : \"Message to echo\"\n }\n },\n \"required\" : [ \"message\" ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"longRunningOperation\",\n \"description\" : \"Demonstrates a long running operation with progress updates\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"duration\" : {\n \"type\" : \"number\",\n \"description\" : \"Duration of the operation in seconds\"\n },\n \"steps\" : {\n \"type\" : \"number\",\n \"description\" : \"Number of steps in the operation\"\n }\n },\n \"required\" : [ ]\n }\n }\n }, {\n \"type\" : \"function\",\n \"function\" : {\n \"name\" : \"add\",\n \"description\" : \"Adds two numbers\",\n \"parameters\" : {\n \"type\" : \"object\",\n \"properties\" : {\n \"a\" : {\n \"type\" : \"number\",\n \"description\" : \"First number\"\n },\n \"b\" : {\n \"type\" : \"number\",\n \"description\" : \"Second number\"\n }\n },\n \"required\" : [ \"a\", \"b\" ]\n }\n }\n } ]\n}", + "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