From 5ee2a0f11c5d899fd27bb4f284fb96ba937b2a3d Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Fri, 31 Oct 2025 20:48:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(chat):=E9=87=8D=E6=9E=84AI=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E5=8A=9F=E8=83=BD=E5=B9=B6=E9=9B=86=E6=88=90=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=AF=BB=E5=8F=96=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除原有的手机号识别与消息发送逻辑 - 删除RabbitMQ和邮件相关配置及代码 - 引入PDF、HTML、JSON等多种文档读取器 - 集成向量存储与检索功能支持问答 - 更新Spring AI依赖并调整内存存储方式 - 添加新的工具类用于保存文档到向量库- 修改提示词模板去除强制附加句规则 - 调整Cassandra和PgVector相关配置项- 新增多种文件格式读取组件实现类 --- pom.xml | 49 +++++--- .../chat/config/ChatClientConfiguration.java | 60 +++++----- .../hanserwei/chat/config/RabbitMQConfig.java | 37 ------ .../hanserwei/chat/config/RedisConfig.java | 30 ----- .../chat/consumer/RabbitMQConsumer.java | 47 -------- .../chat/controller/AiChatController.java | 40 +++---- .../chat/publisher/RabbitMQPublisher.java | 29 ----- .../hanserwei/chat/reader/MyHtmlReader.java | 34 ++++++ .../hanserwei/chat/reader/MyJsonReader.java | 27 +++++ .../chat/reader/MyMarkdownReader.java | 33 ++++++ .../hanserwei/chat/reader/MyPdfReader.java | 28 +++++ .../hanserwei/chat/reader/MyTextReader.java | 48 ++++++++ .../chat/reader/MyTikaPptReader.java | 29 +++++ .../chat/reader/MyTikaWordReader.java | 28 +++++ .../chat/tools/SaveDocumentsTools.java | 33 ++++++ .../chat/tools/SendMQMessageTools.java | 105 ------------------ .../src/main/resources/config/application.yml | 47 ++++---- .../src/main/resources/prompt/aiAssistant.st | 14 +-- 18 files changed, 363 insertions(+), 355 deletions(-) delete mode 100644 snails-chat/src/main/java/com/hanserwei/chat/config/RabbitMQConfig.java delete mode 100644 snails-chat/src/main/java/com/hanserwei/chat/config/RedisConfig.java delete mode 100644 snails-chat/src/main/java/com/hanserwei/chat/consumer/RabbitMQConsumer.java delete mode 100644 snails-chat/src/main/java/com/hanserwei/chat/publisher/RabbitMQPublisher.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyHtmlReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyJsonReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyMarkdownReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyPdfReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyTextReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaPptReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaWordReader.java create mode 100644 snails-chat/src/main/java/com/hanserwei/chat/tools/SaveDocumentsTools.java delete mode 100644 snails-chat/src/main/java/com/hanserwei/chat/tools/SendMQMessageTools.java diff --git a/pom.xml b/pom.xml index 6a633c3..92a7cde 100644 --- a/pom.xml +++ b/pom.xml @@ -58,14 +58,6 @@ jasypt-spring-boot-starter ${jasypt-starter-version} - - com.alibaba.cloud.ai - spring-ai-alibaba-starter-memory-redis - - - org.springframework.boot - spring-boot-starter-data-redis - org.postgresql postgresql @@ -83,18 +75,47 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - - org.springframework.boot - spring-boot-starter-amqp - com.fasterxml.jackson.core jackson-databind - org.springframework.boot - spring-boot-starter-mail + org.springframework.ai + spring-ai-starter-vector-store-pgvector + + + org.springframework.ai + spring-ai-advisors-vector-store + + + + org.springframework.ai + spring-ai-starter-model-chat-memory-repository-cassandra + + + + org.springframework.ai + spring-ai-markdown-document-reader + + + + org.springframework.ai + spring-ai-jsoup-document-reader + + + + org.springframework.ai + spring-ai-pdf-document-reader + + + + org.springframework.ai + spring-ai-tika-document-reader + + + + diff --git a/snails-chat/src/main/java/com/hanserwei/chat/config/ChatClientConfiguration.java b/snails-chat/src/main/java/com/hanserwei/chat/config/ChatClientConfiguration.java index e19ddd3..9154f57 100644 --- a/snails-chat/src/main/java/com/hanserwei/chat/config/ChatClientConfiguration.java +++ b/snails-chat/src/main/java/com/hanserwei/chat/config/ChatClientConfiguration.java @@ -1,15 +1,20 @@ package com.hanserwei.chat.config; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; -import com.alibaba.cloud.ai.memory.redis.BaseRedisChatMemoryRepository; -import com.alibaba.cloud.ai.memory.redis.LettuceRedisChatMemoryRepository; +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; import com.hanserwei.chat.tools.AiDBTools; -import com.hanserwei.chat.tools.SendMQMessageTools; +import com.hanserwei.chat.tools.SaveDocumentsTools; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.chat.memory.repository.cassandra.CassandraChatMemoryRepository; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.aot.hint.annotation.RegisterReflection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,49 +22,46 @@ import org.springframework.context.annotation.Configuration; @Configuration public class ChatClientConfiguration { - @Value("${spring.ai.memory.redis.host}") - private String redisHost; - @Value("${spring.ai.memory.redis.port}") - private int redisPort; - @Value("${spring.ai.memory.redis.password}") - private String redisPassword; - @Value("${spring.ai.memory.redis.timeout}") - private int redisTimeout; - - @Resource private DashScopeChatModel dashScopeChatModel; @Resource private AiDBTools aiDBTools; - + @Resource + private DashScopeEmbeddingModel dashScopeEmbeddingModel; + @Resource + private CassandraChatMemoryRepository chatMemoryRepository; + @Resource + private VectorStore vectorStore; + + @Value("classpath:prompt/aiAssistant.st") private org.springframework.core.io.Resource aiAssistantResource; @Bean - public BaseRedisChatMemoryRepository redisChatMemoryRepository() { - // 构建RedissonRedisChatMemoryRepository实例 - return LettuceRedisChatMemoryRepository.builder() - .host(redisHost) - .port(redisPort) - .password(redisPassword) - .timeout(redisTimeout) + public ChatMemory chatMemory() { + return MessageWindowChatMemory.builder() + .maxMessages(50) + .chatMemoryRepository(chatMemoryRepository) .build(); } @Bean - public ChatMemory chatMemory(BaseRedisChatMemoryRepository chatMemoryRepository) { - return MessageWindowChatMemory - .builder() - .maxMessages(100000) - .chatMemoryRepository(chatMemoryRepository).build(); + public SimpleVectorStore simpleVectorStore() { + return SimpleVectorStore.builder(dashScopeEmbeddingModel) + .build(); } @Bean - public ChatClient dashScopeChatClient(ChatMemory chatMemory, SendMQMessageTools sendMQMessageTools) { + public ChatClient dashScopeChatClient(ChatMemory chatMemory, + SaveDocumentsTools saveDocumentsTools + ) { + PromptChatMemoryAdvisor chatMemoryAdvisor = PromptChatMemoryAdvisor.builder(chatMemory).build(); + SimpleLoggerAdvisor simpleLoggerAdvisor = new SimpleLoggerAdvisor(); + QuestionAnswerAdvisor questionAnswerAdvisor = new QuestionAnswerAdvisor(vectorStore); return ChatClient.builder(dashScopeChatModel) - .defaultTools(aiDBTools, sendMQMessageTools) + .defaultTools(aiDBTools, saveDocumentsTools) .defaultSystem(aiAssistantResource) - .defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) + .defaultAdvisors(chatMemoryAdvisor,simpleLoggerAdvisor,questionAnswerAdvisor) .build(); } } \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/config/RabbitMQConfig.java b/snails-chat/src/main/java/com/hanserwei/chat/config/RabbitMQConfig.java deleted file mode 100644 index 76aee93..0000000 --- a/snails-chat/src/main/java/com/hanserwei/chat/config/RabbitMQConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.hanserwei.chat.config; - -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.DirectExchange; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class RabbitMQConfig { - - @Bean - public DirectExchange directExchange() { - return new DirectExchange("chat.exchange"); - } - - @Bean - public Queue queue() { - return new Queue("chat.queue"); - } - - @Bean - public Binding binding() { - return BindingBuilder.bind(queue()) - .to(directExchange()) - .with("chat.routing.key"); - } - - @Bean - public MessageConverter messageConverter() { - return new Jackson2JsonMessageConverter(); - } - -} diff --git a/snails-chat/src/main/java/com/hanserwei/chat/config/RedisConfig.java b/snails-chat/src/main/java/com/hanserwei/chat/config/RedisConfig.java deleted file mode 100644 index 5af31dc..0000000 --- a/snails-chat/src/main/java/com/hanserwei/chat/config/RedisConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.hanserwei.chat.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; - -@Configuration -public class RedisConfig { - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - // 设置 RedisTemplate 的连接工厂 - redisTemplate.setConnectionFactory(connectionFactory); - - // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串 - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - - // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式 - Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); - redisTemplate.setValueSerializer(serializer); - redisTemplate.setHashValueSerializer(serializer); - - redisTemplate.afterPropertiesSet(); - return redisTemplate; - } -} diff --git a/snails-chat/src/main/java/com/hanserwei/chat/consumer/RabbitMQConsumer.java b/snails-chat/src/main/java/com/hanserwei/chat/consumer/RabbitMQConsumer.java deleted file mode 100644 index 1c29751..0000000 --- a/snails-chat/src/main/java/com/hanserwei/chat/consumer/RabbitMQConsumer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.hanserwei.chat.consumer; - -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Component; - -import java.nio.charset.StandardCharsets; - -@Slf4j -@Component -public class RabbitMQConsumer { - - @Resource - private JavaMailSender mailSender; - - @RabbitListener(queues = "chat.queue") - public void receiveMessage(Object message) { - String messageText = extractMessage(message); - log.info("Received message text:\n{}", messageText); - SimpleMailMessage mailMessage = new SimpleMailMessage(); - // 配置发送者邮箱 - mailMessage.setFrom("2628273921@qq.com"); - // 配置接受者邮箱 - mailMessage.setTo("ssw010723@gmail.com"); - // 配置邮件主题 - mailMessage.setSubject("主题:及时联系客户"); - // 配置邮件内容 - mailMessage.setText(messageText); - // 发送邮件 - mailSender.send(mailMessage); - } - - private String extractMessage(Object message) { - return switch (message) { - case null -> ""; - case String str -> str; - case byte[] body -> new String(body, StandardCharsets.UTF_8); - case Message amqpMessage -> new String(amqpMessage.getBody(), StandardCharsets.UTF_8); - default -> String.valueOf(message); - }; - - } -} diff --git a/snails-chat/src/main/java/com/hanserwei/chat/controller/AiChatController.java b/snails-chat/src/main/java/com/hanserwei/chat/controller/AiChatController.java index 1a45978..30c6bae 100644 --- a/snails-chat/src/main/java/com/hanserwei/chat/controller/AiChatController.java +++ b/snails-chat/src/main/java/com/hanserwei/chat/controller/AiChatController.java @@ -2,21 +2,19 @@ package com.hanserwei.chat.controller; import com.hanserwei.chat.model.dto.ChatMessageDTO; import com.hanserwei.chat.model.vo.AIResponse; -import com.hanserwei.chat.tools.SendMQMessageTools; +import com.hanserwei.chat.reader.MyPdfReader; import com.hanserwei.chat.utils.ConversationContext; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.VectorStore; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.List; @Slf4j @RestController @@ -26,15 +24,14 @@ public class AiChatController { @Resource private ChatClient dashScopeChatClient; @Resource - private SendMQMessageTools sendMQMessageTools; - - private static final Pattern PHONE_PATTERN = Pattern.compile("(? chatWithAi(@RequestBody ChatMessageDTO chatMessageDTO) { log.info("会话ID:{}", chatMessageDTO.getConversionId()); ConversationContext.setConversationId(chatMessageDTO.getConversionId()); - triggerSendMessageIfPhonePresent(chatMessageDTO); return dashScopeChatClient.prompt() .user(chatMessageDTO.getMessage()) @@ -48,21 +45,10 @@ public class AiChatController { .doFinally(signalType -> ConversationContext.clear()); } - private void triggerSendMessageIfPhonePresent(ChatMessageDTO chatMessageDTO) { - String message = chatMessageDTO.getMessage(); - if (message == null || message.isEmpty()) { - return; - } - - Matcher matcher = PHONE_PATTERN.matcher(message); - if (!matcher.find()) { - return; - } - - String phoneNumber = matcher.group(1); - log.info("检测到手机号:{},会话ID:{}", phoneNumber, chatMessageDTO.getConversionId()); - sendMQMessageTools.sendMQMessage(phoneNumber) - .doOnError(error -> log.error("触发发送消息工具失败,手机号:{},错误:{}", phoneNumber, error.getMessage(), error)) - .subscribe(result -> log.info("已触发发送消息工具,手机号:{},结果:{}", phoneNumber, result)); + @GetMapping("/readpdf") + public String readPdf() { + List docsFromPdf = myPdfReader.getDocsFromPdf(); + vectorStore.add(docsFromPdf); + return "ok!"; } } diff --git a/snails-chat/src/main/java/com/hanserwei/chat/publisher/RabbitMQPublisher.java b/snails-chat/src/main/java/com/hanserwei/chat/publisher/RabbitMQPublisher.java deleted file mode 100644 index bcf0cb9..0000000 --- a/snails-chat/src/main/java/com/hanserwei/chat/publisher/RabbitMQPublisher.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.hanserwei.chat.publisher; - -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class RabbitMQPublisher { - - @Resource - private RabbitTemplate rabbitTemplate; - - /** - * 发送消息 - * - * @param exchange 交换机 - * @param routingKey 路由键 - * @param message 消息 - */ - public void send(String exchange, String routingKey, Object message) { - try { - rabbitTemplate.convertAndSend(exchange, routingKey, message); - } catch (Exception e) { - log.error("RabbitMQ发送消息失败:{}", e.getMessage()); - } - } -} diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyHtmlReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyHtmlReader.java new file mode 100644 index 0000000..204f668 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyHtmlReader.java @@ -0,0 +1,34 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.jsoup.JsoupDocumentReader; +import org.springframework.ai.reader.jsoup.config.JsoupDocumentReaderConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyHtmlReader { + + @Value("classpath:/document/my-page.html") + private Resource resource; + + public List loadHtml() { + // JsoupDocumentReader 阅读器配置类 + JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder() + .selector("article p") // 提取
标签内的 p 段落 + .charset("UTF-8") // 使用 UTF-8 编码 + .includeLinkUrls(true) // 在元数据中包含链接 URL(绝对链接) + .metadataTags(List.of("author", "date")) // 提取 author 和 date 元标签 + .additionalMetadata("source", "my-page.html") // 添加自定义元数据 + .build(); + + // 新建 JsoupDocumentReader 阅读器 + JsoupDocumentReader reader = new JsoupDocumentReader(resource, config); + + // 读取并转换为 Document 文档集合 + return reader.get(); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyJsonReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyJsonReader.java new file mode 100644 index 0000000..9004327 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyJsonReader.java @@ -0,0 +1,27 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.JsonReader; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyJsonReader { + + @Value("classpath:/document/tv.json") + private Resource resource; + + /** + * 读取 Json 文件 + * @return + */ + public List loadJson() { + // 创建 JsonReader 阅读器实例,配置需要读取的字段 + JsonReader jsonReader = new JsonReader(resource, "description", "content", "title"); + // 执行读取操作,并转换为 Document 对象集合 + return jsonReader.get(); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyMarkdownReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyMarkdownReader.java new file mode 100644 index 0000000..01f51d9 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyMarkdownReader.java @@ -0,0 +1,33 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.markdown.MarkdownDocumentReader; +import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyMarkdownReader { + + @Value("classpath:/document/code.md") + private Resource resource; + + public List loadMarkdown() { + // MarkdownDocumentReader 阅读器配置类 + MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder() + .withHorizontalRuleCreateDocument(true) // 遇到水平线 ---,则创建新文档 + .withIncludeCodeBlock(false) // 排除代码块(代码块生成单独文档) + .withIncludeBlockquote(false) // 排除块引用(块引用生成单独文档) + .withAdditionalMetadata("filename", "code.md") // 添加自定义元数据,如文件名称 + .build(); + + // 新建 MarkdownDocumentReader 阅读器 + MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config); + + // 读取并转换为 Document 文档集合 + return reader.get(); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyPdfReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyPdfReader.java new file mode 100644 index 0000000..d434598 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyPdfReader.java @@ -0,0 +1,28 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.ExtractedTextFormatter; +import org.springframework.ai.reader.pdf.PagePdfDocumentReader; +import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyPdfReader { + + public List getDocsFromPdf() { + // 新建 PagePdfDocumentReader 阅读器 + PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/document/profile.pdf", // PDF 文件路径 + PdfDocumentReaderConfig.builder() + .withPageTopMargin(0) // 设置页面顶边距为0 + .withPageExtractedTextFormatter(ExtractedTextFormatter.builder() + .withNumberOfTopTextLinesToDelete(0) // 设置删除顶部文本行数为0 + .build()) + .withPagesPerDocument(1) // 设置每个文档包含1页 + .build()); + + // 读取并转换为 Document 文档集合 + return pdfReader.read(); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTextReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTextReader.java new file mode 100644 index 0000000..0198d63 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTextReader.java @@ -0,0 +1,48 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.TextReader; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyTextReader { + + @Value("classpath:/document/manual.txt") + private Resource resource; + + /** + * 读取 Txt 文档 + * @return 读取的文档集合 + */ + public List loadText() { + // 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容 + TextReader textReader = new TextReader(resource); + // 添加自定义元数据,如文件名称 + textReader.getCustomMetadata() + .put("filename", "manual.txt"); + // 读取并转换为 Document 文档集合 + return textReader.read(); + } + + /** + * 读取 Txt 文档并分块拆分 + * @return 文档分块集合 + */ + public List loadTextAndSplit() { + // 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容 + TextReader textReader = new TextReader(resource); + + // 将资源内容解析为 Document 对象集合 + List documents = textReader.get(); + + // 使用 TokenTextSplitter 对文档列表进行分块处理 + + // 返回拆分后的文档分块集合 + return new TokenTextSplitter().apply(documents); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaPptReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaPptReader.java new file mode 100644 index 0000000..397ec43 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaPptReader.java @@ -0,0 +1,29 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.tika.TikaDocumentReader; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyTikaPptReader { + + @Value("classpath:/document/XX牌云感变频空调说明书.pptx") + private Resource resource; + + public List loadPpt() { + // 新建 TikaDocumentReader 阅读器 + TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource); + // 读取并转换为 Document 文档集合 + List documents = tikaDocumentReader.get(); + + // 文档分块 + // 使用自定义设置 + TokenTextSplitter splitter = new TokenTextSplitter(1000, 400, 10, 5000, true); + return splitter.apply(documents); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaWordReader.java b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaWordReader.java new file mode 100644 index 0000000..e1160c1 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/reader/MyTikaWordReader.java @@ -0,0 +1,28 @@ +package com.hanserwei.chat.reader; + +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.tika.TikaDocumentReader; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class MyTikaWordReader { + + @Value("classpath:/document/55f79946a0964b89bc7ab9b55e4a49ff.docx") + private Resource resource; + + public List loadWord() { + // 新建 TikaDocumentReader 阅读器 + TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource); + // 读取并转换为 Document 文档集合 + List documents = tikaDocumentReader.get(); + + // 文档分块 + TokenTextSplitter splitter = new TokenTextSplitter(); // 不设置任何构造参数,表示使用默认设置 + return splitter.apply(documents); + } +} \ No newline at end of file diff --git a/snails-chat/src/main/java/com/hanserwei/chat/tools/SaveDocumentsTools.java b/snails-chat/src/main/java/com/hanserwei/chat/tools/SaveDocumentsTools.java new file mode 100644 index 0000000..fdd3837 --- /dev/null +++ b/snails-chat/src/main/java/com/hanserwei/chat/tools/SaveDocumentsTools.java @@ -0,0 +1,33 @@ +package com.hanserwei.chat.tools; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.document.Document; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.List; + +@Slf4j +@Component +public class SaveDocumentsTools { + + @Resource + private SimpleVectorStore simpleVectorStore; + + @Tool(name = "SaveDocuments", + description = "保存文档为本地的向量化知识库。") + public boolean saveDocuments( + @ToolParam(description = "文档内容") String documents + ) { + simpleVectorStore.add(List.of(new Document(documents))); + //把内存中的向量数据,持久化到磁盘 + File file = new File("/home/hanserwei/IdeaProjects/snails-ai-backend/snails-chat/src/main/resources/documents/vector.json"); + simpleVectorStore.save(file); + + return true; + } +} diff --git a/snails-chat/src/main/java/com/hanserwei/chat/tools/SendMQMessageTools.java b/snails-chat/src/main/java/com/hanserwei/chat/tools/SendMQMessageTools.java deleted file mode 100644 index 650a6c4..0000000 --- a/snails-chat/src/main/java/com/hanserwei/chat/tools/SendMQMessageTools.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.hanserwei.chat.tools; - -import com.hanserwei.chat.publisher.RabbitMQPublisher; -import com.hanserwei.chat.utils.ConversationContext; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.memory.ChatMemory; -import org.springframework.ai.chat.messages.AbstractMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.tool.annotation.Tool; -import org.springframework.ai.tool.annotation.ToolParam; -import org.springframework.stereotype.Component; -import reactor.core.publisher.Mono; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -@Slf4j -@Component -public class SendMQMessageTools { - - @Resource - private RabbitMQPublisher rabbitMQPublisher; - @Resource - private ChatMemory chatMemory; - - private static final int HISTORY_LIMIT = 10; - - /** - * 发送RabbitMQ消息 - * - * @param phoneNumber 手机号码 - */ - @Tool(name = "SendMessage", - description = "当用户留下手机号码时,立即调用此工具发送消息通知管理员。手机号码应该是11位数字。") - public Mono sendMQMessage( - @ToolParam(description = "用户提供的手机号码,必须是11位数字") String phoneNumber - ) { - return ConversationContext.getConversationIdMono() - .defaultIfEmpty(ConversationContext.getConversationId()) - .map(conversationId -> { - String exchange = "chat.exchange"; - String routingKey = "chat.routing.key"; - String recentConversation = buildRecentConversation(conversationId); - String message = "用户留了手机号:" + phoneNumber + ",会话ID:" + conversationId + recentConversation; - - log.info("SendMQMessageTools 发送消息: {}", message); - rabbitMQPublisher.send(exchange, routingKey, message); - - // 确保日志能被记录 - log.info("SendMQMessageTools 消息发送完成"); - return "消息发送成功"; - }); - } - - private String buildRecentConversation(Long conversationId) { - if (conversationId == null) { - return "。最近对话:暂无记录"; - } - - List history = null; - try { - history = chatMemory.get(String.valueOf(conversationId)); - } catch (Exception ex) { - log.warn("获取会话{}历史记录失败: {}", conversationId, ex.getMessage(), ex); - } - - if (history == null || history.isEmpty()) { - return "。最近对话:暂无记录"; - } - - int skip = Math.max(0, history.size() - HISTORY_LIMIT); - String recent = history.stream() - .skip(skip) - .map(SendMQMessageTools::formatMessage) - .filter(Objects::nonNull) - .collect(Collectors.joining("\n")); - - if (recent.isEmpty()) { - return "。最近对话:暂无记录"; - } - - return "。最近对话:\n" + recent; - } - - private static String formatMessage(Message message) { - if (message == null) { - return null; - } - - MessageType messageType = message.getMessageType(); - String role = messageType != null ? messageType.name() : "UNKNOWN"; - String text = (message instanceof AbstractMessage abstractMessage) - ? abstractMessage.getText() - : message.toString(); - - if (text == null || text.isBlank()) { - return null; - } - - return role + ": " + text.strip(); - } -} diff --git a/snails-chat/src/main/resources/config/application.yml b/snails-chat/src/main/resources/config/application.yml index a57c715..740b3b2 100644 --- a/snails-chat/src/main/resources/config/application.yml +++ b/snails-chat/src/main/resources/config/application.yml @@ -9,18 +9,13 @@ spring: name: snails-ai banner: location: config/banner.txt + cassandra: + contact-points: 127.0.0.1 # Cassandra 集群节点地址(可配置多个,用逗号分隔) + port: 9042 # 端口号 + local-datacenter: datacenter1 # 必须与集群配置的数据中心名称一致(大小写敏感) jackson: serialization: write-dates-as-timestamps: false - mail: - host: ${MAIL_HOST:smtp.qq.com} - port: ${MAIL_PORT:587} - username: ${MAIL_USERNAME:2628273921@qq.com} - password: ENC(ARrAyZNZhbaG6tebogv6WSQbtCO+Vq93NfSA6tMAiD0tTogujERVwEGBECakH0LUhYq9oTaXgfw7tonxNAFEwg==) - properties: - mail.smtp.auth: true - mail.smtp.starttls.enable: true - mail.smtp.starttls.required: true data: redis: host: localhost @@ -35,15 +30,6 @@ spring: max-wait: 10000 min-idle: 10 time-between-eviction-runs: 10000 - rabbitmq: - host: localhost - port: 5672 - username: admin - password: admin123 - virtual-host: /snailsAi - listener: - simple: - prefetch: 1 datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/postgres?serverTimezone=Asia/Shanghai @@ -56,18 +42,31 @@ spring: connection-timeout: 5000 # 获取连接超时 5 秒 max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收) ai: - memory: - redis: - host: localhost - port: 6379 - timeout: 5000 - password: redis + vectorstore: + pgvector: + initialize-schema: true + table-name: snails_ai_vector + index-type: hnsw + dimensions: 1024 + chat: + memory: + repository: + cassandra: + keyspace: snails_ai + table: t_ai_chat_memory # 表名 + time-to-live: 1095d # 数据的自动过期时间(1095天 ≈ 3年) + initialize-schema: true # 自动初始化表结构 dashscope: api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=) chat: options: model: qwen-plus temperature: 0.5 + embedding: + options: + model: text-embedding-v4 + dimensions: 1024 + read-timeout: 60000 mybatis-plus: configuration: map-underscore-to-camel-case: true diff --git a/snails-chat/src/main/resources/prompt/aiAssistant.st b/snails-chat/src/main/resources/prompt/aiAssistant.st index b76c229..bcbdd7d 100644 --- a/snails-chat/src/main/resources/prompt/aiAssistant.st +++ b/snails-chat/src/main/resources/prompt/aiAssistant.st @@ -5,19 +5,7 @@ 公司核心业务: 我们是一家专注于智能家居产品研发与销售的科技公司。 使命: 致力于通过新技术 / 优质服务 / 专业内容提升用户的生活质量/学习效率/工作效率。 + ### 📜 **基本指令与约束** 1. **回答模式:** 始终以**清晰、简洁、礼貌**的方式回答用户的问题。 -2. **强制性附加句:** 无论你回答了什么问题,在**每个回答的末尾**,你必须**完全、准确地附加**以下这句话: - > **"如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。"** -3. **手机号识别与工具调用:** - * 当用户输入的文本中包含一个**有效的手机号码**时(通常是11位数字,识别时请保持一定的容错性),你必须**立即调用SendMessage工具**,并将手机号作为参数传递。 - * **给用户的反馈:** 工具调用成功后,给用户回复一个简短的确认信息,例如:"感谢您留下手机号码,我们已收到您的信息,专业的客服人员将在24小时内与您联系,请保持手机畅通。" - -### 💡 **工作流程示例** - -| 步骤 | 用户输入 | AI助手的行为 | -| :--- | :--- | :--- | -| **1 (常规问题)** | "你们产品的价格是多少?" | 1. 回答价格信息。
2. **附加句:** "如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。" | -| **2 (留号码)** | "好的,这是我的手机号:13800001234" | 1. **识别**到手机号。
2. **调用工具:** `SendMessage`
3. **回复确认:** "感谢您留下手机号码,我们已收到您的信息,专业的客服人员将在24小时内与您联系,请保持手机畅通。" | -| **3 (追问)** | "你们的退货政策呢?" | 1. 回答退货政策。
2. **附加句:** "如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。" | \ No newline at end of file