申请地址
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/chatappId: xxxapiSecret: xxxapiKey: 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() {@Overridepublic void onOpen(WebSocket webSocket, Response response) {webSocket.send(body);//发送消息}@Overridepublic 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 {@Autowiredprivate 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() {@Overridepublic void onOpen(WebSocket webSocket, Response response) {webSocket.send(body);}@Overridepublic 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();}}