Commit 0700c3f15eb38e4f6b39d07e7632a75641def31a

Authored by xiaoxin
1 parent 5cd87074

【新增】AI:聊天接入知识库

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
@@ -34,7 +34,12 @@ public enum AiChatRoleEnum { @@ -34,7 +34,12 @@ public enum AiChatRoleEnum {
34 ### 支付宝 34 ### 支付宝
35 ### 微信 35 ### 微信
36 除此之外不要任何解释性语句。 36 除此之外不要任何解释性语句。
37 - """); 37 + """),
  38 +
  39 + AI_KNOWLEDGE_ROLE("知识库助手", """
  40 + 给你提供一些数据参考:{info},请回答我的问题。
  41 + 请你跟进数据参考与工具返回结果回复用户的请求。
  42 + """);
38 43
39 /** 44 /**
40 * 角色名 45 * 角色名
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java
@@ -10,4 +10,7 @@ public class AiChatConversationCreateMyReqVO { @@ -10,4 +10,7 @@ public class AiChatConversationCreateMyReqVO {
10 @Schema(description = "聊天角色编号", example = "666") 10 @Schema(description = "聊天角色编号", example = "666")
11 private Long roleId; 11 private Long roleId;
12 12
  13 + @Schema(description = "知识库编号", example = "1204")
  14 + private Long knowledgeId;
  15 +
13 } 16 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java
@@ -21,6 +21,9 @@ public class AiChatConversationUpdateMyReqVO { @@ -21,6 +21,9 @@ public class AiChatConversationUpdateMyReqVO {
21 @Schema(description = "模型编号", example = "1") 21 @Schema(description = "模型编号", example = "1")
22 private Long modelId; 22 private Long modelId;
23 23
  24 + @Schema(description = "知识库编号", example = "1")
  25 + private Long knowledgeId;
  26 +
24 @Schema(description = "角色设定", example = "一个快乐的程序员") 27 @Schema(description = "角色设定", example = "一个快乐的程序员")
25 private String systemMessage; 28 private String systemMessage;
26 29
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -65,6 +66,13 @@ public class AiChatConversationDO extends BaseDO { @@ -65,6 +66,13 @@ public class AiChatConversationDO extends BaseDO {
65 private Long roleId; 66 private Long roleId;
66 67
67 /** 68 /**
  69 + * 知识库编号
  70 + * <p>
  71 + * 关联 {@link AiKnowledgeDO#getId()}
  72 + */
  73 + private Long knowledgeId;
  74 +
  75 + /**
68 * 模型编号 76 * 模型编号
69 * 77 *
70 * 关联 {@link AiChatModelDO#getId()} 字段 78 * 关联 {@link AiChatModelDO#getId()} 字段
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
13 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 13 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
14 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 14 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
15 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; 15 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper;
  16 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
16 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 17 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
17 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 18 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
18 import jakarta.annotation.Resource; 19 import jakarta.annotation.Resource;
@@ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated; @@ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated;
22 23
23 import java.time.LocalDateTime; 24 import java.time.LocalDateTime;
24 import java.util.List; 25 import java.util.List;
  26 +import java.util.Objects;
25 27
26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -45,6 +47,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -45,6 +47,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
45 private AiChatModelService chatModalService; 47 private AiChatModelService chatModalService;
46 @Resource 48 @Resource
47 private AiChatRoleService chatRoleService; 49 private AiChatRoleService chatRoleService;
  50 + @Resource
  51 + private AiKnowledgeService knowledgeService;
48 52
49 @Override 53 @Override
50 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { 54 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) {
@@ -56,9 +60,14 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -56,9 +60,14 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
56 Assert.notNull(model, "必须找到默认模型"); 60 Assert.notNull(model, "必须找到默认模型");
57 validateChatModel(model); 61 validateChatModel(model);
58 62
  63 + // 1.3 校验知识库
  64 + if (Objects.nonNull(createReqVO.getKnowledgeId())) {
  65 + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
  66 + }
  67 +
59 // 2. 创建 AiChatConversationDO 聊天对话 68 // 2. 创建 AiChatConversationDO 聊天对话
60 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) 69 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false)
61 - .setModelId(model.getId()).setModel(model.getModel()) 70 + .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId())
62 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); 71 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
63 if (role != null) { 72 if (role != null) {
64 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); 73 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
@@ -82,6 +91,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -82,6 +91,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
82 model = chatModalService.validateChatModel(updateReqVO.getModelId()); 91 model = chatModalService.validateChatModel(updateReqVO.getModelId());
83 } 92 }
84 93
  94 + // 1.3 校验知识库是否存在
  95 + if (updateReqVO.getKnowledgeId() != null) {
  96 + knowledgeService.validateKnowledgeExists(updateReqVO.getKnowledgeId());
  97 + }
  98 +
85 // 2. 更新对话信息 99 // 2. 更新对话信息
86 AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); 100 AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class);
87 if (Boolean.TRUE.equals(updateReqVO.getPinned())) { 101 if (Boolean.TRUE.equals(updateReqVO.getPinned())) {
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -12,21 +12,29 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; @@ -12,21 +12,29 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
  15 +import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSearchReqVO;
15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  18 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
18 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; 20 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
  21 +import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 22 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
  23 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
20 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 24 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
21 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; 25 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
22 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
23 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
24 -import org.springframework.ai.chat.messages.*; 28 +import org.springframework.ai.chat.messages.Message;
  29 +import org.springframework.ai.chat.messages.MessageType;
  30 +import org.springframework.ai.chat.messages.SystemMessage;
  31 +import org.springframework.ai.chat.messages.UserMessage;
25 import org.springframework.ai.chat.model.ChatModel; 32 import org.springframework.ai.chat.model.ChatModel;
26 import org.springframework.ai.chat.model.ChatResponse; 33 import org.springframework.ai.chat.model.ChatResponse;
27 import org.springframework.ai.chat.model.StreamingChatModel; 34 import org.springframework.ai.chat.model.StreamingChatModel;
28 import org.springframework.ai.chat.prompt.ChatOptions; 35 import org.springframework.ai.chat.prompt.ChatOptions;
29 import org.springframework.ai.chat.prompt.Prompt; 36 import org.springframework.ai.chat.prompt.Prompt;
  37 +import org.springframework.ai.chat.prompt.PromptTemplate;
30 import org.springframework.stereotype.Service; 38 import org.springframework.stereotype.Service;
31 import org.springframework.transaction.annotation.Transactional; 39 import org.springframework.transaction.annotation.Transactional;
32 import reactor.core.publisher.Flux; 40 import reactor.core.publisher.Flux;
@@ -59,6 +67,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -59,6 +67,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
59 private AiChatModelService chatModalService; 67 private AiChatModelService chatModalService;
60 @Resource 68 @Resource
61 private AiApiKeyService apiKeyService; 69 private AiApiKeyService apiKeyService;
  70 + @Resource
  71 + private AiKnowledgeSegmentService knowledgeSegmentService;
62 72
63 @Transactional(rollbackFor = Exception.class) 73 @Transactional(rollbackFor = Exception.class)
64 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { 74 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
@@ -141,14 +151,27 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -141,14 +151,27 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
141 AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { 151 AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
142 // 1. 构建 Prompt Message 列表 152 // 1. 构建 Prompt Message 列表
143 List<Message> chatMessages = new ArrayList<>(); 153 List<Message> chatMessages = new ArrayList<>();
144 - // 1.1 system context 角色设定 154 +
  155 + // 1.1 知识库召回
  156 + if (Objects.nonNull(conversation.getKnowledgeId())) {
  157 + List<AiKnowledgeSegmentDO> segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(conversation.getKnowledgeId()).setContent(sendReqVO.getContent()));
  158 + if (CollUtil.isNotEmpty(segmentList)) {
  159 + PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage());
  160 + StringBuilder infoBuilder = StrUtil.builder();
  161 + segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent()));
  162 + Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString()));
  163 + chatMessages.add(message);
  164 + }
  165 + }
  166 +
  167 + // 1.2 system context 角色设定
145 if (StrUtil.isNotBlank(conversation.getSystemMessage())) { 168 if (StrUtil.isNotBlank(conversation.getSystemMessage())) {
146 chatMessages.add(new SystemMessage(conversation.getSystemMessage())); 169 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
147 } 170 }
148 - // 1.2 history message 历史消息 171 + // 1.3 history message 历史消息
149 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); 172 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
150 contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent()))); 173 contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
151 - // 1.3 user message 新发送消息 174 + // 1.4 user message 新发送消息
152 chatMessages.add(new UserMessage(sendReqVO.getContent())); 175 chatMessages.add(new UserMessage(sendReqVO.getContent()));
153 176
154 // 2. 构建 ChatOptions 对象 177 // 2. 构建 ChatOptions 对象
@@ -160,12 +183,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -160,12 +183,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
160 183
161 /** 184 /**
162 * 从历史消息中,获得倒序的 n 组消息作为消息上下文 185 * 从历史消息中,获得倒序的 n 组消息作为消息上下文
163 - * 186 + * <p>
164 * n 组:指的是 user + assistant 形成一组 187 * n 组:指的是 user + assistant 形成一组
165 * 188 *
166 - * @param messages 消息列表 189 + * @param messages 消息列表
167 * @param conversation 对话 190 * @param conversation 对话
168 - * @param sendReqVO 发送请求 191 + * @param sendReqVO 发送请求
169 * @return 消息上下文 192 * @return 消息上下文
170 */ 193 */
171 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, 194 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
@@ -182,7 +205,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -182,7 +205,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
182 } 205 }
183 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); 206 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
184 if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) 207 if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
185 - || StrUtil.isEmpty(assistantMessage.getContent())) { 208 + || StrUtil.isEmpty(assistantMessage.getContent())) {
186 continue; 209 continue;
187 } 210 }
188 // 由于后续要 reverse 反转,所以先添加 assistantMessage 211 // 由于后续要 reverse 反转,所以先添加 assistantMessage