一、简介
本文分享一个我前几个月实现的一个智能聊天系统小项目,包含了java后端,微信小程序端,web页面端三个子工程。
代码已经全部开源,地址放在了文末。
最近一年,的火爆程度,已经不需要我再多说了,但是依旧有很多人想用却用不上,原因大家也都很清楚,因为需要科学上网才可以访问,并且注册也需要绑定海外的银行卡。那么这就给了很多人赚钱的机会,于是很多套壳类网站层出不穷,只需要简单写一下代码,部署到海外的服务器上,就可以进行访问了,并且可以实现和官网一样的效果。然后再通过充值会员,或者购买次数,来赚钱。
当然,我这个项目也是很久之前就已经实现了,但是并不是为了赚钱,当时的想法是,第一是为了练习编程技术,作为程序员,遇到新鲜的事物,总是会想着尝试一番。第二是为了方便自己,因为自己平时学习或者办公中,也会经常使用,但是公司网络又不允许我使用官网,那么不如自己来套壳一个,然后再提供前端页面进行交互,不就可以了吗。
后来,因为网页版对于手机使用非常不方便,于是就又开发出一个微信小程序,可以随时随地使用了,对我的工作和学习帮助还是挺大的,遇到问题就可以直接询问了,而且我预设置了多种角色,精心调试了,来实现特定场景或特定领域的问答机器人。
现在,我决定将这些所有的东西全部开源,毫无保留,大家可以使用代码进行学习或者部署使用,微信小程序端就不建议大家发布了,现在微信是不允许对接的,大概率会审核不通过。我是因为发布的早,并且没有做过宣传,只是自己和身边人使用,访问量非常小。
二、效果图展示
先来看下效果图吧,这样才能更加直观的展示。vue实现的网页端和微信小程序端,整体功能是一样的,只是布局有一点小的差异。
网页端
可以根据类别进行划分,每个类别下有多种角色
聊天页面,类似微信聊天一样,左侧是联系人列表,右边是对话框,下方是输入框。对话框中,自己的输入在右边显示,的输出在左边显示。支持格式转换。
微信小程序端
类似的布局,同样的功能,支持积分扣减
三、后端项目介绍
后端是使用 boot进行搭建的, 同时会使用到mysql, , data jpa, redis等组件。
为什么使用 boot,因为自己是一名java程序员,当然对和go也略知一二,只是对性能要求没有那么高,并且自己根据熟悉,所以选择了java语言,而 Boot是一个开源的Java开发框架,它简化了Java应用程序的开发和部署过程。相比于传统的Java开发, Boot具有以下优点:
1.简化配置: Boot提供了自动配置的功能,可以根据项目的依赖自动配置各种组件,无需手动编写大量的XML配置文件,大大提高了开发效率。
2.内嵌服务器: Boot内置了常用的服务器,如、Jetty等,可以直接将应用程序打包为可执行的Jar包或War包,简化了部署和运行的过程。
3.良好的扩展性: Boot基于框架,可以与其他生态系统的组件无缝集成,如 Data、 等,拓展了开发的能力。
搭建 Boot项目环境非常简单,只需要几个步骤:
1.在IDE中创建一个新的 Boot项目,可以选择使用 或者直接创建一个空的Maven或项目。
2.在项目的依赖管理文件(如pom.xml)中,添加 Boot的启动器依赖,例如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.编写 Boot应用程序的入口类,例如:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.在入口类中,可以添加一些配置和组件,例如定义数据源、配置日志等。
至此,一个简单的 Boot项目就搭建完成了。接下来,我们将介绍如何集成接口实现功能。
为了实现功能,我们需要先了解接口的相关信息。是一个人工智能技术公司,提供了各种自然语言处理的API,其中包括了功能。是一个强大的对话生成模型,可以根据输入的对话历史生成下一句话。
要使用接口,我们需要进行以下步骤:
1.注册账号并获取API密钥:在官网注册一个账号,并获取API密钥,用于进行API请求。
2.集成接口到 Boot项目:添加的API依赖到项目中,我这里使用的是另外一个开源项目-java,例如:
<dependency>
<groupId>com.unfbx</groupId>
<artifactId>chatgpt-java</artifactId>
<version>1.0.12</version>
</dependency>
3.使用。
是一种在客户端和服务器之间实现双向通信的协议,通过该协议可以在服务端主动向客户端推送数据,实现实时的双向通信。与传统的HTTP请求-响应模式不同,的连接一旦建立,客户端和服务器可以持久性地保持连接,双方可以随时发送和接收消息,达到实时通信的效果。这种实时通信对于聊天应用、实时数据展示和协同编辑等场景非常有用。
(1)添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
(2)建立连接,这里需要参数用户id
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) {
log.info("websocket open,uid:{}", uid);
this.session = session;
this.uid = uid;
webSocketSet.add(this);
SESSIONS.add(session);
if (chatWebSocketMap.containsKey(uid)) {
chatWebSocketMap.remove(uid);
chatWebSocketMap.put(uid, this);
} else {
chatWebSocketMap.put(uid, this);
addOnlineCount();
}
log.info("websocket onOpen, userId:{}, online count:{}", this.uid, getOnlineCount());
}
(3)接收前端消息,并调用。
因为只能有定义一个字符串进行前后端交互,所以如果我们需要传递多个参数的话,需要将其转换为json字符串传递进来,并在接受后进行解析。如下所示,我们需要将传递进来,代表是和哪一个角色进行对话,这个在后面会用到,列表,里面存放了历史对话记录,这里传递进来是为了保持上下文进行通信,创建一个,并把传入进去,最后 就是与交互的结构体。
@OnMessage
public void onMessage(String message) {
log.info("onMessage, userId:{} ", this.uid);
JSONObject jsonObject = JSONUtil.parseObj(message);
Integer roleId = jsonObject.getInt("roleId");
String messageString = jsonObject.getStr("message");
List<Message> messages = new ArrayList<>();
messages = JSONUtil.toList(messageString, Message.class);
//接受参数
OpenAIWebSocketEventSourceListener eventSourceListener = new OpenAIWebSocketEventSourceListener(this.session);
ChatCompletion chatCompletion = buildChatCompletion(roleId, messages);
openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
}
(4)组装报文。
我们可以将每一种角色的,使用到的模型,最大token等参数,保存到数据库中,根据进行获取,然后组装成。其中,结构体中,包含了role和,role分为三种,分别是,user,。其中是系统指定的,里面可以存放你的,用来告诉,它现在要扮演什么角色,要怎么输出内容,都可以在指令中进行指定。user为用户输入的文本。为的回答的文本。model中可以指定自己使用的模型,可以使用gpt-3.5-turbo,gpt-4-32k等,根据自己实际情况进行选择。
private ChatCompletion buildChatCompletion(int id, List<Message> messages) {
ChatCompletion chatCompletion;
Role role = roleService.getRoleById(id);
if (role != null) {
Message roleMessage = Message.builder().content(role.getRoleMessage())
.role(Message.Role.SYSTEM).build();
messages.add(0, roleMessage);
chatCompletion = ChatCompletion.builder()
.temperature(role.getTemperature())
.model(role.getModel())
.maxTokens(role.getMaxTokens())
.topP(role.getTopP())
.presencePenalty(role.getPresencePenalty())
.frequencyPenalty(role.getFrequencyPenalty())
.messages(messages)
.stream(true)
.user(this.uid)
.build();
} else {
chatCompletion = ChatCompletion.builder()
.messages(messages)
.stream(true)
.user(this.uid)
.build();
}
return chatCompletion;
}
(5)发送请求到
public void streamChatCompletion(ChatCompletion chatCompletion, EventSourceListener eventSourceListener) {
if (Objects.isNull(eventSourceListener)) {
log.error("参数异常:EventSourceListener不能为空,可以参考:com.unfbx.chatgpt.sse.ConsoleEventSourceListener");
throw new BaseException(CommonError.PARAM_ERROR);
} else {
if (!chatCompletion.isStream()) {
chatCompletion.setStream(true);
}
try {
EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
ObjectMapper mapper = new ObjectMapper();
String requestBody = mapper.writeValueAsString(chatCompletion);
Request request = (new Request.Builder()).url(this.apiHost + "v1/chat/completions").post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)).build();
factory.newEventSource(request, eventSourceListener);
} catch (JsonProcessingException var8) {
log.error("请求参数解析异常:{}", var8);
var8.printStackTrace();
} catch (Exception var9) {
log.error("请求参数解析异常:{}", var9);
var9.printStackTrace();
}
}
}
上面代码中,通过创建了一个.对象,然后创建了一个对象,用于将数据转换为JSON格式。使用将对象转换为JSON字符串,存储在变量中,使用将对象转换为JSON字符串,存储在变量中,最后通过已创建的.对象调用方法,传入创建的对象和参数。这样就创建了一个对象,并开始监听事件。简单来说,这段代码的作用是创建一个对象,并发送一个POST请求。
现在有两项技术需要解释一下,分别是和。