Commit 802dee2fc353f41d16fb4c7176f7ededaf6f0fa2
1 parent
3a10fedd
【新增】AI:发送消息时,增加上下文
Showing
3 changed files
with
72 additions
and
19 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java
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; | |
| 3 | 4 | import org.springframework.ai.chat.messages.MessageType; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| ... | ... | @@ -27,14 +28,23 @@ public class AiChatMessageDO extends BaseDO { |
| 27 | 28 | /** |
| 28 | 29 | * 编号,作为每条聊天记录的唯一标识符 |
| 29 | 30 | */ |
| 31 | + @TableId | |
| 30 | 32 | private Long id; |
| 31 | 33 | |
| 32 | 34 | /** |
| 33 | 35 | * 会话编号 |
| 34 | 36 | * |
| 35 | - * 关联 {@link AiChatConversationDO#getId()} | |
| 37 | + * 关联 {@link AiChatConversationDO#getId()} 字段 | |
| 36 | 38 | */ |
| 37 | 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 | 85 | */ |
| 76 | 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 | 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 | 7 | import cn.hutool.core.util.ObjUtil; |
| 4 | 8 | import cn.hutool.core.util.StrUtil; |
| 5 | 9 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| ... | ... | @@ -109,15 +113,14 @@ public class AiChatServiceImpl implements AiChatService { |
| 109 | 113 | StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform); |
| 110 | 114 | |
| 111 | 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 | 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 | 123 | // 3.2 创建 chat 需要的 Prompt |
| 120 | - // TODO 消息上下文 | |
| 121 | 124 | Prompt prompt = buildPrompt(conversation, historyMessages, sendReqVO); |
| 122 | 125 | Flux<ChatResponse> streamResponse = chatClient.stream(prompt); |
| 123 | 126 | |
| ... | ... | @@ -139,32 +142,66 @@ public class AiChatServiceImpl implements AiChatService { |
| 139 | 142 | } |
| 140 | 143 | |
| 141 | 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 | 145 | // 1. 构建 Prompt Message 列表 |
| 147 | 146 | List<Message> chatMessages = new ArrayList<>(); |
| 148 | 147 | // 1.1 system context 角色设定 |
| 149 | 148 | chatMessages.add(new SystemMessage(conversation.getSystemMessage())); |
| 150 | 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 | 152 | // 1.3 user message 新发送消息 |
| 153 | 153 | chatMessages.add(new UserMessage(sendReqVO.getContent())); |
| 154 | 154 | |
| 155 | 155 | // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了; |
| 156 | + // TODO 每一轮 token 数量 | |
| 156 | 157 | // ChatOptions chatOptions = ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build(); |
| 157 | 158 | // return new Prompt(chatMessages, null); |
| 158 | 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 | 205 | message.setCreateTime(LocalDateTime.now()); |
| 169 | 206 | chatMessageMapper.insert(message); |
| 170 | 207 | return message; | ... | ... |