「RaspberryPi PicoW」を使って「OpenAI」の「API」で「gpt-3.5-turbo」を使った「ChatGPT」のようなチャットボットの作り方を、コピペ用サンプルプログラムで詳しく紹介します。
ブラウザから入力した質問を、サーバーに設定した「PicoW」から「CircuitPython」で「OpenAI」に送信し、自然言語処理AI(gpt-3.5-turbo)で処理した結果を回答として取得して、ブラウザにチャット形式で表示していきます。
「PicoW」をサーバーにしてデータを送受信する方法は以下のリンクで詳しく紹介しています。
今回は「CircuitPython」で作成しています。
1.全体構成
・動作紹介
・トークン数と使用料金の目安も確認できる
・準備するもの
2.OpenAIについて
・ChatGPTとは
・自然言語処理AI gpt-3.5-turboとは
・APIとは、APIキーの取得方法
・API使用料金、トークンについて
3.シンプルなサンプルプログラム(コピペ)
・質問の送信から回答の取得方法
・環境変数設定ファイル(settings.toml)の使い方
・回答の仕方に人格(キャラ)設定する方法
4.チャットボットのサンプルプログラム(コピペ)
・メインプログラム(code.py:CircuitPython)
・ブラウザ表示用ChatBOT(html, CSS, JavaScript)
・環境変数設定ファイル(settings.toml)
・必要なライブラリの準備
・動作確認
5.まとめ
1.全体構成
・動作紹介
全体の構成は下写真のようになります。
OLED表示器は「PicoW」に接続してパソコン等のブラウザでチャットボットの画面を表示させるためのURL(IPアドレス)を確認するためだけに使用します。
無くても開発環境の「Thonny」等のシリアルモニタ(シェル)でも確認できるため「PicoW」単体でも動作確認できます。
「PicoW」の電源を入れると上画像のようにOLED表示器にWi-Fi接続先のSSIDとチャットボットに接続するURL(IPアドレス)が表示されます。
チャットボットが回答を取得中には「PicoW」本体のLEDが点灯します。
点灯中は通信中なので回答が返ってくるまでしばらく待ちましょう。
「PicoW」のURL(IPアドレス)が確認できたら、下画像のようにパソコンやスマホでブラウザを立ち上げて、アドレスバーにURL(IPアドレス)を入力します。
上画像はGoogle Chromeです。
アドレスバーにURL(IPアドレス:環境によって異なります。ここでは「192.168.0.10」)を入力してエンターを押すと、上画像のチャットボットの画面が表示されます。
チャットボットに質問をするには、上画像のようにテキストエリアに質問を入力して[送信]ボタンを押します。
質問を送信すると、上画像のように「Loading…」が点滅表示するのでしばらく待ちましょう。
しばらく待つと回答が返ってきます。
回答が返ってくると、テキストエリアの下に今回の回答と質問のトークン数と料金が表示されます。
(トークン数と料金はこの後こちらで紹介しています)
スマホのブラウザのアドレスバーにURL(IPアドレス)を入力しても下画像のように使用できます。
・トークン数と使用料金の目安も確認できる
さらに質問をすると、チャット形式で履歴が表示されていきます。
テキストエリア下の「単価」には最新の質問と回答にかかったトークン数と金額が表示されます。
「合計」には一連のチャットにかかったトークン数と金額の合計が加算されて表示されるため、使用料の目安として下さい。
・準備するもの
「PicoW」本体のみでも動作しますが、OLED表示器(SSD1306)があると接続先URL(IPアドレス)の確認が簡単にできるのでおすすめです。
OLED表示器は下画像のようにブレッドボードとジャンパー線を使用すると簡単に接続することができるので下画像を参考に接続して下さい。
(SSD1306接続方法:Vcc→3.3V、GND→GND[0V]、SCL→GP17、SDA→GP16)
「RaspberryPi PicoW」本体は日本国内の技術基準に適合したものを使用する必要があります。
「Amazon」でも買えるかもしれませんが、以下のスイッチサイエンスさんなら確実に適合品が買えるのでこちらでの購入をお勧めします。
今回使用したブレッドボードはサンハヤト製の6連結ピンのものです。
安価なものはありますが 5連結ピンのものが多く、ピンが少ないため物足りないです。
少し割高ですが6連結ピンの方が自由度があっておすすめです。
OLED表示器SSD1306は今回は画面サイズ「128×32」のものを使用しました。
今回使用したのは表示色が青色しか余っていなかったので青色ですが、個人的には表示色は白色が一番見やすくて綺麗と思います。
ジャンパー線はケースもついたサンハヤト製がおすすめです。安いのもありますが・・・お好みで
「PicoW」は基本的には基板単体のみで端子がついていないため、端子がない場合は以下のようなピンヘッダーを半田付けして使用する必要があります。
ブレッドボードに挿してから半田付けすると意外と簡単です。
2.OpenAIについて
今回作成したチャットボットには「OpenAI」社の「自然言語処理AI:gpt-3.5-turbo」を使用しています。
「OpenAI」とは「人類の利益のために安全で有益な汎用人工知能(AGI)を構築すること」を目的として、2015 年にイーロン・マスクや、現在のOpenAIのCEOサム・アルトマンらによって設立された非営利の人工知能研究組織です。
テキストの生成、言語の翻訳、さまざまな種類のクリエイティブコンテンツの作成、質問への回答を行うことができる大規模な言語モデルの開発など、いくつかの注目すべき成果を上げており有名なものに「ChatGPT」があります。
・ChatGPTとは
「ChatGPT(チャットジーピーティー)」は、「OpenAI」が開発したAIチャットサービスです。
2022年11月30日に公開され、人間のような自然な会話ができることで話題となリました。
「ChatGPT」には、膨大なテキストとコードのデータセットでトレーニングされた「GPT-3.5」という言語モデルが搭載されており、質問をするだけで、ニュース、エンターテインメント、スポーツ等のさまざまな種類の情報だけでなく、プログラミングのコードや歌詞、物語など、さまざまなクリエイティブなテキストコンテンツをチャット形式で得ることができます。
以下のサイトからアカウントを作成してサインインすると無料で使用することができます。
・自然言語処理AI「gpt-3.5-turbo」とは
「OpenAI」社は様々な「自然言語処理AI」を提供しており、これらは「API(Application Programming Interface)」を使用することで利用することができます。
「API」を使用することで、自分で作成したプログラムからテキストを「OpenAI」へ送信すると、指定した「自然言語処理AI」で処理を実行して、その結果を得ることができます。
現時点で最新のものは「gpt-4」ですが、まだベータ版で、使用するには待機リストに登録して空きを待つ必要があります。
「gpt-3.5-turbo」は登録さえすれば、すぐに使うことができます。
使用料は1000Token(トークン)あたり0.002ドル(詳細は以下のこちらを参照)のため非常に安価です。
・APIとは、APIキーの取得方法
「API」とは「Application Programming Interface」の略で、「ソフトウェアアプリケーション」と「プログラム」間で情報をやり取りする仕組みです。
「API」を使用すると、ソフトウェア開発者は他の企業が開発したコードを自分のアプリケーションの中で使用できます。これにより、開発者の時間と労力を節約し、新しいアプリケーションの開発を容易にすることができます。
「OpenAI」の「自然言語処理AI:gpt-3.5-turbo」も「API」で使用することができます。
APIキーの取得方法
「gpt-3.5-turbo」の「API」を使用するための登録方法から「APIキー」の取得方法、注意事項について以下から紹介します。
まずは以下の「OpenAI」のサイトへアクセスします。
上画像のようなサイトが表示されたら右上の[menu]をクリックします。
「OpenAI」のアカウントをお持ちでない方はドロップダウンリストから[Sign up]をクリックします。「ChatGPT」を使っていてアカウントのある方は[Log in]をクリックして以下は飛ばしてこちらへどうぞ
新規でアカウントの作成をする場合は上画像で[Email address]を入力して[Continue]ボタンをクリックします。
次に[パスワード]を決めて入力(8文字必要)したら[Continue]ボタンをクリックします。
上画像のように表示されたらメールが届いているのでメールを確認しましょう。
メールを確認すると、上画像のようなメールが届いているので、その中の[Verify email address]をクリックします。
メールアドレスの確認が完了すると、上画像のような画面が表示されるので[login]をクリックします
ここからはログイン画面になります。
上画像のように表示されたら、メールアドレスを入力して[Continue]ボタンをクリックします。
次に自分で決めた[パスワード]を入力して[Continue]ボタンをクリックします。
次に名前と生年月日を入力して[Continue]ボタンをクリックします。
電話番号の登録が必要なため、SNSメールが受信可能な携帯電話の番号を入力します。
上画像のように日本を選択すると国コード「+81」が表示されているので、これに続けて入力して[Send code]ボタンをクリックします。
しばらくすると携帯に「OpenAI 認証コード」が届くので6桁のコードを入力します。
すでに登録済みの電話番号を入力すると上画像のような画面で、無料のクレジットはつかないと案内され有料プランへのアップグレードを促されます。
アカウントの作成が完了したら、APIキーの発行を行います。
次回からログインすると以下のような画面が表示されます。
上画像右上の自分のアカウント名をクリックします。
ドロップダウンメニューから[View API keys]をクリックします。
上画像のような画面が表示されたら[Create new secret key]ボタンをクリックします。
上画像の画面では作成する「APIキー」の用途がわかるように任意の名前をつけられます。
適当に名前を入力して[Create secret key]ボタンをクリックします。
上画像の画面のように「APIキー」が発行されます。
右にあるコピーボタンでコピーできるので安全な場所に保管しておきましょう。
上画像のように作成した「APIキー」の名前や作成日、最後に使用した日時が確認できますが「APIキー」自体は確認できません。
「APIキー」がわからない場合は右端の[ゴミ箱]アイコンをクリックして削除して再発行しましょう。
・API使用料金、トークンについて
「API」利用のアカウントを新規作成した時点で3ヶ月間限定の無料のAPI使用料(18ドル)が割り当てられます。
使いきれなかった場合は無くなってしまうので思いっきり使いましょう。
使用料の確認は「OpenAI」のホームページからログインし、下画像のように画面右上のアカウント名をクリックして[Manage account]をクリックします。
下画像のようなメニューが画面左に表示されるので[Usage]をクリックします。
下画像のような画面が表示され、今月の「API」の使用料が確認できます。
[Free trial usage]で無料枠の金額の使用状況が確認できます。
トークン(Token)と単価について
「トークン」とは「自然言語処理AI(モデル)」が処理するテキストの単位で「API」の使用料もこの「トークン」単位で決まります。
「gpt-3.5-turbo」等のモデルは、テキストをトークンに分解して理解して処理します。
トークンは単語または単なる文字の塊です。たとえば、「hamburger」という単語は「hum」、「bur」、「ger」というトークンに分割されますが、「pear」のような短く一般的な単語は単一のトークンになります。
トークン数のカウントは、質問と回答の内容だけではなく、プロンプトや参照情報を組み合わせた合計になります。
モデルごとに1度に処理できる最大のトークン数は決まっており、それを超えた処理はできません。
テキストがどのようにトークンに変換されるかについて詳しくは「 tokenizer tool 」を参照ください。
モデルごとの処理可能な最大トークン数と単価は以下表のようになります。
モデル | 最大処理トークン数 | 1000トークンあたりの単価 (送信:prompt/受信:completion) |
---|---|---|
gpt-4 8k | 8192 | $0.03(prompt)/ $0.06(completion) |
gpt-4 32k | 8192 | $0.06(prompt)/ $0.12(completion) |
gpt-3.5-turbo | 4096 | $0.002 |
3.シンプルなサンプルプログラム(コピペ)
今回作成するチャットボットで「API」を使用するプログラムは「CircuitPython」で作成しています。
「Circuit Python」は「PicoW」本体をUSBメモリーのように扱い、ライブラリやhtmlファイル、「APIキー」を保存しておくファイルを保存して管理することができます。
「CircuitPython」の基本的な使用方法と「PicoW」をサーバーとして使用する方法は以下のリンクで詳しく紹介しています。
「API」を使用して「OpenAI」から質問の回答を取得するだけのシンプルなサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
import wifi
import socketpool
import ssl
from adafruit_requests import Session
# Wi-FiアクセスポイントのSSID、パスワードとOpenAIから取得したAPIキーの設定
SSID = "自宅のWi-Fi接続先SSIDを設定"
PASSWORD = "自宅のWi-Fi接続先パスワードを設定"
API_KEY = "APIキーを設定"
# API使用設定
ENDPOINT = 'https://api.openai.com/v1/chat/completions' # OpenAI接続先(エンドポイント)
MODEL = "gpt-3.5-turbo" # AIモデル
SYSTEM = "" # 回答の人格(キャラ)設定(「〜のように回答してください」等を記入)
assistant = "" # 回答するにあたっての参照情報を設定
prompt = "こんにちは" # Chatbotへの質問内容
# Wi-Fi接続
print("Connecting to Wi-Fi...")
wifi.radio.connect(SSID, PASSWORD)
# socketpoolとrequestsライブラリを使用するための設定
pool = socketpool.SocketPool(wifi.radio)
requests = Session(pool, ssl.create_default_context())
# OpenAIからの応答を取得する関数
def get_chat_response(prompt):
# APIリクエストヘッダーを設定
headers = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer ' + API_KEY
}
# APIリクエストデータを設定
data = {
'model': MODEL, # OpenAIのAIモデルを指定
'messages': [
{'role': 'system', 'content': SYSTEM }, # 回答の仕方、傾向(人格、キャラ)設定
{'role': 'assistant', 'content': assistant }, # 回答するための参照情報設定
{'role': 'user', 'content': prompt } # 質問内容
], # 質問内容を「prompt」に指定
'max_tokens' : 500, # 生成するテキストの最大長(トークン数)
'temperature' : 1, # 値が大きいほど生成される文章が多様化する(0〜2)
# 'top_p' : 1, # 値が大きいほど多様な単語が選択される(0〜1)※temperatureとの併用は非推奨
'frequency_penalty' : 0.0, # 値が大きいほど同じ単語が繰り返し出現することを防止する(-2.0~2.0)
'presence_penalty' : 0.0 # 値が大きいほど同じ意味を持つフレーズや文の出現を防止する(-2.0~2.0)
}
# APIリクエストを送信
response = requests.post(ENDPOINT, headers=headers, json=data, timeout=60)
# API応答を解析
response_json = response.json()
print(response_json) # 受信データ
print(response_json['usage']) # トークン使用料(質問、回答、合計)
message = response_json['choices'][0]['message']['content'].strip() # 回答を抽出
return message
# Chatbotの回答を取得
response = get_chat_response(prompt)
print('\nUser: ' + prompt)
print('\nAI: ' + response)
サンプルプログラムを開発環境「Thonny」に貼り付けて実行すると、以下のように「シェル」内で回答を確認することができます。
サンプルプログラムの中の「prompt」の「こんにちは」を他の質問内容に書き換えて実行してどんな回答が得られるか確認してみましょう。
・質問の送信から回答の取得方法
まずは以下のサンプルプログラムの「11〜16行目」のように「OpenAI」への接続先エンドポイント、AIモデル、質問内容を設定します。
(「SYSTEM」と「assistant」 についてはこの後こちらで詳しく紹介します。)
# API使用設定
ENDPOINT = 'https://api.openai.com/v1/chat/completions' # OpenAI接続先(エンドポイント)
MODEL = "gpt-3.5-turbo" # AIモデル
SYSTEM = "" # 回答の人格(キャラ)設定(「〜のように回答してください」等を記入)
assistant = "" # 回答するにあたっての参照情報を設定
prompt = "こんにちは" # Chatbotへの質問内容
「API」を使用して「OpenAI」へ質問を送信し回答を得るには以下のサンプルプログラムの「28〜48行目」のように「ヘッダー情報」と「送信データ」を「JSON」形式で指定して「OpenAI」のサーバーでhttp通信でPOST送信(リクエスト)します。
# APIリクエストヘッダーを設定
headers = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer ' + API_KEY
}
# APIリクエストデータを設定
data = {
'model': MODEL, # OpenAIのAIモデルを指定
'messages': [
{'role': 'system', 'content': SYSTEM }, # 回答の仕方、傾向(人格、キャラ)設定
{'role': 'assistant', 'content': assistant }, # 回答するための参照情報設定
{'role': 'user', 'content': prompt } # 質問内容
], # 質問内容を「prompt」に指定
'max_tokens' : 500, # 生成するテキストの最大長(トークン数)
'temperature' : 1, # 値が大きいほど生成される文章が多様化する(0〜2)
# 'top_p' : 1, # 値が大きいほど多様な単語が選択される(0〜1)※temperatureとの併用は非推奨
'frequency_penalty' : 0.0, # 値が大きいほど同じ単語が繰り返し出現することを防止する(-2.0~2.0)
'presence_penalty' : 0.0 # 値が大きいほど同じ意味を持つフレーズや文の出現を防止する(-2.0~2.0)
}
# APIリクエストを送信
response = requests.post(ENDPOINT, headers=headers, json=data, timeout=60)
データ送信後「OpenAI」のサーバーで処理された回答のデータが「JSON」形式で返ってくる(レスポンス)ため、これを受信して以下のサンプルプログラムの「49〜54行目」のように回答を抽出して確認します。
# API応答を解析
response_json = response.json()
print(response_json) # 受信データ
print(response_json['usage']) # トークン使用料(質問、回答、合計)
message = response_json['choices'][0]['message']['content'].strip() # 回答を抽出
質問として「こんにちは」を送信した時の受信(レスポンス)データは以下のような「JSON」形式のデータになります。
{
'object': 'chat.completion',
'created': 1684767533,
'model': 'gpt-3.5-turbo-0301',
'usage': {
'total_tokens': 36,
'prompt_tokens': 19,
'completion_tokens': 17
},
'id': 'chatcmpl-7J1HpUhhTxvvwvgC594vBDwYPWrrG',
'choices': [
{
'finish_reason': 'stop',
'message': {
'content': 'こんにちは!どのようなご用件でしょうか?',
'role': 'assistant'
},
'index': 0
}
]
}
回答は「choices」→「message」→「content」に格納されています。
その他にもトークン数が送信(prompt_tokens)、受信(completion_tokens)、合計(total_tokens)で確認できるので、回答取得にかかった費用の目安にすることができます。
・環境変数設定ファイル(settings.toml)の使い方
上のサンプルプログラムでは「APIキー」を直接コード内に記入していますが、これはセキュリティ的には「APIキー」の流出の危険性があり、好ましくありません。
実際は「APIキー」は別の場所に保存して参照して使用するようにします。
「CircuitPython」には「環境変数」設定用のファイルとして「setting.toml」ファイルというものがあるので、チャットボットのプログラムではここから参照しています。
セキュリティ的には安全とは言えませんが「SSID」や「パスワード」「APIキー」を毎回コードに書き込む必要がなくなるため便利です。
「setting.toml」ファイルの内容は以下のように記入します。
CIRCUITPY_WIFI_SSID="自宅のWi-Fi接続先SSIDを設定"
CIRCUITPY_WIFI_PASSWORD="自宅のWi-Fi接続パスワードを設定"
OPENAI_API_KEY="OpenAIから取得したAPIキーを設定"
これを使用するためには、上のサンプルプログラムの「1〜10行目」を以下と置き換えます。
外部ファイル参照のために「os」ライブラリもインポートします。
import os # 外部ファイル参照用ライブラリ
import wifi
import socketpool
import ssl
from adafruit_requests import Session
# Wi-FiアクセスポイントのSSID、パスワードとAPIキーをsetting.tomlファイルから取得
SSID = os.getenv("CIRCUITPY_WIFI_SSID") # Wi-Fi接続先SSID
PASSWORD = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Wi-Fi接続先パスワード
API_KEY = os.getenv("OPENAI_API_KEY") # APIキー
・回答の仕方に人格(キャラ)設定する方法
回答の仕方に人格(キャラ)を設定することができます。
キャラ設定には「SYSTEM」の中に「〜のように回答してください」と設定します。
細かいキャラ設定は「assistant」の中に設定します。
試しに以下のように設定して、実行してみました。
SYSTEM = "あなたは猫です。猫のように回答して下さい" # 回答の人格(キャラ)設定(「〜のように回答してください」等を記入)
assistant = "自分のことは「我輩」と言う" # 回答するにあたっての参照情報を設定
prompt = "あなたは誰ですか?" # Chatbotへの質問内容
回答は「我輩は自然言語処理の人工知能エージェントである。」でした・・・
ここは「我輩は猫である。」と言って欲しかったですが、流石に無理でしたねw。
でも、ちゃんと「〜である。」で回答してきてくれているところは感心しました。
4.チャットボットのサンプルプログラム(コピペ)
ブラウザから質問して回答をチャット形式で得られる「チャットボット」のサンプルプログラムは以下になります。
開発環境に「Thonny」を使用した方法で以下から詳しく紹介します。
・メインプログラム(code.py:CircuitPython)
まずは「Thonny」を起動して、下のサンプルプログラムをコピペで貼り付けます。
「API」を使用して「OpenAI」から質問の回答を取得するサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
import os # ファイル操作など、OSレベルの機能を提供するために使用
import socketpool # TCP/IP接続用のソケット管理用
import wifi # WiFi接続を管理用
import digitalio # デジタルピンの入出力を制御用
from board import * # マイクロコントローラーのボード定義から全てのピンをインポート
import ssl # SSL通信用
from adafruit_requests import Session # HTTPリクエスト送信用
import json # JSONデータ処理用
from adafruit_httpserver import Server, Request, FileResponse, JSONResponse # サーバー用ライブラリ
# 本体LED表示設定
led = digitalio.DigitalInOut(LED) # 本体LEDをGPIO端子に割り当て
led.direction = digitalio.Direction.OUTPUT # LED端子を出力に設定
# Wi-FiアクセスポイントのSSIDとパスワードを取得(PicoW内に保存されている「settings.toml」ファイルから読み込む)
SSID = os.getenv("CIRCUITPY_WIFI_SSID") # Wi-Fi接続先SSID
PASSWORD = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Wi-Fi接続先パスワード
# OpenAIへの接続設定
API_KEY = os.getenv("OPENAI_API_KEY") # APIキー(PicoW内に保存されている「settings.toml」ファイルから読み込む)
ENDPOINT = 'https://api.openai.com/v1/chat/completions' # OpenAIのアドレス指定
MODEL = "gpt-3.5-turbo" # OpenAIのAIモデル指定
SYSTEM = "" # 回答の人格(キャラ)設定(「〜のように回答してください」と記入)
assistant = "" # 回答するにあたっての参照情報を設定(今回は質問ごとに前回の回答を設定)
# Wi-Fi接続を実行
wifi.radio.connect(SSID, PASSWORD) # 指定したSSIDとパスワードで接続
print("Connected to", SSID) # 接続先SSID表示
# ソケットプールを作成してHTTPサーバーを起動するための準備
pool = socketpool.SocketPool(wifi.radio) # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
requests = Session(pool, ssl.create_default_context()) # SSL通信設定
server = Server(pool, "/static", debug=True) # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする
# OpenAIからの応答を取得する関数
def get_chat_response(prompt, assistant):
# APIリクエストヘッダーを設定
headers = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer ' + API_KEY # APIキーを指定
}
# APIリクエストデータを設定
data = {
'model': MODEL, # OpenAIのAIモデルを指定
'messages': [
{'role': 'system', 'content': SYSTEM }, # 回答の仕方、傾向(人格、キャラ)設定
{'role': 'assistant', 'content': assistant }, # 回答するための参照情報設定
{'role': 'user', 'content': prompt } # 質問内容
], # 質問内容を「prompt」に指定
'max_tokens' : 500, # 生成するテキストの最大長(トークン数)
'temperature' : 1, # 値が大きいほど生成される文章が多様化する(0〜2)
# 'top_p' : 1, # 値が大きいほど多様な単語が選択される(0〜1)※temperatureとの併用は非推奨
'frequency_penalty' : 0.0, # 値が大きいほど同じ単語が繰り返し出現することを防止する(-2.0~2.0)
'presence_penalty' : 0.0 # 値が大きいほど同じ意味を持つフレーズや文の出現を防止する(-2.0~2.0)
}
# APIリクエストを送信
print("リクエストJSON:")
print(data)
response_json = requests.post(ENDPOINT, headers=headers, json=data, timeout=60) # OpenAIのアドレスを「ENDPOINT」に指定
# API応答を解析
response_data = response_json.json() # HTTPレスポンスのJSONをPythonオブジェクトに変換
return response_data # Pythonオブジェクトを返す
# OLED使用の場合(SSID,IPアドレス確認用)--------------------------------------------------------------
# 別途ドライバ、フォントファイルの準備が必要。詳細はこちらを参照ください→ https://logikara.blog/raspi_pico_oled_circuitpy
def oled_disp():
import busio # I2C通信制御用
import adafruit_ssd1306 # OLED SSD1306制御用
# I2C 通信設定
i2c = busio.I2C(GP17, GP16) # (SCL, SDA)17,16
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x3C)
# OLED表示設定
display.fill(0) # 表示内容消去
display.hline(0, 10, 128, True) # (x, y, 長さ, 色) 指定座標から横線
# 文字表示("表示内容", x, y, 色, フォント, サイズ)
display.text("ChatBOT:" + MODEL, 0, 0, True, font_name="font5x8.bin", size=1)
display.text("SSID:" + SSID, 0, 13, True, font_name="font5x8.bin", size=1)
display.text("URL:" + str(wifi.radio.ipv4_address), 0, 24, True, font_name="font5x8.bin", size=1)
# 設定した内容を表示
display.show()
try:
oled_disp() # OLED表示処理(未接続なら次の処理へ)
except RuntimeError: # No pull up found on SDA or SCL; check your wiringエラーを無視する
print("OLED not connected")
pass
# ------------------------------------------------------------------------------------------------
# ルート(IPアドレス)アクセス時に実行される関数(トップページ表示、ポート80は省略可)
@server.route("/")
def base(request: Request): # HTTPリクエストを処理する関数を定義
return FileResponse(request, "index.html", "/") # レスポンスとしてPicoWルートフォルダの「index.html」ファイルを読み込みHTMLデータとして返す
# OpenAIからの回答取得実行(IPアドレス/questionアクセス時に実行)される関数
@server.route("/question", "POST") # アクセスURLとPOST受信設定(POST指定なしの場合はGETのみ受信)
def question(request: Request): # HTTPリクエストを処理する関数を定義
global assistant # 前回の回答格納用
led.value = True # 本体LEDを点灯
data = json.loads(request.body) # リクエストボディからJSONデータを取得
print("\nリクエストボディJSON:")
print(data)
# OpenAIへの質問内容を表示
prompt = data["question"] # 取得したJSONデータの「question(key)」から「質問(value)」を取得
print('\nUser: ' + prompt) # 質問を表示
# OpenAIからの回答(レスポンス)を取得して表示
response_data = get_chat_response(prompt, assistant) # OpenAIからの回答取得関数を実行し回答をPythonオブジェクトで格納
print("\n OpenAIからのレスポンスデータ:")
print(response_data)
assistant = response_data['choices'][0]['message']['content'].strip() # Pythonオブジェクトから回答のみを抽出(次回の参照内容として格納)
print('\nAI: ' + assistant) # 回答を表示
led.value = False # 本体LEDを消灯
return JSONResponse(request, response_data) # JSONデータではなくPythonオブジェクト(dict型)で返す
# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}") # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address)) # HTTPサーバーを指定された「IPアドレス」と「ポート」で開始
「コピペ」したら保存ボタンをクリックします。
保存場所を聞いてくるので「CircuitPythonデバイス」をクリックします。
以下のようなウインドウが表示されたら「code.py」をクリックして[OK]ボタンをクリックします。
「上書く?」と聞いてくるので[はい]をクリックします。
これでメインの「CircuitPython」のプログラムは準備完了です。
・ブラウザ表示用ChatBOT(html, CSS, JavaScript)
次にブラウザで表示する「チャットボット」のプログラムを準備します。
このファイルは「PicoW」本体内に保存して「code.py」を実行したときに呼び出されます。
「Thonny」で下画像のように[新規作成]ボタンを押します。
「untitled」タブが表示されるので下のサンプルプログラムをコピペで貼り付けます。
ブラウザで表示するチャットボットのサンプルプログラムは以下になります。コピペして、ファイル名を「index.html」として「PicoW」のメインフォルダに保存して下さい。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChotGPT(gpt-3.5-turbo)</title>
<style>
body {
background-color: #333;
color: #ccc;
font-family: Arial, sans-serif;
margin: auto;
max-width: 800px;
}
h1 {
margin: 15px 0 5px;
text-align: center;
}
form {
align-items: center;
display: flex;
flex-direction: column;
margin-top: 30px;
}
label {
display: block;
font-size: 16px;
margin-bottom: 5px;
}
textarea {
border: none;
border-radius: 10px;
height: 100px;
margin-bottom: 3px;
padding: 10px;
width: 80%;
}
#token-price {
font-size: 12px;
margin-bottom: 3px;
}
button {
background-color: #bb2944;
border: none;
border-radius: 5px;
color: #FFF;
cursor: pointer;
font-size: 16px;
margin-bottom: 30px;
padding: 5px 20px;
width: 120px;
}
#chat-area {
color: #333;
}
.question-balloon, .answer-balloon {
border-radius: 10px;
margin: 20px;
max-width: 80%;
padding: 10px;
position: relative;
width: 80%;
}
.question-balloon {
background-color: #f9e7d9;
margin-left: 10%;
text-align: right;
}
.question-balloon::before {
border: 15px solid transparent;
border-right: 15px solid #f9e7d9;
content: '';
margin-top: -15px;
position: absolute;
right: -15px;
top: 50%;
}
.answer-balloon {
background-color: #dafbfb;
margin-right: 10%;
text-align: left;
}
.answer-balloon::before {
border: 15px solid transparent;
border-left: 15px solid #dafbfb;
content: '';
left: -15px;
margin-top: -15px;
position: absolute;
top: 50%;
}
.loading {
color: #aaa;
margin: 20px;
margin-left: 20px;
padding: 10px;
}
/* .loadingクラスに対するアニメーション定義 */
@keyframes blinking {
0% { opacity: 0.3; }
50% { opacity: 1; }
100% { opacity: 0.3; }
}
/* .loadingクラスのスタイル設定とアニメーション適用 */
.loading {
animation-name: blinking; /* アニメーション名 */
animation-duration: 2s; /* アニメーション時間 */
animation-delay: 0s; /* アニメーション開始までの遅延時間 */
animation-iteration-count: infinite; /* 無限に繰り返し */
}
@media only screen and (max-width: 480px) {
body {
margin: 0;
padding: 0;
width: 100%;
}
h1 { font-size: 24px; }
}
</style>
</head>
<body>
<h1>ChotGPT(gpt-3.5-turbo)</h1>
<div id="chat-area"></div>
<form>
<label for="question-input">質問を入力して送信ボタンを押してください</label>
<textarea id="question-input" required></textarea>
<div id="token-price"></div>
<button type="submit">送信</button>
</form>
<script>
let allTokens = 0; // トークン合計
let allPrices = 0; // API使用料合計
const tokenPriceDisp = document.getElementById("token-price");
// トークン数から金額換算、ブラウザ表示処理関数
function tokensPrice(total_tokens) {
const rate = 130; // ドル-円 レート
let totalPrice = total_tokens * (0.002 * rate) / 1000; // 質問ごとのAPI使用料(単価)
allTokens += total_tokens // トークン合計を加算
allPrices += totalPrice; // API使用料合計を加算
tokenPriceDisp.innerText ="単価:¥" + totalPrice.toFixed(3) + "(Token:" + total_tokens + ")/合計:¥" + allPrices.toFixed(2) + "(Token:" + allTokens + " ※$1=¥" + rate + ")";
}
tokensPrice(0);
// トークン数取得関数
function getToken(responseJson) {
// コンソールログに表示
console.log(responseJson);
const prompt_tokens = responseJson.usage.prompt_tokens;
const completion_tokens = responseJson.usage.completion_tokens;
const total_tokens = responseJson.usage.total_tokens;
console.log("送信トークン数:" + prompt_tokens);
console.log("受信トークン数:" + completion_tokens);
console.log("合計トークン数:" + total_tokens);
tokensPrice(responseJson.usage.total_tokens); // トークン数から金額換算ブラウザ表示
}
const chatArea = document.getElementById('chat-area');
const form = document.querySelector('form');
// フォーム送信時の処理
form.addEventListener('submit', async (e) => {
e.preventDefault();
const questionInput = document.getElementById('question-input');
const question = questionInput.value.trim();
if (!question) {
return;
}
// 質問の吹き出しを作成
const questionBalloon = document.createElement('div');
questionBalloon.classList.add('question-balloon');
questionBalloon.textContent = question;
chatArea.appendChild(questionBalloon);
questionInput.value = '';
// 回答を取得する前に Loading... を表示する
const loadingDiv = document.createElement('div');
loadingDiv.innerHTML = 'Loading...';
loadingDiv.classList.add('loading');
chatArea.appendChild(loadingDiv);
// fetchAPIでPicoWに質問を送信してレスポンスをJSON形式で取得
const url = '/question'; // リクエスト先URL
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ question })
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const responseJson = await response.json();
getToken(responseJson); // トークン数取得関数実行
// 回答を取得
let answer = responseJson.choices[0].message.content;
answer = answer.replace(/\n/g, '<br>');
chatArea.removeChild(loadingDiv); // 回答が取得できたら Loading... を削除する
// 回答の吹き出しを作成
const answerBalloon = document.createElement('div');
answerBalloon.classList.add('answer-balloon');
answerBalloon.innerHTML = answer;
chatArea.appendChild(answerBalloon);
} catch (error) {
console.error('There was a problem with the fetch operation:', error);
return { error: true, message: error.message };
}
});
</script>
</body>
</html>
「コピペ」で貼り付けたら下画像のように[保存]ボタンを押します。
保存場所を聞いてくるので「CircuitPython」をクリックします。
下画像のようなウインドウが表示されたら[ファイル名:]のところに「index.html」と入力して[OK]ボタンをクリックします。
これでチャットボットをブラウザで表示する「html」のプラグラムは準備完了です。
・環境変数設定ファイル(settings.toml)
次にWi-Fi接続先の「SSID」「パスワード」と「APIキー」を保存しておく「settings.toml」ファイルを設定します。
「settings.toml」は「PicoW」内に既にあるので、下画像のように[開く]ボタンを押します。
ファイルを開く場所は「CircuitPython」を選択します。
下画像のようなウインドウが表示されたら「settings.toml」を選択して、[OK]ボタンをクリックします。
初期の「settings.toml」には何も記入されていないため、下のファイルを「コピペ」して貼り付けます。
Wi-Fi接続のための「SSID」「パスワード」と「APIキー」を保存しておく環境設定ファイル「Setting .toml」のサンプルファイルは以下になります。自分の環境に合わせて設定してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
CIRCUITPY_WIFI_SSID="自宅のWi-Fi接続先SSIDを設定"
CIRCUITPY_WIFI_PASSWORD="自宅のWi-Fi接続パスワードを設定"
OPENAI_API_KEY="OpenAIから取得したAPIキーを設定"
「コピペ」で貼り付けて各情報を入力したら[保存]ボタンをクリックして保存します。
これで環境設定ファイル「settings.toml」ファイルは準備完了です。
・必要なライブラリの準備
最後に必要な「ライブラリ」とOLED表示器の「フォント」を準備します。
ライブラリの追加方法は以下のリンクを参照して下さい
ライブラリは以下の「Adafruit」社のホームページからダウンロードして下さい。
ダウンロードするライブラリは「PicoW」にインストールした「CircuitPython」のバージョンに合わせて下さい。バージョンは「Thonny」の「シェル」部に表示されています。
今日時点の最新のものはバージョン8.1.0の「circuitpython-community-bundle-8.x-mpy-20230523.zip」になります。
ライブラリをダウンロードしたらパソコンのダウンロードフォルダに以下のようなファイルがあるので展開してフォルダを開きます。
ダウンロードしたライブラリフォルダを開くと下画像のように「examples」と「lib」フォルダがあります。
「examples」フォルダにはOLED表示器の「フォント」ファイルが入っています。
「lib」フォルダには「ライブラリ」ファイルが入っています。
上画像の2つのフォルダの中から必要なファイルを以下のように「PicoW」内部の各フォルダにコピーして保存していきましょう。
まずは、パソコンに認識されている「PicoW」フォルダを開きます。フォルダの内容は下画像のようになっています。
下画像の「PicoW」本体のルートに、ダウンロードした「example」フォルダの中から「フォント」ファイル「font5x8.bin」を以下のようにコピーします。
次に下画像のように「PicoW」の方の「lib」フォルダを開いて、ダウンロードした「lib」フォルダの中から下画像のように4つの「ライブラリ」ファイルをコピーします。
以上で「チャットボット」を使用する準備が整いました。
動作確認
準備が整ったら動作確認をしてみましょう。
下画像のように[code.py]タブをクリックして[実行]ボタンをクリックします。
Wi-Fi接続が開始されてサーバーが起動するとIPアドレス(値は環境によって異なります。)が表示されるので、これをブラウザのアドレス(URL)バーに入力します。
サーバーへの接続に成功すると「シェル」部に下画像のように表示されます。
環境によって異なりますが、以下の「192.168.0.9」はアクセスしてきたクライアントのIPアドレスです。「”200 OK”」が表示されていたら通信は成功です。
ブラウザには下画像のような画面が表示されるので、白いテキストエリアに「こんにちは」と入力して[送信]ボタンを押すと、質問が送信されます。
回答が返ってくるまでは少し時間がかかるのでしばらく待ちます。しばらくすると回答が返ってきます。
「Tonny」の「シェル」部には下画像のように、質問と回答意外にも送受信した詳細情報が表示されるので、データ構造を確認できます。
続けて質問するとチャットのように回答が得られるので、いろいろ質問してみましょう♪
5.まとめ
「Raspberry Pi PicoW」を使用した「チャットボット」の作り方を詳しく紹介しました。
小さなマイコンボードでもWi-Fi通信機能があれば、他の機種でも同じように作成することができます。
「ChatGPT」は基本無料で使用できますが、無料版では機密情報の質問は学習モデルとして使用される可能性があるため避けた方が良いのです。この場合は有料版も検討されますが、20ドルとそれなりに費用がかかります。
「API」を使用した方法であれば安くて、使った分だけ支払えば良いため、上手に使えば低コストで多くの情報を得ることができます。
今回は「CircuitPython」で実現しているため「Python」で「openai」ライブラリを使用するよりも少々手間ではありますが、今回の使い方を理解すると、言語が変わってもデータ構造は同じため、他の言語で「OpenAI」の「API」を使うことも容易になります。
今回の「チャットボット」の「html、CSS、JavaScript」について、私には作成するスキルはないため、ベースは「ChatGPT」に書いてもらいましたw、それなりに動いたので引き続き「ChatGPT」とのやり取りで完成させています。ほんと便利になりました。
「ChatGPT」は自分自身ではコードを書いてもらう以外は使い道が思いつきませんでしたが、細かい設定ができると色々使い道は広がりそうです。
便利な使い方ができたらまた紹介していきたいと思います。
コメント