赞
踩
讯飞的星火大模型是有免费版本赠送200万个Token,就个人学习来说完全够用了。
免费申请过后,到控制台,两个红色方框是最要紧的。
星火认知大模型Web文档 | 讯飞开放平台文档中心 (xfyun.cn)这是官方文档对于接口的详细使用,里面有对各种请求的详细描述。接下来我们将在Spring项目中使用星火的AIGC能力。
首先是星火给的APPID、APISecret、APIKey是连接到大模型的关键,通过这三个参数按照通用鉴权URL生成说明 | 讯飞开放平台文档中心 (xfyun.cn)生成通用鉴权URL就可以就行html请求,发送请求参数,接受返回信息。
https://xfyun-doc.cn-bj.ufileos.com/static%2F16933832521697972%2Fbig_model.zip 这是官方给出的Java例子。
官方给的,直接拿来用。
/ 获得鉴权地址 public static String getAuthUrl(String hostUrl, String apiSecret, String apiKey) 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(); }
在项目中使用我们需要一个Config进行全局配置,一个WebListener发送接受请求,还有与星火交互的DTO。
/**
* 星火大模型AI接口配置
*/
@Configuration
@Data
@ConfigurationProperties(prefix = "xfun.open")
public class XfunConfig {
private String appid;
private String apiSecret;
private String hostUrl;
private String apiKey;
}
在application.yml中的配置
#星火
xfun:
open:
appid: XXXXXXXXXXXXXXXXXXXXXXX
apiSecret: XXXXXXXXXXXXXXXXXXXXXXX
hostUrl: https://spark-api.xf-yun.com/v2.1/chat
apiKey: XXXXXXXXXXXXXXXXXXXXXXX
看官方文档中对请求参数以及对各个字段的解释。
# 参数构造示例如下 { "header": { "app_id": "12345", "uid": "12345" }, "parameter": { "chat": { "domain": "general", "temperature": 0.5, "max_tokens": 1024, } }, "payload": { "message": { # 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例 # 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息 "text": [ {"role": "user", "content": "你是谁"} # 用户的历史问题 {"role": "assistant", "content": "....."} # AI的历史回答结果 # ....... 省略的历史对话 {"role": "user", "content": "你会做什么"} # 最新的一条问题,如无需上下文,可只传最新一条问题 ] } } }
其中Message单独为一个类
/** * 消息对象 * */ @Data public class MsgDTO { /** * 角色 */ private String role; /** * 消息内容 */ private String content; private Integer index; @Getter public static enum Role { SYSTEM("system"), USER("user"), ASSISTANT("assistant"); private String name; private Role(String name) { this.name = name; } } }
这样一个请求构成的数据对象类如下:
@Data public class XfunSendRequest { @JsonProperty("header") private Header header; @JsonProperty("parameter") private ParameterDTO parameterDTO; @JsonProperty("payload") private PayloadDTO payload; @Data public static class Header{ @JSONField(name = "app_id") private String appId; @JSONField(name = "uid") private String uid; } @ Data public static class ParameterDTO{ @JsonProperty("chat") private ChatDTO chat; @Data public static class ChatDTO { @JsonProperty("domain") private String domain = "generalv2"; @JsonProperty("temperature") private Double temperature = 0.5; @JSONField(name = "max_tokens") private Integer maxTokens = 2048; } } @Data public static class PayloadDTO { @JsonProperty("message") private MessageDTO message; @Data public static class MessageDTO { @JsonProperty("text") private List<MsgDTO> text; } } }
接下来是构造接口响应对象:
# 接口为流式返回,此示例为最后一次返回结果,开发者需要将接口多次返回的结果进行拼接展示 { "header":{ "code":0, "message":"Success", "sid":"cht000cb087@dx18793cd421fb894542", "status":2 }, "payload":{ "choices":{ "status":2, "seq":0, "text":[ { "content":"我可以帮助你的吗?", "role":"assistant", "index":0 } ] }, "usage":{ "text":{ "question_tokens":4, "prompt_tokens":5, "completion_tokens":9, "total_tokens":14 } } } }
由官方描述可见,其中 payload.choice.text中的内容就是构造的MsgDto, 根据官方文档给出的接口响应构造XfunReceieveRequest;
@Data public class XfunReceieveRequest { @JsonProperty("header") private HeaderDTO header; @JsonProperty("payload") private PayloadDTO payload; @NoArgsConstructor @Data public static class HeaderDTO { @JsonProperty("code") private Integer code; @JsonProperty("message") private String message; @JsonProperty("sid") private String sid; @JsonProperty("status") private Integer status; } @NoArgsConstructor @Data public static class PayloadDTO { @JsonProperty("choices") private ChoicesDTO choices; @JsonProperty("usage") private UsageDTO usage; @NoArgsConstructor @Data public static class ChoicesDTO { @JsonProperty("status") private Integer status; @JsonProperty("seq") private Integer seq; @JsonProperty("text") private List<MsgDTO> text; } @NoArgsConstructor @Data public static class UsageDTO { @JsonProperty("text") private TextDTO text; @NoArgsConstructor @Data public static class TextDTO { @JsonProperty("question_tokens") private Integer questionTokens; @JsonProperty("prompt_tokens") private Integer promptTokens; @JsonProperty("completion_tokens") private Integer completionTokens; @JsonProperty("total_tokens") private Integer totalTokens; } } } }
这样发送和接受请求都构造完成可以尝试与星火进行交互。
使用WebSocket将XfunSendRequest发出即可;以下是XfunListener中发送消息代码:
public XfunListener sendMsg(String uid, List<MsgDTO> msgs, XfunListener webSocketListener) throws Exception { // 获取鉴权url String url = getAuthUrl(hostUrl,apiSecret,apiKey); //建立请求 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Request request = new Request.Builder().url(url).build(); WebSocket webSocket = okHttpClient.newWebSocket(request, webSocketListener); XfunSendRequest xfunSendRequest = this.getSendRequest(uid, msgs); System.out.println("params:" + JSONObject.toJSONString(xfunSendRequest)); //发送消息 webSocket.send(JSONObject.toJSONString(xfunSendRequest)); return webSocketListener; }
可以看到整个发送消息的过程:
接受消息写在XfunListener的OnMessage重载函数中,根据XfunReceiveRequest来处理数据;
具体代码如下:
最关键的就是两个函数:
@Builder public class XfunListener extends WebSocketListener { private String hostUrl; private String appid; private String apiSecret; private String apiKey; @Builder.Default public boolean is_finished = false; @Builder.Default private String answer = ""; public String getAnswer() { return answer; } public boolean isFinished() { return is_finished; } public List<MsgDTO> getHistoryList() { return historyList; } @Builder.Default public List<MsgDTO> historyList = new ArrayList<>(); @Override public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { super.onOpen(webSocket, response); } public void deleteHistory(){ historyList = new ArrayList<>(); } public void init_chat(){ is_finished = false; } // 接收到消息如何处理 @Override public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { super.onMessage(webSocket, text); // System.out.println("接收到消息:" + text); // 消息格式处理 XfunReceieveRequest xfunReceieveRequest = JSONObject.parseObject(text, XfunReceieveRequest.class); //状态判断 if(xfunReceieveRequest.getHeader().getCode() == 0) { // 0的话 ,获取状态成功 XfunReceieveRequest.PayloadDTO payload = xfunReceieveRequest.getPayload(); XfunReceieveRequest.PayloadDTO.ChoicesDTO choices = payload.getChoices(); //处理得到的答案 List<MsgDTO> msgs = choices.getText(); //打上index for(int i = 0; i < msgs.size(); i++){ MsgDTO msg =msgs.get(i); msg.setIndex(historyList.size()+i); historyList.add(msg); } if(xfunReceieveRequest.getHeader().getStatus() == 2){ //表示会话来到最后一个结果 XfunReceieveRequest.PayloadDTO.UsageDTO.TextDTO text1 = payload.getUsage().getText(); System.out.println("PromptTokecn:" + text1.getPromptTokens()); System.out.println("QuestionToken:" + text1.getQuestionTokens()); System.out.println("CompletionToken:" + text1.getCompletionTokens()); System.out.println("TotalToken"+text1.getTotalTokens()); is_finished = true; // 消息整合 StringBuilder message = new StringBuilder(); for(MsgDTO msg: historyList){ message.append(msg.getContent()); } deleteHistory(); answer = message.toString(); //断开连接 // webSocket.close(3,"客户端断开连接"); } } } // 获得鉴权地址 public static String getAuthUrl(String hostUrl, String apiSecret, String apiKey) 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(); } // msgs和uid 转成 XfunSendRequest public XfunSendRequest getSendRequest(String uid, List<MsgDTO> msgs) { XfunSendRequest xfunSendRequest = new XfunSendRequest(); XfunSendRequest.Header header = new XfunSendRequest.Header(); header.setAppId(appid); header.setUid(uid); xfunSendRequest.setHeader(header); XfunSendRequest.ParameterDTO parameterDTO = new XfunSendRequest.ParameterDTO(); XfunSendRequest.ParameterDTO.ChatDTO chatDTO = new XfunSendRequest.ParameterDTO.ChatDTO(); parameterDTO.setChat(chatDTO); xfunSendRequest.setParameterDTO(parameterDTO); XfunSendRequest.PayloadDTO payloadDTO = new XfunSendRequest.PayloadDTO(); XfunSendRequest.PayloadDTO.MessageDTO messageDTO = new XfunSendRequest.PayloadDTO.MessageDTO(); messageDTO.setText(msgs); payloadDTO.setMessage(messageDTO); xfunSendRequest.setPayload(payloadDTO); return xfunSendRequest; } /** * 发送信息 */ public XfunListener sendMsg(String uid, List<MsgDTO> msgs, XfunListener webSocketListener) throws Exception { // 获取鉴权url String url = getAuthUrl(hostUrl,apiSecret,apiKey); //建立请求 OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Request request = new Request.Builder().url(url).build(); WebSocket webSocket = okHttpClient.newWebSocket(request, webSocketListener); XfunSendRequest xfunSendRequest = this.getSendRequest(uid, msgs); System.out.println("params:" + JSONObject.toJSONString(xfunSendRequest)); //发送消息 webSocket.send(JSONObject.toJSONString(xfunSendRequest)); return webSocketListener; } }
可以看到这个XfunListener不是一个Bean,所以在MainApplication中创建了这个Bean。
@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) @MapperScan("com.yupi.springbootinit.mapper") @EnableScheduling @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) public class MainApplication { @Autowired private XfunConfig xfunConfig; public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } @Bean public XfunListener getXfunListener() { return XfunListener.builder() .apiKey(xfunConfig.getApiKey()) .apiSecret(xfunConfig.getApiSecret()) .hostUrl(xfunConfig.getHostUrl()) .appid(xfunConfig.getAppid()).build(); } }
这样整个星火的AIGC就设计完成,可以随意复用在任意的项目之中,利用XfunListener即可发送消息或获取消息。
详细的数据处理,发送消息,另外写一个AI的Service做具体的处理即可。例如以下一个简单的实例:
@Service public class AiManager { @Resource private XfunListener xfunListener; // 实现发送接受消息的websockect public String testChat(String question){ //8位随机数 String random = String.valueOf((int)((Math.random()*9+1)*10000000)); List<MsgDTO> msgs = new ArrayList<>(); MsgDTO msgDTO = new MsgDTO( ); msgDTO.setRole("user"); msgDTO.setContent(question); msgDTO.setIndex(0); msgs.add(msgDTO); xfunListener.init_chat(); try { // 获取接受消息的webSoeckt XfunListener webSocket = xfunListener.sendMsg(random, msgs, xfunListener); //等待weSocked返回消息 , 这是一个笨笨的处理方法。 int cnt = 30; //最长等待30S while (!webSocket.isFinished() && cnt > 0){ Thread.sleep(1000); //休息1S cnt--; } if(cnt == 0){ return null; } String answer = webSocket.getAnswer(); //返回答案 return answer; } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { AiManager ai = new AiManager(); ai.xfunListener = XfunListener.builder() .apiKey("xxxxxxx") .apiSecret("xxxxxxx") .appid("xxxxxxx") .hostUrl("https://spark-api.xf-yun.com/v2.1/chat") .build(); System.out.println(ai.testChat("你好啊!")); } }
AiManager运行结果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。