Files
playchat/docs/architecture.md
colcaroliandClaude Opus 5 a08b1785a4 Add shared notes with tabbed pages editable by user and AI
Adds a note_pages table backing a tabbed notes panel where the user and
the model can both read and write. Only page titles (plus a short
preview) are injected into the system prompt on every turn; full page
content is kept out of context and fetched on demand via a read_note
tool, with write_note letting the model append to, replace, or create a
page.

Generalizes llm.resolve_web_search into resolve_tool_calls so the
tool-calling loop handles web_search and the new note tools through one
path. The chat stream emits a notes_updated SSE event when the model
edits a note so the frontend panel can refresh; local edits are tracked
as dirty and never clobbered by that refresh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 01:27:31 +09:00

6.3 KiB
Raw Permalink Blame History

アーキテクチャ概要

全体構成

フロントエンド (Vue 3 + TypeScript + Vite, :5173)
        ↕ REST + SSE (Vite dev proxy 経由)
バックエンド (FastAPI + Python, :8000)
        ↕ SQLAlchemy
   SQLite (playchat.db)
        ↕ LiteLLM
   OpenAI / Anthropic / Ollama など任意の LLM

バックエンド

ディレクトリ構成

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 テーブル

テーブル 主なカラム 用途
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 要約による長期メモリ
note_pages id, title, content, position, created_at, updated_at ユーザーと AI が共同編集する共有ノートのページ
api_logs id, session_id, model, request_json, response_text, error, latency_ms, created_at LLM API 呼び出しログ

メモリ機構(core/memory.py)

  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() が有効になり詳細ログが出力される

共有ノート(core/notes.py / api/notes.py)

ユーザーと AI が共同編集するノート。note_pages の 1 行が UI の 1 タブに対応する。

  • get_notes_context() がタイトル一覧のみをシステムメッセージとして生成し、get_context_messages() が会話コンテキストに差し込む(本文は含めない)
  • 本文の読み書きはツール経由: read_note(本文取得)/ write_note(追記・置換・新規作成)
  • llm.resolve_tool_calls() が web_search と同じループでこれらを解決する。ノート系ツールは db を渡したときのみモデルに提示される
  • REST(/api/notes)はフロントのノートパネル用。GET 一覧 / POST 作成 / PATCH 更新 / DELETE 削除

SSE ストリーミング(api/chat.py)

POST /chat/stream は StreamingResponse で以下のイベントを順番に送出する:

data: {"type":"session_id","value":"<uuid>"}   # 新規セッション時のみ
data: {"type":"chunk","value":"テキスト断片"}   # 0回以上
data: {"type":"notes_updated"}                  # AIが共有ノートを更新した時のみ
data: {"type":"done"}                           # 正常完了
data: {"type":"error","value":"エラー内容"}     # 例外発生時

バッファリング防止のため Cache-Control: no-cache と X-Accel-Buffering: no を付与する。


フロントエンド

ディレクトリ構成

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 を付与。