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 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 10 @Schema(description = "聊天角色编号", example = "666")
11 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 21 @Schema(description = "模型编号", example = "1")
22 22 private Long modelId;
23 23  
  24 + @Schema(description = "知识库编号", example = "1")
  25 + private Long knowledgeId;
  26 +
24 27 @Schema(description = "角色设定", example = "一个快乐的程序员")
25 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 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
4 5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
5 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
6 7 import com.baomidou.mybatisplus.annotation.KeySequence;
... ... @@ -65,6 +66,13 @@ public class AiChatConversationDO extends BaseDO {
65 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 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 13 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
14 14 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
15 15 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper;
  16 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService;
16 17 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
17 18 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
18 19 import jakarta.annotation.Resource;
... ... @@ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated;
22 23  
23 24 import java.time.LocalDateTime;
24 25 import java.util.List;
  26 +import java.util.Objects;
25 27  
26 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -45,6 +47,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
45 47 private AiChatModelService chatModalService;
46 48 @Resource
47 49 private AiChatRoleService chatRoleService;
  50 + @Resource
  51 + private AiKnowledgeService knowledgeService;
48 52  
49 53 @Override
50 54 public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) {
... ... @@ -56,9 +60,14 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
56 60 Assert.notNull(model, "必须找到默认模型");
57 61 validateChatModel(model);
58 62  
  63 + // 1.3 校验知识库
  64 + if (Objects.nonNull(createReqVO.getKnowledgeId())) {
  65 + knowledgeService.validateKnowledgeExists(createReqVO.getKnowledgeId());
  66 + }
  67 +
59 68 // 2. 创建 AiChatConversationDO 聊天对话
60 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 71 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
63 72 if (role != null) {
64 73 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
... ... @@ -82,6 +91,11 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
82 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 99 // 2. 更新对话信息
86 100 AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class);
87 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 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
13 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
14 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 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
16 17 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  18 +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
17 19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
18 20 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
  21 +import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;
19 22 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
  23 +import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
20 24 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
21 25 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
22 26 import jakarta.annotation.Resource;
23 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 32 import org.springframework.ai.chat.model.ChatModel;
26 33 import org.springframework.ai.chat.model.ChatResponse;
27 34 import org.springframework.ai.chat.model.StreamingChatModel;
28 35 import org.springframework.ai.chat.prompt.ChatOptions;
29 36 import org.springframework.ai.chat.prompt.Prompt;
  37 +import org.springframework.ai.chat.prompt.PromptTemplate;
30 38 import org.springframework.stereotype.Service;
31 39 import org.springframework.transaction.annotation.Transactional;
32 40 import reactor.core.publisher.Flux;
... ... @@ -59,6 +67,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
59 67 private AiChatModelService chatModalService;
60 68 @Resource
61 69 private AiApiKeyService apiKeyService;
  70 + @Resource
  71 + private AiKnowledgeSegmentService knowledgeSegmentService;
62 72  
63 73 @Transactional(rollbackFor = Exception.class)
64 74 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
... ... @@ -141,14 +151,27 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
141 151 AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
142 152 // 1. 构建 Prompt Message 列表
143 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 168 if (StrUtil.isNotBlank(conversation.getSystemMessage())) {
146 169 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
147 170 }
148   - // 1.2 history message 历史消息
  171 + // 1.3 history message 历史消息
149 172 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
150 173 contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
151   - // 1.3 user message 新发送消息
  174 + // 1.4 user message 新发送消息
152 175 chatMessages.add(new UserMessage(sendReqVO.getContent()));
153 176  
154 177 // 2. 构建 ChatOptions 对象
... ... @@ -160,12 +183,12 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
160 183  
161 184 /**
162 185 * 从历史消息中,获得倒序的 n 组消息作为消息上下文
163   - *
  186 + * <p>
164 187 * n 组:指的是 user + assistant 形成一组
165 188 *
166   - * @param messages 消息列表
  189 + * @param messages 消息列表
167 190 * @param conversation 对话
168   - * @param sendReqVO 发送请求
  191 + * @param sendReqVO 发送请求
169 192 * @return 消息上下文
170 193 */
171 194 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
... ... @@ -182,7 +205,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
182 205 }
183 206 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
184 207 if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
185   - || StrUtil.isEmpty(assistantMessage.getContent())) {
  208 + || StrUtil.isEmpty(assistantMessage.getContent())) {
186 209 continue;
187 210 }
188 211 // 由于后续要 reverse 反转,所以先添加 assistantMessage
... ...