diff --git a/bigtop-manager-ai/pom.xml b/bigtop-manager-ai/pom.xml index 1ba22accb..6eb68ca83 100644 --- a/bigtop-manager-ai/pom.xml +++ b/bigtop-manager-ai/pom.xml @@ -48,30 +48,12 @@ bigtop-manager-dao - dev.langchain4j - langchain4j + org.springframework.ai + spring-ai-openai - dev.langchain4j - langchain4j-reactor - - - dev.langchain4j - langchain4j-open-ai - - - dev.langchain4j - langchain4j-community-qianfan - - - dev.langchain4j - langchain4j-community-dashscope - - - org.slf4j - slf4j-simple - - + org.springframework.ai + spring-ai-deepseek org.springframework.boot diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/GeneralAssistantFactory.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/GeneralAssistantFactory.java index 5bad559b4..38951c8ec 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/GeneralAssistantFactory.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/GeneralAssistantFactory.java @@ -34,8 +34,6 @@ import org.springframework.stereotype.Component; -import dev.langchain4j.service.tool.ToolProvider; - import jakarta.annotation.Resource; import java.util.ArrayList; import java.util.List; @@ -70,8 +68,7 @@ private AIAssistant.Builder initializeBuilder(PlatformType platformType) { } @Override - public AIAssistant createWithPrompt( - AIAssistantConfig config, ToolProvider toolProvider, SystemPrompt systemPrompt) { + public AIAssistant createWithPrompt(AIAssistantConfig config, Object toolProvider, SystemPrompt systemPrompt) { GeneralAssistantConfig generalAssistantConfig = (GeneralAssistantConfig) config; PlatformType platformType = generalAssistantConfig.getPlatformType(); Object id = generalAssistantConfig.getId(); @@ -81,9 +78,8 @@ public AIAssistant createWithPrompt( AIAssistant.Builder builder = initializeBuilder(platformType); builder.id(id) - .memoryStore(chatMemoryStoreProvider.createPersistentChatMemoryStore()) - .withConfig(generalAssistantConfig) - .withToolProvider(toolProvider); + .memoryStore(chatMemoryStoreProvider.createPersistentChatMemoryStore(id)) + .withConfig(generalAssistantConfig); configureSystemPrompt(builder, systemPrompt, generalAssistantConfig.getLanguage()); @@ -91,15 +87,14 @@ public AIAssistant createWithPrompt( } @Override - public AIAssistant createForTest(AIAssistantConfig config, ToolProvider toolProvider) { + public AIAssistant createForTest(AIAssistantConfig config, Object toolProvider) { GeneralAssistantConfig generalAssistantConfig = (GeneralAssistantConfig) config; PlatformType platformType = generalAssistantConfig.getPlatformType(); AIAssistant.Builder builder = initializeBuilder(platformType); builder.id(null) .memoryStore(chatMemoryStoreProvider.createInMemoryChatMemoryStore()) - .withConfig(generalAssistantConfig) - .withToolProvider(toolProvider); + .withConfig(generalAssistantConfig); return builder.build(); } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/provider/ChatMemoryStoreProvider.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/provider/ChatMemoryStoreProvider.java index 67003f10e..6e4a73aad 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/provider/ChatMemoryStoreProvider.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/provider/ChatMemoryStoreProvider.java @@ -22,11 +22,11 @@ import org.apache.bigtop.manager.dao.repository.ChatMessageDao; import org.apache.bigtop.manager.dao.repository.ChatThreadDao; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.stereotype.Component; -import dev.langchain4j.store.memory.chat.ChatMemoryStore; -import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore; - import jakarta.annotation.Resource; @Component @@ -37,11 +37,17 @@ public class ChatMemoryStoreProvider { @Resource private ChatMessageDao chatMessageDao; - public ChatMemoryStore createPersistentChatMemoryStore() { - return new PersistentChatMemoryStore(chatThreadDao, chatMessageDao); + public ChatMemory createPersistentChatMemoryStore(Object conversationId) { + PersistentChatMemoryStore repository = + new PersistentChatMemoryStore((Long) conversationId, chatThreadDao, chatMessageDao); + return MessageWindowChatMemory.builder() + .chatMemoryRepository(repository) + .build(); } - public ChatMemoryStore createInMemoryChatMemoryStore() { - return new InMemoryChatMemoryStore(); + public ChatMemory createInMemoryChatMemoryStore() { + return MessageWindowChatMemory.builder() + .chatMemoryRepository(new InMemoryChatMemoryRepository()) + .build(); } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/store/PersistentChatMemoryStore.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/store/PersistentChatMemoryStore.java index 9349d4adc..aaf6f6e7f 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/store/PersistentChatMemoryStore.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/store/PersistentChatMemoryStore.java @@ -24,12 +24,12 @@ import org.apache.bigtop.manager.dao.repository.ChatMessageDao; import org.apache.bigtop.manager.dao.repository.ChatThreadDao; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.ChatMessageType; -import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import org.springframework.ai.chat.memory.ChatMemoryRepository; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; + import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; @@ -38,41 +38,49 @@ import java.util.stream.Collectors; @Slf4j -public class PersistentChatMemoryStore implements ChatMemoryStore { +public class PersistentChatMemoryStore implements ChatMemoryRepository { - private final List messagesInMemory = new ArrayList<>(); + private final List messagesInMemory = new ArrayList<>(); private final ChatThreadDao chatThreadDao; private final ChatMessageDao chatMessageDao; + private final Long conversationId; - public PersistentChatMemoryStore(ChatThreadDao chatThreadDao, ChatMessageDao chatMessageDao) { + public PersistentChatMemoryStore(Long conversationId, ChatThreadDao chatThreadDao, ChatMessageDao chatMessageDao) { + this.conversationId = conversationId; this.chatThreadDao = chatThreadDao; this.chatMessageDao = chatMessageDao; } - private ChatMessage convertToChatMessage(ChatMessagePO chatMessagePO) { + private Message convertToChatMessage(ChatMessagePO chatMessagePO) { String sender = chatMessagePO.getSender().toLowerCase(); if (sender.equals(MessageType.AI.getValue())) { - return new AiMessage(chatMessagePO.getMessage()); + return new AssistantMessage(chatMessagePO.getMessage()); } else if (sender.equals(MessageType.USER.getValue())) { return new UserMessage(chatMessagePO.getMessage()); + } else if (sender.equals(MessageType.SYSTEM.getValue())) { + return new SystemMessage(chatMessagePO.getMessage()); } else { return null; } } - private ChatMessagePO convertToChatMessagePO(ChatMessage chatMessage, Long chatThreadId) { + private ChatMessagePO convertToChatMessagePO(Message message, Long chatThreadId) { ChatMessagePO chatMessagePO = new ChatMessagePO(); - if (chatMessage.type().equals(ChatMessageType.AI)) { + if (message.getMessageType() == org.springframework.ai.chat.messages.MessageType.ASSISTANT) { chatMessagePO.setSender(MessageType.AI.getValue()); - AiMessage aiMessage = (AiMessage) chatMessage; - if (aiMessage.text() == null) { + AssistantMessage assistantMessage = (AssistantMessage) message; + if (assistantMessage.getText() == null) { return null; } - chatMessagePO.setMessage(aiMessage.text()); - } else if (chatMessage.type().equals(ChatMessageType.USER)) { + chatMessagePO.setMessage(assistantMessage.getText()); + } else if (message.getMessageType() == org.springframework.ai.chat.messages.MessageType.USER) { chatMessagePO.setSender(MessageType.USER.getValue()); - UserMessage userMessage = (UserMessage) chatMessage; - chatMessagePO.setMessage(userMessage.singleText()); + UserMessage userMessage = (UserMessage) message; + chatMessagePO.setMessage(userMessage.getText()); + } else if (message.getMessageType() == org.springframework.ai.chat.messages.MessageType.SYSTEM) { + chatMessagePO.setSender(MessageType.SYSTEM.getValue()); + SystemMessage systemMessage = (SystemMessage) message; + chatMessagePO.setMessage(systemMessage.getText()); } else { return null; } @@ -82,11 +90,11 @@ private ChatMessagePO convertToChatMessagePO(ChatMessage chatMessage, Long chatT return chatMessagePO; } - private List sortMessages(List messages) { - List systemMessages = messages.stream() + private List sortMessages(List messages) { + List systemMessages = messages.stream() .filter(message -> message instanceof SystemMessage) .collect(Collectors.toList()); - List otherMessages = messages.stream() + List otherMessages = messages.stream() .filter(message -> !(message instanceof SystemMessage)) .toList(); @@ -95,9 +103,15 @@ private List sortMessages(List messages) { } @Override - public List getMessages(Object threadId) { - List chatMessages = chatMessageDao.findAllByThreadId((Long) threadId); - List allChatMessages = new ArrayList<>(); + public List findConversationIds() { + // Return the current conversation ID as a list + return List.of(String.valueOf(conversationId)); + } + + @Override + public List findByConversationId(String conversationId) { + List chatMessages = chatMessageDao.findAllByThreadId(this.conversationId); + List allChatMessages = new ArrayList<>(); if (!chatMessages.isEmpty()) { allChatMessages.addAll(chatMessages.stream() .map(this::convertToChatMessage) @@ -111,20 +125,22 @@ public List getMessages(Object threadId) { } @Override - public void updateMessages(Object threadId, List messages) { - ChatMessage newMessage = messages.get(messages.size() - 1); - ChatMessagePO chatMessagePO = convertToChatMessagePO(newMessage, (Long) threadId); - if (chatMessagePO == null) { - messagesInMemory.add(newMessage); - return; + public void saveAll(String conversationId, List messages) { + for (Message message : messages) { + ChatMessagePO chatMessagePO = convertToChatMessagePO(message, this.conversationId); + if (chatMessagePO == null) { + messagesInMemory.add(message); + continue; + } + chatMessageDao.save(chatMessagePO); } - chatMessageDao.save(chatMessagePO); } @Override - public void deleteMessages(Object threadId) { - List chatMessagePOS = chatMessageDao.findAllByThreadId((Long) threadId); + public void deleteByConversationId(String conversationId) { + List chatMessagePOS = chatMessageDao.findAllByThreadId(this.conversationId); chatMessagePOS.forEach(chatMessage -> chatMessage.setIsDeleted(true)); chatMessageDao.partialUpdateByIds(chatMessagePOS); + messagesInMemory.clear(); } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/AbstractAIAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/AbstractAIAssistant.java index 58e8268a4..30ec7ab62 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/AbstractAIAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/AbstractAIAssistant.java @@ -21,18 +21,20 @@ import org.apache.bigtop.manager.ai.core.config.AIAssistantConfig; import org.apache.bigtop.manager.ai.core.factory.AIAssistant; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.memory.chat.MessageWindowChatMemory; -import dev.langchain4j.service.tool.ToolProvider; -import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; + import reactor.core.publisher.Flux; public abstract class AbstractAIAssistant implements AIAssistant { protected final AIAssistant.Service aiServices; protected static final Integer MEMORY_LEN = 10; protected final ChatMemory chatMemory; + protected final Object memoryId; - protected AbstractAIAssistant(ChatMemory chatMemory, AIAssistant.Service aiServices) { + protected AbstractAIAssistant(Object memoryId, ChatMemory chatMemory, AIAssistant.Service aiServices) { + this.memoryId = memoryId; this.chatMemory = chatMemory; this.aiServices = aiServices; } @@ -44,7 +46,7 @@ public boolean test() { @Override public Object getId() { - return chatMemory.id(); + return memoryId; } @Override @@ -60,19 +62,13 @@ public String ask(String chatMessage) { public abstract static class Builder implements AIAssistant.Builder { protected Object id; - protected ChatMemoryStore chatMemoryStore; + protected ChatMemory chatMemory; protected AIAssistantConfig config; - protected ToolProvider toolProvider; protected String systemPrompt; public Builder() {} - public Builder withToolProvider(ToolProvider toolProvider) { - this.toolProvider = toolProvider; - return this; - } - public Builder withSystemPrompt(String systemPrompt) { this.systemPrompt = systemPrompt; return this; @@ -88,19 +84,18 @@ public Builder id(Object id) { return this; } - public Builder memoryStore(ChatMemoryStore chatMemoryStore) { - this.chatMemoryStore = chatMemoryStore; + public Builder memoryStore(ChatMemory chatMemory) { + this.chatMemory = chatMemory; return this; } - public MessageWindowChatMemory getChatMemory() { - MessageWindowChatMemory.Builder builder = MessageWindowChatMemory.builder() - .chatMemoryStore(chatMemoryStore) - .maxMessages(MEMORY_LEN); - if (id != null) { - builder.id(id); + public ChatMemory getChatMemory() { + if (chatMemory == null) { + chatMemory = MessageWindowChatMemory.builder() + .chatMemoryRepository(new InMemoryChatMemoryRepository()) + .build(); } - return builder.build(); + return chatMemory; } } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistant.java index 6092590ae..ff2b96f5c 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistant.java @@ -21,11 +21,10 @@ import org.apache.bigtop.manager.ai.core.config.AIAssistantConfig; import org.apache.bigtop.manager.ai.core.enums.PlatformType; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.service.tool.ToolProvider; -import dev.langchain4j.store.memory.chat.ChatMemoryStore; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; + import reactor.core.publisher.Flux; public interface AIAssistant { @@ -72,12 +71,10 @@ interface Service { interface Builder { Builder id(Object id); - Builder memoryStore(ChatMemoryStore memoryStore); + Builder memoryStore(ChatMemory memoryStore); Builder withConfig(AIAssistantConfig configProvider); - Builder withToolProvider(ToolProvider toolProvider); - Builder withSystemPrompt(String systemPrompt); AIAssistant build(); diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistantFactory.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistantFactory.java index d947e778f..06e1dafef 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistantFactory.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistantFactory.java @@ -21,15 +21,13 @@ import org.apache.bigtop.manager.ai.core.config.AIAssistantConfig; import org.apache.bigtop.manager.ai.core.enums.SystemPrompt; -import dev.langchain4j.service.tool.ToolProvider; - public interface AIAssistantFactory { - AIAssistant createWithPrompt(AIAssistantConfig config, ToolProvider toolProvider, SystemPrompt systemPrompt); + AIAssistant createWithPrompt(AIAssistantConfig config, Object toolProvider, SystemPrompt systemPrompt); - AIAssistant createForTest(AIAssistantConfig config, ToolProvider toolProvider); + AIAssistant createForTest(AIAssistantConfig config, Object toolProvider); - default AIAssistant createAIService(AIAssistantConfig config, ToolProvider toolProvider) { + default AIAssistant createAIService(AIAssistantConfig config, Object toolProvider) { return createWithPrompt(config, toolProvider, SystemPrompt.DEFAULT_PROMPT); } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DashScopeAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DashScopeAssistant.java index cad0a9ee2..6661e6e02 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DashScopeAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DashScopeAssistant.java @@ -22,18 +22,29 @@ import org.apache.bigtop.manager.ai.core.enums.PlatformType; import org.apache.bigtop.manager.ai.core.factory.AIAssistant; -import dev.langchain4j.community.model.dashscope.QwenChatModel; -import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel; -import dev.langchain4j.internal.ValidationUtils; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.service.AiServices; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.util.Assert; + +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; public class DashScopeAssistant extends AbstractAIAssistant { - public DashScopeAssistant(ChatMemory chatMemory, AIAssistant.Service aiServices) { - super(chatMemory, aiServices); + private static final String BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode"; + + public DashScopeAssistant(Object memoryId, ChatMemory chatMemory, AIAssistant.Service aiServices) { + super(memoryId, chatMemory, aiServices); } @Override @@ -47,39 +58,101 @@ public static Builder builder() { public static class Builder extends AbstractAIAssistant.Builder { - public AIAssistant build() { - AIAssistant.Service aiService = AiServices.builder(AIAssistant.Service.class) - .chatModel(getChatModel()) - .streamingChatModel(getStreamingChatModel()) - .chatMemory(getChatMemory()) - .toolProvider(toolProvider) - .systemMessageProvider(threadId -> { - if (threadId != null) { - return systemPrompt; - } - return null; - }) - .build(); - return new DashScopeAssistant(getChatMemory(), aiService); - } - @Override public ChatModel getChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - return QwenChatModel.builder().apiKey(apiKey).modelName(model).build(); + String model = config.getModel(); + Assert.notNull(model, "model must not be null"); + String apiKey = config.getCredentials().get("apiKey"); + Assert.notNull(apiKey, "apiKey must not be null"); + + OpenAiApi openAiApi = + OpenAiApi.builder().baseUrl(BASE_URL).apiKey(apiKey).build(); + OpenAiChatOptions options = OpenAiChatOptions.builder().model(model).build(); + return OpenAiChatModel.builder() + .openAiApi(openAiApi) + .defaultOptions(options) + .build(); } @Override public StreamingChatModel getStreamingChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - return QwenStreamingChatModel.builder() - .apiKey(apiKey) - .modelName(model) - .build(); + // In Spring AI, OpenAiChatModel handles both sync and streaming + return getChatModel(); + } + + public AIAssistant build() { + ChatModel chatModel = getChatModel(); + StreamingChatModel streamingChatModel = getStreamingChatModel(); + ChatMemory memory = getChatMemory(); + + AIAssistant.Service aiService = new AIAssistant.Service() { + @Override + public String chat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + String response = + chatModel.call(prompt).getResult().getOutput().getText(); + + // Save to memory + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage(response))); + + return response; + } + + @Override + public Flux streamChat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + + StringBuilder responseBuilder = new StringBuilder(); + return streamingChatModel.stream(prompt) + .map(chatResponse -> { + String content = + chatResponse.getResult().getOutput().getText(); + if (content != null) { + responseBuilder.append(content); + } + return content; + }) + .doOnComplete(() -> { + // Save to memory when streaming completes + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage( + responseBuilder.toString()))); + }); + } + }; + + return new DashScopeAssistant(id, memory, aiService); } } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DeepSeekAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DeepSeekAssistant.java index ed39621c2..962ad8258 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DeepSeekAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DeepSeekAssistant.java @@ -16,26 +16,34 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.bigtop.manager.ai.platform; import org.apache.bigtop.manager.ai.core.AbstractAIAssistant; import org.apache.bigtop.manager.ai.core.enums.PlatformType; import org.apache.bigtop.manager.ai.core.factory.AIAssistant; -import dev.langchain4j.internal.ValidationUtils; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; -import dev.langchain4j.model.openai.OpenAiStreamingChatModel; -import dev.langchain4j.service.AiServices; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.deepseek.DeepSeekChatModel; +import org.springframework.ai.deepseek.DeepSeekChatOptions; +import org.springframework.ai.deepseek.api.DeepSeekApi; +import org.springframework.util.Assert; -public class DeepSeekAssistant extends AbstractAIAssistant { +import reactor.core.publisher.Flux; - private static final String BASE_URL = "https://api.deepseek.com/v1"; +import java.util.ArrayList; +import java.util.List; + +public class DeepSeekAssistant extends AbstractAIAssistant { - public DeepSeekAssistant(ChatMemory chatMemory, AIAssistant.Service aiServices) { - super(chatMemory, aiServices); + public DeepSeekAssistant(Object memoryId, ChatMemory chatMemory, AIAssistant.Service aiServices) { + super(memoryId, chatMemory, aiServices); } @Override @@ -51,42 +59,99 @@ public static class Builder extends AbstractAIAssistant.Builder { @Override public ChatModel getChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - return OpenAiChatModel.builder() - .apiKey(apiKey) - .baseUrl(BASE_URL) - .modelName(model) + String model = config.getModel(); + Assert.notNull(model, "model must not be null"); + String apiKey = config.getCredentials().get("apiKey"); + Assert.notNull(apiKey, "apiKey must not be null"); + + DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build(); + DeepSeekChatOptions options = + DeepSeekChatOptions.builder().model(model).build(); + return DeepSeekChatModel.builder() + .deepSeekApi(deepSeekApi) + .defaultOptions(options) .build(); } @Override public StreamingChatModel getStreamingChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - return OpenAiStreamingChatModel.builder() - .apiKey(apiKey) - .baseUrl(BASE_URL) - .modelName(model) - .build(); + // DeepSeekChatModel handles both sync and streaming + return getChatModel(); } public AIAssistant build() { - AIAssistant.Service aiService = AiServices.builder(AIAssistant.Service.class) - .chatModel(getChatModel()) - .streamingChatModel(getStreamingChatModel()) - .chatMemory(getChatMemory()) - .toolProvider(toolProvider) - .systemMessageProvider(threadId -> { - if (threadId != null) { - return systemPrompt; - } - return null; - }) - .build(); - return new DeepSeekAssistant(getChatMemory(), aiService); + ChatModel chatModel = getChatModel(); + StreamingChatModel streamingChatModel = getStreamingChatModel(); + ChatMemory memory = getChatMemory(); + + AIAssistant.Service aiService = new AIAssistant.Service() { + @Override + public String chat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + String response = + chatModel.call(prompt).getResult().getOutput().getText(); + + // Save to memory + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage(response))); + + return response; + } + + @Override + public Flux streamChat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + + StringBuilder responseBuilder = new StringBuilder(); + return streamingChatModel.stream(prompt) + .map(chatResponse -> { + String content = + chatResponse.getResult().getOutput().getText(); + if (content != null) { + responseBuilder.append(content); + } + return content; + }) + .doOnComplete(() -> { + // Save to memory when streaming completes + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage( + responseBuilder.toString()))); + }); + } + }; + + return new DeepSeekAssistant(id, memory, aiService); } } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/OpenAIAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/OpenAIAssistant.java index ce2738ecc..b51fa75ef 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/OpenAIAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/OpenAIAssistant.java @@ -22,20 +22,29 @@ import org.apache.bigtop.manager.ai.core.enums.PlatformType; import org.apache.bigtop.manager.ai.core.factory.AIAssistant; -import dev.langchain4j.internal.ValidationUtils; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; -import dev.langchain4j.model.openai.OpenAiStreamingChatModel; -import dev.langchain4j.service.AiServices; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.util.Assert; + +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; public class OpenAIAssistant extends AbstractAIAssistant { - private static final String BASE_URL = "https://api.openai.com/v1"; + private static final String BASE_URL = "https://api.openai.com"; - public OpenAIAssistant(ChatMemory chatMemory, AIAssistant.Service aiServices) { - super(chatMemory, aiServices); + public OpenAIAssistant(Object memoryId, ChatMemory chatMemory, AIAssistant.Service aiServices) { + super(memoryId, chatMemory, aiServices); } @Override @@ -51,42 +60,99 @@ public static class Builder extends AbstractAIAssistant.Builder { @Override public ChatModel getChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); + String model = config.getModel(); + Assert.notNull(model, "model must not be null"); + String apiKey = config.getCredentials().get("apiKey"); + Assert.notNull(apiKey, "apiKey must not be null"); + + OpenAiApi openAiApi = + OpenAiApi.builder().baseUrl(BASE_URL).apiKey(apiKey).build(); + OpenAiChatOptions options = OpenAiChatOptions.builder().model(model).build(); return OpenAiChatModel.builder() - .apiKey(apiKey) - .baseUrl(BASE_URL) - .modelName(model) + .openAiApi(openAiApi) + .defaultOptions(options) .build(); } @Override public StreamingChatModel getStreamingChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - return OpenAiStreamingChatModel.builder() - .apiKey(apiKey) - .baseUrl(BASE_URL) - .modelName(model) - .build(); + // In Spring AI, OpenAiChatModel handles both sync and streaming + return getChatModel(); } public AIAssistant build() { - AIAssistant.Service aiService = AiServices.builder(AIAssistant.Service.class) - .chatModel(getChatModel()) - .streamingChatModel(getStreamingChatModel()) - .chatMemory(getChatMemory()) - .toolProvider(toolProvider) - .systemMessageProvider(threadId -> { - if (threadId != null) { - return systemPrompt; - } - return null; - }) - .build(); - return new OpenAIAssistant(getChatMemory(), aiService); + ChatModel chatModel = getChatModel(); + StreamingChatModel streamingChatModel = getStreamingChatModel(); + ChatMemory memory = getChatMemory(); + + AIAssistant.Service aiService = new AIAssistant.Service() { + @Override + public String chat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + String response = + chatModel.call(prompt).getResult().getOutput().getText(); + + // Save to memory + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage(response))); + + return response; + } + + @Override + public Flux streamChat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + + StringBuilder responseBuilder = new StringBuilder(); + return streamingChatModel.stream(prompt) + .map(chatResponse -> { + String content = + chatResponse.getResult().getOutput().getText(); + if (content != null) { + responseBuilder.append(content); + } + return content; + }) + .doOnComplete(() -> { + // Save to memory when streaming completes + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage( + responseBuilder.toString()))); + }); + } + }; + + return new OpenAIAssistant(id, memory, aiService); } } } diff --git a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/QianFanAssistant.java b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/QianFanAssistant.java index cf5db353e..468020eb1 100644 --- a/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/QianFanAssistant.java +++ b/bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/QianFanAssistant.java @@ -22,18 +22,29 @@ import org.apache.bigtop.manager.ai.core.enums.PlatformType; import org.apache.bigtop.manager.ai.core.factory.AIAssistant; -import dev.langchain4j.community.model.qianfan.QianfanChatModel; -import dev.langchain4j.community.model.qianfan.QianfanStreamingChatModel; -import dev.langchain4j.internal.ValidationUtils; -import dev.langchain4j.memory.ChatMemory; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.chat.StreamingChatModel; -import dev.langchain4j.service.AiServices; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.util.Assert; + +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; public class QianFanAssistant extends AbstractAIAssistant { - public QianFanAssistant(ChatMemory chatMemory, AIAssistant.Service aiServices) { - super(chatMemory, aiServices); + private static final String BASE_URL = "https://qianfan.baidubce.com"; + + public QianFanAssistant(Object memoryId, ChatMemory chatMemory, AIAssistant.Service aiServices) { + super(memoryId, chatMemory, aiServices); } @Override @@ -47,48 +58,103 @@ public static Builder builder() { public static class Builder extends AbstractAIAssistant.Builder { - public AIAssistant build() { - AIAssistant.Service aiService = AiServices.builder(AIAssistant.Service.class) - .chatModel(getChatModel()) - .streamingChatModel(getStreamingChatModel()) - .chatMemory(getChatMemory()) - .toolProvider(toolProvider) - .systemMessageProvider(threadId -> { - if (threadId != null) { - return systemPrompt; - } - return null; - }) - .build(); - return new QianFanAssistant(getChatMemory(), aiService); - } - @Override public ChatModel getChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - String secretKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("secretKey"), "secretKey"); - return QianfanChatModel.builder() + String model = config.getModel(); + Assert.notNull(model, "model must not be null"); + String apiKey = config.getCredentials().get("apiKey"); + Assert.notNull(apiKey, "apiKey must not be null"); + + OpenAiApi openAiApi = OpenAiApi.builder() + .baseUrl(BASE_URL) + .completionsPath("/v2/chat/completions") .apiKey(apiKey) - .secretKey(secretKey) - .modelName(model) + .build(); + OpenAiChatOptions options = OpenAiChatOptions.builder().model(model).build(); + return OpenAiChatModel.builder() + .openAiApi(openAiApi) + .defaultOptions(options) .build(); } @Override public StreamingChatModel getStreamingChatModel() { - String model = ValidationUtils.ensureNotNull(config.getModel(), "model"); - String apiKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("apiKey"), "apiKey"); - String secretKey = - ValidationUtils.ensureNotNull(config.getCredentials().get("secretKey"), "secretKey"); - return QianfanStreamingChatModel.builder() - .apiKey(apiKey) - .secretKey(secretKey) - .modelName(model) - .build(); + return getChatModel(); + } + + public AIAssistant build() { + ChatModel chatModel = getChatModel(); + StreamingChatModel streamingChatModel = getStreamingChatModel(); + ChatMemory memory = getChatMemory(); + + AIAssistant.Service aiService = new AIAssistant.Service() { + @Override + public String chat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + String response = + chatModel.call(prompt).getResult().getOutput().getText(); + + // Save to memory + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage(response))); + + return response; + } + + @Override + public Flux streamChat(String userMessage) { + List messages = new ArrayList<>(); + if (systemPrompt != null) { + messages.add(new SystemMessage(systemPrompt)); + } + // Add conversation history + String convId = String.valueOf(id); + List history = memory.get(convId); + messages.addAll(history); + // Add new user message + UserMessage newUserMessage = new UserMessage(userMessage); + messages.add(newUserMessage); + + Prompt prompt = new Prompt(messages); + + StringBuilder responseBuilder = new StringBuilder(); + return streamingChatModel.stream(prompt) + .map(chatResponse -> { + String content = + chatResponse.getResult().getOutput().getText(); + if (content != null) { + responseBuilder.append(content); + } + return content; + }) + .doOnComplete(() -> { + // Save to memory when streaming completes + memory.add( + convId, + List.of( + newUserMessage, + new org.springframework.ai.chat.messages.AssistantMessage( + responseBuilder.toString()))); + }); + } + }; + + return new QianFanAssistant(id, memory, aiService); } } } diff --git a/bigtop-manager-ai/src/test/java/assistant/GeneralAssistantFactoryTest.java b/bigtop-manager-ai/src/test/java/assistant/GeneralAssistantFactoryTest.java index 778c1ffe3..e87d2c418 100644 --- a/bigtop-manager-ai/src/test/java/assistant/GeneralAssistantFactoryTest.java +++ b/bigtop-manager-ai/src/test/java/assistant/GeneralAssistantFactoryTest.java @@ -58,7 +58,6 @@ void testCreateAIAssistant() { when(mockBuilder.id(any())).thenReturn(mockBuilder); when(mockBuilder.memoryStore(any())).thenReturn(mockBuilder); when(mockBuilder.withConfig(any())).thenReturn(mockBuilder); - when(mockBuilder.withToolProvider(any())).thenReturn(mockBuilder); when(mockBuilder.withSystemPrompt(any())).thenReturn(mockBuilder); when(mockBuilder.build()).thenReturn(mock(AIAssistant.class)); diff --git a/bigtop-manager-ai/src/test/java/assistant/store/PersistentChatMemoryStoreTest.java b/bigtop-manager-ai/src/test/java/assistant/store/PersistentChatMemoryStoreTest.java index 242bfc53b..deeeab640 100644 --- a/bigtop-manager-ai/src/test/java/assistant/store/PersistentChatMemoryStoreTest.java +++ b/bigtop-manager-ai/src/test/java/assistant/store/PersistentChatMemoryStoreTest.java @@ -27,22 +27,18 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.ChatMessageType; -import dev.langchain4j.data.message.SystemMessage; -import dev.langchain4j.data.message.UserMessage; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -54,12 +50,12 @@ class PersistentChatMemoryStoreTest { @Mock private ChatMessageDao chatMessageDao; - @InjectMocks private PersistentChatMemoryStore persistentChatMemoryStore; @BeforeEach void setUp() { - persistentChatMemoryStore = new PersistentChatMemoryStore(chatThreadDao, chatMessageDao); + Long threadId = 1L; + persistentChatMemoryStore = new PersistentChatMemoryStore(threadId, chatThreadDao, chatMessageDao); } @Test @@ -81,37 +77,34 @@ void testGetMessages() { chatMessagePOS.add(messagePO3); when(chatMessageDao.findAllByThreadId(threadId)).thenReturn(chatMessagePOS); - List result = persistentChatMemoryStore.getMessages(threadId); + List result = persistentChatMemoryStore.findByConversationId(String.valueOf(threadId)); assertEquals(2, result.size()); - assertTrue(result.get(0) instanceof AiMessage); - assertEquals("Hello from AI", ((AiMessage) result.get(0)).text()); + assertTrue(result.get(0) instanceof AssistantMessage); + assertEquals("Hello from AI", ((AssistantMessage) result.get(0)).getText()); } @Test - void testUpdateMessages() { + void testAddMessages() { Long threadId = 1L; ChatThreadPO chatThreadPO = new ChatThreadPO(); chatThreadPO.setUserId(123L); when(chatThreadDao.findById(threadId)).thenReturn(chatThreadPO); - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); messages.add(new SystemMessage("Hello System")); - persistentChatMemoryStore.updateMessages(threadId, messages); + persistentChatMemoryStore.saveAll(String.valueOf(threadId), messages); + messages.clear(); messages.add(new UserMessage("Hello User")); - persistentChatMemoryStore.updateMessages(threadId, messages); - messages.add(new AiMessage("Hello AI")); - persistentChatMemoryStore.updateMessages(threadId, messages); - - ChatMessage mockMessage = mock(ChatMessage.class); - when(mockMessage.type()).thenReturn(ChatMessageType.TOOL_EXECUTION_RESULT); - messages.add(mockMessage); - persistentChatMemoryStore.updateMessages(threadId, messages); + persistentChatMemoryStore.saveAll(String.valueOf(threadId), messages); + messages.clear(); + messages.add(new AssistantMessage("Hello AI")); + persistentChatMemoryStore.saveAll(String.valueOf(threadId), messages); } @Test - void testDeleteMessages() { + void testClearMessages() { Long threadId = 1L; List chatMessagePOS = new ArrayList<>(); @@ -121,7 +114,7 @@ void testDeleteMessages() { when(chatMessageDao.findAllByThreadId(threadId)).thenReturn(chatMessagePOS); - persistentChatMemoryStore.deleteMessages(threadId); + persistentChatMemoryStore.deleteByConversationId(String.valueOf(threadId)); Assertions.assertTrue(chatMessagePOS.get(0).getIsDeleted()); } @@ -130,12 +123,16 @@ void testDeleteMessages() { void testSystemMessage() { Long threadId = 1L; - when(chatMessageDao.findAllByThreadId(threadId)).thenReturn(new ArrayList<>()); - persistentChatMemoryStore.updateMessages(threadId, List.of(new SystemMessage("Hello from System"))); - List result = persistentChatMemoryStore.getMessages(threadId); + ChatMessagePO systemMessagePO = new ChatMessagePO(); + systemMessagePO.setSender("system"); + systemMessagePO.setMessage("Hello from System"); + + when(chatMessageDao.findAllByThreadId(threadId)).thenReturn(List.of(systemMessagePO)); + + List result = persistentChatMemoryStore.findByConversationId(String.valueOf(threadId)); assertEquals(1, result.size()); assertTrue(result.get(0) instanceof SystemMessage); - assertEquals("Hello from System", ((SystemMessage) result.get(0)).text()); + assertEquals("Hello from System", ((SystemMessage) result.get(0)).getText()); } } diff --git a/bigtop-manager-bom/pom.xml b/bigtop-manager-bom/pom.xml index d701c4f01..878ae561b 100644 --- a/bigtop-manager-bom/pom.xml +++ b/bigtop-manager-bom/pom.xml @@ -32,7 +32,7 @@ 1.0.0-RC1 - 3.1.1 + 3.2.0 2.2.0 2.3.32 3.14.0 @@ -52,8 +52,8 @@ 1.12.4 8.1.2.192 2.15.0 - 1.0.1 - 1.0.1-beta6 + 1.0.1 + 24.0.1 3.0.3 2.1.0 4.29.0 @@ -261,19 +261,23 @@ ${grpc-spring-boot.version} - dev.langchain4j - langchain4j - ${langchain4j-core.version} + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + - dev.langchain4j - langchain4j-open-ai - ${langchain4j-core.version} + org.springframework.ai + spring-ai-openai + ${spring-ai.version} + - dev.langchain4j - langchain4j-community-qianfan - ${langchain4j.version} + org.springframework.ai + spring-ai-deepseek + ${spring-ai.version} @@ -282,22 +286,23 @@ ${spring-ai.version} + - com.github.victools - jsonschema-module-jackson - ${victools.version} + dev.langchain4j + langchain4j + ${langchain4j.version} - dev.langchain4j - langchain4j-community-dashscope - ${langchain4j.version} + org.jetbrains + annotations + ${jetbrains-annotations.version} - dev.langchain4j - langchain4j-reactor - ${langchain4j.version} + com.github.victools + jsonschema-module-jackson + ${victools.version} diff --git a/bigtop-manager-server/pom.xml b/bigtop-manager-server/pom.xml index 7d4545749..e6d3ff384 100644 --- a/bigtop-manager-server/pom.xml +++ b/bigtop-manager-server/pom.xml @@ -63,6 +63,14 @@ org.apache.bigtop bigtop-manager-ai + + dev.langchain4j + langchain4j + + + org.jetbrains + annotations + com.github.victools jsonschema-module-jackson diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/ChatbotController.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/ChatbotController.java index 2206970eb..c6b835d55 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/ChatbotController.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/ChatbotController.java @@ -62,7 +62,7 @@ public ResponseEntity createChatThread(@RequestBody ChatbotThreadR @Operation(summary = "update thread", description = "Update a chat thread") @PutMapping("/threads/{threadId}") public ResponseEntity updateChatThread( - @PathVariable Long threadId, @RequestBody ChatbotThreadReq chatbotThreadReq) { + @PathVariable(name = "threadId") Long threadId, @RequestBody ChatbotThreadReq chatbotThreadReq) { ChatThreadDTO chatThreadDTO = ChatThreadConverter.INSTANCE.fromReq2DTO(chatbotThreadReq); chatThreadDTO.setId(threadId); return ResponseEntity.success(chatbotService.updateChatThread(chatThreadDTO)); @@ -70,13 +70,13 @@ public ResponseEntity updateChatThread( @Operation(summary = "delete thread", description = "Delete a chat thread") @DeleteMapping("/threads/{threadId}") - public ResponseEntity deleteChatThread(@PathVariable Long threadId) { + public ResponseEntity deleteChatThread(@PathVariable(name = "threadId") Long threadId) { return ResponseEntity.success(chatbotService.deleteChatThread(threadId)); } @Operation(summary = "get thread", description = "Get a chat thread") @GetMapping("/threads/{threadId}") - public ResponseEntity getChatThread(@PathVariable Long threadId) { + public ResponseEntity getChatThread(@PathVariable(name = "threadId") Long threadId) { return ResponseEntity.success(chatbotService.getChatThread(threadId)); } @@ -88,7 +88,7 @@ public ResponseEntity> getAllChatThreads() { @Operation(summary = "talk", description = "Talk with Chatbot") @PostMapping("/threads/{threadId}/talk") - public SseEmitter talk(@PathVariable Long threadId, @RequestBody ChatbotMessageReq messageReq) { + public SseEmitter talk(@PathVariable(name = "threadId") Long threadId, @RequestBody ChatbotMessageReq messageReq) { ChatbotCommand command = ChatbotCommand.getCommandFromMessage(messageReq.getMessage()); if (command != null) { messageReq.setMessage( @@ -99,7 +99,7 @@ public SseEmitter talk(@PathVariable Long threadId, @RequestBody ChatbotMessageR @Operation(summary = "history", description = "Get chat records") @GetMapping("/threads/{threadId}/history") - public ResponseEntity> history(@PathVariable Long threadId) { + public ResponseEntity> history(@PathVariable(name = "threadId") Long threadId) { return ResponseEntity.success(chatbotService.history(threadId)); } diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LLMConfigController.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LLMConfigController.java index 2b27cbb66..141d5341a 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LLMConfigController.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LLMConfigController.java @@ -58,13 +58,14 @@ public ResponseEntity> platforms() { @Operation(summary = "get platform", description = "Get platform") @GetMapping("/platforms/{id}") - public ResponseEntity getPlatform(@PathVariable Long id) { + public ResponseEntity getPlatform(@PathVariable(name = "id") Long id) { return ResponseEntity.success(llmConfigService.getPlatform(id)); } @Operation(summary = "platform credentials", description = "Get platform auth credentials") @GetMapping("/platforms/{platformId}/auth-credentials") - public ResponseEntity> platformsAuthCredential(@PathVariable Long platformId) { + public ResponseEntity> platformsAuthCredential( + @PathVariable(name = "platformId") Long platformId) { return ResponseEntity.success(llmConfigService.platformsAuthCredentials(platformId)); } @@ -91,7 +92,7 @@ public ResponseEntity addAuthorizedPlatform(@RequestBody AuthPla @Operation(summary = "update auth platform", description = "Update authorized platform") @PutMapping("/auth-platforms/{authId}") public ResponseEntity updateAuthorizedPlatform( - @PathVariable Long authId, @RequestBody AuthPlatformReq authPlatformReq) { + @PathVariable(name = "authId") Long authId, @RequestBody AuthPlatformReq authPlatformReq) { AuthPlatformDTO authPlatformDTO = AuthPlatformConverter.INSTANCE.fromReq2DTO(authPlatformReq); authPlatformDTO.setId(authId); return ResponseEntity.success(llmConfigService.updateAuthorizedPlatform(authPlatformDTO)); @@ -99,25 +100,25 @@ public ResponseEntity updateAuthorizedPlatform( @Operation(summary = "get auth platform", description = "Get a authorized platform") @GetMapping("/auth-platforms/{authId}") - public ResponseEntity getAuthorizedPlatform(@PathVariable Long authId) { + public ResponseEntity getAuthorizedPlatform(@PathVariable(name = "authId") Long authId) { return ResponseEntity.success(llmConfigService.getAuthorizedPlatform(authId)); } @Operation(summary = "delete auth platform", description = "Delete a authorized platform") @DeleteMapping("/auth-platforms/{authId}") - public ResponseEntity deleteAuthorizedPlatform(@PathVariable Long authId) { + public ResponseEntity deleteAuthorizedPlatform(@PathVariable(name = "authId") Long authId) { return ResponseEntity.success(llmConfigService.deleteAuthorizedPlatform(authId)); } @Operation(summary = "activate auth platform", description = "Activate authorized platform") @PostMapping("/auth-platforms/{authId}/activate") - public ResponseEntity activateAuthorizedPlatform(@PathVariable Long authId) { + public ResponseEntity activateAuthorizedPlatform(@PathVariable(name = "authId") Long authId) { return ResponseEntity.success(llmConfigService.activateAuthorizedPlatform(authId)); } @Operation(summary = "deactivate auth platform", description = "Deactivate authorized platform") @PostMapping("/auth-platforms/{authId}/deactivate") - public ResponseEntity deactivateAuthorizedPlatform(@PathVariable Long authId) { + public ResponseEntity deactivateAuthorizedPlatform(@PathVariable(name = "authId") Long authId) { return ResponseEntity.success(llmConfigService.deactivateAuthorizedPlatform(authId)); } } diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java index 08be70246..0627bb585 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java @@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; @@ -53,14 +54,14 @@ public class LoginController { @Operation(summary = "salt", description = "Generate salt") @GetMapping(value = "/salt") - public ResponseEntity salt(String username) { + public ResponseEntity salt(@RequestParam("username") String username) { String salt = Pbkdf2Utils.generateSalt(username); return ResponseEntity.success(salt); } @Operation(summary = "nonce", description = "Generate nonce") @GetMapping(value = "/nonce") - public ResponseEntity nonce(String username) { + public ResponseEntity nonce(@RequestParam("username") String username) { String nonce = PasswordUtils.randomString(16); String cacheKey = username + ":" + nonce; CacheUtils.setCache(Caches.CACHE_NONCE, cacheKey, nonce, Caches.NONCE_EXPIRE_TIME_MINUTES, TimeUnit.MINUTES); diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java index 3632001e5..6cf6bce99 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java @@ -20,7 +20,6 @@ import org.apache.bigtop.manager.common.utils.JsonUtils; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.tool.execution.ToolCallResultConverter; @@ -43,7 +42,7 @@ public class JsonToolCallResultConverter implements ToolCallResultConverter { private static final Logger logger = LoggerFactory.getLogger(JsonToolCallResultConverter.class); - @NotNull @Override + @Override public String convert(@Nullable Object result, @Nullable Type returnType) { if (returnType == Void.TYPE) { logger.debug("The tool has no return type. Converting to conventional response."); diff --git a/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql b/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql index aeab69311..773dd94f7 100644 --- a/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql +++ b/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql @@ -355,8 +355,8 @@ VALUES INSERT INTO llm_platform (credential, name, support_models) VALUES ('{"apiKey": "API Key"}', 'OpenAI', 'gpt-3.5-turbo,gpt-4,gpt-4o,gpt-3.5-turbo-16k,gpt-4-turbo-preview,gpt-4-32k,gpt-4o-mini'), -('{"apiKey": "API Key"}', 'DashScope', 'qwen-1.8b-chat,qwen-max,qwen-plus,qwen-turbo,qwen3-235b-a22b,qwen3-30b-a3b,qwen-plus-latest,qwen-turbo-latest'), -('{"apiKey": "API Key", "secretKey": "Secret Key"}', 'QianFan','Yi-34B-Chat,ERNIE-4.0-8K,ERNIE-3.5-128K,ERNIE-Speed-8K,Llama-2-7B-Chat,Fuyu-8B'), +('{"apiKey": "API Key"}', 'DashScope', 'qwen-max,qwen-plus,qwen-turbo,qwen3-235b-a22b,qwen3-30b-a3b,qwen-plus-latest,qwen-turbo-latest'), +('{"apiKey": "API Key"}', 'QianFan','ernie-speed-8k'), ('{"apiKey": "API Key"}','DeepSeek','deepseek-chat,deepseek-reasoner'); UPDATE `llm_platform` @@ -368,7 +368,7 @@ SET `desc` = 'Get your API Key in https://bailian.console.aliyun.com/?apiKey=1#/ WHERE `name` = 'DashScope'; UPDATE `llm_platform` -SET `desc` = 'Get API Key and Secret Key in https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1' +SET `desc` = 'Get API Key and Secret Key in https://console.bce.baidu.com/iam/#/iam/apikey/list' WHERE `name` = 'QianFan'; UPDATE `llm_platform` diff --git a/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql b/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql index 35feb63ff..eb34c0bac 100644 --- a/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql +++ b/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql @@ -366,8 +366,8 @@ VALUES INSERT INTO llm_platform (credential, name, support_models) VALUES ('{"apiKey": "API Key"}','OpenAI','gpt-3.5-turbo,gpt-4,gpt-4o,gpt-3.5-turbo-16k,gpt-4-turbo-preview,gpt-4-32k,gpt-4o-mini'), -('{"apiKey": "API Key"}','DashScope','qwen-1.8b-chat,qwen-max,qwen-plus,qwen-turbo,qwen3-235b-a22b,qwen3-30b-a3b,qwen-plus-latest,qwen-turbo-latest'), -('{"apiKey": "API Key", "secretKey": "Secret Key"}','QianFan','Yi-34B-Chat,ERNIE-4.0-8K,ERNIE-3.5-128K,ERNIE-Speed-8K,Llama-2-7B-Chat,Fuyu-8B'), +('{"apiKey": "API Key"}','DashScope','qwen-max,qwen-plus,qwen-turbo,qwen3-235b-a22b,qwen3-30b-a3b,qwen-plus-latest,qwen-turbo-latest'), +('{"apiKey": "API Key"}', 'QianFan','ernie-speed-8k'), ('{"apiKey": "API Key"}','DeepSeek','deepseek-chat,deepseek-reasoner'); UPDATE llm_platform @@ -379,7 +379,7 @@ SET "desc" = 'Get your API Key in https://bailian.console.aliyun.com/?apiKey=1#/ WHERE "name" = 'DashScope'; UPDATE llm_platform -SET "desc" = 'Get API Key and Secret Key in https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1' +SET "desc" = 'Get API Key and Secret Key in https://console.bce.baidu.com/iam/#/iam/apikey/list' WHERE "name" = 'QianFan'; UPDATE llm_platform