-
前言
“AI大战”正在如火如荼地进行,科大讯飞作为国内知名的智能语音和人工智能企业,自然不愿落后。今年5月份,科大讯飞宣布推出了“讯飞星火认知大模型”,引起了广泛关注。近期我也获得了星火大模型的体验资格,今天我将详细介绍科大讯飞星火进入QQ的过程。
-
成果展示
欢迎大家加入体验(812531736)
-
官方Web文档
- 对自己有信心的小伙伴可以先看官方文档熟悉一下流程
星火认知大模型Web文档 | 讯飞开放平台文档中心 (xfyun.cn)https://www.xfyun.cn/doc/spark/Web.html
-
申请星火api
- 1.先到官网申请免费试用。
讯飞星火认知大模型-AI大语言模型-星火大模型-科大讯飞 (xfyun.cn)https://xinghuo.xfyun.cn/sparkapi?scr=price
- 2.星火大模型V2.0需要和具体的应用绑定,我们需要先创建一个新应用。
- 3.填写完成后,等待后台审核,项目紧急可以在官网联系技术支持,快速审核通过。
-
前置教程(已出)
- 有兴趣的小伙伴可以看前置教程更快的熟悉和上手项目
- 1.
- 2.
- 3.
-
调用流程概览
- 1.针对星火大模型的接口对接,为了确保跨平台的兼容性,采用web方式进行接口对接。
- 2.根据官方文档的指示,我们首先需要调用鉴权接口,以获取接口授权。
- 3.接下来,我们使用websocket协议与服务端进行握手,并确保握手成功后,在60秒内发送请求。
- 4.接口采用流式输出模式,因此我们需要根据返回的数据进行判断,并拼接成完整的回复信息。以下是大致的流程:
-
鉴权URL
-
1.鉴权说明
需要自行先在控制台创建应用,利用应用中提供的appid,APIKey, APISecret进行鉴权,生成最终请求的鉴权url。鉴权方法见下方
-
2.鉴权参数
参数 | 类型 | 必须 | 说明 | 示例 |
---|---|---|---|---|
host | string | 是 | 请求的主机 | aichat.xf-yun.com(使用时需替换为实际使用的接口地址) |
date | string | 是 | 当前时间戳,采用RFC1123格式,时间偏差需控制在300s内 | Fri, 05 May 2023 10:43:39 GMT |
authorization | string | 是 | base64编码的签名信息 | 参考下方生成方式 |
-
3.具体鉴权过程及代码
- 1)到控制台获取APIKey 和APISecret参数
- 2)利用下方的date动态拼接生成字符串tmp,这里以星火url为例,实际使用需要根据具体的请求url替换host和path。
- 3)利用hmac-sha256算法结合APISecret对上一步的tmp签名,获得签名后的摘要tmp_sha。
- 4)将上一步的tmp_sha进行base64编码生成signature
- 5)利用上一步生成的signature,拼接下方的字符串生成authorization_origin
- 6)最后再将上一步的authorization_origin进行base64编码,生成最终的authorization
- 7)将鉴权参数组合成最终的键值对生成最终的握手url。小伙伴可先根据步骤一步步进行参数校验,确保生成的参数无误。
//星火url鉴权
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";
// 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);
// 拼接
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();
return httpUrl.toString();
}
-
WebSocket通信过程
-
1.首先,你需要准备好一些必要的参数,包括hostUrl、apiKey和apiSecret。
-
2.然后,你可以调用getAuthUrl方法来生成鉴权URL,该方法使用了之前提到的一系列步骤,包括动态拼接字符串、进行签名和编码等操作。
-
3.接下来,你需要创建一个OkHttpClient实例,并且将鉴权URL进行一些替换操作,将其转换为WebSocket的URL。
-
4.构建一个WebSocket的请求对象,并且传入URL。
-
5.创建一个CountDownLatch对象,并将其初始化为1。
-
6.实现WebSocketListener的几个回调方法,包括onOpen、onMessage等。在onOpen方法中,你可以发送你的问题到服务器端。
-
7.在onMessage方法中,你可以解析服务器返回的消息,并将答案添加到answerList中。如果返回的消息中的header.status为2,表示对话结束,此时关闭WebSocket并释放CountDownLatch。
-
8.调用latch.await()方法来等待CountDownLatch为0,确保WebSocket连接完成。
-
9.最后,将answerList转换为数组并返回。
//星火
public static String[] Spark(String question) throws Exception {
final List<String> answerList = new ArrayList<>();
String hostUrl = "https://spark-api.xf-yun.com/v2.1/chat";
String apiKey = "";
String apiSecret = "";
String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
String appid = "cc36e4de";
OkHttpClient client = new OkHttpClient.Builder().build();
String url = authUrl.replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
CountDownLatch latch = new CountDownLatch(1);
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
// 发送问题
JsonObject requestJson = new JsonObject();
JsonObject header = new JsonObject();
header.addProperty("app_id", appid);
header.addProperty("uid", UUID.randomUUID().toString().substring(0, 10));
JsonObject parameter = new JsonObject();
JsonObject chat = new JsonObject();
chat.addProperty("domain", "generalv2");
chat.addProperty("temperature", 0.5);
chat.addProperty("max_tokens", 4096);
parameter.add("chat", chat);
JsonObject payload = new JsonObject();
JsonObject message = new JsonObject();
JsonArray text = new JsonArray();
RoleContent roleContent = new RoleContent();
roleContent.role = "user";
roleContent.content = question;
text.add(new Gson().toJsonTree(roleContent));
message.add("text", text);
payload.add("message", message);
requestJson.add("header", header);
requestJson.add("parameter", parameter);
requestJson.add("payload", payload);
String jsonString = new Gson().toJson(requestJson);
webSocket.send(jsonString);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
Gson gson = new Gson();
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) {
answerList.add(temp.content);
}
if (myJsonParse.header.status == 2) {
webSocket.close(1000, "");
latch.countDown();
}
}
});
latch.await();
String[] answers = new String[answerList.size()];
return answerList.toArray(answers);
}
-
辅助方法和类
-
RoleContent
:表示对话中的角色和内容,包含role
和content
属性。 -
JsonParse
:表示JSON数据的解析结果,包含header
和payload
属性。 -
Header
:表示JSON数据中的头部信息,包含code
、status
和sid
属性。 -
Payload
:表示JSON数据中的有效负载信息,包含choices
属性。 -
Choices
:表示对话中的选项,包含一个text
属性,它是一个Text
对象的列表。 -
Text
:表示对话中的文本内容,包含一个content
属性。static class RoleContent { String role; String content; } static class JsonParse { Header header; Payload payload; } static class Header { int code; int status; String sid; } static class Payload { Choices choices; } static class Choices { List<Text> text; } static class Text { String content; }