LINE BotとOpenAI APIを使ってシンプル会話Botを作る

# はじめに
Pythonのline-bot-sdkやopenaiモジュールなど使い、LINE上でGPT-4oが応答してくれる超シンプルなBotを作ってみます。

流れのイメージはこんな感じです。

![図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/80c87ff0-2b2f-4f0f-9c1e-30f43a3d1e0a.png)

1. ユーザがLINEでメッセージ送信
2. Webhook URL（https\://xxxx.ngrok-free.app/callback）に対してPOSTリクエストが送信される
3. ngrokにより、/callbackエンドポイントがローカルのPython実行環境に渡される
4. Pythonのコード内で一連の処理を行い、ユーザに結果が返される

# 動作環境
- GPT-4o
- Python 3.8
- ubuntu 20.04（Python実行環境）
- ngrok

# 各種アカウント準備
以下、必要なアカウントの準備をしていきます。
- LINE Messaging API
- OpenAI API
- ngrok

### LINE Messaging API
LINEのAPIを使う準備を行います。
以下を参考に、LINE公式アカウントの作成と、Messaging APIの有効化を行います。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

LINE Official Account Managerからアイコンやチャンネル名など設定できるので、適当に設定しておきます。
![無題5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/c244f064-7ffb-4a0a-8d04-37840bd13eba.png)

### OpenAI API
OpenAIのAPIを使う準備を行います。
[OpenAI](https://auth.openai.com/create-account)からアカウント作成orサインインします。
※ChatGPTなど使っている場合はそのままのアカウントで入れます。

サインインできたら、Billing > Add to credit balanceからAPI利用のための金額をチャージしていきます。
※デフォルトだと自動チャージが有効になっていた気がするので、注意が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/b1aae2a5-5bb8-4eea-97ce-fe4f649dd0f2.png)

### ngrok
今回はローカルのPC上で動かすため、ローカルのサーバ（localhost）を一時的にインターネットに公開してくれる、ngrokというツールを使います。（便利！）
使うにはアカウント登録が必要なので、https://ngrok.com/ から登録します。

登録できたら、Your Authtokenから後々使うトークンを確認しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/66eba5f0-f5a5-413f-92f5-232526ea94be.png)

ちなみにngrokは、無料版だと1環境まで外部公開できるみたいです。

# 実装
### ngrokインストール
まずは以下に従って、ローカルの実行環境にngrokをインストールします。

https://ngrok.com/downloads/linux?tab=install

インストールできたら、手順にある通り、tokenを設定します。
登録した際に確認した、Your Authtokenを\<token>に入れます。
```
ngrok config add-authtoken <token>
```

### Python実行環境準備

次にPythonの実行環境を用意していきます。
※当方**Python初心者**のため、このあたりはChatGPTの力を借りて進めていきました。間違いがあったらすみません・・・m(__)m

一時的な作業なので、仮想環境を作成して実施します。
```
mkdir line-gpt-bot ; cd line-gpt-bot

# 仮想環境作成
python3 -m venv venv

# 有効化
source venv/bin/activate
```
続いて、今回必要なライブラリをインストールしていきます。
```
pip install flask line-bot-sdk openai==0.28 python-dotenv
```

ライブラリをインストールできたら、ファイルを作成していきます。

```txt:ファイル構成
line-gpt-bot/
├──  .env
├──  app.py
```

#### 変数定義ファイル

まず、`.env`ファイルで環境変数を定義します。

```txt:.env
LINE_CHANNEL_SECRET=チャネルシークレット
LINE_CHANNEL_ACCESS_TOKEN=チャネルアクセストークン
OPENAI_API_KEY=OpenAIのAPIキー（sk-から始まるもの）
```

上2つはLINE Developersのコンソール > チャネル基本設定、Messaging API設定から確認可能です。

![無題6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/17c54521-4952-4f58-bff2-200d31510660.png)


OpenAIのAPIキーは、OpenAIのPlatform > Create new secret keyから作成したものを指定します。
![無題3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/567f5f18-ac7e-477f-97b3-c12473905225.png)

#### app.py
続いて、動作の大元となるPythonファイルを作成します。

```py:app.py
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

from dotenv import load_dotenv
import os
import openai

# .envからキーを読み込む
load_dotenv()
line_bot_api = LineBotApi(os.getenv("LINE_CHANNEL_ACCESS_TOKEN"))
handler = WebhookHandler(os.getenv("LINE_CHANNEL_SECRET"))
openai.api_key = os.getenv("OPENAI_API_KEY")

# Flaskアプリ作成
app = Flask(__name__)

# Webhookを受けるルート
@app.route("/callback", methods=["POST"])
def callback():
    signature = request.headers["X-Line-Signature"]
    body = request.get_data(as_text=True)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return "OK"

# メッセージイベントの処理
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    user_message = event.message.text

    # OpenAI GPT-4oで返答を生成
    response = openai.ChatCompletion.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "あなたは丁寧なAIアシスタントです。"},
            {"role": "user", "content": user_message}
        ]
    )

    ai_reply = response["choices"][0]["message"]["content"]
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=ai_reply)
    )

if __name__ == "__main__":
    app.run()
```

今回使っているライブラリが何者なのか、自分なりの理解は以下の通りです。
※ふわっと理解
- flask
    - Python製の軽量Webアプリケーションフレームワーク。
    - 今回は、Webhookから受け取ったhttpリクエストを「/callback」という受け口でさばいてくれる。
- line-bot-sdk
    - LINE公式から提供されている、PythonでLINE Messaging APIを使用してBotを開発するためのパッケージ。
    - 今回は、Webhookから受け取ったリクエストのパース処理※をしている。
    ※リクエストの中身を取り出して扱いやすい形にする処理
    - https://github.com/line/line-bot-sdk-python
- openai==0.28
    - OpenAI公式から提供されている、PythonでOpenAIのAPIを呼び出すためのパッケージ。
    - 今回は、GPT-4oへのメッセージ送信と応答取得の処理をしている。
    - ※0.28は古いバージョンらしく、1.0以降になるとコードの書き方が大きく変わるとのことなので、0.28をバージョン指定してインストールしました。
    - https://github.com/openai/openai-python
- python-dotenv 
    - `.env` ファイルに記載したAPIキーやトークンなどの環境変数を、Python内で自動的に読み込めるようにするためのパッケージ。 
    - `.env` に書いた内容を `os.getenv("KEY名")` で取得できるようになる。 
    - https://github.com/theskumar/python-dotenv


# 動作確認

### ngrokで公開

以下コマンドでlocalhostの5000ポートを外部公開します。

```
ngrok http 5000
```
実行すると、以下のように出力されます。
`https\://xxx.ngrok-free.app` が、localhost:5000へアクセスするURLになります。
```
ngrok　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　(Ctrl+C to quit)

??  ngrok? We're hiring https://ngrok.com/careers

Session Status                online
Account                       <メールアドレス> (Plan: Free)
Version                       3.22.1
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://xxx.ngrok-free.app -> http://localhost:5000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
```

### Webhook設定
上記で公開された`https\://xxx.ngrok-free.app`に、flaskの受け口である`/callback`を付けて、Webhook URLに設定します。
LINE Developersのコンソール > Messaging API設定から、Webhook設定という項目があるので、Webhook URLを入れ、Webhookの利用をオンにします。

![無題4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/748b99fa-ddf0-4155-afb4-a89a0f33e69c.png)

### Python実行
ngrokとは別のターミナルを開き、先ほど作成した`app.py`を実行します。
```
python app.py
```
```bash:実行結果
(venv) root@MyVM:~/Test/line-openai-bot# python app.py
app.py:12: LineBotSdkDeprecatedIn30: Call to deprecated class LineBotApi. (Use v3 class; linebot.v3.<feature>. See https://github.com/line/line-bot-sdk-python/blob/master/README.rst for more details.) -- Deprecated since version 3.0.0.
  line_bot_api = LineBotApi(os.getenv("LINE_CHANNEL_ACCESS_TOKEN"))
app.py:13: LineBotSdkDeprecatedIn30: Call to deprecated class WebhookHandler. (Use 'from linebot.v3.webhook import WebhookHandler' instead. See https://github.com/line/line-bot-sdk-python/blob/master/README.rst for more details.) -- Deprecated since version 3.0.0.
  handler = WebhookHandler(os.getenv("LINE_CHANNEL_SECRET"))
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

```

### LINEからメッセージを送ってみる
これで準備は整ったので、いよいよメッセージを送ってみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/269ee547-3874-4855-8154-87d38d9b9625.png)

返ってきました！

# おまけ
今回はgpt-4oを使いましたが、他のバージョンだとどうなのか、旧モデルのgpt-4との回答を比較してみました。

gpt-4の回答

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/d46d673a-df12-41d0-bb2f-c71c5adf847f.png)

gpt-4oの回答

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075592/0fb30d94-2cc0-43a5-b096-39bd7e3d8471.png)

面白いかは置いておいて、gpt-4oの方がスマートになってますね！笑

# 感想
ChatGPTはずっと使っていましたが、APIを使って何かやるのは初めてだったので、良い勉強になりました。
今回はシンプルBotでしたが、今後はさらに、会話履歴を保持したり、webに検索しにいく処理を入れたり、画像処理を入れたり…等々、拡張の幅は広がるなと思いました！