Add system memory monitoring, Bedrock support, and Discord integrations

- Add CPU/GPU memory monitoring endpoint and frontend widget
- Add Amazon Bedrock model support (API key / inference profiles) plus model selector entries
- Add Discord weather/laundry notification job (JMA forecast + LLM-generated message)
- Add Discord bot that replies to mentions via the configured LLM, with weather-aware context
- Add scheduled "useless tips" job with separate webhook and quiet-hours skip
- Switch FastAPI startup to lifespan handler, log viewer, markdown rendering, and toast UI additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 00:59:38 +09:00
co-authored by Claude Sonnet 4.6
parent 903d34761c
commit 4a08ba29fa
34 changed files with 5051 additions and 160 deletions
+104 -11
View File
@@ -3,6 +3,8 @@
ベースURL: `http://localhost:8000`
Swagger UI: `http://localhost:8000/docs`
---
## チャット
### POST /chat/stream
@@ -12,28 +14,119 @@ SSE でストリーミング応答を返す。
```json
{
"message": "こんにちは",
"session_id": "uuid (省略時は新規セッション作成)",
"model": "gpt-4o (省略時は DEFAULT_MODEL)"
"session_id": "uuid(省略時は新規セッション作成)",
"model": "gpt-5.4(省略時は DEFAULT_MODEL)"
}
```
**SSEイベント**
```
data: {"type":"session_id","value":"<uuid>"} // 新規セッション時のみ
data: {"type":"chunk","value":"テキスト断片"}
data: {"type":"done"}
```
**SSE イベント**
| type | value | タイミング |
|---|---|---|
| `session_id` | セッション UUID | 新規セッション作成直後 |
| `chunk` | テキスト断片 | LLM トークン生成ごと |
| `done` | — | 正常完了 |
| `error` | エラーメッセージ | 例外発生時 |
---
### GET /chat/sessions
セッション一覧を `updated_at` 降順で返す。
**レスポンス**
```json
[
{
"id": "uuid",
"title": "セッションタイトル",
"model": "gpt-5.4",
"updated_at": "2026-06-01T12:00:00"
}
]
```
---
### GET /chat/sessions/{session_id}/messages
セッションのメッセージ一覧を `created_at` 昇順で返す。
**レスポンス**
```json
[
{ "id": 1, "role": "user", "content": "...", "created_at": "..." },
{ "id": 2, "role": "assistant", "content": "...", "created_at": "..." }
]
```
---
### DELETE /chat/sessions/{session_id}
セッションと紐づくメッセージ・メモリをすべて削除。
セッションと紐づくメッセージ・メモリをすべて削除。`204 No Content` を返す。
---
## メモリ
### GET /memory/{session_id}
セッションの長期メモリ(要約)一覧を返す。
セッションの長期メモリ(要約)一覧を `created_at` 昇順で返す。
**レスポンス**
```json
[
{
"id": 1,
"session_id": "uuid",
"summary": "要約テキスト",
"created_at": "2026-06-01T12:00:00"
}
]
```
---
### DELETE /memory/{memory_id}
特定のメモリエントリを削除。
特定のメモリエントリを削除。`204 No Content` を返す。
---
## ログ
### GET /logs
LLM API 呼び出しログを `created_at` 降順で返す。
**クエリパラメーター**
| パラメーター | 型 | 説明 |
|---|---|---|
| `session_id` | string | セッションで絞り込み |
| `status` | `ok` / `error` | 成功・失敗で絞り込み |
| `limit` | int (≤500) | 最大件数(デフォルト 100) |
| `offset` | int | スキップ件数 |
**レスポンス**
```json
[
{
"id": 1,
"session_id": "uuid",
"model": "gpt-5.4",
"request_json": "[{\"role\":\"user\",\"content\":\"...\"}]",
"response_text": "...",
"error": null,
"latency_ms": 832.5,
"created_at": "2026-06-01T12:00:00"
}
]
```
---
### DELETE /logs/{log_id}
指定ログを削除。`204 No Content` を返す。
---
## ヘルスチェック
### GET /health
```json
{ "status": "ok" }
```
+105 -31
View File
@@ -3,47 +3,121 @@
## 全体構成
```
フロントエンド (Vue 3 + TypeScript + Vite, port 5173)
↕ REST / SSE
バックエンド (FastAPI + Python, port 8000)
↕
フロントエンド (Vue 3 + TypeScript + Vite, :5173)
↕ REST + SSE (Vite dev proxy 経由)
バックエンド (FastAPI + Python, :8000)
↕ SQLAlchemy
SQLite (playchat.db)
↕
LiteLLM → OpenAI / Anthropic / Ollama (任意のLLM)
↕ LiteLLM
OpenAI / Anthropic / Ollama など任意の LLM
```
---
## バックエンド
### エントリーポイント
`app/main.py` — FastAPI インスタンス作成、CORS 設定、DB 初期化、ルーター登録。
### ディレクトリ構成
### ルーター
| ファイル | エンドポイント | 役割 |
```
backend/
├── app/
│ ├── main.py # FastAPI インスタンス、CORS、ルーター登録、DB初期化
│ ├── api/
│ │ ├── chat.py # チャット・セッション関連エンドポイント
│ │ ├── memory.py # 長期メモリ参照・削除
│ │ └── logs.py # API ログ参照・削除
│ ├── core/
│ │ ├── config.py # .env 読み込み・定数定義
│ │ ├── llm.py # LiteLLM ラッパー(ストリーミング・ログ記録)
│ │ └── memory.py # メモリ読み書きロジック(要約・コンテキスト生成)
│ └── db/
│ ├── database.py # SQLite 接続・セッションファクトリ・テーブル自動作成
│ └── models.py # SQLAlchemy モデル定義
└── pyproject.toml
```
### DB テーブル
| テーブル | 主なカラム | 用途 |
|---|---|---|
| `app/api/chat.py` | `POST /chat/stream` | SSE でストリーミング応答 |
| `app/api/chat.py` | `GET /chat/sessions` | セッション一覧 |
| `app/api/chat.py` | `DELETE /chat/sessions/{id}` | セッション削除 |
| `app/api/memory.py` | `GET /memory/{session_id}` | メモリ一覧 |
| `app/api/memory.py` | `DELETE /memory/{id}` | メモリ削除 |
| `sessions` | id (UUID), title, model, created_at, updated_at | チャットセッション |
| `messages` | id, session_id, role, content, created_at | 会話メッセージ(短期メモリ) |
| `memories` | id, session_id, summary, embedding_json, created_at | 要約による長期メモリ |
| `api_logs` | id, session_id, model, request_json, response_text, error, latency_ms, created_at | LLM API 呼び出しログ |
### メモリ機構
- **短期メモリ**: セッション内のすべてのメッセージをコンテキストとして送信
- **長期メモリ**: メッセージ数が `MEMORY_SUMMARIZE_THRESHOLD`(デフォルト20) を超えると古い半分をLLMで要約し `memories` テーブルに保存
- 要約はシステムメッセージとして次回以降のコンテキスト先頭に注入される
### メモリ機構(`core/memory.py`)
### LLM 接続
`app/core/llm.py` が LiteLLM を薄くラップ。モデル名は `DEFAULT_MODEL` 環境変数で制御。
ローカルの Ollama は `ollama/llama3` のように `ollama/` プレフィックスで指定。
1. **短期メモリ**: セッション内の全 `messages` をコンテキストとして送信
2. **長期メモリ**: メッセージ数が `MEMORY_SUMMARIZE_THRESHOLD`(デフォルト 20)を超えると、古い半分を LLM で要約して `memories` に保存しメッセージを削除
3. **コンテキスト合成**: `get_context_messages()` が `memories` の要約をシステムメッセージとして先頭に付加し、残存メッセージと結合して返す
### LLM 呼び出し(`core/llm.py`)
- `stream_chat()` / `complete()` が LiteLLM を薄くラップ
- `_log_context` で呼び出しを囲み、成功・失敗どちらも `api_logs` テーブルに記録
- `DEBUG=true` で `litellm._turn_on_debug()` が有効になり詳細ログが出力される
### SSE ストリーミング(`api/chat.py`)
`POST /chat/stream` は `StreamingResponse` で以下のイベントを順番に送出する:
```
data: {"type":"session_id","value":"<uuid>"} # 新規セッション時のみ
data: {"type":"chunk","value":"テキスト断片"} # 0回以上
data: {"type":"done"} # 正常完了
data: {"type":"error","value":"エラー内容"} # 例外発生時
```
バッファリング防止のため `Cache-Control: no-cache` と `X-Accel-Buffering: no` を付与する。
---
## フロントエンド
### 状態管理
`src/stores/chat.ts` (Pinia) が全状態を保持:
- `sessions` — バックエンドから取得したセッション一覧
- `messages` — 現在セッションの表示用メッセージ配列
- `isStreaming` — SSE 受信中フラグ
- `selectedModel` — UI で選択中のモデル
### ディレクトリ構成
### SSE ストリーミング
`src/api/client.ts` の `streamChat()` が fetch + ReadableStream で SSE を受信。
チャンクは3種類: `session_id`(新規セッションID通知)、`chunk`(テキスト断片)、`done`(完了)。
```
frontend/src/
├── api/
│ └── client.ts # fetch ラッパー・SSE クライアント・型定義
├── stores/
│ └── chat.ts # Pinia ストア(全アプリ状態)
├── components/
│ ├── SessionList.vue # セッション一覧サイドバー
│ ├── ChatWindow.vue # メッセージ表示エリア
│ ├── MessageInput.vue # 入力欄・送信
│ ├── ModelSelector.vue # LLM モデル切替セレクト
│ ├── MarkdownContent.vue # marked + DOMPurify によるレンダリング
│ ├── LogViewer.vue # API ログ閲覧パネル
│ └── ToastList.vue # エラートースト通知
└── App.vue # ルートレイアウト
```
### 状態管理(`stores/chat.ts`)
| 状態 | 型 | 説明 |
|---|---|---|
| `sessions` | `SessionInfo[]` | セッション一覧 |
| `currentSessionId` | `string \| null` | 選択中セッション |
| `messages` | `Message[]` | 表示中メッセージ |
| `isStreaming` | `boolean` | SSE 受信中フラグ |
| `selectedModel` | `string` | 選択中 LLM モデル |
| `toasts` | `Toast[]` | エラー通知キュー |
### SSE 受信フロー
1. `sendMessage()` がユーザーメッセージと空のアシスタントメッセージを `messages` に追加
2. `streamChat()` が fetch + ReadableStream で SSE を受信
3. `chunk` イベント受信のたびに `messages.value[msgIdx].content` を更新(Vue reactive proxy 経由)
4. `done` で `pending` フラグを解除
5. `error` / ネットワークエラーは `error` フラグを立て赤バブル表示 + トースト通知
### Markdown レンダリング(`MarkdownContent.vue`)
- アシスタントメッセージのみ `marked` で HTML に変換後 `DOMPurify` でサニタイズ
- ストリーミング中(`pending=true`)は未閉じのコードフェンス(` ``` `)を自動補完してから描画
### Vite プロキシ設定
`/chat`、`/memory`、`/logs`、`/health` を `http://localhost:8000` に転送。
`/chat` は SSE バッファリング無効化のため `configure` で `x-accel-buffering: no` を付与。