Commit 32e1ef4da81848179bd5f7e6c098f1f0bc4c0ddc
1 parent
b709af11
【功能新增】AI:聊天记录返回时,增加 segments
Showing
2 changed files
with
41 additions
and
16 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
| @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; | @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | import lombok.Data; | 4 | import lombok.Data; |
| 5 | 5 | ||
| 6 | import java.time.LocalDateTime; | 6 | import java.time.LocalDateTime; |
| 7 | +import java.util.List; | ||
| 7 | 8 | ||
| 8 | @Schema(description = "管理后台 - AI 聊天消息发送 Response VO") | 9 | @Schema(description = "管理后台 - AI 聊天消息发送 Response VO") |
| 9 | @Data | 10 | @Data |
| @@ -28,6 +29,12 @@ public class AiChatMessageSendRespVO { | @@ -28,6 +29,12 @@ public class AiChatMessageSendRespVO { | ||
| 28 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") | 29 | @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") |
| 29 | private String content; | 30 | private String content; |
| 30 | 31 | ||
| 32 | + @Schema(description = "知识库段落编号数组", example = "[1,2,3]") | ||
| 33 | + private List<Long> segmentIds; | ||
| 34 | + | ||
| 35 | + @Schema(description = "知识库段落数组") | ||
| 36 | + private List<AiChatMessageRespVO.KnowledgeSegment> segments; | ||
| 37 | + | ||
| 31 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | 38 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 32 | private LocalDateTime createTime; | 39 | private LocalDateTime createTime; |
| 33 | 40 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| @@ -10,14 +10,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -10,14 +10,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | 11 | 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.AiChatMessageRespVO; | ||
| 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.AiChatMessageSendReqVO; |
| 14 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; | 15 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; |
| 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.AiKnowledgeDocumentDO; | ||
| 17 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | 19 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 18 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; | 20 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| 19 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; | 21 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; |
| 20 | 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.AiKnowledgeDocumentService; | ||
| 21 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; | 24 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 22 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; | 25 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| 23 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; | 26 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; |
| @@ -76,6 +79,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -76,6 +79,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 76 | private AiModelService modalService; | 79 | private AiModelService modalService; |
| 77 | @Resource | 80 | @Resource |
| 78 | private AiKnowledgeSegmentService knowledgeSegmentService; | 81 | private AiKnowledgeSegmentService knowledgeSegmentService; |
| 82 | + @Resource | ||
| 83 | + private AiKnowledgeDocumentService knowledgeDocumentService; | ||
| 79 | 84 | ||
| 80 | @Transactional(rollbackFor = Exception.class) | 85 | @Transactional(rollbackFor = Exception.class) |
| 81 | public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { | 86 | public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { |
| @@ -108,18 +113,23 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -108,18 +113,23 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 108 | Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); | 113 | Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); |
| 109 | ChatResponse chatResponse = chatModel.call(prompt); | 114 | ChatResponse chatResponse = chatModel.call(prompt); |
| 110 | 115 | ||
| 111 | - // 3.3 段式返回 | 116 | + // 3.3 更新响应内容 |
| 112 | String newContent = chatResponse.getResult().getOutput().getText(); | 117 | String newContent = chatResponse.getResult().getOutput().getText(); |
| 113 | chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); | 118 | chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); |
| 119 | + // 3.4 响应结果 | ||
| 120 | + List<AiChatMessageRespVO.KnowledgeSegment> segments = BeanUtils.toBean(knowledgeSegments, AiChatMessageRespVO.KnowledgeSegment.class, | ||
| 121 | + segment -> { | ||
| 122 | + AiKnowledgeDocumentDO document = knowledgeDocumentService.getKnowledgeDocument(segment.getDocumentId()); | ||
| 123 | + segment.setDocumentName(document != null ? document.getName() : null); | ||
| 124 | + }); | ||
| 114 | return new AiChatMessageSendRespVO() | 125 | return new AiChatMessageSendRespVO() |
| 115 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) | 126 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 116 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) | 127 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) |
| 117 | - .setContent(newContent)); | 128 | + .setContent(newContent).setSegments(segments)); |
| 118 | } | 129 | } |
| 119 | 130 | ||
| 120 | @Override | 131 | @Override |
| 121 | - public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, | ||
| 122 | - Long userId) { | 132 | + public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { |
| 123 | // 1.1 校验对话存在 | 133 | // 1.1 校验对话存在 |
| 124 | AiChatConversationDO conversation = chatConversationService | 134 | AiChatConversationDO conversation = chatConversationService |
| 125 | .validateChatConversationExists(sendReqVO.getConversationId()); | 135 | .validateChatConversationExists(sendReqVO.getConversationId()); |
| @@ -132,8 +142,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -132,8 +142,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 132 | StreamingChatModel chatModel = modalService.getChatModel(model.getId()); | 142 | StreamingChatModel chatModel = modalService.getChatModel(model.getId()); |
| 133 | 143 | ||
| 134 | // 2. 知识库找回 | 144 | // 2. 知识库找回 |
| 135 | - List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(), | ||
| 136 | - conversation); | 145 | + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(), conversation); |
| 137 | 146 | ||
| 138 | // 3. 插入 user 发送消息 | 147 | // 3. 插入 user 发送消息 |
| 139 | AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, | 148 | AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, |
| @@ -152,14 +161,23 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -152,14 +161,23 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 152 | // 4.3 流式返回 | 161 | // 4.3 流式返回 |
| 153 | StringBuffer contentBuffer = new StringBuffer(); | 162 | StringBuffer contentBuffer = new StringBuffer(); |
| 154 | return streamResponse.map(chunk -> { | 163 | return streamResponse.map(chunk -> { |
| 164 | + // 处理知识库的返回,只有首次才有 | ||
| 165 | + List<AiChatMessageRespVO.KnowledgeSegment> segments = null; | ||
| 166 | + if (StrUtil.isEmpty(contentBuffer)) { | ||
| 167 | + segments = BeanUtils.toBean(knowledgeSegments, AiChatMessageRespVO.KnowledgeSegment.class, | ||
| 168 | + segment -> TenantUtils.executeIgnore(() -> { | ||
| 169 | + AiKnowledgeDocumentDO document = knowledgeDocumentService.getKnowledgeDocument(segment.getDocumentId()); | ||
| 170 | + segment.setDocumentName(document != null ? document.getName() : null); | ||
| 171 | + })); | ||
| 172 | + } | ||
| 173 | + // 响应结果 | ||
| 155 | String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; | 174 | String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; |
| 156 | newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 | 175 | newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 |
| 157 | contentBuffer.append(newContent); | 176 | contentBuffer.append(newContent); |
| 158 | - // 响应结果 | ||
| 159 | return success(new AiChatMessageSendRespVO() | 177 | return success(new AiChatMessageSendRespVO() |
| 160 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) | 178 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 161 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) | 179 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) |
| 162 | - .setContent(newContent))); | 180 | + .setContent(newContent).setSegments(segments))); |
| 163 | }).doOnComplete(() -> { | 181 | }).doOnComplete(() -> { |
| 164 | // 忽略租户,因为 Flux 异步无法透传租户 | 182 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 165 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( | 183 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( |
| @@ -173,7 +191,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -173,7 +191,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 173 | } | 191 | } |
| 174 | 192 | ||
| 175 | private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content, | 193 | private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content, |
| 176 | - AiChatConversationDO conversation) { | 194 | + AiChatConversationDO conversation) { |
| 177 | // 1. 查询聊天角色 | 195 | // 1. 查询聊天角色 |
| 178 | if (conversation == null || conversation.getRoleId() == null) { | 196 | if (conversation == null || conversation.getRoleId() == null) { |
| 179 | return Collections.emptyList(); | 197 | return Collections.emptyList(); |
| @@ -193,8 +211,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -193,8 +211,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 193 | } | 211 | } |
| 194 | 212 | ||
| 195 | private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, | 213 | private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, |
| 196 | - List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments, | ||
| 197 | - AiModelDO model, AiChatMessageSendReqVO sendReqVO) { | 214 | + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments, |
| 215 | + AiModelDO model, AiChatMessageSendReqVO sendReqVO) { | ||
| 198 | List<Message> chatMessages = new ArrayList<>(); | 216 | List<Message> chatMessages = new ArrayList<>(); |
| 199 | // 1.1 System Context 角色设定 | 217 | // 1.1 System Context 角色设定 |
| 200 | if (StrUtil.isNotBlank(conversation.getSystemMessage())) { | 218 | if (StrUtil.isNotBlank(conversation.getSystemMessage())) { |
| @@ -235,8 +253,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -235,8 +253,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 235 | * @return 消息上下文 | 253 | * @return 消息上下文 |
| 236 | */ | 254 | */ |
| 237 | private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, | 255 | private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, |
| 238 | - AiChatConversationDO conversation, | ||
| 239 | - AiChatMessageSendReqVO sendReqVO) { | 256 | + AiChatConversationDO conversation, |
| 257 | + AiChatMessageSendReqVO sendReqVO) { | ||
| 240 | if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { | 258 | if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { |
| 241 | return Collections.emptyList(); | 259 | return Collections.emptyList(); |
| 242 | } | 260 | } |
| @@ -265,9 +283,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | @@ -265,9 +283,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { | ||
| 265 | } | 283 | } |
| 266 | 284 | ||
| 267 | private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, | 285 | private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, |
| 268 | - AiModelDO model, Long userId, Long roleId, | ||
| 269 | - MessageType messageType, String content, Boolean useContext, | ||
| 270 | - List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments) { | 286 | + AiModelDO model, Long userId, Long roleId, |
| 287 | + MessageType messageType, String content, Boolean useContext, | ||
| 288 | + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments) { | ||
| 271 | AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) | 289 | AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) |
| 272 | .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) | 290 | .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) |
| 273 | .setType(messageType.getValue()).setContent(content).setUseContext(useContext) | 291 | .setType(messageType.getValue()).setContent(content).setUseContext(useContext) |