我们在前几期学习了利用socket进行前后端的交互,但那只是基础性知识,这次,通过参考讯飞星火的java参考文档,再结合之前所学的socket服务,成功实现了通过后端将AI的调用实现在了自己的APP中。
本次的学习内容
1.真机的调用
2.讯飞星火账号appid的申请与调用
3.前后端的交互
4.服务器与AI的结合
1.真机的调用
打开真机的开发者模式,并用USB线将电脑与手机连接起来,并在手机的选中文件传输,让你的手机打开流量并开启热点,并保证你的电脑连接到这个热点,接下来,打开你电脑的命令提示窗口,输入以下命令: 获得IPv4地址,这个地址就是你的手机ip地址,记录下来后面要用到,这样你的手机已经可以连接到你的电脑后端了。
2.申请讯飞星火的账号
这个没什么好说的在讯飞星火的开发平台中申请一个账号, 并将这三个key记录下来,这是连接AI的必要条件,之后下载讯飞星火的Java示例代码,之后我们的服务器要在它的示例代码上进行修改:
当然,讯飞星火的AI也可以直接在app前端中进行调用,但目前版本适配性过低,而且限定的Java与SDK的版本,对版本不统一的使用者来说十分不友好,所以我还是建议通过后端去调用AI,避免前端线程的混乱。
3.前后端的交互
这里的代码与Android studio进阶开发(三)–socket通信服务的使用的代码几乎是一致的,只是把其中的IP地址转换为真机的地址 这里直接放代码: Android: 1.创建dateuitl,获取当前时间
package com.example.newsoket;
import android.annotation.SuppressLint;
import java.text.SimpleDateFormat;
import java.util.Date;
@SuppressLint("SimpleDateFormat")
public class DateUtil {
// 获取当前的日期时间
public static String getNowDateTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(new Date());
}
// 获取当前的时间
public static String getNowTime() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
return sdf.format(new Date());
}
// 获取当前的分钟
public static String getNowMinute() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
return sdf.format(new Date());
}
// 获取当前的时间(精确到毫秒)
public static String getNowTimeDetail() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
return sdf.format(new Date());
}
// 将长整型的时间数值格式化为日期时间字符串
public static String formatDate(long time) {
Date date = new Date(time);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
2.创建NetConst ,初始化端口和IP地址
package com.example.newsoket;
public class NetConst {
// HTTP地址的前缀
public final static String HTTP_PREFIX = "http://192.168.1.7:8080/HttpServer/";
// WebSocket服务的前缀
public final static String WEBSOCKET_PREFIX = "ws://192.168.1.7:8080/HttpServer/";
//public final static String BASE_IP = "192.168.1.7"; // 基础Socket服务的ip
public final static String BASE_IP = "192.168.43.9"; // 基础Socket服务的ip(这里要改为前面获取的IPv4的地址)
public final static int BASE_PORT = 9010; // 基础Socket服务的端口
public final static String CHAT_IP = "192.168.1.7"; // 聊天Socket服务的ip
public final static int CHAT_PORT = 9011; // 聊天Socket服务的端口
}
这里一定要改,否则连接不上后端服务器
3.创建SocketUtil 与socket连接进行判断
package com.example.newsoket;
import android.app.Activity;
import android.widget.Toast;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import io.socket.client.Socket;
public class SocketUtil {
// 把对象数据转换为json串,然后发给Socket服务器
public static void emit(Socket socket, String event, Object obj) {
try {
JSONObject json = new JSONObject(new Gson().toJson(obj));
socket.emit(event, json);
} catch (Exception e) {
e.printStackTrace();
}
}
// 判断Socket能否连通
public static void checkSocketAvailable(Activity act, String host, int port) {
new Thread(() -> {
try (java.net.Socket socket = new java.net.Socket()) {
SocketAddress address = new InetSocketAddress(host, port);
socket.connect(address, 1500);
} catch (Exception e) {
e.printStackTrace();
act.runOnUiThread(() -> {
Toast.makeText(act, "无法连接Socket服务器", Toast.LENGTH_SHORT).show();
});
}
}).start();
}
}
4.创建sockettake.activity进行交互 xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp" >
<EditText
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/editext_selector"<!可有可无,可以改为自己想要的>
android:hint="请输入聊天内容"
android:textColor="@color/black"
android:textSize="17sp" />
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送文本消息"
android:textColor="@color/black"
android:textSize="17sp" />
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
java:
package com.example.newsoket;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.newsoket.NetConst;
import com.example.newsoket.DateUtil;
import com.example.newsoket.SocketUtil;
import java.net.URISyntaxException;
import io.socket.client.IO;
import io.socket.client.Socket;
import java.net.URISyntaxException;
public class Sockettext extends AppCompatActivity {
private static final String TAG = "SocketioTextActivity";
private EditText et_input; // 声明一个编辑框对象
private TextView tv_response; // 声明一个文本视图对象
private Socket mSocket; // 声明一个套接字对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sockettext);
et_input = findViewById(R.id.et_input);
tv_response = findViewById(R.id.tv_response);
findViewById(R.id.btn_send).setOnClickListener(v -> {
String content = et_input.getText().toString();
if (TextUtils.isEmpty(content)) {
Toast.makeText(this, "请输入聊天消息", Toast.LENGTH_SHORT).show();
return;
}
mSocket.emit("send_text", content); // 往Socket服务器发送文本消息
});
initSocket(); // 初始化套接字
}
// 初始化套接字
private void initSocket() {
// 检查能否连上Socket服务器
SocketUtil.checkSocketAvailable(this, NetConst.BASE_IP, NetConst.BASE_PORT);
try {
String uri = String.format("http://%s:%d/", NetConst.BASE_IP, NetConst.BASE_PORT);
mSocket = IO.socket(uri); // 创建指定地址和端口的套接字实例
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
mSocket.connect(); // 建立Socket连接
// 等待接收传来的文本消息
mSocket.on("receive_text", (args) -> {
String desc = String.format("%s 收到服务端消息:%s",
DateUtil.getNowTime(), (String) args[0]);
runOnUiThread(() -> tv_response.setText(desc));
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mSocket.off("receive_text"); // 取消接收传来的文本消息
if (mSocket.connected()) { // 已经连上Socket服务器
mSocket.disconnect(); // 断开Socket连接
}
mSocket.close(); // 关闭Socket连接
}
}
后端idea的代码:
package com.socketio.server;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
public class SocketServer {
public static void main(String[] args) {
Configuration config = new Configuration();
// 如果调用了setHostname方法,就只能通过主机名访问,不能通过IP访问
//config.setHostname("localhost");
config.setPort(9010); // 设置监听端口
final SocketIOServer server = new SocketIOServer(config);
// 添加连接连通的监听事件
server.addConnectListener(client -> {
System.out.println(client.getSessionId().toString()+"已连接");
});
// 添加连接断开的监听事件
server.addDisconnectListener(client -> {
System.out.println(client.getSessionId().toString()+"已断开");
});
// 添加文本发送的事件监听器
server.addEventListener("send_text", String.class, (client, message, ackSender) -> {
System.out.println(client.getSessionId().toString()+"发送文本消息:"+message);
client.sendEvent("receive_text", "我已收到,马上做出回应。");
});
// 添加图像发送的事件监听器
server.addEventListener("send_image", JSONObject.class, (client, json, ackSender) -> {
String desc = String.format("%s,序号为%d", json.getString("name"), json.getIntValue("seq"));
System.out.println(client.getSessionId().toString()+"发送图片消息:"+desc);
client.sendEvent("receive_image", json);
});
server.start(); // 启动Socket服务
}
}
我们先来看下之前没有放出的示例图: 一定要先运行服务端的代码,再打开APP调试
这样前后端就有了响应。
4.服务器与AI的结合
还记得我们的标题吗,没错是我们与AI的互动,也就说,我们在对话框中输出文字并发送之后,给予我们回复的不在是固定的文字,而是AI根据我们的问题进行回复,实现基本的人机交互。 (1)解压在讯飞星火下载的压缩包
用idea打开文件夹下的讯飞大模型 将这部分填好,试着运行一下:
在下方中的“我”后面输入问题,看看是否有“大模型”进行回复
我们先来看一下两段代码
socket通信:
package com.socketio.server;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
public class SocketServer {
public static void main(String[] args) {
Configuration config = new Configuration();
// 如果调用了setHostname方法,就只能通过主机名访问,不能通过IP访问
//config.setHostname("localhost");
config.setPort(9010); // 设置监听端口
final SocketIOServer server = new SocketIOServer(config);
// 添加连接连通的监听事件
server.addConnectListener(client -> {
System.out.println(client.getSessionId().toString()+"已连接");
});
// 添加连接断开的监听事件
server.addDisconnectListener(client -> {
System.out.println(client.getSessionId().toString()+"已断开");
});
// 添加文本发送的事件监听器
server.addEventListener("send_text", String.class, (client, message, ackSender) -> {
System.out.println(client.getSessionId().toString()+"发送文本消息:"+message);
client.sendEvent("receive_text", "我已收到,马上做出回应。");
});
// 添加图像发送的事件监听器
server.addEventListener("send_image", JSONObject.class, (client, json, ackSender) -> {
String desc = String.format("%s,序号为%d", json.getString("name"), json.getIntValue("seq"));
System.out.println(client.getSessionId().toString()+"发送图片消息:"+desc);
client.sendEvent("receive_image", json);
});
server.start(); // 启动Socket服务
}
}
AI大模型(BigModelNew类 )
注意:是src底下的类,target底下的代码受到保护,无法编译
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOServer;
import com.google.gson.Gson;
import okhttp3.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
public class BigModelNew extends WebSocketListener {
// 各版本的hostUrl及其对应的domian参数,具体可以参考接口文档 https://www.xfyun.cn/doc/spark/Web.html
// Spark Lite https://spark-api.xf-yun.com/v1.1/chat domain参数为lite
// Spark Pro https://spark-api.xf-yun.com/v3.1/chat domain参数为generalv3
// Spark Pro-128K https://spark-api.xf-yun.com/chat/pro-128k domain参数为pro-128k
// Spark Max https://spark-api.xf-yun.com/v3.5/chat domain参数为generalv3.5
// Spark Max-32K https://spark-api.xf-yun.com/chat/max-32k domain参数为max-32k
// Spark4.0 Ultra https://spark-api.xf-yun.com/v4.0/chat domain参数为4.0Ultra
public static final String hostUrl = "https://spark-api.xf-yun.com/v3.1/chat";
public static final String domain = "generalv3";
public static final String appid = "你的appid";
public static final String apiSecret = "你的apiSecret";
public static final String apiKey = "你的apiKey";
public static List<RoleContent> historyList=new ArrayList<>(); // 对话历史存储集合
public static String totalAnswer=""; // 大模型的答案汇总
// 环境治理的重要性 环保 人口老龄化 我爱我的祖国
public static String NewQuestion = "";
public static final Gson gson = new Gson();
// 个性化参数
private String userId;
private Boolean wsCloseFlag;
private static Boolean totalFlag=true; // 控制提示用户是否输入
// 构造函数
public BigModelNew(String userId, Boolean wsCloseFlag) {
this.userId = userId;
this.wsCloseFlag = wsCloseFlag;
}
// 主函数
public static void main(String[] args) throws Exception {
while (true){
if(totalFlag){
Scanner scanner=new Scanner(System.in);
System.out.print("我:");
totalFlag=false;
NewQuestion=scanner.nextLine();
// 构建鉴权url
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
OkHttpClient client = new OkHttpClient.Builder().build();
String url = authUrl.toString().replace("https://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
for (int i = 0; i < 1; i++) {
totalAnswer="";
WebSocket webSocket = client.newWebSocket(request, new BigModelNew(i + "",
false));
}
}else{
Thread.sleep(200);
}
}
}
public static boolean canAddHistory(){ // 由于历史记录最大上线1.2W左右,需要判断是能能加入历史
int history_length=0;
for(RoleContent temp:historyList){
history_length=history_length+temp.content.length();
}
if(history_length>12000){
historyList.remove(0);
historyList.remove(1);
historyList.remove(2);
historyList.remove(3);
historyList.remove(4);
return false;
}else{
return true;
}
}
// 线程来发送音频与参数
class MyThread extends Thread {
private WebSocket webSocket;
public MyThread(WebSocket webSocket) {
this.webSocket = webSocket;
}
public void run() {
try {
JSONObject requestJson=new JSONObject();
JSONObject header=new JSONObject(); // header参数
header.put("app_id",appid);
header.put("uid",UUID.randomUUID().toString().substring(0, 10));
JSONObject parameter=new JSONObject(); // parameter参数
JSONObject chat=new JSONObject();
chat.put("domain",domain);
chat.put("temperature",0.5);
chat.put("max_tokens",4096);
parameter.put("chat",chat);
JSONObject payload=new JSONObject(); // payload参数
JSONObject message=new JSONObject();
JSONArray text=new JSONArray();
// 历史问题获取
if(historyList.size()>0){
for(RoleContent tempRoleContent:historyList){
text.add(JSON.toJSON(tempRoleContent));
}
}
// 最新问题
RoleContent roleContent=new RoleContent();
roleContent.role="user";
roleContent.content=NewQuestion;
text.add(JSON.toJSON(roleContent));
historyList.add(roleContent);
message.put("text",text);
payload.put("message",message);
requestJson.put("header",header);
requestJson.put("parameter",parameter);
requestJson.put("payload",payload);
// System.err.println(requestJson); // 可以打印看每次的传参明细
webSocket.send(requestJson.toString());
// 等待服务端返回完毕后关闭
while (true) {
// System.err.println(wsCloseFlag + "—");
Thread.sleep(200);
if (wsCloseFlag) {
break;
}
}
webSocket.close(1000, "");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
System.out.print("大模型:");
MyThread myThread = new MyThread(webSocket);
myThread.start();
}
@Override
public void onMessage(WebSocket webSocket, String text) {
// System.out.println(userId + "用来区分那个用户的结果" + text);
JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);
if (myJsonParse.header.code != 0) {
System.out.println("发生错误,错误码为:" + myJsonParse.header.code);
System.out.println("本次请求的sid为:" + myJsonParse.header.sid);
webSocket.close(1000, "");
}
List<Text> textList = myJsonParse.payload.choices.text;
for (Text temp : textList) {
System.out.print(temp.content);
totalAnswer=totalAnswer+temp.content;
}
if (myJsonParse.header.status == 2) {
// 可以关闭连接,释放资源
System.out.println();
System.out.println("*************************************************************************************");
if(canAddHistory()){
RoleContent roleContent=new RoleContent();
roleContent.setRole("assistant");
roleContent.setContent(totalAnswer);
historyList.add(roleContent);
}else{
historyList.remove(0);
RoleContent roleContent=new RoleContent();
roleContent.setRole("assistant");
roleContent.setContent(totalAnswer);
historyList.add(roleContent);
}
wsCloseFlag = true;
totalFlag=true;
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
try {
if (null != response) {
int code = response.code();
System.out.println("onFailure code:" + code);
System.out.println("onFailure body:" + response.body().string());
if (101 != code) {
System.out.println("connection failed");
System.exit(0);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 鉴权方法
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
URL url = new URL(hostUrl);
// 时间
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 拼接
String preStr = "host: " + url.getHost() + "\\n" +
"date: " + date + "\\n" +
"GET " + url.getPath() + " HTTP/1.1";
// System.err.println(preStr);
// SHA256加密
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
String sha = Base64.getEncoder().encodeToString(hexDigits);
// System.err.println(sha);
// 拼接
String authorization = String.format("api_key=\\"%s\\", algorithm=\\"%s\\", headers=\\"%s\\", signature=\\"%s\\"", apiKey, "hmac-sha256", "host date request-line", sha);
// 拼接地址
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();
// System.err.println(httpUrl.toString());
return httpUrl.toString();
}
//返回的json结果拆解
class JsonParse {
Header header;
Payload payload;
}
class Header {
int code;
int status;
String sid;
}
class Payload {
Choices choices;
}
class Choices {
List<Text> text;
}
class Text {
String role;
String content;
}
class RoleContent{
String role;
String content;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
}
这是官方给我们的示例代码,但是只能将在下方运行框中进行使用,但是,如果你仔细看过我们的socket代码,我们其中的send_text和receive_text也是会在底下的输入框中进行输出的,所以我们可以将send_text替换为原AI代码中的“我”的部分,把receive_text的部分转换为“大模型“的部分并返回给页面,同时我们还要加上socket服务,在启动时,先要打开服务器,然后进行一系列操作
这是改正之后的,代码相当于两部分结合了一下,想要理解的同学不妨直接扔给AI解释一下(dogs):
AI与socket的结合
pom.xml的配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>big_model</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!– https://mvnrepository.com/artifact/com.alibaba/fastjson –>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.19</version> <!– 版本号根据需求调整 –>
</dependency>
<!– https://mvnrepository.com/artifact/com.google.code.gson/gson –>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!– https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket –>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
<!– https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp –>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<!– https://mvnrepository.com/artifact/com.squareup.okio/okio –>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>2.10.0</version>
</dependency>
</dependencies>
</project>
package com.day;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.google.gson.Gson;
import okhttp3.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class BigModelNew extends WebSocketListener {
// 各版本的hostUrl及其对应的domian参数,具体可以参考接口文档 https://www.xfyun.cn/doc/spark/Web.html
// Spark Lite https://spark-api.xf-yun.com/v1.1/chat domain参数为lite
// Spark Pro https://spark-api.xf-yun.com/v3.1/chat domain参数为generalv3
// Spark Pro-128K https://spark-api.xf-yun.com/chat/pro-128k domain参数为pro-128k
// Spark Max https://spark-api.xf-yun.com/v3.5/chat domain参数为generalv3.5
// Spark Max-32K https://spark-api.xf-yun.com/chat/max-32k domain参数为max-32k
// Spark4.0 Ultra https://spark-api.xf-yun.com/v4.0/chat domain参数为4.0Ultra
public static final String hostUrl = "https://spark-api.xf-yun.com/v3.1/chat";
public static final String domain = "generalv3";
public static final String appid = "你的appid";
public static final String apiSecret = "你的apiSecret";
public static final String apiKey = "你的apiKey";
private static final Map<String, List<RoleContent>> sessionHistories = new ConcurrentHashMap<>();
public static List<RoleContent> historyList=new ArrayList<>(); // 对话历史存储集合
// 环境治理的重要性 环保 人口老龄化 我爱我的祖国
public static String NewQuestion = "";
public static final Gson gson = new Gson();
// 个性化参数
private String userId;
private Boolean wsCloseFlag;
private static Boolean totalFlag=true; // 控制提示用户是否输入
// 构造函数
public BigModelNew(String userId,
SocketIOClient client,
List<RoleContent> history,
Runnable historyUpdater,
String message) { // 新增参数
this.userId = userId;
this.client = client;
this.history = new ArrayList<>(history);
this.historyUpdater = historyUpdater;
this.wsCloseFlag = false;
this.message = message; // 新增字段
}
// 新增字段定义
private final String message;
private final SocketIOClient client;
private final List<RoleContent> history;
private final Runnable historyUpdater;
private String totalAnswer = "";
private String getLatestQuestion() {
return history.isEmpty() ? "" :
history.get(history.size()–1).content;
}
// 主函数
public static void main(String[] args) throws Exception {
Configuration config = new Configuration();
config.setPort(9010);
final SocketIOServer server = new SocketIOServer(config);
server.addConnectListener(client -> {
String sessionId = client.getSessionId().toString();
sessionHistories.put(sessionId, new ArrayList<>());
System.out.println(sessionId + "已连接");
});
server.addDisconnectListener(client -> {
String sessionId = client.getSessionId().toString();
sessionHistories.remove(sessionId);
System.out.println(sessionId + "已断开");
});
server.addEventListener("send_text", String.class, (client, msg, ackSender) -> {
String sessionId = client.getSessionId().toString();
CompletableFuture.runAsync(() -> {
try {
List<RoleContent> history = sessionHistories.getOrDefault(sessionId, new ArrayList<>());
BigModelNew handler = new BigModelNew(
sessionId,
client,
history,
() -> sessionHistories.put(sessionId, new ArrayList<>(history)),
msg // 传递用户消息
);
handler.processWithAI();
} catch (Exception e) {
client.sendEvent("error", "处理错误: " + e.getMessage());
e.printStackTrace();
}
});
});
server.start();
}
private void processWithAI() throws Exception {
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url(authUrl.replace("https://", "wss://"))
.build();
okHttpClient.newWebSocket(request, this);
// 添加用户消息到历史(使用this.message)
history.add(new RoleContent("user", this.message));
// 控制历史长度
while (calculateHistoryLength(history) > 12000) {
history.remove(0);
}
}
private static int calculateHistoryLength(List<RoleContent> history) {
return history.stream().mapToInt(r -> r.content.length()).sum();
}
public static boolean canAddHistory(){ // 由于历史记录最大上线1.2W左右,需要判断是能能加入历史
int history_length=0;
for(RoleContent temp:historyList){
history_length=history_length+temp.content.length();
}
if(history_length>12000){
historyList.remove(0);
historyList.remove(1);
historyList.remove(2);
historyList.remove(3);
historyList.remove(4);
return false;
}else{
return true;
}
}
// 线程来发送音频与参数
class MyThread extends Thread {
private WebSocket webSocket;
public MyThread(WebSocket webSocket) {
this.webSocket = webSocket;
}
public void run() {
try {
JSONObject requestJson=new JSONObject();
JSONObject header=new JSONObject(); // header参数
header.put("app_id",appid);
header.put("uid",UUID.randomUUID().toString().substring(0, 10));
JSONObject parameter=new JSONObject(); // parameter参数
JSONObject chat=new JSONObject();
chat.put("domain",domain);
chat.put("temperature",0.5);
chat.put("max_tokens",4096);
parameter.put("chat",chat);
JSONObject payload=new JSONObject(); // payload参数
JSONObject message=new JSONObject();
JSONArray text=new JSONArray();
// 历史问题获取
if(historyList.size()>0){
for(RoleContent tempRoleContent:historyList){
text.add(JSON.toJSON(tempRoleContent));
}
}
// 最新问题
RoleContent roleContent=new RoleContent();
roleContent.role="user";
roleContent.content=NewQuestion;
text.add(JSON.toJSON(roleContent));
historyList.add(roleContent);
message.put("text",text);
payload.put("message",message);
requestJson.put("header",header);
requestJson.put("parameter",parameter);
requestJson.put("payload",payload);
// System.err.println(requestJson); // 可以打印看每次的传参明细
webSocket.send(requestJson.toString());
// 等待服务端返回完毕后关闭
while (true) {
// System.err.println(wsCloseFlag + "—");
Thread.sleep(200);
if (wsCloseFlag) {
break;
}
}
webSocket.close(1000, "");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
JSONObject requestJson = buildRequestJson(history);
webSocket.send(requestJson.toString());
}
@Override
public void onMessage(WebSocket webSocket, String text) {
// System.out.println(userId + "用来区分那个用户的结果" + text);
JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);
if (myJsonParse.header.code != 0) {
System.out.println("发生错误,错误码为:" + myJsonParse.header.code);
System.out.println("本次请求的sid为:" + myJsonParse.header.sid);
webSocket.close(1000, "");
}
List<Text> textList = myJsonParse.payload.choices.text;
for (Text temp : textList) {
System.out.print(temp.content);
totalAnswer=totalAnswer+temp.content;
}
if (myJsonParse.header.status == 2) {
// 可以关闭连接,释放资源
System.out.println();
System.out.println("*************************************************************************************");
if(canAddHistory()){
RoleContent roleContent=new RoleContent();
roleContent.setRole("assistant");
roleContent.setContent(totalAnswer);
historyList.add(roleContent);
}else{
historyList.remove(0);
RoleContent roleContent=new RoleContent();
roleContent.setRole("assistant");
roleContent.setContent(totalAnswer);
historyList.add(roleContent);
}
wsCloseFlag = true;
totalFlag=true;
}
if (!textList.isEmpty()) {
String delta = textList.get(0).content;
client.sendEvent("receive_text", delta);
}
if (myJsonParse.header.status == 2) {
// 最终结果处理
RoleContent assistantMsg = new RoleContent("assistant", totalAnswer);
history.add(assistantMsg);
historyUpdater.run();
client.sendEvent("receive_end", totalAnswer);
}
}
private JSONObject buildRequestJson(List<RoleContent> history) {
JSONObject requestJson = new JSONObject();
// Header
JSONObject header = new JSONObject();
header.put("app_id", appid);
header.put("uid", UUID.randomUUID().toString().substring(0, 10));
// Parameter
JSONObject parameter = new JSONObject();
JSONObject chat = new JSONObject();
chat.put("domain", domain);
chat.put("temperature", 0.5);
chat.put("max_tokens", 4096);
parameter.put("chat", chat);
// Payload
JSONObject payload = new JSONObject();
JSONObject messageObj = new JSONObject();
JSONArray text = new JSONArray();
for (RoleContent content : history) {
text.add(JSON.toJSON(content));
}
text.add(JSON.toJSON(new RoleContent("user", message)));
messageObj.put("text", text);
payload.put("message", messageObj);
requestJson.put("header", header);
requestJson.put("parameter", parameter);
requestJson.put("payload", payload);
return requestJson;
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
try {
if (null != response) {
int code = response.code();
System.out.println("onFailure code:" + code);
System.out.println("onFailure body:" + response.body().string());
if (101 != code) {
System.out.println("connection failed");
System.exit(0);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 鉴权方法
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
URL url = new URL(hostUrl);
// 时间
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 拼接
String preStr = "host: " + url.getHost() + "\\n" +
"date: " + date + "\\n" +
"GET " + url.getPath() + " HTTP/1.1";
// System.err.println(preStr);
// SHA256加密
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
String sha = Base64.getEncoder().encodeToString(hexDigits);
// System.err.println(sha);
// 拼接
String authorization = String.format("api_key=\\"%s\\", algorithm=\\"%s\\", headers=\\"%s\\", signature=\\"%s\\"", apiKey, "hmac-sha256", "host date request-line", sha);
// 拼接地址
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();
// System.err.println(httpUrl.toString());
return httpUrl.toString();
}
//返回的json结果拆解
class JsonParse {
Header header;
Payload payload;
}
class Header {
int code;
int status;
String sid;
}
class Payload {
Choices choices;
}
class Choices {
List<Text> text;
}
class Text {
String role;
String content;
}
class RoleContent{
String role;
String content;
public RoleContent(String role, String content) {
this.role = role;
this.content = content;
}
public RoleContent() {}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
}
我们来看一下效果:
尾言
这样看来,我们的ai+Socket服务器就连接成功了,并且能够进行实时对话,但可以看到,我们的页面只能显示最后输出的一句话,所以仍然需要进行修改,由于篇幅与时间问题,我这里就不再修改了,留给读者们自由发挥的空间,这篇文章是作者的呕心沥血之作,也填补了目前网站所没有的空白,所以可以的话,可以给作者点一个赞或关注鼓励一下作者,如果有问题的话也欢迎与作者进行交流与讨论。
评论前必须登录!
注册