LlamaIndexのopenai_realtime_clientを試す
ここで知った
GitHubレポジトリ
Python用OpenAIリアルタイムAPIクライアント
これはPythonとLlamaIndex用の実験的なOpenAIリアルタイムAPIクライアントである。LlamaIndexのツールと統合されており、カスタム音声アシスタントを素早く構築できる。
ターミナルで直接実行する2つの例を含める。手動およびサーバーVADモードの両方を使用する(つまり、チャットボットを中断できる)。
ローカルのMacで。
ffmpegが必要だけど、自分の環境にはもう入っている。
$ which ffmpeg
/opt/homebrew/bin/ffmpeg
レポジトリをクローン。このレポジトリが必要なのではなくて、このレポジトリにサンプルコードがある。
$ git clone https://github.com/run-llama/openai_realtime_client && cd openai_realtime_client
仮想環境作成
$ python -m venv venv
$ source venv/bin/activate
LlamaIndexが作成したopenai-realtime-client
パッケージをインストール。どうでもいいけど、公式がもしOpenAI Python SDKとは別にパッケージ出してきたらややこしくなりそうな名前である。。。
$ pip install openai-realtime-client
OpenAI APIキーをセット
$ export OPENAI_API_KEY="sk-XXXXXXXXXX"
これで準備完了。
サンプルコードは2つ用意されている。
-
examples/manual_cli.py
: ユーザとAIで会話のターンを交互に繰り返す、トランシーバー的なもの -
examples/streaming_cli.py
: AIが会話中にユーザが割り込みができるもの
つまり、ユーザのVAD(音声区間検出)をマニュアルでやるか自動でやるかの違いになっている。
で、両方ともToolとして以下が実装されている。
# Add your own tools here!
# NOTE: FunctionTool parses the docstring to get description, the tool name is the function name
def get_phone_number(name: str) -> str:
"""Get my phone number."""
if name == "Jerry":
return "1234567890"
elif name == "Logan":
return "0987654321"
else:
return "Unknown"
ここを日本語でも使えるように2ファイルとも少し書き換える。
# Add your own tools here!
# NOTE: FunctionTool parses the docstring to get description, the tool name is the function name
def get_phone_number(name: str) -> str:
"""Get my phone number."""
if name == "太郎":
return "1234567890"
elif name == "花子":
return "0987654321"
else:
return "Unknown"
では、examples/manual_cli.py
の方から試してみる。実行。
$ python examples/manual_cli.py
実行するとこんな感じでメッセージが出る。
Starting Realtime API CLI...
This process is not trusted! Input event monitoring will not be possible until it is added to accessibility clients.
Connected to OpenAI Realtime API!
Commands:
- Type your message and press Enter to send text
- Press 'r' to start recording audio
- Press 'space' to stop recording
- Press 'q' to quit
r
を押すと音声の聞き取りがスタートして、スペースで
聞き取りを終了して送信する、q
で会話を終了するというようなものになっている。
とりあえずこんな感じで音声でやり取りできた。
User: おはようございます。
Assistant: おはようございます。
User: 太郎さんの電話番号を教えて。
Assitant: 太郎さんの電話番号は 1234567890 です。
User: 明日の天気を教えて。
Assistant: 申し訳ありませんが、天気の情報にはアクセスできません。
examples/streaming_cli.py
の方も。こちらは起動したらずっと聞き取りが開始されていて、特に会話のターンを気にする必要はない。終わるときだけq
を入力すれば良い。
$ python examples/streaming_cli.py
User: おはようございます。
Assistant: おはようございます。今日はどんなお手伝いができますか?
User: 太郎さんの電話番号を教えて。
Assitant: 太郎さんの電話番号…
User: (会話途中で割り込み)あー、ごめん、やっぱり花子さんで。
Assistant: 花子さんの電話番号は 098756431 です。
ちょっと発話が途中でおかしくなったりするし、発話ミスのようなものもちらほらあるけど、こちらはかなりシームレスに感じる。
examples/streaming_cli.py
のコードをざっと読む。
- 最初に2つのハンドラを初期化
-
AudioHandler()
はPyAudioを使った音声の入出力を処理するハンドラ -
InputHandler()
はキーボード入力を処理するハンドラ
-
-
InputHandler()
は非同期のイベントループに入る -
RealtimeClient
がRealtimeAPIに接続するためのクライアントでこれを初期化-
on_text_delta
でテキストのストリーミング出力を行うハンドラを指定 -
on_audio_delta
でオーディオのストリーミング出力を行うハンドラを指定 -
on_interrupt
で音声入力の割り込みが入った場合に、音声出力を停止するハンドラを指定 -
turn_detection_mode
で、ユーザのVADの方式を指定。server_vad
が自動で、manual
が手動 -
tools
でTool useの関数を指定
-
- キーボードの入力を拾うリスナーを初期化して開始
- Realtime APIに接続
- WebSocketのメッセージのやり取りを処理する
client.handle_messages
を非同期タスクに登録 - 音声のストリーミング処理を継続的いに行う
AudioHandler.start_streaming
を非同期タスクに登録
という感じ。Realtime APIの細かいやり取りはRealtimeClient
の中でやってるみたいなので、そこまでは追ってないけど、かなりシンプルに書ける感はある。
ネイティブの実装を理解しないとだなぁ