# 機能一覧 このリポジトリにあるアプリ(PlayChat)が「何ができるか」を機能単位でまとめたもの。 内部設計は [architecture.md](architecture.md)、エンドポイント仕様は [api.md](api.md)、 Discord 連携は [discord-bot.md](discord-bot.md)、ノートの操作方法は [notes-guide.md](notes-guide.md)、 デプロイ手順は [deploy-lightsail.md](deploy-lightsail.md) を参照。 ## これは何か 個人用の LLM チャットアプリ。FastAPI(Python)+ Vue 3(TypeScript)で、会話・記憶・ノートを すべて手元の SQLite に持つ。LLM は LiteLLM 経由なので OpenAI / Anthropic / Ollama / Amazon Bedrock を 同じ画面のプルダウンで切り替えられる。単なるチャット UI ではなく、 - 会話が長くなったら勝手に要約して長期記憶に落とす - 知らないことは自分で Web 検索して、その結果を次回以降も使える知識として溜める - ユーザーと AI が同じノートを共同編集する - Discord 側にも住んでいて、天気を知らせたり雑談に乗ったりする といった「動き続ける」部分を持つのが特徴。 ## 機能サマリ | # | 機能 | 概要 | 主な実装 | |---|---|---|---| | 1 | チャット(SSE ストリーミング) | 逐次表示・Markdown 描画・セッション管理 | `api/chat.py` / `ChatWindow.vue` | | 2 | セッションの書き出し / 取り込み | 会話を JSON でダウンロード・復元 | `api/chat.py` / `SessionList.vue` | | 3 | 5 層コンテキスト管理 | 蓄積知識・ノート・ローカル KB・長期/短期メモリを毎回合成 | `core/memory_kb/memory.py` | | 4 | 自動要約による長期記憶 | 一定件数を超えたら古い会話を要約して圧縮 | `memory_kb/memory.py` | | 5 | Web 検索と知識の蓄積 | Tavily で検索 →要約 → DB とローカル KB に保存 | `core/search/` / `memory_kb/knowledge.py` | | 6 | ローカル Markdown ナレッジベース | 話題に近いファイルを埋め込み類似度で選んで全文注入 | `memory_kb/local_knowledge.py` | | 7 | 共有ノート | タブ/セクション付きノートを人と AI の両方が編集 | `core/notes.py` / `NotePanel.vue` | | 8 | 自動タグ付け | 発言・要約にタグを 3 つ付与(話題選びに使う) | `memory_kb/tagging.py` | | 9 | ワードプール | 直近の名詞をインメモリに保持しタグクラウド表示 | `memory_kb/word_memory.py` / `WordPool.vue` | | 10 | Discord Bot | メンションに返信。天気・雑談キーワードに反応 | `core/discord/discord_bot.py` | | 11 | 定期通知 | 毎日の洗濯天気通知と、定期的な「どうでもいい豆知識」 | `core/discord/scheduler.py` | | 12 | 天気予報(気象庁 API) | 明日の天気・降水確率から洗濯指数を判定 | `core/weather/` / `Clock.vue` | | 13 | モデル切替とプリセット管理 | 選択肢を DB で管理し画面から追加・編集・削除 | `api/models.py` / `SettingsPanel.vue` | | 14 | GUI 設定画面 | API キー等を再起動なしで変更(許可リスト制・マスク表示) | `core/settings_store.py` | | 15 | API ログビューア | LLM 呼び出しの成功/失敗・レイテンシ・リクエスト全文 | `api/logs.py` / `LogViewer.vue` | | 16 | システムモニタ | CPU・メモリ・GPU 使用率を 10 秒ごとに表示 | `api/system.py` / `MemoryMonitor.vue` | | 17 | サイト認証 | 共有パスワード 1 つでサイト全体を保護 | `core/site_auth.py` / `LoginGate.vue` | | 18 | Docker 一式 | Caddy + nginx + FastAPI で HTTPS 公開 | `docker-compose.yml` / `Caddyfile` | --- ## 1. チャット - `POST /chat/stream` が SSE を返し、フロントは fetch + ReadableStream で受けて 1 文字ずつ描画する。 イベントは `session_id` / `chunk` / `sources` / `notes_updated` / `done` / `error` の 6 種。 - アシスタントの発言は `marked` + `DOMPurify` で描画する。ストリーミング途中で閉じていないコードフェンスは 自動で補完してから描画するので、生成中でもレイアウトが崩れない。 - セッションは左サイドバーの一覧から切り替え・削除できる。タイトルは最初の発言の先頭 40 文字。 - Web 検索が走った応答には出典(`sources`)が、各メッセージには自動生成されたタグが添えて表示される。 - 失敗時は赤いバブルとトースト通知の両方で知らせる。 ## 2. セッションの書き出し / 取り込み - `GET /chat/sessions/{id}/export` がセッションのメタ情報と全メッセージ(タグ込み)を JSON で返す。 - `POST /chat/sessions/import` は同じ形式の JSON から**新しいセッションを作成**する(上書きではない)。 サイドバーの ⭱ ボタンからファイルを選ぶだけで取り込める。 - 注意: 要約済みの会話は元メッセージが物理削除されているため、エクスポートしても生ログは戻らない。 ## 3. コンテキスト管理(5 層) 毎リクエスト `get_context_messages()` が以下の順に system メッセージを積み、最後に会話履歴を連ねる。 | 層 | 中身 | 量の制御 | |---|---|---| | 1 蓄積知識 | 過去の Web 検索結果の**見出し一覧のみ**(本文は `read_knowledge` で読む) | 最新 `KNOWLEDGE_CONTEXT_LIMIT`(既定 15)件 | | 2 共有ノート | ページのタイトル一覧+本文先頭 40 文字 | 全ページ(本文は入れない) | | 3 ローカル KB | 選ばれた Markdown の**全文** | 類似度上位 `LOCAL_KB_TOP_K`(既定 3)件かつ `LOCAL_KB_MIN_SCORE`(既定 0.75)以上 | | 4 長期メモリ | 要約を全件連結 | 上限なし | | 5 短期メモリ | セッション内の全メッセージ | 上限なし(要約で間接的に制御) | 重い情報(ノート本文・蓄積知識の本文・Web 検索)は目次だけを渡し、本体はツール(`read_note` / `write_note` / `read_knowledge` / `web_search`)で 取りに行かせる遅延読み込み方式。`llm.resolve_tool_calls()` が本番生成の前に最大 2 ラウンドでこれを解決する。 ## 4. 自動要約による長期記憶 - メッセージ数が `MEMORY_SUMMARIZE_THRESHOLD`(既定 20)を超えると、古い側の半分を LLM が要約して `memories` に 1 行として保存する。 - **要約した元メッセージは DB から物理削除される**(復元不可)。 - 保存した要約にもタグが付き、Discord Bot の話題選びの材料になる。 - 溜まった要約は `GET /memory/{session_id}` で確認、`DELETE /memory/{id}` で個別削除できる(API のみ。専用の画面は無い)。 ## 5. Web 検索と知識の蓄積 - LLM が必要と判断したときだけ `web_search` ツールが走る(Tavily。`TAVILY_API_KEY` 未設定なら空振り)。 - 結果は `SEARCH_SUMMARY_MODEL` で構造化要約にされ、**2 か所**に保存される。 - `knowledge_entries` / `knowledge_sources` テーブル → 次回以降、層 1 として全セッションに効く - `knowledge_base/web_search/*.md` → 層 3 の類似度検索の対象になる - `POST /chat/knowledge/regenerate` で、既存の知識エントリを出典 URL から取り直して要約し直せる。 ## 6. ローカル Markdown ナレッジベース - `backend/knowledge_base/**.md` を置いておくだけで、話題が近いときに自動で全文が注入される。 - 直近のユーザー発言との埋め込みコサイン類似度で選ぶ。埋め込みは `local_knowledge_docs` に 内容ハッシュ付きでキャッシュされ、ファイルが変わったときだけ取り直す。 - プロンプト上は「Web 検索結果より優先。これで答えられるなら検索するな」と明示している。 ## 7. 共有ノート - 1 ページ=1 タブ。作成・改名・削除・本文編集(入力が止まると自動保存)ができる。 - **セクション**でページをグループ化でき、同じセクションのタブは常に隣り合って表示される。 - タブは**ドラッグ&ドロップ**で並べ替えでき、他セクションのタブにドロップすればそのまま移動になる。 - AI 側はタイトル一覧だけを常に見ており、`read_note` で本文を読み、`write_note` で追記/全置換/新規作成する。 AI が書き換えると SSE の `notes_updated` が飛び、開いているノートパネルが自動で最新化される。 - 自分が編集中(未保存)のページは AI の更新で上書きされない。 ## 8-9. 自動タグ付けとワードプール - 各メッセージと各要約に、バックグラウンドでタグが最大 3 つ付く(応答ストリームはブロックしない)。 タグはコンテキストには注入せず、Discord Bot が「近い話題」を選ぶために使う。 - `POST /api/system/tags/backfill` で、過去の未タグ分をまとめて処理できる。 - ワードプールは Janome で抽出した名詞を `deque(maxlen=200)` にプロセス内保持するもの。 **DB に永続化されず再起動で消える**が、起動時に直近 200 件のメッセージから再シードされる。 サイドバーに出現回数に応じた大きさのタグクラウドとして表示される。 ## 10-11. Discord Bot と定期通知 - Bot はメンションされたときだけ反応する。本文に天気系キーワード(天気・洗濯・雨など)が含まれれば 気象庁データを添えて回答し、雑談系キーワード(どうでもいい・暇など)なら発言からタグを起こして 関連する話題の豆知識を即座に投げ返す。 - APScheduler による定期ジョブが 2 つ。 - **洗濯天気通知**: 毎日 `WEATHER_NOTIFY_HOUR` 時(既定 22 時)に、翌日の天気と洗濯指数を配信 - **どうでもいい豆知識**: `USELESS_TIPS_ENABLED=true` のときだけ登録され、`USELESS_TIPS_INTERVAL_HOURS`(既定 3 時間)ごとに実行。深夜 3 時台はスキップ - どちらも `POST /api/system/weather-notify/test` / `POST /api/system/useless-tip/test` で即時実行して確認できる。 ## 12. 天気予報 - 気象庁の無料 JSON API から明日の予報を取り、天気文言と降水確率から洗濯指数を判定する (雨雪なら室内干し、降水確率 30% 以下なら外干し OK、それ以上は室内干し推奨)。 - 取得した最新データはメモリ上にキャッシュされ、画面右上の時計の横にも表示される(`GET /api/system/weather/latest`)。 - 対象地域は `WEATHER_AREA_CODE`(既定 120000=千葉県)と `WEATHER_AREA_KEYWORD`(既定「北西部」)で指定する。 ## 13-14. モデル選択と設定画面 - モデルのプルダウンは `model_presets` テーブル由来で、初回起動時に既定の一覧がシードされる。 設定画面からラベル/モデル文字列を追加・編集・削除できる(重複は 400 で弾く)。 - 設定画面では API キー類を**再起動なしで**変更できる。 - 変更できるのは許可リストにあるキーだけ(`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / AWS 系 / `OLLAMA_API_BASE` / `TAVILY_API_KEY` / `LOCAL_KB_EMBEDDING_MODEL`) - 値は末尾 4 文字だけを見せるマスク表示。空欄で保存すると「変更なし」扱い - 保存すると `app_settings` テーブルに入り `os.environ` に反映される。削除すると `.env` 由来の起動時の値に戻る ## 15-16. ログビューアとシステムモニタ - LLM 呼び出しは成功・失敗どちらも `api_logs` に記録される(モデル・リクエスト全文・応答・ レイテンシ・エラー)。ログパネルからセッション別/成功失敗別に絞り込み、個別削除ができる。 - サイドバーの監視パネルは CPU 使用率・メモリ使用量を 10 秒ごとに更新する。 `pynvml` が使える環境なら GPU メモリも表示する(無ければその欄が出ないだけ)。 ## 17. 認証 パスワードが 2 種類あり、役割が違う。 | 変数 | 守る対象 | 方式 | |---|---|---| | `SITE_PASSWORD` | チャット含むサイト全体 | ログイン画面 → HMAC 署名付きセッション Cookie(既定 30 日) | | `SETTINGS_PASSWORD` | 設定画面と モデルプリセットの更新系のみ | HTTP Basic(リクエストごとに送る) | - どちらも未設定時の挙動が逆なので注意。`SITE_PASSWORD` 未設定=**誰でも使える**、 `SETTINGS_PASSWORD` 未設定=**設定画面は常に 403**(空パスワードでの突破を防ぐため)。 - `SESSION_SECRET_KEY` を設定しないと起動ごとに鍵が作り直され、再起動のたびに全員ログインし直しになる。 - ローカルの http 環境でログインが効かない場合のみ `SESSION_COOKIE_SECURE=false` にする。 ## 18. デプロイ - `docker-compose.yml` に backend(FastAPI)/ frontend(nginx で静的配信+API リバースプロキシ)/ caddy(Let's Encrypt 自動取得と HTTPS 終端)の 3 コンテナが定義済み。 - SQLite と `knowledge_base` は Docker ボリュームに永続化される。 - フロントは API をすべて相対パスで呼ぶため、同一オリジン配信なら CORS 設定は実質不要。 --- ## 制約・注意点 このアプリを触るときに引っかかりやすい点。 - **1 応答あたり LLM 呼び出しは最低 2 回**。ツールを使わない会話でも `resolve_tool_calls()` が フルコンテキストを 1 回投げてから本番生成に入る。 - **トークン数ベースの制御が一切ない**。すべて件数・文字数ベースなので、層 3(Markdown 全文 ×3)は 会話が短くてもコンテキストを大きく押し上げる(層 1 は見出しのみなので軽い)。 - **長期メモリに上限がない**。長いセッションでは要約が積み上がり続ける(要約の再要約はしない)。 - **要約は不可逆**。要約対象になった元メッセージは DB から消える。 - **ワードプールは揮発性**。再起動でリセットされ、直近 200 件から作り直される。 - **DB マイグレーションは簡易実装**。Alembic は依存に入っているが使っておらず、起動時に `PRAGMA table_info` を見て不足カラムを `ALTER TABLE` で足すだけ(SQLite 前提)。 - **自動テストは無い**。`pytest` は dev 依存に入っているが `backend/tests/` は存在しない。