Commit f84d25d3b710f0788c0427f5f0d59739a291033d
1 parent
905ce773
stream 保存聊天记录
Showing
3 changed files
with
92 additions
and
47 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/ChatController.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.controller; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.exceptions.ExceptionUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.chat.ChatResponse; |
| 5 | -import cn.iocoder.yudao.framework.ai.config.AiClient; | |
| 6 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | 6 | import cn.iocoder.yudao.module.ai.service.ChatService; |
| 8 | 7 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| ... | ... | @@ -38,7 +37,6 @@ import java.util.function.Consumer; |
| 38 | 37 | public class ChatController { |
| 39 | 38 | |
| 40 | 39 | @Autowired |
| 41 | - private AiClient aiClient; | |
| 42 | 40 | private final ChatService chatService; |
| 43 | 41 | |
| 44 | 42 | @Operation(summary = "聊天-chat", description = "这个一般等待时间比较久,需要全部完成才会返回!") |
| ... | ... | @@ -52,30 +50,7 @@ public class ChatController { |
| 52 | 50 | @GetMapping(value = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
| 53 | 51 | public SseEmitter chatStream(@Validated @ModelAttribute ChatReq req) { |
| 54 | 52 | Utf8SseEmitter sseEmitter = new Utf8SseEmitter(); |
| 55 | - Flux<ChatResponse> streamResponse = chatService.chatStream(req); | |
| 56 | - streamResponse.subscribe( | |
| 57 | - new Consumer<ChatResponse>() { | |
| 58 | - @Override | |
| 59 | - public void accept(ChatResponse chatResponse) { | |
| 60 | - String content = chatResponse.getResults().get(0).getOutput().getContent(); | |
| 61 | - try { | |
| 62 | - sseEmitter.send(content, MediaType.APPLICATION_JSON); | |
| 63 | - } catch (IOException e) { | |
| 64 | - log.error("发送异常{}", ExceptionUtil.getMessage(e)); | |
| 65 | - // 如果不是因为关闭而抛出异常,则重新连接 | |
| 66 | - sseEmitter.completeWithError(e); | |
| 67 | - } | |
| 68 | - } | |
| 69 | - }, | |
| 70 | - error -> { | |
| 71 | - // | |
| 72 | - log.error("subscribe错误 {}", ExceptionUtil.getMessage(error)); | |
| 73 | - }, | |
| 74 | - () -> { | |
| 75 | - log.info("发送完成!"); | |
| 76 | - sseEmitter.complete(); | |
| 77 | - } | |
| 78 | - ); | |
| 53 | + chatService.chatStream(req, sseEmitter); | |
| 79 | 54 | return sseEmitter; |
| 80 | 55 | } |
| 81 | 56 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/ChatService.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.ai.chat.ChatResponse; | |
| 4 | -import cn.iocoder.yudao.module.ai.enums.AiClientNameEnum; | |
| 3 | +import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | |
| 5 | 4 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| 6 | -import reactor.core.publisher.Flux; | |
| 7 | 5 | |
| 8 | 6 | /** |
| 9 | 7 | * 聊天 chat |
| ... | ... | @@ -26,7 +24,8 @@ public interface ChatService { |
| 26 | 24 | * chat stream |
| 27 | 25 | * |
| 28 | 26 | * @param req |
| 27 | + * @param sseEmitter | |
| 29 | 28 | * @return |
| 30 | 29 | */ |
| 31 | - Flux<ChatResponse> chatStream(ChatReq req); | |
| 30 | + void chatStream(ChatReq req, Utf8SseEmitter sseEmitter); | |
| 32 | 31 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/ChatServiceImpl.java
| ... | ... | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.ai.config.AiClient; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.exception.ServerException; |
| 9 | 9 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 10 | 10 | import cn.iocoder.yudao.module.ai.ErrorCodeConstants; |
| 11 | +import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | |
| 11 | 12 | import cn.iocoder.yudao.module.ai.dataobject.AiChatConversationDO; |
| 12 | 13 | import cn.iocoder.yudao.module.ai.dataobject.AiChatMessageDO; |
| 13 | 14 | import cn.iocoder.yudao.module.ai.dataobject.AiChatRoleDO; |
| ... | ... | @@ -21,10 +22,14 @@ import cn.iocoder.yudao.module.ai.service.ChatService; |
| 21 | 22 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| 22 | 23 | import lombok.AllArgsConstructor; |
| 23 | 24 | import lombok.extern.slf4j.Slf4j; |
| 25 | +import org.springframework.http.MediaType; | |
| 24 | 26 | import org.springframework.stereotype.Service; |
| 25 | 27 | import org.springframework.transaction.annotation.Transactional; |
| 26 | 28 | import reactor.core.publisher.Flux; |
| 27 | 29 | |
| 30 | +import java.io.IOException; | |
| 31 | +import java.util.function.Consumer; | |
| 32 | + | |
| 28 | 33 | /** |
| 29 | 34 | * 聊天 service |
| 30 | 35 | * |
| ... | ... | @@ -51,25 +56,17 @@ public class ChatServiceImpl implements ChatService { |
| 51 | 56 | */ |
| 52 | 57 | @Transactional(rollbackFor = Exception.class) |
| 53 | 58 | public String chat(ChatReq req) { |
| 59 | + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | |
| 54 | 60 | // 获取 client 类型 |
| 55 | 61 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); |
| 56 | 62 | // 获取 对话类型(新建还是继续) |
| 57 | 63 | ChatConversationTypeEnum chatConversationTypeEnum = ChatConversationTypeEnum.valueOfType(req.getConversationType()); |
| 64 | + AiChatConversationDO aiChatConversationDO = getChatConversationNoExistToCreate(req, chatConversationTypeEnum, loginUserId); | |
| 58 | 65 | |
| 59 | - AiChatConversationDO aiChatConversationDO; | |
| 60 | - Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | |
| 61 | - if (ChatConversationTypeEnum.NEW == chatConversationTypeEnum) { | |
| 62 | - // 创建一个新的对话 | |
| 63 | - aiChatConversationDO = createNewChatConversation(req, loginUserId); | |
| 64 | - } else { | |
| 65 | - // 继续对话 | |
| 66 | - if (req.getConversationId() == null) { | |
| 67 | - throw new ServerException(ErrorCodeConstants.AI_CHAT_CONTINUE_CONVERSATION_ID_NOT_NULL); | |
| 68 | - } | |
| 69 | - aiChatConversationDO = aiChatConversationMapper.selectById(req.getConversationId()); | |
| 70 | - } | |
| 66 | + // 保存 chat message | |
| 67 | + saveChatMessage(req, aiChatConversationDO.getId(), loginUserId); | |
| 71 | 68 | |
| 72 | - String content; | |
| 69 | + String content = null; | |
| 73 | 70 | try { |
| 74 | 71 | // 创建 chat 需要的 Prompt |
| 75 | 72 | Prompt prompt = new Prompt(req.getPrompt()); |
| ... | ... | @@ -81,13 +78,19 @@ public class ChatServiceImpl implements ChatService { |
| 81 | 78 | content = call.getResult().getOutput().getContent(); |
| 82 | 79 | } catch (Exception e) { |
| 83 | 80 | content = ExceptionUtil.getMessage(e); |
| 81 | + } finally { | |
| 82 | + // 保存 chat message | |
| 83 | + saveSystemChatMessage(req, aiChatConversationDO.getId(), loginUserId, content); | |
| 84 | 84 | } |
| 85 | + return content; | |
| 86 | + } | |
| 85 | 87 | |
| 88 | + private void saveChatMessage(ChatReq req, Long chatConversationId, Long loginUserId) { | |
| 86 | 89 | // 增加 chat message 记录 |
| 87 | 90 | aiChatMessageMapper.insert( |
| 88 | 91 | new AiChatMessageDO() |
| 89 | 92 | .setId(null) |
| 90 | - .setChatConversationId(aiChatConversationDO.getId()) | |
| 93 | + .setChatConversationId(chatConversationId) | |
| 91 | 94 | .setUserId(loginUserId) |
| 92 | 95 | .setMessage(req.getPrompt()) |
| 93 | 96 | .setMessageType(MessageType.USER.getValue()) |
| ... | ... | @@ -98,7 +101,39 @@ public class ChatServiceImpl implements ChatService { |
| 98 | 101 | |
| 99 | 102 | // chat count 先+1 |
| 100 | 103 | aiChatConversationMapper.updateIncrChatCount(req.getConversationId()); |
| 101 | - return content; | |
| 104 | + } | |
| 105 | + | |
| 106 | + public void saveSystemChatMessage(ChatReq req, Long chatConversationId, Long loginUserId, String systemPrompts) { | |
| 107 | + // 增加 chat message 记录 | |
| 108 | + aiChatMessageMapper.insert( | |
| 109 | + new AiChatMessageDO() | |
| 110 | + .setId(null) | |
| 111 | + .setChatConversationId(chatConversationId) | |
| 112 | + .setUserId(loginUserId) | |
| 113 | + .setMessage(systemPrompts) | |
| 114 | + .setMessageType(MessageType.SYSTEM.getValue()) | |
| 115 | + .setTopK(req.getTopK()) | |
| 116 | + .setTopP(req.getTopP()) | |
| 117 | + .setTemperature(req.getTemperature()) | |
| 118 | + ); | |
| 119 | + | |
| 120 | + // chat count 先+1 | |
| 121 | + aiChatConversationMapper.updateIncrChatCount(req.getConversationId()); | |
| 122 | + } | |
| 123 | + | |
| 124 | + private AiChatConversationDO getChatConversationNoExistToCreate(ChatReq req, ChatConversationTypeEnum chatConversationTypeEnum, Long loginUserId) { | |
| 125 | + AiChatConversationDO aiChatConversationDO; | |
| 126 | + if (ChatConversationTypeEnum.NEW == chatConversationTypeEnum) { | |
| 127 | + // 创建一个新的对话 | |
| 128 | + aiChatConversationDO = createNewChatConversation(req, loginUserId); | |
| 129 | + } else { | |
| 130 | + // 继续对话 | |
| 131 | + if (req.getConversationId() == null) { | |
| 132 | + throw new ServerException(ErrorCodeConstants.AI_CHAT_CONTINUE_CONVERSATION_ID_NOT_NULL); | |
| 133 | + } | |
| 134 | + aiChatConversationDO = aiChatConversationMapper.selectById(req.getConversationId()); | |
| 135 | + } | |
| 136 | + return aiChatConversationDO; | |
| 102 | 137 | } |
| 103 | 138 | |
| 104 | 139 | private AiChatConversationDO createNewChatConversation(ChatReq req, Long loginUserId) { |
| ... | ... | @@ -133,16 +168,52 @@ public class ChatServiceImpl implements ChatService { |
| 133 | 168 | * chat stream |
| 134 | 169 | * |
| 135 | 170 | * @param req |
| 171 | + * @param sseEmitter | |
| 136 | 172 | * @return |
| 137 | 173 | */ |
| 138 | 174 | @Override |
| 139 | - public Flux<ChatResponse> chatStream(ChatReq req) { | |
| 175 | + public void chatStream(ChatReq req, Utf8SseEmitter sseEmitter) { | |
| 176 | + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | |
| 177 | + // 获取 client 类型 | |
| 140 | 178 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); |
| 179 | + // 获取 对话类型(新建还是继续) | |
| 180 | + ChatConversationTypeEnum chatConversationTypeEnum = ChatConversationTypeEnum.valueOfType(req.getConversationType()); | |
| 181 | + AiChatConversationDO aiChatConversationDO = getChatConversationNoExistToCreate(req, chatConversationTypeEnum, loginUserId); | |
| 141 | 182 | // 创建 chat 需要的 Prompt |
| 142 | 183 | Prompt prompt = new Prompt(req.getPrompt()); |
| 143 | 184 | req.setTopK(req.getTopK()); |
| 144 | 185 | req.setTopP(req.getTopP()); |
| 145 | 186 | req.setTemperature(req.getTemperature()); |
| 146 | - return aiClient.stream(prompt, clientNameEnum.getName()); | |
| 187 | + // 保存 chat message | |
| 188 | + saveChatMessage(req, aiChatConversationDO.getId(), loginUserId); | |
| 189 | + Flux<ChatResponse> streamResponse = aiClient.stream(prompt, clientNameEnum.getName()); | |
| 190 | + | |
| 191 | + StringBuffer contentBuffer = new StringBuffer(); | |
| 192 | + streamResponse.subscribe( | |
| 193 | + new Consumer<ChatResponse>() { | |
| 194 | + @Override | |
| 195 | + public void accept(ChatResponse chatResponse) { | |
| 196 | + String content = chatResponse.getResults().get(0).getOutput().getContent(); | |
| 197 | + try { | |
| 198 | + contentBuffer.append(content); | |
| 199 | + sseEmitter.send(content, MediaType.APPLICATION_JSON); | |
| 200 | + } catch (IOException e) { | |
| 201 | + log.error("发送异常{}", ExceptionUtil.getMessage(e)); | |
| 202 | + // 如果不是因为关闭而抛出异常,则重新连接 | |
| 203 | + sseEmitter.completeWithError(e); | |
| 204 | + } | |
| 205 | + } | |
| 206 | + }, | |
| 207 | + error -> { | |
| 208 | + // | |
| 209 | + log.error("subscribe错误 {}", ExceptionUtil.getMessage(error)); | |
| 210 | + }, | |
| 211 | + () -> { | |
| 212 | + log.info("发送完成!"); | |
| 213 | + sseEmitter.complete(); | |
| 214 | + // 保存 chat message | |
| 215 | + saveSystemChatMessage(req, aiChatConversationDO.getId(), loginUserId, contentBuffer.toString()); | |
| 216 | + } | |
| 217 | + ); | |
| 147 | 218 | } |
| 148 | 219 | } | ... | ... |