Commit 802dee2fc353f41d16fb4c7176f7ededaf6f0fa2

Authored by YunaiV
1 parent 3a10fedd

【新增】AI:发送消息时,增加上下文

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java
@@ -19,4 +19,7 @@ public class AiChatMessageSendReqVO { @@ -19,4 +19,7 @@ public class AiChatMessageSendReqVO {
19 @NotEmpty(message = "聊天内容不能为空") 19 @NotEmpty(message = "聊天内容不能为空")
20 private String content; 20 private String content;
21 21
  22 + @Schema(description = "是否携带上下文", example = "true")
  23 + private Boolean useContext;
  24 +
22 } 25 }
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 com.baomidou.mybatisplus.annotation.TableId;
3 import org.springframework.ai.chat.messages.MessageType; 4 import org.springframework.ai.chat.messages.MessageType;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
@@ -27,14 +28,23 @@ public class AiChatMessageDO extends BaseDO { @@ -27,14 +28,23 @@ public class AiChatMessageDO extends BaseDO {
27 /** 28 /**
28 * 编号,作为每条聊天记录的唯一标识符 29 * 编号,作为每条聊天记录的唯一标识符
29 */ 30 */
  31 + @TableId
30 private Long id; 32 private Long id;
31 33
32 /** 34 /**
33 * 会话编号 35 * 会话编号
34 * 36 *
35 - * 关联 {@link AiChatConversationDO#getId()} 37 + * 关联 {@link AiChatConversationDO#getId()} 字段
36 */ 38 */
37 private Long conversationId; 39 private Long conversationId;
  40 + /**
  41 + * 回复消息编号
  42 + *
  43 + * 关联 {@link #id} 字段
  44 + *
  45 + * 大模型回复的消息编号,用于“问答”的关联
  46 + */
  47 + private Long replyId;
38 48
39 /** 49 /**
40 * 消息类型 50 * 消息类型
@@ -75,6 +85,9 @@ public class AiChatMessageDO extends BaseDO { @@ -75,6 +85,9 @@ public class AiChatMessageDO extends BaseDO {
75 */ 85 */
76 private String content; 86 private String content;
77 87
78 - // TODO 芋艿:是否作为上下文语料?use_context,待定 88 + /**
  89 + * 是否携带上下文
  90 + */
  91 + private Boolean useContext;
79 92
80 } 93 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.impl; 1 package cn.iocoder.yudao.module.ai.service.impl;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.BooleanUtil;
3 import cn.hutool.core.util.ObjUtil; 7 import cn.hutool.core.util.ObjUtil;
4 import cn.hutool.core.util.StrUtil; 8 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 9 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
@@ -109,15 +113,14 @@ public class AiChatServiceImpl implements AiChatService { @@ -109,15 +113,14 @@ public class AiChatServiceImpl implements AiChatService {
109 StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform); 113 StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform);
110 114
111 // 2. 插入 user 发送消息 115 // 2. 插入 user 发送消息
112 - AiChatMessageDO userMessage = createChatMessage(conversation.getId(), model,  
113 - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent()); 116 + AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
  117 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext());
114 118
115 // 3.1 插入 assistant 接收消息 119 // 3.1 插入 assistant 接收消息
116 - AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), model,  
117 - userId, conversation.getRoleId(), MessageType.ASSISTANT, ""); 120 + AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model,
  121 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext());
118 122
119 // 3.2 创建 chat 需要的 Prompt 123 // 3.2 创建 chat 需要的 Prompt
120 - // TODO 消息上下文  
121 Prompt prompt = buildPrompt(conversation, historyMessages, sendReqVO); 124 Prompt prompt = buildPrompt(conversation, historyMessages, sendReqVO);
122 Flux<ChatResponse> streamResponse = chatClient.stream(prompt); 125 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
123 126
@@ -139,32 +142,66 @@ public class AiChatServiceImpl implements AiChatService { @@ -139,32 +142,66 @@ public class AiChatServiceImpl implements AiChatService {
139 } 142 }
140 143
141 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, AiChatMessageSendReqVO sendReqVO) { 144 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, AiChatMessageSendReqVO sendReqVO) {
142 - // TODO 芋艿:1)保留 n 个上下文;2)每一轮 token 数量  
143 -// if (conversation.getMaxContexts() != null && messages.size() > conversation.getMaxContexts()) {  
144 -//  
145 -// }  
146 // 1. 构建 Prompt Message 列表 145 // 1. 构建 Prompt Message 列表
147 List<Message> chatMessages = new ArrayList<>(); 146 List<Message> chatMessages = new ArrayList<>();
148 // 1.1 system context 角色设定 147 // 1.1 system context 角色设定
149 chatMessages.add(new SystemMessage(conversation.getSystemMessage())); 148 chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
150 // 1.2 history message 历史消息 149 // 1.2 history message 历史消息
151 - messages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent()))); 150 + List<AiChatMessageDO> contextMessages = filterContextMessages(messages, conversation, sendReqVO);
  151 + contextMessages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent())));
152 // 1.3 user message 新发送消息 152 // 1.3 user message 新发送消息
153 chatMessages.add(new UserMessage(sendReqVO.getContent())); 153 chatMessages.add(new UserMessage(sendReqVO.getContent()));
154 154
155 // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了; 155 // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了;
  156 + // TODO 每一轮 token 数量
156 // ChatOptions chatOptions = ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build(); 157 // ChatOptions chatOptions = ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build();
157 // return new Prompt(chatMessages, null); 158 // return new Prompt(chatMessages, null);
158 return new Prompt(chatMessages); 159 return new Prompt(chatMessages);
159 } 160 }
160 161
161 - private AiChatMessageDO createChatMessage(Long conversationId, AiChatModelDO model,  
162 - Long userId, Long roleId,  
163 - MessageType messageType, String content) {  
164 - AiChatMessageDO message = new AiChatMessageDO()  
165 - .setConversationId(conversationId).setModel(model.getModel()).setModelId(model.getId())  
166 - .setUserId(userId).setRoleId(roleId)  
167 - .setType(messageType.getValue()).setContent(content); 162 + /**
  163 + * 从历史消息中,获得倒序的 n 组消息作为消息上下文
  164 + *
  165 + * n 组:指的是 user + assistant 形成一组
  166 + *
  167 + * @param messages 消息列表
  168 + * @param conversation 会话
  169 + * @param sendReqVO 发送请求
  170 + * @return 消息上下文
  171 + */
  172 + private List<AiChatMessageDO> filterContextMessages(List<AiChatMessageDO> messages, AiChatConversationDO conversation, AiChatMessageSendReqVO sendReqVO) {
  173 + if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) {
  174 + return Collections.emptyList();
  175 + }
  176 + List<AiChatMessageDO> contextMessages = new ArrayList<>(conversation.getMaxContexts() * 2);
  177 + for (int i = messages.size() - 1; i >= 0; i--) {
  178 + AiChatMessageDO assistantMessage = CollUtil.get(messages, i);
  179 + if (assistantMessage == null || assistantMessage.getReplyId() == null) {
  180 + continue;
  181 + }
  182 + AiChatMessageDO userMessage = CollUtil.get(messages, i - 1);
  183 + if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId())
  184 + || StrUtil.isEmpty(assistantMessage.getContent())) {
  185 + continue;
  186 + }
  187 + // 由于后续要 reverse 反转,所以先添加 assistantMessage
  188 + contextMessages.add(assistantMessage);
  189 + contextMessages.add(userMessage);
  190 + // 超过最大上下文,结束
  191 + if (contextMessages.size() >= conversation.getMaxContexts() * 2) {
  192 + break;
  193 + }
  194 + }
  195 + Collections.reverse(contextMessages);
  196 + return contextMessages;
  197 + }
  198 +
  199 + private AiChatMessageDO createChatMessage(Long conversationId, Long replyId,
  200 + AiChatModelDO model, Long userId, Long roleId,
  201 + MessageType messageType, String content, Boolean useContext) {
  202 + AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId)
  203 + .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId)
  204 + .setType(messageType.getValue()).setContent(content).setUseContext(useContext);
168 message.setCreateTime(LocalDateTime.now()); 205 message.setCreateTime(LocalDateTime.now());
169 chatMessageMapper.insert(message); 206 chatMessageMapper.insert(message);
170 return message; 207 return message;