智譜清言 AI 通用大語(yǔ)言模型 ChatGLM Java SDK - Github
此項(xiàng)目是由 Java 的 JDK11 的長(zhǎng)期版本開發(fā),設(shè)備環(huán)境需要 JDK >= 11
?? 當(dāng)前 ChatGLM Java SDK 最新為 0.1.1 Beta 版本。
Java Maven Dependency (BlueChatGLM)調(diào)用
<dependency>
<groupId>top.pulselink</groupId>
<artifactId>bluechatglm</artifactId>
<version>0.1.1-Beta</version>
</dependency>
Java Gradle (BlueChatGLM)調(diào)用
implementation group: 'top.pulselink', name: 'bluechatglm', version: '0.1.1-Beta'
Java sbt (BlueChatGLM)調(diào)用
libraryDependencies += "top.pulselink" % "bluechatglm" % "0.1.1-Beta"
1. Utils 工具
1.1 NTP 網(wǎng)絡(luò)時(shí)間服務(wù)器
它通過互聯(lián)網(wǎng)或局域網(wǎng)上的時(shí)間服務(wù)器來提供高精度,高安全的時(shí)間信息,確保所有設(shè)備都使用相同的時(shí)間是關(guān)鍵的。這里的應(yīng)用是對(duì)于 JWT
驗(yàn)證使用
//獲取網(wǎng)絡(luò)時(shí)間協(xié)議服務(wù)器(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 密鑰并將其存儲(chǔ)在調(diào)用 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 保存聊天內(nèi)容文件
用戶聊天和 ChatGLM 回復(fù)將保存在chatglm_history.txt
中,聊天內(nèi)容 txt 文件將在每個(gè)會(huì)話結(jié)束時(shí)刪除。
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() { //關(guān)閉程序的時(shí)候刪除歷史聊天記錄
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
Files.deleteIfExists(Paths.get(historyFilePath));
} catch (IOException e) {
e.printStackTrace();
}
}));
}
2. 易于使用的 SDK
2.1 易于調(diào)用且使用的 Java Maven 庫(kù)
相對(duì)于很多人來說,使用這個(gè) SDK 的難度較低??。以下的三個(gè)示例是使用 Scanner 輸入你的問題,控制臺(tái)將輸出 ChatGLM 回答:
調(diào)用SSE請(qǐng)求,示例代碼如下 (目前已解決無(wú)法輸入中文等問題,可以正常使用):
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 (實(shí)例化)
chats.registerShutdownHook(); //刪除聊天的歷史文件
chats.SSEInvoke(userInput); //將你輸入的問題賦值給流式請(qǐng)求的
System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答內(nèi)容
System.out.println();
}
}
調(diào)用異步請(qǐng)求,示例代碼如下:
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 (實(shí)例化)
chats.registerShutdownHook(); //刪除聊天的歷史文件
chats.AsyncInvoke(userInput); //將你輸入的問題賦值給異步請(qǐng)求的
System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答內(nèi)容
System.out.println();
}
}
調(diào)用同步請(qǐng)求,示例代碼如下:
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 (實(shí)例化)
chats.registerShutdownHook(); //刪除聊天的歷史文件
chats.SyncInvoke(userInput); //將你輸入的問題賦值給同步請(qǐng)求的
System.out.print(chats.getResponseMessage()); //打印出 ChatGLM 的回答內(nèi)容
System.out.println();
}
}
2.2 資深開發(fā)者???????
對(duì)于資深開發(fā)者,我們會(huì)后續(xù)跟進(jìn)開發(fā)工作,目前的版本是 ChatGLM 4 的語(yǔ)言模型版本,并且已經(jīng)解決了SSE中文輸入看不懂的問題,當(dāng)然我們也希望其他的開發(fā)商為本項(xiàng)目提供技術(shù)支持! 先感謝您!
3.項(xiàng)目介紹
CustomJWT 是對(duì)于這個(gè)項(xiàng)目的自定制而寫的,后期會(huì)繼續(xù)開發(fā)更新,拓展這個(gè)項(xiàng)目
根據(jù) JWT.io 這個(gè)網(wǎng)站進(jìn)行了解以及原理的學(xué)習(xí),對(duì)于這個(gè)項(xiàng)目的JWT 驗(yàn)證,Java實(shí)現(xiàn)起來還是較容易實(shí)現(xiàn)的,其中使用的部分是 Base64Url
而不是常規(guī)的 Base64
編碼 Base64Url 使用的編輯如下:
private String encodeBase64Url(byte[] data) {
String base64url = Base64.getUrlEncoder().withoutPadding().encodeToString(data); //將輸入的內(nèi)容轉(zhuǎn)換成 Base64Url
return base64url; //返回 base64url
}
創(chuàng)建 JWT,實(shí)現(xiàn) Header 驗(yàn)證:
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;
}
驗(yàn)證 JWT 簽名部分是否與輸出的結(jié)果一致:
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);
}
請(qǐng)求調(diào)用??
在同步請(qǐng)求和SSE請(qǐng)求中使用的請(qǐng)求方式如下:
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");
}
}
并在此處調(diào)用并接收 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());
}
異步請(qǐng)求傳輸模型(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();
整體使用的是異步發(fā)送信息,這樣的好處是可以減少線程阻塞,這里的code
和status
是獲取錯(cuò)誤消息。當(dāng)你得到一個(gè)request_id
的時(shí)候,再進(jìn)行查詢
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()));
}
}
});
當(dāng)你得到需要的Task_id的時(shí)候,進(jìn)行GET請(qǐng)求查詢(部分代碼):
.....略 .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());
}
同步請(qǐng)求傳輸模型(InvokeModel:推薦使用,速度較快)
同步請(qǐng)求還算不錯(cuò),運(yùn)行的時(shí)候一般情況下都還算快,當(dāng)然同步的缺點(diǎn)就是請(qǐng)求量過大可能會(huì)阻塞線程(單線程
)
這里直接說明關(guān)于處理信息這一塊,這一塊就是解析 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());
}
總體下來,介紹本項(xiàng)目三種請(qǐng)求方式應(yīng)該還是相對(duì)簡(jiǎn)單,如果有任何問題,可以在 Issue 處發(fā)起討論??,也希望各路大神的對(duì)這個(gè)項(xiàng)目的支援!再次感謝??!
4.結(jié)語(yǔ)
感謝您打開我的項(xiàng)目,這是一個(gè)自主開發(fā) ChatGLM Java SDK 的開發(fā)項(xiàng)目,為了解決官方的 SDK 存在的問題我也在努力開發(fā)和更新這個(gè)項(xiàng)目,當(dāng)然我個(gè)人也會(huì)繼續(xù)開發(fā)這個(gè)項(xiàng)目,我也比較堅(jiān)持開源的原則,畢竟白嫖不香嘛(doge)。最后也希望越來越多的人一起參與開發(fā)?? ,最后如果喜歡的朋友,可以打開我的這個(gè) ChatGLM Java SDK 項(xiàng)目 幫我點(diǎn)個(gè) ?? ,謝謝大家看到最后!????文章來源:http://www.zghlxwxcb.cn/news/detail-828211.html
最后的最后感恩 gson 的 jar 包開發(fā)人員??????????文章來源地址http://www.zghlxwxcb.cn/news/detail-828211.html
到了這里,關(guān)于ChatGLM Java SDK:智譜 AI 通用語(yǔ)言模型 Zhipu ChatGLM Java SDK的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!