ChatGPT APIを用いたLINE botに音声認識機能を搭載してみた。

[ChatGPT APIを用いたLINE bot](https://qiita.com/katsuki_ono/items/41d995607516b6ce8691)に音声認識機能を付けてみました。

この機能を搭載する事で、LINE botはテキストだけでなく、音声を送っても返答してくれるようになります。

使用しているAWSのインスタンスのインスタンスイメージはubuntuです。
音声認識には、Openai APIのWisperを使用しています。

手順は、ChatGPT APIを用いたLINE botに音声認識機能を付けるだけです。
簡単に、音声認識機能を搭載したChatGPT APIを用いたLINE botのコード解説も付けます。

# ⒈ChatGPT APIを用いたLINE botに音声認識機能を付ける
まず、インスタンスにSSH接続します。

インスタンスのコンソールが開いたら以下をインストールします。

aptのアップデート
```
sudo apt update
```

pipのインストール
```
sudo apt install python3-pip
```

LINEのインストール
```
sudo pip install line-bot-sdk
```

ngrokのインストール
```
sudo pip install pyngrok
```

flaskのインストール
```
sudo pip install flask
```

openaiのインストール
```
sudo pip install openai
```

pydubのインストール
```
sudo pip install pydub
```

次にpythonのファイルを保存するディレクトリを作成します。
```
mkdir ディレクトリ名
```

作成したディレクトリに移動します。
```
cd ディレクトリ名
```

作成したディレクトリでファイルを作成します。
```
nano ファイル名
```

作成したファイルの中に下記のbotのコードを入れて、ctrl oでファイルを保存して、ctrl xでファイルを閉じます。
※LINE、ngrok、openaiのキーやトークンをコードに入れてください。これらの取得については、[こちら](https://qiita.com/katsuki_ono/items/df8830c8b07e3a59f354)を参考にしてください。

``` python
# LINEのチャネルシークレット
LINE_CHANNEL_SECRET = ''
# LINEのチャネルアクセストークン
LINE_ACCESS_TOKEN = ''
# ngrokのAuthtoken
NGROK_AUTHTOKEN = ''
# openaiのキー
OPENAI_API_KEY = ""


import os
import requests

from pyngrok import ngrok
from pyngrok.conf import PyngrokConfig

from flask import Flask, request, abort

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, AudioMessage, TextSendMessage, TextMessage, ImageSendMessage

from pydub import AudioSegment

import openai


# URLの生成
ngrok.set_auth_token(NGROK_AUTHTOKEN)
os.system('kill -9 $(pgrep ngrok)')
https_tunnel = ngrok.connect(addr='127.0.0.1:5000',bind_tls=True)
print(https_tunnel)


app = Flask(__name__)


openai.api_key = OPENAI_API_KEY
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
handler = WebhookHandler(LINE_CHANNEL_SECRET)


@app.route("/test")
def test():
    return "TEST OK"

@app.route("/", 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'



assist1 = ""  # 初期化

# テキストメッセージを処理
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    global assist1  # assist1をグローバル変数にする

    text = event.message.text
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "任意の設定"},
            {"role": "assistant", "content": assist1},
            {"role": "user", "content": text},
        ]
    )
    assist1 = response["choices"][0]["message"]["content"]  # assist1を更新
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response["choices"][0]["message"]["content"]),
    )


# 音声メッセージを処理
def transcribe_audio(file_path):
    with open(file_path, "rb") as audio_file:
        response = openai.Audio.transcribe("whisper-1", audio_file)
    return response.text

# LINEの音声認識
@handler.add(MessageEvent, message=AudioMessage)
def handle_audio_message(event):
    message_content = line_bot_api.get_message_content(event.message.id)

    audio_data = b''
    for chunk in message_content.iter_content():
        audio_data += chunk

    audio_format = "m4a"
    audio_filename = f"audio_{event.message.id}.{audio_format}"

    with open(audio_filename, "wb") as f:
        f.write(audio_data)

    transcript = transcribe_audio(audio_filename)

    # 音声ファイルを削除する
    os.remove(audio_filename)

    global assist1  # assist1をグローバル変数にする

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "任意の設定"},
            {"role": "assistant", "content": assist1},
            {"role": "user", "content": transcript},
        ]
    )
    assist1 = response["choices"][0]["message"]["content"]  # assist1を更新
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response["choices"][0]["message"]["content"]),
    )


if __name__ == "__main__":
    app.run(port=int(os.getenv("PORT", 5000)))

```

一度、上手くファイルが作成されているか下記のコマンドで確認します。
```
find ~
```

上手くファイルが作成されていたら、screenを作成します。
```
screen -S スクリーン名
```

ルート権限で、screen内でpythonで作成したファイルを実行します。
```
sudo python3 ファイル名.py
```

実行したら、生成されたURLを取得して、LINE Developers のダッシュボードにwebhook URLを設定します。(詳しくは、[こちら](https://qiita.com/katsuki_ono/items/df8830c8b07e3a59f354)を参考にしてください。)

※稀にエラーが発生します。エラーが発生した場合、エラー処理して頂くか、インスタンスを再起動してください。

webhook URLを設定後、LINE Developersで検証を押します。
成功と表示されたら、上手く接続出来ています。(インスタンス側では、上手くいっている場合は200と返されます。500の場合、プログラムに問題ありなどなので、普通のエラーと同じです。)

ここまで出来たら、LINE  botを友達登録してテキストと音声で、話しかけてみてください。(音声入力については、LINEのテキスト入力の右横のボタンを押すと音声入力のボタンが現れます。)
 それぞれで、botから返答があれば、成功です！

音声だと以下のようになります。
![IMG_8186.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3287642/630f98d2-b2e2-e4e5-b49a-0f3686ad1902.jpeg)

# ⒉音声認識機能を搭載したChatGPT APIを用いたLINE botのコード解説
簡単に音声認識機能を搭載したChatGPT APIを用いたLINE botのコード解説をします。

## トークンやキーの入力
LINEとngrok、openaiに必要な情報を入力します。　
``` python
# LINEのチャネルシークレット
LINE_CHANNEL_SECRET = ''
# LINEのチャネルアクセストークン
LINE_ACCESS_TOKEN = ''
# ngrokのAuthtoken
NGROK_AUTHTOKEN = ''
# openaiのキー
OPENAI_API_KEY = ""
```

## インポート
ngrokとLINEに必要なインポートを行なっています。
``` python
import os
import requests

from pyngrok import ngrok
from pyngrok.conf import PyngrokConfig

from flask import Flask, request, abort

from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, AudioMessage, TextSendMessage, TextMessage, ImageSendMessage

from pydub import AudioSegment

import openai
```

## webhook URLの生成
webhook用にngrokをインストールして、URLを作成しています。
この処理の為に管理者権限で実行する必要があります。
``` python
# URLの生成
ngrok.set_auth_token(NGROK_AUTHTOKEN)
os.system('kill -9 $(pgrep ngrok)')
https_tunnel = ngrok.connect(addr='127.0.0.1:5000',bind_tls=True)
print(https_tunnel)
```

## LINE botを実行するにあたり必要な処理
LINE botに必要な基本的な機能等を記載します。
``` python
app = Flask(__name__)


openai.api_key = OPENAI_API_KEY
line_bot_api = LineBotApi(LINE_ACCESS_TOKEN)
handler = WebhookHandler(LINE_CHANNEL_SECRET)


@app.route("/test")
def test():
    return "TEST OK"

@app.route("/", 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'
```

## テキストメッセージを処理する
普通のChatGPTを搭載したLINE botと同じコードです。
``` python
assist1 = ""  # 初期化

# テキストメッセージを処理
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    global assist1  # assist1をグローバル変数にする

    text = event.message.text
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "任意の設定"},
            {"role": "assistant", "content": assist1},
            {"role": "user", "content": text},
        ]
    )
    assist1 = response["choices"][0]["message"]["content"]  # assist1を更新
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response["choices"][0]["message"]["content"]),
    )
```

## 音声メッセージを処理する関数
LINE botが受け取ってファイルにした音声メッセージをwisperで処理してテキストにする関数です。
``` python
# 音声メッセージを処理
def transcribe_audio(file_path):
    with open(file_path, "rb") as audio_file:
        response = openai.Audio.transcribe("whisper-1", audio_file)
    return response.text
```

## 音声メッセージを処理
LINEが音声メッセージを受信したら、音声ファイルにして、先程の関数で音声ファイルをテキストにします。
音声をテキストにしたら、音声ファイルは削除して、テキストをChatGPTに渡して返答メッセージを作成します。
その後、作成したメッセージをLINEに返します。
``` python
# LINEの音声認識
@handler.add(MessageEvent, message=AudioMessage)
def handle_audio_message(event):
    message_content = line_bot_api.get_message_content(event.message.id)

    audio_data = b''
    for chunk in message_content.iter_content():
        audio_data += chunk

    audio_format = "m4a"
    audio_filename = f"audio_{event.message.id}.{audio_format}"

    with open(audio_filename, "wb") as f:
        f.write(audio_data)

    transcript = transcribe_audio(audio_filename)

    # 音声ファイルを削除する
    os.remove(audio_filename)

    global assist1  # assist1をグローバル変数にする

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "任意の設定"},
            {"role": "assistant", "content": assist1},
            {"role": "user", "content": transcript},
        ]
    )
    assist1 = response["choices"][0]["message"]["content"]  # assist1を更新
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response["choices"][0]["message"]["content"]),
    )
```

## LINE botを実行中にする
LINE botを実行中にします。
``` python
if __name__ == "__main__":
    app.run(port=int(os.getenv("PORT", 5000)))

```

# 最後に
LINE botと話せると感動する方が多いですね。
openai APIが優秀なだけですが。
また何か作ってみます。