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 | 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 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; | |
| 6 | 5 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 7 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 8 | 7 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 8 | +import com.baomidou.mybatisplus.annotation.TableField; | |
| 9 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 9 | 10 | import com.baomidou.mybatisplus.annotation.TableName; |
| 11 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
| 10 | 12 | import lombok.*; |
| 13 | +import org.springframework.ai.chat.messages.MessageType; | |
| 14 | + | |
| 15 | +import java.util.List; | |
| 11 | 16 | |
| 12 | 17 | /** |
| 13 | 18 | * AI Chat 消息 DO |
| ... | ... | @@ -66,6 +71,15 @@ public class AiChatMessageDO extends BaseDO { |
| 66 | 71 | */ |
| 67 | 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 | 90 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 91 | 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 | 98 | ChatResponse chatResponse = chatModel.call(prompt); |
| 96 | 99 | |
| 97 | - // 3.3 段式返回 | |
| 100 | + // 3.4 段式返回 | |
| 98 | 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 | 103 | return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 101 | 104 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); |
| 102 | 105 | } |
| ... | ... | @@ -121,11 +124,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 121 | 124 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 122 | 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 | 133 | Flux<ChatResponse> streamResponse = chatModel.stream(prompt); |
| 127 | 134 | |
| 128 | - // 3.3 流式返回 | |
| 135 | + // 3.4 流式返回 | |
| 129 | 136 | // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 |
| 130 | 137 | StringBuffer contentBuffer = new StringBuffer(); |
| 131 | 138 | return streamResponse.map(chunk -> { |
| ... | ... | @@ -138,7 +145,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 138 | 145 | }).doOnComplete(() -> { |
| 139 | 146 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 140 | 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 | 150 | }).doOnError(throwable -> { |
| 143 | 151 | log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); |
| 144 | 152 | // 忽略租户,因为 Flux 异步无法透传租户 |
| ... | ... | @@ -147,21 +155,26 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 147 | 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 | 167 | AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { |
| 152 | 168 | // 1. 构建 Prompt Message 列表 |
| 153 | 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 | 180 | // 1.2 system context 角色设定 | ... | ... |