Commit 0700c3f15eb38e4f6b39d07e7632a75641def31a
1 parent
5cd87074
【新增】AI:聊天接入知识库
Showing
6 changed files
with
66 additions
and
10 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java
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 | ... | ... |