【ChatGPT API × ラズパイ × LINEbot】ChatGPT君、きょう何着て行く？

# 春の服選びはめんどくさい
コートを着て行くと暑くなり、パーカーで出かけると寒くなる。かと思えば半袖が必要なくらい暑い日もある。

困りませんか？困りますよね。

**そんなときは！**

みんな大好き**ChatGPT**君に**LINE**して何を着ればいいか教えてもらいましょう:robot:

# 事前準備
以下の準備は先人たちの知恵をお借りして、各自準備をお願いします。
書いてある通りにやれば動く、とってもわかりやすい記事を集めました。

- ラズベリーパイ初期設定：https://sozorablog.com/raspberrypi_initial_setting/
- LINEbot(Python)：https://note.com/khe00716/n/n34bb4c087fdc

OpenAIのアカウント作成もお忘れなく。
- Sign up：https://chat.openai.com/auth/login

# ChatGPT APIの料金体系について知ろう
ブラウザでChatGPTを使うのは無料(注:課金バージョンも有り)ですが、APIは基本的に**有料**です。高額請求が来るのが怖かったので、先人の知恵をお借りし、料金体系について勉強しました！

- ChatGPT API課金仕様解説：https://auto-worker.com/blog/?p=7459

ふむふむ…

ChatGPTのAPIは**トークン**という単位で課金されるのですね。
記事によると、トークン量を減らして利用料金を抑えるためには以下のことに気を付ければいいようです。
- APIに含めるやり取りは直前n回までと設定する
- max_tokensの上限を設定する
- 英語でやり取りする

また高額請求を避けるため、APIの利用上限を設定することも必要そうです。

# 機能設計
当初はこんなアプリを作ろうと考えていました。
![IMG_7271.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/86da9cb8-a28b-7a29-2fd4-90079bc47c6d.jpeg)

まずはChatGPT君が期待する質問に答えてくれるか確認するため、ブラウザ版を利用したテストを行いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/4a1e631a-2d1d-015d-f4a3-ef784c1f4461.png)
**むむむ…！？**

なんか、思ってたのと違う:neutral_face:

何度やってもChatGPT君は、最高気温と最低気温それぞれに合わせたコーディネートを1つずつ提案してくる:neutral_face:

ということで初期構想の機能は一旦あきらめ、
**わたし：「今日の気温は15度」**
**ChatGPT：「パーカーに長袖長ズボンを着ましょう」**
くらいの単純な機能から実装することにしました！

# コーディング
以下のコードを作成しました。
- APIkeyの発行・APIの利用部分で参考にさせていただいたサイト：https://zenn.dev/umi_mori/articles/chatgpt-api-python
```python:app.py
from flask import Flask, request, abort
#解説１
from translate import Translator
import json
from linebot import (
   LineBotApi, WebhookHandler
)
from linebot.exceptions import (
   InvalidSignatureError
)
from linebot.models import (
   MessageEvent, TextMessage, TextSendMessage,
)
import openai
import os

# ご自身で取得したキーを入力
line_bot_api = LineBotApi('Channel access token')
handler = WebhookHandler('Channel secret')
openai.api_key = 'Openai apikey'

# 解説２
def chat_chatgpt(lineRes) :
    chatgpt_res = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {
                "role": "user",
                "content": 'Suggest one of best outfits in ' + str(lineRes) + 'C with 10words limit'
            }
        ]
    )
    res_eng = chatgpt_res["choices"][0]["message"]["content"]
    translator = Translator(from_lang='en', to_lang='ja')
    res = translator.translate(res_eng)
    return res


app = Flask(__name__)

@app.route("/callback", methods=['POST'])
def callback():
   # get X-Line-Signature header value
   signature = request.headers['X-Line-Signature']
   
   # get request body as text
   body = request.get_data(as_text=True)
   app.logger.info("Request body: " + body)
   
   # handle webhook body
   try:
       handler.handle(body, signature)
   except InvalidSignatureError:
       print("Invalid signature. Please check your channel access token/channel secret.")
       abort(400)
   return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
   lineRes = event.message.text
   botRes = 'ピピピ…今日ノ気温ヲ入力シテクダサイ…'
   
   # 解説３
   if lineRes.isdecimal():
        botRes = chat_chatgpt(lineRes)

   line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=botRes))


if __name__ == "__main__":
   app.run()
```
### 解説１
ChatGPTは英語でやり取りするとトークン量を減らすことができますが、このサービスのユーザは日本語話者が多いと想定しています。
そこで、Pythonの**Translatorライブラリ**を利用し、ユーザがLINE上でChatGPT君と日本語でやり取りできるようにしました。
- Translatorライブラリの使い方：https://qiita.com/FKjujcc/items/c7e206bec306da891573

### 解説２
ChatGPT君に話しかけるための関数です。ユーザの入力値(数値)を受け取り、ChatGPTにリクエストを投げます。

**Suggest one of best outfits in ' + str(lineRes) + 'C with 10words limit**を和訳すると、「**10語以内**でおすすめのコーディネートを1つ提案してください」になります。10語以内縛りにした理由はこちらです👇
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/6572f023-3697-0e84-8f3a-2803759cdd90.png)
ChatGPT君によると、10語を超えるかどうかでトークン使用量が2変わるようなので、無理やりリクエスト・レスポンスともに10語以下になるように設定しました(ドケチ)
ほんとにChatGPT君はなんでも教えてくれますね。

### 解説３
ユーザとLINEでやり取りするための関数です。
ユーザの入力値が数値の場合ChatGPTとやり取りするための関数**chat_chatgpt**を呼び出します。数値以外の場合はデフォルトで設定した返答をユーザに返します。

コードの解説は以上となります。

# 動作確認
動くかわかんないけど、とりあえず実行！！
LINEで話しかけてみた！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/2046a598-fad6-2a91-99ef-efc6170089c5.png)

・・・・・・使えちゃった:neutral_face:

あれ、でもさっき調べたトークンやAPIの上限値設定とかしてないんだけど…？
そもそもクレカも何も登録してないんだけど…？？？
支払いどうなってるのーーーー！？！？！？:scream:

# ChatGPT APIの支払いどうなってるの？
ということで調べました。
おそるおそる検索してみると、支払いの設定などができるサイトがあるとの情報を入手！
- サイト：https://platform.openai.com/
**Personal⇒Manage Account⇒Uasge**でAPIの使用量が確認できます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/bac4bff7-bf17-3230-502f-10dcefddb0c5.png)
わ！ちゃんと使ったことになってる！！
0.00025ドルなら大丈夫だ、払える:joy:
(というかこのページAPIkey発行で使ったな・・・)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/b3c84c8d-3926-8d9e-66e2-0b65c747e4f7.png)
あれ？Free trial Usage…？18ドル分…！？！？

これについても調べました。

ChatGPTのAPIには**初回3か月の無料トライアル使用枠**があるようです。
私がOpenAIのアカウントを作成したのはたしか2月頃なので、5/1で3か月ということなのでしょう。ただし、現在は無料使用枠は**5ドル/3か月**に減ってしまっているようです。

無料使用枠が終了してもAPIが使えなくなるだけで、勝手に課金されることはないようです。よかった:sweat_smile:

ちなみにAPI使用量の上限は**Billing⇒Usage limits**で設定できますが、無料使用枠を使っている間は設定ができないようです。

- Pricing：https://openai.com/pricing
- What happens after I use my free tokens or the 3-months is up in the free trial?：https://help.openai.com/en/articles/4936830-what-happens-after-i-use-my-free-tokens-or-the-3-months-is-up-in-the-free-trial

ちなみにちなみに、以下の設定方法はChatGPT君が教えてくれましたが、今回は~~めんどくさいので~~やり取りの文字数制限をしていることもあり、実装はしません。
- APIに含めるやり取りは直前n回までと設定する
- max_tokensの上限を設定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/dda884fa-0f5a-600a-316b-284774c8c4c9.png)
```python:made_by_chatgpt.py
import openai
openai.api_key = "YOUR_API_KEY"

prompt = "Hello, how are you doing today?"
model = "text-davinci-002"
max_tokens = 50
temperature = 0.5
max_history = 2

response = openai.Completion.create(
    engine=model,
    prompt=prompt,
    max_tokens=max_tokens,
    temperature=temperature,
    max_history=max_history
)

print(response.choices[0].text)
```
- APIリファレンス：https://platform.openai.com/docs/api-reference/completions/create

# 機能追加
ユーザが気温を入力するのは少しイケてないですよね。。。
そこで、ユーザが質問すればお天気APIから今日の気温を取得して服装を提案する機能を追加してみました！

- OpenWeatherAPIのAPIkeyの発行・APIの利用部分で参考にさせていただいたサイト：https://boook24.com/?p=1507

```python:app.py
from flask import Flask, request, abort
from translate import Translator
import json
from linebot import (
   LineBotApi, WebhookHandler
)
from linebot.exceptions import (
   InvalidSignatureError
)
from linebot.models import (
   MessageEvent, TextMessage, TextSendMessage,
)
import openai
import os

# ご自身で取得したキーを入力
line_bot_api = LineBotApi('Channel access token')
handler = WebhookHandler('Channel secret')
openai.api_key = 'Openai apikey'

# added
import requests
weatherAPI='https://api.openweathermap.org/data/2.5/weather'
params={
    # ご自身で取得したキーを入力
    "appid": "OpenWeatherAPIkey",
    "q": "TOKYO",
    "units": "metric",
    "lang": "ja"
}

def get_temperature():
    weather = requests.get(weatherAPI, params=params).json()
    temp = weather["main"]["temp"]
    return temp
# added

# modified
def chat_chatgpt() :
    temp = get_temperature()
    chatgpt_res = openai.ChatCompletion.create(
       model="gpt-3.5-turbo",
       messages=[
           {
               "role": "user",
               "content": 'Suggest one of best outfits in ' + str(temp) + 'C with 10words limit'
           }
       ]
   )
    res_eng = chatgpt_res["choices"][0]["message"]["content"]
    translator = Translator(from_lang='en', to_lang='ja')
    res = translator.translate(res_eng)
    return res
# modified

app = Flask(__name__)

@app.route("/callback", methods=['POST'])
def callback():
   # get X-Line-Signature header value
   signature = request.headers['X-Line-Signature']
   
   # get request body as text
   body = request.get_data(as_text=True)
   app.logger.info("Request body: " + body)
   
   # handle webhook body
   try:
       handler.handle(body, signature)
   except InvalidSignatureError:
       print("Invalid signature. Please check your channel access token/channel secret.")
       abort(400)
   return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
   lineRes = event.message.text
   botRes = 'ピピピ…服装ヲ提案シマス…'
   
   # modified
   if '何着' in lineRes:
       botRes = chat_chatgpt()
   # modified
   
   line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=botRes))


if __name__ == "__main__":
   app.run()
```
詳しい説明は省きますが、addedとmidifiedで挟まれているところが最初のコードとの差分になります！

# 完成！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958610/b6dee1bd-1d41-89e4-0b48-69ec162bb7f7.png)

・・・クロップトップ？コンバットブーツ？

クロップトップ、調べてみたら露出が多くて今日の気温(18度くらい)だと寒そうです:joy:
とにもかくにも、ChatGPT君からLINEで服装のアドバイスをもらえるようになりました:raised_hands:

# 参考にしたサイト
たくさんの先人の知恵をお借りしました。感謝申し上げます。
- 【2023年最新版】OSインストールから初期設定まで｜セットアップ手順のすべて(sozorablog)：https://sozorablog.com/raspberrypi_initial_setting
- LINEのメッセージでRaspberryPIのLEDをON／OFFしてみる（その１）設定(ゆう)：https://note.com/khe00716/n/n34bb4c087fdc
- ChatGPTのAPIの利用料金を詳しく解説！使い方次第でトークン増大で高額費用も(AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門)：https://auto-worker.com/blog/?p=7459
- 【最新速報】ChatGPT APIの「概要と使い方」（Pythonコード付き）：https://zenn.dev/umi_mori/articles/chatgpt-api-python
- PythonでライブラリTranslatorの使い方の簡単な説明：https://qiita.com/FKjujcc/items/c7e206bec306da891573
- Pricing(OpenAI)：https://openai.com/pricing
- What happens after I use my free tokens or the 3-months is up in the free trial?(OpenAI)：https://help.openai.com/en/articles/4936830-what-happens-after-i-use-my-free-tokens-or-the-3-months-is-up-in-the-free-trial
- API REFERENCE：https://platform.openai.com/docs/api-reference/completions/create
- 【python/javascript】OpenWeather 天気取得のAPI：https://boook24.com/?p=1507