ESP32 gọi API mô hình ngôn ngữ qua HTTPS: gửi câu hỏi kèm số đo cảm biến, đọc JSON, giữ an toàn

ESP32 hỏi mô hình ngôn ngữ lớn qua API kiểu Chat Completions: luồng HTTPS, JSON gửi và nhận, chương trình dùng ArduinoJson, kiểm tra trước khi điều khiển, bảo vệ khóa API và chi phí.

Mô hình ngôn ngữ lớn (loại trả lời câu hỏi, viết văn bản) cần máy chủ hàng chục GB bộ nhớ, không thể chạy trên ESP32. Nhưng ESP32 có Wi-Fi, nên có thể gửi câu hỏi tới API của một dịch vụ mô hình ngôn ngữ và đọc câu trả lời, kèm theo số đo cảm biến để câu trả lời sát với tình huống thật. Bài này giải thích luồng một lần hỏi, cấu trúc JSON gửi và nhận, chương trình ESP32 dùng HTTPS và ArduinoJson, cách giữ an toàn khi dùng câu trả lời để điều khiển thiết bị, bảo vệ khóa API và kiểm soát chi phí.

1. ESP32 hỏi mô hình ngôn ngữ như thế nào

Luồng: gõ câu hỏi ở Serial Monitor, ESP32 ghép câu hỏi và số đo, gửi JSON tới máy chủ API mô hình ngôn ngữ lớn, nhận JSON trả lời, in ra Serial hoặc OLED; mỗi lần hỏi gồm kết nối, bắt tay TLS, gửi khóa API và JSON, chờ mô hình sinh chữ, đọc JSON
Mô hình chạy trên máy chủ; ESP32 chỉ gửi câu hỏi và đọc câu trả lời.

Nhiều nhà cung cấp mô hình ngôn ngữ hỗ trợ cùng một kiểu API gọi là Chat Completions: gửi một danh sách tin nhắn dạng JSON qua HTTPS, nhận lại câu trả lời dạng JSON. Chương trình trong bài viết theo kiểu này nên dùng được với nhiều nhà cung cấp, chỉ cần đổi địa chỉ API, tên mô hình và khóa API.

2. Chuẩn bị

  • Một ESP32 DevKit V1, dây USB, mạng Wi-Fi 2,4 GHz.
  • Tài khoản ở một nhà cung cấp API mô hình ngôn ngữ có hỗ trợ kiểu Chat Completions: lấy địa chỉ API, tên mô hình và khóa API. Chi phí tính theo số token (đơn vị chữ) của cả câu hỏi lẫn câu trả lời; xem bảng giá, hạn mức của nhà cung cấp.
  • Thư viện ArduinoJson bản 7 (cài trong Library Manager của Arduino IDE).
  • Tùy chọn: một cảm biến analog hoặc biến trở ở GPIO34 để có số đo gửi kèm.

3. JSON gửi đi và nhận về

Bên trái: JSON yêu cầu gồm model, max_tokens 200, messages có tin system chứa hướng dẫn và số đo 1834 mV, tin user chứa câu hỏi nhiệt độ 31 độ có nên bật quạt; bên phải: phản hồi rút gọn có choices, message role assistant, content, usage số token
Đúng các trường chương trình gửi; nội dung là ví dụ.
  • system: hướng dẫn chung cho mô hình (trả lời ngắn, bằng tiếng Việt) kèm bối cảnh là số đo cảm biến.
  • user: câu hỏi.
  • max_tokens: giới hạn độ dài câu trả lời; vừa giữ chi phí, vừa để ESP32 không phải xử lý chuỗi quá dài.
  • Khóa API gửi trong tiêu đề Authorization: Bearer …, không nằm trong JSON.

4. Chương trình

Điền tên Wi-Fi, mật khẩu, địa chỉ API, tên mô hình và khóa API vào các chỗ giữ chỗ. Mở Serial Monitor ở 115200, chọn kiểu kết thúc dòng "New Line", gõ câu hỏi rồi Enter.

// PYWORLD.VN - ESP32 gọi API mô hình ngôn ngữ qua HTTPS (định dạng Chat Completions): hỏi từ Serial, kèm số đo cảm biến
// PYWORLD.VN - Địa chỉ API, tên mô hình, khóa API là chữ giữ chỗ: điền theo nhà cung cấp bạn chọn, không chia sẻ khóa
// PYWORLD.VN - Thư viện ArduinoJson 7; dán chứng chỉ gốc của máy chủ API vào CHUNG_CHI_GOC để kiểm tra kết nối an toàn

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char *TEN_WIFI = "TEN_WIFI";
const char *MAT_KHAU_WIFI = "MAT_KHAU_WIFI";
const char *DIA_CHI_API = "https://DIA_CHI_API/v1/chat/completions";
const char *TEN_MO_HINH = "TEN_MO_HINH";
const char *KHOA_API = "KHOA_API";
const char *CHUNG_CHI_GOC = "";            // PEM của chứng chỉ gốc; để trống = không kiểm tra (chỉ dùng khi thử)
const int SO_TOKEN_TOI_DA = 200;           // giới hạn độ dài câu trả lời (và chi phí)
const int CHAN_CAM_BIEN = 34;              // ví dụ: một cảm biến analog hoặc biến trở

String hoiMoHinh(const String &cauHoi, const String &boiCanh) {
  WiFiClientSecure ketNoi;
  if (strlen(CHUNG_CHI_GOC) > 0) {
    ketNoi.setCACert(CHUNG_CHI_GOC);
  } else {
    ketNoi.setInsecure();
  }
  HTTPClient http;
  http.setTimeout(30000);                  // mô hình có thể mất vài giây mới trả lời
  http.useHTTP10(true);                    // tránh kiểu truyền "chunked" để đọc JSON thẳng từ luồng
  if (!http.begin(ketNoi, DIA_CHI_API)) return "Loi: khong mo duoc dia chi API";
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", String("Bearer ") + KHOA_API);

  JsonDocument yeuCau;
  yeuCau["model"] = TEN_MO_HINH;
  yeuCau["max_tokens"] = SO_TOKEN_TOI_DA;
  JsonArray tinNhan = yeuCau["messages"].to<JsonArray>();
  JsonObject heThong = tinNhan.add<JsonObject>();
  heThong["role"] = "system";
  heThong["content"] = String("Bạn là trợ lý trong một thiết bị ESP32. Trả lời ngắn gọn bằng tiếng Việt, tối đa 3 câu. ") + boiCanh;
  JsonObject nguoiDung = tinNhan.add<JsonObject>();
  nguoiDung["role"] = "user";
  nguoiDung["content"] = cauHoi;
  String than;
  serializeJson(yeuCau, than);

  int ma = http.POST(than);
  if (ma != 200) {
    String loi = "Loi HTTP " + String(ma) + ": " + http.getString().substring(0, 200);
    http.end();
    return loi;
  }
  // Chỉ giữ lại choices[0].message.content để tiết kiệm RAM
  JsonDocument boLoc;
  boLoc["choices"][0]["message"]["content"] = true;
  JsonDocument phanHoi;
  DeserializationError loiJson = deserializeJson(phanHoi, http.getStream(), DeserializationOption::Filter(boLoc));
  http.end();
  if (loiJson) return String("Loi doc JSON: ") + loiJson.c_str();
  const char *noiDung = phanHoi["choices"][0]["message"]["content"];
  return noiDung ? String(noiDung) : String("Khong co noi dung tra loi");
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(TEN_WIFI, MAT_KHAU_WIFI);
  Serial.print("Ket noi Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print('.');
  }
  Serial.println(" xong");
  if (strlen(CHUNG_CHI_GOC) == 0) Serial.println("Canh bao: chua kiem tra chung chi may chu API");
  Serial.println("Go cau hoi roi bam Enter");
}

void loop() {
  if (!Serial.available()) return;
  String cauHoi = Serial.readStringUntil('\n');
  cauHoi.trim();
  if (cauHoi.length() == 0) return;

  uint32_t mv = analogReadMilliVolts(CHAN_CAM_BIEN);
  String boiCanh = "Số đo hiện tại của cảm biến ở chân GPIO34: " + String(mv) + " mV.";
  Serial.println("Hoi: " + cauHoi);
  uint32_t batDau = millis();
  String traLoi = hoiMoHinh(cauHoi, boiCanh);
  Serial.printf("Tra loi (%lu ms): %s\n", (unsigned long)(millis() - batDau), traLoi.c_str());
}
  • HTTPS và chứng chỉ: dán chứng chỉ gốc (PEM) của máy chủ API vào CHUNG_CHI_GOC để ESP32 kiểm tra đúng máy chủ thật; cách lấy chứng chỉ gốc có trong bài HTTPS và chứng chỉ của loạt ESP32 nâng cao. Để trống thì chương trình bỏ qua bước kiểm tra (setInsecure()) và in cảnh báo: chỉ nên dùng khi thử.
  • useHTTP10(true) buộc máy chủ gửi phản hồi liền một khối để ArduinoJson đọc thẳng từ luồng mạng.
  • Bộ lọc của ArduinoJson chỉ giữ choices[0].message.content, bỏ phần còn lại, tiết kiệm RAM.
  • Mỗi lần hỏi thường mất vài giây; thời gian chờ đặt 30 giây.
Serial Monitor: kết nối Wi-Fi xong; hỏi nhiệt độ phòng đang 31 độ có nên bật quạt không; trả lời sau 3420 ms: với 31 độ nên bật quạt ở mức vừa, nếu phòng kín mở thêm cửa sổ để thông gió
Câu trả lời và thời gian là ví dụ minh họa.

5. Dùng câu trả lời để điều khiển: luôn kiểm tra trước

Mô hình gợi ý bật quạt; ESP32 kiểm tra lệnh có trong danh sách cho phép, nhiệt độ thật trên 28 độ, chưa bật trong 1 phút qua; đạt thì bật relay quạt, không đạt thì bỏ qua và ghi log
Mô hình chỉ gợi ý; quyết định cuối cùng là quy tắc trong ESP32.

Câu trả lời của mô hình ngôn ngữ có thể sai, có thể khác nhau giữa các lần hỏi, và có thể bị lừa bởi câu hỏi được viết khéo. Nếu muốn dùng câu trả lời để bật tắt thiết bị: yêu cầu mô hình trả về một từ khóa trong danh sách cố định, ESP32 tự kiểm tra bằng số đo thật và giới hạn cứng rồi mới thực hiện. Không để mô hình điều khiển trực tiếp thiết bị điện công suất lớn, máy móc, khóa cửa.

6. Bảo vệ khóa API và chi phí

  • Không đưa mã nguồn có khóa API thật lên mạng, không chụp màn hình có khóa.
  • Đặt hạn mức chi tiêu ở trang quản lý của nhà cung cấp; ESP32 lỗi lặp vô hạn có thể gửi hàng nghìn yêu cầu.
  • Giới hạn số lần hỏi trong chương trình (ví dụ tối đa vài lần mỗi phút) và giữ max_tokens nhỏ.
  • Với sản phẩm phát cho nhiều người: đặt khóa ở một máy chủ trung gian của bạn, ESP32 chỉ gọi máy chủ đó, không chứa khóa.

7. Lỗi hay gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Loi HTTP 401Khóa API sai, hết hạnTạo khóa mới, kiểm tra dấu cách thừa
Loi HTTP 404 hoặc 400Sai địa chỉ API, sai tên mô hìnhĐối chiếu tài liệu của nhà cung cấp
Loi HTTP 429Gọi quá nhanh, hết hạn mứcGiãn khoảng cách giữa các lần hỏi; kiểm tra hạn mức
Loi HTTP -1 (không kết nối)Chứng chỉ gốc không đúng máy chủ, mất Wi-FiLấy lại chứng chỉ gốc; kiểm tra mạng
Loi doc JSON NoMemoryPhản hồi quá lớnGiảm max_tokens; giữ bộ lọc JSON
Câu trả lời bị cắt giữa chừngmax_tokens quá nhỏTăng vừa đủ hoặc yêu cầu trả lời ngắn hơn

8. Xem thêm

  • Kết nối Wi-Fi trên ESP32
  • Các bài cùng loạt AI trên vi điều khiển: TinyML là gì (vì sao mô hình lớn không chạy trên chip), loa trợ lý AI trên ESP32-S3; bài HTTPS và chứng chỉ gốc trong loạt ESP32 nâng cao.

Tin tức liên quan

Cảm biến lực (load cell): cấu tạo, nguyên lý, cách chế tạo và tự đọc HX711 bằng ESP32
Cảm biến lực (load cell): cấu tạo, nguyên lý, cách chế tạo và tự đọc HX711 bằng ESP32

Load cell hoạt động thế nào: các dạng thân, bốn strain gauge nối cầu Wheatstone, quy trình chế tạo, FSR và cảm biến áp điện, giao thức HX711 và cách tự đọc HX711 bằng ESP32.

Chuẩn chiều dài: căn mẫu song phẳng ISO 3650, giao thoa laser và nhiệt độ tham chiếu 20 °C
Chuẩn chiều dài: căn mẫu song phẳng ISO 3650, giao thoa laser và nhiệt độ tham chiếu 20 °C

Mét được hiện thực ra sao, căn mẫu song phẳng và các cấp K, 0, 1, 2 theo ISO 3650, hiệu chuẩn căn mẫu bằng giao thoa và so sánh, giao thoa laser cho máy CNC và vì sao đo ở 20 °C.

Quy trình thiết kế kỹ thuật trong dự án STEM: 5 hoạt động, ví dụ làm cối xay gió nâng vật
Quy trình thiết kế kỹ thuật trong dự án STEM: 5 hoạt động, ví dụ làm cối xay gió nâng vật

Dự án STEM là gì và quy trình thiết kế kỹ thuật 8 bước, 5 hoạt động theo hướng dẫn của Bộ GD&ĐT, minh họa bằng dự án cối xay gió nâng vật với phiếu thử nghiệm công bằng.

TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32
TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32

Giải thích TCP và UDP cho người làm vi điều khiển: địa chỉ IP, cổng, kết nối, xác nhận, vì sao TCP là dòng byte. Kèm hai chương trình ESP32: máy chủ TCP nhận lệnh và phát, nhận gói UDP trong mạng Wi-Fi.

Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC
Jack DC 5.5×2.1 và 5.5×2.5: phân biệt, cực tính, chọn jack gắn hộp và dây nguồn DC

Jack DC 5.5×2.1 và 5.5×2.5 khác nhau ở lỗ trong: cách phân biệt khi không có nhãn, ký hiệu tâm dương, đo cực tính adapter, lắp jack gắn hộp và nối nguồn vào mạch an toàn.

LM2596 là gì: sơ đồ chân, thông số, cách chỉnh 5 V – 3,3 V, dòng tải thực tế và nhiệt độ
LM2596 là gì: sơ đồ chân, thông số, cách chỉnh 5 V – 3,3 V, dòng tải thực tế và nhiệt độ

LM2596 là IC hạ áp buck 150 kHz: sơ đồ chân, thông số datasheet, sơ đồ nguyên lý, công thức Vout = 1,23 × (1 + R2/R1), cách chỉnh module ra 5 V, 3,3 V, dòng tải, nhiệt và lỗi hay gặp.

Cấu tạo máy CNC mini và máy khắc laser: khung, ray trượt, truyền động, đầu công cụ, bộ điều khiển
Cấu tạo máy CNC mini và máy khắc laser: khung, ray trượt, truyền động, đầu công cụ, bộ điều khiển

Cấu tạo máy CNC kiểu cổng và máy khắc laser khung hở: khung nhôm định hình, ray dẫn hướng, vít me và dây đai, động cơ bước, spindle, module laser, phần điện và ba kiểu kết cấu.

Cách đấu jack Type-C tích hợp mạch sạc với pin 18650, Li-po: sơ đồ, đèn báo và lỗi thường gặp
Cách đấu jack Type-C tích hợp mạch sạc với pin 18650, Li-po: sơ đồ, đèn báo và lỗi thường gặp

Hướng dẫn đấu jack Type-C tích hợp mạch sạc với pin 18650 hoặc Li-po một cell: dây đỏ, dây đen nối vào đâu, tải nối thế nào, khi nào cần mạch bảo vệ, cách đọc đèn đỏ xanh và bảng tra lỗi thường gặp.


Bình luận
Đã thêm vào giỏ hàng