Architecture Deep Dive
ローカルLLM × 音声 × TTS × 外部API 統合チャットアプリの設計
| クラス | 責務 | スレッド |
|---|---|---|
| ChatApp | Tkinter ウィンドウ全体・イベント配線・UI 更新 | メインスレッド |
| Controller | 入力統合・LLM 制御・後処理・TTS トリガー | メイン + ワーカー生成 |
| LLMService | ストリーミング推論・abort フラグ管理 | ワーカー(デーモン) |
| TTSWorker | SAPI5 発話キュー・on_start / on_stop コールバック | デーモン常駐 |
| VoiceRecognizer | PyAudio + RMS-VAD + Whisper 音声認識 | デーモン常駐 |
| AvatarWindow | 瞬き・口パクアニメーション(Toplevel) | メインスレッド |
| SettingsDialog | 設定編集・モデルパス・温度・VAD 閾値 | メインスレッド |
root.after(0, cb) 経由でのみ UI 更新を受け付けるroot.after(0, _on_llm_done)root.after(0, on_start/on_stop)root.after(0, _voice_input)root.after(0, _chat_write)⚠ Tkinter を直接操作できるのはメインスレッドのみ
class ChatApp:
def _send(self):
# UI読取
text = self._entry.get(...)
# LLM スレッド生成
threading.Thread(
target=self._llm_worker
).start()
def _llm_worker(self, ...):
# 推論・ストリーミング
...
def _on_llm_done(self, ...):
# 後処理・TTS・保存
...
class ChatApp:
def _send(self):
self._ctrl.handle_text()
def _voice_input(self, text):
self._ctrl.handle_voice(text)
def _stop_all(self):
self._ctrl.stop()
class Controller:
# 制御ロジックをここに集約
...
win32com) を使う常駐デーモン。_play_loop が順次発話。stop_all() でキューをフラッシュ&中断。
voice._tts_active = True にセット。_kakeibo_build_prompt() で支出カテゴリ一覧とスキーマを挿入_extract_kakeibo_json() で ```json ... ``` ブロックを正規表現でパースPOST /api/kakeibo/record をデーモンスレッドで非同期 POST_health_build_prompt() で体重・体脂肪・食事・活動スキーマを挿入_extract_health_json() で計測値を取得{
"title": "今日の夕飯は…",
"history": [
{
"user": "メッセージ",
"assistant": "応答"
}
],
"summary": "要約テキスト"
}
ターン数が SUMMARY_THRESHOLD(4) の倍数になると
バックグラウンドで LLM 要約を生成
{
"model_path": "models/gemma...",
"n_ctx": 8192,
"max_tokens": 1024,
"temperature":0.7,
"vad_threshold":150,
"mic_enabled": false,
"tts_enabled": false
}
adjust_llm() で gpu_layers を自動削減。