admin管理员组文章数量:1621125
乐鑫 ESP32-ChatGLM 大模型自定义对话
使用乐鑫 ESP32 平台来享受单片机上的开放的大语言模型 ChatGLM !
ESP32 烧入 ChatGLM-4 项目
使用官方的异步调用方式来请求其 API。 ChatGLM API 可从以下网站获取:
API 获取地址:https://open.bigmodel/
主页如下(官方可能会进行更新或更改):
😲 请按照以下步骤操作:
步骤 1
下载 Arduino IDE 并安装。 打开 Arduino IDE
并找到 File -> Perference。
请使用 ESP32 官方的开发板管理器地址。
步骤2
下载项目并下载库。 (内置库的话即可直接使用,否则需要找到相应的资源下载)
这些是该项目的库:
#include <Arduino.h> //内置
#include <CustomJWT.h> //从开发平台库中查找
#include <ESPAsyncWebServer.h> // 从 https://github/me-no-dev/ESPAsyncWebServer 获取
#include <ArduinoJson.h> //从开发平台库中查找
#include <WiFiClientSecure.h> //内置
#include <WiFiUdp.h> //内置
#include <time.h> //内置
#include <HTTPClient.h> //从开发平台库中查找
步骤 3 🤨
找到库文件夹并找到 CustomJWT
文件夹。 (我也上传了 CustomJWT.h
文件,直接替换成我 CustomJWT ☝️)
Windows 电脑默认应该是在:C:\Users\xxxx\Documents\Arduino\libraries\CustomJWT\src
CustomJWT.h 文件中的下面的代码不正确:
sprintf(headerJSON, "{\"alg\": \"%s\",\"typ\":\"%s\",\"sign_type\":\"%s\"}", alg、typ、sign_type);
错误: 这是 “alg”: “%s” 之间的问题,在 “alg”: 和 “%s” 之间有一个空格! 请删除它! 如果没有,它将显示不同的 Base64 编码。
修复 CustomJWT.h
文件中的代码,如下所示:
sprintf(headerJSON, "{\"alg\":\"%s\",\"typ\":\"%s\",\"sign_type\":\"%s\"}", alg、typ、sign_type);
步骤4
将 ChatGLM.ino 和 index.h 项目放入您自己的项目文件夹中,文件夹名称为:ChatGLM(⚠️这里文件夹名称需要和 *.ino 文件保持一致)。你可以更改您的个人 请求API 和 WiFI ,甚至可以更改 system_role 的角色内容以及 NTP 网络时间服务器,甚至您可以为index.h 文件
步骤 5
通过串口和电脑的 USB 连接 ESP32 模块。 请选择正确的 ESP32 板,我的是 ESP32-S3 的单片机
终于可以愉快烧录你的 ESP32 设备了! 😄🥇
📔 代码详情
Github ESP32-ChatGLM 详细代码
Gitee ESP32-ChatGLM 详细代码
主程序:
#include <Arduino.h> //build in
#include <CustomJWT.h> //find from library
#include <ESPAsyncWebServer.h> // Get from https://github/me-no-dev/ESPAsyncWebServer
#include <ArduinoJson.h> //find from library
#include <WiFiClientSecure.h> //build in
#include <WiFiUdp.h> //build in
#include <HTTPClient.h> //find from library
#include <NTPClient.h> //find from library
#include "chatconfig.h"
#include "index.h"
#include "async_invoke.h"
#include "sync_invoke.h"
char header[50];
char payload[500];
char signature[100];
char out[500];
char jsonString[500];
char idCharArray[150];
char secretCharArray[100];
String invokeChoice = "Async_invoke";
//String invokeChoice = "Sync_invoke";
String LLM_Model = "glm-4";
String JsonToken, responseMessage, userMessage;
HTTPClient http, http_id;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.aliyun", 0, 30000);
AsyncWebServer server(9898); // Web Page IP:9898
DynamicJsonDocument doc(20480);
long long int timestamp_generation() {
if (wifiConnect) {
timeClient.update();
long long int timestamp_generation = timeClient.getEpochTime() * 1000ULL; //get Timestamp
return timestamp_generation;
}
}
void splitApiKey(const char *apikey) {
const char *delimiter = strchr(apikey, '.');
if (delimiter != NULL) {
size_t idLength = delimiter - apikey;
if (idLength < sizeof(idCharArray)) {
strncpy(idCharArray, apikey, idLength);
idCharArray[idLength] = '\0';
strcpy(secretCharArray, delimiter + 1);
snprintf(jsonString, sizeof(jsonString), "{\"api_key\":\"%s\",\"exp\":%lld,\"timestamp\":%lld}", idCharArray, static_cast<long long>(timestamp_generation()) * 3, static_cast<long long>(timestamp_generation()));
CustomJWT jwt(secretCharArray, header, sizeof(header), payload, sizeof(payload), signature, sizeof(signature), out, sizeof(out));
jwt.encodeJWT(jsonString);
JsonToken = jwt.out;
Serial.println(JsonToken); //Debug
jwt.clear();
} else {
Serial.println("ID part of API key is not valid.");
}
} else {
Serial.println("Invalid API key format.");
}
}
int tryWiFiConnection(const char *ssid, const char *identity, const char *password, int networkNumber) {
Serial.printf("Connecting to WiFi_%d...\n", networkNumber);
int attempts = 0;
const int maxAttempts = 10;
while (attempts < maxAttempts) {
if (strcmp(identity, "none") == 0) {
WiFi.begin(ssid, password);
} else {
WiFi.begin(ssid, WPA2_AUTH_PEAP, identity, identity, password); //WPA2_ENTERPRISE | Eduroam calling
}
int connectionAttempt = 0;
while (connectionAttempt < 4) {
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("Connected to WiFi_%d.\n", networkNumber);
return networkNumber;
}
delay(1000);
connectionAttempt++;
}
Serial.printf("Retry WiFi%d connection...\n", networkNumber);
WiFi.disconnect();
attempts++;
}
Serial.printf("WiFi%d connection failed.\n", networkNumber);
return -1;
}
void setup() {
Serial.begin(115200);
delay(100);
for (int networkNumber = 0; networkNumber < 3 && !wifiConnect; networkNumber++) {
Serial.printf("Connecting to WiFi_%d...\n", networkNumber + 1);
int successfulConnection = tryWiFiConnection(ssidList[networkNumber], identityList[networkNumber], passwordList[networkNumber], networkNumber + 1);
if (successfulConnection != -1 && !wifiConnect) {
Serial.printf("Connected to WiFi_%d\n", successfulConnection);
Serial.print("The Internet IP: ");
Serial.println(WiFi.localIP());
if (timestamp_generation > 0) {
splitApiKey(setApiKey);
if (invokeChoice == "Async_invoke") {
asyncMessage(server, http_id, doc, JsonToken, responseMessage, userMessage, checkEmpty); //Async
} else if (invokeChoice == "Sync_invoke") {
syncMessage(server, responseMessage, userMessage, checkEmpty); //Sync
}
wifiConnect = true;
} else {
Serial.println(F("Failed to obtain Beijing time"));
}
}
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Failed to connect to any WiFi network.");
}
}
void loop() {
if (wifiConnect && WiFi.status() == WL_CONNECTED) {
if (invokeChoice == "Async_invoke") {
loopingSetting(http, LLM_Model, doc, JsonToken, userMessage, invokeChoice, checkEmpty); //Async
} else if (invokeChoice == "Sync_invoke") {
loopingSetting(http, LLM_Model, JsonToken, responseMessage, userMessage, invokeChoice, checkEmpty); //Sync
}
}
delay(100);
}
异步请求调用:
void asyncMessage(AsyncWebServer &server, HTTPClient &http_id, DynamicJsonDocument &doc, String &JsonToken, String &responseMessage, String &userMessage, bool &checkEmpty) {
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", html);
});
server.begin();
server.on("/send", HTTP_GET, [&responseMessage, &userMessage, &checkEmpty](AsyncWebServerRequest *request) {
responseMessage.clear();
userMessage = request->getParam("message")->value();
if (userMessage.length() > 0) {
checkEmpty = true;
Serial.print(F("My Question is: "));
Serial.print(userMessage);
Serial.println();
}
request->send(200, "text/plain", "Message sent to Serial");
});
server.on("/receiveTextMessage", HTTP_GET, [&http_id, &doc, &JsonToken, &responseMessage](AsyncWebServerRequest *request) {
const char *getMessage = doc["id"];
String web_search_id = "https://open.bigmodel/api/paas/v4/async-result/" + String(getMessage); //GET Method(+below)
http_id.begin(web_search_id);
http_id.addHeader("Accept", "application/json");
http_id.addHeader("Content-Type", "application/json; charset=UTF-8");
http_id.addHeader("Authorization", JsonToken);
int httpResponseIDCode = http_id.GET();
if (httpResponseIDCode > 0) {
responseMessage = http_id.getString();
} else {
responseMessage = "Error: External API request failed!";
}
request->send(200, "text/html", responseMessage);
});
}
void loopingSetting(HTTPClient &http, String &LLM, DynamicJsonDocument &doc, String &JsonToken, String &userMessage, String &invokeChoice, bool &checkEmpty) {
const char *async_web_hook = "https://open.bigmodel/api/paas/v4/async/chat/completions"; //New ChatGLM4 async
if (invokeChoice == "Async_invoke") {
if (checkEmpty) {
int maxRetries = 5; // 最大重试次数
int retryCount = 0;
while (retryCount < maxRetries) {
http.begin(async_web_hook);
http.addHeader("Accept", "application/json");
http.addHeader("Content-Type", "application/json; charset=UTF-8");
http.addHeader("Authorization", JsonToken);
String payloadMessage = "{\"model\":\"" + LLM + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + String(system_role) + "\"},{\"role\":\"user\",\"content\":\"" + userMessage + "\"}]}";
int httpResponseCode = http.POST(payloadMessage);
//Serial.println(httpResponseCode); //Debug
if (httpResponseCode > 0) {
String messages = http.getString();
//Serial.println(messages); //debug
DeserializationError error = deserializeJson(doc, messages);
if (error) {
Serial.print(F("JSON parsing failed: "));
Serial.println(F(error.c_str()));
}
break;
} else if (httpResponseCode == -2) {
retryCount++;
delay(500); // 可以根据需求调整重试间隔
}
}
}
http.end();
checkEmpty = false;
}
}
同步请求调用:
void syncMessage(AsyncWebServer &server, String &responseMessage, String &userMessage, bool &checkEmpty) {
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", html);
});
server.begin();
server.on("/send", HTTP_GET, [&responseMessage, &userMessage, &checkEmpty](AsyncWebServerRequest *request) {
responseMessage.clear();
userMessage = request->getParam("message")->value();
if (userMessage.length() > 0) {
checkEmpty = true;
Serial.print(F("My Question is: "));
Serial.print(userMessage);
Serial.println();
request->send(200, "text/plain", "Message sent to Serial");
}
});
server.on("/receiveTextMessage", HTTP_GET, [&responseMessage](AsyncWebServerRequest *request) {
if (responseMessage.length() > 0) {
request->send(200, "text/html", responseMessage);
} else {
request->send(400, "text/html", "Message error! Please Check Token or Others!");
}
});
}
void loopingSetting(HTTPClient &http, String &LLM, String &JsonToken, String &responseMessage, String &userMessage, String &invokeChoice, bool &checkEmpty) {
const char *sync_web_hook = "https://open.bigmodel/api/paas/v4/chat/completions";
if (invokeChoice == "Sync_invoke") {
if (checkEmpty) {
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
http.begin(sync_web_hook);
http.setTimeout(30000);
http.addHeader("Accept", "application/json");
http.addHeader("Content-Type", "application/json; charset=UTF-8");
http.addHeader("Authorization", JsonToken);
String payloadMessage = "{\"model\":\"" + LLM + "\", \"messages\":[{\"role\":\"system\",\"content\":\"" + String(system_role) + "\"},{\"role\":\"user\",\"content\":\"" + userMessage + "\"}],\"stream\":false}";
int httpResponseCode = http.POST(payloadMessage);
responseMessage.clear();
if (httpResponseCode > 0) {
responseMessage = http.getString();
//Serial.println(responseMessage); //debug
break;
} else {
retryCount++;
Serial.print(F("HTTP POST request failed, error: "));
Serial.println(httpResponseCode);
delay(500);
}
}
}
http.end();
checkEmpty = false;
}
}
✏️ 未来新增功能
- 可能会添加流式请求(SSE)的调用方法
- 添加上下文的理解功能
- 修改已知或未知 BUG
- 优化代码,减少性能损耗
其他问题:
目前最新支持 ChatGLM-4 的,默认集成了 AI 角色扮演的内容,这一个版本去掉了 0.0.2 版本的SSE请求。由于官方给的文档是大范围修改,将流式传输和同步请求的基本上合二为一,这里我就留下了同步请求,如果您还有其他问题,可以在 Github / Gitee 发起 讨论(Discussion) 或者发起 Issue, 同时也可以 fork 本项目,感谢对本项目的支持,感恩 ☺️!
版权声明:本文标题:乐鑫 ESP32 ChatGLM-4 大模型自定义对话 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1725588627a1031472.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论