From 9aff7f2888ef7b755dc49c600594258eb9f8491d Mon Sep 17 00:00:00 2001 From: Zineb Bendhiba Date: Fri, 3 Jul 2026 10:35:04 +0200 Subject: [PATCH] CAMEL-23382: camel-ai-tool - align spring-ai-chat with camel-ai-tool component Co-Authored-By: Claude Opus 4.6 --- .../camel-spring-ai-chat/pom.xml | 10 ++- .../main/docs/spring-ai-chat-component.adoc | 26 +++--- .../springai/chat/AiToolSpecToSpringAi.java | 73 +++++++++++++++ .../chat/SpringAiChatConfiguration.java | 2 +- .../springai/chat/SpringAiChatProducer.java | 38 ++++---- .../chat/SpringAiChatToolsDiscoveryTest.java | 88 ++++++++----------- .../chat/SpringAiChatToolsIntegrationIT.java | 34 +++---- .../pages/camel-4x-upgrade-guide-4_22.adoc | 41 +++++++++ 8 files changed, 213 insertions(+), 99 deletions(-) create mode 100644 components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/AiToolSpecToSpringAi.java diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/pom.xml b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/pom.xml index 6734018c57a77..6ffe7156082f9 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/pom.xml +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/pom.xml @@ -40,10 +40,10 @@ camel-support - + org.apache.camel - camel-spring-ai-tools + camel-ai-tool ${project.version} true @@ -98,6 +98,12 @@ assertj-core test + + org.awaitility + awaitility + ${awaitility-version} + test + org.mockito mockito-junit-jupiter diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc index 4193ba1953fbc..f8d54d1e417bd 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc @@ -501,9 +501,9 @@ NOTE: The component automatically configures conversation isolation using metada === Function Calling / Tool Integration -The component integrates with the xref:spring-ai-tools-component.adoc[Spring AI Tools Component] to enable LLMs to call Camel routes as functions/tools. This extends the LLM's capabilities with custom logic, external APIs, database queries, and more. +The component integrates with the xref:ai-tool-component.adoc[AI Tool Component] to enable LLMs to call Camel routes as functions/tools. This extends the LLM's capabilities with custom logic, external APIs, database queries, and more. -When you configure the `tags` parameter, the chat component discovers all matching tools from `spring-ai-tools` routes, registers them with Spring AI's ChatClient, and allows the LLM to call them during conversation. +When you configure the `tags` parameter, the chat component discovers all matching tools from `ai-tool` routes, registers them with Spring AI's ChatClient, and allows the LLM to call them during conversation. [tabs] ==== @@ -512,13 +512,13 @@ Java:: [source,java] ---- // Define tools -from("spring-ai-tools:weather?tags=weather&description=Get current weather for a location") +from("ai-tool:weather?tags=weather&description=Get current weather for a location") .setBody(constant("Sunny")); -from("spring-ai-tools:calculator?tags=math&description=Evaluate mathematical expressions") +from("ai-tool:calculator?tags=math&description=Evaluate mathematical expressions") .setBody(constant("42")); -from("spring-ai-tools:stock?tags=finance&description=Get current stock price") +from("ai-tool:stock?tags=finance&description=Get current stock price") .setBody(constant("42")); // Chat endpoints with different tool combinations @@ -538,17 +538,17 @@ XML:: ---- - + Sunny - + 42 - + 42 @@ -576,7 +576,7 @@ YAML:: # Define tools - route: from: - uri: spring-ai-tools:weather + uri: ai-tool:weather parameters: tags: weather description: Get current weather for a location @@ -586,7 +586,7 @@ YAML:: - route: from: - uri: spring-ai-tools:calculator + uri: ai-tool:calculator parameters: tags: math description: Evaluate mathematical expressions @@ -596,7 +596,7 @@ YAML:: - route: from: - uri: spring-ai-tools:stock + uri: ai-tool:stock parameters: tags: finance description: Get current stock price @@ -1028,7 +1028,7 @@ template.request("direct:chat", e -> { ---- NOTE: Tool context works with Spring AI `@Tool` methods and MCP tools. -Camel route tools (defined via `spring-ai-tools` consumer) do not receive the `ToolContext`. +Camel route tools (defined via `ai-tool` consumer) do not receive the `ToolContext`. === Structured Output Validation @@ -1384,7 +1384,7 @@ logging.level.org.springframework.ai.chat.client.advisor=DEBUG * https://docs.spring.io/spring-ai/reference/[Spring AI Documentation] * https://docs.spring.io/spring-ai/reference/api/tools.html[Spring AI Function Calling] -* xref:spring-ai-tools-component.adoc[Spring AI Tools Component] +* xref:ai-tool-component.adoc[AI Tool Component] * xref:spring-ai-embeddings-component.adoc[Spring AI Embeddings Component] * xref:spring-ai-vector-store-component.adoc[Spring AI VectorStore Component] * xref:spring-ai-image-component.adoc[Spring AI Image Component] diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/AiToolSpecToSpringAi.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/AiToolSpecToSpringAi.java new file mode 100644 index 0000000000000..e4992f29e30c8 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/AiToolSpecToSpringAi.java @@ -0,0 +1,73 @@ +/* + * 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.springai.chat; + +import java.util.Map; +import java.util.function.Function; + +import org.apache.camel.Exchange; +import org.apache.camel.component.ai.tool.AiToolExecutor; +import org.apache.camel.component.ai.tool.AiToolResult; +import org.apache.camel.component.ai.tool.AiToolSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; + +/** + * Converts a framework-agnostic {@link AiToolSpec} into a Spring AI {@link ToolCallback}. Delegates tool execution to + * {@link AiToolExecutor} so that argument filtering, required-parameter validation, and route invocation are handled + * uniformly across all AI framework adapters. + */ +final class AiToolSpecToSpringAi { + + private static final Logger LOG = LoggerFactory.getLogger(AiToolSpecToSpringAi.class); + + private AiToolSpecToSpringAi() { + } + + static ToolCallback toToolCallback(AiToolSpec spec) { + Function, String> function = args -> { + Exchange toolExchange = spec.getConsumer().getEndpoint().createExchange(); + try { + AiToolResult result = AiToolExecutor.execute(spec, args, toolExchange); + if (result instanceof AiToolResult.Success success) { + return success.value(); + } else if (result instanceof AiToolResult.ArgumentError argErr) { + return "Tool execution failed: " + argErr.message(); + } else if (result instanceof AiToolResult.ExecutionError execErr) { + LOG.warn("Tool '{}' execution failed: {}", spec.getName(), execErr.message(), execErr.cause()); + return "Tool execution failed"; + } + return "Tool execution failed"; + } finally { + spec.getConsumer().releaseExchange(toolExchange, false); + } + }; + + FunctionToolCallback.Builder builder = FunctionToolCallback + .builder(spec.getName(), function) + .description(spec.getDescription()) + .inputType(Map.class); + + if (spec.getParametersJsonSchema() != null) { + builder.inputSchema(spec.getParametersJsonSchema()); + } + + return builder.build(); + } +} diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatConfiguration.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatConfiguration.java index 52e4324c8542c..f637ad6ebca75 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatConfiguration.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatConfiguration.java @@ -360,7 +360,7 @@ public String getTags() { /** * Tags for discovering and calling Camel route tools. When provided, the chat component will automatically register - * tools from camel-spring-ai-tools routes matching these tags. + * tools from ai-tool routes matching these tags. */ public void setTags(String tags) { this.tags = tags; diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java index 34b01bfedb9ef..c7836d197fcba 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,10 +32,10 @@ import org.apache.camel.InvalidPayloadException; import org.apache.camel.NoSuchHeaderException; import org.apache.camel.WrappedFile; +import org.apache.camel.component.ai.tool.AiToolParameterHelper; +import org.apache.camel.component.ai.tool.AiToolRegistry; +import org.apache.camel.component.ai.tool.AiToolSpec; import org.apache.camel.component.springai.chat.mcp.SpringAiChatMcpManager; -import org.apache.camel.component.springai.tools.TagsHelper; -import org.apache.camel.component.springai.tools.spec.CamelToolExecutorCache; -import org.apache.camel.component.springai.tools.spec.CamelToolSpecification; import org.apache.camel.support.DefaultProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -958,7 +959,7 @@ private void processStructuredOutputRequest( } /** - * Register tools from camel-spring-ai-tools routes based on configured tags + * Register tools from ai-tool routes based on configured tags * * Tools are registered via ChatOptions.toolCallbacks() which is the correct way to pass ToolCallback instances in * Spring AI. The tools() method expects objects with @Tool annotated methods, not ToolCallback instances. @@ -969,8 +970,8 @@ private List getToolCallbacksForTags(String tags) { return List.of(); } - // Discover tools from Camel Spring AI Tools routes - List toolCallbacks = discoverTools(tags); + // Discover tools from the unified AiToolRegistry + List toolCallbacks = discoverAiRegistryTools(tags); if (!toolCallbacks.isEmpty()) { // Collect tool names for enhanced logging @@ -994,21 +995,22 @@ private List getToolCallbacksForTags(String tags) { } /** - * Discover tools by tags and return a list of ToolCallback instances + * Discover tools registered via {@code ai-tool:} consumer endpoints in the shared {@link AiToolRegistry}. Converts + * each {@link AiToolSpec} to a Spring AI {@link ToolCallback} via {@link AiToolSpecToSpringAi}. */ - private List discoverTools(String tags) { - final CamelToolExecutorCache toolCache = CamelToolExecutorCache.getInstance(); - final Map> tools = toolCache.getTools(); - final String[] tagArray = TagsHelper.splitTags(tags); - - final List toolCallbacks = Arrays.stream(tagArray) - .flatMap(tag -> tools.entrySet().stream() - .filter(entry -> entry.getKey().equals(tag)) - .flatMap(entry -> entry.getValue().stream())) - .map(CamelToolSpecification::getToolCallback) + private List discoverAiRegistryTools(String tags) { + final AiToolRegistry registry = AiToolRegistry.getOrCreate(getEndpoint().getCamelContext()); + final String[] tagArray = AiToolParameterHelper.splitTags(tags); + + final Set uniqueSpecs = new LinkedHashSet<>(); + for (String tag : tagArray) { + uniqueSpecs.addAll(registry.getToolsByTag(tag)); + } + final List toolCallbacks = uniqueSpecs.stream() + .map(AiToolSpecToSpringAi::toToolCallback) .collect(Collectors.toList()); - LOG.debug("Discovered {} unique tools for tags: {}", toolCallbacks.size(), tags); + LOG.debug("Discovered {} tools from AiToolRegistry for tags: {}", toolCallbacks.size(), tags); return toolCallbacks; } diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsDiscoveryTest.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsDiscoveryTest.java index 298529b1eaf3f..16c693ef8e787 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsDiscoveryTest.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsDiscoveryTest.java @@ -16,60 +16,53 @@ */ package org.apache.camel.component.springai.chat; +import java.util.concurrent.TimeUnit; + import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.springai.tools.spec.CamelToolExecutorCache; +import org.apache.camel.component.ai.tool.AiToolRegistry; import org.apache.camel.test.junit6.CamelTestSupport; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; /** * Unit test for tool discovery mechanism in camel-spring-ai-chat component. * - * This test verifies that tools are properly registered in the cache when defined using the spring-ai-tools component. + * This test verifies that tools are properly registered in the AiToolRegistry when defined using the ai-tool component. */ public class SpringAiChatToolsDiscoveryTest extends CamelTestSupport { - @AfterEach - public void cleanupToolCache() { - // Clean up the tool cache after each test - CamelToolExecutorCache.getInstance().getTools().clear(); - } - @Test - public void testToolsAreRegisteredWithCache() throws Exception { - // Wait for routes to start and tools to be registered - Thread.sleep(500); - - var toolCache = CamelToolExecutorCache.getInstance(); - var tools = toolCache.getTools(); - - // Verify that tools are registered with correct tags - assertThat(tools).isNotEmpty(); - assertThat(tools).containsKey("weather"); - assertThat(tools).containsKey("math"); - assertThat(tools).containsKey("database"); - - // Verify weather tools - assertThat(tools.get("weather")).isNotEmpty(); - assertThat(tools.get("weather")).hasSize(1); - - // Verify math tools - assertThat(tools.get("math")).isNotEmpty(); - assertThat(tools.get("math")).hasSize(1); - - // Verify database tools - assertThat(tools.get("database")).isNotEmpty(); - assertThat(tools.get("database")).hasSize(1); + public void testToolsAreRegisteredWithRegistry() throws Exception { + var registry = AiToolRegistry.getOrCreate(context); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + var tools = registry.getTools(); + + assertThat(tools).isNotEmpty(); + assertThat(tools).containsKey("weather"); + assertThat(tools).containsKey("math"); + assertThat(tools).containsKey("database"); + + assertThat(tools.get("weather")).isNotEmpty(); + assertThat(tools.get("weather")).hasSize(1); + + assertThat(tools.get("math")).isNotEmpty(); + assertThat(tools.get("math")).hasSize(1); + + assertThat(tools.get("database")).isNotEmpty(); + assertThat(tools.get("database")).hasSize(1); + }); } @Test public void testToolExecutionWithParameters() throws Exception { - // Wait for routes to start - Thread.sleep(500); + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + var registry = AiToolRegistry.getOrCreate(context); + assertThat(registry.getTools()).isNotEmpty(); + }); - // Test that a tool can be executed directly via the tool route var exchange = template().request("direct:testWeatherTool", e -> { e.getIn().setHeader("city", "Paris"); }); @@ -80,18 +73,15 @@ public void testToolExecutionWithParameters() throws Exception { } @Test - public void testMultipleToolsWithSameTag() throws Exception { - // Wait for routes to start - Thread.sleep(500); - - var toolCache = CamelToolExecutorCache.getInstance(); - var tools = toolCache.getTools(); + public void testToolExecutionWithDifferentInput() throws Exception { + var registry = AiToolRegistry.getOrCreate(context); - // Verify that the weather tag has exactly one tool - assertThat(tools.get("weather")).isNotNull(); - assertThat(tools.get("weather")).hasSize(1); + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + var tools = registry.getTools(); + assertThat(tools.get("weather")).isNotNull(); + assertThat(tools.get("weather")).hasSize(1); + }); - // Verify the tool can be executed var exchange = template().request("direct:testWeatherTool", e -> { e.getIn().setHeader("city", "London"); }); @@ -107,7 +97,7 @@ protected RouteBuilder createRouteBuilder() throws Exception { @Override public void configure() throws Exception { // Define weather tool - from("spring-ai-tools:weather?tags=weather&description=Get weather for a city") + from("ai-tool:weather?tags=weather&description=Get weather for a city") .process(exchange -> { String city = exchange.getIn().getHeader("city", String.class); String weather = "The weather in " + city + " is sunny"; @@ -115,14 +105,14 @@ public void configure() throws Exception { }); // Define calculator tool - from("spring-ai-tools:calculator?tags=math&description=Calculate expressions") + from("ai-tool:calculator?tags=math&description=Calculate expressions") .process(exchange -> { String expression = exchange.getIn().getHeader("expression", String.class); exchange.getIn().setBody("Result: " + expression); }); // Define database tool - from("spring-ai-tools:queryDb?tags=database&description=Query database") + from("ai-tool:queryDb?tags=database&description=Query database") .process(exchange -> { String query = exchange.getIn().getHeader("query", String.class); exchange.getIn().setBody("Query result: " + query); diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsIntegrationIT.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsIntegrationIT.java index 201eb62fc8f09..593809b0cc158 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsIntegrationIT.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatToolsIntegrationIT.java @@ -16,16 +16,19 @@ */ package org.apache.camel.component.springai.chat; +import java.util.concurrent.TimeUnit; + import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.ai.tool.AiToolRegistry; import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.component.springai.tools.spec.CamelToolExecutorCache; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; /** - * Integration test demonstrating interaction between camel-spring-ai-chat and camel-spring-ai-tools components. + * Integration test demonstrating interaction between camel-spring-ai-chat and ai-tool components. * * This test shows how Camel routes can be exposed as tools/functions that LLMs can call during conversations. * @@ -42,18 +45,17 @@ public class SpringAiChatToolsIntegrationIT extends OllamaTestSupport { @Test public void testToolsAreRegistered() throws Exception { - // Verify that tools are registered in the cache before testing - var toolCache = CamelToolExecutorCache.getInstance(); - var tools = toolCache.getTools(); - - // Give routes time to start and register tools - Thread.sleep(1000); - - assertThat(tools).isNotEmpty(); - assertThat(tools).containsKey("weather"); - assertThat(tools).containsKey("math"); - assertThat(tools.get("weather")).hasSize(1); - assertThat(tools.get("math")).hasSize(1); + var registry = AiToolRegistry.getOrCreate(context); + + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + var tools = registry.getTools(); + + assertThat(tools).isNotEmpty(); + assertThat(tools).containsKey("weather"); + assertThat(tools).containsKey("math"); + assertThat(tools.get("weather")).hasSize(1); + assertThat(tools.get("math")).hasSize(1); + }); } @Test @@ -190,7 +192,7 @@ public void configure() throws Exception { bindChatModel(getCamelContext()); // Define weather tool - gets weather for a specified city - from("spring-ai-tools:weather?tags=weather&description=Get current weather for a city¶meter.city=string") + from("ai-tool:weather?tags=weather&description=Get current weather for a city¶meter.city=string") .log("Weather tool called with city: ${header.city}") .process(exchange -> { String city = exchange.getIn().getHeader("city", String.class); @@ -210,7 +212,7 @@ public void configure() throws Exception { .to("mock:weather"); // Define calculator tool - performs simple calculations - from("spring-ai-tools:calculator?tags=math&description=Calculate mathematical expressions¶meter.expression=string") + from("ai-tool:calculator?tags=math&description=Calculate mathematical expressions¶meter.expression=string") .process(exchange -> { String expression = exchange.getIn().getHeader("expression", String.class); String result; diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index 88c5a3f180c4c..10c5d6951a558 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -210,6 +210,47 @@ The following 10 Oracle connector options have been removed by Debezium 3.6.0 an If you use any of these options, remove them from your configuration as they will cause errors. +=== camel-spring-ai-chat + +==== Camel route tools now use ai-tool + +The `spring-ai-chat` producer now discovers Camel route tools from the `ai-tool` component +(`AiToolRegistry`) instead of the `spring-ai-tools` component (`CamelToolExecutorCache`). + +Migrate your tool definition routes from `spring-ai-tools:` to `ai-tool:`: + +[source,java] +---- +// Before (no longer works with spring-ai-chat) +from("spring-ai-tools:weather?tags=weather&description=Get current weather for a city¶meter.city=string") + .setBody(constant("{\"city\": \"Paris\", \"temp\": \"22°C\"}")); + +// After +from("ai-tool:weather?tags=weather&description=Get current weather for a city¶meter.city=string") + .setBody(constant("{\"city\": \"Paris\", \"temp\": \"22°C\"}")); +---- + +The `spring-ai-chat` producer endpoint is unchanged — only the tool consumer routes need +to be migrated: + +[source,java] +---- +from("direct:chat") + .to("spring-ai-chat:weatherChat?tags=weather&chatModel=#chatModel"); +---- + +Add the `camel-ai-tool` dependency to your project: + +[source,xml] +---- + + org.apache.camel + camel-ai-tool + +---- + +The `camel-spring-ai-tools` dependency is no longer required by `camel-spring-ai-chat`. + === camel-fory with JDK 25+ - Breaking change Due to new requirements from Apache Fory, when using Apache Fory Dataformat, the JVM parameter `--add-opens java.base/java.lang.invoke=ALL-UNNAMED` must be provided.