Commit 32e1ef4da81848179bd5f7e6c098f1f0bc4c0ddc

Authored by YunaiV
1 parent b709af11

【功能新增】AI:聊天记录返回时,增加 segments

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)