最近很荣幸的申请到了科大讯飞星火大模型的内测资格(已经开放网页版可以直接用,类似于chatgpt那种),给了我400w的免费token,有效期一年,其中1.5版本和2.0版本各200w,于是自己想着基于星火大模型写一个简单的聊天界面,刚开始是基于springboot搭建的一个网页版的,但是页面布局啥的太难看,调了好长时间也就那样,最后就想到了还是用swing来做算了,废话不多说,上干货
首先第一步,要先去科大讯飞官网申请内测资格,通过了之后,会生成唯一的appId之类的东西,这个做开发的应该都知道,一般接入第三方开放平台,都是这种玩法,具体怎么申请,我就不说了,官网连接:讯飞开放平台-以语音交互为核心的人工智能开放平台
第二部就是用java的swing写一个简单的聊天界面,下面是最终的效果(很丑,勿喷):
下面直接上代码:
首先是聊天界面:
package com.tianjun.chat;
import com.google.gson.Gson;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class ChatBox extends JFrame {
public static final Gson gson = new Gson();
private static int mode_gen = 1;
private JTextArea chatArea;
private JTextField messageField;
private WebSocketClient client;
public ChatBox() {
setTitle("科大讯飞星火大模型");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1200, 900);
chatArea = new JTextArea();
chatArea.setLineWrap(true); // 自动换行
chatArea.setWrapStyleWord(true); // 按单词边界换行
chatArea.setEditable(false);
chatArea.setMargin(new Insets(10, 10, 10, 10)); // 设置边距,使文本不贴边
chatArea.setBackground(Color.WHITE); // 设置背景色,使文本区域更清晰可见
chatArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 设置边框,使文本区域更美观
JScrollPane scrollPane = new JScrollPane(chatArea);
messageField = new JTextField();
messageField.setPreferredSize(new Dimension(300, 50));
messageField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
doConnectAndSendMessage();
}
}
});
JButton sendButton = new JButton("Send");
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doConnectAndSendMessage();
}
});
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(scrollPane, BorderLayout.CENTER);
container.add(messageField, BorderLayout.SOUTH);
container.add(sendButton, BorderLayout.EAST);
setExtendedState(JFrame.MAXIMIZED_BOTH); // 最大化窗口
setResizable(true); // 禁止调整窗口大小
chatArea.setFont(new Font("微软雅黑", Font.PLAIN, 20));
messageField.setFont(new Font("微软雅黑", Font.PLAIN, 20));
chatArea.getDocument().putProperty("charset", "GBK");// 指定字符编码为 UTF-8
messageField.getDocument().putProperty("charset", "GBK"); // 指定字符编码为 UTF-8
}
public void doConnectAndSendMessage() {
mode_gen++;
chatArea.setText("");
String message = messageField.getText();
if (!message.isEmpty()) {
try {
client = getClient();
String myQuestion = mode_gen % 2 == 0 ? BigModelNewV2.getQuestion(message) : BigModelNewV1.getQuestion(message);
client.send(myQuestion);
messageField.setText("");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private WebSocketClient getClient() throws Exception {
String url = mode_gen % 2 == 0 ? BigModelNewV2.getAuthUrl().toString().replace("http://", "ws://").replace("https://", "wss://")
: BigModelNewV1.getAuthUrl().toString().replace("http://", "ws://").replace("https://", "wss://");
URI serverUri = new URI(url);
WebSocketClient client = new WebSocketClient(serverUri) {
@Override
public void onOpen(ServerHandshake handshakedata) {
String modelGen = mode_gen % 2 == 0 ? "第二代模型" : "第一代模型";
chatArea.append(modelGen + "\n" + messageField.getText() + "\n");
}
@Override
public void onMessage(String message) {
ChatBox.JsonParse myJsonParse = gson.fromJson(message, ChatBox.JsonParse.class);
if (myJsonParse.header.code != 0) {
System.out.println("发生错误,错误码为:" + myJsonParse.header.code);
System.out.println("本次请求的sid为:" + myJsonParse.header.sid);
}
List textList = myJsonParse.payload.choices.text;
for (ChatBox.Text temp : textList) {
System.out.print(temp.content);
chatArea.append(temp.content);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
chatArea.append("\nConnection closed\n");
}
@Override
public void onError(Exception ex) {
chatArea.append("Error: " + ex.getMessage() + "\n");
}
};
client.connect();
while (true) {
TimeUnit.MILLISECONDS.sleep(100);
if (client.isOpen()) {
break;
}
}
return client;
}
public void start() throws Exception {
getClient();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ChatBox chatBox = new ChatBox();
try {
chatBox.start();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
class JsonParse {
Header header;
Payload payload;
}
class Header {
int code;
int status;
String sid;
}
class Payload {
Choices choices;
}
class Choices {
List text;
}
class Text {
String role;
String content;
}
}
这个类的主要作用就是构建聊天界面,发送消息和接收回显消息,接下来的两个类,一个是对应1.5版本的大模型,一个是对应2.0版本的大模型,这两个类的代码在讯飞官网样例可以直接下载,就是要里面的几个关键的appid和key替换为自己的就可以
package com.tianjun.chat;
import com.google.gson.Gson;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
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.TimeUnit;
public class BigModelNewV1 extends WebSocketListener {
// 地址与鉴权信息
public static final String hostUrl = "https://spark-api.xf-yun.com/v1.1/chat";
public static final String appid = "";
public static final String apiSecret = "";
public static final String apiKey = "";
// 环境治理的重要性 环保 人口老龄化 我爱我的祖国
public static final Gson gson = new Gson();
// 个性化参数
private String userId;
private Boolean wsCloseFlag;
private static String requestJson = "{\n" +
" \"header\": {\n" +
" \"app_id\": \"" + appid + "\",\n" +
" \"uid\": \"" + UUID.randomUUID().toString().substring(0, 10) + "\"\n" +
" },\n" +
" \"parameter\": {\n" +
" \"chat\": {\n" +
" \"domain\": \"general\",\n" +
" \"temperature\": 0.5,\n" +
" \"max_tokens\": 1024\n" +
" }\n" +
" },\n" +
" \"payload\": {\n" +
" \"message\": {\n" +
" \"text\": [\n" +
" {\n" +
" \"role\": \"user\",\n" +
" \"content\": \"" + "${myQuestion}" + "\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
public static String getQuestion(String myQuestion) {
String question = requestJson.replace("${myQuestion}", myQuestion);
System.err.println(question);
return question;
}
// 构造函数
public BigModelNewV1(String userId, Boolean wsCloseFlag) {
this.userId = userId;
this.wsCloseFlag = wsCloseFlag;
}
// 主函数
public static void main(String[] args) throws Exception {
Scanner reader = new Scanner(System.in);
while (true) {
String line = null;
try {
line = reader.nextLine();
} catch (Exception e) {
e.printStackTrace();
}
if (StringUtils.isBlank(line)) {
continue;
}
WebSocket webSocket = webSocket();
String myQuestion = getQuestion(line);
System.err.println(webSocket.send(myQuestion));
webSocket.close(1000, "");
TimeUnit.SECONDS.sleep(2);
}
}
public static String getAuthUrl() throws Exception {
return getAuthUrl(hostUrl, apiKey, apiSecret);
}
public static WebSocket webSocket() throws Exception {
// 构建鉴权url
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
OkHttpClient client = new OkHttpClient();
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
// 个性化参数入口,如果是并发使用,可以在这里模拟
WebSocket webSocket = client.newWebSocket(request, new BigModelNewV1(1 + "",
false));
return webSocket;
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
}
@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 textList = myJsonParse.payload.choices.text;
for (Text temp : textList) {
System.out.print(temp.content);
}
if (myJsonParse.header.status == 2) {
// 可以关闭连接,释放资源
wsCloseFlag = 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;
}
class Text {
String role;
String content;
}
}
package com.tianjun.chat;
import com.google.gson.Gson;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
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.TimeUnit;
public class BigModelNewV2 extends WebSocketListener {
// 地址与鉴权信息
public static final String hostUrl = "https://spark-api.xf-yun.com/v2.1/chat";
public static final String appid = "";
public static final String apiSecret = "";
public static final String apiKey = "";
// 环境治理的重要性 环保 人口老龄化 我爱我的祖国
public static final Gson gson = new Gson();
// 个性化参数
private String userId;
private Boolean wsCloseFlag;
private static String requestJson = "{\n" +
" \"header\": {\n" +
" \"app_id\": \"" + appid + "\",\n" +
" \"uid\": \"" + UUID.randomUUID().toString().substring(0, 10) + "\"\n" +
" },\n" +
" \"parameter\": {\n" +
" \"chat\": {\n" +
" \"domain\": \"generalv2\",\n" +
" \"temperature\": 0.5,\n" +
" \"max_tokens\": 1024\n" +
" }\n" +
" },\n" +
" \"payload\": {\n" +
" \"message\": {\n" +
" \"text\": [\n" +
" {\n" +
" \"role\": \"user\",\n" +
" \"content\": \"" + "${myQuestion}" + "\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
"}";
public static String getQuestion(String myQuestion) {
String question = requestJson.replace("${myQuestion}", myQuestion);
System.err.println(question);
return question;
}
// 构造函数
public BigModelNewV2(String userId, Boolean wsCloseFlag) {
this.userId = userId;
this.wsCloseFlag = wsCloseFlag;
}
// 主函数
public static void main(String[] args) throws Exception {
Scanner reader = new Scanner(System.in);
while (true) {
String line = null;
try {
line = reader.nextLine();
} catch (Exception e) {
e.printStackTrace();
}
if (StringUtils.isBlank(line)) {
continue;
}
WebSocket webSocket = webSocket();
String myQuestion = getQuestion(line);
System.err.println(webSocket.send(myQuestion));
webSocket.close(1000, "");
TimeUnit.SECONDS.sleep(2);
}
}
public static String getAuthUrl() throws Exception {
return getAuthUrl(hostUrl, apiKey, apiSecret);
}
public static WebSocket webSocket() throws Exception {
// 构建鉴权url
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
OkHttpClient client = new OkHttpClient();
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
// 个性化参数入口,如果是并发使用,可以在这里模拟
WebSocket webSocket = client.newWebSocket(request, new BigModelNewV2(1 + "",
false));
return webSocket;
}
public static void send(WebSocket webSocket) {
try {
// 等待服务端返回完毕后关闭
Scanner reader = new Scanner(System.in);
while (true) {
String line = null;
try {
line = reader.nextLine();
} catch (Exception e) {
e.printStackTrace();
}
// 发送一行数据,并加上回车换行符
// 将String类型的消息转换为ByteBuf类型的消息
String myQuestion = getQuestion(line);
System.err.println(webSocket.send(myQuestion));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
}
@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 textList = myJsonParse.payload.choices.text;
for (Text temp : textList) {
System.out.print(temp.content);
}
if (myJsonParse.header.status == 2) {
// 可以关闭连接,释放资源
wsCloseFlag = 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;
}
class Text {
String role;
String content;
}
}
ps:星火大模型是通过websocket接入的,但是每次发送一个问题之后服务端就会主动断开连接,所以每次发送问题的时候都需要重新建立连接,这个可能是考虑到服务端的压力才这么做的吧,官网下载下来的样例代码,pom依赖是有问题的,然后注意一下发送请求的时候1.5和2.0的请求参数部分domain的值不要搞错了,这个在官网也有说明,2.0的值是generalv2
,这个是我改过之后的pom文件(主要看依赖部分):
4.0.0
com.tianjun.chat
chat
1.0-SNAPSHOT
chat
http://www.example.com
UTF-8
1.8
1.8
1.8
com.alibaba
fastjson
1.2.67
com.google.code.gson
gson
2.8.5
org.java-websocket
Java-WebSocket
1.3.8
com.squareup.okhttp3
okhttp
4.9.1
com.squareup.okio
okio
2.10.0
io.netty
netty-all
4.1.42.Final
org.apache.commons
commons-lang3
3.12.0
org.asynchttpclient
async-http-client
2.12.3
org.java-websocket
Java-WebSocket
1.5.3
maven-clean-plugin
3.1.0
maven-resources-plugin
3.0.2
maven-compiler-plugin
3.8.0
maven-surefire-plugin
2.22.1
maven-jar-plugin
3.0.2
maven-install-plugin
2.5.2
maven-deploy-plugin
2.8.2
maven-site-plugin
3.7.1
maven-project-info-reports-plugin
3.0.0
maven-compiler-plugin
3.8.0
1.8
1.8
org.apache.maven.plugins
maven-shade-plugin
3.2.1
false
com.tianjun.chat.ChatBox
package
shade
org.apache.maven.plugins
maven-jar-plugin
3.2.0
true
lib/
com.tianjun.chat.ChatBox