Commit c05d7c9f9521ad2b1d1534c66423f8ce4fa9f535

Authored by xiaoxin
1 parent 6b651bae

【新增】AI:对话消息记录召回段落

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 角色设定
... ...