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,7 +2,6 @@ package cn.iocoder.yudao.module.ai.controller; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.exceptions.ExceptionUtil; | 3 | import cn.hutool.core.exceptions.ExceptionUtil; |
| 4 | import cn.iocoder.yudao.framework.ai.chat.ChatResponse; | 4 | import cn.iocoder.yudao.framework.ai.chat.ChatResponse; |
| 5 | -import cn.iocoder.yudao.framework.ai.config.AiClient; | ||
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | import cn.iocoder.yudao.module.ai.service.ChatService; | 6 | import cn.iocoder.yudao.module.ai.service.ChatService; |
| 8 | import cn.iocoder.yudao.module.ai.vo.ChatReq; | 7 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| @@ -38,7 +37,6 @@ import java.util.function.Consumer; | @@ -38,7 +37,6 @@ import java.util.function.Consumer; | ||
| 38 | public class ChatController { | 37 | public class ChatController { |
| 39 | 38 | ||
| 40 | @Autowired | 39 | @Autowired |
| 41 | - private AiClient aiClient; | ||
| 42 | private final ChatService chatService; | 40 | private final ChatService chatService; |
| 43 | 41 | ||
| 44 | @Operation(summary = "聊天-chat", description = "这个一般等待时间比较久,需要全部完成才会返回!") | 42 | @Operation(summary = "聊天-chat", description = "这个一般等待时间比较久,需要全部完成才会返回!") |
| @@ -52,30 +50,7 @@ public class ChatController { | @@ -52,30 +50,7 @@ public class ChatController { | ||
| 52 | @GetMapping(value = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) | 50 | @GetMapping(value = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
| 53 | public SseEmitter chatStream(@Validated @ModelAttribute ChatReq req) { | 51 | public SseEmitter chatStream(@Validated @ModelAttribute ChatReq req) { |
| 54 | Utf8SseEmitter sseEmitter = new Utf8SseEmitter(); | 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 | return sseEmitter; | 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 | package cn.iocoder.yudao.module.ai.service; | 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 | import cn.iocoder.yudao.module.ai.vo.ChatReq; | 4 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| 6 | -import reactor.core.publisher.Flux; | ||
| 7 | 5 | ||
| 8 | /** | 6 | /** |
| 9 | * 聊天 chat | 7 | * 聊天 chat |
| @@ -26,7 +24,8 @@ public interface ChatService { | @@ -26,7 +24,8 @@ public interface ChatService { | ||
| 26 | * chat stream | 24 | * chat stream |
| 27 | * | 25 | * |
| 28 | * @param req | 26 | * @param req |
| 27 | + * @param sseEmitter | ||
| 29 | * @return | 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,6 +8,7 @@ import cn.iocoder.yudao.framework.ai.config.AiClient; | ||
| 8 | import cn.iocoder.yudao.framework.common.exception.ServerException; | 8 | import cn.iocoder.yudao.framework.common.exception.ServerException; |
| 9 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | 9 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 10 | import cn.iocoder.yudao.module.ai.ErrorCodeConstants; | 10 | import cn.iocoder.yudao.module.ai.ErrorCodeConstants; |
| 11 | +import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 11 | import cn.iocoder.yudao.module.ai.dataobject.AiChatConversationDO; | 12 | import cn.iocoder.yudao.module.ai.dataobject.AiChatConversationDO; |
| 12 | import cn.iocoder.yudao.module.ai.dataobject.AiChatMessageDO; | 13 | import cn.iocoder.yudao.module.ai.dataobject.AiChatMessageDO; |
| 13 | import cn.iocoder.yudao.module.ai.dataobject.AiChatRoleDO; | 14 | import cn.iocoder.yudao.module.ai.dataobject.AiChatRoleDO; |
| @@ -21,10 +22,14 @@ import cn.iocoder.yudao.module.ai.service.ChatService; | @@ -21,10 +22,14 @@ import cn.iocoder.yudao.module.ai.service.ChatService; | ||
| 21 | import cn.iocoder.yudao.module.ai.vo.ChatReq; | 22 | import cn.iocoder.yudao.module.ai.vo.ChatReq; |
| 22 | import lombok.AllArgsConstructor; | 23 | import lombok.AllArgsConstructor; |
| 23 | import lombok.extern.slf4j.Slf4j; | 24 | import lombok.extern.slf4j.Slf4j; |
| 25 | +import org.springframework.http.MediaType; | ||
| 24 | import org.springframework.stereotype.Service; | 26 | import org.springframework.stereotype.Service; |
| 25 | import org.springframework.transaction.annotation.Transactional; | 27 | import org.springframework.transaction.annotation.Transactional; |
| 26 | import reactor.core.publisher.Flux; | 28 | import reactor.core.publisher.Flux; |
| 27 | 29 | ||
| 30 | +import java.io.IOException; | ||
| 31 | +import java.util.function.Consumer; | ||
| 32 | + | ||
| 28 | /** | 33 | /** |
| 29 | * 聊天 service | 34 | * 聊天 service |
| 30 | * | 35 | * |
| @@ -51,25 +56,17 @@ public class ChatServiceImpl implements ChatService { | @@ -51,25 +56,17 @@ public class ChatServiceImpl implements ChatService { | ||
| 51 | */ | 56 | */ |
| 52 | @Transactional(rollbackFor = Exception.class) | 57 | @Transactional(rollbackFor = Exception.class) |
| 53 | public String chat(ChatReq req) { | 58 | public String chat(ChatReq req) { |
| 59 | + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | ||
| 54 | // 获取 client 类型 | 60 | // 获取 client 类型 |
| 55 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); | 61 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); |
| 56 | // 获取 对话类型(新建还是继续) | 62 | // 获取 对话类型(新建还是继续) |
| 57 | ChatConversationTypeEnum chatConversationTypeEnum = ChatConversationTypeEnum.valueOfType(req.getConversationType()); | 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 | try { | 70 | try { |
| 74 | // 创建 chat 需要的 Prompt | 71 | // 创建 chat 需要的 Prompt |
| 75 | Prompt prompt = new Prompt(req.getPrompt()); | 72 | Prompt prompt = new Prompt(req.getPrompt()); |
| @@ -81,13 +78,19 @@ public class ChatServiceImpl implements ChatService { | @@ -81,13 +78,19 @@ public class ChatServiceImpl implements ChatService { | ||
| 81 | content = call.getResult().getOutput().getContent(); | 78 | content = call.getResult().getOutput().getContent(); |
| 82 | } catch (Exception e) { | 79 | } catch (Exception e) { |
| 83 | content = ExceptionUtil.getMessage(e); | 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 | // 增加 chat message 记录 | 89 | // 增加 chat message 记录 |
| 87 | aiChatMessageMapper.insert( | 90 | aiChatMessageMapper.insert( |
| 88 | new AiChatMessageDO() | 91 | new AiChatMessageDO() |
| 89 | .setId(null) | 92 | .setId(null) |
| 90 | - .setChatConversationId(aiChatConversationDO.getId()) | 93 | + .setChatConversationId(chatConversationId) |
| 91 | .setUserId(loginUserId) | 94 | .setUserId(loginUserId) |
| 92 | .setMessage(req.getPrompt()) | 95 | .setMessage(req.getPrompt()) |
| 93 | .setMessageType(MessageType.USER.getValue()) | 96 | .setMessageType(MessageType.USER.getValue()) |
| @@ -98,7 +101,39 @@ public class ChatServiceImpl implements ChatService { | @@ -98,7 +101,39 @@ public class ChatServiceImpl implements ChatService { | ||
| 98 | 101 | ||
| 99 | // chat count 先+1 | 102 | // chat count 先+1 |
| 100 | aiChatConversationMapper.updateIncrChatCount(req.getConversationId()); | 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 | private AiChatConversationDO createNewChatConversation(ChatReq req, Long loginUserId) { | 139 | private AiChatConversationDO createNewChatConversation(ChatReq req, Long loginUserId) { |
| @@ -133,16 +168,52 @@ public class ChatServiceImpl implements ChatService { | @@ -133,16 +168,52 @@ public class ChatServiceImpl implements ChatService { | ||
| 133 | * chat stream | 168 | * chat stream |
| 134 | * | 169 | * |
| 135 | * @param req | 170 | * @param req |
| 171 | + * @param sseEmitter | ||
| 136 | * @return | 172 | * @return |
| 137 | */ | 173 | */ |
| 138 | @Override | 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 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); | 178 | AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); |
| 179 | + // 获取 对话类型(新建还是继续) | ||
| 180 | + ChatConversationTypeEnum chatConversationTypeEnum = ChatConversationTypeEnum.valueOfType(req.getConversationType()); | ||
| 181 | + AiChatConversationDO aiChatConversationDO = getChatConversationNoExistToCreate(req, chatConversationTypeEnum, loginUserId); | ||
| 141 | // 创建 chat 需要的 Prompt | 182 | // 创建 chat 需要的 Prompt |
| 142 | Prompt prompt = new Prompt(req.getPrompt()); | 183 | Prompt prompt = new Prompt(req.getPrompt()); |
| 143 | req.setTopK(req.getTopK()); | 184 | req.setTopK(req.getTopK()); |
| 144 | req.setTopP(req.getTopP()); | 185 | req.setTopP(req.getTopP()); |
| 145 | req.setTemperature(req.getTemperature()); | 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 | } |