データフロー
このドキュメントでは、メッセージ送信、ツール呼び出し、状態管理、キャッシュ戦略など、Elftia 内のレイヤー間でデータがどのように流れるかを説明します。
メッセージ送信の完全フロー
ユーザーが入力欄にテキストを入力してから AI の応答が完全にレンダリングされるまでの、完全なデータフローです。
sequenceDiagram
participant U as User Input
participant UI as UnifiedInput
participant UCC as UnifiedChatContext
participant CBC as ChatBackendContext
participant IPC as IPC Layer
participant CR as CompletionRouter
participant CS as CompletionService
participant ED as EngineDispatcher
participant Engine as Engine (Chat/SDK/TinyElf/CLI)
participant LLM as LLM API
participant DB as DbClient (Worker)
participant Store as chatStore (Zustand)
participant Render as MessageRenderer
U->>UI: Type message, press Enter
UI->>UCC: sendMessage(content, attachments)
UCC->>UCC: Create user message, update local state
UCC->>CBC: Send message via IPC
CBC->>IPC: window.api.completion.chatInSession(params)
IPC->>CR: secureHandle validates token
CR->>CS: completion.chatInSession(sessionId, messages, config)
CS->>ED: engineDispatcher.chat(session)
ED->>Engine: Route to appropriate engine
Engine->>LLM: HTTP request (SSE stream)
loop SSE streaming response
LLM-->>Engine: data chunk
Engine-->>CR: IPC event (stream:delta)
CR-->>IPC: mainWindow.send('stream:delta', chunk)
IPC-->>CBC: onStreamDelta callback
CBC-->>Store: setStreamingState({content})
Store-->>Render: Re-render StreamingMessage
end
Engine-->>CR: stream complete
CR->>DB: Persist assistant message
CR-->>IPC: stream:end event
IPC-->>CBC: onStreamEnd callback
CBC-->>UCC: Update message list
UCC-->>Store: clearStreamingState, addMessage
Store-->>Render: Render final message
主要ステップの説明
- ユーザー入力 —
UnifiedInputコンポーネントが入力内容、添付ファイル、モデル選択を受け取る - UnifiedChatContext —
userメッセージオブジェクトを作成し、ローカル状態を楽観的に更新する - IPC 呼び出し — Preload 経由で公開された
window.api.completion.chatInSession()でメインプロセスに送信される - CompletionRouter — トークンを検証し、パラメータを解析して
CompletionServiceを呼び出す - EngineDispatcher — Agent の
engineTypeに基づいて適切なエンジンにルーティングする - LLM API — エンジンが HTTP リクエストを行い、SSE ストリーミングレスポンスを受信する
- ストリーミングプッシュ — 各デルタが IPC イベント経由でフロントエンドに送信され、Zustand ストアがリアルタイムで更新される
- 永続化 — 完了後、バックエンドがアシスタントメッセージをデータベースに書き込む
- 状態同期 — フロントエンドがストリーミング状態をクリアし、最終メッセージを表示する
Agent ツール呼び出しフロー
LLM がツール呼び出し(tool_use)を返した場合の処理フローです。
flowchart TB
LLM[LLM returns tool_use] --> Parse[Parse tool_call]
Parse --> FW{ExecutionFirewall<br/>path check}
FW -->|deny| Block[Return denied result to LLM]
FW -->|pass| Guardian{GuardianAgent<br/>AI safety review}
Guardian -->|risk: high/critical| PermGate{ChannelPermissionGate<br/>human confirmation}
Guardian -->|risk: low/none| Execute[Execute tool]
Guardian -->|monitor mode| LogOnly[Log and execute]
PermGate -->|user denies| Block
PermGate -->|user approves| Execute
Execute --> Result[Tool execution result]
Result --> Audit[AuditLogger records]
Result --> BackToLLM[Result returned to LLM]
BackToLLM --> LLM
LogOnly --> Execute
{/* Tool call pipeline in TinyElf Agent Loop */}
interface ToolCallPipeline {
firewall: ExecutionFirewall; // 1. Deterministic check (zero LLM overhead)
guardian: GuardianAgent; // 2. AI review (mode-dependent)
permissionGate: ChannelPermissionGate; // 3. Human confirmation (Channel sources only)
executor: ToolExecutor; // 4. Execute
auditLogger: AuditLogger; // 5. Audit
}
状態管理の階層
フロントエンドの状態管理は、それぞれ明確な責務を持つ 3 つのレイヤーに分かれています。
graph TB
subgraph "Layer 1: Zustand Store (core state)"
CS[chatStore — sessions/messages/streaming/branches]
SS[settingsStore — settings state]
end
subgraph "Layer 2: React Context (domain state)"
CDC[ChatDataContext — data cache wrapper]
UCC2[UnifiedChatContext — unified chat API]
TC[ThemeContext — theme]
AC[AuthContext — authentication]
EC[ElfiContext — Elfi assistant]
end
subgraph "Layer 3: Feature Context (feature state)"
CTC[ChatTabsContext — multi-tab]
WIC[WorldInfoHighlightContext — WI highlights]
MSC[MessageSelectionContext — multi-select]
end
CS --> CDC
CDC --> UCC2
UCC2 --> CTC
レイヤーの責務
| レイヤー | 技術 | 特性 | ユースケース |
|---|---|---|---|
| Layer 1 | Zustand | 高頻度更新、精密なサブスクリプション、Provider ネスト不要 | ストリーミングメッセージ、ブランチ切り替え、セッション一覧 |
| Layer 2 | React Context | 中頻度更新、API メソッドの提供、依存性注入 | チャット操作(送信/再生成)、認証、テーマ |
| Layer 3 | React Context | 低頻度更新、機能の分離 | マルチタブ、キーワードハイライト、メッセージ選択 |
状態移行の方向
ChatDataContext (deprecated) ──migrating──> chatStore (Zustand)
│
v
UnifiedChatContext (unified API layer)
ChatDataContext は Zustand ストアへの移行途中にある旧来の Context ラッパーです。新規コードは chatStore または UnifiedChatContext を直接使用してください。
メッセージのブランチ
チャットメッセージはツリー構造を使用し、ブランチ(再生成/編集で新規ブランチを作成)をサポートします。
graph TB
M1[User: Hello] --> M2a[Assistant: Hello! v1]
M1 --> M2b[Assistant: Hi! v2]
M2a --> M3[User: Help me write some code]
M3 --> M4a[Assistant: Sure v1]
M3 --> M4b[Assistant: Of course v2]
{/* Branch data structure */}
interface BranchInfo {
id: string;
parentMessageId: string;
children: string[];
currentIndex: number;
}
{/* Branch operations */}
interface BranchOperations {
switchBranch(messageId: string, index: number): void;
getActivePath(rootId: string): Message[];
regenerate(messageId: string): void;
editMessage(messageId: string, newContent: string): void;
}
ブランチ切り替えのロジック
- ユーザーがブランチナビゲーション矢印をクリックする
switchBranch(parentMessageId, newIndex)がBranchInfo.currentIndexを更新するgetActivePath()がルートから末端までのアクティブなメッセージパスを再計算する- メッセージ一覧が再レンダリングされる
キャッシュ戦略
フロントエンドキャッシュ
| キャッシュ | 技術 | TTL | 目的 |
|---|---|---|---|
| メッセージキャッシュ | Zustand messageCache | セッション存続期間 | メッセージの再読み込みを回避 |
| UI 状態 | IndexedDB (frontendCache) | 永続 | 下書き、折りたたみ状態、スクロール位置 |
| セッション一覧 | Zustand sessions | 更新時に再取得 | サイドバーのセッション一覧 |
| プロバイダー一覧 | Zustand providers | 更新時に再取得 | モデルセレクター |
バックエンドキャッシュ
| キャッシュ | 場所 | TTL | 目的 |
|---|---|---|---|
| MCP ツール一覧 | CacheService | 5 分 | MCP ツールの繰り返し列挙を回避 |
| Transformer チェーン | TransformerService | 10 分 | コンパイル済み変換チェーン |
| プロバイダーインデックス | LLMConfigService | O(1) Map | ID によるプロバイダーの高速検索 |
| API キークールダウン | ApiKeyPoolService | 60 秒〜15 分(指数バックオフ) | 429/529 エラー後のクールダウン |
| PromptGuardian 結果 | PromptGuardian | SHA-256 キー | レビュー済みプロンプトのキャッシュ結果 |
| GuardianAgent 結果 | GuardianAgent | SHA-256 キー | レビュー済みツール呼び出しのキャッシュ結果 |
セッション保護
アクティブな会話中に WebSocket のプロジェクト更新がサイドバーを更新してチャットメッセージをクリアしないよう防ぎます。
{/* Session protection flow */}
interface SessionProtection {
activeSessions: Set<string>;
processingSessions: Set<string>;
markActive(sessionId: string): void; // Mark when user sends a message
shouldSkipRefresh(): boolean; // activeSessions.size > 0
markInactive(sessionId: string): void; // Remove after conversation completes
}
マルチキーラウンドロビン(ApiKeyPoolService)
バックエンドは LLM プロバイダーごとに複数の API キーの設定をサポートし、セッションアフィニティ付きの加重ラウンドロビンを使用します。
flowchart LR
Req[Request] --> Check{Session bound?}
Check -->|yes| BoundKey[Use bound key]
Check -->|no| RR[Weighted round-robin selects key]
RR --> Bind[Bind to session]
Bind --> Call[API call]
BoundKey --> Call
Call --> OK{Success?}
OK -->|429/529| Cool[Cool down this key]
Cool --> Retry[Switch to next key and retry]
OK -->|success| Done[Return result]
{/* Simplified ApiKeyPoolService */}
class ApiKeyPoolService {
private sessionBindings: Map<string, string>;
private cooldowns: Map<string, { until: number; backoff: number }>;
resolveApiKeyForRequest(
providerId: string,
sessionId?: string,
): Promise<{ keyId: string; apiKey: string }>;
markKeyError(keyId: string, statusCode: number): void;
}
関連ファイル
| ファイル | 説明 |
|---|---|
packages/renderer/src/shared/state/chatStore.ts | Zustand チャット状態ストア + 統合チャット API(送信/再生成/編集、データキャッシュ。旧 UnifiedChatContext / ChatDataContext はここに統合済み) |
packages/renderer/src/features/chat/hooks/useSessionProtection.ts | セッション保護 |
packages/renderer/src/shared/utils/frontendCache.ts | フロントエンド IndexedDB キャッシュ |
packages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts | LLM 補完サービス |
packages/desktop/app/main/services/capabilities/llm/completion/ApiKeyPoolService.ts | マルチキーラウンドロビン |
packages/desktop/app/main/services/agent-core/engine/EngineDispatcher.ts | エンジンディスパッチ |
packages/desktop/app/main/services/routers/CompletionRouter.ts | 補完 IPC ルーター |
packages/desktop/app/main/services/platform/security/ExecutionFirewall.ts | パスファイアウォール |
packages/desktop/app/main/services/platform/security/GuardianAgent.ts | AI ツールレビュー |
packages/desktop/app/main/services/infra/cache/CacheService.ts | バックエンドキャッシュサービス |