ChatGPT-AI问答助手项目笔记
github仓库:ChatGPT-AI问答助手
总体工作流程:
项目定时通过发送 get 请求得到知识星球中的 “等我回答” 模块中的问题内容—> 通过调用 openAI 的 API 接口实现对问题的回答—> 通过发送 post 请求将回答的内容返回给用户
1.项目使用 DDD 架构,最后的项目结构目录为:
其中每个模块负责自己模块下的事务,一个包下就是一个类似mvc的架构,各个模块下通过pom文件相互引用。
其中:
- chatbot-api-interfaces 负责调用接口类的调试以及项目的启动
- chatbot-api-domain 负责整个项目中需要的一些实体类
- chatbot-api-application 负责项目中应用服务的实现.
- chatbot-api-infractructure 负责做基础的工作,例如对接数据库等,但是这个项目并没有使用到,所以就只是初始状态
1.编写知识星球接口类相关代码
我们通过想知识星球中的 “等我回答” 功能可以得到对应的 http 接口信息
将得到的 json 数据转化为实体对象,就可以得到我们的问题内容,推荐一个 json 转 Java 实体的网站:
在线JSON转C#实体类,JSON转Java实体类 (sojson.com)
之后我们就可以直接编(复)写(制)代(粘)码(贴)了。qwq
注意:我们写的都是我们需要运用的对象信息,所有我们需要将其放到 domain 模块中.
这里就不粘贴代码了,实在有点长. qwq
里面的 res,req封装的是回复和请求信息, vo 就是普通类,aggregates 得到问题的聚合对象.
之后我们就可以通过发送 get 请求查询到所有的问题了,这里用了 apache 的 http 包:
public interface IZsxqApi {
UnAnsweredquestionAggregates queryUnAnsweredQuestion(String groupId, String cookie) throws IOException;
}
@Service
@Slf4j
public class ZsxqApi implements IZsxqApi {
private Logger logger= LoggerFactory.getLogger(ZsxqApi.class);
/**
* 查找未回答问题的对象数据
* @param groupId 用户ID
* @param cookie cookie数据
* @return 返回未回答问题的聚合数据
* @throws IOException
*/
@Override
public UnAnsweredquestionAggregates queryUnAnsweredQuestion(String groupId, String cookie) throws IOException {
CloseableHttpClient httpClient= HttpClientBuilder.create().build();
HttpGet get=new HttpGet("https://api.zsxq.com/v2/groups/"+groupId+"/topics?scope=unanswered_questions&count=20");
get.addHeader("cookie",cookie);
get.addHeader("Content-Type","application/json; charset=UTF-8");
CloseableHttpResponse response = httpClient.execute(get);
if(response.getStatusLine().getStatusCode()== HttpStatus.HTTP_OK){
String jsonStr = EntityUtils.toString(response.getEntity());
logger.info("拉取提问数据:用户groupId:{},拉取jsonStr:{}",groupId,jsonStr);
//将json数据转化为聚合对象数据并返回
return JSON.parseObject(jsonStr, UnAnsweredquestionAggregates.class);
}
else{
throw new RuntimeException("queryUnAnsweredQuestionsTopicId Err code is "+response.getStatusLine().getStatusCode());
}
}
2.编写ChatGPT相关代码
之后我们通过调用 openAI 的 API 接口实现对问题的回答.
这里推荐一个 github 仓库,他有免费的 Key(只支持 ChatGPT-3.5),我的key就是从上面获取的:
编写的代码如下:
/**
* ChatGPT openAI 接口
*/
public interface IOpenAI {
//ChatGPT 问询接口
String doQuery(String question,String openAIkey) throws IOException;
}
@Service
@Slf4j
public class OpenAI implements IOpenAI {
@Override
public String doQuery(String question,String openaiKey) throws IOException {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost request = new HttpPost("https://api.chatanywhere.com.cn/v1/chat/completions");
// 设置请求头
request.addHeader("Content-Type", "application/json");
request.addHeader("Authorization", "Bearer "+openaiKey);
// 设置请求体
ChatGPTReq chatGPTReq=new ChatGPTReq("gpt-3.5-turbo",new Messages[]{new Messages("user",question)},0.7);
String parmJson = JSON.toJSONString(chatGPTReq);
System.out.println(parmJson);
StringEntity stringEntity=new StringEntity(parmJson, ContentType.create("text/json","UTF-8"));
request.setEntity(stringEntity);
HttpResponse response = httpClient.execute(request);
int answerCode = response.getStatusLine().getStatusCode();
if(answerCode == HttpStatus.HTTP_OK){
String jsonStr = EntityUtils.toString(response.getEntity());
AIAnswer aiAnswer = JSON.parseObject(jsonStr, AIAnswer.class);
//因为它的回答是一段一段的,所以要将所有的回答结果都拼接在一起,下面就是拼接操作
StringBuilder answer=new StringBuilder();
for (Choices choice : aiAnswer.getChoices()) {
answer.append(choice.getMessage().getContent());
}
return answer.toString();
}
else{
log.error("Connection error code is "+ answerCode);
return "sorry!服务连接超时,请重新提问。";
}
}
}
调用这个免费 key 的文档:接口文档分享
等到问题后,我们就将这个问题设置到请求 API 的请求体中
等到回答后,我们再发送 post 请求将回复内容发送到用户端即可。
注:这里依旧需要通过自己回答一个问题后,查看具体的 json 数据,再通过:
在线JSON转C#实体类,JSON转Java实体类 (sojson.com) 这个网站得到 json 实体
3.整合两个接口
在 application 模块中,我们来进行整合两个模块的代码编写:
代码如下:
@Slf4j
public class ChatbotSchedule implements Runnable{
private String groupName;
private String groupId;
private String cookie;
private String key;
private IZsxqApi iZsxqApi;
private IOpenAI iOpenAI;
public ChatbotSchedule(String groupName, String groupId, String cookie, String key, IZsxqApi iZsxqApi, IOpenAI iOpenAI) {
this.groupName = groupName;
this.groupId = groupId;
this.cookie = cookie;
this.key = key;
this.iZsxqApi = iZsxqApi;
this.iOpenAI = iOpenAI;
}
//cron在线生成工具:https://cron.qqe2.com/
@Override
public void run(){
try {
//获取当前小时,在指定时间停止回复
GregorianCalendar calendar=new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
//夜晚的时候停止回复
if(hour>22 || hour<6){
log.info("AI下班了...");
return;
}
//使曲线平滑,防止风控
boolean flag= new Random().nextBoolean();
if(flag == false){
log.info("随机打烊中...");
return;
}
//1.问题检索
UnAnsweredquestionAggregates unAnsweredquestionAggregates = iZsxqApi.queryUnAnsweredQuestion(groupId, cookie);
List<Topics> topics = unAnsweredquestionAggregates.getRespData().getTopics();
if(null==topics || topics.isEmpty() ){
log.info("本次查询为查询到待回答问题");
return;
}
//2.获取问题
Topics topic = topics.get(0);
String question= topic.getQuestion().getText().trim();
String topicId=topic.getTopic_id();
//3.调用openAI回答问题
String answer = iOpenAI.doQuery(question,key);
//4.回显给用户
boolean status = iZsxqApi.answer(groupId, cookie, topicId, answer, false);
log.info("问题编号:{},问题内容:{},回复内容:{},状态:{}",topicId,question,answer,status);
}
catch (Exception e){
log.error("自动回答问题异常:"+e);
}
}
}
我们运用 Spring 中的定时任务,先实现了 Runable 接口,并重写 run 方法(run方法中就是我们编写的定时任务的逻辑).
定时任务的逻辑:
定时通过 get 请求获取问题 —> 调用openAI 接口对问题内容的回复 -->将回复内容通过 post 请求发送给用户
之后,我们编写定时任务注册类:
/**
* 读取配置信息,实现多任务配置
*/
@EnableScheduling
@Configuration
@Slf4j
public class TaskAutoRegisteration implements EnvironmentAware, SchedulingConfigurer {
@Autowired
private Environment environment;
@Autowired
private IZsxqApi zsxqApi;
@Autowired
private IOpenAI openAI;
//任务组
private Map<String,Map<String, Object> > taskMap=new HashMap<>();
/**
* 获取环境配置,以便注册任务
* @param environment
*/
@Override
public void setEnvironment(Environment environment) {
String prefix="chatbot.";
String lanchlist = environment.getProperty(prefix+"lanchlist");
System.out.println(lanchlist);
if(StringUtils.isEmpty(lanchlist)) return;
String[] takeKeySet=lanchlist.split(",");
for (String taskKey : takeKeySet) {
String groupName=environment.getProperty(prefix+taskKey+".groupName");
String groupId = environment.getProperty(prefix + taskKey+".groupId");
String cookie = environment.getProperty(prefix + taskKey+".cookie");
String key = environment.getProperty(prefix + taskKey+".key");
String cronExpression = environment.getProperty(prefix + taskKey+".cronExpression");
Map<String,Object> task=new HashMap<>();
task.put("groupId",groupId);
task.put("cookie",cookie);
task.put("key",key);
task.put("groupName",groupName);
task.put("cronExpression",cronExpression);
taskMap.put(taskKey,task);
}
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Set<String> keySet = taskMap.keySet();
for (String taskKey : keySet) {
Map<String, Object> task = taskMap.get(taskKey);
String groupName=task.get("groupName").toString();
String groupId = task.get("groupId").toString();
String cookie = task.get("cookie").toString();
String key = task.get("key").toString();
String cronExpression = task.get("cronExpression").toString();
log.info("星球名称: {},用户id: {},cornExpression: {} 启动成功!",groupName,groupId,cronExpression);
//添加任务,任务必须实行Runable接口,并重写run方法
taskRegistrar.addCronTask(new ChatbotSchedule(groupName,groupId, cookie,key,zsxqApi,openAI),cronExpression);
}
}
}
其中,我们通过实现 Spring 中的 EnvironmentAware 接口,来实现动态读取配置文件,使用 SchedulingConfigurer 接口来实现定时任务的注册.
EnvironmentAware 接口要重写 setEnvironment 方法,我们可以通过注入 Environment 类来读取配置信息,这样我们就可以在配置信息中配置多个知识星球的接口服务的配置信息。
SchedulingConfigurer 接口需要重写 configureTasks 方法,我们可以通过编写这个方法配置定时任务的注册,我们通过得到的配置信息来实现每个定时任务的注册。
其中的 cronExpression 就是 cron 表达式,可以通过一些在线网站来在线生成需要的 cron 表达式:
例如:在线Cron表达式生成器
4.Docker容器部署
通过docker 容器,我们将项目部署到我们的本地主机上。
IDEA 有与 docker 的整合,我们可以通过现在本地 windows 上安装 docker 容器,之后在 IDEA 中的设置连接本地 docker 即可.
之后,我们编写 dockerfile:
#基本镜像
FROM openjdk:17
#作者
MAINTAINER zc
#配置
ENV PARAMS=""
#时区
ENV TZ=PRC
RUN ln -snf /usrr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#添加应用
ADD /chatbot-api-interfaces/target/chatbot-api-interfaces-1.0-SNAPSHOT.jar /chatbot-api.jar
#执行镜像
#ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /chatbot-api.jar $PARRAMS"]
CMD java -jar chatbot-api.jar
启动容器即可.
完美撒花 💮