AIおしゃべりロボット・ハロ開発奮闘記(gpt-realtime/google stt/azure speech/voicevox)
AIおしゃべりロボットを作りました。
どんなものかは見てもらうのが早いでしょう。
中身はこのようになっています。
構成
ハードウェア
簡単に構成の紹介です。
- Raspberry pi 4 Model B (掃除してたら出てきたので使った)
- サーボモーター2つ
- pan用
- 口パク用
- LED (ハロスピーカーのものをそのまま流用)
- スピーカー (ハロスピーカーのモノをそのまま流用)
ソフトウェア
- 音声認識 (切り替え可能/realtime apiを使う場合は別プログラム実行が必要)
- azure speech
- google stt v2
- Realtime api(gpt realtime)
- LLM
- gpt-4o mini
- 音声合成
- voicevox (ラズパイ内ではきついので自宅サーバーにアクセス)
はじまりはハロスピーカー
音に合わせて目が点滅くらいするだろう……そんなことを思っていました。
けれど残念ながらそんなことはなく、電源をつけると目が普通に光るだけ。
そこでLチカ(LEDチカチカ)くらいは自分で実装しようと思い立ちました。
けどそこで思ったのです。
「いやいや。Lチカだけじゃなく動いておしゃべりまでさせてしまおう!」
ハロスピーカーのLEDとスピーカーを奪う
分解の様子は取っていなかったのですが、この方のブログにある通り中身はスカスカです。
サイズ感的にもちょうど良さそうです。ここはまた後程。
LEDは配線を切り取り、5kΩを挟みラズパイに接続し点灯を確認。
LEDは明るすぎるとハロの目が輝きすぎて覚醒or自爆してしまいそうな雰囲気になります。
pythonでGPIOをimportして使っています。
スピーカーは特に切り取らずオーディオケーブルで接続しただけです。
(最初の写真通り)
音声認識→LLM→音声合成のオーソドックスな構成 vs Speech-to-Speech
続いておしゃべりです。
「おしゃべりができる」を分解すると「聞く」「考える」「話す」に分けられます。
これを愚直に実装しています。
ただ実装中にgpt-realtime(Realtime api)が出たのでそちらも試しています。
こだわるべきは「速い反応」
ゲーム作りで「ユーザーが操作したらとにかく音を出せ」といったような話があります。
ユーザーのアクションが何かあったら反応をさせろ、ですね。
このハロも同じで、とにかくユーザーのアクションに対して反応する、しかも速く反応を出す、にこだわっています。
取り組みは後述です。
音声認識(STT/Speech To Text)
Google Speech-To-Text(latest_short)
最初はgoogleのSpeech-To-Text v2のlatest_shortをstreamingで使っていました。
ただどうにも安定しない。
10回に1回くらいでしたがテキストが返ってこず、そのまま話しているとまとめて認識結果が出てくることがありました。
調査してみると、どうやらis_final(終話検知/話が終わったことの検知)が返ってこないことがありそうなことが報告されていました。
Azure Speech Service
次に使ってみたのは Azure Speech Serviceです。streamingで使っています。
こちらは実装も難しくないですし、とても安定もしています。
しっかりと認識テキストが返ってきます。
ただ、遅い。
他のSTTを見た後だと鈍重に感じてしまいます。
とはいえgoogleで大苦戦して頭を抱えた経験後の安定動作は魅力的です。
速度はGoogle、精度は大差なし
ラズパイ4B上でazure speechとgoogle sttを比較していました。
「おしゃべりできる?」
くらいのワードの場合、google sttの方がazureより1秒程度は認識結果が返ってくるのが速いです。
azureは良くも悪くも安定です。
認識精度は最近のものはすべてよく、azure/googleともに「おしゃべりできる?」といった会話程度の発話なら100%の精度でした。
ただ先に書いた通りgoogleはまたに認識結果が返ってこないことがあったためazureを採用しました。
(とはいえ速度が魅力でgoogle sttに切り替えられるようにもなっている)
発話までの遅さは別手段でカバーしています。それはまた後程。
Realtime api(gpt-realtime)
ハロを作っている途中、gpt-realtime(モデル)が使えるようになったので早速使いました。
Realtime apiはSpeech-to-Speechです。
最初に書いた音声認識→LLM→音声合成という流れを自分で行うことなく、話したことに対するレスポンスが返ってきます。
ラズパイ上での動作でも高速で返事があります。話者の発話完了から合成音声の再生まで1~1.5秒程度でしょうか。
あまりに驚いて声が出てしまうほどの速度、でしょうか。
音声認識→LLM→音声合成は発話者の発話完了から音声合成の再生まで2.5秒から3秒かかります。
速度的に勝負になりません。
しかし……あのアメリカンのノリがどうにも(笑)
Realtime apiでも大変なことがありました。それが……
instructionsが効かない!
instructionsはいうなればsystem prompotです。
これが効かないと、アメリカンなノリでとんでもなく長い話を聞かされることになります。
raspberry上で戻ってきたデータを確認していてわかったことが、
session_updateのsessionの中にはtypeという項目がない。
公式だと"type":"realtime"
としっかり書かれています。
なのでこの通りに行うとerrorでinstructionsが反映されないということになります。
ただ謎なのが、windowsで試していた時はtypeがあるのです……。
raspberry piで動いたのでこの謎は特にこれ以上は追っていません。
また、Realtime apiでは自分の声を拾って話しだしてしまうので、一旦話している間マイクオフを入れています。
後々になって自分の声を無視させる実装もしました(realtime apiには使ってないけれど)。
それは後述します。
Realtime apiの音声合成をvoicevoxの声に変更する
Realtime apiは決められた声しか使えません。
けれどハロにはそれは合いません。
よって、声を奪って違う声にします。
やり方は難しくありません。
Realtime apiは話す音声と一緒にテキストもstreamingで返ってきます。
なので音声は再生させず、streamingで返ってきたメッセージをvoicevoxに投げるだけです。
チャンクで投げてしまうと一言ごとの合成になってしまうので、句読点(つまり文のキリがいい場所)までテキストをためてそこで再生させる、という手段を取っています。
ただこの場合voicevoxの音声合成時間が入るため、1~2秒程度音声再生が遅れます。
この時点で発話完了から音声合成開始までの速度は音声認識→LLM→音声合成と大差なくなります。
音声合成はPCにvoicevoxのサーバーを立てて対応
音声合成をraspberry piにやらせるのは利がありません。
よって家のPCにvoicevoxのサーバーを立ち上げます。
といっても簡単です。
voicevoxのフォルダにvv-engineというフォルダがあります。
そこでrun.exeを以下の様に実行すればよいです。
run.exe --host 0.0.0.0 --port 50021
50021ポートは開ける必要があります。
windowsの場合、
windows Defender ファイアウォール→詳細設定→受信の規則→新しい規則
とやって50021を開けてあげるとよいです。
音声合成は合成と再生を同期実行させています。
またLLMをstreamingにして来た順にstreamingで音声合成させるのもいいのですが、文の区切りの待ち時間が絶妙に長いので、シンプルなLLM→音声合成の構成を取っています。
話者発話完了から音声合成再生開始の時間をできる限り早くする
やはりおしゃべりをするときに言い終わってからすぐに反応は欲しいものです。
その場合、Realtime apiが最速ですが、音声を変えるとなると他と大差がありません。
そうなると音声認識→LLM→音声合成で速いのは以下の組み合わせです(音声合成はvoicevoxしか試していない)
google stt v2 → gpt-4.1-nano → voicevox
raspberry pi 4Bで話者の発話完了から音声合成再生開始まで2秒ほどになります。
ただやっぱり……gpt-4.1-nanoのレスはちょっとイマイチ……。
話者発話完了から音声合成再生開始までの隙間を埋めるアプローチ
ゲームの格言でもないですが、できれば話したらすぐに反応をしてほしいわけです。
ただ今の状態だとそれもかないませんので、隙間時間で「考え中~」といった相槌をwavファイル化して再生させるアプローチをとりました。
2秒程度に調整した相槌のwavファイルを100程度用意し、発話完了と同時にwavファイルを流して、あたかも「すぐに反応」したように見せています。
無音期間がほぼないだけでだいぶ体感が変わります。
音声での割り込み
音声で割り込みができるように実装も行いました。
つまり、ハロが話している最中に私が話しかけると止まって音声認識を始めるという実装です。
一見VAD(voice-activity-detection/音声検知)で止めてしまえばよいように思えます。
ですがそれではうまくいきません。
ハロ自身が話しているので、単純にVADだと自分の発話に反応して発話を止めてしまいます。
よって自分の発話を打ち消すような実装が必要です。
これにはエコーキャンセリング(AEC)があります。
pythonですとwebrtc-audio-processingがありそうです。
ただraspberry piに実装する前にwindowsで動作確認をしているのですが、webrtc-audio-processingは残念ながらwindowsには入りません。wslを使えばよいと言えばよいのですが、これまで作ってきた環境もあるので、いったん置きです。
今回はマイクの音が自分のTTS音とどれだけ似てるか(正規化相関)を判定して、似ていたら無視する方式を取っています。(今までの実装をあまり変えなくても実装できる)
エコーキャンセリングと違って、閾値の絶妙な調整などが必要ではあります。
結果としては以下の通り、うまく動いています。
ボリュームに左右されない計算……のはずですが、合成音声の音量を上げてしまうと反応はしてしまいます。
ハードウェアの実装
raspberry piをハロに載せるのはギリギリできると言えばできるのですが……
マイクが入り切りませんでした(笑)
そこでハロのお尻はこのように加工しています。
これでUSBも使えますし、ネットワークが気になるなら有線LANで接続することも可能です。
サーボモーターの実装はGPIOだと震える
ソフトはやってきましたが、サーボモーターの実装は初めてでした。
そこで、いろんなページで紹介されているようにLEDと同様にGPIOで制御しようとしました。
その結果、特に命令を送っていないのにビクビクと震えるハロになってしまいました。
そこでGPIOをやめてpigpioで実装。
こちらだと先ほどの動画の様に動作が安定しました。
サーボモーターの音が気になりますが、これはこれでメカメカしくて好きです(笑)
ハロの上下を止めているのは磁石
raspberry piを載せるためにねじを止める部分を全て取っ払ってしまいました。
そこでどうやって上下を止めようかと思ったのですが、そこは磁石で固定することにしました。
横部分は小さい磁石、後ろは大き目の磁石です。
これには理由があります。
口パクをさせるためにその磁石サイズと大きさにしています。
ハロの口近くにサーボモーターを仕込んでいます。
これが45度ほど動くことによって上半分を持ち上げます。
このとき横の小さな磁石は離れますが、後ろの大き目な磁石は離れません。
これによって後ろの磁石が軸になり「口が開く」という機構を実現しています。
TTS(Text To Speech)に合わせてLED光らせる&口パク
TTSも制御下にあるので、音声合成生成に合わせてLEDをチカチカさせて口パクをさせました。
よくよく考えるとハロは話すときに目はチカチカしますが、口は開かなかった来がします。
けれど可愛いし私が実装したかったのだから良しです(笑)
最後に
思い出したままに記載したので長い文章となってしまいました。
ロボット開発は初めてでしたがとても楽しい体験でした。
今もハロは絶賛手を加えていて、パソコンのブラウザを制御できるような仕組みを組み込みました。
なんちゃってfunction callingです。
ハロが「これを紹介したい」というときにブラウザを操作して表示できるようにしています。
switchbotもapi利用ができますしスマートホーム制御もできそうです。
自分でロボットを作ると夢が広がります(笑)
まずはマスコットロボットとして私の横で、アニメで見るハロの様におしゃべりしてくれる可愛らしいハロにしていきます(笑)
Discussion
帽子がつきましたw