Commit 5a4162cdc13bc9cb8939133cb24f1a9a936d3435

Authored by cherishsince
1 parent aaf1599f

【优化】优化 chat event stream 模式交互,增加 add message 优先记录

Showing 13 changed files with 155 additions and 233 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java
@@ -36,5 +36,9 @@ public interface ErrorCodeConstants { @@ -36,5 +36,9 @@ public interface ErrorCodeConstants {
36 ErrorCode AI_CHAT_ROLE_NOT_EXIST = new ErrorCode(1_022_000_060, "AI 角色不存在!"); 36 ErrorCode AI_CHAT_ROLE_NOT_EXIST = new ErrorCode(1_022_000_060, "AI 角色不存在!");
37 ErrorCode AI_CHAT_ROLE_NOT_PUBLIC = new ErrorCode(1_022_000_060, "AI 角色未公开!"); 37 ErrorCode AI_CHAT_ROLE_NOT_PUBLIC = new ErrorCode(1_022_000_060, "AI 角色未公开!");
38 38
  39 + // chat
  40 +
  41 + ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_022_000_100, "AI 提问的 MessageId 不存在!");
  42 +
39 43
40 } 44 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
1 package cn.iocoder.yudao.module.ai.controller.admin.chat; 1 package cn.iocoder.yudao.module.ai.controller.admin.chat;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;  
5 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 4 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
6 import cn.iocoder.yudao.module.ai.service.AiChatService; 5 import cn.iocoder.yudao.module.ai.service.AiChatService;
7 import io.swagger.v3.oas.annotations.Operation; 6 import io.swagger.v3.oas.annotations.Operation;
8 import io.swagger.v3.oas.annotations.Parameter; 7 import io.swagger.v3.oas.annotations.Parameter;
@@ -38,10 +37,16 @@ public class AiChatMessageController { @@ -38,10 +37,16 @@ public class AiChatMessageController {
38 // TODO @fan:要不要使用 Flux 来返回;可以使用 Flux<AiChatMessageRespVO> 37 // TODO @fan:要不要使用 Flux 来返回;可以使用 Flux<AiChatMessageRespVO>
39 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") 38 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
40 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 39 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
41 - public Flux<AiChatMessageRespVO> sendMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { 40 + public Flux<AiChatMessageRespVO> sendMessageStream(@Validated @RequestBody AiChatMessageSendStreamReqVO sendReqVO) {
42 return chatService.chatStream(sendReqVO); 41 return chatService.chatStream(sendReqVO);
43 } 42 }
44 43
  44 + @Operation(summary = "添加/提问", description = "先创建好 message 前端才好渲染")
  45 + @PostMapping(value = "/add")
  46 + public CommonResult<AiChatMessageRespVO> add(@Validated @RequestBody AiChatMessageAddReqVO req) {
  47 + return success(chatService.add(req));
  48 + }
  49 +
45 @Operation(summary = "获得指定会话的消息列表") 50 @Operation(summary = "获得指定会话的消息列表")
46 @GetMapping("/list-by-conversation-id") 51 @GetMapping("/list-by-conversation-id")
47 @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024") 52 @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageAddReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - AI 聊天消息发送 Request VO")
  9 +@Data
  10 +public class AiChatMessageAddReqVO {
  11 +
  12 + @Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + @NotNull(message = "聊天对话编号不能为空")
  14 + private Long conversationId;
  15 +
  16 + @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
  17 + @NotEmpty(message = "聊天内容不能为空")
  18 + private String content;
  19 +
  20 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageAddRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - AI 聊天消息 Add Response VO")
  9 +@Data
  10 +public class AiChatMessageAddRespVO {
  11 +
  12 + @Schema(description = "用户信息")
  13 + private AiChatMessageRespVO userMessage;
  14 +
  15 + @Schema(description = "系统信息")
  16 + private AiChatMessageRespVO systemMessage;
  17 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendStreamReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - AI 聊天消息发送 Request VO")
  9 +@Data
  10 +public class AiChatMessageSendStreamReqVO {
  11 +
  12 + @Schema(description = "提问的 messageId", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + @NotNull(message = "提问的 messageId 不能为空")
  14 + private Long id;
  15 +
  16 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java
@@ -26,4 +26,13 @@ public interface AiChatMessageConvert { @@ -26,4 +26,13 @@ public interface AiChatMessageConvert {
26 * @return 26 * @return
27 */ 27 */
28 List<AiChatMessageRespVO> convertAiChatMessageRespVOList(List<AiChatMessageDO> aiChatMessageDOList); 28 List<AiChatMessageRespVO> convertAiChatMessageRespVOList(List<AiChatMessageDO> aiChatMessageDOList);
  29 +
  30 + /**
  31 + * 转换 - aiChatMessageDO
  32 + *
  33 + * @param aiChatMessageDO
  34 + * @return
  35 + */
  36 + AiChatMessageRespVO convertAiChatMessageRespVO(AiChatMessageDO aiChatMessageDO);
  37 +
29 } 38 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java
@@ -11,7 +11,6 @@ import org.apache.ibatis.annotations.Mapper; @@ -11,7 +11,6 @@ import org.apache.ibatis.annotations.Mapper;
11 11
12 import java.util.Collection; 12 import java.util.Collection;
13 import java.util.List; 13 import java.util.List;
14 -import java.util.Set;  
15 14
16 /** 15 /**
17 * API 聊天模型 Mapper 16 * API 聊天模型 Mapper
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatModelService.java deleted
1 -package cn.iocoder.yudao.module.ai.service;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
4 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.*;  
5 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
6 -  
7 -import java.util.List;  
8 -import java.util.Set;  
9 -  
10 -/**  
11 - * ai modal  
12 - *  
13 - * @author fansili  
14 - * @time 2024/4/24 19:42  
15 - * @since 1.0  
16 - */  
17 -public interface AiChatModelService {  
18 -  
19 - /**  
20 - * ai modal - 列表  
21 - *  
22 - * @param req  
23 - * @return  
24 - */  
25 - PageResult<AiChatModelListRespVO> list(AiChatModelListReqVO req);  
26 -  
27 - /**  
28 - * ai modal - 添加  
29 - *  
30 - * @param req  
31 - */  
32 - void add(AiChatModelAddReqVO req);  
33 -  
34 - /**  
35 - * ai modal - 更新  
36 - *  
37 - * @param req  
38 - */  
39 - void update(AiChatModelUpdateReqVO req);  
40 -  
41 - /**  
42 - * ai modal - 删除  
43 - *  
44 - * @param id  
45 - */  
46 - void delete(Long id);  
47 -  
48 - /**  
49 - * 获取 - 获取 modal  
50 - *  
51 - * @param modalId  
52 - * @return  
53 - */  
54 - AiChatModalRespVO getChatModalOfValidate(Long modalId);  
55 -  
56 - /**  
57 - * 校验 - 是否存在  
58 - *  
59 - * @param id  
60 - * @return  
61 - */  
62 - AiChatModelDO validateExists(Long id);  
63 -  
64 - /**  
65 - * 校验 - 校验是否可用  
66 - *  
67 - * @param chatModal  
68 - */  
69 - void validateAvailable(AiChatModalRespVO chatModal);  
70 -  
71 - /**  
72 - * 获取 - 根据 ids 批量获取  
73 - *  
74 - * @param modalIds  
75 - * @return  
76 - */  
77 - List<AiChatModelDO> getModalByIds(Set<Long> modalIds);  
78 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java
1 package cn.iocoder.yudao.module.ai.service; 1 package cn.iocoder.yudao.module.ai.service;
2 2
3 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;  
4 -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 3 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
5 import reactor.core.publisher.Flux; 4 import reactor.core.publisher.Flux;
6 5
7 import java.util.List; 6 import java.util.List;
@@ -29,7 +28,15 @@ public interface AiChatService { @@ -29,7 +28,15 @@ public interface AiChatService {
29 * @param sendReqVO 28 * @param sendReqVO
30 * @return 29 * @return
31 */ 30 */
32 - Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendReqVO sendReqVO); 31 + Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendStreamReqVO sendReqVO);
  32 +
  33 + /**
  34 + * 添加 - message
  35 + *
  36 + * @param sendReqVO
  37 + * @return
  38 + */
  39 + AiChatMessageRespVO add(AiChatMessageAddReqVO sendReqVO);
33 40
34 /** 41 /**
35 * 获取 - 获取对话 message list 42 * 获取 - 获取对话 message list
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatModalServiceImpl.java deleted
1 -package cn.iocoder.yudao.module.ai.service.impl;  
2 -  
3 -import cn.hutool.core.util.StrUtil;  
4 -import cn.hutool.extra.validation.ValidationUtil;  
5 -import cn.iocoder.yudao.framework.ai.AiPlatformEnum;  
6 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
7 -import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;  
8 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
9 -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;  
10 -import cn.iocoder.yudao.module.ai.ErrorCodeConstants;  
11 -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.*;  
12 -import cn.iocoder.yudao.module.ai.convert.AiChatModelConvert;  
13 -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;  
14 -import cn.iocoder.yudao.module.ai.dal.mysql.AiChatModelMapper;  
15 -import cn.iocoder.yudao.module.ai.dal.vo.AiChatModalConfigVO;  
16 -import cn.iocoder.yudao.module.ai.service.AiChatModelService;  
17 -import jakarta.validation.ConstraintViolation;  
18 -import lombok.AllArgsConstructor;  
19 -import lombok.extern.slf4j.Slf4j;  
20 -import org.springframework.stereotype.Service;  
21 -  
22 -import java.util.List;  
23 -import java.util.Set;  
24 -  
25 -/**  
26 - * ai 模型  
27 - *  
28 - * @author fansili  
29 - * @time 2024/4/24 19:42  
30 - * @since 1.0  
31 - */  
32 -@AllArgsConstructor  
33 -@Service  
34 -@Slf4j  
35 -public class AiChatModalServiceImpl implements AiChatModelService {  
36 -  
37 - private final AiChatModelMapper aiChatModelMapper;  
38 -  
39 - @Override  
40 - public PageResult<AiChatModelListRespVO> list(AiChatModelListReqVO req) {  
41 - LambdaQueryWrapperX<AiChatModelDO> queryWrapperX = new LambdaQueryWrapperX<>();  
42 - // 查询的都是未禁用的模型  
43 - queryWrapperX.eq(AiChatModelDO::getStatus, CommonStatusEnum.ENABLE.getStatus());  
44 - // search  
45 - if (!StrUtil.isBlank(req.getSearch())) {  
46 - queryWrapperX.like(AiChatModelDO::getName, req.getSearch().trim());  
47 - }  
48 - // 默认排序  
49 - queryWrapperX.orderByAsc(AiChatModelDO::getSort);  
50 - // 查询  
51 - PageResult<AiChatModelDO> aiChatModalDOPageResult = aiChatModelMapper.selectPage(req, queryWrapperX);  
52 - // 转换 res  
53 - List<AiChatModelListRespVO> resList = AiChatModelConvert.INSTANCE.convertAiChatModalListRes(aiChatModalDOPageResult.getList());  
54 - return new PageResult<>(resList, aiChatModalDOPageResult.getTotal());  
55 - }  
56 -  
57 - @Override  
58 - public void add(AiChatModelAddReqVO req) {  
59 - // 校验 platform、type  
60 - validatePlatform(req.getPlatform());  
61 - // 转换 do  
62 - AiChatModelDO insertChatModalDO = AiChatModelConvert.INSTANCE.convertAiChatModalDO(req);  
63 - // 设置默认属性  
64 - insertChatModalDO.setStatus(CommonStatusEnum.ENABLE.getStatus());  
65 - // 保存数据库  
66 - aiChatModelMapper.insert(insertChatModalDO);  
67 - }  
68 -  
69 - @Override  
70 - public void update(AiChatModelUpdateReqVO req) {  
71 - // 校验 platform  
72 - validatePlatform(req.getPlatform());  
73 - // 校验模型是否存在  
74 - validateExists(req.getId());  
75 - // 转换 updateChatModalDO  
76 - AiChatModelDO updateChatModalDO = AiChatModelConvert.INSTANCE.convertAiChatModalDO(req);  
77 - updateChatModalDO.setId(req.getId());  
78 - // 更新数据库  
79 - aiChatModelMapper.updateById(updateChatModalDO);  
80 - }  
81 -  
82 - @Override  
83 - public void delete(Long id) {  
84 - // 检查 modal 是否存在  
85 - validateExists(id);  
86 - // 删除 delete  
87 - aiChatModelMapper.deleteById(id);  
88 - }  
89 -  
90 - @Override  
91 - public AiChatModalRespVO getChatModalOfValidate(Long modalId) {  
92 - // 检查 modal 是否存在  
93 - AiChatModelDO aiChatModalDO = validateExists(modalId);  
94 - return AiChatModelConvert.INSTANCE.convertAiChatModalRes(aiChatModalDO);  
95 - }  
96 -  
97 - @Override  
98 - public void validateAvailable(AiChatModalRespVO chatModal) {  
99 - // 对话模型是否可用  
100 - if (!CommonStatusEnum.ENABLE.getStatus().equals(chatModal.getStatus())) {  
101 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MODAL_DISABLE_NOT_USED);  
102 - }  
103 - }  
104 -  
105 - @Override  
106 - public List<AiChatModelDO> getModalByIds(Set<Long> modalIds) {  
107 - return aiChatModelMapper.selectByIds(modalIds);  
108 - }  
109 -  
110 - public AiChatModelDO validateExists(Long id) {  
111 - AiChatModelDO aiChatModalDO = aiChatModelMapper.selectById(id);  
112 - if (aiChatModalDO == null) {  
113 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MODAL_NOT_EXIST);  
114 - }  
115 - return aiChatModalDO;  
116 - }  
117 -  
118 - private void validatePlatform(String platform) {  
119 - try {  
120 - AiPlatformEnum.valueOfPlatform(platform);  
121 - } catch (IllegalArgumentException e) {  
122 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MODAL_PLATFORM_PARAMS_INCORRECT, e.getMessage());  
123 - }  
124 - }  
125 -  
126 - private void validateModalConfig(AiChatModalConfigVO aiChatModalConfigVO) {  
127 - Set<ConstraintViolation<AiChatModalConfigVO>> validate = ValidationUtil.validate(aiChatModalConfigVO);  
128 - for (ConstraintViolation<AiChatModalConfigVO> constraintViolation : validate) {  
129 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MODAL_CONFIG_PARAMS_INCORRECT, constraintViolation.getMessage());  
130 - }  
131 - }  
132 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
@@ -7,11 +7,15 @@ import cn.iocoder.yudao.framework.ai.chat.ChatResponse; @@ -7,11 +7,15 @@ import cn.iocoder.yudao.framework.ai.chat.ChatResponse;
7 import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient; 7 import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient;
8 import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; 8 import cn.iocoder.yudao.framework.ai.chat.messages.MessageType;
9 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; 9 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  10 +import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
10 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 11 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  12 +import cn.iocoder.yudao.module.ai.ErrorCodeConstants;
11 import cn.iocoder.yudao.module.ai.config.AiChatClientFactory; 13 import cn.iocoder.yudao.module.ai.config.AiChatClientFactory;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
  15 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageAddReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; 16 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 17 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
  18 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendStreamReqVO;
15 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert; 19 import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
16 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; 20 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
17 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 21 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
@@ -19,11 +23,12 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; @@ -19,11 +23,12 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
19 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatConversationMapper; 23 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatConversationMapper;
20 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper; 24 import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper;
21 import cn.iocoder.yudao.module.ai.service.AiChatConversationService; 25 import cn.iocoder.yudao.module.ai.service.AiChatConversationService;
22 -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;  
23 import cn.iocoder.yudao.module.ai.service.AiChatRoleService; 26 import cn.iocoder.yudao.module.ai.service.AiChatRoleService;
24 import cn.iocoder.yudao.module.ai.service.AiChatService; 27 import cn.iocoder.yudao.module.ai.service.AiChatService;
  28 +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
25 import lombok.AllArgsConstructor; 29 import lombok.AllArgsConstructor;
26 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
  31 +import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
27 import org.springframework.stereotype.Service; 32 import org.springframework.stereotype.Service;
28 import org.springframework.transaction.annotation.Transactional; 33 import org.springframework.transaction.annotation.Transactional;
29 import reactor.core.publisher.Flux; 34 import reactor.core.publisher.Flux;
@@ -53,6 +58,7 @@ public class AiChatServiceImpl implements AiChatService { @@ -53,6 +58,7 @@ public class AiChatServiceImpl implements AiChatService {
53 private final AiChatConversationService chatConversationService; 58 private final AiChatConversationService chatConversationService;
54 private final AiChatModelService aiChatModalService; 59 private final AiChatModelService aiChatModalService;
55 private final AiChatRoleService aiChatRoleService; 60 private final AiChatRoleService aiChatRoleService;
  61 + private final HttpMessageConverters messageConverters;
56 62
57 @Transactional(rollbackFor = Exception.class) 63 @Transactional(rollbackFor = Exception.class)
58 public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) { 64 public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) {
@@ -124,10 +130,15 @@ public class AiChatServiceImpl implements AiChatService { @@ -124,10 +130,15 @@ public class AiChatServiceImpl implements AiChatService {
124 return insertChatMessageDO; 130 return insertChatMessageDO;
125 } 131 }
126 132
127 - public Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendReqVO req) { 133 + public Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendStreamReqVO req) {
128 Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); 134 Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
  135 + // 查询提问的 message
  136 + AiChatMessageDO aiChatMessageDO = aiChatMessageMapper.selectById(req.getId());
  137 + if (aiChatMessageDO == null) {
  138 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST);
  139 + }
129 // 查询对话 140 // 查询对话
130 - AiChatConversationRespVO conversation = chatConversationService.getConversationOfValidate(req.getConversationId()); 141 + AiChatConversationRespVO conversation = chatConversationService.getConversationOfValidate(aiChatMessageDO.getConversationId());
131 // 获取对话模型 142 // 获取对话模型
132 AiChatModelDO chatModel = aiChatModalService.validateChatModel(conversation.getModelId()); 143 AiChatModelDO chatModel = aiChatModalService.validateChatModel(conversation.getModelId());
133 // 获取角色信息 144 // 获取角色信息
@@ -138,14 +149,14 @@ public class AiChatServiceImpl implements AiChatService { @@ -138,14 +149,14 @@ public class AiChatServiceImpl implements AiChatService {
138 // 校验角色是否公开 149 // 校验角色是否公开
139 aiChatRoleService.validateIsPublic(aiChatRoleDO); 150 aiChatRoleService.validateIsPublic(aiChatRoleDO);
140 // 创建 chat 需要的 Prompt 151 // 创建 chat 需要的 Prompt
141 - Prompt prompt = new Prompt(req.getContent()); 152 + Prompt prompt = new Prompt(aiChatMessageDO.getContent());
  153 + // 提前创建一个 system message
  154 + AiChatMessageDO systemMessage = insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),
  155 + chatModel.getModel(), chatModel.getId(), "",
  156 + 0, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts());
142 // req.setTopK(req.getTopK()); 157 // req.setTopK(req.getTopK());
143 // req.setTopP(req.getTopP()); 158 // req.setTopP(req.getTopP());
144 // req.setTemperature(req.getTemperature()); 159 // req.setTemperature(req.getTemperature());
145 - // 保存 chat message  
146 - insertChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(),  
147 - chatModel.getModel(), chatModel.getId(), req.getContent(),  
148 - null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts());  
149 // 获取 client 类型 160 // 获取 client 类型
150 AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform()); 161 AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform());
151 StreamingChatClient streamingChatClient = aiChatClientFactory.getStreamingChatClient(platformEnum); 162 StreamingChatClient streamingChatClient = aiChatClientFactory.getStreamingChatClient(platformEnum);
@@ -154,7 +165,8 @@ public class AiChatServiceImpl implements AiChatService { @@ -154,7 +165,8 @@ public class AiChatServiceImpl implements AiChatService {
154 StringBuffer contentBuffer = new StringBuffer(); 165 StringBuffer contentBuffer = new StringBuffer();
155 AtomicInteger tokens = new AtomicInteger(0); 166 AtomicInteger tokens = new AtomicInteger(0);
156 return streamResponse.map(res -> { 167 return streamResponse.map(res -> {
157 - AiChatMessageRespVO aiChatMessageRespVO = new AiChatMessageRespVO(); 168 + AiChatMessageRespVO aiChatMessageRespVO =
  169 + AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVO(systemMessage);
158 aiChatMessageRespVO.setContent(res.getResult().getOutput().getContent()); 170 aiChatMessageRespVO.setContent(res.getResult().getOutput().getContent());
159 contentBuffer.append(res.getResult().getOutput().getContent()); 171 contentBuffer.append(res.getResult().getOutput().getContent());
160 tokens.incrementAndGet(); 172 tokens.incrementAndGet();
@@ -165,23 +177,47 @@ public class AiChatServiceImpl implements AiChatService { @@ -165,23 +177,47 @@ public class AiChatServiceImpl implements AiChatService {
165 public void run() { 177 public void run() {
166 log.info("发送完成!"); 178 log.info("发送完成!");
167 // 保存 chat message 179 // 保存 chat message
168 - insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),  
169 - chatModel.getModel(), chatModel.getId(), contentBuffer.toString(),  
170 - tokens.get(), conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); 180 + aiChatMessageMapper.updateById(new AiChatMessageDO()
  181 + .setId(systemMessage.getId())
  182 + .setContent(contentBuffer.toString())
  183 + .setTokens(tokens.get())
  184 + );
171 } 185 }
172 }).doOnError(new Consumer<Throwable>() { 186 }).doOnError(new Consumer<Throwable>() {
173 @Override 187 @Override
174 public void accept(Throwable throwable) { 188 public void accept(Throwable throwable) {
175 log.error("发送错误 {}!", throwable.getMessage()); 189 log.error("发送错误 {}!", throwable.getMessage());
176 - // 保存 chat message  
177 - insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(),  
178 - chatModel.getModel(), chatModel.getId(), throwable.getMessage(),  
179 - tokens.get(), conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); 190 + // 更新错误信息
  191 + aiChatMessageMapper.updateById(new AiChatMessageDO()
  192 + .setId(systemMessage.getId())
  193 + .setContent(throwable.getMessage())
  194 + .setTokens(tokens.get())
  195 + );
180 } 196 }
181 }); 197 });
182 } 198 }
183 199
184 @Override 200 @Override
  201 + public AiChatMessageRespVO add(AiChatMessageAddReqVO req) {
  202 + Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
  203 + // 查询对话
  204 + AiChatConversationRespVO conversation = chatConversationService.getConversationOfValidate(req.getConversationId());
  205 + // 获取对话模型
  206 + AiChatModelDO chatModel = aiChatModalService.validateChatModel(conversation.getModelId());
  207 + // 获取角色信息
  208 + AiChatRoleDO aiChatRoleDO = null;
  209 + if (conversation.getRoleId() != null) {
  210 + aiChatRoleDO = aiChatRoleService.validateExists(conversation.getRoleId());
  211 + }
  212 + // 校验角色是否公开
  213 + aiChatRoleService.validateIsPublic(aiChatRoleDO);
  214 + AiChatMessageDO userMessage = insertChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(),
  215 + chatModel.getModel(), chatModel.getId(), req.getContent(),
  216 + null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts());
  217 + return AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVO(userMessage);
  218 + }
  219 +
  220 + @Override
185 public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) { 221 public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) {
186 // 校验对话是否存在 222 // 校验对话是否存在
187 chatConversationService.validateExists(conversationId); 223 chatConversationService.validateExists(conversationId);
@@ -207,4 +243,5 @@ public class AiChatServiceImpl implements AiChatService { @@ -207,4 +243,5 @@ public class AiChatServiceImpl implements AiChatService {
207 public Boolean deleteMessage(Long id) { 243 public Boolean deleteMessage(Long id) {
208 return aiChatMessageMapper.deleteById(id) > 0; 244 return aiChatMessageMapper.deleteById(id) > 0;
209 } 245 }
  246 +
210 } 247 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java
@@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatMode @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatMode
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.util.List;
  10 +import java.util.Set;
  11 +
9 /** 12 /**
10 * AI 聊天模型 Service 接口 13 * AI 聊天模型 Service 接口
11 * 14 *
@@ -60,4 +63,11 @@ public interface AiChatModelService { @@ -60,4 +63,11 @@ public interface AiChatModelService {
60 */ 63 */
61 AiChatModelDO validateChatModel(Long id); 64 AiChatModelDO validateChatModel(Long id);
62 65
  66 + /**
  67 + * 获取 - 根据多个 ids 获取
  68 + *
  69 + * @param modalIds
  70 + * @return
  71 + */
  72 + List<AiChatModelDO> getModalByIds(Set<Long> modalIds);
63 } 73 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java
@@ -12,6 +12,9 @@ import jakarta.annotation.Resource; @@ -12,6 +12,9 @@ import jakarta.annotation.Resource;
12 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
13 import org.springframework.validation.annotation.Validated; 13 import org.springframework.validation.annotation.Validated;
14 14
  15 +import java.util.List;
  16 +import java.util.Set;
  17 +
15 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16 import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.*; 19 import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.*;
17 20
@@ -89,4 +92,9 @@ public class AiChatModelServiceImpl implements AiChatModelService { @@ -89,4 +92,9 @@ public class AiChatModelServiceImpl implements AiChatModelService {
89 return model; 92 return model;
90 } 93 }
91 94
  95 + @Override
  96 + public List<AiChatModelDO> getModalByIds(Set<Long> modalIds) {
  97 + return chatModelMapper.selectByIds(modalIds);
  98 + }
  99 +
92 } 100 }
93 \ No newline at end of file 101 \ No newline at end of file