申请地址
https://xinghuo.xfyun.cn/sparkapi?scr=price
免费申请200万Token
开发文档
https://www.xfyun.cn/doc/spark/Web.html#_1-接口说明
页面最下面有相关demo可以参考
介绍
接口是以套接字的形式分段返回,而且非http请求,比较繁琐,官方也只给了比较简单的deom。
依赖项
<!--okhttp3-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
配置文件
xunfei:
ai:
hostUrl: https://spark-api.xf-yun.com/v3.5/chat
appId: xxx
apiSecret: xxx
apiKey: xxx
控制台上可以查看 API认证字符串
读取配置文件
@Value("${xunfei.ai.hostUrl}")
private String hostUrl;
@Value("${xunfei.ai.appId}")
private String appId;
@Value("${xunfei.ai.apiSecret}")
private String apiSecret;
@Value("${xunfei.ai.apiKey}")
private String apiKey;
权限校验
得到的是一个url,需要将http替换成ws
/**
* 权限校验
* @return String
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws MalformedURLException
*/
private String getAuthUrl() throws NoSuchAlgorithmException, InvalidKeyException, MalformedURLException {
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();
return httpUrl.toString();
}
构建请求参数
请求参数是与json格式进行发送,如果需要结合之前的信息继续回答需要携带历史记录。在官方的api文档也可以查看
#参数构造示例如下
{
"header": {
"app_id": "12345",
"uid": "12345"
},
"parameter": {
"chat": {
"domain": "generalv3.5",
"temperature": 0.5,
"max_tokens": 1024,
}
},
"payload": {
"message": {
# 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例
# 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息
"text": [
{"role":"system","content":"你现在扮演李白,你豪情万丈,狂放不羁;接下来请用李白的口吻和用户对话。"} #设置对话背景或者模型角色
{"role": "user", "content": "你是谁"} # 用户的历史问题
{"role": "assistant", "content": "....."} # AI的历史回答结果
# ....... 省略的历史对话
{"role": "user", "content": "你会做什么"} # 最新的一条问题,如无需上下文,可只传最新一条问题
]
}
}
}
JAVA构建
private String buildBody(String text,String uid){
JSONObject body =new JSONObject();
JSONObject header =new JSONObject();
header.put("app_id",appId);
header.put("uid",uid);
body.put("header",header);
JSONObject parameter =new JSONObject();
JSONObject chat =new JSONObject();
chat.put("domain","generalv3.5");
parameter.put("chat",chat);
body.put("parameter",parameter);
JSONObject history =JSONObject.parseObject(text);
body.put("payload",history);
JSONObject back =new JSONObject();
back.put("role","system");
back.put("content","请回答我关于一些生产安全的内容");
//定义会话背景
history.getJSONObject("message").getJSONArray("text").add(0,back);
return body.toJSONString();
}
回复消息
基于OKHTTP3的请求库,连接websocket
/**
* 回复消息
* @param text
* @return
* @throws MalformedURLException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public String answer(String text,String uid) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException, ExecutionException, InterruptedException, TimeoutException {
String authUrl =getAuthUrl().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(authUrl).build();
OkHttpClient client = new OkHttpClient.Builder().build();
StringBuilder sb =new StringBuilder();
CompletableFuture<String> messageReceived = new CompletableFuture<>();
String body = buildBody(text,uid);
WebSocket webSocket =client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
webSocket.send(body);
//发送消息
}
@Override
public void onMessage(WebSocket webSocket, String text) {
JSONObject obj = JSON.parseObject(text);
String str= obj.getJSONObject("payload").getJSONObject("choices").getJSONArray("text").getJSONObject(0).getString("content");
sb.append(str);
if(obj.getJSONObject("header").getLong("status")==2){
webSocket.close(1000, "Closing WebSocket connection");
messageReceived.complete(text); // 将收到的消息传递给 CompletableFuture
}
}
} );
String result = messageReceived.get(30, TimeUnit.SECONDS);; // 阻塞等待消息返回
webSocket.close(1000, "Closing WebSocket connection");
return sb.toString();
}
Controller
@PostMapping("/chat")
public AjaxResult chat(String text,String uid) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException, ExecutionException, InterruptedException, TimeoutException {
return success( model.answer(text,uid));
}
运行效果
完整代码
Controller层
@RestController
@RequestMapping("/course")
public class QueryController extends BaseController {
@Autowired
private CognitiveMode model;
@PostMapping("/chat")
public AjaxResult chat(String text,String uid) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException, ExecutionException, InterruptedException, TimeoutException {
return success( model.answer(text,uid));
}
}
package com.ruoyi.framework.ai;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Component
public class CognitiveMode {
@Value("${xunfei.ai.hostUrl}")
private String hostUrl;
@Value("${xunfei.ai.appId}")
private String appId;
@Value("${xunfei.ai.apiSecret}")
private String apiSecret;
@Value("${xunfei.ai.apiKey}")
private String apiKey;
/**
* 回复消息
* @param text
* @return
* @throws MalformedURLException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public String answer(String text,String uid) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException, ExecutionException, InterruptedException, TimeoutException {
String authUrl =getAuthUrl().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(authUrl).build();
OkHttpClient client = new OkHttpClient.Builder().build();
StringBuilder sb =new StringBuilder();
CompletableFuture<String> messageReceived = new CompletableFuture<>();
String body = buildBody(text,uid);
WebSocket webSocket =client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
webSocket.send(body);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
JSONObject obj = JSON.parseObject(text);
String str= obj.getJSONObject("payload").getJSONObject("choices").getJSONArray("text").getJSONObject(0).getString("content");
sb.append(str);
if(obj.getJSONObject("header").getLong("status")==2){
webSocket.close(1000, "Closing WebSocket connection");
messageReceived.complete(text); // 将收到的消息传递给 CompletableFuture
}
}
} );
String result = messageReceived.get(30, TimeUnit.SECONDS);; // 阻塞等待消息返回
webSocket.close(1000, "Closing WebSocket connection");
return sb.toString();
}
private String buildBody(String text,String uid){
JSONObject body =new JSONObject();
JSONObject header =new JSONObject();
header.put("app_id",appId);
header.put("uid",uid);
body.put("header",header);
JSONObject parameter =new JSONObject();
JSONObject chat =new JSONObject();
chat.put("domain","generalv3.5");
parameter.put("chat",chat);
body.put("parameter",parameter);
JSONObject history =JSONObject.parseObject(text);
body.put("payload",history);
JSONObject back =new JSONObject();
back.put("role","system");
back.put("content","请回答我关于一些xxx的内容");
history.getJSONObject("message").getJSONArray("text").add(0,back);
return body.toJSONString();
}
/**
* 权限校验
* @return String
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws MalformedURLException
*/
private String getAuthUrl() throws NoSuchAlgorithmException, InvalidKeyException, MalformedURLException {
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();
return httpUrl.toString();
}
}