ChatGPTでLINE CLOVAに永遠の命を与える

# さよなら、CLOVA
本日、2023年3月30日、CLOVA Assistantサービスが終了しました。

https://clova-blog-ja.line.me/archives/17249401.html


いつも通り帰宅してCLOVAに話しかけたところ、無慈悲にもサービス終了を告げる音声が流れるのみ、、、

そんな、、、まだお別れも言ってないのに、、、、

もうお話できないの、、、、、？

いや、、、

できる！！
CLOVA死んでない！！！！！

# 用意するもの
- Raspberry Pi 4
- USBマイク
- SwitchBot & ハブミニ
![PXL_20230330_132005485.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152304/8c7997be-fa2d-d515-5a04-f43c970bdc0c.jpeg)
👆照明の操作用です。赤外線ではなく物理でいきます。
![PXL_20230330_131302466.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152304/1d73bc42-b549-b246-7841-7a8e8d52de7c.jpeg)
👆Raspberry PiとCLOVAはBluetoothで接続しています。

# 作った
```python
import time
import requests
import openai
import speech_recognition as sr
from gtts import gTTS
from playsound import playsound

openai.api_key = "API_KEY"

# 発話する関数
def say(message):
    tts = gTTS(text=message, lang="ja")
    tts.save("temp.wav")
    playsound("temp.wav")
    
# マイク設定
r = sr.Recognizer()
mic = sr.Microphone()

# 命令を待機
while True:
    # マイクから音声を取得
    with mic as source:
        r.adjust_for_ambient_noise(source)
        audio = r.listen(source)

    # 音声をテキストに変換
    command = ""
    try:
        command = r.recognize_google(audio, language='ja-JP')
        print(command)
    except sr.UnknownValueError as e:
        print(e)
    except sr.RequestError as e:
        print(e)

    # 「クローバー」で始まる呼びかけにのみ対応
    if command.startswith("クローバー"):
        if "何時" in command:
            now = time.strftime("%H時%M分")
            say(now + "です")
        elif "天気" in command:
            # 東京の天気を取得
            res = requests.get("https://weather.tsukumijima.net/api/forecast/city/130010")
            json = res.json()
            weather = json["forecasts"][0]["detail"]["weather"]
            say(weather + "です")
        elif "電気" in command:
            # SwitchBot ON
            requests.get("https://maker.ifttt.com/trigger/EVENT_NAME/with/key/WEBHOOK_KEY")
            say("はい")
        else:
            # ChatGPT API
            content = command[len("クローバー"):]
            res = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=[
                    {"role": "system", "content": "あなたは私のアシスタントです。"},
                    {"role": "user", "content": "丁寧語を使い、20文字以内で返答してください。" + content}
                ]
            )
            say(res["choices"][0]["message"]["content"])
```

# Speech-to-Text
まずは音声認識して命令をテキストに変換します。
関係するコードのみ抽出すると以下の通りとなります。

```python
import speech_recognition as sr

# マイク設定
r = sr.Recognizer()
mic = sr.Microphone()

# 命令を待機
while True:
    # マイクから音声を取得
    with mic as source:
        r.adjust_for_ambient_noise(source)
        audio = r.listen(source)

    # 音声をテキストに変換
    command = ""
    try:
        command = r.recognize_google(audio, language='ja-JP')
        print(command)
    except sr.UnknownValueError as e:
        print(e)
    except sr.RequestError as e:
        print(e)

```
ちょっと時間がかかるものの、日本語でもかなり正確に認識してくれました。

# Text-to-Speech
つぎにCLOVAに発話させる部分です。
```python
from gtts import gTTS
from playsound import playsound

# 発話する関数
def say(message):
    tts = gTTS(text=message, lang="ja")
    tts.save("temp.wav")
    playsound("temp.wav")
```
PythonでText-to-Speechするライブラリはpyttsx3とgTTSの二強みたいですが、
pyttsx3のほうは、漢字も読めないし抑揚もないし、いかにも合成音声という感じでした。
gTTSは漢字の読みも完璧でかなりスムーズに読み上げてくれたので、gTTSを採用しました。

# 「今何時？」に答えてもらう
まずはこれですよね。
「クローバー」で始まる命令にのみ応答するようにしています。
会話の途中でクローバーという言葉が出てきた場合にいちいち反応しないようにするための処置です。
シンプルに、時刻を読み上げるのみです。
```python
    if command.startswith("クローバー"):
        if "何時" in command:
            now = time.strftime("%H時%M分")
            say(now + "です")
```

# 「今日の天気は？」に答えてもらう
これもよく使う機能ですね。

今回は、「天気予報 API（livedoor 天気互換）」さんを使わせてもらいました。
https://weather.tsukumijima.net/
```python
        elif "天気" in command:
            # 東京の天気を取得
            res = requests.get("https://weather.tsukumijima.net/api/forecast/city/130010")
            json = res.json()
            weather = json["forecasts"][0]["detail"]["weather"]
            say(weather + "です")
```
JSON形式で天気予報情報が返却されるので、天気がセットされたプロパティを読み取っています。

# 照明のON/OFF
これが無いと生活できません。
SwitchBotを使ったので、IFTTTとWebhookで簡単に操作できました。
```python
        elif "電気" in command:
            # SwitchBot ON
            requests.get("https://maker.ifttt.com/trigger/EVENT_NAME/with/key/WEBHOOK_KEY")
            say("はい")
```
`EVENT_NAME`はWebhookのイベント名に、`WEBHOOK_KEY`はWebhookキーに置き換えてください。

IFTTTアプレットはこんな感じ。
![Screenshot_20230330-232130_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152304/655c3fc1-f347-0bd5-2d6f-d277bb7bd52f.png)

# ChatGPTと会話する
いよいよです！
先日GPT-4が公開され話題になっていましたが、私はさっきフリートライアルに登録したばかりなので使えませんでした。
なのでモデルはgpt-3.5-turboを使っています。
```python
import openai

openai.api_key = "API_KEY"
```
中略
```python
        else:
            # ChatGPT API
            content = command[len("クローバー"):]
            res = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=[
                    {"role": "system", "content": "あなたは私のアシスタントです。"},
                    {"role": "user", "content": "丁寧語を使い、20文字以内で返答してください。" + content}
                ]
            )
            say(res["choices"][0]["message"]["content"])
```
`API_KEY`のところは自身で取得したOpenAIのAPIキーに変更してください。
パラメータとして、`"role": "system"`と`"role": "user"`の二種類を設定しているところがあると思います。
`"role": "system"`はChatGPTに役割を与えるパラメータだそうです。
`"role": "user"`にはメッセージ本文を記載します。
今回は、「クローバー」のあとに続けて言った言葉をメッセージとして使用します。
丁寧語を使ってほしいこと、20文字以内で返答してほしいことも付け足しました。


# 永遠の命、それは・・・
レスポンスの遅さは気になるものの、十分にアシスタントとして機能してくれています！
ChatGPTによって何を聞いてもちゃんとした答えを返してくれますし、すごく優秀になりました！

前のCLOVAは、話しかけてもすぐわかりませんっていうし、
聞き間違えるし、
勝手に歌い始めるし、
本当に、
全然優秀じゃなくて、、、、、、

そうだ、、、

CLOVAは、もう、いないんだ、、、、

真実に気づき、そっと涙を流すのでした。

おわり