ローカルPC上で完全に動作する音声会話AIを作る方法を解説します。DeepSeek-R1(Ollama)・Silero VAD・Whisper・VOICEVOXを組み合わせて、「話しかけると自動で認識して自然な声で返答してくれるシステム」をWindows環境に構築します。
システム構成
マイク音声 ↓ Silero VAD(発話検出) ← 声が来たら自動で反応 ↓ Whisper(音声→テキスト) ↓ DeepSeek-R1 / Ollama(AIの返答生成) ↓ VOICEVOX(テキスト→自然な音声) ↓ スピーカー
必要なもの
| ツール | 用途 | 入手先 |
|---|---|---|
| Python(embed版 or 通常版) | スクリプト実行 | python.org |
| ollama.exe | AIモデルのサーバー | github.com/ollama/ollama/releases |
| VOICEVOX(run.exe) | 日本語音声合成 | voicevox.hiroshiba.jp |
| ffmpeg.exe | Whisperの音声前処理 | github.com/BtbN/FFmpeg-Builds/releases |
フォルダ構成
すべてを1つのフォルダにまとめることで、フォルダごとコピーして別PCでも動かせます。
AItalker\ ├── ollama.exe ├── ffmpeg.exe ├── ffprobe.exe ├── voicevox\ │ └── run.exe ├── python-3.13.13-embed-amd64\ ├── models\ ← Ollamaのモデル保存先 ├── cache\ ← Whisperのモデル保存先 ├── voice_chat_vad.py ← A版(基本) ├── voice_chat_vad_full.py ← B版(ウェイクワード・割り込み対応) └── start_voice_chat.bat ← 起動用バッチ
セットアップ手順
Step 1:Python embed版の準備
Python embed版を使う場合は pip が使えるよう設定が必要です。
① python313._pth を編集
python-3.13.13-embed-amd64\python313._pth をメモ帳で開き、
# import site ↓ 変更(# を削除するだけ) import site
② pip をインストール
cd python-3.13.13-embed-amd64
curl -O https://bootstrap.pypa.io/get-pip.py
python get-pip.py
Step 2:ライブラリをインストール
python -m pip install setuptools wheel
python -m pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
python -m pip install numpy pyaudio openai-whisper ollama
※ NVIDIA GPU がある場合はCUDAバージョンに合わせてtorchを変更します。
REM CUDA 12.4 の場合
python -m pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124
※ pyaudio がインストールできない場合
python -m pip install pipwin
python -m pipwin install pyaudio
Step 3:Ollamaとモデルのセットアップ
OllamaはPythonのライブラリとは別のアプリケーションです。pip install ollama でインストールされるのはPythonからOllamaを操作するクライアントライブラリのみで、実際にAIを動かすOllama本体は別途必要です。
REM モデルをダウンロード(約5GB)
ollama.exe pull deepseek-r1:8b
Step 4:起動バッチファイルを作成
start_voice_chat.bat として保存してください。
@echo off
chcp 65001
cd /d C:\Users\Taro\Desktop\AItalker
REM パス設定(ffmpeg・Whisperモデル保存先・Ollamaモデル保存先)
set PATH=%~dp0;%PATH%
set XDG_CACHE_HOME=%~dp0cache
set OLLAMA_MODELS=%~dp0models
REM VOICEVOX 起動
echo VOICEVOX を起動中...
start /min "" "%~dp0voicevox\run.exe"
timeout /t 15 /nobreak > nul
REM Ollama 起動(二重起動防止)
tasklist | find "ollama.exe" > nul
if errorlevel 1 (
echo Ollama を起動中...
start /min "" "%~dp0ollama.exe" serve
timeout /t 5 /nobreak > nul
) else (
echo Ollama は既に起動中です。
)
REM Python 実行
python-3.13.13-embed-amd64\python voice_chat_vad.py
REM 終了処理
taskkill /f /im ollama.exe > nul 2>&1
taskkill /f /im run.exe > nul 2>&1
pause
あとは start_voice_chat.bat をダブルクリックするだけで全部起動します。
サンプルコード
常時マイクを監視して、声を検出したら自動で認識・返答します。ボタン操作は一切不要です。
"""
voice_chat_vad.py ─ A版(基本)
Silero VAD で常時マイクを監視し自然な会話を実現
"""
import sys, time, tempfile, os, re, json, wave
import urllib.request, urllib.parse, subprocess
import numpy as np
# ── 設定 ──────────────────────────────────────────
OLLAMA_MODEL = "deepseek-r1:8b"
WHISPER_MODEL = "medium" # tiny/base/small/medium から選ぶ
LANGUAGE = "ja"
SAMPLE_RATE = 16000
CHUNK_SAMPLES = 512 # Silero VAD 固定値(変更不可)
CHUNK_MS = int(CHUNK_SAMPLES / SAMPLE_RATE * 1000) # ≒ 32ms
VAD_THRESHOLD = 0.5 # 発話判定閾値(大きいほど厳しい)
SILENCE_TRIGGER_MS = 800 # 無音が続いたら発話終了とみなす時間(ms)
MIN_SPEECH_MS = 300 # これより短い発話は誤検知として無視
VOICEVOX_URL = "http://localhost:50021"
VOICEVOX_SPEAKER = 3 # 3=ずんだもん / 1=四国めたん / 8=春日部つむぎ
SYSTEM_PROMPT = """あなたは親切な日本語アシスタントです。
音声で会話するので、回答は3文以内で簡潔にまとめてください。
思考プロセス (...) は除いて最終的な回答のみ返してください。"""
EXIT_WORDS = ["終了", "やめて", "バイバイ", "おわり"]
# ── ライブラリ読み込み ──────────────────────────────
import torch, pyaudio, whisper, ollama
# ── VAD・Whisper 初期化 ────────────────────────────
def load_vad():
model, _ = torch.hub.load(
"snakers4/silero-vad", "silero_vad",
force_reload=False, trust_repo=True
)
return model
def load_whisper_model():
return whisper.load_model(WHISPER_MODEL)
# ── VOICEVOX 読み上げ ──────────────────────────────
def speak(text: str):
print(f"🔊 AI: {text}")
try:
url1 = f"{VOICEVOX_URL}/audio_query?text={urllib.parse.quote(text)}&speaker={VOICEVOX_SPEAKER}"
with urllib.request.urlopen(urllib.request.Request(url1, method="POST"), timeout=10) as r:
query = json.loads(r.read())
query["speedScale"] = 1.1
url2 = f"{VOICEVOX_URL}/synthesis?speaker={VOICEVOX_SPEAKER}"
data = json.dumps(query).encode()
req2 = urllib.request.Request(url2, data=data, method="POST",
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req2, timeout=30) as r:
wav = r.read()
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
f.write(wav); tmp = f.name
subprocess.run(["powershell", "-c",
f'(New-Object Media.SoundPlayer "{tmp}").PlaySync()'],
check=True, creationflags=subprocess.CREATE_NO_WINDOW)
os.unlink(tmp)
except Exception as e:
print(f"⚠️ TTS エラー: {e}")
# ── VOICEVOX 起動待ち ──────────────────────────────
def wait_for_voicevox(timeout=30):
print("⏳ VOICEVOX 起動待ち", end="", flush=True)
for _ in range(timeout):
try:
urllib.request.urlopen(f"{VOICEVOX_URL}/version", timeout=1)
print(" ✅"); return True
except:
print(".", end="", flush=True); time.sleep(1)
return False
# ── DeepSeek-R1 問い合わせ ─────────────────────────
def ask_deepseek(text: str, history: list) -> str:
history.append({"role": "user", "content": text})
res = ollama.chat(
model=OLLAMA_MODEL,
messages=[{"role": "system", "content": SYSTEM_PROMPT}] + history
)
answer = res.message.content if hasattr(res, "message") else res["message"]["content"]
answer = re.sub(r".*?", "", answer, flags=re.DOTALL).strip()
history.append({"role": "assistant", "content": answer})
return answer
# ── WAV 保存 ───────────────────────────────────────
def save_wav(frames, path):
with wave.open(path, "wb") as wf:
wf.setnchannels(1); wf.setsampwidth(2)
wf.setframerate(SAMPLE_RATE)
wf.writeframes(b"".join(frames))
# ── メイン VAD ループ ──────────────────────────────
def vad_loop(vad_model, whisper_model):
pa = pyaudio.PyAudio()
stream = pa.open(rate=SAMPLE_RATE, channels=1, format=pyaudio.paInt16,
input=True, frames_per_buffer=CHUNK_SAMPLES)
WAITING, RECORDING, PROCESSING = "WAITING", "RECORDING", "PROCESSING"
state = WAITING
frames, silence_n, speech_n = [], 0, 0
silence_limit = int(SILENCE_TRIGGER_MS / CHUNK_MS)
min_speech_chunks = int(MIN_SPEECH_MS / CHUNK_MS)
history = []
print("\n👂 聞いています...\n")
try:
while True:
raw = stream.read(CHUNK_SAMPLES, exception_on_overflow=False)
audio = torch.from_numpy(
np.frombuffer(raw, dtype=np.int16).astype(np.float32) / 32768.0)
with torch.no_grad():
is_speech = vad_model(audio, SAMPLE_RATE).item() > VAD_THRESHOLD
if state == WAITING:
if is_speech:
state = RECORDING; frames = [raw]; silence_n = 0; speech_n = 1
print("🎤 発話検知")
elif state == RECORDING:
frames.append(raw)
if is_speech: silence_n = 0; speech_n += 1
else: silence_n += 1
if silence_n >= silence_limit:
state = PROCESSING if speech_n >= min_speech_chunks else WAITING
if state == WAITING:
frames = []; silence_n = speech_n = 0
if state == PROCESSING:
stream.stop_stream()
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
tmp = f.name
save_wav(frames, tmp)
result = whisper_model.transcribe(tmp, language=LANGUAGE, fp16=False)
os.unlink(tmp)
text = result["text"].strip()
print(f"👤 あなた: {text}")
if not text:
pass
elif any(w in text for w in EXIT_WORDS):
speak("またね!"); break
else:
print("🤔 考え中...")
speak(ask_deepseek(text, history))
print("\n👂 聞いています...\n")
state = WAITING; frames = []; silence_n = speech_n = 0
stream.start_stream()
except KeyboardInterrupt:
speak("終了します。")
finally:
stream.stop_stream(); stream.close(); pa.terminate()
# ── エントリポイント ───────────────────────────────
if __name__ == "__main__":
if not wait_for_voicevox(): sys.exit(1)
vad_loop(load_vad(), load_whisper_model())
ハマりやすいエラーと解決策
❌ Cannot import ‘setuptools.build_meta’
embed版Pythonのデフォルト設定では setuptools が使えません。
解決: python313._pth の # import site → import site に変更してから get-pip.py を実行。
❌ ValueError: Provided number of samples is 1536
Silero VAD はチャンクサイズが 512サンプル固定 です。
解決: 設定を以下のように修正。
# ❌ 間違い
CHUNK_MS = 96
CHUNK_SAMPLES = int(SAMPLE_RATE * CHUNK_MS / 1000) # = 1536
# ✅ 正しい
CHUNK_SAMPLES = 512 # 変更不可
CHUNK_MS = int(CHUNK_SAMPLES / SAMPLE_RATE * 1000) # ≒ 32ms
❌ URLError: [WinError 10061] 接続が拒否されました
VOICEVOXが起動する前にPythonが接続しようとしています。
解決①: バッチファイルの待機時間を延ばす(timeout /t 15)
解決②: Pythonスクリプト側でVOICEVOXの起動を確認してから進む。
def wait_for_voicevox(timeout=30):
for _ in range(timeout):
try:
urllib.request.urlopen("http://localhost:50021/version", timeout=1)
return True
except:
time.sleep(1)
return False
❌ VOICEVOX がバッチから起動しない
start "" voicevox\run.exe だと相対パスが解決できない場合があります。
解決: %~dp0(バッチファイルの場所)を使って絶対パスで指定。
start /min "" "%~dp0voicevox\run.exe"
❌ ffmpegが見つからない
WhisperはPythonの中だけで動くのではなく、内部でffmpegコマンドを呼び出します。システムにffmpegがインストールされていない場合はエラーになります。
解決: ffmpeg.exe を AItalker\ フォルダに置き、バッチで PATH に追加。
set PATH=%~dp0;%PATH%
❌ ollama に接続できません
pip install ollama はPythonのクライアントライブラリのみです。AIを実際に動かすOllama本体(ollama.exe)は別途必要です。
解決: ollama.exe serve が起動しているか確認。バッチファイルで自動起動させる。
Whisperモデルの保存先
デフォルトのダウンロード先は C:\Users\ユーザー名\.cache\whisper\ です。
AItalkerフォルダ内にまとめたい場合はバッチファイルに追加:
set XDG_CACHE_HOME=%~dp0cache
これで AItalker\cache\whisper\medium.pt のように保存されます。
| モデル | サイズ | 日本語精度 | 推奨用途 |
|---|---|---|---|
| tiny | 39MB | △ | とにかく速さ優先 |
| base | 74MB | ○ | まず試すならここ |
| small | 244MB | ◎ | バランス重視 |
| medium | 769MB | ◎◎ | 日本語をしっかり認識したい |
| large | 1.5GB | ◎◎◎ | 最高精度・重い |
まとめ
本記事で構築したシステムの特徴:
- 完全ローカル動作:音声認識・AI・音声合成すべてオフライン
- 無料:使用ツールはすべて無料・オープンソース
- ポータブル:AItalkerフォルダごとコピーで別PCでも動作
- 自然な会話:ボタン不要・話しかけるだけで自動応答
VOICEVOXのキャラクターは VOICEVOX_SPEAKER の番号を変えるだけで変更でき、Whisperのモデルサイズを medium にすると日本語認識の精度が大幅に向上します。


