ChatGPT と声で会話をする Raspberry Pi プログラム　ー　ChatGPT を使って開発

![ChatGPTと声で会話-title.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/7fba3fb7-2a97-e5fc-ed87-07fb8c95ac33.jpeg)

## はじめに

　**最**近毎日耳にする ChatGPT。確かに何かに使えそうな予感のする新技術ですが、「AI がこれまでの古典的な職業を奪う」のではないかと懸念する声もまた、毎日耳にしています。私はご存知の通り、プログラムを組む事を生業とした「*職業プログラマー*」ですが、ChatGPT はコーディングも得意です。OpenAI が発表した情報でも、ChatGPT の登場で影響を受けるであろう職業に含まれています。「影響を受ける」という言葉の意味が何を指しているのか微妙ですが、私の現在の主観を述べさせて頂くと、「GPT は、**CASE Tool**」、つまり、"Generative Pre-trained Transformer is *Computer Aided Software Engineering Tool*"(生成系学習済みトランスフォーマーは、*コンピューター支援型ソフトウェアエンジニアリングの道具*)です。今回のプログラムも VS Code に ChatGPT の拡張機能を組み込んで開発しましたが、非常に便利に利用することができました。ちなみに、表題の画像は、Stable Diffusion で出力したものです。

:::note info
Stable Diffusion による画像生成に使用した prompt は、こちらです。

"a pretty and cute girl is programming by using an old desktop computer and keyboard and mouse at the desk with smile and laugh." -
:::

https://ja.wikipedia.org/wiki/Compute修正r_Aided_Software_Engineering

https://arxiv.org/abs/2303.10130

## 作成

　もちろん、Raspberry Pi でなければならない理由はないのですが、Python で書いている限り、Python が動けば基本、どんな処理系でも大丈夫です。使用する GPT には、私は今回、VS Code の ChatGPT 機能拡張「**ChatGPT - Genie AI**」を使用しました。

https://marketplace.visualstudio.com/items?itemName=genieai.chatgpt-vscode

こちらを、お手持ちの VS Code に追加して、ChatGPT の API Key を設定すると使用できるようになります。

https://platform.openai.com/account/api-keys

VS Code の左端の魔法のランプのアイコンが Genie AI の拡張機能です。このアイコンをクリックすると *GENIE:EDITOR VIEW* が開き、中程に ChatGPT への指示を入力するテキストボックスが現れます。試しに「C言語の "Hello C World." のプログラムを出力してください。」とでも入力してみると、新しい TAB が開いてコードがつらつらと出力されてきます。

ここで今回は、表題の通り「ChatGPT と声で会話をするプログラム」を生成させてみたいと思います。生成させてみて思いましたが、これは、ちょっとした仕様書かもしれませんね。

ChatGPT への指示は、下記のようなものです。

:::note info
ChatGPT に接続して音声で会話をする Complete Python プログラムを出力してください。

1.マイクから音声を取り込む。
2.取り込んだ音声を、音声認識でテキストへ変換
3.変換したテキストを表示
4.ChatGPT へ接続
5.テキストを ChatGPTへ送信して、レスポンスを取得
6.取得したレスポンスをテキスト表示
7.スピーチシンセサイザーで、テキストを発話
8.1から繰り返す

※音声認識とスピーチシンセサイザーには、日本語を使用する
:::

ここで、"*Complete*"という Word を使用しないと、部分的なコードが出力された為、当該 Word を追加し、*ASK* を押すと、何となくこんな感じのコードを出力してきます。コードは、ASK ボタンを押すたびに少し違ったものを返してきます。

**※画像は、クリックで拡大可**
![Screenshot 2023-04-15 12:52:33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/93a9d191-3047-1ce1-6afd-97e9adaae4ed.png)

このままではやはり実行できない（エラーになる）ので、追加で部分的なコードを出力させてみます。今度は、下記のような指示を与えてみます。

:::note info
python で ChatGPT API にアクセスして質問を送信し、レスポンスをテキスト表示するプログラムを書いてください。
:::

```python:request2gptapi.py
import requests
import json

# APIキー
API_KEY = 'YOUR_API_KEY'

# APIのエンドポイント
ENDPOINT = 'https://api.openai.com/v1/engines/davinci-codex/completions'

# 質問
prompt = 'Hello, how are you?'

# リクエストのパラメーター
data = {
    'prompt': prompt,
    'max_tokens': 10,
    'temperature': 0.5
}

# ヘッダー
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {API_KEY}'
}

# HTTPリクエストを送信してレスポンスを取得
response = requests.post(ENDPOINT, headers=headers, data=json.dumps(data))

# レスポンスをパースしてテキスト形式で表示
result = json.loads(response.text)['choices'][0]['text']
print(result)
```

最初に吐いてきたコードの28行目「ChatGPTへ送信してレスポンスを取得」の辺りに、このコードを入れれば良さそうです。しかし、それだけで動くコードになるわけでもありません。そこで、ここからはいつも通り、ウェブで API リファレンスなどを調べながらプログラムを完成させていきます。

そしてごにょごにょしてできたコードが以下の通りです。

## ソースコード解説

```python:gpt_voicetalk.py
#!/usr/bin/python3
#
################################################################################
#                                                                              #
#  Title  : A talk to GPT by using Japanese Voice                              #
#  Program Name : gpt_voicetalk.py                                             #
#                                                                              #
#  Detail : Create                                                             #
#  Date   : 2023.04.14                                                         #
#  Author : Akihiro Kashiwagi                                                  #
#  e-mail : a-kashiwagi@hotmail.com                                            #
#                                                                              #
#  Replace --------------------------------------------------------------------#
#                                                                              #
#  Date   :                                                                    #
#  Author :                                                                    #
#  Deteil :                                                                    #
#                                                                              #
# -------+---------+---------+---------+---------+---------+---------+---------#
#234567890123456789012345678901234567890123456789012345678901234567890123456789#
################################################################################

                                                # Import
import os
                                                # About Speech recognizer
                                                # and Speech synthesizer
import speech_recognition as sr
from gtts import gTTS
import pyaudio
                                                # About GPT API
import requests
import json
import re

#ENDPOINT = 'https://api.openai.com/v1/completions'
                                                # End point of API for text

ENDPOINT = 'https://api.openai.com/v1/chat/completions'
                                                # End point of API for chat

API_KEY = 'YOUR API KEY'
BEEP_CMD = "/usr/bin/aplay /home/pi/ding.wav"
JTALK_CMD = "/home/pi/jsay.sh"
MP3_CMD = "/usr/bin/mpg321"
GTTS_FILE = "/var/tmp/result.mp3"
TERMINATE_WORD = "おしゃべり終了"

                                                # SpeechRecognizer
r = sr.Recognizer()
mic = sr.Microphone(device_index=0)

while True:
                                                # Procedure loop
    with mic as source:
                                                # Get a mic input
        print("Listening...")
                                                # listen for 1 second
                                                # to calibrate the energy
                                                # threshold for ambient
                                                # noise levels
        #r.adjust_for_ambient_noise(source)
                                                # If one second silence
                                                # then pause
        r.pause_threshold = 1
                                                # Start listen
        audio = r.listen(source)

    try:
                                                # Recognize japanese voice
        print("Recognizing...")
        text = r.recognize_google(audio, language="ja-JP")

                                                # Put a beep sound
        os.system(BEEP_CMD)
        print("You said: " + text)

    except:
                                                # Continue
        print("Continue")
        continue

                                                # Strip a spaces of text
    prompt = re.sub('( |　)+','',text)    

                                                # Chack words
    if prompt == TERMINATE_WORD:
                                                # Terminate
        exit(0)

                                                # Request parameters
                                                # for a text endpoint
    #data = {
    #    'prompt': prompt,
    #    'model': 'text-davinci-003',
    #    'max_tokens': 500,
    #    'temperature': 0.5
    #}
                                                # for a chat endpoint
    data = {
        'model': 'gpt-3.5-turbo',
        'messages': [{'role': 'user', 'content': prompt }]
    }

                                                # Request header with key
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + API_KEY
    }
                                                # Get a response by send a request
    print("Requesting...")
    response = requests.post(ENDPOINT, headers=headers, data=json.dumps(data))

    print(response.text)

                                                # Response parse
    #result = json.loads(response.text)['choices'][0]['text'].strip()
                                                # for a text endpoint

    result = json.loads(response.text)['choices'][0]['message']['content'].strip()
                                                # for a chat endpoint

    #result = re.sub('^.*\n\n','',result)
                                                # Strip a header word and two LF
    result = re.sub('\n','',result)
                                                # Strip LF
    print("ChatGPT said:" + result)


                                                # Speech text by using Open JTalk
    os.system(JTALK_CMD + " '" + result + "'")

                                                # by using gTTS
    #tts = gTTS(result, lang='ja')
    #tts.save(GTTS_FILE)
    #os.system(MP3_CMD + " " + GTTS_FILE)
```

音声認識には、*SpeechRecognition モジュール*を使用しています。音声合成 Speech Synthesizer には、*Open JTalk* 或いは、*gTTS* を使用しています。ChatGPT への送信は、HTTP Request を利用して、Header と Data を POST する方法を使用しています。実行するには、コードの最初にある import セクションを見て必要なモジュールを *pip install* 或いは *apt install* してください。

:::note warn
Raspberry Pi で pyaudio を利用するには、少し手こずるかも知れません。
:::

```python:import
                                                # Import
import os
                                                # About Speech recognizer
                                                # and Speech synthesizer
import speech_recognition as sr
from gtts import gTTS
import pyaudio
                                                # About GPT API
import requests
import json
import re
```

Open JTalk については、下記のウェブサイトなどを参考に、設定等、調整してください。今回は、Open JTalk を呼び出して発話するシェルスクリプトへの引数として、テキストを指定する方法で発話しています。

https://monomonotech.jp/kurage/raspberrypi/raspi_speech_synthesis.html

:::note info
ソースコード上には、gTTS を使用した Speech Synthesizer 用のコードもコメントアウトの形で記載してありますので、gTTS を使用したい場合は Open JTalk の呼び出し行をコメントアウトして、代りにこちらのコメントアウトを外して使用してください。gTTS は、Google の Speech Synthesizer なので、聞きやすく、高性能です。

```python:gTTS
                                                # Speech text by using Open JTalk
    #os.system(JTALK_CMD + " '" + result + "'")

                                                # by using gTTS
    tts = gTTS(result, lang='ja')
    tts.save(GTTS_FILE)
    os.system(MP3_CMD + " " + GTTS_FILE)

```
:::

また、Raspberry Pi で音声認識を動かすにはマイクが必要になりますので、下記ウェブサイトで解説されているような *USB マイク等*を用意してください。AVS(Alexa Voice Service)も同じように音声認識のソフトウェアなので、AVS で実績のあるマイクロフォンであれば、今回の用途にも適しています。

https://dream-soft.mydns.jp/blog/developper/smarthome/2020/05/958/

冒頭でご紹介した API Key へのリンクから、新しい API Key を取得して、ソースコードの最初の方にある変数へ設定してください。

```python:API_KEY
API_KEY = 'YOUR API KEY'
```

また、音声認識に成功した際に鳴らす音声ファイルも用意してください。Google などで検索して、ライセンスフリーの音声素材ウェブサイトなどから、お好みの音声ファイルを取得して、"ding.wav" の代りに指定してください。

```python:BEEP_CMD
BEEP_CMD = "/usr/bin/aplay /home/pi/ding.wav"
```

Open JTalk についても同様に、前述のウェブサイト等で作成したシェルスクリプトを指定してください。発話するテキストは、このコマンドの引数として渡しています。

```python:JTALK_CMD
JTALK_CMD = "/home/pi/jsay.sh"
```

gTTS を使用している場合は、生成された MP3 の音声ファイルを外部コマンドから再生していますので、Raspberry Pi では、"*mpg321*" などの MP3 再生用コマンドを apt install mpg321 しておいてください。

```python:MP3_CMD
MP3_CMD = "/usr/bin/mpg321"
```

Speech Recognizer による音声認識処理では、Google の音声認識を使用しています。以前では考えられないほどの精度で、日本語音声を認識してくれます。そして、"mic = sr.Microphone(device_index=0)"で指定している device_index は、USB マイクなどの環境に合わせて適程、変更してください。

```python:SpeechRecognize
                                                # SpeechRecognizer
r = sr.Recognizer()
mic = sr.Microphone(device_index=0)

while True:
                                                # Procedure loop
    with mic as source:
                                                # Get a mic input
        print("Listening...")
                                                # listen for 1 second
                                                # to calibrate the energy
                                                # threshold for ambient
                                                # noise levels
        #r.adjust_for_ambient_noise(source)
                                                # If one second silence
                                                # then pause
        r.pause_threshold = 1
                                                # Start listen
        audio = r.listen(source)

    try:
                                                # Recognize japanese voice
        print("Recognizing...")
        text = r.recognize_google(audio, language="ja-JP")

                                                # Put a beep sound
        os.system(BEEP_CMD)
        print("You said: " + text)

    except:
                                                # Continue
        print("Continue")
        continue
```

listen() で取得した音声は、recognize_google() で認識させますが、空っぽだった場合など、認識にに失敗した時は、try-exception で例外を拾って loop を continue するようにしています。

:::note info
adjust_for_ambient_noise() は、繰り返し処理をした場合、問題があったのでコメントアウトし、pause_threshold で等価処理を行っています。
:::

音声認識で取得したテキストは、スペース（半角全角）を削除して、文字列比較に利用しやすくしています。例えば今回は、冒頭で設定している TERMINATE_WORD = "おしゃべり終了" という呪文で、プログラムを終了できるようにしています。

```python:Execute
                                                # Strip a spaces of text
    prompt = re.sub('( |　)+','',text)    

                                                # Chack words
    if prompt == TERMINATE_WORD:
                                                # Terminate
        exit(0)
```

HTTP Request の為の Data は、ChatGPT の End Point に合わせて指定する必要があります。End Point は幾つか用意されていて、Chat 用の End Point は、名前の通り、Chat 用の Request に適したモデル(gpt-3.5-turbo)になっています。

:::note info
今回は、Chat 用の End Point に合わせてありますが、Text 用の End Point を使用する場合は、今、コメントアウトしてある Text 用 Data のコメントを外して、代りに今使用している Chat 用の Data をコメントアウトして使用してください。また、冒頭で指定している End Point も、入れ替えてください。

```python:EndPoint
ENDPOINT = 'https://api.openai.com/v1/completions'
                                                # End point of API for text

#ENDPOINT = 'https://api.openai.com/v1/chat/completions'
                                                # End point of API for chat
```

Text 用のモデル(text-davinci-003)は、ソースコードの生成などに適しているようです。詳しくは、下記、Open AI の API を説明したウェブサイト等をご参照ください。
:::

https://platform.openai.com/docs/models/model-endpoint-compatibility

```python:HeaderData
                                                # Request parameters
                                                # for a text endpoint
    #data = {
    #    'prompt': prompt,
    #    'model': 'text-davinci-003',
    #    'max_tokens': 500,
    #    'temperature': 0.5
    #}
                                                # for a chat endpoint
    data = {
        'model': 'gpt-3.5-turbo',
        'messages': [{'role': 'user', 'content': prompt }]
    }

                                                # Request header with key
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + API_KEY
    }
```

HTTP Request では、上記で設定した Header と Data を ChatGPT へ Request し、Response を取得しています。Response は、Request の種類によって変わってくるので、parse 解析処理も Chat 用と Text 用を用意しています。実際に Request を出してみると解りますが、Text 用の Request に対する Response は、Chat 用の Response とは少し違っています。

```python:HTTPRequest
                                                # Get a response by send a request
    print("Requesting...")
    response = requests.post(ENDPOINT, headers=headers, data=json.dumps(data))

    print(response.text)

                                                # Response parse
    #result = json.loads(response.text)['choices'][0]['text'].strip()
                                                # for a text endpoint

    result = json.loads(response.text)['choices'][0]['message']['content'].strip()
                                                # for a chat endpoint

    #result = re.sub('^.*\n\n','',result)
                                                # Strip a header word and two LF
    result = re.sub('\n','',result)
                                                # Strip LF
    print("ChatGPT said:" + result)
```

:::note info
例えば、*Chat* 用の Request に対する Response は、このようなものです。

{"id":"chatcmpl-75SYDKaZX44YIOa7IC20fQEkEE6Is","object":"chat.completion","created":1681535745,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":34,"completion_tokens":472,"total_tokens":506},"choices":[{"message":{"role":"assistant","content":"ロック鳥（ラーク鳥、またはラーク・アーロー）は、『千一夜物語』の中で、主人公たちが出会う、驚くべき性質を持った鳥です。\n\nロック鳥は、金や宝石のような美しい歌声を持ち、その歌声で聴衆を魅了します。しかし、この鳥を捕まえようとする者に対しては、恐ろしい攻撃力によって立ち向かいます。ロック鳥は、卵が孵化した時に片方の親鳥が先ずやって来て、おおよそ最初に聞くものを真似て歌うことから、ラーク（つばめ）のように歌い、そして、アーロー（上手い歌い手）のようにしばしば人間の歌を真似ます。そのため、ロック鳥は、人気のある歌を歌うことで、往々にして人間社会で愛された存在として描かれます。\n\n一方で、ロック鳥は、自分自身の存在の奇妙さと、特別な性質を持つことが強調されることもあります。この鳥は、他の鳥とはまったく異なる姿をしており、また、卵も普通のものとは大きさが違っていて、その中には不思議な生き物がいたりすることがあります。そのため、ロック鳥は、神秘的で、不思議な存在として描かれることが多いです。"},"finish_reason":"stop","index":0}]}

それに対して、*Text* 用の Request に対する Response は、このように「・・・ください」という質問を補完する言葉から始まっています。GPT(Generative Pre-trained Transformer)の特徴なのでしょうね。

{"id":"cmpl-75W8NLVjpIcJY7bsCEPKJicAZsIzK","object":"text_completion","created":1681549519,"model":"text-davinci-003","choices":[{"text":"ください\n\n白鳥は、ヨーロッパに棲息する鳥類です。体長は約90cmほどで、体重は4～5kg程度です。羽毛は白く、頭部から後頭部にか","index":0,"logprobs":null,"finish_reason":"length"}],"usage":{"prompt_tokens":15,"completion_tokens":100,"total_tokens":115}}

ここも同様に、Text 用の Request を利用する時は、Chat 用の処理をコメントアウトし、Text 用の処理のコメントアウトを外して利用してください。

```python:parse&strip
                                                # Response parse
    result = json.loads(response.text)['choices'][0]['text'].strip()
                                                # for a text endpoint

    #result = json.loads(response.text)['choices'][0]['message']['content'].strip()
                                                # for a chat endpoint

    result = re.sub('^.*\n\n','',result)
                                                # Strip a header word and two LF
    #result = re.sub('\n','',result)
                                                # Strip LF
```
:::

最後に、Speech Synthesizer 音声合成ですが、冒頭でも説明した通り、Open JTalk で発話するシェルスクリプトを呼び出す形で使用しています。

```python:SpeechSynthesizer
                                                # Speech text by using Open JTalk
    os.system(JTALK_CMD + " '" + result + "'")

                                                # by using gTTS
    #tts = gTTS(result, lang='ja')
    #tts.save(GTTS_FILE)
    #os.system(MP3_CMD + " " + GTTS_FILE)
```

## 実行

<iframe width="480" height="480" src="https://www.youtube.com/embed/Ji7eiZe1ZfU?cc_load_policy=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

:::note warn
"insufficient_quota" というエラーが返ってくる場合は、登録初期無料期間の３ヶ月を経過している可能性があります。一度、OpenAI のアカウントをチェックしてみましょう。"Usage" をみると、"Expired" の日付を確認することが出来ます。
:::

## まとめ

　今回は、ChatGPT の支援によるプログラム開発(*Computer Aided Software Engineering*)を実際に行いました。確かに便利で、開発に要する工数も削減できるのではないでしょうか。また現状では、出力されるソースコードをそのまま利用することは難しく、それをベースにしてプログラムを完成していかなければならないという観点でみると、Visual Studio などの統合開発環境が最初に出力するスケルトンやフレームワークと何ら変わりありません。プログラムに対する知識の少ない初心者には、同様に、難しいコーディングになるのでしょう。プログラミングに慣れた人達にとっては、便利なツールです。

　この「ChatGPT と声で会話をする Raspberry Pi プログラム」は、かなりいい感じではないでしょうか。実際に会話が成り立ちますし、質問にも広範な知識を用いて答えてくれます。ChatGPT の特性である「**回答の真偽の程は不定**」と言うところが微妙ですので、記事の中で取り上げている「ロック鳥」の説明も、100[%]本当なのか後で調べてみなければなりません。

**以上**