Commit f84d25d3b710f0788c0427f5f0d59739a291033d

Authored by cherishsince
1 parent 905ce773

stream 保存聊天记录

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 }