使用Java SDK接入智谱AI:ChatGLM通用语言模型实战

智谱清言 AI 通用大语言模型 ChatGLM Java SDK -
Github

此项目是由
Java

JDK11
的长期版本开发,设备环境需要
JDK >= 11


🚩 当前

ChatGLM Java SDK
最新为 0.1.1 Beta 版本。

Java Maven Dependency (BlueChatGLM)调用

<dependency>
  <groupId>top.pulselink</groupId>
  <artifactId>bluechatglm</artifactId>
  <version>0.1.1-Beta</version>
</dependency>

Java Gradle (BlueChatGLM)调用

implementation group: 'top.pulselink', name: 'bluechatglm', version: '0.1.1-Beta'

Java sbt (BlueChatGLM)调用

libraryDependencies += "top.pulselink" % "bluechatglm" % "0.1.1-Beta"

1. Utils 工具

1.1 NTP 网络时间服务器

它通过互联网或局域网上的时间服务器来提供高精度,高安全的时间信息,确保所有设备都使用相同的时间是关键的。这里的应用是对于
JWT
验证使用

//获取网络时间协议服务器(NTP Server)

    private long getNTPTime() throws IOException {
        int port = 123;
        InetAddress address = InetAddress.getByName("ntp.aliyun.com");

        try (DatagramSocket socket = new DatagramSocket()) {
            byte[] buf = new byte[48];
            buf[0] = 0x1B;
            DatagramPacket requestPacket = new DatagramPacket(buf, buf.length, address, port);
            socket.send(requestPacket);

            DatagramPacket responsePacket = new DatagramPacket(new byte[48], 48);
            socket.receive(responsePacket);

            byte[] rawData = responsePacket.getData();
            long secondsSince1900 = 0;
            for (int i = 40; i <= 43; i++) {
                secondsSince1900 = (secondsSince1900 << 8) | (rawData[i] & 0xff);
            }
            return (secondsSince1900 - 2208988800L) * 1000;
        }
    }

1.2 保存 API 密钥

保存
API
密钥并将其存储在调用
chatglm_api_key.txt
文件的本地文件中:

    private static String loadApiKey() {                    //加载 API 密钥
        try (BufferedReader reader = new BufferedReader(new FileReader(API_KEY_FILE))) {
            return reader.readLine();
        } catch (IOException e) {
            return null; // If the file doesn't exist or an error occurs, return null
        }
    }

    private static void saveApiKey(String apiKey) {           //保存 API 密钥
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(API_KEY_FILE))) {
            writer.write(apiKey);
        } catch (IOException e) {
            e.printStackTrace(); // Handle the exception according to your needs
        }
    }

1.3 保存聊天内容文件

用户聊天和
ChatGLM
回复将保存在
chatglm_history.txt
中,聊天内容
txt
文件将在每个会话结束时删除。

private void createHistoryFileIfNotExists() {                //检查是否存在文件
    Path filePath = Paths.get(historyFilePath);
    if (Files.exists(filePath)) {
        try {
            Files.delete(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    try {
        Files.createFile(filePath);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

    private void registerShutdownHook() {                      //关闭程序的时候删除历史聊天记录
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                Files.deleteIfExists(Paths.get(historyFilePath));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }));
    }


2. 易于使用的 SDK

2.1 易于调用且使用的 Java Maven 库

相对于很多人来说,使用这个
SDK
的难度较低🤩。以下的三个示例是使用
Scanner
输入你的问题,控制台将输出
ChatGLM
回答:

调用
SSE请求
,示例代码如下 (目前已解决无法输入中文等问题,可以正常使用):

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String apiKeyss = loadApiKey();                          //加载 API 密钥

        if (apiKeyss == null) {                                  //如果不存在文件或者密钥为空,则需要输入密钥
            System.out.println("Enter your API key:");
            apiKeyss = scanner.nextLine();
            saveApiKey(apiKeyss);
        }

        while (scanner.hasNext()) {
            String userInput = scanner.nextLine();

             ChatClient chats = new ChatClient(apiKeyss);      //初始 ChatClient (实例化)
             chats.registerShutdownHook();                     //删除聊天的历史文件
             chats.SSEInvoke(userInput);                    //将你输入的问题赋值给流式请求的
             System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答内容
            System.out.println();
        }
    }

调用
异步请求
,示例代码如下:

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String apiKeyss = loadApiKey();                          //加载 API 密钥

        if (apiKeyss == null) {                                  //如果不存在文件或者密钥为空,则需要输入密钥
            System.out.println("Enter your API key:");
            apiKeyss = scanner.nextLine();
            saveApiKey(apiKeyss);
        }
        while (scanner.hasNext()) {
            String userInput = scanner.nextLine();

             ChatClient chats = new ChatClient(apiKeyss);      //初始 ChatClient (实例化)
             chats.registerShutdownHook();                     //删除聊天的历史文件
             chats.AsyncInvoke(userInput);                    //将你输入的问题赋值给异步请求的
             System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答内容
            System.out.println();
        }
    }

调用
同步请求
,示例代码如下:

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String apiKeyss = loadApiKey();                          //加载 API 密钥

        if (apiKeyss == null) {                                  //如果不存在文件或者密钥为空,则需要输入密钥
            System.out.println("Enter your API key:");
            apiKeyss = scanner.nextLine();
            saveApiKey(apiKeyss);
        }
        while (scanner.hasNext()) {
            String userInput = scanner.nextLine();

             ChatClient chats = new ChatClient(apiKeyss);      //初始 ChatClient (实例化)
             chats.registerShutdownHook();                     //删除聊天的历史文件
             chats.SyncInvoke(userInput);                    //将你输入的问题赋值给同步请求的
             System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答内容
            System.out.println();
        }
    }

2.2 资深开发者👨🏼‍💻

对于资深开发者,我们会后续跟进开发工作,目前的版本是
ChatGLM 4
的语言模型版本,并且已经解决了SSE中文输入看不懂的问题,当然我们也希望其他的开发商为本项目提供技术支持! 先感谢您!


3.项目介绍

CustomJWT

是对于这个项目的自定制而写的,后期会继续开发更新,拓展这个项目

根据
JWT.io
这个网站进行了解以及原理的学习,对于这个项目的
JWT
验证,
Java
实现起来还是较容易实现的,其中使用的部分是
Base64Url
而不是常规的
Base64

编码 Base64Url
使用的编辑如下:

private String encodeBase64Url(byte[] data) {
        String base64url = Base64.getUrlEncoder().withoutPadding().encodeToString(data);  //将输入的内容转换成 Base64Url
        return base64url;             //返回 base64url
    }


创建
JWT
,实现
Header
验证:

protected String createJWT() {
        String encodedHeader = encodeBase64Url(header.getBytes());
        String encodedPayload = encodeBase64Url(payload.getBytes());
        String toSign = encodedHeader + "." + encodedPayload;

        byte[] signatureBytes = generateSignature(toSign, secret, algorithm);
        String calculatedSignature = encodeBase64Url(signatureBytes);
        return toSign + "." + calculatedSignature;
    }


验证
JWT
签名部分是否与输出的结果一致:

protected boolean verifyJWT(String jwt) {
        jwt = jwt.trim();

        String[] parts = jwt.split("\\.");
        if (parts.length != 3) {
            return false;
        }

        String encodedHeader = parts[0];
        String encodedPayload = parts[1];
        String signature = parts[2];

        String toVerify = encodedHeader + "." + encodedPayload;
        byte[] calculatedSignatureBytes = generateSignature(toVerify, secret, algorithm);
        String calculatedSignature = encodeBase64Url(calculatedSignatureBytes);

        return calculatedSignature.equals(signature);
    }
 

请求调用🌐


同步请求

SSE请求
中使用的请求方式如下:

HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(apiUrl))
                .header("Accept", "application/json")
                .header("Content-Type", "application/json;charset=UTF-8")
                .header("Authorization", "Bearer " + token)
                .POST(HttpRequest.BodyPublishers.ofString(jsonRequestBody))
                .build();

使用
POST
方法制作
jsonRequestBody
,如下文所示(同步方法的
Stream 为 false
):

String jsonRequestBody = String.format("{\"model\":\"%s\", \"messages\":[{\"role\":\"%s\",\"content\":\"%s\"},{\"role\":\"%s\",\"content\":\"%s\"}], \"stream\":false,\"temperture\":%f,\"top_p\":%f}", Language_Model, system_role, system_content, user_role, message, temp_float, top_p_float);

SSE 流式传输模型(可以正常使用!完美支持)

这里我们将使用
concurrent.Flow
方法来解决
SSE
流处理的问题:

public class SSESubscriber implements Flow.Subscriber<String> {

        private Flow.Subscription subscription;

        @Override
        public void onSubscribe(Flow.Subscription subscription) {
            this.subscription = subscription;
            subscription.request(1);
        }

        @Override
        public void onNext(String item) {
            if (item.startsWith("data: ")) {
                String jsonData = item.substring("data: ".length());
                //System.out.println("Received SSE item: " + jsonData); //Debug

                if (!jsonData.equals("[DONE]")) {
                    responseDataBody(jsonData.replaceAll("Invalid JSON format: \\[\"DONE\"\\]", ""));
                }
            }
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            System.out.println("Error in SSESubscriber: " + throwable.getMessage());
        }

        @Override
        public void onComplete() {
            //System.out.println("SSESubscriber completed");
        }
    }

并在此处调用并接收 chatglm-4 消息:

try (JsonReader jsonReader = new JsonReader(new StringReader(responseData))) {
            jsonReader.setLenient(true);
            JsonElement jsonElement = JsonParser.parseReader(jsonReader);

            if (jsonElement.isJsonObject()) {
                JsonObject jsonResponse = jsonElement.getAsJsonObject();

                if (jsonResponse.has("choices")) {
                    JsonArray choices = jsonResponse.getAsJsonArray("choices");

                    if (!choices.isEmpty()) {
                        JsonObject choice = choices.get(0).getAsJsonObject();

                        if (choice.has("delta")) {
                            JsonObject delta = choice.getAsJsonObject("delta");

                            if (delta.has("content")) {
                                String content = delta.get("content").getAsString();
                                getMessage = convertUnicodeEmojis(content);
                                getMessage = getMessage.replaceAll("\"", "")
                                        .replaceAll("\\\\n\\\\n", "\n")
                                        .replaceAll("\\\\nn", "\n")
                                        .replaceAll("\\n", "\n")
                                        .replaceAll("\\\\", "")
                                        .replaceAll("\\\\", "");

                                for (char c : getMessage.toCharArray()) {
                                    charQueue.offer(c);
                                }

                                while (!charQueue.isEmpty()) {
                                    queueResult.append(charQueue.poll());
                                }
                            }
                        }
                    }
                }
            } else {
                System.out.println("Invalid JSON format: " + jsonElement);
            }
        } catch (IOException e) {
            System.out.println("Error reading JSON: " + e.getMessage());
        }

异步请求传输模型(AsyncInvokeModel:推荐使用,速度快)

这里采用的是
HTTPRequest
方法,来接收消息:

String jsonRequestBody = String.format("{\"model\":\"%s\", \"messages\":[{\"role\":\"%s\",\"content\":\"%s\"},{\"role\":\"%s\",\"content\":\"%s\"}],\"temperture\":%f,\"top_p\":%f}",
                Language_Model, system_role, system_content, user_role, message, temp_float, top_p_float);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(apiUrl))
                .header("Accept", "application/json")
                .header("Content-Type", "application/json;charset=UTF-8")
                .header("Authorization", "Bearer " + token)
                .POST(HttpRequest.BodyPublishers.ofString(jsonRequestBody))
                .build();

整体使用的是异步发送信息,这样的好处是可以减少线程阻塞,这里的
code

status
是获取错误消息。当你得到一个
request_id
的时候,再进行查询

                    if (response.statusCode() == 200) {      //When the response value is 200, output the corresponding parameters of the interface for an asynchronous request.
                        processResponseData(response.body());
                        return CompletableFuture.completedFuture(response.body());
                    } else {
                        JsonObject errorResponse = JsonParser.parseString(response.body()).getAsJsonObject();
                        if (errorResponse.has("id") && errorResponse.has("task_status")) {
                            int code = errorResponse.get("id").getAsInt();
                            String status = errorResponse.get("task_status").getAsString();
                            throw new RuntimeException("HTTP request failure, Your request id is: " + code + ", Status: " + status);
                        } else {
                            return CompletableFuture.failedFuture(new RuntimeException("HTTP request failure, Code: " + response.statusCode()));
                        }
                    }
                });

当你得到需要的
Task_id
的时候,进行
GET
请求查询(部分代码):

                .....略 .sendAsync(HttpRequest.newBuilder()
                        .uri(URI.create(checkUrl + ID))
                        .header("Accept", "application/json")
                        .header("Content-Type", "application/json;charset=UTF-8")
                        .header("Authorization", "Bearer " + token)
                        .GET()
                        .build(), HttpResponse.BodyHandlers.ofString())
                .thenCompose(response -> {
                    if (response.statusCode() == 200) {
                        return CompletableFuture.completedFuture(response.body());
                    } else {
                        return CompletableFuture.failedFuture(new RuntimeException("HTTP request failure, Code: " + response.statusCode()));
                    }
                });

最后通过
JSON
的提取,提取代码示例为:

try {
            JsonObject jsonResponse = JsonParser.parseString(responseData).getAsJsonObject();

            if (jsonResponse.has("choices")) {
                JsonArray choices = jsonResponse.getAsJsonArray("choices");

                if (!choices.isEmpty()) {
                    JsonObject choice = choices.get(0).getAsJsonObject();

                    if (choice.has("message")) {
                        JsonObject message = choice.getAsJsonObject("message");

                        if (message.has("content")) {
                            String content = message.get("content").getAsString();
                            getMessage = convertUnicodeEmojis(content);
                            getMessage = getMessage.replaceAll("\"", "")
                                    .replaceAll("\\\\n\\\\n", "\n")
                                    .replaceAll("\\\\nn", "\n")
                                    .replaceAll("\\n", "\n")
                                    .replaceAll("\\\\", "")
                                    .replaceAll("\\\\", "");
                        }
                    }
                }
            }
        } catch (JsonSyntaxException e) {
            System.out.println("Error processing task status: " + e.getMessage());
        }

同步请求传输模型(InvokeModel:推荐使用,速度较快)

同步请求
还算不错,运行的时候一般情况下都还算快,当然同步的缺点就是请求量过大可能会阻塞线程(
单线程

这里直接说明关于处理信息这一块,这一块就是解析
JSON
也没有其他的东西了,示例代码:

try {
            JsonObject jsonResponse = JsonParser.parseString(responseData).getAsJsonObject();

            if (jsonResponse.has("choices")) {
                JsonArray choices = jsonResponse.getAsJsonArray("choices");

                if (!choices.isEmpty()) {
                    JsonObject choice = choices.get(0).getAsJsonObject();

                    if (choice.has("message")) {
                        JsonObject message = choice.getAsJsonObject("message");

                        if (message.has("content")) {
                            String content = message.get("content").getAsString();
                            getMessage = convertUnicodeEmojis(content);
                            getMessage = getMessage.replaceAll("\"", "")
                                    .replaceAll("\\\\n\\\\n", "\n")
                                    .replaceAll("\\\\nn", "\n")
                                    .replaceAll("\\n", "\n")
                                    .replaceAll("\\\\", "")
                                    .replaceAll("\\\\", "");
                        }
                    }
                }
            }
        } catch (JsonSyntaxException e) {
            System.out.println("Error processing task status: " + e.getMessage());
        }

总体下来,介绍本项目三种请求方式应该还是相对简单,如果有任何问题,可以在
Issue
处发起讨论🥳,也希望各路大神的对这个项目的支援!再次感谢🎉!


4.结语

感谢您打开我的项目,这是一个自主开发
ChatGLM Java SDK
的开发项目,为了解决官方的 SDK 存在的问题我也在努力开发和更新这个项目,当然我个人也会继续开发这个项目,我也比较坚持开源的原则,毕竟白嫖不香嘛(doge)。最后也希望越来越多的人一起参与开发🚀 ,最后如果喜欢的朋友,可以打开我的这个
ChatGLM Java SDK 项目
帮我点个 ⭐️ ,谢谢大家看到最后!😆👏


最后的最后感恩 gson 的 jar 包开发人员
👩‍💻👨‍💻

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
通过Java语言与海康SDK进行视频浏览接入是可行的。海康SDK是海康威视公司提供的一套用于处理视频监控设备的开发工具包,它提供了一系列的接口和方法,用于实现视频浏览、云台控制、录像回放等功能。 首先,我们需要在Java项目中引入海康SDK的相关依赖,并配置好SDK的相关信息,如IP地址、端口号、用户名、密码等。然后,我们可以通过SDK提供的接口与海康设备进行通信。 例如,要实现视频浏览功能,我们可以通过SDK提供的接口打开视频流,然后将视频流渲染到Java应用程序中的界面上。可以通过调用openSDK的方法来进行设备的登录验证,然后使用openLive接口打开实时预览,传入相应的设备ID和通道号。在回调函数中,获取到视频流数据后,可以将其解码并在界面上进行展示。 同时,通过海康SDK,我们还可以实现其他功能,如云台控制(上下左右移动、焦距调节等)、录像回放等。可以通过调用相应的接口,传入参数实现这些功能。 需要注意的是,使用海康SDK进行视频浏览接入,需要对SDK使用方法和相关设备进行了解,并进行相关的开发与调试。此外,还需要确保网络连接正常,设备的IP地址、端口号、用户名、密码等配置信息正确无误。 总结来说,利用Java语言通过海康SDK进行视频浏览接入是可行的,可以通过调用SDK提供的接口实现视频流的打开和渲染,同时还可以实现云台控制、录像回放等功能。这样,我们可以在Java应用程序中方便地浏览和操作海康监控设备的视频。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值