diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-builtin/src/main/java/com/bytechef/component/ai/agent/chat/memory/builtin/cluster/ChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-builtin/src/main/java/com/bytechef/component/ai/agent/chat/memory/builtin/cluster/ChatMemory.java index 47a2225d3a6..c6504480449 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-builtin/src/main/java/com/bytechef/component/ai/agent/chat/memory/builtin/cluster/ChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-builtin/src/main/java/com/bytechef/component/ai/agent/chat/memory/builtin/cluster/ChatMemory.java @@ -22,6 +22,7 @@ import com.bytechef.component.definition.ComponentDsl; import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -48,6 +49,7 @@ protected static PromptChatMemoryAdvisor apply(ChatMemoryRepository chatMemoryRe .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cassandra/src/main/java/com/bytechef/component/ai/agent/chat/memory/cassandra/cluster/CassandraChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cassandra/src/main/java/com/bytechef/component/ai/agent/chat/memory/cassandra/cluster/CassandraChatMemory.java index e91c0adeddd..10cf4b74c73 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cassandra/src/main/java/com/bytechef/component/ai/agent/chat/memory/cassandra/cluster/CassandraChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cassandra/src/main/java/com/bytechef/component/ai/agent/chat/memory/cassandra/cluster/CassandraChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -55,6 +56,7 @@ protected static PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cosmosdb/src/main/java/com/bytechef/component/ai/agent/chat/memory/cosmosdb/cluster/CosmosDbChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cosmosdb/src/main/java/com/bytechef/component/ai/agent/chat/memory/cosmosdb/cluster/CosmosDbChatMemory.java index 93802309f5a..329abf43bcd 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cosmosdb/src/main/java/com/bytechef/component/ai/agent/chat/memory/cosmosdb/cluster/CosmosDbChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-cosmosdb/src/main/java/com/bytechef/component/ai/agent/chat/memory/cosmosdb/cluster/CosmosDbChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -55,6 +56,7 @@ protected static PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-in-memory/src/main/java/com/bytechef/component/ai/agent/chat/memory/memory/cluster/InMemoryChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-in-memory/src/main/java/com/bytechef/component/ai/agent/chat/memory/memory/cluster/InMemoryChatMemory.java index f35cafd4ce1..4af40d3b7a8 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-in-memory/src/main/java/com/bytechef/component/ai/agent/chat/memory/memory/cluster/InMemoryChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-in-memory/src/main/java/com/bytechef/component/ai/agent/chat/memory/memory/cluster/InMemoryChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; /** @@ -50,6 +51,7 @@ protected static PromptChatMemoryAdvisor apply( Map componentConnections) { return PromptChatMemoryAdvisor.builder(inMemoryChatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-jdbc/src/main/java/com/bytechef/component/ai/agent/chat/memory/jdbc/cluster/JdbcChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-jdbc/src/main/java/com/bytechef/component/ai/agent/chat/memory/jdbc/cluster/JdbcChatMemory.java index 9f96959ebba..ac6bf10bbcf 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-jdbc/src/main/java/com/bytechef/component/ai/agent/chat/memory/jdbc/cluster/JdbcChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-jdbc/src/main/java/com/bytechef/component/ai/agent/chat/memory/jdbc/cluster/JdbcChatMemory.java @@ -27,6 +27,7 @@ import com.bytechef.platform.component.service.ClusterElementDefinitionService; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -67,6 +68,7 @@ protected PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-mongodb/src/main/java/com/bytechef/component/ai/agent/chat/memory/mongodb/cluster/MongoDbChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-mongodb/src/main/java/com/bytechef/component/ai/agent/chat/memory/mongodb/cluster/MongoDbChatMemory.java index e108ca4fe57..9f7221a513a 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-mongodb/src/main/java/com/bytechef/component/ai/agent/chat/memory/mongodb/cluster/MongoDbChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-mongodb/src/main/java/com/bytechef/component/ai/agent/chat/memory/mongodb/cluster/MongoDbChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -52,6 +53,7 @@ protected static PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-neo4j/src/main/java/com/bytechef/component/ai/agent/chat/memory/neo4j/cluster/Neo4jChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-neo4j/src/main/java/com/bytechef/component/ai/agent/chat/memory/neo4j/cluster/Neo4jChatMemory.java index c751698da19..a76b6246781 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-neo4j/src/main/java/com/bytechef/component/ai/agent/chat/memory/neo4j/cluster/Neo4jChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-neo4j/src/main/java/com/bytechef/component/ai/agent/chat/memory/neo4j/cluster/Neo4jChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -55,6 +56,7 @@ protected static PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-redis/src/main/java/com/bytechef/component/ai/agent/chat/memory/redis/cluster/RedisChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-redis/src/main/java/com/bytechef/component/ai/agent/chat/memory/redis/cluster/RedisChatMemory.java index d8a59e84ecf..bd63b4eea0b 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-redis/src/main/java/com/bytechef/component/ai/agent/chat/memory/redis/cluster/RedisChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-redis/src/main/java/com/bytechef/component/ai/agent/chat/memory/redis/cluster/RedisChatMemory.java @@ -26,6 +26,7 @@ import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import java.util.Map; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; @@ -52,6 +53,7 @@ protected static PromptChatMemoryAdvisor apply( .build(); return PromptChatMemoryAdvisor.builder(chatMemory) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200) .build(); } diff --git a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-vectorstore/src/main/java/com/bytechef/component/ai/chat/memory/cluster/VectorStoreChatMemory.java b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-vectorstore/src/main/java/com/bytechef/component/ai/chat/memory/cluster/VectorStoreChatMemory.java index 57dd88cbf25..7e2e4557722 100644 --- a/server/libs/modules/components/ai/agent/chat-memory/chat-memory-vectorstore/src/main/java/com/bytechef/component/ai/chat/memory/cluster/VectorStoreChatMemory.java +++ b/server/libs/modules/components/ai/agent/chat-memory/chat-memory-vectorstore/src/main/java/com/bytechef/component/ai/chat/memory/cluster/VectorStoreChatMemory.java @@ -30,6 +30,7 @@ import com.bytechef.platform.configuration.domain.ClusterElement; import com.bytechef.platform.configuration.domain.ClusterElementMap; import java.util.Map; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor.Builder; @@ -82,7 +83,8 @@ protected VectorStoreChatMemoryAdvisor apply( ParametersFactory.create(componentConnectionConnectionParameters), ParametersFactory.create(clusterElement.getExtensions()), componentConnections)) .defaultTopK( - inputParameters.getInteger(CHAT_MEMORY_RETRIEVE_SIZE, 20)); + inputParameters.getInteger(CHAT_MEMORY_RETRIEVE_SIZE, 20)) + .order(BaseAdvisor.HIGHEST_PRECEDENCE + 200); return builder.build(); } diff --git a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/AiAgentComponentHandler.java b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/AiAgentComponentHandler.java index 6c39177c9fc..932d1be030a 100644 --- a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/AiAgentComponentHandler.java +++ b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/AiAgentComponentHandler.java @@ -30,6 +30,7 @@ import com.bytechef.platform.component.definition.AbstractComponentDefinitionWrapper; import com.bytechef.platform.component.definition.AiAgentComponentDefinition; import com.bytechef.platform.component.service.ClusterElementDefinitionService; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.stereotype.Component; /** @@ -41,10 +42,11 @@ public class AiAgentComponentHandler implements ComponentHandler { private final AiAgentComponentDefinition componentDefinition; public AiAgentComponentHandler( - ClusterElementDefinitionService clusterElementDefinitionService, AiAgentToolFacade aiAgentToolFacade) { + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { final ActionDefinition aiAgentChatActionDefinition = - AiAgentChatAction.of(clusterElementDefinitionService, aiAgentToolFacade); + AiAgentChatAction.of(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); this.componentDefinition = new AiAgentComponentDefinitionImpl( component(AI_AGENT) @@ -54,7 +56,7 @@ public AiAgentComponentHandler( .categories(ComponentCategory.ARTIFICIAL_INTELLIGENCE) .actions( aiAgentChatActionDefinition, - AiAgentStreamChatAction.of(clusterElementDefinitionService, aiAgentToolFacade)) + AiAgentStreamChatAction.of(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager)) .clusterElements(AiAgentChatTool.of(aiAgentChatActionDefinition))); } diff --git a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatAction.java b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatAction.java index a4575a9fc86..7e4f6b38074 100644 --- a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatAction.java +++ b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatAction.java @@ -63,11 +63,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.ToolCallAdvisor; import org.springframework.ai.chat.client.advisor.api.Advisor; +import org.springframework.ai.chat.client.advisor.api.BaseAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.augment.AugmentedToolCallbackProvider; import org.springframework.ai.tool.definition.ToolDefinition; @@ -83,12 +86,15 @@ public abstract class AbstractAiAgentChatAction { private final ClusterElementDefinitionService clusterElementDefinitionService; private final AiAgentToolFacade aiAgentToolFacade; + private final ToolCallingManager toolCallingManager; protected AbstractAiAgentChatAction( - ClusterElementDefinitionService clusterElementDefinitionService, AiAgentToolFacade aiAgentToolFacade) { + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { - this.clusterElementDefinitionService = clusterElementDefinitionService; this.aiAgentToolFacade = aiAgentToolFacade; + this.clusterElementDefinitionService = clusterElementDefinitionService; + this.toolCallingManager = toolCallingManager; } protected ChatClient.ChatClientRequestSpec getChatClientRequestSpec( @@ -143,7 +149,7 @@ private static ChatClient.ChatClientRequestSpec createPrompt( return chatClient.prompt(converter.getFormat()); } - private List getAdvisors( + List getAdvisors( ClusterElementMap clusterElementMap, Map connectionParameters, ActionContext context) { @@ -155,11 +161,25 @@ private List getAdvisors( .map(clusterElement -> getGuardrailsAdvisor(connectionParameters, clusterElement)) .ifPresent(advisors::add); + // tool call + + ToolCallAdvisor.Builder toolCallAdvisorBuilder = ToolCallAdvisor.builder() + .toolCallingManager(toolCallingManager) + .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300); + // memory - clusterElementMap.fetchClusterElement(CHAT_MEMORY) + Advisor chatMemoryAdvisor = clusterElementMap.fetchClusterElement(CHAT_MEMORY) .map(clusterElement -> getChatMemoryAdvisor(connectionParameters, clusterElement)) - .ifPresent(advisors::add); + .orElse(null); + + if (chatMemoryAdvisor != null) { + advisors.add(chatMemoryAdvisor); + + toolCallAdvisorBuilder.disableInternalConversationHistory(); + } + + advisors.add(toolCallAdvisorBuilder.build()); // RAG diff --git a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentChatAction.java b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentChatAction.java index c0b722ce522..4f65a35be3b 100644 --- a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentChatAction.java +++ b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentChatAction.java @@ -42,6 +42,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient.ChatClientRequestSpec; +import org.springframework.ai.model.tool.ToolCallingManager; /** * @author Ivica Cardic @@ -49,14 +50,17 @@ public class AiAgentChatAction extends AbstractAiAgentChatAction { public static ChatActionDefinitionWrapper of( - ClusterElementDefinitionService clusterElementDefinitionService, AiAgentToolFacade aiAgentToolFacade) { + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { - return new AiAgentChatAction(clusterElementDefinitionService, aiAgentToolFacade).build(); + return new AiAgentChatAction(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager).build(); } - private AiAgentChatAction(ClusterElementDefinitionService clusterElementDefinitionService, - AiAgentToolFacade aiAgentToolFacade) { - super(clusterElementDefinitionService, aiAgentToolFacade); + private AiAgentChatAction( + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { + + super(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); } private ChatActionDefinitionWrapper build() { diff --git a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentStreamChatAction.java b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentStreamChatAction.java index dcd2534548c..463093eea16 100644 --- a/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentStreamChatAction.java +++ b/server/libs/modules/components/ai/agent/src/main/java/com/bytechef/component/ai/agent/action/AiAgentStreamChatAction.java @@ -45,6 +45,7 @@ import org.springframework.ai.chat.client.ChatClient.ChatClientRequestSpec; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.model.tool.ToolCallingManager; import reactor.core.publisher.Flux; /** @@ -53,15 +54,18 @@ public class AiAgentStreamChatAction extends AbstractAiAgentChatAction { public static ActionDefinition of( - ClusterElementDefinitionService clusterElementDefinitionService, AiAgentToolFacade aiAgentToolFacade) { + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { - return new AiAgentStreamChatAction(clusterElementDefinitionService, aiAgentToolFacade).build(); + return new AiAgentStreamChatAction(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager) + .build(); } private AiAgentStreamChatAction( - ClusterElementDefinitionService clusterElementDefinitionService, AiAgentToolFacade aiAgentToolFacade) { + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { - super(clusterElementDefinitionService, aiAgentToolFacade); + super(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); } private ChatActionDefinitionWrapper build() { diff --git a/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatActionTest.java b/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatActionTest.java index 05061b35b74..50e3531afd8 100644 --- a/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatActionTest.java +++ b/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/ai/agent/action/AbstractAiAgentChatActionTest.java @@ -16,6 +16,7 @@ package com.bytechef.component.ai.agent.action; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -30,8 +31,11 @@ import com.bytechef.component.definition.Parameters; import com.bytechef.component.test.definition.MockParametersFactory; import com.bytechef.platform.component.ComponentConnection; +import com.bytechef.platform.component.definition.ai.agent.ChatMemoryFunction; import com.bytechef.platform.component.definition.ai.agent.ModelFunction; import com.bytechef.platform.component.service.ClusterElementDefinitionService; +import com.bytechef.platform.configuration.domain.ClusterElementMap; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,7 +44,11 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.ai.chat.client.advisor.ToolCallAdvisor; +import org.springframework.ai.chat.client.advisor.api.Advisor; +import org.springframework.ai.chat.client.advisor.api.BaseChatMemoryAdvisor; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.model.tool.ToolCallingManager; /** * @author Ivica Cardic @@ -48,11 +56,14 @@ @ExtendWith(MockitoExtension.class) class AbstractAiAgentChatActionTest { + @Mock + private AiAgentToolFacade aiAgentToolFacade; + @Mock private ClusterElementDefinitionService clusterElementDefinitionService; @Mock - private AiAgentToolFacade aiAgentToolFacade; + private ToolCallingManager toolCallingManager; @Test void testGetChatClientRequestSpecWithNullParameterValues() throws Exception { @@ -92,7 +103,8 @@ void testGetChatClientRequestSpecWithNullParameterValues() throws Exception { ActionContext actionContext = mock(ActionContext.class); - TestAiAgentChatAction action = new TestAiAgentChatAction(clusterElementDefinitionService, aiAgentToolFacade); + TestAiAgentChatAction action = new TestAiAgentChatAction( + aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); try (MockedStatic modelUtilsMockedStatic = mockStatic(ModelUtils.class)) { modelUtilsMockedStatic.when(() -> ModelUtils.getMessages(any(), any())) @@ -103,11 +115,106 @@ void testGetChatClientRequestSpecWithNullParameterValues() throws Exception { } } + @Test + void testGetAdvisorsIncludesToolCallAdvisorWithDefaultConversationHistoryWhenNoChatMemory() { + ClusterElementMap clusterElementMap = ClusterElementMap.of( + Map.of("clusterElements", Map.of("model", buildModelClusterElement()))); + + ActionContext actionContext = mock(ActionContext.class); + + TestAiAgentChatAction action = new TestAiAgentChatAction( + aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); + + List advisors = action.getAdvisors(clusterElementMap, Map.of(), actionContext); + + ToolCallAdvisor toolCallAdvisor = findToolCallAdvisor(advisors); + + assertThat(toolCallAdvisor).isNotNull(); + assertThat(advisors).noneMatch(BaseChatMemoryAdvisor.class::isInstance); + assertThat(readConversationHistoryEnabled(toolCallAdvisor)).isTrue(); + } + + @Test + void testGetAdvisorsAddsChatMemoryBeforeToolCallAdvisorAndDisablesInternalConversationHistory() throws Exception { + Map chatMemoryElement = new HashMap<>(); + + chatMemoryElement.put("name", "memory_1"); + chatMemoryElement.put("type", "memoryComponent/v1/memoryElement"); + chatMemoryElement.put("parameters", Map.of()); + + ClusterElementMap clusterElementMap = ClusterElementMap.of( + Map.of( + "clusterElements", + Map.of("model", buildModelClusterElement(), "chatMemory", chatMemoryElement))); + + BaseChatMemoryAdvisor chatMemoryAdvisor = mock(BaseChatMemoryAdvisor.class); + + ChatMemoryFunction chatMemoryFunction = mock(ChatMemoryFunction.class); + + when(chatMemoryFunction.apply(any(), any(), any(), any())).thenReturn(chatMemoryAdvisor); + when(clusterElementDefinitionService.getClusterElement( + eq("memoryComponent"), eq(1), eq("memoryElement"))).thenReturn(chatMemoryFunction); + + ComponentConnection memoryConnection = new ComponentConnection( + "memoryComponent", 1, 2L, Map.of(), null); + + Map connectionParameters = Map.of("memory_1", memoryConnection); + ActionContext actionContext = mock(ActionContext.class); + + TestAiAgentChatAction action = new TestAiAgentChatAction( + aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); + + List advisors = action.getAdvisors(clusterElementMap, connectionParameters, actionContext); + + int chatMemoryIndex = advisors.indexOf(chatMemoryAdvisor); + ToolCallAdvisor toolCallAdvisor = findToolCallAdvisor(advisors); + int toolCallIndex = advisors.indexOf(toolCallAdvisor); + + assertThat(chatMemoryIndex).isGreaterThanOrEqualTo(0); + assertThat(toolCallIndex).isGreaterThan(chatMemoryIndex); + assertThat(readConversationHistoryEnabled(toolCallAdvisor)).isFalse(); + } + + private static Map buildModelClusterElement() { + Map modelElement = new HashMap<>(); + + modelElement.put("name", "model_1"); + modelElement.put("type", "testComponent/v1/testModel"); + modelElement.put("parameters", Map.of()); + + return modelElement; + } + + private static ToolCallAdvisor findToolCallAdvisor(List advisors) { + return advisors.stream() + .filter(ToolCallAdvisor.class::isInstance) + .map(ToolCallAdvisor.class::cast) + .findFirst() + .orElseThrow(() -> new AssertionError("Expected ToolCallAdvisor in advisor list")); + } + + private static boolean readConversationHistoryEnabled(ToolCallAdvisor toolCallAdvisor) { + try { + Field field = ToolCallAdvisor.class.getDeclaredField("conversationHistoryEnabled"); + + field.setAccessible(true); + + return field.getBoolean(toolCallAdvisor); + } catch (ReflectiveOperationException exception) { + throw new AssertionError( + "Unable to read conversationHistoryEnabled from ToolCallAdvisor — " + + "field name changed in Spring AI?", + exception); + } + } + private static class TestAiAgentChatAction extends AbstractAiAgentChatAction { - TestAiAgentChatAction(ClusterElementDefinitionService clusterElementDefinitionService, - AiAgentToolFacade aiAgentToolFacade) { - super(clusterElementDefinitionService, aiAgentToolFacade); + TestAiAgentChatAction( + AiAgentToolFacade aiAgentToolFacade, ClusterElementDefinitionService clusterElementDefinitionService, + ToolCallingManager toolCallingManager) { + + super(aiAgentToolFacade, clusterElementDefinitionService, toolCallingManager); } } } diff --git a/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/chat/AiAgentComponentHandlerTest.java b/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/chat/AiAgentComponentHandlerTest.java index 47dcf59d080..89847b00f15 100644 --- a/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/chat/AiAgentComponentHandlerTest.java +++ b/server/libs/modules/components/ai/agent/src/test/java/com/bytechef/component/chat/AiAgentComponentHandlerTest.java @@ -25,6 +25,6 @@ public class AiAgentComponentHandlerTest { @Test public void testGetComponentDefinition() { JsonFileAssert.assertEquals( - "definition/ai-agent_v1.json", new AiAgentComponentHandler(null, null).getDefinition()); + "definition/ai-agent_v1.json", new AiAgentComponentHandler(null, null, null).getDefinition()); } }