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