KDDIアジャイル開発センター たかたにです。話題のスタックチャンをローカルではなくクラウド上で音声認識させてみた話です。

この記事について
デスクの上のスタックチャンに AWS の AI サービスをつなぎ込んで、
「話しかけたら喋り返してくれるロボット」を作りました。
使ったのは M5Stack CoreS3 と AWS AgentCore、
Amazon Bedrock の Claude Haiku、Amazon Transcribe、Amazon Polly です。
きっかけ
朝イチでオフィスに着いたとき、まだ誰もいないことってよくありますよね。
「おはよう」と言っても返事がない。ちょっとした雑談相手がいるだけで一日の
スタートが変わると思いつつ、そのまま一人で PC を開く日々でした。
そこで考えたのが「デスクの上のスタックチャンが迎えてくれたら」というアイデアです。
趣味でスタックチャンに声と知能を授けるプロジェクトをやっていたので、
ちょうどいいテーマでした。
スタックチャンとは?
ししかわさん(@meganetaaan)が公開している
オープンソースの手乗りロボットです。
ハードウェア構成はシンプルで、M5Stack にサーボ 2 軸(首振り)を足して
3D プリントした筐体に入れるだけ。ソフトウェアは M5Stack-Avatar(顔表示)と
stackchan-arduino(サーボ制御)が中心で、派生キットやコミュニティも活発です。
今回は CoreS3 を選びました。マイク・スピーカー・カメラがすべて内蔵されていて、
会話機能を乗せるのに必要なものが最初から揃っています。
会話のしくみ
話しかけると喋り返してくれる体験を作るため、こんなフローを組みました。
「今日の調子はどう?」(マイクで録音 16kHz/16bit/3秒)
↓ AWS へ POST
↓ STT — Amazon Transcribe でテキスト化
↓ Chat — Claude Haiku で応答生成(感情タグ付き)
↓ TTS — Amazon Polly(Kazuha)で MP3 合成
↓ S3 presigned URL 経由で取得
「元気だよ!今日もいい天気だね!」← ロパク+首振り付き
さらに、感情に応じてアバターの表情とサーボが連動します(後述)。
AWS AgentCore を使った理由
AgentCore はエージェント向けのマネージドゲートウェイです。
MCP(Model Context Protocol)ツールとして speak(text) を登録しておくと、
エージェントが「喋りたい」と判断したタイミングで勝手に呼んでくれます。
Cognito OAuth2 による認証も Gateway が面倒を見てくれるので、
デバイス側の実装がかなりシンプルになりました。
ツール定義の例はこんな感じです。
{
"name": "speak",
"parameters": {
"text": "合成するテキスト",
"voice_id": "Kazuha"
},
"returns": {
"audio_url": "S3のpresigned URL (5分TTL)"
}
}
ただ、実際に運用してみると「毎回 AgentCore 経由だとラウンドトリップが多くて遅い」
という問題にぶつかりました。最終的には STT + Chat + TTS を 1 回の POST にまとめた
統合 Lambda に切り替えています(詳しくは後述)。
AgentCore が本領を発揮するのは、複数のツールを AI が判断して使い分けるシナリオだと思います。
感情タグで表情とサーボを連動させる
この部分が個人的に一番楽しかったです。
Claude Haiku に「応答の先頭に感情タグをつけてください」と指示します。
レスポンス例: "[happy] 元気だよ!今日もいい天気だね!"
M5Stack 側でこのタグをパースして、Avatar の表情とサーボの動きを切り替えます。
| タグ | Avatar の表情 | サーボの動き |
|---|---|---|
| happy | 嬉しい | 首を左右にフリフリ |
| sad | 悲しい | うなだれる(首が下を向く) |
| angry | 怒り | |
| doubt | 疑問 | |
| sleepy | 眠い |
サーボ制御は FreeRTOS タスクでメイン処理と並列実行しています。
AI が感情を返して体が動く、この連動がめちゃくちゃ楽しいです。
製造業エンジニアの苦しみ — 組込み × クラウド AI はレイテンシとの戦い
ここが一番しんどかったところです。問題と対策をまとめます。
音声データがデカすぎる
presigned URL で音声を取得しようとしたら、PCM 16kHz だと 500KB を超えてメモリが厳しい。
minimp3 ライブラリで MP3 にしたところ 50KB まで圧縮できました。
PSRAM の余裕も一気に改善しました。
往復レイテンシが長すぎる
録音 → 送信 → STT → AI → TTS → 再生の一連の流れは往復で数秒かかります。
AgentCore 経由で毎回 speak を呼ぶ構成だとさらにラウンドトリップが増えました。
STT + Chat + TTS を 1 回の POST で完結させる統合 Lambda に変えて解決しました。
タッチパネルのゴーストタッチ
CoreS3 のタッチパネルがゴーストタッチを拾いまくる問題がありました。
デバウンス 1 秒 + 同座標 3 回ブロックのフィルタを実装して落ち着きました。
文単位ストリームで Time-to-first-audio を短くする
レイテンシ対策でもう一つ効いたのが「文単位ストリーム」です。
AI の応答例: 「こんにちは。今日もいい天気。お散歩日和だね。」
句読点「。」で文を分割して、1 文目を再生しながら 2 文目を裏側で取得します。
FreeRTOS の Core 0 でバックグラウンド取得を走らせることで、
ユーザーが感じる待ち時間をかなり短くできました。
[合成1] こんにちは。
[再生1] ▶▶▶▶▶▶▶▶
[合成2] 今日もいい天気。
[再生2] ▶▶▶▶▶▶▶▶▶▶▶
[合成3] お散歩日和だね。
[再生3] ▶▶▶▶▶▶▶▶▶▶▶▶▶
実装してわかった「組込み AI あるある」
| やってみたこと | 現実 | 対応 |
|---|---|---|
| presigned URL で音声取得 | PCM 16kHz で 500KB 超え、メモリ圧迫 | minimp3 で 50KB に圧縮、PSRAM に余裕 |
| AgentCore 経由で毎回 speak | ラウンドトリップ多すぎ、遅い | STT+Chat+TTS を統合 Lambda で 1 POST に |
| タッチパネルで操作 | CoreS3 のゴーストタッチ地獄 | デバウンス 1s + 同座標 3 回ブロック |
動くプロトタイプは 1 日で作れます。でも「まともに動く」レベルになるまで 3 日かかりました。
組込みあるあるです。
スタックチャンかわいい
話しかけると嬉しそうに首を振るスタックチャン、最高です。
コードは GitHub で公開予定なので、もし興味があればぜひ試してみてください。
質問や感想はお気軽にどうぞ。