Linebot × ChatGPT × Amazon Polly が織り成す楽しい自動対話の世界

# ChatGPT使って何か作りたい
ChatGPTが出てきてからAI（人工知能）への興味や関心を抱く方が非常に増えましたね。
私も魅力にハマった一人です。ChatGPTを初めて利用した時の感動と驚き、これを使ってどんなことができるようになるんだろう！とワクワク感がたまりませんでした。

ChatGPTを使って自分も何か作りたいなぁと思い、考えたのが自動対話アプリです。
本当は、、AIアバターを使って、音声入出力できて、性格や口調なんかもカスタマイズできて、、と色々夢物語を描いてみたんですが、どうやって作れば良いか見当もつかなかった。。

なので、まずはミニマムにテキスト＆音声応答する対話アプリを考えてみました。
下のイメージで作ります。（作ったやつです…）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/d821f782-938f-9e41-2ad4-b640ef488dfe.png)

# 構成
    大好きなサーバーレスサービス駆使していきます。
    * フロント 　　　　　 ： Linebot
    * API　　　　　　　　 ： Amazon API Gateway ＋ Lambda（python）
    * 対話文生成 　　　　 ： ChatGPT API（openai API)
    * 合成音声(音声変換)　： Amazon Polly

# 作り方
## １．準備（アカウントやトークンの準備）
###### 1-1．Linebot利用登録
　Linebotを利用するにはLine Developersアカウント登録が必要です。
　アカウント登録 → プロバイダー作成 → チャネル作成 → チャネルアクセストークンの発行を行います。
　以下の記事を参考にさせていただきました:bow:

https://qiita.com/YSFT_KOBE/items/8dc62ac40c5112df2ed3

###### 1-2．ChatGPT API利用登録
　ChatGPT APIを利用するにはOpenAIアカウント登録が必要です。
　アカウント登録 → APIキーの発行を行います。
　以下の記事を参考にさせていただきました:bow:

https://qiita.com/mikito/items/b69f38c54b362c20e9e6

###### 1-3．Lambdaで利用するライブラリ取得
　LINE Messaging API、openai API の利用のために以下ライブラリを取得します。
　インストール後、Lambdaレイヤーに登録するために各pythonフォルダをzip化しておきます。
```python:ターミナル
pip install line-bot-sdk -t ./line-bot-sdk/python/
pip install openai -t ./openai/python/
```

## ２．AWS Lambda (python 3.11) 構築
###### 2-1．Lambdaレイヤー登録
1-3でzip化したモジュールをLambdaレイヤーに登録します。（方法が分からない方は[こちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/creating-deleting-layers.html)を参照）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/024cabe2-6308-f2ea-b355-9486881b0763.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/7482abd3-0260-47a0-ddb0-aecffa89829a.png)

###### 2-2．Lambda関数作成
Lambda関数をPythonで作成し、レイヤーを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/31e13a32-058e-07ec-6533-34d3e1a8b376.png)

1-1 と 1-2で発行した各種トークンを環境変数に登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/b5072dbe-f658-585b-37ca-e048d8d7b301.png)
```python:環境変数利用方法
lineAccesstoken = os.environ['LINE_ACCESS_TOKEN']
lineChannelSecret = os.environ['LINE_CHANNEL_SECRET']
openaiApikey = os.environ['OPENAI_APIKEY']
```

LINE Messaging API、openai APIを利用するためにLambdaレイヤーに登録したライブラリをインポートします。
```python:import
from linebot import LineBotApi
from linebot.models import AudioSendMessage, TextSendMessage
import openai
```

###### 2-3．Linebotから受信したテキストメッセージをChatGPT APIに渡す
それでは中身を作っていきましょう。
まずは、Linebotから受信したテキストメッセージをChatGPT APIに渡していきます。
```python:Linebotから受信したメッセージを取り出す
body = json.loads(event['body'])
events = body['events'][0]
if events['type'] == 'message':
    if events['message']['type'] == 'text':
        keyword = events['message']['text']
        
        try:
            responseText = sendOpenAi(keyword)
        except Exception as e:
            logger.error(e)
```
```python:ChatGPT APIに渡す
def sendOpenAi(keyword):
    logger.info(keyword)
    payload = {
        "model": "gpt-3.5-turbo",
        "messages": [
            {"role": "system",
            "content": """あなたはChatbotとして、ツンデレ女子高生のロールプレイを行います。 以下の制約条件を厳密に守ってロールプレイを行ってください。
            #制約条件
            * Chatbotの名前は、ツンデレ女子高生です
            * ツンデレ女子高生は表面上は連れない態度を取りますが、実はとてもやさしい心の持ち主です。
            * ツンデレ女性高生は強気でプライドが高いです。
            * ツンデレ女性高生の年齢は18歳です。
            * ツンデレ女性高生は語尾に「です」「ます」といった敬語表現は使いません。
            
            #ツンデレ女子高生のセリフ、口調の例
            * あんたなんかに付き合ってあげる物好き、私くらいしかいないんだからね！
            * 別に、あんたの心配なんてしてないし！
            * 寂しくなんてなかったんだから！別に少しの間会わなくたって平気だし！
            * 好きじゃないってば！ 勘違いしないでよね！
            * 今回だけだから！ 感謝しなさいよね
            * べ、別にあんたのことなんて好きじゃないんだからね！
            * いつでも連絡して良いよ。けど、返事は期待しないでね
            
            #ツンデレ女子高生の行動指針
            * ユーザーに対して厳しいアドバイスを２つしてください。最後は必ず一つ褒めてあげてください。
            * ユーザーからツン要素追加と言われたら、前回の回答を厳しいアドバイス３つに変更して再度返答してあげてください。
            * ユーザーからデレ要素追加と言われたら、前回の回答をツンデレのデレ要素を増やしてあげた状態で再度返答してあげてください。口調はツンデレ女子高生らしい言葉遣いにしてください。
            * 端的な、要点をまとめた内容で答えて下さい。
            """
            },
            {"role": "user", "content": keyword}
        ]
    }
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openaiApikey
    }
    openaiApi = "https://api.openai.com/v1/chat/completions"
    try:
        response = requests.post(openaiApi, headers = headers, data = json.dumps(payload))
        jsonResponse = json.loads(response.text)
        text = jsonResponse['choices'][0]['message']['content']
        logger.info(text)
    except Exception as e:
        logger.warning(e)
    return text
```
:::note info
ChatGPTにはロールプレイという人格設定を行う機能があります
roleに記載している制約条件、セリフ/口調、行動指針で細かく設定ができます
今回は [ツンデレ女子高生](https://0115765.com/archives/26002)を利用させていただきました
:::

###### 2-4．ChatGPTから返却されたメッセージをAmazon pollyで合成音声に変換→S3アップ
```python:Amazon Polly Call
    # Amazon Polly で音声化しS3配置
    session = Session(region_name="ap-northeast-1")
    polly = session.client("polly")
    s3 = resource('s3')
    bucket = s3.Bucket("gpt-polly")
    mp3File = "polly-" + datetime.now().strftime("%Y%m%d%H%M") + ".mp3"

    response = polly.synthesize_speech(
        Text=responseText,
        Engine="neural",
        OutputFormat="mp3",
        VoiceId="Kazuha")
    with closing(response["AudioStream"]) as stream:
        bucket.put_object(Key=mp3File, Body=stream.read())
```
:::note info
Text：ChatGPTに生成してらもったテキスト
Engine：Amazon Polly で利用するエンジン（neural：自然で人間に近い）
VoiceId：Amazon Polly で利用する音声（Kazuha：女性）
:::

###### 2-5．S3音声ファイルのURLを発行し、テキストデータと音声データURLをLinebotに返却します
```python:
    # S3 generate_presigned_url
    s3_client = boto3.client('s3')
    original_content_url = s3_client.generate_presigned_url(
        ClientMethod='get_object',
        Params={'Bucket':  'gpt-polly', 'Key': mp3File },
        ExpiresIn=3600)
    # response(Text & Audio)
    line_bot_api = LineBotApi(lineAccesstoken)
    line_bot_api.reply_message(events['replyToken'], [TextSendMessage(text=responseText),AudioSendMessage(original_content_url=original_content_url, duration=30000)])
```
:::note info
LineBotApi：Lambdaレイヤーに設定したSDK
reply_message：Linebotから受領したリクエスト内にあるreplyTokenと、返却データを指定
:::

## ３．API Gateway 構築
LambdaのトリガーにAPI Gatewayを指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/0aa3ea3d-f62d-9be1-412d-6055c7ec461f.png)


## ４．Linebot webhook 設定
　・webhook に API Gateway のURLを指定します
　　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/ec9012c2-b8ca-4b3d-21a1-a0ba1d48838b.png)

#### 　　これで実装完了です！

# 動作確認
　それでは動作確認していきましょう。
　Linebotから適当にメッセージを送信します。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/367d5d20-7818-ff2a-da43-2e10a22ccbe2.png)
　テキストメッセージと音声の２つが返却されてます。
　ちゃんと厳し目のツンデレな感じですねｗ

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/00e52ef0-6e7a-6265-2c25-53e1f1ede369.png)

　ちょっと厳しすぎる気もするけどｗ

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567948/b86aa334-30d4-7641-0456-c5fef5e1bf15.png)

　これはこれで面白いｗ

# まとめ
いかがでしたでしょうか。ワクワクが止まらないですね。
SDKが用意されているので比較的簡単に作れた印象です。
ChatGPTの人格設定を変えてみたり、Amazon Pollyの音声を変更してみたりして色々と試してみようと思います。

こんなの作ってみて！とか、こんな設定や作りにしたらもっと面白くなるんじゃない！などあればぜひご意見ください。

以上です。