Commit cddaca586360a3cb924190c9ade7c5a55a1a420c

Authored by YunaiV
1 parent 3bb57edc

【功能新增】AI:聊天时,增加知识库的拼接

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java
@@ -35,11 +35,7 @@ public enum AiChatRoleEnum { @@ -35,11 +35,7 @@ public enum AiChatRoleEnum {
35 ### 微信 35 ### 微信
36 除此之外不要任何解释性语句。 36 除此之外不要任何解释性语句。
37 """), 37 """),
38 -  
39 - AI_KNOWLEDGE_ROLE("知识库助手", """  
40 - 给你提供一些数据参考:{info},请回答我的问题。  
41 - 请你跟进数据参考与工具返回结果回复用户的请求。  
42 - """); 38 + ;
43 39
44 /** 40 /**
45 * 角色名 41 * 角色名
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;  
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;  
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 4 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 7 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 8 import com.baomidou.mybatisplus.annotation.TableName;
@@ -65,14 +64,6 @@ public class AiChatConversationDO extends BaseDO { @@ -65,14 +64,6 @@ public class AiChatConversationDO extends BaseDO {
65 */ 64 */
66 private Long roleId; 65 private Long roleId;
67 66
68 - // TODO @芋艿:可优化,绑定多个知识库。前提,spring ai 支持 RerankModel 的封装  
69 - /**  
70 - * 知识库编号  
71 - * <p>  
72 - * 关联 {@link AiKnowledgeDO#getId()}  
73 - */  
74 - private Long knowledgeId;  
75 -  
76 /** 67 /**
77 * 模型编号 68 * 模型编号
78 * 69 *
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.chat;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
4 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 5 import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;  
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  7 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableField; 9 import com.baomidou.mybatisplus.annotation.TableField;
9 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
10 import com.baomidou.mybatisplus.annotation.TableName; 11 import com.baomidou.mybatisplus.annotation.TableName;
11 -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;  
12 import lombok.*; 12 import lombok.*;
13 import org.springframework.ai.chat.messages.MessageType; 13 import org.springframework.ai.chat.messages.MessageType;
14 14
@@ -71,13 +71,12 @@ public class AiChatMessageDO extends BaseDO { @@ -71,13 +71,12 @@ public class AiChatMessageDO extends BaseDO {
71 */ 71 */
72 private Long roleId; 72 private Long roleId;
73 73
74 -  
75 /** 74 /**
76 - * 段落编号数组 75 + * 知识库段落编号数组
77 * 76 *
78 * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段 77 * 关联 {@link AiKnowledgeSegmentDO#getId()} 字段
79 */ 78 */
80 - @TableField(typeHandler = JacksonTypeHandler.class) 79 + @TableField(typeHandler = LongListTypeHandler.class)
81 private List<Long> segmentIds; 80 private List<Long> segmentIds;
82 81
83 /** 82 /**
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
@@ -68,7 +68,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @@ -68,7 +68,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
68 68
69 // 2. 创建 AiChatConversationDO 聊天对话 69 // 2. 创建 AiChatConversationDO 聊天对话
70 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) 70 AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false)
71 - .setModelId(model.getId()).setModel(model.getModel()).setKnowledgeId(createReqVO.getKnowledgeId()) 71 + .setModelId(model.getId()).setModel(model.getModel())
72 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); 72 .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts());
73 if (role != null) { 73 if (role != null) {
74 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); 74 conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage());
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -14,12 +14,14 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessage @@ -14,12 +14,14 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessage
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 15 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
17 -import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; 17 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; 18 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
19 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; 19 import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
20 -import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum;  
21 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; 20 import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants;
22 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; 21 import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService;
  22 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
  23 +import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
  24 +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
23 import cn.iocoder.yudao.module.ai.service.model.AiModelService; 25 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
24 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
25 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
@@ -32,13 +34,13 @@ import org.springframework.ai.chat.model.ChatResponse; @@ -32,13 +34,13 @@ import org.springframework.ai.chat.model.ChatResponse;
32 import org.springframework.ai.chat.model.StreamingChatModel; 34 import org.springframework.ai.chat.model.StreamingChatModel;
33 import org.springframework.ai.chat.prompt.ChatOptions; 35 import org.springframework.ai.chat.prompt.ChatOptions;
34 import org.springframework.ai.chat.prompt.Prompt; 36 import org.springframework.ai.chat.prompt.Prompt;
35 -import org.springframework.ai.chat.prompt.PromptTemplate;  
36 import org.springframework.stereotype.Service; 37 import org.springframework.stereotype.Service;
37 import org.springframework.transaction.annotation.Transactional; 38 import org.springframework.transaction.annotation.Transactional;
38 import reactor.core.publisher.Flux; 39 import reactor.core.publisher.Flux;
39 40
40 import java.time.LocalDateTime; 41 import java.time.LocalDateTime;
41 import java.util.*; 42 import java.util.*;
  43 +import java.util.stream.Collectors;
42 44
43 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; 46 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
@@ -56,12 +58,21 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N @@ -56,12 +58,21 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N
56 @Slf4j 58 @Slf4j
57 public class AiChatMessageServiceImpl implements AiChatMessageService { 59 public class AiChatMessageServiceImpl implements AiChatMessageService {
58 60
  61 + /**
  62 + * 知识库转 {@link UserMessage} 的内容模版
  63 + */
  64 + private static final String KNOWLEDGE_USER_MESSAGE_TEMPLATE = "使用 <Reference></Reference> 标记中的内容作为本次对话的参考:\n\n" +
  65 + "%s\n\n" + // 多个 <Reference></Reference> 的拼接
  66 + "回答要求:\n- 避免提及你是从 <Reference></Reference> 获取的知识。";
  67 +
59 @Resource 68 @Resource
60 private AiChatMessageMapper chatMessageMapper; 69 private AiChatMessageMapper chatMessageMapper;
61 70
62 @Resource 71 @Resource
63 private AiChatConversationService chatConversationService; 72 private AiChatConversationService chatConversationService;
64 @Resource 73 @Resource
  74 + private AiChatRoleService chatRoleService;
  75 + @Resource
65 private AiModelService modalService; 76 private AiModelService modalService;
66 @Resource 77 @Resource
67 private AiKnowledgeSegmentService knowledgeSegmentService; 78 private AiKnowledgeSegmentService knowledgeSegmentService;
@@ -69,118 +80,143 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -69,118 +80,143 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
69 @Transactional(rollbackFor = Exception.class) 80 @Transactional(rollbackFor = Exception.class)
70 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { 81 public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) {
71 // 1.1 校验对话存在 82 // 1.1 校验对话存在
72 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 83 + AiChatConversationDO conversation = chatConversationService
  84 + .validateChatConversationExists(sendReqVO.getConversationId());
73 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 85 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
74 throw exception(CHAT_CONVERSATION_NOT_EXISTS); 86 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
75 } 87 }
76 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 88 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
77 // 1.2 校验模型 89 // 1.2 校验模型
78 AiModelDO model = modalService.validateModel(conversation.getModelId()); 90 AiModelDO model = modalService.validateModel(conversation.getModelId());
79 - ChatModel chatModel = modalService.getChatModel(model.getKeyId()); 91 + ChatModel chatModel = modalService.getChatModel(model.getId());
  92 +
  93 + // 2. 知识库找回
  94 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  95 + conversation);
80 96
81 - // 2. 插入 user 发送消息 97 + // 3. 插入 user 发送消息
82 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 98 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
83 - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); 99 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  100 + null);
84 101
85 // 3.1 插入 assistant 接收消息 102 // 3.1 插入 assistant 接收消息
86 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, 103 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
87 - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); 104 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  105 + knowledgeSegments);
88 106
89 - // 3.2 召回段落  
90 - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId());  
91 -  
92 - // 3.3 创建 chat 需要的 Prompt  
93 - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); 107 + // 3.2 创建 chat 需要的 Prompt
  108 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
94 ChatResponse chatResponse = chatModel.call(prompt); 109 ChatResponse chatResponse = chatModel.call(prompt);
95 110
96 - // 3.4 段式返回 111 + // 3.3 段式返回
97 String newContent = chatResponse.getResult().getOutput().getText(); 112 String newContent = chatResponse.getResult().getOutput().getText();
98 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId)).setContent(newContent));  
99 - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))  
100 - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); 113 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent));
  114 + return new AiChatMessageSendRespVO()
  115 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  116 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  117 + .setContent(newContent));
101 } 118 }
102 119
103 @Override 120 @Override
104 - public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { 121 + public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO,
  122 + Long userId) {
105 // 1.1 校验对话存在 123 // 1.1 校验对话存在
106 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); 124 + AiChatConversationDO conversation = chatConversationService
  125 + .validateChatConversationExists(sendReqVO.getConversationId());
107 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 126 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
108 throw exception(CHAT_CONVERSATION_NOT_EXISTS); 127 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
109 } 128 }
110 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); 129 List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
111 // 1.2 校验模型 130 // 1.2 校验模型
112 AiModelDO model = modalService.validateModel(conversation.getModelId()); 131 AiModelDO model = modalService.validateModel(conversation.getModelId());
113 - StreamingChatModel chatModel = modalService.getChatModel(model.getKeyId()); 132 + StreamingChatModel chatModel = modalService.getChatModel(model.getId());
  133 +
  134 + // 2. 知识库找回
  135 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment(sendReqVO.getContent(),
  136 + conversation);
114 137
115 - // 2. 插入 user 发送消息 138 + // 3. 插入 user 发送消息
116 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, 139 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
117 - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); 140 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(),
  141 + null);
118 142
119 - // 3.1 插入 assistant 接收消息 143 + // 4.1 插入 assistant 接收消息
120 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, 144 AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
121 - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); 145 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(),
  146 + knowledgeSegments);
122 147
123 - // 3.2 召回段落  
124 - List<AiKnowledgeSegmentDO> segmentList = recallSegment(sendReqVO.getContent(), conversation.getKnowledgeId());  
125 -  
126 - // 3.3 构建 Prompt,并进行调用  
127 - Prompt prompt = buildPrompt(conversation, historyMessages, segmentList, model, sendReqVO); 148 + // 4.2 构建 Prompt,并进行调用
  149 + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO);
128 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 150 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
129 151
130 - // 3.4 流式返回 152 + // 4.3 流式返回
131 StringBuffer contentBuffer = new StringBuffer(); 153 StringBuffer contentBuffer = new StringBuffer();
132 return streamResponse.map(chunk -> { 154 return streamResponse.map(chunk -> {
133 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null; 155 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : null;
134 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 156 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
135 contentBuffer.append(newContent); 157 contentBuffer.append(newContent);
136 // 响应结果 158 // 响应结果
137 - return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))  
138 - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent))); 159 + return success(new AiChatMessageSendRespVO()
  160 + .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  161 + .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class)
  162 + .setContent(newContent)));
139 }).doOnComplete(() -> { 163 }).doOnComplete(() -> {
140 // 忽略租户,因为 Flux 异步无法透传租户 164 // 忽略租户,因为 Flux 异步无法透传租户
141 - TenantUtils.executeIgnore(() ->  
142 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setSegmentIds(convertList(segmentList, AiKnowledgeSegmentDO::getId))  
143 - .setContent(contentBuffer.toString()))); 165 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  166 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
144 }).doOnError(throwable -> { 167 }).doOnError(throwable -> {
145 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); 168 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
146 // 忽略租户,因为 Flux 异步无法透传租户 169 // 忽略租户,因为 Flux 异步无法透传租户
147 - TenantUtils.executeIgnore(() ->  
148 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()))); 170 + TenantUtils.executeIgnore(() -> chatMessageMapper.updateById(
  171 + new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
149 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); 172 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
150 } 173 }
151 174
152 - private List<AiKnowledgeSegmentDO> recallSegment(String content, Long knowledgeId) {  
153 - if (Objects.isNull(knowledgeId)) { 175 + private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content,
  176 + AiChatConversationDO conversation) {
  177 + // 1. 查询聊天角色
  178 + if (conversation == null || conversation.getRoleId() == null) {
  179 + return Collections.emptyList();
  180 + }
  181 + AiChatRoleDO role = chatRoleService.getChatRole(conversation.getRoleId());
  182 + if (role == null || CollUtil.isEmpty(role.getKnowledgeIds())) {
154 return Collections.emptyList(); 183 return Collections.emptyList();
155 } 184 }
156 -// return knowledgeSegmentService.similaritySearch(new AiKnowledgeSegmentSearchReqVO().setKnowledgeId(knowledgeId).setContent(content));  
157 - return null;  
158 - }  
159 -  
160 - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, List<AiKnowledgeSegmentDO> segmentList,  
161 - AiModelDO model, AiChatMessageSendReqVO sendReqVO) {  
162 - // 1. 构建 Prompt Message 列表  
163 - List<Message> chatMessages = new ArrayList<>();  
164 185
165 - // 1.1 召回内容消息构建  
166 - if (CollUtil.isNotEmpty(segmentList)) {  
167 - PromptTemplate promptTemplate = new PromptTemplate(AiChatRoleEnum.AI_KNOWLEDGE_ROLE.getSystemMessage());  
168 - StringBuilder infoBuilder = StrUtil.builder();  
169 - segmentList.forEach(segment -> infoBuilder.append(System.lineSeparator()).append(segment.getContent()));  
170 - Message message = promptTemplate.createMessage(Map.of("info", infoBuilder.toString()));  
171 - chatMessages.add(message); 186 + // 2. 遍历找回
  187 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = new ArrayList<>();
  188 + for (Long knowledgeId : role.getKnowledgeIds()) {
  189 + knowledgeSegments.addAll(knowledgeSegmentService.searchKnowledgeSegment(new AiKnowledgeSegmentSearchReqBO()
  190 + .setKnowledgeId(knowledgeId).setContent(content)));
172 } 191 }
  192 + return knowledgeSegments;
  193 + }
173 194
174 - // 1.2 system context 角色设定 195 + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
  196 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments,
  197 + AiModelDO model, AiChatMessageSendReqVO sendReqVO) {
  198 + List<Message> chatMessages = new ArrayList<>();
  199 + // 1.1 System Context 角色设定
175 if (StrUtil.isNotBlank(conversation.getSystemMessage())) { 200 if (StrUtil.isNotBlank(conversation.getSystemMessage())) {
176 chatMessages.add(new SystemMessage(conversation.getSystemMessage())); 201 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
177 } 202 }
178 - // 1.3 history message 历史消息 203 +
  204 + // 1.2 历史 history message 历史消息
179 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO); 205 List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
180 - contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));  
181 - // 1.4 user message 新发送消息 206 + contextMessages
  207 + .forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent())));
  208 +
  209 + // 1.3 当前 user message 新发送消息
182 chatMessages.add(new UserMessage(sendReqVO.getContent())); 210 chatMessages.add(new UserMessage(sendReqVO.getContent()));
183 211
  212 + // 1.4 知识库,通过 UserMessage 实现
  213 + if (CollUtil.isNotEmpty(knowledgeSegments)) {
  214 + String reference = knowledgeSegments.stream()
  215 + .map(segment -> "<Reference>\n" + segment.getContent() + "</Reference>")
  216 + .collect(Collectors.joining("\n\n"));
  217 + chatMessages.add(new UserMessage(String.format(KNOWLEDGE_USER_MESSAGE_TEMPLATE, reference)));
  218 + }
  219 +
184 // 2. 构建 ChatOptions 对象 220 // 2. 构建 ChatOptions 对象
185 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 221 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
186 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), 222 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
@@ -199,8 +235,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -199,8 +235,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
199 * @return 消息上下文 235 * @return 消息上下文
200 */ 236 */
201 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, 237 private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages,
202 - AiChatConversationDO conversation,  
203 - AiChatMessageSendReqVO sendReqVO) { 238 + AiChatConversationDO conversation,
  239 + AiChatMessageSendReqVO sendReqVO) {
204 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { 240 if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) {
205 return Collections.emptyList(); 241 return Collections.emptyList();
206 } 242 }
@@ -211,7 +247,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -211,7 +247,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
211 continue; 247 continue;
212 } 248 }
213 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); 249 AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
214 - if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) 250 + if (userMessage == null
  251 + || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
215 || StrUtil.isEmpty(assistantMessage.getContent())) { 252 || StrUtil.isEmpty(assistantMessage.getContent())) {
216 continue; 253 continue;
217 } 254 }
@@ -228,11 +265,13 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -228,11 +265,13 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
228 } 265 }
229 266
230 private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, 267 private AiChatMessageDO createChatMessage(Long conversationId, Long replyId,
231 - AiModelDO model, Long userId, Long roleId,  
232 - MessageType messageType, String content, Boolean useContext) { 268 + AiModelDO model, Long userId, Long roleId,
  269 + MessageType messageType, String content, Boolean useContext,
  270 + List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments) {
233 AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) 271 AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId)
234 .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) 272 .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId)
235 - .setType(messageType.getValue()).setContent(content).setUseContext(useContext); 273 + .setType(messageType.getValue()).setContent(content).setUseContext(useContext)
  274 + .setSegmentIds(convertList(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getId));
236 message.setCreateTime(LocalDateTime.now()); 275 message.setCreateTime(LocalDateTime.now());
237 chatMessageMapper.insert(message); 276 chatMessageMapper.insert(message);
238 return message; 277 return message;