星火大模型AI接口Spring中项目中使用【星火、AIGC】

星火大模型AI接口使用

讯飞的星火大模型是有免费版本赠送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例子。

通用鉴权URL的生成

官方给的,直接拿来用。

/ 获得鉴权地址
    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。

请添加图片描述

1. XfunConfig
/**
 *   星火大模型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
3. DTO – Xfun

看官方文档中对请求参数以及对各个字段的解释。

# 参数构造示例如下
{
        "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;
    }

可以看到整个发送消息的过程:

  1. 建立请求,根据请求创建WebSoeckt
  2. 利用WebSocket发出XfunSendRequest

接受消息写在XfunListener的OnMessage重载函数中,根据XfunReceiveRequest来处理数据;

3. XfunListener

具体代码如下:

最关键的就是两个函数:

  • OnMessage: 接受消息,处理消息
  • SendMsg: 发送消息
@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运行结果

请添加图片描述

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年12月11日
下一篇 2023年12月11日

相关推荐