🎙️

GPT-4oとKoemotionをつないで高速な音声応答システムを作る

2024/06/24に公開

はじめに

昨今、OpenAI ChatGPTやAnthropic Claudeをはじめとした高品質な対話型APIが公開されています。 これらの対話型APIの応用の一つとして音声会話が挙げられますが、音声まで返してくれる対話型APIは現状ほとんどなく、ユーザが各自で音声認識・音声合成用のシステムまたはAPIと接続する必要があります。本記事では、OpenAI GPT-4o APIを弊社の提供している音声合成サービスKoemotionと接続し、ユーザからのテキスト入力に対して音声で高速に応答するシステムを作っていきます。

実際に作ってみたものがこちら。
https://x.com/rinna_koemotion/status/1805192410424828088

KoemotionのAPIキーの取得

Koemotionの登録・APIキーの取得方法に記載された方法に従ってKoemotionに登録し、APIキーを取得します。Koemotionでは個人利用にあたってKoemotion Trial, Koemotion Light, Koemotion Standardの3つのプランを提供しています。各プランの詳細はKoemotionのサービスページを参照ください。本記事では、Koemotion APIを使ってみるの範囲はKoemotion Trialプラン、GPT-4oとKoemotionをつなぐ以降はKoemotion StandardプランのAPIキーが必要となります。

Koemotion APIを使ってみる

Koemotionは通常のREST APIとして実装されているため、curlのようなコマンドやPythonのようなプログラミング言語、あるいはPostmanのようなアプリケーションから利用することができます。ここではcurlを使う方法と、以下のPythonベースの公式ライブラリを使う方法の2つを紹介します。

https://github.com/rinnakk/Koemotion

curlを使う方法

  1. 以下のようなコマンドをターミナルで実行します。Ocp-Apim-Subscription-Keyの値には先ほど取得したAPIキーを入力してください。speaker_x, speaker_yを調整することで声を変えることができるので、自分の好きな声を探してみてください。下記のパラメータの他にも、speed (話速) やvolume (音量) などのパラメータを指定できます。リクエストパラメータの詳細はこちらのページをご確認ください。
curl -X POST "https://api.rinna.co.jp/koemotion/infer" \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "Ocp-Apim-Subscription-Key: <your Koemotion API key>" \
    --data-raw '{ 
        "text": "こんにちは",
        "speaker_x": -3.0,
        "speaker_y": 2.0,
    }' > result.json
  1. result.jsonはbase64でエンコードされた音声をaudioというキーで格納しています。そのため、音声を保存するには以下のようなコマンドでデータを抽出、デコードし、result.mp3という名前のファイルに書き出します。
cat result.json | jq -r '.audio' | cut -d "," -f 2 | base64 -d > result.mp3

公式ライブラリを使う方法

  1. 公式リポジトリの「準備」の項目に従って関連パッケージとKoemotionライブラリをインストールします。
# macOSの場合
brew install portaudio ffmpeg
# Ubuntuの場合
sudo apt install ffmpeg portaudio19-dev

# 各OS共通
pip install git+https://github.com/rinnakk/Koemotion
  1. 環境変数KOEMOTION_API_KEYKoemotionのAPIキーの取得で取得したAPIキーを設定します。
export KOEMOTION_API_KEY="<your Koemotion API key>"
  1. 上記の設定が完了すると、以下のようにコマンドラインから簡単にAPIをコールすることができるようになります。
koemotion-request -d "{\"text\": \"こんにちは\"}" -a result.mp3

GPT-4oとKoemotionをつなぐ

いよいよGPT-4oをKoemotionとつなぎ、ユーザのテキスト入力に対して高速に音声応答を行うシステムを実装していきます。まずはOpenAI APIを利用するための準備です。

  1. OpenAIのGPT-4o APIを利用するため、OpenAIのAPI keysのページからAPI keyを発行し、環境変数OPENAI_API_KEYに設定しておきます。
export OPENAI_API_KEY="<your OpenAI API key>"
  1. OpenAIのPythonライブラリをインストールします。
pip install openai

音声応答システムの実装

上記の準備ができたら、Pythonで音声応答システムを実装していきます。実装と言っても応答文生成、音声合成はAPIが担ってくれているので、クライアント側では対話履歴の管理とAPIリクエスト部分のみを実装すればよく、コードが非常にシンプルになります。

  1. OpenAI APIおよびKoemotion APIのクライアントを定義します。
import os
from koemotion import Koemotion
from koemotion.streaming import stream_audio
from openai import OpenAI

openai_client = OpenAI()
koemotion_client = Koemotion()
  1. 対話履歴を管理するmessages、およびKoemotionの各呼び出しに共通するパラメータkoemotion_paramsを定義します。「一言で」「絵文字や記号は含めてはいけません」といった制約をプロンプトに含めることで、音声合成に適した応答文が得られるように工夫しています。
messages = [
    {
        "role": "system",
        "content": "以降のユーザ発話に対して気の効いたリアクションや質問を一言で作成してください.絵文字や記号は含めてはいけません",
    }
]
koemotion_params = {
    "speaker_x": 2.0,
    "speaker_y": 3.0,
    "output_format": "wav",
    "streaming": True,
}
  1. ユーザのメッセージを標準入力として受け取り、GPT-4o APIで応答生成を行う部分を実装します。テンポの良い会話を実現するため、max_tokensの値を32と小さく設定し、応答が長くなりすぎないようにしています。
while True:
    messages.append({"role": "user", "content": input("User: ")})
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=32,
    )
    content = response.choices[0].message.content
    print("System:", content)
    messages.append({"role": "system", "content": content})
  1. 最後に、生成された応答をKoemotion APIに投げて音声を合成し、ローカルの音声デバイスで再生する部分を実装します。"streaming": Trueを指定してkoemotion_clientrequest()メソッドを実行した場合、Koemotion APIは音声をすべて合成し終えるのを待たずに、一定のバッファサイズごと(リクエストパラメータのstreaming_buffer_sizeから指定可能、デフォルト値は8192)に音声波形を返します。stream_audio関数では、response.response.iter_content(chunk_size=chunk_size)を用いて音声波形を少しずつ受け取り、ローカルデバイスに渡して再生します。
    koemotion_params["text"] = content
    response = koemotion_client.request(koemotion_params)
    response.stream_audio()
    response.save_audio(f"wav/{content}.wav", quiet=True)

応答時間はどれくらいか

GPT-4oおよびKoemotionのリクエストにどの程度の時間がかかっているかを調べるため、以下のようなコードを追加します。

from time import perf_counter

# 途中略

    start_time_openai = perf_counter()
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=32,
    )
    end_time_openai = perf_counter()
    print(f"OpenAI API response time: {end_time_openai - start_time_openai:.3f} sec")

    # 途中略

    start_time_koemotion = perf_counter()
    response = koemotion_client.request(koemotion_params)
    end_time_koemotion = perf_counter()
    print(
        f"Koemotion API response time: {end_time_koemotion - start_time_koemotion:.3f} sec"
    )

ここまでの実装をまとめたコードは以下からアクセスすることができます。

https://github.com/rinnakk/Koemotion/blob/master/examples/streaming/realtime_chat_demo.py

上記のコードをUbuntu (WSL2)で動かしてみた結果です。

User: おなかすいたー
System: 今日は何を食べたい気分ですか?
OpenAI API response time: 0.906 sec
Koemotion API response time: 0.178 sec
User: そうだなー、カレーが良いかなあ
System: スパイシーなカレーと一緒に何を合わせたいですか?
OpenAI API response time: 1.104 sec
Koemotion API response time: 0.167 sec
User: カレーといえばラッシーじゃない?
System: ラッシー作りも楽しそうですね、自分で作りますか?
OpenAI API response time: 0.910 sec
Koemotion API response time: 0.164 sec
User: 自作はしたことないな、簡単なの?
System: 意外と簡単ですよ、試してみたいですか?
OpenAI API response time: 0.888 sec
Koemotion API response time: 0.146 sec
User: うん、作り方教えてよ
System: ヨーグルト、牛乳、砂糖、そして氷を混ぜるだけでできますよ。挑戦してみますか?
OpenAI API response time: 1.484 sec
Koemotion API response time: 0.153 sec
User: それだけでできるなら、今日作ってみる
System: ぜひチャレンジしてみてください、結果が楽しみですね。
OpenAI API response time: 0.666 sec
Koemotion API response time: 0.145 sec

APIの応答時間はサーバ側の混雑度合い、ネットワーク環境、住んでいる場所などによって変動しますが、GPT-4oは約1秒、Koemotionは0.2秒以内で応答が返ってきました。GPT-4oによる応答生成が終わってから、Koemotionで合成された音声が再生されるまでのオーバーヘッドはかなり小さく抑えられていることが分かります。実際の人間同士の会話のようなリアルタイム感にはまだ届いていませんが、ネットワークを介したチャットシステムであることを考慮するとかなり高速に応答できています。

httpxの利用による高速化(2024/07/01追記)

ここまでで用いたKoemotionライブラリでは内部的にrequestsモジュールをベースとしたhttpクライアントを利用していましたが、httpxモジュールを代用することでクライアント側から見た応答速度がさらに改善しました。より具体的には、TCP connectionの再利用により、2回目以降のリクエストで高速化を図ることができます。改善後の実行結果を以下に示します。

User: おなかすいたー
System: 何か食べたいものはありますか?
OpenAI API response time: 0.593 sec
Koemotion API response time: 0.096 sec
User: そうだなー、カレーが良いかなあ
System: スパイシーなカレーが一番ですね!
OpenAI API response time: 0.880 sec
Koemotion API response time: 0.062 sec
User: そうだね
System: 自分で作るのですか?
OpenAI API response time: 0.717 sec
Koemotion API response time: 0.099 sec
User: 自作はしたことないな、簡単なの?
System: 意外と簡単ですよ、挑戦してみます?
OpenAI API response time: 0.610 sec
Koemotion API response time: 0.100 sec
User: うん、作り方教えてよ
System: もちろん、どんな具材を使いたいですか?
OpenAI API response time: 0.713 sec
Koemotion API response time: 0.063 sec
User: エビとカレーかな
System: エビカレー、いいですね!まずエビの下ごしらえから始めましょうか。
OpenAI API response time: 1.262 sec
Koemotion API response time: 0.077 sec

Koemotionで合成された音声が再生されるまでに要する時間は、今回の変更により0.1秒以下にまで抑えることができました。Koemotionライブラリ内の実装もhttpxベースのものに変更済みのため、すでにリポジトリをcloneしている方はgit pullを実行して最新のコードを反映してください。

まとめ

本記事では、弊社の提供するKoemotionのAPIを利用して音声合成を行う方法と、OpenAI社のGPT-4oとつないで高速な音声応答システムを実装する方法について解説しました。GPT-4oを用いることで自然な応答文が得られ、さらにKoemotionのストリーミング音声合成を活用することで、0.2秒以下0.1秒以下のオーバーヘッドで応答音声を得ることができました。Koemotionはパラメータを調整することで自分好みの声を探すことができるので、みなさんもぜひ自分の好きな声で音声応答システムを作ってみてください。

Discussion