讯飞星火大模型是科大讯飞最近开放的拥有跨领域的知识和语言理解能力的大模型,能够完成问答对话和文学创作等。由于讯飞星火大模型最近可以免费试用,开发者都可以免费申请一个QPS不超过2的账号,用来实现对平台能力的验证。本文将利用Springboot框架对星火大模型进行整合,使其能够提供简单的问答能力。

2.Springboot整合大模型

2.1 申请开发者账号

讯飞星火认知大模型需要在 讯飞星火官网 进行申请(如下图所示),点击免费试用按钮,填写相关信息即可。
在这里插入图片描述
申请成功后可以在控制台查看对应的账号信息(如下图所示),APPID、APPKey、APPSecret都是唯一的,不要轻易泄漏。
在这里插入图片描述
至此,账号申请工作完成。由于本文主要展示的是利用JAVA语言来实现对大模型的调用,因此可以在API文档中下载JAVA带上下文的调用示例(如下图所示),通过该文档中的代码可以快速进行一个简单的小测试。
在这里插入图片描述

2.2 接口文档参数分析

在讯飞星火认知大模型的对接文档中,由于结果是流式返回的(不是一次性返回),因此案例中代码通过WebSocket长连接方式与服务器建立连接并发送请求,实时接收返回结果。接口请求参数具体如下:

"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" : "你会做什么" } # 最新的一条问题,如无需上下文,可只传最新一条问题

上述请求中对应的参数解释如下:
在这里插入图片描述
在这里需要注意的是:app_id就是我们申请的APPID,uid可以区分不同用户。如果想要大模型能够根据结合上下文去进行问题解答,就要把历史问题和历史回答结果全部传回服务端。
针对上述请求,大模型的接口响应结果如下:

# 接口为流式返回,此示例为最后一次返回结果,开发者需要将接口多次返回的结果进行拼接展示
    "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

返回字段的解释如下:
在这里插入图片描述
需要注意的是:由于请求结果流式返回,因此需要根据header中的状态值status来进行判断(0代表首次返回结果,1代表中间结果,2代表最后一个结果),一次请求过程中可能会出现多个status为1的结果。

2.3 设计思路

本文设计思路如下图所示:
在这里插入图片描述
客户端通过webSocket的方式与整合大模型的Springboot进行连接建立,整合大模型的Springboot在接收到客户端请求时,会去创建与讯飞大模型服务端的webSocket长连接(每次请求会创建一个长连接,当获取到所有请求内容后,会断开长连接)。由于本文使用的账号为开发者账号(非付费模式),因此并发能力有限,本文采用加锁方式来控制请求访问。
Springboot服务与客户端的交互逻辑如下图所示:
在这里插入图片描述
Springboot服务与讯飞认知大模型的交互逻辑如下图所示:
在这里插入图片描述

2.3 项目结构

2.4 核心代码

2.4.1 pom依赖

 <properties>
        <netty.verson>4.1.45.Final</netty.verson>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.verson}</version>
        </dependency>
    </dependencies>

2.4.2 application.properties配置文件

server.port=9903
xf.config.hostUrl=https://spark-api.xf-yun.com/v2.1/chat
xf.config.appId=
xf.config.apiSecret=
xf.config.apiKey=
#最大响应时间,单位:秒
xf.config.maxResponseTime=30

2.4.3 config配置文件

@Data
@Component
@ConfigurationProperties("xf.config")
public class XFConfig {
    private String appId;
    private String apiSecret;
    private String apiKey;
    private String hostUrl;
    private Integer maxResponseTime;

2.4.4 listener文件

XFWebClient类主要用于发送请求至大模型服务端,内部有鉴权方法。

* @Author: ChengLiang * @CreateTime: 2023-10-19 11:04 * @Description: TODO * @Version: 1.0 @Slf4j @Component public class XFWebClient { @Autowired private XFConfig xfConfig; * @description: 发送请求至大模型方法 * @author: ChengLiang * @date: 2023/10/19 16:27 * @param: [用户id, 请求内容, 返回结果监听器listener] * @return: okhttp3.WebSocket public WebSocket sendMsg(String uid, List<RoleContent> questions, WebSocketListener listener) { // 获取鉴权url String authUrl = null; try { authUrl = getAuthUrl(xfConfig.getHostUrl(), xfConfig.getApiKey(), xfConfig.getApiSecret()); } catch (Exception e) { log.error("鉴权失败:{}", e); return null; // 鉴权方法生成失败,直接返回 null OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); // 将 https/http 连接替换为 ws/wss 连接 String url = authUrl.replace("http://", "ws://").replace("https://", "wss://"); Request request = new Request.Builder().url(url).build(); // 建立 wss 连接 WebSocket webSocket = okHttpClient.newWebSocket(request, listener); // 组装请求参数 JSONObject requestDTO = createRequestParams(uid, questions); // 发送请求 webSocket.send(JSONObject.toJSONString(requestDTO)); return webSocket; * @description: 鉴权方法 * @author: ChengLiang * @date: 2023/10/19 16:25 * @param: [讯飞大模型请求地址, apiKey, apiSecret] * @return: java.lang.String 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(); * @description: 请求参数组装方法 * @author: ChengLiang * @date: 2023/10/19 16:26 * @param: [用户id, 请求内容] * @return: com.alibaba.fastjson.JSONObject public JSONObject createRequestParams(String uid, List<RoleContent> questions) { JSONObject requestJson = new JSONObject(); // header参数 JSONObject header = new JSONObject(); header.put("app_id", xfConfig.getAppId()); header.put("uid", uid); // parameter参数 JSONObject parameter = new JSONObject(); JSONObject chat = new JSONObject(); chat.put("domain", "generalv2"); chat.put("temperature", 0.5); chat.put("max_tokens", 4096); parameter.put("chat", chat); // payload参数 JSONObject payload = new JSONObject(); JSONObject message = new JSONObject(); JSONArray jsonArray = new JSONArray(); jsonArray.addAll(questions); message.put("text", jsonArray); payload.put("message", message); requestJson.put("header", header); requestJson.put("parameter", parameter); requestJson.put("payload", payload); return requestJson;

XFWebSocketListener 类主要功能是与星火认知大模型建立webSocket连接,核心代码如下:

* @Author: ChengLiang * @CreateTime: 2023-10-18 10:17 * @Description: TODO * @Version: 1.0 @Slf4j public class XFWebSocketListener extends WebSocketListener { //断开websocket标志位 private boolean wsCloseFlag = false; //语句组装buffer,将大模型返回结果全部接收,在组装成一句话返回 private StringBuilder answer = new StringBuilder(); public String getAnswer() { return answer.toString(); public boolean isWsCloseFlag() { return wsCloseFlag; @Override public void onOpen(WebSocket webSocket, Response response) { super.onOpen(webSocket, response); log.info("大模型服务器连接成功!"); @Override public void onMessage(WebSocket webSocket, String text) { super.onMessage(webSocket, text); JsonParse myJsonParse = JSON.parseObject(text, JsonParse.class); log.info("myJsonParse:{}", JSON.toJSONString(myJsonParse)); if (myJsonParse.getHeader().getCode() != 0) { log.error("发生错误,错误信息为:{}", JSON.toJSONString(myJsonParse.getHeader())); this.answer.append("大模型响应异常,请联系管理员"); // 关闭连接标识 wsCloseFlag = true; return; List<Text> textList = myJsonParse.getPayload().getChoices().getText(); for (Text temp : textList) { log.info("返回结果信息为:【{}】", JSON.toJSONString(temp)); this.answer.append(temp.getContent()); log.info("result:{}", this.answer.toString()); if (myJsonParse.getHeader().getStatus() == 2) { wsCloseFlag = true; //todo 将问答信息入库进行记录,可自行实现 @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); try { if (null != response) { int code = response.code(); log.error("onFailure body:{}", response.body().string()); if (101 != code) { log.error("讯飞星火大模型连接异常"); } catch (IOException e) { log.error("IO异常:{}", e);

2.4.5 netty文件

NettyServer主要是用来监听指定端口,接收客户端的webSocket请求。

@Slf4j
@Component
public class NettyServer {
     * webSocket协议名
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";
     * 端口号
    @Value("${webSocket.netty.port:62632}")
    private int port;
     * webSocket路径
    @Value("${webSocket.netty.path:/webSocket}")
    private String webSocketPath;
    @Autowired
    private WebSocketHandler webSocketHandler;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
     * @throws InterruptedException
    private void start() throws InterruptedException {
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(port));
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 流水线管理通道中的处理程序(Handler),用来处理业务
                // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
        1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
        2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
                ch.pipeline().addLast(new HttpObjectAggregator(8192));
        1、对应webSocket,它的数据是以帧(frame)的形式传递
        2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
        3、核心功能是将http协议升级为ws协议,保持长连接
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
                // 自定义的handler,处理业务逻辑
                ch.pipeline().addLast(webSocketHandler);
        });
        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
     * 释放资源
     * @throws InterruptedException
    @PreDestroy
    public void destroy() throws InterruptedException {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().sync();
        if (workGroup != null) {
            workGroup.shutdownGracefully().sync();
    @PostConstruct()
    public void init() {
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(() -> {
            try {
                start();
                log.info("消息推送线程开启!");
            } catch (InterruptedException e) {
                e.printStackTrace();
        }).start();

WebSocketHandler主要用于接收客户端发送的消息,并返回消息。

* @Author: ChengLiang * @CreateTime: 2023-10-17 15:14 * @Description: TODO * @Version: 1.0 @Slf4j @Component @ChannelHandler.Sharable public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Autowired private PushService pushService; @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { log.info("handlerAdded被调用,{}", JSON.toJSONString(ctx)); //todo 添加校验功能,校验合法后添加到group中 // 添加到channelGroup 通道组 NettyGroup.getChannelGroup().add(ctx.channel()); @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { log.info("服务器收到消息:{}", msg.text()); // 获取用户ID,关联channel JSONObject jsonObject = JSON.parseObject(msg.text()); String channelId = jsonObject.getString("uid"); // 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户ID AttributeKey<String> key = AttributeKey.valueOf("userId"); //String channelId = CharUtil.generateStr(uid); NettyGroup.getUserChannelMap().put(channelId, ctx.channel()); boolean containsKey = NettyGroup.getUserChannelMap().containsKey(channelId); //通道已存在,请求信息返回 if (containsKey) { //接收消息格式{"uid":"123456","text":"中华人民共和国成立时间"} String text = jsonObject.getString("text"); //请求大模型服务器,获取结果 ResultBean resultBean = pushService.pushMessageToXFServer(channelId, text); String data = (String) resultBean.getData(); pushService.pushToOne(channelId, JSON.toJSONString(data)); } else { ctx.channel().attr(key).setIfAbsent(channelId); log.info("连接通道id:{}", channelId); // 回复消息 ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(ResultBean.success(channelId)))); @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { log.info("handlerRemoved被调用,{}", JSON.toJSONString(ctx)); // 删除通道 NettyGroup.getChannelGroup().remove(ctx.channel()); removeUserId(ctx); @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { log.info("通道异常:{}", cause.getMessage()); // 删除通道 NettyGroup.getChannelGroup().remove(ctx.channel()); removeUserId(ctx); ctx.close(); private void removeUserId(ChannelHandlerContext ctx) { AttributeKey<String> key = AttributeKey.valueOf("userId"); String userId = ctx.channel().attr(key).get(); NettyGroup.getUserChannelMap().remove(userId);

2.4.6 service文件

PushServiceImpl 主要用于发送请求至讯飞大模型后台获取返回结果,以及根据指定通道发送信息至用户。

* @Author: ChengLiang * @CreateTime: 2023-10-17 15:58 * @Description: TODO * @Version: 1.0 @Slf4j @Service public class PushServiceImpl implements PushService { @Autowired private XFConfig xfConfig; @Autowired private XFWebClient xfWebClient; @Override public void pushToOne(String uid, String text) { if (StringUtils.isEmpty(uid) || StringUtils.isEmpty(text)) { log.error("uid或text均不能为空"); throw new RuntimeException("uid或text均不能为空"); ConcurrentHashMap<String, Channel> userChannelMap = NettyGroup.getUserChannelMap(); for (String channelId : userChannelMap.keySet()) { if (channelId.equals(uid)) { Channel channel = userChannelMap.get(channelId); if (channel != null) { ResultBean success = ResultBean.success(text); channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(success))); log.info("信息发送成功:{}", JSON.toJSONString(success)); } else { log.error("该id对于channelId不存在!"); return; log.error("该用户不存在!"); @Override public void pushToAll(String text) { String trim = text.trim(); ResultBean success = ResultBean.success(trim); NettyGroup.getChannelGroup().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(success))); log.info("信息推送成功:{}", JSON.toJSONString(success)); //测试账号只有2个并发,此处只使用一个,若是生产环境允许多个并发,可以采用分布式锁 @Override public synchronized ResultBean pushMessageToXFServer(String uid, String text) { RoleContent userRoleContent = RoleContent.createUserRoleContent(text); ArrayList<RoleContent> questions = new ArrayList<>(); questions.add(userRoleContent); XFWebSocketListener xfWebSocketListener = new XFWebSocketListener(); WebSocket webSocket = xfWebClient.sendMsg(uid, questions, xfWebSocketListener); if (webSocket == null) { log.error("webSocket连接异常"); ResultBean.fail("请求异常,请联系管理员"); try { int count = 0; //参考代码中休眠200ms,若配置了maxResponseTime,若指定时间内未返回,则返回请求失败至前端 int maxCount = xfConfig.getMaxResponseTime() * 5; while (count <= maxCount) { Thread.sleep(200); if (xfWebSocketListener.isWsCloseFlag()) { break; count++; if (count > maxCount) { return ResultBean.fail("响应超时,请联系相关人员"); return ResultBean.success(xfWebSocketListener.getAnswer()); } catch (Exception e) { log.error("请求异常:{}", e); } finally { webSocket.close(1000, ""); return ResultBean.success("");

所有代码可参考附录进行获取。

2.5 测试结果

1.本文代码主要用于测试,若考虑并发及性能,需要在上述代码上进行优化;
2.讯飞星火认知大模型对于日常简单问题的问答效率较高,对诗词表达欠佳;
3.在本文代码中,部分代码仍可以优化,后续可以将此模块单独抽象成一个springboot-starter,引入即可使用。

4.参考文献

1.https://www.xfyun.cn/doc/spark/Web.html
2.https://console.xfyun.cn/services/bm2

https://gitee.com/Marinc/nacos/tree/master/xunfei-bigModel

讯飞星火大模型是科大讯飞最近开放的拥有跨领域的知识和语言理解能力的大模型,能够完成问答对话和文学创作等。由于讯飞星火大模型最近可以免费试用,开发者都可以免费申请一个QPS不超过2的账号,用来实现对平台能力的验证。本文将利用Springboot框架对星火大模型进行整合,使其能够提供简单的问答能力。
原定5月6日公布的星火认知模型提前内测了! 第一时间申请了内测,搜集了办公室小伙伴们最感兴趣的问题后,我们第一波测试了星火认知模型的基础能力。 个人数据是否会被用于模型训练?星火和ChatGPT谁更厉害?大模型的创作能力水平如何?它对人类提问的理解到了什么程度?等关注度较高的问题,在本文中都能找到答案! 接下来我们就一起看看星火认知模型的能力究竟如何吧~ 登陆后界面显示还是很简单明了的,也列举了一些问题方便第一次使用的用户理解和提问。 当同时对星火和ChatGPT提出“谁更厉害”的问题时,星火的回答显得更加“谨慎”,表示各有各的优势,而ChatGPT的回答则“谦虚”的多。 下面这两段是大模型自己写的介绍自己能力的文字,你能猜出哪段是星火写的,哪段是ChatGPT写的吗?回答1 作为一个大型语言模型,我具有强大的自然语言处理能力,可以理解和生成人类语言。我可以进行文本分析、语义理解、语音合成、智能问答、自然语言生成等多种任务,能够快速地处理海量的文本数据,为用户提供高质量、个性化的语言交互体验。我的能力不仅局限于中文,还支持多种语言,包括英语、法语、西班牙语等。同时,我
try { ProcessBuilder processBuilder = new ProcessBuilder(); //设置执行的第三方程序(命令),第一个参数是命令,之后的是参数 processBuilder.command(comm.
"百川大语言fastapi接口服务"是指使用FastAPI框架来创建一个接口服务,该服务可以调用大语言模型进行各种自然语言处理任务。FastAPI是一个现代、快速(高性能)的Web框架,用于构建API。 下面是大语言模型FastAPI接口服务的一些意义和作用: 便捷的接口调用:FastAPI框架可以帮助开发者快速地构建出RESTful风格的API接口,这样,其他的应用或服务就可以通过HTTP请求来调用大语言模型,进行文本生成、情感分析、文本摘要等任务。 异步支持:FastAPI支持异步处理请求,这意味着它可以同时处理多个请求,这对于调用计算密集型的大语言模型来说是非常重要的。 性能优秀:FastAPI使用Starlette用于web部分和Pydantic用于数据部分,使其具有出色的性能。这对于需要快速响应的语言模型服务来说非常重要。 自动生成文档:FastAPI可以自动生成API文档,这对于开发和使用API非常方便。 易于维护和扩展:
星火 Node.js SDK是科大推出的一款用于在Node.js环境下调用开放平台的语音识别、语义理解、自然语言处理等接口的软件开发工具包。 星火 Node.js SDK提供了丰富的接口,开发者可以使用它来实现语音识别、语音合成、声纹识别等功能。它支持多种语言,具有强大的性能和稳定性。 通过使用星火 Node.js SDK,我们可以极大地简化语音识别、语义理解等功能的开发过程。只需要按照官方文档提供的接口文档,调用相应的API即可实现相应的功能。SDK提供了丰富的示例代码,我们可以根据自己的需求进行修改和定制。 星火 Node.js SDK还提供了全面的技术支持,开发者在使用中遇到问题或者困惑时,可以通过提供的技术支持渠道进行咨询和求助。的技术团队会及时回应并提供解决方案,帮助开发者顺利完成项目开发。 总之,星火 Node.js SDK是一款功能强大、易于使用、受到广大开发者欢迎的开发工具包。通过它,我们可以更加轻松地实现语音识别、语义理解等功能,提升用户体验,开发出更加智能化的应用程序。