Commit 276ef98ff1b0387559225efb276f5509fc175873

Authored by YunaiV
1 parent 275d1fb6

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

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
@@ -19,10 +19,10 @@ public class AiChatMessageRespVO { @@ -19,10 +19,10 @@ public class AiChatMessageRespVO {
19 private String type; // 参见 MessageType 枚举类 19 private String type; // 参见 MessageType 枚举类
20 20
21 @Schema(description = "用户编号", example = "4096") 21 @Schema(description = "用户编号", example = "4096")
22 - private Long userId; // 仅当 user 发送时非空 22 + private Long userId;
23 23
24 @Schema(description = "角色编号", example = "888") 24 @Schema(description = "角色编号", example = "888")
25 - private Long roleId; // 仅当 assistant 回复时非空 25 + private Long roleId;
26 26
27 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo") 27 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
28 private String model; // 参见 AiOpenAiModelEnum 枚举类 28 private String model; // 参见 AiOpenAiModelEnum 枚举类
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
@@ -47,16 +47,12 @@ public class AiChatMessageDO extends BaseDO { @@ -47,16 +47,12 @@ public class AiChatMessageDO extends BaseDO {
47 /** 47 /**
48 * 用户编号 48 * 用户编号
49 * 49 *
50 - * 仅当 user 发送时非空  
51 - *  
52 * 关联 AdminUserDO 的 userId 字段 50 * 关联 AdminUserDO 的 userId 字段
53 */ 51 */
54 private Long userId; 52 private Long userId;
55 /** 53 /**
56 * 角色编号 54 * 角色编号
57 * 55 *
58 - * 仅当 assistant 回复时非空  
59 - *  
60 * 关联 {@link AiChatRoleDO#getId()} 字段 56 * 关联 {@link AiChatRoleDO#getId()} 字段
61 */ 57 */
62 private Long roleId; 58 private Long roleId;
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.exceptions.ExceptionUtil;  
4 import cn.hutool.core.util.ObjUtil; 3 import cn.hutool.core.util.ObjUtil;
5 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
8 -import org.springframework.ai.chat.ChatClient;  
9 import org.springframework.ai.chat.ChatResponse; 8 import org.springframework.ai.chat.ChatResponse;
10 import org.springframework.ai.chat.StreamingChatClient; 9 import org.springframework.ai.chat.StreamingChatClient;
11 -import org.springframework.ai.chat.messages.MessageType; 10 +import org.springframework.ai.chat.messages.*;
  11 +import org.springframework.ai.chat.prompt.ChatOptions;
  12 +import org.springframework.ai.chat.prompt.ChatOptionsBuilder;
12 import org.springframework.ai.chat.prompt.Prompt; 13 import org.springframework.ai.chat.prompt.Prompt;
13 -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;  
14 import cn.iocoder.yudao.module.ai.config.AiChatClientFactory; 14 import cn.iocoder.yudao.module.ai.config.AiChatClientFactory;
15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
16 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 16 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
17 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert; 17 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
18 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 18 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
20 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;  
21 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper; 20 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper;
22 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; 21 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
23 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 22 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
@@ -30,10 +29,7 @@ import org.springframework.transaction.annotation.Transactional; @@ -30,10 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
30 import reactor.core.publisher.Flux; 29 import reactor.core.publisher.Flux;
31 30
32 import java.time.LocalDateTime; 31 import java.time.LocalDateTime;
33 -import java.util.List;  
34 -import java.util.Map;  
35 -import java.util.Set;  
36 -import java.util.concurrent.atomic.AtomicInteger; 32 +import java.util.*;
37 import java.util.stream.Collectors; 33 import java.util.stream.Collectors;
38 34
39 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -53,64 +49,49 @@ public class AiChatServiceImpl implements AiChatService { @@ -53,64 +49,49 @@ public class AiChatServiceImpl implements AiChatService {
53 49
54 private final AiChatClientFactory chatClientFactory; 50 private final AiChatClientFactory chatClientFactory;
55 51
56 - private final AiChatMessageMapper aiChatMessageMapper; 52 + private final AiChatMessageMapper chatMessageMapper;
  53 +
57 private final AiChatConversationService chatConversationService; 54 private final AiChatConversationService chatConversationService;
58 private final AiChatModelService chatModalService; 55 private final AiChatModelService chatModalService;
59 private final AiChatRoleService chatRoleService; 56 private final AiChatRoleService chatRoleService;
60 57
61 @Transactional(rollbackFor = Exception.class) 58 @Transactional(rollbackFor = Exception.class)
62 public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) { 59 public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) {
63 - Long loginUserId = SecurityFrameworkUtils.getLoginUserId();  
64 - // 查询对话  
65 - AiChatConversationDO conversation = chatConversationService.validateExists(req.getConversationId());  
66 - // 获取对话模型  
67 - AiChatModelDO chatModel = chatModalService.validateChatModel(conversation.getModelId());  
68 - // 获取角色信息  
69 - AiChatRoleDO chatRoleDO = conversation.getRoleId() != null ? chatRoleService.validateChatRole(conversation.getRoleId()) : null;  
70 - // 获取 client 类型  
71 - AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform());  
72 - // 保存 chat message  
73 - insertChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(),  
74 - chatModel.getModel(), chatModel.getId(), req.getContent());  
75 - String content = null;  
76 - int tokens = 0;  
77 - try {  
78 - // 创建 chat 需要的 Prompt  
79 - Prompt prompt = new Prompt(req.getContent());  
80 - // TODO @芋艿 @范 看要不要支持这些  
81 -// req.setTopK(req.getTopK());  
82 -// req.setTopP(req.getTopP());  
83 -// req.setTemperature(req.getTemperature());  
84 - // 发送 call 调用  
85 - ChatClient chatClient = chatClientFactory.getChatClient(platformEnum);  
86 - ChatResponse call = chatClient.call(prompt);  
87 - content = call.getResult().getOutput().getContent();  
88 - tokens = call.getResults().size();  
89 - // 更新 conversation  
90 - } catch (Exception e) {  
91 - content = ExceptionUtil.getMessage(e);  
92 - } finally {  
93 - // 保存 chat message  
94 - insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),  
95 - chatModel.getModel(), chatModel.getId(), content);  
96 - }  
97 - return new AiChatMessageRespVO().setContent(content);  
98 - }  
99 -  
100 - private AiChatMessageDO insertChatMessage(Long conversationId, MessageType messageType, Long loginUserId, Long roleId,  
101 - String model, Long modelId, String content) {  
102 - AiChatMessageDO insertChatMessageDO = new AiChatMessageDO()  
103 - .setConversationId(conversationId)  
104 - .setType(messageType.getValue())  
105 - .setUserId(loginUserId)  
106 - .setRoleId(roleId)  
107 - .setModel(model)  
108 - .setModelId(modelId)  
109 - .setContent(content);  
110 - insertChatMessageDO.setCreateTime(LocalDateTime.now());  
111 - // 增加 chat message 记录  
112 - aiChatMessageMapper.insert(insertChatMessageDO);  
113 - return insertChatMessageDO; 60 + return null; // TODO 芋艿:一起改
  61 +// Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
  62 +// // 查询对话
  63 +// AiChatConversationDO conversation = chatConversationService.validateExists(req.getConversationId());
  64 +// // 获取对话模型
  65 +// AiChatModelDO chatModel = chatModalService.validateChatModel(conversation.getModelId());
  66 +// // 获取角色信息
  67 +// AiChatRoleDO chatRoleDO = conversation.getRoleId() != null ? chatRoleService.validateChatRole(conversation.getRoleId()) : null;
  68 +// // 获取 client 类型
  69 +// AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform());
  70 +// // 保存 chat message
  71 +// createChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(),
  72 +// chatModel.getModel(), chatModel.getId(), req.getContent());
  73 +// String content = null;
  74 +// int tokens = 0;
  75 +// try {
  76 +// // 创建 chat 需要的 Prompt
  77 +// Prompt prompt = new Prompt(req.getContent());
  78 +// // TODO @芋艿 @范 看要不要支持这些
  79 +//// req.setTopK(req.getTopK());
  80 +//// req.setTopP(req.getTopP());
  81 +//// req.setTemperature(req.getTemperature());
  82 +// // 发送 call 调用
  83 +// ChatClient chatClient = chatClientFactory.getChatClient(platformEnum);
  84 +// ChatResponse call = chatClient.call(prompt);
  85 +// content = call.getResult().getOutput().getContent();
  86 +// // 更新 conversation
  87 +// } catch (Exception e) {
  88 +// content = ExceptionUtil.getMessage(e);
  89 +// } finally {
  90 +// // 保存 chat message
  91 +// createChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),
  92 +// chatModel.getModel(), chatModel.getId(), content);
  93 +// }
  94 +// return new AiChatMessageRespVO().setContent(content);
114 } 95 }
115 96
116 @Override 97 @Override
@@ -120,55 +101,78 @@ public class AiChatServiceImpl implements AiChatService { @@ -120,55 +101,78 @@ public class AiChatServiceImpl implements AiChatService {
120 if (ObjUtil.notEqual(conversation.getUserId(), userId)) { 101 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
121 throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; 102 throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接;
122 } 103 }
  104 + List<AiChatMessageDO> historyMessages = chatMessageMapper.selectByConversationId(conversation.getId());
123 // 1.2 校验模型 105 // 1.2 校验模型
124 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); 106 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
125 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 107 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
126 StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform); 108 StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform);
127 109
128 // 2. 插入 user 发送消息 110 // 2. 插入 user 发送消息
129 - AiChatMessageDO userMessage = insertChatMessage(conversation.getId(), MessageType.USER, userId, conversation.getRoleId(),  
130 - conversation.getModel(), conversation.getId(), sendReqVO.getContent()); 111 + AiChatMessageDO userMessage = createChatMessage(conversation.getId(), model,
  112 + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent());
  113 +
  114 + // 3.1 插入 assistant 接收消息
  115 + AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), model,
  116 + userId, conversation.getRoleId(), MessageType.ASSISTANT, "");
131 117
132 - // 3.1 插入 system 接收消息  
133 - AiChatMessageDO systemMessage = insertChatMessage(conversation.getId(), MessageType.SYSTEM, userId, conversation.getRoleId(),  
134 - conversation.getModel(), conversation.getId(), conversation.getSystemMessage());  
135 // 3.2 创建 chat 需要的 Prompt 118 // 3.2 创建 chat 需要的 Prompt
136 // TODO 消息上下文 119 // TODO 消息上下文
137 - Prompt prompt = new Prompt(sendReqVO.getContent());  
138 -// ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build() 120 + Prompt prompt = buildPrompt(conversation, historyMessages, sendReqVO);
139 Flux<ChatResponse> streamResponse = chatClient.stream(prompt); 121 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
140 - // 3.3 转换 flex AiChatMessageRespVO 122 +
  123 + // 3.3 流式返回
141 StringBuffer contentBuffer = new StringBuffer(); 124 StringBuffer contentBuffer = new StringBuffer();
142 - return streamResponse.map(res -> {  
143 - contentBuffer.append(res.getResult().getOutput().getContent());  
144 - AiChatMessageSendRespVO.Message send = new AiChatMessageSendRespVO.Message().setId(userMessage.getId())  
145 - .setType(MessageType.USER.getValue()).setCreateTime(userMessage.getCreateTime())  
146 - .setContent(sendReqVO.getContent());  
147 - AiChatMessageSendRespVO.Message receive = new AiChatMessageSendRespVO.Message().setId(systemMessage.getId())  
148 - .setType(MessageType.SYSTEM.getValue()).setCreateTime(systemMessage.getCreateTime())  
149 - .setContent(res.getResult().getOutput().getContent());  
150 - return new AiChatMessageSendRespVO().setSend(send).setReceive(receive); 125 + return streamResponse.map(response -> {
  126 + String newContent = response.getResult().getOutput().getContent();
  127 + contentBuffer.append(newContent);
  128 + // 响应结果
  129 + return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
  130 + .setReceive(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent));
151 }).doOnComplete(() -> { 131 }).doOnComplete(() -> {
152 - log.info("发送完成!");  
153 - // 保存 chat message  
154 - aiChatMessageMapper.updateById(new AiChatMessageDO()  
155 - .setId(systemMessage.getId())  
156 - .setContent(contentBuffer.toString())); 132 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()));
157 }).doOnError(throwable -> { 133 }).doOnError(throwable -> {
158 - log.error("发送错误 {}!", throwable.getMessage());  
159 - // 更新错误信息 TODO 貌似不应该更新异常  
160 - aiChatMessageMapper.updateById(new AiChatMessageDO()  
161 - .setId(systemMessage.getId())  
162 - .setContent(throwable.getMessage())); 134 + log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
  135 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()));
163 }); 136 });
164 } 137 }
165 138
  139 + private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, AiChatMessageSendReqVO sendReqVO) {
  140 + // TODO 芋艿:1)保留 n 个上下文;2)每一轮 token 数量
  141 +// if (conversation.getMaxContexts() != null && messages.size() > conversation.getMaxContexts()) {
  142 +//
  143 +// }
  144 + // 1. 构建 Prompt Message 列表
  145 + List<Message> chatMessages = new ArrayList<>();
  146 + // 1.1 system context 角色设定
  147 + chatMessages.add(new SystemMessage(conversation.getSystemMessage()));
  148 + // 1.2 history message 历史消息
  149 + messages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent())));
  150 + // 1.3 user message 新发送消息
  151 + chatMessages.add(new UserMessage(sendReqVO.getContent()));
  152 +
  153 + // 2. 构建 ChatOptions 对象
  154 + ChatOptions chatOptions = ChatOptionsBuilder.builder().withTemperature(conversation.getTemperature().floatValue()).build();
  155 + return new Prompt(chatMessages, chatOptions);
  156 + }
  157 +
  158 + private AiChatMessageDO createChatMessage(Long conversationId, AiChatModelDO model,
  159 + Long userId, Long roleId,
  160 + MessageType messageType, String content) {
  161 + AiChatMessageDO message = new AiChatMessageDO()
  162 + .setConversationId(conversationId).setModel(model.getModel()).setModelId(model.getId())
  163 + .setUserId(userId).setRoleId(roleId)
  164 + .setType(messageType.getValue()).setContent(content);
  165 + message.setCreateTime(LocalDateTime.now());
  166 + chatMessageMapper.insert(message);
  167 + return message;
  168 + }
  169 +
166 @Override 170 @Override
167 public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) { 171 public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) {
168 // 校验对话是否存在 172 // 校验对话是否存在
169 chatConversationService.validateExists(conversationId); 173 chatConversationService.validateExists(conversationId);
170 // 获取对话所有 message 174 // 获取对话所有 message
171 - List<AiChatMessageDO> aiChatMessageDOList = aiChatMessageMapper.selectByConversationId(conversationId); 175 + List<AiChatMessageDO> aiChatMessageDOList = chatMessageMapper.selectByConversationId(conversationId);
172 // 获取模型信息 176 // 获取模型信息
173 Set<Long> modalIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getModelId).collect(Collectors.toSet()); 177 Set<Long> modalIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getModelId).collect(Collectors.toSet());
174 List<AiChatModelDO> modalList = chatModalService.getModalByIds(modalIds); 178 List<AiChatModelDO> modalList = chatModalService.getModalByIds(modalIds);
@@ -187,7 +191,7 @@ public class AiChatServiceImpl implements AiChatService { @@ -187,7 +191,7 @@ public class AiChatServiceImpl implements AiChatService {
187 191
188 @Override 192 @Override
189 public Boolean deleteMessage(Long id) { 193 public Boolean deleteMessage(Long id) {
190 - return aiChatMessageMapper.deleteById(id) > 0; 194 + return chatMessageMapper.deleteById(id) > 0;
191 } 195 }
192 196
193 } 197 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
@@ -15,13 +15,13 @@ import lombok.Getter; @@ -15,13 +15,13 @@ import lombok.Getter;
15 public enum AiPlatformEnum { 15 public enum AiPlatformEnum {
16 16
17 OPENAI("OpenAI", "OpenAI"), 17 OPENAI("OpenAI", "OpenAI"),
18 - OLLAMA("dall", "dall"), 18 + OLLAMA("Ollama", "Ollama"),
19 19
20 YI_YAN("yiyan", "一言"), 20 YI_YAN("yiyan", "一言"),
21 QIAN_WEN("qianwen", "千问"), 21 QIAN_WEN("qianwen", "千问"),
22 XING_HUO("xinghuo", "星火"), 22 XING_HUO("xinghuo", "星火"),
23 OPEN_AI_DALL("dall", "dall"), 23 OPEN_AI_DALL("dall", "dall"),
24 - MIDJOURNEY("Ollama", "Ollama"), 24 + MIDJOURNEY("midjourney", "midjourney"),
25 25
26 ; 26 ;
27 27