Commit 779499222507f20c2a4fe1157fabde3881f280e0

Authored by cherishsince
1 parent a2bd9b71

聊天对话,增加 创建对话、还是继续对话逻辑

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ChatTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 聊天类型
  8 + *
  9 + * @author fansili
  10 + * @time 2024/4/14 17:58
  11 + * @since 1.0
  12 + */
  13 +@AllArgsConstructor
  14 +@Getter
  15 +public enum ChatTypeEnum {
  16 +
  17 + ROLE_CHAT("roleChat", "角色模板聊天"),
  18 + USER_CHAT("userChat", "用户普通聊天"),
  19 +
  20 + ;
  21 +
  22 + private String type;
  23 +
  24 + private String name;
  25 +
  26 + public static ChatTypeEnum valueOfType(String type) {
  27 + for (ChatTypeEnum itemEnum : ChatTypeEnum.values()) {
  28 + if (itemEnum.getType().equals(type)) {
  29 + return itemEnum;
  30 + }
  31 + }
  32 + throw new IllegalArgumentException("Invalid MessageType value: " + type);
  33 + }
  34 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dataobject/AiChatMessageDO.java
@@ -23,12 +23,12 @@ public class AiChatMessageDO { @@ -23,12 +23,12 @@ public class AiChatMessageDO {
23 /** 23 /**
24 * 聊天ID,关联到特定的会话或对话 24 * 聊天ID,关联到特定的会话或对话
25 */ 25 */
26 - private Long chatId; 26 + private Long chatConversationId;
27 27
28 /** 28 /**
29 * 角色ID,用于标识发送消息的用户或系统的身份 29 * 角色ID,用于标识发送消息的用户或系统的身份
30 */ 30 */
31 - private String userId; 31 + private Long userId;
32 32
33 /** 33 /**
34 * 消息具体内容,存储用户的发言或者系统响应的文字信息 34 * 消息具体内容,存储用户的发言或者系统响应的文字信息
@@ -38,7 +38,7 @@ public class AiChatMessageDO { @@ -38,7 +38,7 @@ public class AiChatMessageDO {
38 /** 38 /**
39 * 消息类型,枚举值可能包括'system'(系统消息)、'user'(用户消息)和'assistant'(助手消息) 39 * 消息类型,枚举值可能包括'system'(系统消息)、'user'(用户消息)和'assistant'(助手消息)
40 */ 40 */
41 - private Double messageType; 41 + private String messageType;
42 42
43 /** 43 /**
44 * 在生成消息时采用的Top-K采样大小, 44 * 在生成消息时采用的Top-K采样大小,
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/ChatServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.impl; 1 package cn.iocoder.yudao.module.ai.service.impl;
2 2
  3 +import cn.hutool.core.exceptions.ExceptionUtil;
3 import cn.iocoder.yudao.framework.ai.chat.ChatResponse; 4 import cn.iocoder.yudao.framework.ai.chat.ChatResponse;
  5 +import cn.iocoder.yudao.framework.ai.chat.messages.MessageType;
4 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; 6 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
5 import cn.iocoder.yudao.framework.ai.config.AiClient; 7 import cn.iocoder.yudao.framework.ai.config.AiClient;
  8 +import cn.iocoder.yudao.framework.common.exception.ServerException;
  9 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  10 +import cn.iocoder.yudao.module.ai.ErrorCodeConstants;
  11 +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.AiChatRoleDO;
6 import cn.iocoder.yudao.module.ai.enums.AiClientNameEnum; 14 import cn.iocoder.yudao.module.ai.enums.AiClientNameEnum;
  15 +import cn.iocoder.yudao.module.ai.enums.ChatConversationTypeEnum;
  16 +import cn.iocoder.yudao.module.ai.enums.ChatTypeEnum;
  17 +import cn.iocoder.yudao.module.ai.mapper.AiChatConversationMapper;
  18 +import cn.iocoder.yudao.module.ai.mapper.AiChatMessageMapper;
  19 +import cn.iocoder.yudao.module.ai.mapper.AiChatRoleMapper;
7 import cn.iocoder.yudao.module.ai.service.ChatService; 20 import cn.iocoder.yudao.module.ai.service.ChatService;
8 import cn.iocoder.yudao.module.ai.vo.ChatReq; 21 import cn.iocoder.yudao.module.ai.vo.ChatReq;
9 import lombok.AllArgsConstructor; 22 import lombok.AllArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
11 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
  25 +import org.springframework.transaction.annotation.Transactional;
12 import reactor.core.publisher.Flux; 26 import reactor.core.publisher.Flux;
13 27
14 /** 28 /**
@@ -24,6 +38,10 @@ import reactor.core.publisher.Flux; @@ -24,6 +38,10 @@ import reactor.core.publisher.Flux;
24 public class ChatServiceImpl implements ChatService { 38 public class ChatServiceImpl implements ChatService {
25 39
26 private final AiClient aiClient; 40 private final AiClient aiClient;
  41 + private final AiChatRoleMapper aiChatRoleMapper;
  42 + private final AiChatMessageMapper aiChatMessageMapper;
  43 + private final AiChatConversationMapper aiChatConversationMapper;
  44 +
27 45
28 /** 46 /**
29 * chat 47 * chat
@@ -31,16 +49,84 @@ public class ChatServiceImpl implements ChatService { @@ -31,16 +49,84 @@ public class ChatServiceImpl implements ChatService {
31 * @param req 49 * @param req
32 * @return 50 * @return
33 */ 51 */
  52 + @Transactional(rollbackFor = Exception.class)
34 public String chat(ChatReq req) { 53 public String chat(ChatReq req) {
  54 + // 获取 client 类型
35 AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal()); 55 AiClientNameEnum clientNameEnum = AiClientNameEnum.valueOfName(req.getModal());
36 - // 创建 chat 需要的 Prompt  
37 - Prompt prompt = new Prompt(req.getPrompt());  
38 - req.setTopK(req.getTopK());  
39 - req.setTopP(req.getTopP());  
40 - req.setTemperature(req.getTemperature());  
41 - // 发送 call 调用  
42 - ChatResponse call = aiClient.call(prompt, clientNameEnum.getName());  
43 - return call.getResult().getOutput().getContent(); 56 + // 获取 对话类型(新建还是继续)
  57 + ChatConversationTypeEnum chatConversationTypeEnum = ChatConversationTypeEnum.valueOfType(req.getConversationType());
  58 +
  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 + }
  71 +
  72 + String content;
  73 + try {
  74 + // 创建 chat 需要的 Prompt
  75 + Prompt prompt = new Prompt(req.getPrompt());
  76 + req.setTopK(req.getTopK());
  77 + req.setTopP(req.getTopP());
  78 + req.setTemperature(req.getTemperature());
  79 + // 发送 call 调用
  80 + ChatResponse call = aiClient.call(prompt, clientNameEnum.getName());
  81 + content = call.getResult().getOutput().getContent();
  82 + } catch (Exception e) {
  83 + content = ExceptionUtil.getMessage(e);
  84 + }
  85 +
  86 + // 增加 chat message 记录
  87 + aiChatMessageMapper.insert(
  88 + new AiChatMessageDO()
  89 + .setId(null)
  90 + .setChatConversationId(aiChatConversationDO.getId())
  91 + .setUserId(loginUserId)
  92 + .setMessage(req.getPrompt())
  93 + .setMessageType(MessageType.USER.getValue())
  94 + .setTopK(req.getTopK())
  95 + .setTopP(req.getTopP())
  96 + .setTemperature(req.getTemperature())
  97 + );
  98 +
  99 + // chat count 先+1
  100 + aiChatConversationMapper.updateIncrChatCount(req.getConversationId());
  101 + return content;
  102 + }
  103 +
  104 + private AiChatConversationDO createNewChatConversation(ChatReq req, Long loginUserId) {
  105 + // 获取 chat 角色
  106 + String chatRoleName = null;
  107 + ChatTypeEnum chatTypeEnum = null;
  108 + Long chatRoleId = req.getChatRoleId();
  109 + if (req.getChatRoleId() != null) {
  110 + AiChatRoleDO aiChatRoleDO = aiChatRoleMapper.selectById(chatRoleId);
  111 + if (aiChatRoleDO == null) {
  112 + throw new ServerException(ErrorCodeConstants.AI_CHAT_ROLE_NOT_EXISTENT);
  113 + }
  114 + chatTypeEnum = ChatTypeEnum.ROLE_CHAT;
  115 + chatRoleName = aiChatRoleDO.getRoleName();
  116 + } else {
  117 + chatTypeEnum = ChatTypeEnum.USER_CHAT;
  118 + }
  119 + //
  120 + AiChatConversationDO insertChatConversation = new AiChatConversationDO()
  121 + .setId(null)
  122 + .setUserId(loginUserId)
  123 + .setChatRoleId(req.getChatRoleId())
  124 + .setChatRoleName(chatRoleName)
  125 + .setChatType(chatTypeEnum.getType())
  126 + .setChatCount(1)
  127 + .setChatTitle(req.getPrompt().substring(0, 20) + "...");
  128 + aiChatConversationMapper.insert(insertChatConversation);
  129 + return insertChatConversation;
44 } 130 }
45 131
46 /** 132 /**
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/vo/ChatReq.java
@@ -24,19 +24,29 @@ public class ChatReq { @@ -24,19 +24,29 @@ public class ChatReq {
24 @Schema(description = "填入固定值,1 issues, 2 pr") 24 @Schema(description = "填入固定值,1 issues, 2 pr")
25 private String prompt; 25 private String prompt;
26 26
  27 + @Schema(description = "chat角色模板")
  28 + private Long chatRoleId;
  29 +
27 @Schema(description = "用于控制随机性和多样性的温度参数") 30 @Schema(description = "用于控制随机性和多样性的温度参数")
28 - private Float temperature; 31 + private Double temperature;
29 32
30 @Schema(description = "生成时,核采样方法的概率阈值。例如,取值为0.8时,仅保留累计概率之和大于等于0.8的概率分布中的token,\n" + 33 @Schema(description = "生成时,核采样方法的概率阈值。例如,取值为0.8时,仅保留累计概率之和大于等于0.8的概率分布中的token,\n" +
31 " * 作为随机采样的候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。\n" + 34 " * 作为随机采样的候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。\n" +
32 " * 默认值为0.8。注意,取值不要大于等于1\n") 35 " * 默认值为0.8。注意,取值不要大于等于1\n")
33 - private Float topP; 36 + private Double topP;
34 37
35 @Schema(description = "在生成消息时采用的Top-K采样大小,表示模型生成回复时考虑的候选项集合的大小") 38 @Schema(description = "在生成消息时采用的Top-K采样大小,表示模型生成回复时考虑的候选项集合的大小")
36 - private Integer topK; 39 + private Double topK;
37 40
38 @Schema(description = "ai模型(查看 AiClientNameEnum)") 41 @Schema(description = "ai模型(查看 AiClientNameEnum)")
39 @NotNull(message = "模型不能为空!") 42 @NotNull(message = "模型不能为空!")
40 @Size(max = 30, message = "模型字符最大30个字符!") 43 @Size(max = 30, message = "模型字符最大30个字符!")
41 private String modal; 44 private String modal;
  45 +
  46 + @Schema(description = "对话类型(new、continue)")
  47 + @NotNull(message = "对话类型,不能为空!")
  48 + private String conversationType;
  49 +
  50 + @Schema(description = "对话Id")
  51 + private Long conversationId;
42 } 52 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/messages/AbstractMessage.java
@@ -59,7 +59,7 @@ public abstract class AbstractMessage implements Message { @@ -59,7 +59,7 @@ public abstract class AbstractMessage implements Message {
59 } 59 }
60 60
61 protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData, 61 protected AbstractMessage(MessageType messageType, String textContent, List<MediaData> mediaData,
62 - Map<String, Object> messageProperties) { 62 + Map<String, Object> messageProperties) {
63 63
64 Assert.notNull(messageType, "Message type must not be null"); 64 Assert.notNull(messageType, "Message type must not be null");
65 Assert.notNull(textContent, "Content must not be null"); 65 Assert.notNull(textContent, "Content must not be null");