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 19 private String type; // 参见 MessageType 枚举类
20 20  
21 21 @Schema(description = "用户编号", example = "4096")
22   - private Long userId; // 仅当 user 发送时非空
  22 + private Long userId;
23 23  
24 24 @Schema(description = "角色编号", example = "888")
25   - private Long roleId; // 仅当 assistant 回复时非空
  25 + private Long roleId;
26 26  
27 27 @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
28 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 47 /**
48 48 * 用户编号
49 49 *
50   - * 仅当 user 发送时非空
51   - *
52 50 * 关联 AdminUserDO 的 userId 字段
53 51 */
54 52 private Long userId;
55 53 /**
56 54 * 角色编号
57 55 *
58   - * 仅当 assistant 回复时非空
59   - *
60 56 * 关联 {@link AiChatRoleDO#getId()} 字段
61 57 */
62 58 private Long roleId;
... ...
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.exceptions.ExceptionUtil;
4 3 import cn.hutool.core.util.ObjUtil;
5 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 6 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
7 7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
8   -import org.springframework.ai.chat.ChatClient;
9 8 import org.springframework.ai.chat.ChatResponse;
10 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 13 import org.springframework.ai.chat.prompt.Prompt;
13   -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
14 14 import cn.iocoder.yudao.module.ai.config.AiChatClientFactory;
15 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
16 16 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
17 17 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
18 18 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
19 19 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
20   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
21 20 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper;
22 21 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
23 22 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
... ... @@ -30,10 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
30 29 import reactor.core.publisher.Flux;
31 30  
32 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 33 import java.util.stream.Collectors;
38 34  
39 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -53,64 +49,49 @@ public class AiChatServiceImpl implements AiChatService {
53 49  
54 50 private final AiChatClientFactory chatClientFactory;
55 51  
56   - private final AiChatMessageMapper aiChatMessageMapper;
  52 + private final AiChatMessageMapper chatMessageMapper;
  53 +
57 54 private final AiChatConversationService chatConversationService;
58 55 private final AiChatModelService chatModalService;
59 56 private final AiChatRoleService chatRoleService;
60 57  
61 58 @Transactional(rollbackFor = Exception.class)
62 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 97 @Override
... ... @@ -120,55 +101,78 @@ public class AiChatServiceImpl implements AiChatService {
120 101 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
121 102 throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接;
122 103 }
  104 + List<AiChatMessageDO> historyMessages = chatMessageMapper.selectByConversationId(conversation.getId());
123 105 // 1.2 校验模型
124 106 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
125 107 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
126 108 StreamingChatClient chatClient = chatClientFactory.getStreamingChatClient(platform);
127 109  
128 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 118 // 3.2 创建 chat 需要的 Prompt
136 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 121 Flux<ChatResponse> streamResponse = chatClient.stream(prompt);
140   - // 3.3 转换 flex AiChatMessageRespVO
  122 +
  123 + // 3.3 流式返回
141 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 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 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 170 @Override
167 171 public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) {
168 172 // 校验对话是否存在
169 173 chatConversationService.validateExists(conversationId);
170 174 // 获取对话所有 message
171   - List<AiChatMessageDO> aiChatMessageDOList = aiChatMessageMapper.selectByConversationId(conversationId);
  175 + List<AiChatMessageDO> aiChatMessageDOList = chatMessageMapper.selectByConversationId(conversationId);
172 176 // 获取模型信息
173 177 Set<Long> modalIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getModelId).collect(Collectors.toSet());
174 178 List<AiChatModelDO> modalList = chatModalService.getModalByIds(modalIds);
... ... @@ -187,7 +191,7 @@ public class AiChatServiceImpl implements AiChatService {
187 191  
188 192 @Override
189 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 15 public enum AiPlatformEnum {
16 16  
17 17 OPENAI("OpenAI", "OpenAI"),
18   - OLLAMA("dall", "dall"),
  18 + OLLAMA("Ollama", "Ollama"),
19 19  
20 20 YI_YAN("yiyan", "一言"),
21 21 QIAN_WEN("qianwen", "千问"),
22 22 XING_HUO("xinghuo", "星火"),
23 23 OPEN_AI_DALL("dall", "dall"),
24   - MIDJOURNEY("Ollama", "Ollama"),
  24 + MIDJOURNEY("midjourney", "midjourney"),
25 25  
26 26 ;
27 27  
... ...