mirror of
https://github.com/zongzibinbin/MallChat.git
synced 2025-12-26 04:47:53 +08:00
chatAI-handler
This commit is contained in:
parent
c06934bc89
commit
440159e8a0
@ -37,9 +37,14 @@ public class RedisKey {
|
||||
public static final String USER_SUMMARY_STRING = "userSummary:uid_%d";
|
||||
|
||||
/**
|
||||
* 用户AI聊天次数
|
||||
* 用户GPT聊天次数
|
||||
*/
|
||||
public static final String USER_CHAT_NUM = "userAIChatNum:uid_%d";
|
||||
public static final String USER_CHAT_NUM = "useChatGPTNum:uid_%d";
|
||||
|
||||
/**
|
||||
* 用户上次使用GLM使用时间
|
||||
*/
|
||||
public static final String USER_GLM2_TIME_LAST = "userGLM2UseTime:uid_%d";
|
||||
|
||||
public static String getKey(String key, Object... objects) {
|
||||
return BASE_KEY + String.format(key, objects);
|
||||
|
||||
@ -63,8 +63,13 @@ wx:
|
||||
secret: ${mallchat.wx.secret} # 公众号的appsecret
|
||||
token: ${mallchat.wx.token} # 接口配置里的Token值
|
||||
aesKey: ${mallchat.wx.aesKey} # 接口配置里的EncodingAESKey值
|
||||
openai:
|
||||
use-openai: true
|
||||
ai-user-id: xxxxx
|
||||
key: xxxxxxx
|
||||
proxy-url: https://xxxxxxx
|
||||
chatai:
|
||||
chatgpt:
|
||||
use: true
|
||||
AIUserId: 10450
|
||||
key: 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
|
||||
chatglm2:
|
||||
use: true
|
||||
url: http://vastmiao.natapp1.cc
|
||||
minute: 3 # 每个用户每3分钟可以请求一次
|
||||
AIUserId: 10451
|
||||
|
||||
@ -11,9 +11,13 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* 聊天信息点播
|
||||
* Description: 消息发送请求体
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-03-23
|
||||
*
|
||||
* @author zhaoyuhang
|
||||
* @date 2023/06/30
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
|
||||
@ -38,7 +38,6 @@ import com.abin.mallchat.custom.chat.service.strategy.mark.MsgMarkFactory;
|
||||
import com.abin.mallchat.custom.chat.service.strategy.msg.AbstractMsgHandler;
|
||||
import com.abin.mallchat.custom.chat.service.strategy.msg.MsgHandlerFactory;
|
||||
import com.abin.mallchat.custom.chat.service.strategy.msg.RecallMsgHandler;
|
||||
import com.abin.mallchat.custom.openai.event.OpenAIEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -96,7 +95,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
msgHandler.saveMsg(insert, request);
|
||||
//发布消息发送事件
|
||||
applicationEventPublisher.publishEvent(new MessageSendEvent(this, insert.getId()));
|
||||
applicationEventPublisher.publishEvent(new OpenAIEvent(this, insert.getId()));
|
||||
return insert.getId();
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.abin.mallchat.custom.openai.enums;
|
||||
package com.abin.mallchat.custom.chatai.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@ -10,7 +10,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum OpenAIModelEnums {
|
||||
public enum ChatGPTModelEnum {
|
||||
// chat
|
||||
GPT_35_TURBO("gpt-3.5-turbo", 3, 40000),
|
||||
GPT_35_TURBO_0301("gpt-3.5-turbo-0301", 3, 40000),
|
||||
@ -79,13 +79,13 @@ public enum OpenAIModelEnums {
|
||||
*/
|
||||
private final Integer TPM;
|
||||
|
||||
private static final Map<String, OpenAIModelEnums> cache;
|
||||
private static final Map<String, ChatGPTModelEnum> cache;
|
||||
|
||||
static {
|
||||
cache = Arrays.stream(OpenAIModelEnums.values()).collect(Collectors.toMap(OpenAIModelEnums::getName, Function.identity()));
|
||||
cache = Arrays.stream(ChatGPTModelEnum.values()).collect(Collectors.toMap(ChatGPTModelEnum::getName, Function.identity()));
|
||||
}
|
||||
|
||||
public static OpenAIModelEnums of(String name) {
|
||||
public static ChatGPTModelEnum of(String name) {
|
||||
return cache.get(name);
|
||||
}
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
package com.abin.mallchat.custom.chatai.handler;
|
||||
|
||||
import cn.hutool.core.thread.NamedThreadFactory;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.handler.GlobalUncaughtExceptionHandler;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.msg.TextMsgReq;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp;
|
||||
import com.abin.mallchat.custom.user.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public abstract class AbstractChatAIHandler implements DisposableBean, InitializingBean {
|
||||
public static ExecutorService EXECUTOR;
|
||||
|
||||
@Autowired
|
||||
protected ChatService chatService;
|
||||
@Autowired
|
||||
protected UserService userService;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
ChatAIHandlerFactory.register(getChatAIUserId(), getChatAIName(), this);
|
||||
}
|
||||
// 获取机器人id
|
||||
public abstract Long getChatAIUserId();
|
||||
// 获取机器人名称
|
||||
public abstract String getChatAIName();
|
||||
|
||||
public void chat(Message message) {
|
||||
if (!supports(message)) {
|
||||
return;
|
||||
}
|
||||
EXECUTOR.execute(() -> {
|
||||
String text = doChat(message);
|
||||
if (StringUtils.isNotBlank(text)) {
|
||||
answerMsg(text, message.getRoomId(), message.getFromUid());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持
|
||||
*
|
||||
* @param message 消息
|
||||
* @return boolean true 支持 false 不支持
|
||||
*/
|
||||
protected abstract boolean supports(Message message);
|
||||
|
||||
/**
|
||||
* 执行聊天
|
||||
*
|
||||
* @param message 消息
|
||||
* @return {@link String} AI回答的内容
|
||||
*/
|
||||
protected abstract String doChat(Message message);
|
||||
|
||||
|
||||
protected void answerMsg(String text, Long roomId, Long uid) {
|
||||
UserInfoResp userInfo = userService.getUserInfo(uid);
|
||||
text = "@" + userInfo.getName() + " " + text;
|
||||
if (text.length() < 450) {
|
||||
save(text, roomId, uid);
|
||||
}else {
|
||||
int maxLen = 450;
|
||||
int len = text.length();
|
||||
int count = (len + maxLen - 1) / maxLen;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int start = i * maxLen;
|
||||
int end = Math.min(start + maxLen, len);
|
||||
save(text.substring(start, end), roomId, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save(String text, Long roomId, Long uid) {
|
||||
ChatMessageReq answerReq = new ChatMessageReq();
|
||||
answerReq.setRoomId(roomId);
|
||||
answerReq.setMsgType(MessageTypeEnum.TEXT.getType());
|
||||
TextMsgReq textMsgReq = new TextMsgReq();
|
||||
textMsgReq.setContent(text);
|
||||
textMsgReq.setAtUidList(Collections.singletonList(uid));
|
||||
answerReq.setBody(textMsgReq);
|
||||
chatService.sendMsg(answerReq, getChatAIUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
EXECUTOR = new ThreadPoolExecutor(
|
||||
10,
|
||||
10,
|
||||
0L,
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(15),
|
||||
new NamedThreadFactory("openAI-chat-gpt",
|
||||
null,
|
||||
false,
|
||||
new GlobalUncaughtExceptionHandler()),
|
||||
(r, executor) -> {
|
||||
throw new BusinessException("别问的太快了,我的脑子不够用了");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
EXECUTOR.shutdown();
|
||||
if (!EXECUTOR.awaitTermination(30, TimeUnit.SECONDS)) { //最多等30秒,处理不完就拉倒
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Timed out while waiting for executor [{}] to terminate", EXECUTOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.abin.mallchat.custom.chatai.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ChatAIHandlerFactory {
|
||||
private static final Map<Long, AbstractChatAIHandler> CHATAI_ID_MAP = new ConcurrentHashMap<>();
|
||||
private static final Map<String, AbstractChatAIHandler> CHATAI_NAME_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void register(Long aIUserId, String name, AbstractChatAIHandler chatAIHandler) {
|
||||
CHATAI_ID_MAP.put(aIUserId, chatAIHandler);
|
||||
CHATAI_NAME_MAP.put(name, chatAIHandler);
|
||||
}
|
||||
|
||||
public static AbstractChatAIHandler getChatAIHandlerById(List<Long> userIds) {
|
||||
if (CollectionUtils.isEmpty(userIds)) {
|
||||
return null;
|
||||
}
|
||||
for (Long userId : userIds) {
|
||||
AbstractChatAIHandler chatAIHandler = CHATAI_ID_MAP.get(userId);
|
||||
if (chatAIHandler != null) {
|
||||
return chatAIHandler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static AbstractChatAIHandler getChatAIHandlerByName(String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, AbstractChatAIHandler> entry : CHATAI_NAME_MAP.entrySet()) {
|
||||
if (text.contains("@"+entry.getKey())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package com.abin.mallchat.custom.chatai.handler;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.common.constant.RedisKey;
|
||||
import com.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.custom.chatai.properties.ChatGLM2Properties;
|
||||
import com.abin.mallchat.custom.chatai.utils.ChatGLM2Utils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.abin.mallchat.common.common.constant.RedisKey.USER_GLM2_TIME_LAST;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ChatGLM2Handler extends AbstractChatAIHandler {
|
||||
|
||||
private static final List<String> ERROR_MSG = Arrays.asList(
|
||||
"还摸鱼呢?你不下班我还要下班呢。。。。",
|
||||
"没给钱,矿工了。。。。",
|
||||
"服务器被你们玩儿坏了。。。。",
|
||||
"你们这群人,我都不想理你们了。。。。",
|
||||
"还艾特我呢?那是另外的价钱。。。。",
|
||||
"得加钱");
|
||||
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Autowired
|
||||
private ChatGLM2Properties glm2Properties;
|
||||
|
||||
@Override
|
||||
public Long getChatAIUserId() {
|
||||
return glm2Properties.getAIUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChatAIName() {
|
||||
if (StringUtils.isNotBlank(glm2Properties.getAIUserName())) {
|
||||
return glm2Properties.getAIUserName();
|
||||
}
|
||||
String name = userService.getUserInfo(glm2Properties.getAIUserId()).getName();
|
||||
glm2Properties.setAIUserName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doChat(Message message) {
|
||||
String content = message.getContent().replace("@" +glm2Properties.getAIUserName(), "").trim();
|
||||
Long uid = message.getFromUid();
|
||||
Long minute;
|
||||
String text;
|
||||
if ((minute = userMinutesLater(uid)) > 0) {
|
||||
text = "你太快了 " + minute + "分钟后重试";
|
||||
} else {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
response = ChatGLM2Utils
|
||||
.create()
|
||||
.url(glm2Properties.getUrl())
|
||||
.prompt(content)
|
||||
.send();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return getErrorText();
|
||||
}
|
||||
text = ChatGLM2Utils.parseText(response);
|
||||
if (StringUtils.isNotBlank(text)) {
|
||||
RedisUtils.set(RedisKey.getKey(USER_GLM2_TIME_LAST, uid), new Date(), glm2Properties.getMinute(), TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static String getErrorText() {
|
||||
int index = RANDOM.nextInt(ERROR_MSG.size());
|
||||
return ERROR_MSG.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户多少分钟后才能再次聊天
|
||||
*
|
||||
* @param uid
|
||||
* @return
|
||||
*/
|
||||
private Long userMinutesLater(Long uid) {
|
||||
// 获取用户最后聊天时间
|
||||
Date lastChatTime = RedisUtils.get(RedisKey.getKey(USER_GLM2_TIME_LAST, uid), Date.class);
|
||||
if (lastChatTime == null) {
|
||||
// 如果没有聊天记录,则可以立即聊天
|
||||
return 0L;
|
||||
}
|
||||
// 计算当前时间和上次聊天时间之间的时间差
|
||||
long now = System.currentTimeMillis();
|
||||
long lastChatTimeMillis = lastChatTime.getTime();
|
||||
long durationMillis = now - lastChatTimeMillis;
|
||||
long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis);
|
||||
// 计算剩余等待时间
|
||||
long remainingMinutes = glm2Properties.getMinute() - minutes;
|
||||
return remainingMinutes > 0 ? remainingMinutes : 0L;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean supports(Message message) {
|
||||
if (!glm2Properties.isUse()) {
|
||||
return false;
|
||||
}
|
||||
/* 前端传@信息后取消注释 */
|
||||
|
||||
// MessageExtra extra = message.getExtra();
|
||||
// if (extra == null) {
|
||||
// return false;
|
||||
// }
|
||||
// if (CollectionUtils.isEmpty(extra.getAtUidList())) {
|
||||
// return false;
|
||||
// }
|
||||
// if (!extra.getAtUidList().contains(ChatAIServiceImpl.AI_USER_ID)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (StringUtils.isBlank(message.getContent())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.contains(message.getContent(), "@" + glm2Properties.getAIUserName())
|
||||
&& StringUtils.isNotBlank(message.getContent().replace(glm2Properties.getAIUserName(), "").trim());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
package com.abin.mallchat.custom.chatai.handler;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.common.constant.RedisKey;
|
||||
import com.abin.mallchat.common.common.utils.DateUtils;
|
||||
import com.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.custom.chatai.properties.ChatGPTProperties;
|
||||
import com.abin.mallchat.custom.chatai.utils.ChatGPTUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class GPTChatAIHandler extends AbstractChatAIHandler {
|
||||
|
||||
@Autowired
|
||||
private ChatGPTProperties chatGPTProperties;
|
||||
|
||||
@Override
|
||||
public Long getChatAIUserId() {
|
||||
return chatGPTProperties.getAIUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChatAIName() {
|
||||
if (StringUtils.isNotBlank(chatGPTProperties.getAIUserName())) {
|
||||
return chatGPTProperties.getAIUserName();
|
||||
}
|
||||
String name = userService.getUserInfo(chatGPTProperties.getAIUserId()).getName();
|
||||
chatGPTProperties.setAIUserName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doChat(Message message) {
|
||||
String content = message.getContent().replace("@" +chatGPTProperties.getAIUserName(), "").trim();
|
||||
Long uid = message.getFromUid();
|
||||
Long chatNum;
|
||||
String text;
|
||||
if ((chatNum = userChatNumInrc(uid)) > chatGPTProperties.getLimit()) {
|
||||
text = "你今天已经和我聊了" + chatNum + "次了,我累了,明天再聊吧";
|
||||
} else {
|
||||
HttpResponse response = ChatGPTUtils.create(chatGPTProperties.getKey())
|
||||
.proxyUrl(chatGPTProperties.getProxyUrl())
|
||||
.model(chatGPTProperties.getModelName())
|
||||
.prompt(content)
|
||||
.send();
|
||||
text = ChatGPTUtils.parseText(response);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private Long userChatNumInrc(Long uid) {
|
||||
//todo:白名单
|
||||
return RedisUtils.inc(RedisKey.getKey(RedisKey.USER_CHAT_NUM, uid), DateUtils.getEndTimeByToday().intValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean supports(Message message) {
|
||||
if (!chatGPTProperties.isUse()) {
|
||||
return false;
|
||||
}
|
||||
/* 前端传@信息后取消注释 */
|
||||
|
||||
// MessageExtra extra = message.getExtra();
|
||||
// if (extra == null) {
|
||||
// return false;
|
||||
// }
|
||||
// if (CollectionUtils.isEmpty(extra.getAtUidList())) {
|
||||
// return false;
|
||||
// }
|
||||
// if (!extra.getAtUidList().contains(ChatAIServiceImpl.AI_USER_ID)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (StringUtils.isBlank(message.getContent())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.contains(message.getContent(), "@" + chatGPTProperties.getAIUserName())
|
||||
&& StringUtils.isNotBlank(message.getContent().replace(chatGPTProperties.getAIUserName(), "").trim());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.abin.mallchat.custom.chatai.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* ChatGLM2 配置文件
|
||||
*
|
||||
* @author zhaoyuhang
|
||||
* @date 2023/06/30
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "chatai.chatglm2")
|
||||
public class ChatGLM2Properties {
|
||||
|
||||
/**
|
||||
* 使用
|
||||
*/
|
||||
private boolean use;
|
||||
|
||||
/**
|
||||
* url
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 机器人 id
|
||||
*/
|
||||
private Long AIUserId;
|
||||
|
||||
/**
|
||||
* 机器人名称
|
||||
*/
|
||||
private String AIUserName;
|
||||
|
||||
/**
|
||||
* 每个用户每3分钟可以请求一次
|
||||
*/
|
||||
private Long minute = 3L;
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.abin.mallchat.custom.chatai.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "chatai.chatgpt")
|
||||
public class ChatGPTProperties {
|
||||
|
||||
/**
|
||||
* 是否使用openAI
|
||||
*/
|
||||
private boolean use;
|
||||
/**
|
||||
* 机器人 id
|
||||
*/
|
||||
private Long AIUserId;
|
||||
|
||||
/**
|
||||
* 机器人名称
|
||||
*/
|
||||
private String AIUserName;
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName = "text-davinci-003";
|
||||
/**
|
||||
* openAI key
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* 代理地址
|
||||
*/
|
||||
private String proxyUrl;
|
||||
|
||||
/**
|
||||
* 用户每天条数限制
|
||||
*/
|
||||
private Integer limit = 5;
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.abin.mallchat.custom.chatai.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
|
||||
public interface IChatAIService {
|
||||
|
||||
void chat(Message message);
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.abin.mallchat.custom.chatai.service.impl;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra;
|
||||
import com.abin.mallchat.custom.chatai.handler.AbstractChatAIHandler;
|
||||
import com.abin.mallchat.custom.chatai.handler.ChatAIHandlerFactory;
|
||||
import com.abin.mallchat.custom.chatai.service.IChatAIService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ChatAIServiceImpl implements IChatAIService {
|
||||
@Override
|
||||
public void chat(Message message) {
|
||||
MessageExtra extra = message.getExtra();
|
||||
if (extra == null) {
|
||||
return;
|
||||
}
|
||||
AbstractChatAIHandler chatAI = ChatAIHandlerFactory.getChatAIHandlerByName(message.getContent());
|
||||
// AbstractChatAIHandler chatAI = ChatAIHandlerFactory.getChatAIHandlerById(extra.getAtUidList());
|
||||
if (chatAI != null) {
|
||||
chatAI.chat(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
package com.abin.mallchat.custom.chatai.utils;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class ChatGLM2Utils {
|
||||
|
||||
|
||||
private final Map<String, String> headers;
|
||||
/**
|
||||
* 超时30秒
|
||||
*/
|
||||
private Integer timeout = 60 * 1000;
|
||||
|
||||
private String url;
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 历史
|
||||
*/
|
||||
private List<Object> history;
|
||||
|
||||
|
||||
public ChatGLM2Utils() {
|
||||
HashMap<String, String> _headers_ = new HashMap<>();
|
||||
_headers_.put("Content-Type", "application/json");
|
||||
this.headers = _headers_;
|
||||
}
|
||||
|
||||
public static ChatGLM2Utils create() {
|
||||
return new ChatGLM2Utils();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public ChatGLM2Utils url(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ChatGLM2Utils timeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatGLM2Utils prompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
return this;
|
||||
}
|
||||
public HttpResponse send() {
|
||||
JSONObject param = new JSONObject();
|
||||
param.set("prompt", prompt);
|
||||
log.info("headers >>> " + headers);
|
||||
log.info("param >>> " + param);
|
||||
return HttpUtil.createPost(url)
|
||||
.addHeaders(headers)
|
||||
.body(param.toString())
|
||||
.timeout(timeout)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static String parseText(String body) {
|
||||
log.info("body >>> " + body);
|
||||
JSONObject jsonObj = new JSONObject(body);
|
||||
if (200 != jsonObj.getInt("status")) {
|
||||
log.error("status >>> " + jsonObj.getInt("status"));
|
||||
return "闹脾气了,等会再试试吧~";
|
||||
}
|
||||
return jsonObj.getStr("response");
|
||||
}
|
||||
|
||||
public static String parseText(HttpResponse response) {
|
||||
return parseText(response.body());
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpResponse send = null;
|
||||
try {
|
||||
send = ChatGLM2Utils
|
||||
.create()
|
||||
.url("http://vastmiao.natapp1.cc")
|
||||
.timeout(60 * 1000)
|
||||
.prompt("Spring的启动流程是什么")
|
||||
.send();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("send = " + send);
|
||||
|
||||
System.out.println("parseText(send) = " + parseText(send));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.abin.mallchat.custom.openai.utils;
|
||||
package com.abin.mallchat.custom.chatai.utils;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
@ -12,7 +12,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class OpenAIUtils {
|
||||
public class ChatGPTUtils {
|
||||
|
||||
private static final String URL = "https://api.openai.com/v1/completions";
|
||||
|
||||
@ -56,7 +56,7 @@ public class OpenAIUtils {
|
||||
|
||||
private String proxyUrl;
|
||||
|
||||
public OpenAIUtils(String key) {
|
||||
public ChatGPTUtils(String key) {
|
||||
HashMap<String, String> _headers_ = new HashMap<>();
|
||||
_headers_.put("Content-Type", "application/json");
|
||||
if (StringUtils.isBlank(key)) {
|
||||
@ -66,8 +66,8 @@ public class OpenAIUtils {
|
||||
this.headers = _headers_;
|
||||
}
|
||||
|
||||
public static OpenAIUtils create(String key) {
|
||||
return new OpenAIUtils(key);
|
||||
public static ChatGPTUtils create(String key) {
|
||||
return new ChatGPTUtils(key);
|
||||
}
|
||||
|
||||
public static String parseText(HttpResponse response) {
|
||||
@ -87,47 +87,47 @@ public class OpenAIUtils {
|
||||
return choiceObj.getStr("text");
|
||||
}
|
||||
|
||||
public OpenAIUtils model(String model) {
|
||||
public ChatGPTUtils model(String model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils timeout(int timeout) {
|
||||
public ChatGPTUtils timeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils maxTokens(int maxTokens) {
|
||||
public ChatGPTUtils maxTokens(int maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils temperature(int temperature) {
|
||||
public ChatGPTUtils temperature(int temperature) {
|
||||
this.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils topP(int topP) {
|
||||
public ChatGPTUtils topP(int topP) {
|
||||
this.topP = topP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils frequencyPenalty(int frequencyPenalty) {
|
||||
public ChatGPTUtils frequencyPenalty(int frequencyPenalty) {
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils presencePenalty(int presencePenalty) {
|
||||
public ChatGPTUtils presencePenalty(int presencePenalty) {
|
||||
this.presencePenalty = presencePenalty;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils prompt(String prompt) {
|
||||
public ChatGPTUtils prompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpenAIUtils proxyUrl(String proxyUrl) {
|
||||
public ChatGPTUtils proxyUrl(String proxyUrl) {
|
||||
this.proxyUrl = proxyUrl;
|
||||
return this;
|
||||
}
|
||||
@ -149,7 +149,7 @@ public class OpenAIUtils {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpResponse send = OpenAIUtils.create("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")
|
||||
HttpResponse send = ChatGPTUtils.create("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")
|
||||
.timeout(30 * 1000)
|
||||
.prompt("Spring的启动流程是什么")
|
||||
.send();
|
||||
@ -5,9 +5,11 @@ import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.common.event.MessageSendEvent;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.custom.chatai.service.IChatAIService;
|
||||
import com.abin.mallchat.custom.user.service.WebSocketService;
|
||||
import com.abin.mallchat.custom.user.service.adapter.WSAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -27,6 +29,8 @@ public class MessageSendListener {
|
||||
private ChatService chatService;
|
||||
@Autowired
|
||||
private MessageDao messageDao;
|
||||
@Autowired
|
||||
private IChatAIService openAIService;
|
||||
|
||||
@Async
|
||||
@TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true)
|
||||
@ -36,4 +40,10 @@ public class MessageSendListener {
|
||||
webSocketService.sendToAllOnline(WSAdapter.buildMsgSend(msgResp), message.getFromUid());
|
||||
}
|
||||
|
||||
@TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true)
|
||||
public void handlerMsg(@NotNull MessageSendEvent event) {
|
||||
Message message = messageDao.getById(event.getMsgId());
|
||||
openAIService.chat(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
package com.abin.mallchat.custom.openai.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Getter
|
||||
public class OpenAIEvent extends ApplicationEvent {
|
||||
private Long msgId;
|
||||
|
||||
public OpenAIEvent(Object source, Long msgId) {
|
||||
super(source);
|
||||
this.msgId = msgId;
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package com.abin.mallchat.custom.openai.event.listener;
|
||||
|
||||
import com.abin.mallchat.common.chat.dao.MessageDao;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.custom.openai.event.OpenAIEvent;
|
||||
import com.abin.mallchat.custom.openai.service.IOpenAIService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
import static com.abin.mallchat.custom.openai.service.impl.OpenAIServiceImpl.MALL_CHAT_AI_NAME;
|
||||
|
||||
/**
|
||||
* 是否AI回复监听器
|
||||
*
|
||||
* @author zhaoyuhang
|
||||
* @date 2023/06/29
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OpenAIListener {
|
||||
@Autowired
|
||||
private IOpenAIService openAIService;
|
||||
@Autowired
|
||||
private MessageDao messageDao;
|
||||
|
||||
@TransactionalEventListener(classes = OpenAIEvent.class, fallbackExecution = true)
|
||||
public void notifyAllOnline(@NotNull OpenAIEvent event) {
|
||||
Message message = messageDao.getById(event.getMsgId());
|
||||
if (ATedAI(message)) {
|
||||
openAIService.chat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @了AI
|
||||
*/
|
||||
private boolean ATedAI(Message message) {
|
||||
/* 前端传@信息后取消注释 */
|
||||
|
||||
// MessageExtra extra = message.getExtra();
|
||||
// if (extra == null) {
|
||||
// return false;
|
||||
// }
|
||||
// if (CollectionUtils.isEmpty(extra.getAtUidList())) {
|
||||
// return false;
|
||||
// }
|
||||
// if (!extra.getAtUidList().contains(OpenAIServiceImpl.AI_USER_ID)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (StringUtils.isBlank(message.getContent())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.contains(message.getContent(), "@" + MALL_CHAT_AI_NAME)
|
||||
&& StringUtils.isNotBlank(message.getContent().replace(MALL_CHAT_AI_NAME, "").trim());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package com.abin.mallchat.custom.openai.service;
|
||||
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
|
||||
public interface IOpenAIService {
|
||||
|
||||
|
||||
void chat(ChatMessageReq chatMessageReq, Long uid);
|
||||
void chat(Message message);
|
||||
}
|
||||
@ -1,171 +0,0 @@
|
||||
package com.abin.mallchat.custom.openai.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.thread.NamedThreadFactory;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.abin.mallchat.common.chat.domain.entity.Message;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
||||
import com.abin.mallchat.common.common.constant.RedisKey;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.handler.GlobalUncaughtExceptionHandler;
|
||||
import com.abin.mallchat.common.common.utils.DateUtils;
|
||||
import com.abin.mallchat.common.common.utils.RedisUtils;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq;
|
||||
import com.abin.mallchat.custom.chat.domain.vo.request.msg.TextMsgReq;
|
||||
import com.abin.mallchat.custom.chat.service.ChatService;
|
||||
import com.abin.mallchat.custom.openai.enums.OpenAIModelEnums;
|
||||
import com.abin.mallchat.custom.openai.service.IOpenAIService;
|
||||
import com.abin.mallchat.custom.openai.utils.OpenAIUtils;
|
||||
import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp;
|
||||
import com.abin.mallchat.custom.user.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpenAIServiceImpl implements IOpenAIService, DisposableBean, InitializingBean {
|
||||
private static ExecutorService EXECUTOR;
|
||||
|
||||
@Value("${openai.use-openai:false}")
|
||||
private boolean USE_OPENAI;
|
||||
@Value("${openai.ai-user-id}")
|
||||
public Long AI_USER_ID;
|
||||
|
||||
@Value("${openai.model.name:text-davinci-003}")
|
||||
private String modelName;
|
||||
@Value("${openai.key}")
|
||||
private String key;
|
||||
@Value("${openai.proxy-url:}")
|
||||
private String proxyUrl;
|
||||
|
||||
@Value("${openai.limit:5}")
|
||||
private Integer limit;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Lazy
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
|
||||
public static String MALL_CHAT_AI_NAME;
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*
|
||||
* @param chatMessageReq 提示词
|
||||
* @param uid 用户id
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void chat(ChatMessageReq chatMessageReq, Long uid) {
|
||||
TextMsgReq body = BeanUtil.toBean(chatMessageReq.getBody(), TextMsgReq.class);
|
||||
String content = body.getContent().replace(MALL_CHAT_AI_NAME, "").trim();
|
||||
EXECUTOR.execute(() -> {
|
||||
Long chatNum;
|
||||
if ((chatNum = userChatNumInrc(uid)) > limit) {
|
||||
answerMsg("你今天已经和我聊了" + chatNum + "次了,我累了,明天再聊吧", chatMessageReq.getRoomId(), uid);
|
||||
} else {
|
||||
chat(content, chatMessageReq.getRoomId(), uid);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chat(Message message) {
|
||||
String content = message.getContent().replace(MALL_CHAT_AI_NAME, "").trim();
|
||||
Long roomId = message.getRoomId();
|
||||
Long uid = message.getFromUid();
|
||||
EXECUTOR.execute(() -> {
|
||||
Long chatNum;
|
||||
if ((chatNum = userChatNumInrc(uid)) > limit) {
|
||||
answerMsg("你今天已经和我聊了" + chatNum + "次了,我累了,明天再聊吧", roomId, uid);
|
||||
} else {
|
||||
chat(content, roomId, uid);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private Long userChatNumInrc(Long uid) {
|
||||
//todo:白名单
|
||||
return RedisUtils.inc(RedisKey.getKey(RedisKey.USER_CHAT_NUM, uid), DateUtils.getEndTimeByToday().intValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void chat(String content, Long roomId, Long uid) {
|
||||
HttpResponse response = OpenAIUtils.create(key)
|
||||
.proxyUrl(proxyUrl)
|
||||
.model(modelName)
|
||||
.prompt(content)
|
||||
.send();
|
||||
String text = OpenAIUtils.parseText(response);
|
||||
answerMsg(text, roomId, uid);
|
||||
}
|
||||
|
||||
private void answerMsg(String text, Long roomId, Long uid) {
|
||||
ChatMessageReq answerReq = new ChatMessageReq();
|
||||
answerReq.setRoomId(roomId);
|
||||
answerReq.setMsgType(MessageTypeEnum.TEXT.getType());
|
||||
UserInfoResp userInfo = userService.getUserInfo(uid);
|
||||
TextMsgReq textMsgReq = new TextMsgReq();
|
||||
textMsgReq.setContent("@" + userInfo.getName() + " " + text);
|
||||
textMsgReq.setAtUidList(Collections.singletonList(uid));
|
||||
answerReq.setBody(textMsgReq);
|
||||
chatService.sendMsg(answerReq, AI_USER_ID);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!USE_OPENAI) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotBlank(proxyUrl) && !HttpUtil.isHttp(proxyUrl) && !HttpUtil.isHttps(proxyUrl)) {
|
||||
throw new BusinessException("openai.proxy-url 配置错误");
|
||||
}
|
||||
OpenAIModelEnums modelEnum = OpenAIModelEnums.of(modelName);
|
||||
if (modelEnum == null) {
|
||||
throw new BusinessException("openai.model.name 配置错误");
|
||||
}
|
||||
Integer rpm = modelEnum.getRPM();
|
||||
EXECUTOR = new ThreadPoolExecutor(10, 10,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(rpm),
|
||||
new NamedThreadFactory("openAI-chat-gpt",
|
||||
null,
|
||||
false,
|
||||
new GlobalUncaughtExceptionHandler()),
|
||||
(r, executor) -> {
|
||||
throw new BusinessException("别问的太快了,我的脑子不够用了");
|
||||
});
|
||||
UserInfoResp userInfo = userService.getUserInfo(AI_USER_ID);
|
||||
if (userInfo == null) {
|
||||
throw new BusinessException("openai.ai-user-id 配置错误");
|
||||
}
|
||||
MALL_CHAT_AI_NAME = userInfo.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
EXECUTOR.shutdown();
|
||||
if (!EXECUTOR.awaitTermination(30, TimeUnit.SECONDS)) { //最多等30秒,处理不完就拉倒
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("Timed out while waiting for executor [{}] to terminate", EXECUTOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user