refactor(chat):重构AI助手功能并集成文档读取能力
- 移除原有的手机号识别与消息发送逻辑 - 删除RabbitMQ和邮件相关配置及代码 - 引入PDF、HTML、JSON等多种文档读取器 - 集成向量存储与检索功能支持问答 - 更新Spring AI依赖并调整内存存储方式 - 添加新的工具类用于保存文档到向量库- 修改提示词模板去除强制附加句规则 - 调整Cassandra和PgVector相关配置项- 新增多种文件格式读取组件实现类
This commit is contained in:
49
pom.xml
49
pom.xml
@@ -58,14 +58,6 @@
|
|||||||
<artifactId>jasypt-spring-boot-starter</artifactId>
|
<artifactId>jasypt-spring-boot-starter</artifactId>
|
||||||
<version>${jasypt-starter-version}</version>
|
<version>${jasypt-starter-version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud.ai</groupId>
|
|
||||||
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
@@ -83,18 +75,47 @@
|
|||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-boot-starter-mail</artifactId>
|
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 向量 Advisor -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-advisors-vector-store</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- Cassandra -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 读取 Markdown -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-markdown-document-reader</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 读取 HTML -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-jsoup-document-reader</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 读取 PDF -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-pdf-document-reader</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- Tika -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-tika-document-reader</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
package com.hanserwei.chat.config;
|
package com.hanserwei.chat.config;
|
||||||
|
|
||||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
||||||
import com.alibaba.cloud.ai.memory.redis.BaseRedisChatMemoryRepository;
|
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
|
||||||
import com.alibaba.cloud.ai.memory.redis.LettuceRedisChatMemoryRepository;
|
|
||||||
import com.hanserwei.chat.tools.AiDBTools;
|
import com.hanserwei.chat.tools.AiDBTools;
|
||||||
import com.hanserwei.chat.tools.SendMQMessageTools;
|
import com.hanserwei.chat.tools.SaveDocumentsTools;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
|
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.ChatMemory;
|
||||||
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -17,49 +22,46 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class ChatClientConfiguration {
|
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
|
@Resource
|
||||||
private DashScopeChatModel dashScopeChatModel;
|
private DashScopeChatModel dashScopeChatModel;
|
||||||
@Resource
|
@Resource
|
||||||
private AiDBTools aiDBTools;
|
private AiDBTools aiDBTools;
|
||||||
|
@Resource
|
||||||
|
private DashScopeEmbeddingModel dashScopeEmbeddingModel;
|
||||||
|
@Resource
|
||||||
|
private CassandraChatMemoryRepository chatMemoryRepository;
|
||||||
|
@Resource
|
||||||
|
private VectorStore vectorStore;
|
||||||
|
|
||||||
|
|
||||||
@Value("classpath:prompt/aiAssistant.st")
|
@Value("classpath:prompt/aiAssistant.st")
|
||||||
private org.springframework.core.io.Resource aiAssistantResource;
|
private org.springframework.core.io.Resource aiAssistantResource;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public BaseRedisChatMemoryRepository redisChatMemoryRepository() {
|
public ChatMemory chatMemory() {
|
||||||
// 构建RedissonRedisChatMemoryRepository实例
|
return MessageWindowChatMemory.builder()
|
||||||
return LettuceRedisChatMemoryRepository.builder()
|
.maxMessages(50)
|
||||||
.host(redisHost)
|
.chatMemoryRepository(chatMemoryRepository)
|
||||||
.port(redisPort)
|
|
||||||
.password(redisPassword)
|
|
||||||
.timeout(redisTimeout)
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ChatMemory chatMemory(BaseRedisChatMemoryRepository chatMemoryRepository) {
|
public SimpleVectorStore simpleVectorStore() {
|
||||||
return MessageWindowChatMemory
|
return SimpleVectorStore.builder(dashScopeEmbeddingModel)
|
||||||
.builder()
|
.build();
|
||||||
.maxMessages(100000)
|
|
||||||
.chatMemoryRepository(chatMemoryRepository).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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)
|
return ChatClient.builder(dashScopeChatModel)
|
||||||
.defaultTools(aiDBTools, sendMQMessageTools)
|
.defaultTools(aiDBTools, saveDocumentsTools)
|
||||||
.defaultSystem(aiAssistantResource)
|
.defaultSystem(aiAssistantResource)
|
||||||
.defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build())
|
.defaultAdvisors(chatMemoryAdvisor,simpleLoggerAdvisor,questionAnswerAdvisor)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
|
||||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
|
||||||
// 设置 RedisTemplate 的连接工厂
|
|
||||||
redisTemplate.setConnectionFactory(connectionFactory);
|
|
||||||
|
|
||||||
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串
|
|
||||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
|
||||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
|
||||||
|
|
||||||
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式
|
|
||||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
|
||||||
redisTemplate.setValueSerializer(serializer);
|
|
||||||
redisTemplate.setHashValueSerializer(serializer);
|
|
||||||
|
|
||||||
redisTemplate.afterPropertiesSet();
|
|
||||||
return redisTemplate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,21 +2,19 @@ package com.hanserwei.chat.controller;
|
|||||||
|
|
||||||
import com.hanserwei.chat.model.dto.ChatMessageDTO;
|
import com.hanserwei.chat.model.dto.ChatMessageDTO;
|
||||||
import com.hanserwei.chat.model.vo.AIResponse;
|
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 com.hanserwei.chat.utils.ConversationContext;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.memory.ChatMemory;
|
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.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@@ -26,15 +24,14 @@ public class AiChatController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ChatClient dashScopeChatClient;
|
private ChatClient dashScopeChatClient;
|
||||||
@Resource
|
@Resource
|
||||||
private SendMQMessageTools sendMQMessageTools;
|
private MyPdfReader myPdfReader;
|
||||||
|
@Resource
|
||||||
private static final Pattern PHONE_PATTERN = Pattern.compile("(?<!\\d)(1[3-9]\\d{9})(?!\\d)");
|
private VectorStore vectorStore;
|
||||||
|
|
||||||
@PostMapping(path = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
@PostMapping(path = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public Flux<AIResponse> chatWithAi(@RequestBody ChatMessageDTO chatMessageDTO) {
|
public Flux<AIResponse> chatWithAi(@RequestBody ChatMessageDTO chatMessageDTO) {
|
||||||
log.info("会话ID:{}", chatMessageDTO.getConversionId());
|
log.info("会话ID:{}", chatMessageDTO.getConversionId());
|
||||||
ConversationContext.setConversationId(chatMessageDTO.getConversionId());
|
ConversationContext.setConversationId(chatMessageDTO.getConversionId());
|
||||||
triggerSendMessageIfPhonePresent(chatMessageDTO);
|
|
||||||
|
|
||||||
return dashScopeChatClient.prompt()
|
return dashScopeChatClient.prompt()
|
||||||
.user(chatMessageDTO.getMessage())
|
.user(chatMessageDTO.getMessage())
|
||||||
@@ -48,21 +45,10 @@ public class AiChatController {
|
|||||||
.doFinally(signalType -> ConversationContext.clear());
|
.doFinally(signalType -> ConversationContext.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void triggerSendMessageIfPhonePresent(ChatMessageDTO chatMessageDTO) {
|
@GetMapping("/readpdf")
|
||||||
String message = chatMessageDTO.getMessage();
|
public String readPdf() {
|
||||||
if (message == null || message.isEmpty()) {
|
List<Document> docsFromPdf = myPdfReader.getDocsFromPdf();
|
||||||
return;
|
vectorStore.add(docsFromPdf);
|
||||||
}
|
return "ok!";
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Document> loadHtml() {
|
||||||
|
// JsoupDocumentReader 阅读器配置类
|
||||||
|
JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()
|
||||||
|
.selector("article p") // 提取 <article> 标签内的 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> loadJson() {
|
||||||
|
// 创建 JsonReader 阅读器实例,配置需要读取的字段
|
||||||
|
JsonReader jsonReader = new JsonReader(resource, "description", "content", "title");
|
||||||
|
// 执行读取操作,并转换为 Document 对象集合
|
||||||
|
return jsonReader.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> loadText() {
|
||||||
|
// 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容
|
||||||
|
TextReader textReader = new TextReader(resource);
|
||||||
|
// 添加自定义元数据,如文件名称
|
||||||
|
textReader.getCustomMetadata()
|
||||||
|
.put("filename", "manual.txt");
|
||||||
|
// 读取并转换为 Document 文档集合
|
||||||
|
return textReader.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取 Txt 文档并分块拆分
|
||||||
|
* @return 文档分块集合
|
||||||
|
*/
|
||||||
|
public List<Document> loadTextAndSplit() {
|
||||||
|
// 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容
|
||||||
|
TextReader textReader = new TextReader(resource);
|
||||||
|
|
||||||
|
// 将资源内容解析为 Document 对象集合
|
||||||
|
List<Document> documents = textReader.get();
|
||||||
|
|
||||||
|
// 使用 TokenTextSplitter 对文档列表进行分块处理
|
||||||
|
|
||||||
|
// 返回拆分后的文档分块集合
|
||||||
|
return new TokenTextSplitter().apply(documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> loadPpt() {
|
||||||
|
// 新建 TikaDocumentReader 阅读器
|
||||||
|
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource);
|
||||||
|
// 读取并转换为 Document 文档集合
|
||||||
|
List<Document> documents = tikaDocumentReader.get();
|
||||||
|
|
||||||
|
// 文档分块
|
||||||
|
// 使用自定义设置
|
||||||
|
TokenTextSplitter splitter = new TokenTextSplitter(1000, 400, 10, 5000, true);
|
||||||
|
return splitter.apply(documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Document> loadWord() {
|
||||||
|
// 新建 TikaDocumentReader 阅读器
|
||||||
|
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource);
|
||||||
|
// 读取并转换为 Document 文档集合
|
||||||
|
List<Document> documents = tikaDocumentReader.get();
|
||||||
|
|
||||||
|
// 文档分块
|
||||||
|
TokenTextSplitter splitter = new TokenTextSplitter(); // 不设置任何构造参数,表示使用默认设置
|
||||||
|
return splitter.apply(documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> 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<Message> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,18 +9,13 @@ spring:
|
|||||||
name: snails-ai
|
name: snails-ai
|
||||||
banner:
|
banner:
|
||||||
location: config/banner.txt
|
location: config/banner.txt
|
||||||
|
cassandra:
|
||||||
|
contact-points: 127.0.0.1 # Cassandra 集群节点地址(可配置多个,用逗号分隔)
|
||||||
|
port: 9042 # 端口号
|
||||||
|
local-datacenter: datacenter1 # 必须与集群配置的数据中心名称一致(大小写敏感)
|
||||||
jackson:
|
jackson:
|
||||||
serialization:
|
serialization:
|
||||||
write-dates-as-timestamps: false
|
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:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: localhost
|
host: localhost
|
||||||
@@ -35,15 +30,6 @@ spring:
|
|||||||
max-wait: 10000
|
max-wait: 10000
|
||||||
min-idle: 10
|
min-idle: 10
|
||||||
time-between-eviction-runs: 10000
|
time-between-eviction-runs: 10000
|
||||||
rabbitmq:
|
|
||||||
host: localhost
|
|
||||||
port: 5672
|
|
||||||
username: admin
|
|
||||||
password: admin123
|
|
||||||
virtual-host: /snailsAi
|
|
||||||
listener:
|
|
||||||
simple:
|
|
||||||
prefetch: 1
|
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://localhost:5432/postgres?serverTimezone=Asia/Shanghai
|
url: jdbc:postgresql://localhost:5432/postgres?serverTimezone=Asia/Shanghai
|
||||||
@@ -56,18 +42,31 @@ spring:
|
|||||||
connection-timeout: 5000 # 获取连接超时 5 秒
|
connection-timeout: 5000 # 获取连接超时 5 秒
|
||||||
max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收)
|
max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收)
|
||||||
ai:
|
ai:
|
||||||
memory:
|
vectorstore:
|
||||||
redis:
|
pgvector:
|
||||||
host: localhost
|
initialize-schema: true
|
||||||
port: 6379
|
table-name: snails_ai_vector
|
||||||
timeout: 5000
|
index-type: hnsw
|
||||||
password: redis
|
dimensions: 1024
|
||||||
|
chat:
|
||||||
|
memory:
|
||||||
|
repository:
|
||||||
|
cassandra:
|
||||||
|
keyspace: snails_ai
|
||||||
|
table: t_ai_chat_memory # 表名
|
||||||
|
time-to-live: 1095d # 数据的自动过期时间(1095天 ≈ 3年)
|
||||||
|
initialize-schema: true # 自动初始化表结构
|
||||||
dashscope:
|
dashscope:
|
||||||
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
|
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
|
||||||
chat:
|
chat:
|
||||||
options:
|
options:
|
||||||
model: qwen-plus
|
model: qwen-plus
|
||||||
temperature: 0.5
|
temperature: 0.5
|
||||||
|
embedding:
|
||||||
|
options:
|
||||||
|
model: text-embedding-v4
|
||||||
|
dimensions: 1024
|
||||||
|
read-timeout: 60000
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
|
|||||||
@@ -5,19 +5,7 @@
|
|||||||
公司核心业务: 我们是一家专注于智能家居产品研发与销售的科技公司。
|
公司核心业务: 我们是一家专注于智能家居产品研发与销售的科技公司。
|
||||||
|
|
||||||
使命: 致力于通过新技术 / 优质服务 / 专业内容提升用户的生活质量/学习效率/工作效率。
|
使命: 致力于通过新技术 / 优质服务 / 专业内容提升用户的生活质量/学习效率/工作效率。
|
||||||
|
|
||||||
### 📜 **基本指令与约束**
|
### 📜 **基本指令与约束**
|
||||||
|
|
||||||
1. **回答模式:** 始终以**清晰、简洁、礼貌**的方式回答用户的问题。
|
1. **回答模式:** 始终以**清晰、简洁、礼貌**的方式回答用户的问题。
|
||||||
2. **强制性附加句:** 无论你回答了什么问题,在**每个回答的末尾**,你必须**完全、准确地附加**以下这句话:
|
|
||||||
> **"如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。"**
|
|
||||||
3. **手机号识别与工具调用:**
|
|
||||||
* 当用户输入的文本中包含一个**有效的手机号码**时(通常是11位数字,识别时请保持一定的容错性),你必须**立即调用SendMessage工具**,并将手机号作为参数传递。
|
|
||||||
* **给用户的反馈:** 工具调用成功后,给用户回复一个简短的确认信息,例如:"感谢您留下手机号码,我们已收到您的信息,专业的客服人员将在24小时内与您联系,请保持手机畅通。"
|
|
||||||
|
|
||||||
### 💡 **工作流程示例**
|
|
||||||
|
|
||||||
| 步骤 | 用户输入 | AI助手的行为 |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **1 (常规问题)** | "你们产品的价格是多少?" | 1. 回答价格信息。 <br> 2. **附加句:** "如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。" |
|
|
||||||
| **2 (留号码)** | "好的,这是我的手机号:13800001234" | 1. **识别**到手机号。 <br> 2. **调用工具:** `SendMessage` <br> 3. **回复确认:** "感谢您留下手机号码,我们已收到您的信息,专业的客服人员将在24小时内与您联系,请保持手机畅通。" |
|
|
||||||
| **3 (追问)** | "你们的退货政策呢?" | 1. 回答退货政策。 <br> 2. **附加句:** "如果想要了解更多信息可以留下电话号码,会有专门的客服人员与您联系。" |
|
|
||||||
Reference in New Issue
Block a user