これまで、道具を使えるローカルAIエージェント(p935)と、声で話せるAIアシスタント(p936)を別々に作りました。今回はこの2つを合体させて、「声で頼むと、エージェントが道具を使って実行し、結果を声で返す」——そんな“声で操作できるローカルAIエージェント”にします。もちろん全部Macローカルで完結します。

🎙 この記事でわかること
p935のエージェント(Ministral 3 14B・検索/ファイル/PC操作の道具・RAG)音声の入出力を足す方法
p936の音声部品(Whisper=耳/VOICEVOX=口)そのまま移植して統合する流れ
・音声で聞きやすくするための「短く返させる」工夫と状態表示
声で「◯◯を検索して」と頼むと、道具を実行して声で答える実機デモ。つまずきと対処つき

作るもの:声で操作できるローカルAIエージェント

スポンサーリンク

完成形がこちら。見た目はp935のエージェントのままですが、下に「🎤話す」ボタンと「音声で返す」トグルが増えています。声で話しかけると、エージェントが動いて、答えを声で返してくれます。

音声機能を統合したローカルAIエージェントの起動画面
起動画面。ステータスに「🎙音声ON(Whisper=small/VOICEVOX)」。下部に🎤話す・WAVで話す・音声で返す・声の選択が追加された

いちばん見せたいのはこれ。「東京タワーの高さを検索して教えて」と“声で”頼むと、エージェントが自分で web_search の道具を実行し、「333メートルです」と短く声で答えます。

声で検索を頼むとエージェントがweb_searchを実行して短く答える画面
声で「検索して」→ 🔧web_searchを実行 → 「東京タワーの高さは333メートルです」と短く回答=これをVOICEVOXが読み上げる(下は「話しています🔊」)

発想:p935の“頭脳”に、p936の“耳と口”を足す

統合といっても難しくありません。p935のエージェントは「テキストを渡すと、道具を使って考え、テキストで返す」仕組み(agent.run(文章))。その前後に音声を足すだけです。

図解:p935エージェントの入出力を“音声化”する(すべてMacローカル) マイク/録音音声(WAV) Whisper耳:音声→文字 p935エージェントMinistral 3 14B道具・RAG・安全機構 VOICEVOX口:文字→音声 スピーカー再生🔊 紫の中央=p935の頭脳(そのまま)。左にWhisper(耳)、右にVOICEVOX(口)を足すだけ。 道具・RAG・緊急停止・確認ダイアログなどp935の機能は一切変えずに“声で操作”できるようになる。
中央のエージェント(頭脳)はそのまま。前にWhisper(耳)、後ろにVOICEVOX(口)を足すだけで“声で操作”に

どう統合したか(部品の移植)

やったことは大きく2つだけです。

  • p936の音声部品をそのまま移植core/stt.py(音声認識)・core/tts.py(VOICEVOX)・core/recorder.py(録音)を、p935のフォルダにコピーしただけ。自己完結したモジュールなので改造不要でした(音声認識は後述の理由でfaster-whisperに統一)。
  • アプリに音声の入口と出口を追加:p935のGUIに「🎤話す/WAVで話す/音声で返す/声の選択」を足し、エージェント処理の前に文字起こし、後に読み上げを挟みました。

ここが今回の方針でもあります。「ローカルLLMで実現できることは、独立アプリを増やさず“エージェントへの機能追加”として育てる」——音声もその第一弾です。頭脳(Ministral 3)はp935のまま、耳と口だけを足しました。

実装の要点:音声入力→エージェント→音声で返答

流れはこれだけ。録音WAV→Whisperで文字起こし→agent.run()→返答をVOICEVOXで読み上げ。GUIが固まらないよう、それぞれ別スレッドで動かします。

# ① 音声を文字に(Whisper)→ その文字をいつものエージェントへ流す
def _on_heard(self, text):
    self.inp.setPlainText(text)                 # 画面には聞き取り文をそのまま表示
    hint = text + "\n\n(音声での質問です。声で読み上げるので1〜2文で簡潔に)"
    self.on_send(send_override=hint)            # 送信文にだけ“簡潔に”ヒントを添える

# ② エージェントの返答が出たら、音声で返す(VOICEVOX)
def on_answer(self, ans):
    self.show(ans)                              # 画面に表示(従来どおり)
    if self.voice_out.isChecked():
        text = self._voice_text(ans)            # 読み上げ用に短く整える(後述)
        TtsWorker(text, speaker).start()        # VOICEVOXで合成→再生

ポイントは、エージェント本体(agent.run)には一切手を入れていないこと。道具の実行も、削除などの確認ダイアログも、緊急停止も、p935のまま活きています。声はあくまで“入口と出口”です。

工夫:音声用に“短く”返させる

そのままだと問題がありました。エージェントの返答は丁寧で長い(道具の結果や箇条書きを含む)ため、声で全部読み上げると長すぎるし、生成にも時間がかかります。最初のテストでは返答に84秒かかりました。

そこで2段構えにしました。

  • 入力時:音声からの質問には「声で読み上げるので1〜2文で簡潔に」というヒントを添えて送る(画面表示は聞き取り文のまま)。これで返答が短くなり、生成も速くなりました。
  • 出力時:読み上げる前に _voice_text()記号やURLを除き、長ければ先頭の数文だけにする。画面には全文、耳には要点、という住み分けです。
def _voice_text(self, text):
    t = re.sub(r"https?://\S+", "", text)   # URLは読まない
    t = re.sub(r"[*#`\[\]<>]", "", t)        # 記号を除去
    if len(t) > 160:                         # 長い返答は先頭の数文だけ読み上げる
        t = first_sentences(t, limit=160) + "…(続きは画面で)"
    return t

動かす:声で「検索して」→道具を実行→声で返す

実演です(再現性のため、マイクの代わりに短い録音WAVを入力にしています=p936と同じやり方)。

まず「あなたは何ができるの?」と声で質問。エージェントが自分の機能(検索・ファイル/PC操作・コマンド実行など)を短く答え、それを読み上げます。処理中はインジケータがオレンジ、読み上げ中は「話しています🔊」に変わります。

声の質問にエージェントが短く答えて読み上げる画面
声で「何ができる?」→ エージェントが自分の能力を簡潔に回答 → VOICEVOXで読み上げ(下は「話しています🔊」)

そして本命。「東京タワーの高さを検索して教えて」と声で頼むと、エージェントが自分でweb_searchの道具を実行し、検索結果から「333メートルです」と短く声で答えました(先ほどの冒頭の画像)。声だけで、道具を使う作業を頼めたわけです。

もちろん、道具の使用中も緊急停止(Esc・画面隅)や、削除・PC操作などの確認ダイアログはp935のまま効きます。声で操作できても、安全装置はそのままです。

つまずきポイントと対処

  • 返答が長く・遅い:14Bのエージェントは丁寧で、声にすると長すぎ・生成84秒も。→ 音声入力に「1〜2文で簡潔に」ヒントを添え、読み上げは先頭数文だけに。体感が一気に良くなりました。
  • 聞き取りの精度p936同様、音声認識モデルはsmallを使用(baseは頭字語を誤認識しやすい)。
  • Windowsで音声認識が動かない(重要):Windows 11のSmart App Control(SAC)がONだと、openai-whisperが使う numba の未署名DLLをブロックし、音声認識が ImportError で起動しませんでした。SACはセキュリティ機能で気軽に切れない(OFFにするとOS再セットアップまで戻せない)ため、→ numba非依存の faster-whisper に切り替え、音声デコードも soundfile で行うことでSACを切らずに動作。Mac/Windows共通の1つのコードにできました。
  • メモリの配分:Mac(32GBユニファイド)は余裕。Windows(RTX3060 12GB)で動かす場合は、Ministral 14BをGPU、Whisper・VOICEVOXはCPUに寄せるとVRAMに収まります。
  • 確認は“声”ではなく画面で:削除・送信などの重要操作は、誤操作防止のため従来どおり画面の確認ダイアログで受けるようにしました(声だけで許可しない)。
  • VOICEVOXの起動:音声で返すにはVOICEVOX ENGINEの起動が必要(p936参照)。未起動なら「音声=Whisperのみ」と表示し、テキストでは普通に使えます。

Windowsでも声で操作できた(RTX3060)

この音声統合も、Windows 11(RTX3060)で検証しました。結論、声で操作する一連の流れ(音声→文字起こし→エージェントが応答→読み上げ)がWindowsでも動作。前章のSmart App Control対策でfaster-whisperに統一したことで、Mac/Windows同じコードで動きます。

Windowsで音声統合エージェントが動作している画面
Windows:WAV入力→faster-whisperで文字起こし→エージェントが自分の機能を回答→VOICEVOXで読み上げ。「🎙音声ON(Whisper=small/VOICEVOX)」表示

メモリの配分も狙いどおりで、Ministral 3(頭脳)はGPU(VRAM約8.7GB/12GB)、faster-whisperとVOICEVOXはCPU。GPUを取り合わないので、RTX3060の12GBに収まりました。文字起こしはCPUのfaster-whisper(small/int8)で約1.2秒/件と実用的です。

この振り分けは意図的で、実装は次の3か所に分かれています。GPUは頭脳(LLM)専用に空けておき、音声系はCPUに寄せる——これでVRAM 12GBに収まります。

図解:GPUとCPUの役割分担(RTX3060 12GBに収める) GPU(RTX3060 / VRAM 12GB) Ministral 3 14B(頭脳)道具・RAG・画像理解 … 約8.7GBllama-cpp: n_gpu_layers=-1 CPU faster-whisper(音声認識)device="cpu" VOICEVOX(音声合成)CPU版エンジンを起動 GPUはLLM専用に空ける → 音声系はCPUに寄せる → VRAM 12GBに収まる(Macはユニファイドメモリなので同じコードでそのまま動く)
頭脳(Ministral)だけGPU、音声認識・音声合成はCPU。GPUを取り合わせないのがVRAM12GBに収めるコツ
# ① 頭脳(Ministral)は GPU へ(llama-cpp-python / core/llm.py)
Llama(model_path="…Ministral-3-14B…Q4_K_M.gguf",
      n_gpu_layers=-1)          # -1 = 全レイヤーをGPU(CUDA/Metal)へ載せる

# ② 音声認識(faster-whisper)は CPU に固定(core/stt.py)
WhisperModel("small", device="cpu", compute_type="int8")   # ←GPUを使わない

# ③ 音声合成(VOICEVOX)は「CPU版エンジン」を起動して HTTP で使う(core/tts.py)
#    アプリは 127.0.0.1:50021 に繋ぐだけ。デバイスはエンジンのビルド(CPU版/GPU版)で決まる
requests.post("http://127.0.0.1:50021/audio_query", params={"text": text, "speaker": 3})

つまり①②はコードで明示的にGPU/CPUを指定し、③はCPU版のVOICEVOXエンジンを起動するという設計です(アプリ側はHTTPで話すだけ)。

💡 「CPUに寄せる」=データをRAMに置く、という意味
モデルをGPUで動かすと、その重みや計算データはVRAM(GPU専用メモリ)に載ります。CPUで動かすと、代わりにRAM(メインメモリ)に載ってCPUが計算します。RTX3060のVRAMは12GBしかないので、そこはMinistral(約8.7GB)専用に空け、音声認識(Whisper)と音声合成(VOICEVOX)は潤沢なRAM+CPUに逃がす——というのが今回の配分です。CPUは少し遅いですが、Whisper-small(約1.2秒)やVOICEVOXは実用速度でした。
なおVOICEVOXにはGPU版(NVIDIA CUDA / DirectML)もあり、GPUで合成すればより速いですが、それだとMinistralとVRAMを取り合うので今回はCPU版を選んでいます。Macはユニファイドメモリ(RAMとVRAMが一体)なので、この区別自体が不要で、同じコードでそのまま動きます。

※ Windows特有の小ハマり:faster-whisperの一部DLL(llvmlite)ロードに失敗する場合は、VC++再頒布可能パッケージ(winget install Microsoft.VCRedist.2015+.x64)で解消しました。

まとめ:エージェントに機能を足していく

別々に作ったエージェント(頭脳)音声(耳と口)を合体させ、「声で頼むと道具を実行して声で返す」ローカルAIエージェントができました。頭脳には手を入れず、入口と出口に音声を足すだけ。まさに“機能追加”です。

  • Whisper(耳)+Ministral 3(頭脳・道具)+VOICEVOX(口)を1つに。全部Macローカル
  • 音声用の「短く返させる」工夫で、待ち時間と聞きやすさを改善
  • 道具・RAG・緊急停止・確認ダイアログなどp935の機能はそのまま活きる

今後も、ローカルLLM(Ministral 3)でできることはこの“ローカルAIエージェント”に機能を足していく方針です。独立したアプリを増やすのではなく、1つの相棒を育てていく——そのほうが使い込むほど便利になります。声で操作できるようになって、ぐっと“相棒”らしくなりました。

それでは、今回はここまで。最後までありがとうございました😊