DEV Community

codemee
codemee

Posted on • Updated on

在 MicroPython 中使用 OpenAI API

OpenAI 雖然提供有 Python 的官方套件, 不過如果你是要在 MicroPython 中使用 OpeAnAI 的 API, 並不能直接套用, 這時就要回歸到 OpenAI API 最根本的 HTTP Post API 了。

最簡單的 OpenAI API

OpenAI 是透過 HTTP Post 提供服務, 以聊天為例, 用的是 ChatCompletion 服務:

因此, 最簡單的 OpenAI HTTP Post API 的程式就像是這樣:

import requests

API_KEY = '你的 OpenAI API 金鑰'

response = requests.post(
    'https://api.openai.com/v1/chat/completions',
    headers = {
        'Authorization': 'Bearer ' + API_KEY
    },
    json = {
        'model': 'gpt-3.5-turbo',
        "messages": [{"role": "user", "content": "你好"}]}
)
print(response.status_code)
print(response.reason)
reply = response.json()
print(reply["choices"][0]["message"]["content"])
Enter fullscreen mode Exit fullscreen mode

執行結果如下:

>>> %Run openai_pc.py
  200
  OK
  您好!我是语言模型AI的GPT-3,有什么可以帮助您的吗?
Enter fullscreen mode Exit fullscreen mode

改用 MicroPython

既然是使用 HTTP Post, 哪麼只要從 requests 模組改成 urequests 模組, 應該就可以原封不動照搬程式了, 我們來試看看:

import network     
import time
import urequests

# 連線至無線網路
sta=network.WLAN(network.STA_IF)
sta.active(True)   
sta.connect('你的無線網路名稱', '無線網路密碼')  
while not sta.isconnected() :
    pass
print('Wi-Fi連線成功')

API_KEY = '你的 OpenAI API 金鑰'

response = urequests.post(
    'https://api.openai.com/v1/chat/completions',
    headers = {
        'Authorization': 'Bearer ' + API_KEY
    },
    json = {
        'model': 'gpt-3.5-turbo',
        "messages": [{"role": "user", "content": "你好"}]}
)
print(response.status_code)
print(response.reason)
reply = response.json()
print(reply["choices"][0]["message"]["content"])
Enter fullscreen mode Exit fullscreen mode

不過執行後就會看到 OpenAI 伺服器端回覆 400 錯誤:

>>> %Run -c $EDITOR_CONTENT
  Wi-Fi連線成功
  400
  b'Bad Request'
  Traceback (most recent call last):
    File "<stdin>", line 32, in <module>
  KeyError: choices
Enter fullscreen mode Exit fullscreen mode

同樣的程式, 搬到 MicroPython 上會出錯, 第一個懷疑的就是中文編碼的問題, 如果把程式中傳遞的 "你好" 改成純英文試看看:

...
    json = {
        'model': 'gpt-3.5-turbo',
        "messages": [{"role": "user", "content": "hello"}]}
...
Enter fullscreen mode Exit fullscreen mode

再執行一次就會發現可以正確執行:

>>> %Run -c $EDITOR_CONTENT
Wi-Fi連線成功
200
b'OK'
Hello there! How may I assist you today?
Enter fullscreen mode Exit fullscreen mode

顯然問題就是出在 urequests.post 對於 json 參數的處理。

json 模組的中文處理

urequests.post 中會使用 json 模組 (MicroPython 中 json 與 ujson 是同一個模組) 的 dumps 函式將 Python 字典轉成字串格式的 JSON 資料, 可是它的輸出結果會保留以 UTF-16 編碼的中文字, 例如:

>>> import json
>>> json.dumps({"content": "你好"})
'{"content": "\u4f60\u597d"}'
Enter fullscreen mode Exit fullscreen mode

其中 \u4f60 是 "你" 的 UTF-16 編碼, 但是 json 規格需要的是 UTF8 編碼, 或是使用 "\u" 跳脫序列標註的 UTF-16 編碼, 好在 Str 的 encode 方法可以幫我們將字串轉換成 UTF8 編碼的位元組串:

>>> json.dumps({"content": "你好"}).encode('utf8')
b'{"content": "\xe4\xbd\xa0\xe5\xa5\xbd"}'
Enter fullscreen mode Exit fullscreen mode

這樣結果就對了。不過因為要自行處理字典轉 json 格式位元組的工作, 所以就不能直接在 urequests.post 中使用 json 參數傳入字典了。

改用 data 參數傳入 json 資料

urequests.postdata 參數可以直接傳入要送給伺服端的資料, 因此我們就可以將程式改成如下:

import network     
import time
import urequests
import ujson

# 連線至無線網路
sta=network.WLAN(network.STA_IF)
sta.active(True)   
sta.connect('你的無線網路名稱', '你的無線網路密碼')  
while not sta.isconnected() :
    pass
print('Wi-Fi連線成功')

API_KEY = '你的 OpenAI API 金鑰'

response = urequests.post(
    'https://api.openai.com/v1/chat/completions',
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + API_KEY
    },
    data = ujson.dumps({
        'model': 'gpt-3.5-turbo',
        "messages": [{"role": "user", "content": "你好"}]
        }).encode('utf8')
)
print(response.status_code)
print(response.reason)
reply = response.json()
print(reply["choices"][0]["message"]["content"])
Enter fullscreen mode Exit fullscreen mode

要特別留意的是使用 json 參數傳入 Python 字典時, urequests.post 會幫你在表頭加上 'Content-Type: application/json', 自行使用 data 參數傳入 json 資料時就要記得在表頭補上標示遞交內容的格式, 否則無法正確執行。

這樣一來, 就可以正確叫用 API 了:

>>> %Run -c $EDITOR_CONTENT
  Wi-Fi連線成功
  200
  b'OK'
  你好!有什么我可以帮助您的吗?
Enter fullscreen mode Exit fullscreen mode

Top comments (0)