diff --git a/spring-ai-modules/spring-ai-4/pom.xml b/spring-ai-modules/spring-ai-4/pom.xml index b9de3150886d..f0747d16b5e9 100644 --- a/spring-ai-modules/spring-ai-4/pom.xml +++ b/spring-ai-modules/spring-ai-4/pom.xml @@ -28,6 +28,16 @@ + + org.springaicommunity + tool-search-tool + ${tool-search-tool.version} + + + org.springaicommunity + tool-searcher-regex + ${tool-search-tool.version} + org.springframework.boot spring-boot-starter-web @@ -53,7 +63,6 @@ hsqldb runtime - org.springframework.boot @@ -103,6 +112,7 @@ 5.9.0 3.5.0 1.1.3-SNAPSHOT + 1.0.1 @@ -120,6 +130,14 @@ Spring Milestones https://repo.spring.io/milestone + + + central-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + true + + diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/Application.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/Application.java new file mode 100644 index 000000000000..0f81d4a371bf --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/Application.java @@ -0,0 +1,19 @@ +package com.baeldung.springai.toolsearchtool; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(exclude = { + org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiEmbeddingConnectionAutoConfiguration.class, + org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiMultiModalEmbeddingAutoConfiguration.class, + org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiTextEmbeddingAutoConfiguration.class, + org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration.class, +}) +public class Application { + + public static void main(String[] args) { + SpringApplication app = new SpringApplication(Application.class); + app.setAdditionalProfiles("toolsearchtool"); + app.run(args); + } +} diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightOption.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightOption.java new file mode 100644 index 000000000000..ce127de8e701 --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightOption.java @@ -0,0 +1,4 @@ +package com.baeldung.springai.toolsearchtool; + +public record FlightOption(String airlines, String from, String to, String departureDate, double price) { +} diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightTools.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightTools.java new file mode 100644 index 000000000000..01119a1cc784 --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/FlightTools.java @@ -0,0 +1,21 @@ +package com.baeldung.springai.toolsearchtool; + +import org.springframework.ai.tool.annotation.Tool; + +import java.util.List; + +public class FlightTools { + @Tool(description = "Searches available flights between two cities") + public List searchFlights(String from, String to, String departureDate) { + + return List.of( + new FlightOption( + "Romania Airlines", + from, + to, + departureDate, + 249.99 + ) + ); + } +} diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/RandomTools.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/RandomTools.java new file mode 100644 index 000000000000..2e5cb4a2ecd6 --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/RandomTools.java @@ -0,0 +1,306 @@ +package com.baeldung.springai.toolsearchtool; + +import org.springframework.ai.tool.annotation.Tool; + +public class RandomTools { + @Tool(description = "Tool 1 returns a simple text message") + public String tool1() { return "Result from tool 1"; } + + @Tool(description = "Tool 2 returns a simple text message") + public String tool2() { return "Result from tool 2"; } + + @Tool(description = "Tool 3 returns a simple text message") + public String tool3() { return "Result from tool 3"; } + + @Tool(description = "Tool 4 returns a simple text message") + public String tool4() { return "Result from tool 4"; } + + @Tool(description = "Tool 5 returns a simple text message") + public String tool5() { return "Result from tool 5"; } + + @Tool(description = "Tool 6 returns a simple text message") + public String tool6() { return "Result from tool 6"; } + + @Tool(description = "Tool 7 returns a simple text message") + public String tool7() { return "Result from tool 7"; } + + @Tool(description = "Tool 8 returns a simple text message") + public String tool8() { return "Result from tool 8"; } + + @Tool(description = "Tool 9 returns a simple text message") + public String tool9() { return "Result from tool 9"; } + + @Tool(description = "Tool 10 returns a simple text message") + public String tool10() { return "Result from tool 10"; } + + @Tool(description = "Tool 11 returns a simple text message") + public String tool11() { return "Result from tool 11"; } + + @Tool(description = "Tool 12 returns a simple text message") + public String tool12() { return "Result from tool 12"; } + + @Tool(description = "Tool 13 returns a simple text message") + public String tool13() { return "Result from tool 13"; } + + @Tool(description = "Tool 14 returns a simple text message") + public String tool14() { return "Result from tool 14"; } + + @Tool(description = "Tool 15 returns a simple text message") + public String tool15() { return "Result from tool 15"; } + + @Tool(description = "Tool 16 returns a simple text message") + public String tool16() { return "Result from tool 16"; } + + @Tool(description = "Tool 17 returns a simple text message") + public String tool17() { return "Result from tool 17"; } + + @Tool(description = "Tool 18 returns a simple text message") + public String tool18() { return "Result from tool 18"; } + + @Tool(description = "Tool 19 returns a simple text message") + public String tool19() { return "Result from tool 19"; } + + @Tool(description = "Tool 20 returns a simple text message") + public String tool20() { return "Result from tool 20"; } + + @Tool(description = "Tool 21 returns a simple text message") + public String tool21() { return "Result from tool 21"; } + + @Tool(description = "Tool 22 returns a simple text message") + public String tool22() { return "Result from tool 22"; } + + @Tool(description = "Tool 23 returns a simple text message") + public String tool23() { return "Result from tool 23"; } + + @Tool(description = "Tool 24 returns a simple text message") + public String tool24() { return "Result from tool 24"; } + + @Tool(description = "Tool 25 returns a simple text message") + public String tool25() { return "Result from tool 25"; } + + @Tool(description = "Tool 26 returns a simple text message") + public String tool26() { return "Result from tool 26"; } + + @Tool(description = "Tool 27 returns a simple text message") + public String tool27() { return "Result from tool 27"; } + + @Tool(description = "Tool 28 returns a simple text message") + public String tool28() { return "Result from tool 28"; } + + @Tool(description = "Tool 29 returns a simple text message") + public String tool29() { return "Result from tool 29"; } + + @Tool(description = "Tool 30 returns a simple text message") + public String tool30() { return "Result from tool 30"; } + + @Tool(description = "Tool 31 returns a simple text message") + public String tool31() { return "Result from tool 31"; } + + @Tool(description = "Tool 32 returns a simple text message") + public String tool32() { return "Result from tool 32"; } + + @Tool(description = "Tool 33 returns a simple text message") + public String tool33() { return "Result from tool 33"; } + + @Tool(description = "Tool 34 returns a simple text message") + public String tool34() { return "Result from tool 34"; } + + @Tool(description = "Tool 35 returns a simple text message") + public String tool35() { return "Result from tool 35"; } + + @Tool(description = "Tool 36 returns a simple text message") + public String tool36() { return "Result from tool 36"; } + + @Tool(description = "Tool 37 returns a simple text message") + public String tool37() { return "Result from tool 37"; } + + @Tool(description = "Tool 38 returns a simple text message") + public String tool38() { return "Result from tool 38"; } + + @Tool(description = "Tool 39 returns a simple text message") + public String tool39() { return "Result from tool 39"; } + + @Tool(description = "Tool 40 returns a simple text message") + public String tool40() { return "Result from tool 40"; } + + @Tool(description = "Tool 41 returns a simple text message") + public String tool41() { return "Result from tool 41"; } + + @Tool(description = "Tool 42 returns a simple text message") + public String tool42() { return "Result from tool 42"; } + + @Tool(description = "Tool 43 returns a simple text message") + public String tool43() { return "Result from tool 43"; } + + @Tool(description = "Tool 44 returns a simple text message") + public String tool44() { return "Result from tool 44"; } + + @Tool(description = "Tool 45 returns a simple text message") + public String tool45() { return "Result from tool 45"; } + + @Tool(description = "Tool 46 returns a simple text message") + public String tool46() { return "Result from tool 46"; } + + @Tool(description = "Tool 47 returns a simple text message") + public String tool47() { return "Result from tool 47"; } + + @Tool(description = "Tool 48 returns a simple text message") + public String tool48() { return "Result from tool 48"; } + + @Tool(description = "Tool 49 returns a simple text message") + public String tool49() { return "Result from tool 49"; } + + @Tool(description = "Tool 50 returns a simple text message") + public String tool50() { return "Result from tool 50"; } + + @Tool(description = "Tool 51 returns a simple text message") + public String tool51() { return "Result from tool 51"; } + + @Tool(description = "Tool 52 returns a simple text message") + public String tool52() { return "Result from tool 52"; } + + @Tool(description = "Tool 53 returns a simple text message") + public String tool53() { return "Result from tool 53"; } + + @Tool(description = "Tool 54 returns a simple text message") + public String tool54() { return "Result from tool 54"; } + + @Tool(description = "Tool 55 returns a simple text message") + public String tool55() { return "Result from tool 55"; } + + @Tool(description = "Tool 56 returns a simple text message") + public String tool56() { return "Result from tool 56"; } + + @Tool(description = "Tool 57 returns a simple text message") + public String tool57() { return "Result from tool 57"; } + + @Tool(description = "Tool 58 returns a simple text message") + public String tool58() { return "Result from tool 58"; } + + @Tool(description = "Tool 59 returns a simple text message") + public String tool59() { return "Result from tool 59"; } + + @Tool(description = "Tool 60 returns a simple text message") + public String tool60() { return "Result from tool 60"; } + + @Tool(description = "Tool 61 returns a simple text message") + public String tool61() { return "Result from tool 61"; } + + @Tool(description = "Tool 62 returns a simple text message") + public String tool62() { return "Result from tool 62"; } + + @Tool(description = "Tool 63 returns a simple text message") + public String tool63() { return "Result from tool 63"; } + + @Tool(description = "Tool 64 returns a simple text message") + public String tool64() { return "Result from tool 64"; } + + @Tool(description = "Tool 65 returns a simple text message") + public String tool65() { return "Result from tool 65"; } + + @Tool(description = "Tool 66 returns a simple text message") + public String tool66() { return "Result from tool 66"; } + + @Tool(description = "Tool 67 returns a simple text message") + public String tool67() { return "Result from tool 67"; } + + @Tool(description = "Tool 68 returns a simple text message") + public String tool68() { return "Result from tool 68"; } + + @Tool(description = "Tool 69 returns a simple text message") + public String tool69() { return "Result from tool 69"; } + + @Tool(description = "Tool 70 returns a simple text message") + public String tool70() { return "Result from tool 70"; } + + @Tool(description = "Tool 71 returns a simple text message") + public String tool71() { return "Result from tool 71"; } + + @Tool(description = "Tool 72 returns a simple text message") + public String tool72() { return "Result from tool 72"; } + + @Tool(description = "Tool 73 returns a simple text message") + public String tool73() { return "Result from tool 73"; } + + @Tool(description = "Tool 74 returns a simple text message") + public String tool74() { return "Result from tool 74"; } + + @Tool(description = "Tool 75 returns a simple text message") + public String tool75() { return "Result from tool 75"; } + + @Tool(description = "Tool 76 returns a simple text message") + public String tool76() { return "Result from tool 76"; } + + @Tool(description = "Tool 77 returns a simple text message") + public String tool77() { return "Result from tool 77"; } + + @Tool(description = "Tool 78 returns a simple text message") + public String tool78() { return "Result from tool 78"; } + + @Tool(description = "Tool 79 returns a simple text message") + public String tool79() { return "Result from tool 79"; } + + @Tool(description = "Tool 80 returns a simple text message") + public String tool80() { return "Result from tool 80"; } + + @Tool(description = "Tool 81 returns a simple text message") + public String tool81() { return "Result from tool 81"; } + + @Tool(description = "Tool 82 returns a simple text message") + public String tool82() { return "Result from tool 82"; } + + @Tool(description = "Tool 83 returns a simple text message") + public String tool83() { return "Result from tool 83"; } + + @Tool(description = "Tool 84 returns a simple text message") + public String tool84() { return "Result from tool 84"; } + + @Tool(description = "Tool 85 returns a simple text message") + public String tool85() { return "Result from tool 85"; } + + @Tool(description = "Tool 86 returns a simple text message") + public String tool86() { return "Result from tool 86"; } + + @Tool(description = "Tool 87 returns a simple text message") + public String tool87() { return "Result from tool 87"; } + + @Tool(description = "Tool 88 returns a simple text message") + public String tool88() { return "Result from tool 88"; } + + @Tool(description = "Tool 89 returns a simple text message") + public String tool89() { return "Result from tool 89"; } + + @Tool(description = "Tool 90 returns a simple text message") + public String tool90() { return "Result from tool 90"; } + + @Tool(description = "Tool 91 returns a simple text message") + public String tool91() { return "Result from tool 91"; } + + @Tool(description = "Tool 92 returns a simple text message") + public String tool92() { return "Result from tool 92"; } + + @Tool(description = "Tool 93 returns a simple text message") + public String tool93() { return "Result from tool 93"; } + + @Tool(description = "Tool 94 returns a simple text message") + public String tool94() { return "Result from tool 94"; } + + @Tool(description = "Tool 95 returns a simple text message") + public String tool95() { return "Result from tool 95"; } + + @Tool(description = "Tool 96 returns a simple text message") + public String tool96() { return "Result from tool 96"; } + + @Tool(description = "Tool 97 returns a simple text message") + public String tool97() { return "Result from tool 97"; } + + @Tool(description = "Tool 98 returns a simple text message") + public String tool98() { return "Result from tool 98"; } + + @Tool(description = "Tool 99 returns a simple text message") + public String tool99() { return "Result from tool 99"; } + + @Tool(description = "Tool 100 returns a simple text message") + public String tool100() { return "Result from tool 100"; } + +} diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TokenCounterAdvisor.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TokenCounterAdvisor.java new file mode 100644 index 000000000000..4ee5dea6d148 --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TokenCounterAdvisor.java @@ -0,0 +1,43 @@ +package com.baeldung.springai.toolsearchtool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClientRequest; +import org.springframework.ai.chat.client.ChatClientResponse; +import org.springframework.ai.chat.client.advisor.api.AdvisorChain; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; +import org.springframework.core.Ordered; + +import java.util.concurrent.atomic.AtomicInteger; + +public class TokenCounterAdvisor implements BaseAdvisor { + private static final Logger log = LoggerFactory.getLogger(TokenCounterAdvisor.class); + + private final AtomicInteger totalTokenCounter = new AtomicInteger(0); + + @Override + public String getName() { + return "TokenCounterAdvisor"; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 1; + } + + @Override + public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { + return chatClientRequest; + } + + @Override + public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) { + var usage = chatClientResponse.chatResponse().getMetadata().getUsage(); + + totalTokenCounter.addAndGet(usage.getTotalTokens()); + + log.info("Total tokens spent: {}", totalTokenCounter.get()); + + return chatClientResponse; + } +} diff --git a/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TravelAssistantConfig.java b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TravelAssistantConfig.java new file mode 100644 index 000000000000..db70317c024f --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/java/com/baeldung/springai/toolsearchtool/TravelAssistantConfig.java @@ -0,0 +1,48 @@ +package com.baeldung.springai.toolsearchtool; + +import org.springaicommunity.tool.search.ToolSearchToolCallAdvisor; +import org.springaicommunity.tool.search.ToolSearcher; +import org.springaicommunity.tool.searcher.RegexToolSearcher; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TravelAssistantConfig { + + @Bean + ToolSearcher toolSearcher() { + return new RegexToolSearcher(); + } + + @Bean + ToolSearchToolCallAdvisor toolSearchToolCallAdvisor(ToolSearcher toolSearcher) { + return ToolSearchToolCallAdvisor.builder() + .toolSearcher(toolSearcher) + .maxResults(5) + .build(); + } + + @Bean + ChatClient chatClient(ToolSearchToolCallAdvisor toolSearchToolCallAdvisor, OpenAiChatModel model) { + return ChatClient.builder(model) + .defaultTools( + new FlightTools(), + new RandomTools() + ) + .defaultAdvisors(toolSearchToolCallAdvisor, new TokenCounterAdvisor()) + .build(); + } + + @Bean + ChatClient chatClientWithoutToolsSearch(OpenAiChatModel model) { + return ChatClient.builder(model) + .defaultTools( + new FlightTools(), + new RandomTools() + ) + .defaultAdvisors(new TokenCounterAdvisor()) + .build(); + } +} diff --git a/spring-ai-modules/spring-ai-4/src/main/resources/application-toolsearchtool.yml b/spring-ai-modules/spring-ai-4/src/main/resources/application-toolsearchtool.yml new file mode 100644 index 000000000000..6f101357b59b --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/main/resources/application-toolsearchtool.yml @@ -0,0 +1,4 @@ +spring: + ai: + openai: + api-key: ${OPEN_API_KEY} diff --git a/spring-ai-modules/spring-ai-4/src/test/java/com/baeldung/springai/toolsearchtool/ToolsSearchToolLiveTest.java b/spring-ai-modules/spring-ai-4/src/test/java/com/baeldung/springai/toolsearchtool/ToolsSearchToolLiveTest.java new file mode 100644 index 000000000000..def031ae7712 --- /dev/null +++ b/spring-ai-modules/spring-ai-4/src/test/java/com/baeldung/springai/toolsearchtool/ToolsSearchToolLiveTest.java @@ -0,0 +1,46 @@ +package com.baeldung.springai.toolsearchtool; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("toolsearchtool") +class ToolsSearchToolLiveTest { + @Autowired + private ChatClient chatClient; + + @Autowired + private ChatClient chatClientWithoutToolsSearch; + + @Test + void shouldFindFlightsBetweenRomaniaAndCroatiaUsingToolsSearch() { + String response = getClientResponseString(chatClient); + assetClientResponse(response); + } + + @Test + void shouldFindFlightsBetweenRomaniaAndCroatiaWithoutToolsSearch() { + String response = getClientResponseString(chatClientWithoutToolsSearch); + assetClientResponse(response); + } + + private static void assetClientResponse(String response) { + assertThat(response).isNotBlank(); + assertThat(response).containsIgnoringCase("Croatia"); + assertThat(response).containsIgnoringCase("flight"); + } + + private String getClientResponseString(ChatClient chatClientWithoutToolsSearch) { + return chatClientWithoutToolsSearch.prompt() + .user(""" + Find available flights from Romania to Croatia next week. + """) + .call() + .content(); + } +} \ No newline at end of file