Commit c05d7c9f9521ad2b1d1534c66423f8ce4fa9f535
1 parent
6b651bae
【新增】AI:对话消息记录召回段落
Showing
2 changed files
with
48 additions
and
21 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.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 com.baomidou.mybatisplus.annotation.TableId; | ||
| 4 | -import org.springframework.ai.chat.messages.MessageType; | ||
| 5 | 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.AiKnowledgeSegmentDO; | ||
| 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | 5 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 8 | import com.baomidou.mybatisplus.annotation.KeySequence; | 7 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 8 | +import com.baomidou.mybatisplus.annotation.TableField; | ||
| 9 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 9 | import com.baomidou.mybatisplus.annotation.TableName; | 10 | import com.baomidou.mybatisplus.annotation.TableName; |
| 11 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | ||
| 10 | import lombok.*; | 12 | import lombok.*; |
| 13 | +import org.springframework.ai.chat.messages.MessageType; | ||
| 14 | + | ||
| 15 | +import java.util.List; | ||
| 11 | 16 | ||
| 12 | /** | 17 | /** |
| 13 | * AI Chat 消息 DO | 18 | * AI Chat 消息 DO |
| @@ -66,6 +71,15 @@ public class AiChatMessageDO extends BaseDO { | @@ -66,6 +71,15 @@ public class AiChatMessageDO extends BaseDO { | ||
| 66 | */ | 71 | */ |
| 67 | private Long roleId; | 72 | private Long roleId; |
| 68 | 73 | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 段落编号数组 | ||
| 77 | + * | ||
| 78 | + * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段 | ||
| 79 | + */ | ||
| 80 | + @TableField(typeHandler = JacksonTypeHandler.class) | ||
| 81 | + private List<Long> segmentIds; | ||
| 82 | + | ||
| 69 | /** | 83 | /** |
| 70 | * 模型标志 | 84 | * 模型标志 |
| 71 | */ | 85 | */ |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| @@ -90,13 +90,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -90,13 +90,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 90 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, | 90 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 91 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); | 91 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); |
| 92 | 92 | ||
| 93 | - // 3.2 创建 chat 需要的 Prompt | ||
| 94 | - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); | 93 | + // 3.2 召回段落 |
| 94 | + List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); | ||
| 95 | + | ||
| 96 | + // 3.3 创建 chat 需要的 Prompt | ||
| 97 | + Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); | ||
| 95 | ChatResponse chatResponse = chatModel.call(prompt); | 98 | ChatResponse chatResponse = chatModel.call(prompt); |
| 96 | 99 | ||
| 97 | - // 3.3 段式返回 | 100 | + // 3.4 段式返回 |
| 98 | String newContent = chatResponse.getResult().getOutput().getContent(); | 101 | String newContent = chatResponse.getResult().getOutput().getContent(); |
| 99 | - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); | 102 | + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent)); |
| 100 | return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) | 103 | return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 101 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); | 104 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); |
| 102 | } | 105 | } |
| @@ -121,11 +124,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -121,11 +124,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 121 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, | 124 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 122 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); | 125 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); |
| 123 | 126 | ||
| 124 | - // 3.2 构建 Prompt,并进行调用 | ||
| 125 | - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); | 127 | + |
| 128 | + // 3.2 召回段落 | ||
| 129 | + List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId()); | ||
| 130 | + | ||
| 131 | + // 3.3 构建 Prompt,并进行调用 | ||
| 132 | + Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); | ||
| 126 | Flux<ChatResponse> streamResponse = chatModel.stream(prompt); | 133 | Flux<ChatResponse> streamResponse = chatModel.stream(prompt); |
| 127 | 134 | ||
| 128 | - // 3.3 流式返回 | 135 | + // 3.4 流式返回 |
| 129 | // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 | 136 | // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 |
| 130 | StringBuffer contentBuffer = new StringBuffer(); | 137 | StringBuffer contentBuffer = new StringBuffer(); |
| 131 | return streamResponse.map(chunk -> { | 138 | return streamResponse.map(chunk -> { |
| @@ -138,7 +145,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -138,7 +145,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 138 | }).doOnComplete(() -> { | 145 | }).doOnComplete(() -> { |
| 139 | // 忽略租户,因为 Flux 异步无法透传租户 | 146 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 140 | TenantUtils.executeIgnore(() -> | 147 | TenantUtils.executeIgnore(() -> |
| 141 | - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); | 148 | + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)) |
| 149 | + .setContent(contentBuffer.toString()))); | ||
| 142 | }).doOnError(throwable -> { | 150 | }).doOnError(throwable -> { |
| 143 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); | 151 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |
| 144 | // 忽略租户,因为 Flux 异步无法透传租户 | 152 | // 忽略租户,因为 Flux 异步无法透传租户 |
| @@ -147,21 +155,26 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -147,21 +155,26 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 147 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); | 155 | }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); |
| 148 | } | 156 | } |
| 149 | 157 | ||
| 150 | - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, | 158 | + private List<AiKnowledgeSegmentDO> recallSegment(String content, Long knowledgeId) { |
| 159 | + List<AiKnowledgeSegmentDO> segmentList = new ArrayList<>(); | ||
| 160 | + if (Objects.nonNull(knowledgeId)) { | ||
| 161 | + segmentList = knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content)); | ||
| 162 | + } | ||
| 163 | + return segmentList; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,List<AiKnowledgeSegmentDO> segmentList, | ||
| 151 | AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { | 167 | AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { |
| 152 | // 1. 构建 Prompt Message 列表 | 168 | // 1. 构建 Prompt Message 列表 |
| 153 | List<Message> chatMessages = new ArrayList<>(); | 169 | List<Message> chatMessages = new ArrayList<>(); |
| 154 | 170 | ||
| 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 | - } | 171 | + // 1.1 召回内容消息构建 |
| 172 | + if (CollUtil.isNotEmpty(segmentList)) { | ||
| 173 | + PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage()); | ||
| 174 | + StringBuilder infoBuilder = StrUtil.builder(); | ||
| 175 | + segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent())); | ||
| 176 | + Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString())); | ||
| 177 | + chatMessages.add(message); | ||
| 165 | } | 178 | } |
| 166 | 179 | ||
| 167 | // 1.2 system context 角色设定 | 180 | // 1.2 system context 角色设定 |