> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fim.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# コンテキスト管理

> FIM Oneがどのように会話コンテキストを管理するか — トークンオーバーフローを防ぎながら会話品質を保つ5層の多層防御システム。

## 問題

LLMには有限のコンテキストウィンドウがあります。128Kトークンモデルは寛容に聞こえますが、出力予算、システムプロンプト、ツール説明、マルチターン会話の蓄積された履歴を差し引くと話は別です。長い会話、大きなツール結果、マルチステップエージェントループはすべてこの制限に対して圧力をかけます。多くの場合、単一のセッション内でです。

素朴なソリューションは切り詰めです。ウィンドウが満杯になったら古いメッセージを削除します。これは高速で予測可能ですが、コンテキストを無差別に破壊します。ユーザーの元々の意図、以前のターンからの重要な決定、重要なデータポイントはすべて、単純な文字切り詰めが当たると消えてしまいます。反対の極端 — すべてのターンでLLM駆動の要約 — は意味的なコンテンツを保持しますが、高コストで遅く、独自の障害モード（幻覚的な要約、数値精度の喪失）を導入します。

真の課題は「ウィンドウに収まる」ことではありません。それは：**重要な情報を失わずに段階的に劣化し、不要な圧縮にトークンを浪費せず、ユーザーが感じることができるレイテンシを追加しない。**

FIM Oneはこれを5層の多層防御アーキテクチャで解決します。各層は問題の異なるスケールに対処し、きれいに構成されます — 単一の層が完璧である必要はありません。次の層がそれが見落とすものをキャッチするからです。

## 5つの防御層

コンテキスト管理は単一のメカニズムではありません。これはスタックであり、各層は特定の粒度で特定の関心事を処理します:

| 層     | コンポーネント      | 機能                         | 実行タイミング       |
| ----- | ------------ | -------------------------- | ------------- |
| **5** | 予算設定         | モデル仕様から使用可能な入力トークン予算を計算    | 起動時 / リクエストごと |
| **4** | DbMemory     | 永続化された履歴を読み込み、読み込み時にコンパクト化 | リクエストごと1回     |
| **3** | ContextGuard | 反復ごとの予算強制                  | ReAct反復ごと     |
| **2** | CompactUtils | トークン推定、スマート切り詰め、LLMコンパクト化  | 層3と4により呼び出し   |
| **1** | メモリ実装        | 抽象インターフェース + 具体的な戦略        | フレームワークレベル    |

層は下から上に番号付けされています。これは上位層が下位層に依存しているためです。層5が予算を設定します。層4は初期読み込み時のコンパクト化を実行します。層3は反復ごとに予算を強制します。層2と1は層3と4が使用するプリミティブを提供します。

```mermaid theme={null}
flowchart TD
    L5["Layer 5: Budget Configuration<br/><i>context_size - max_output</i>"]
    L4["Layer 4: DbMemory<br/><i>Load history, compact on load</i>"]
    L3["Layer 3: ContextGuard<br/><i>Per-iteration enforcement</i>"]
    L2["Layer 2: CompactUtils<br/><i>Estimation, truncation, LLM compact</i>"]
    L1["Layer 1: Memory Implementations<br/><i>BaseMemory, WindowMemory, SummaryMemory, DbMemory</i>"]

    L5 -->|"budget"| L4
    L5 -->|"budget"| L3
    L4 -->|"calls"| L2
    L3 -->|"calls"| L2
    L4 -.->|"implements"| L1
```

### Layer 5 — 予算設定

予算は2つの値から計算されます:

```
usable_input_tokens = context_size - max_output_tokens
```

デフォルト値: `128,000 - 64,000 = 64,000 トークン`。

静的なシステムプロンプト予約は差し引かれません。システムプロンプト(エージェント命令、ツール説明、フォーマットオーバーヘッド)は、ContextGuardが動的に推定するメッセージリストに含まれます。その上に固定予約を差し引くと、二重計算になり予算を無駄にします。典型的なシステムプロンプトは3K～5Kトークンを消費します。多くのツールや長い命令を持つエージェントはより多く使用する場合があります。ContextGuardは実際のサイズを自動的に考慮します。

予算値は3つのソースから取得でき、優先順に解決されます:

1. **データベース ModelConfig** — 管理者が設定したモデルごとの `context_size` と `max_output_tokens`。
2. **環境変数** — `LLM_CONTEXT_SIZE` と `LLM_MAX_OUTPUT_TOKENS`。
3. **ハードコードされたデフォルト** — 128K コンテキスト、64K 出力。

メイン LLM と高速 LLM は独立した予算を持ちます。DAG ステップ実行は高速 LLM の予算を使用します。ReAct モードはメイン LLM の予算を使用します。これが重要なのは、オペレータが ReAct 用の大規模コンテキストモデル(履歴が蓄積される場所)と DAG ステップ用のより小さく高速なモデル(各ステップが新たに開始される場所)をペアリングすることが多いためです。

4,000トークンの下限が適用されます。設定ミスの値がより小さい予算を生成する場合、システムは静かに失敗するのではなく4Kにクランプします。

### Layer 4 — DbMemory

`DbMemory` は本番環境のメモリ実装です。データベースから永続化された会話履歴を読み込み、エージェントが見る前にトークン予算に合わせてコンパクト化します。

設計は**意図的に読み取り専用**です。永続化は `chat.py` で処理されます。これは API レイヤーで、メッセージのライフサイクル全体（メタデータ、使用状況追跡、画像添付を含む）を管理します。`DbMemory` は読み取りのみを行います。その `add_message()` と `clear()` メソッドは何もしません。この分離により、二重書き込みを防ぎ、永続化ロジックを一箇所に保ちます。

読み込み時に、`DbMemory` は以下を実行します：

1. 会話のすべての `user` と `assistant` メッセージをクエリし、作成時間順に並べます。
2. 末尾のユーザーメッセージ（現在のクエリで、エージェントが再度追加します）を削除します。
3. 画像添付を再構築します。画像を含むユーザーメッセージはメタデータ（`file_id`、`mime_type`）をデータベースに保存し、`DbMemory` はディスクから base64 データ URL を再構築して、LLM が以前のターンからの画像を「見る」ことができるようにします。
4. コンパクト化：`compact_llm` が提供されている場合は `CompactUtils.llm_compact()` を使用します。それ以外の場合は `CompactUtils.smart_truncate()` にフォールバックします。

コンパクト化後、`DbMemory` は追跡フラグ（`was_compacted`、`_original_count`、`_compacted_count`）を設定し、SSE レイヤーがこれを使用してフロントエンドに `compact` イベントを発行します。

### Layer 3 — ContextGuard

`ContextGuard` は反復ごとの予算実行者です。スタンドアロン ReAct モードと DAG ステップ内のエージェント内の両方で、すべての ReAct 反復の最上部で呼び出されます。これはメッセージが LLM API に到達する前の最後の防線です。

実行は 3 段階のプロセスに従います：

1. **サイズの大きい個別メッセージを切り詰める。** 50K 文字を超える単一メッセージは、`[Truncated]` サフィックス付きでハード切り詰めされます。これは暴走するツール出力をキャッチします — Web スクレイプが Web ページ全体を返す場合、ファイル読み取りが大規模なデータセットをダンプする場合などです。

2. **合計トークンを推定する。** 合計が予算内に収まる場合は、すぐに返します。ほとんどの反復はここで通過します — 圧縮は例外であり、常識ではありません。

3. **予算を超える場合は圧縮する。** `compact_llm` が利用可能な場合は、ヒント固有のプロンプトで LLM 駆動の圧縮を使用します。それ以外の場合は、`smart_truncate` にフォールバックします。

**ヒント システム** は、ContextGuard を万能ではなくコンテキスト認識にするものです。状況が異なれば、圧縮戦略も異なります：

| ヒント               | 使用者              | 保持                     | 削除                         |
| ----------------- | ---------------- | ---------------------- | -------------------------- |
| `react_iteration` | ReAct エージェント ループ | 最近の推論チェーン、現在の目標、重要なデータ | 古い冗長なステップ、失敗した再試行、冗長なツール出力 |
| `planner_input`   | DAG エンリッチド クエリ   | ユーザー インテント進化、主要な決定、制約  | 対話の詳細、挨拶、ツール呼び出しメカニクス      |
| `step_dependency` | DAG ステップ コンテキスト  | 主要なデータ、数値、結論           | 推論プロセス、失敗した試み、冗長なフォーマット    |
| `general`         | デフォルト フォールバック    | 主要な事実、決定、ツール結果         | 挨拶、フィラー、冗長なやり取り            |

各ヒントは、圧縮 LLM に何を保持し何を破棄するかを指示する慎重に作成されたシステム プロンプトにマップされます。プロンプトは「会話と同じ言語で記述してください」で終わります — これは CJK ユーザーの要約がそうでなければ英語にデフォルト設定される場合に重要な詳細です。

LLM 圧縮が失敗した場合（ネットワーク エラー、空の応答、例外）、ContextGuard は静かに `smart_truncate` にフォールバックします。エージェントは失敗を見ることはありません。これは意図的な信頼性の選択です：ヒューリスティック切り詰めによってコンテキストを失う方が、反復をクラッシュさせるよりも良いです。

### Layer 2 — CompactUtils

`CompactUtils` はステートレスなユーティリティクラスです — インスタンスなし、状態なし、純粋な関数のみです。レイヤー 3 と 4 が構築する 3 つの機能を提供します。

**トークン推定** はトークナイザーライブラリをインポートせずにテキストをおおよそのトークン数に変換します。ヒューリスティック：

* ASCII 文字：\~1 トークンあたり 4 文字
* CJK / 非 ASCII 文字：\~1 トークンあたり 1.5 文字
* 画像：画像あたり 765 トークン（固定コスト）
* メッセージごとのオーバーヘッド：4 トークン（ロールマーカー、デリミタ）

**`smart_truncate`** はヒューリスティックフォールバックです。ピン留めされたメッセージを無条件に保持し、ピン留めされていないメッセージを逆向きに走査して、予算が尽きるまで蓄積します。結果は会話のサフィックスで、予算内に収まります。また、結果が先行するユーザーメッセージのない孤立したアシスタントターンで始まらないようにします — これは LLM を混乱させます。

**`llm_compact`** は LLM 駆動のパスです。メッセージを 3 つのグループに分割します — システムメッセージ（常に保持）、ピン留めされたメッセージ（常に保持）、コンパクト化可能なメッセージです。最も古いコンパクト化可能なメッセージは単一の `[Conversation summary]` システムメッセージに要約され、最新の 4 メッセージはそのまま保持されます。コンパクト化された結果がまだ長すぎる場合は、コンパクト化された出力に対して `smart_truncate` にフォールバックします — 二重の安全装置です。

### レイヤー 1 — メモリ実装

メモリレイヤーは `BaseMemory` インターフェースを定義します: `add_message()`、`get_messages()`、`clear()`。3 つの実装が存在します:

* **WindowMemory** — カウントベースのスライディングウィンドウ。最後の N 個の非システムメッセージを保持します。シンプルで予測可能、LLM 呼び出しなし。本番環境では使用されていません。テストとステートレスシナリオに役立ちます。

* **SummaryMemory** — メッセージカウントがしきい値を超えたときに LLM 要約をトリガーします。古いメッセージを `[Conversation summary]` システムメッセージに圧縮します。本番環境では使用されていません。より洗練された ContextGuard アプローチより前のものです。

* **DbMemory** — 本番実装 (レイヤー 4 で説明)。データベースバック、読み取り専用、ロード時に LLM またはヒューリスティック圧縮を使用します。

WindowMemory と SummaryMemory はコードベースに残っています。これらはテストに役立つプリミティブとして機能し、Web レイヤーなしで FIM One のコアライブラリを組み込むユーザーにとって有用だからです。これらはデッドコードではなく、アーキテクチャが成長した単純なケースです。

## ReActを通じたコンテキストの流れ

ReActエージェントは、ロード時と反復時の2つの異なるフェーズでコンテキスト管理を使用します。

```mermaid theme={null}
sequenceDiagram
    participant Chat as chat.py
    participant Db as DbMemory
    participant Agent as ReActAgent
    participant CG as ContextGuard
    participant LLM as LLM API

    Chat->>Db: get_messages(budget)
    Db->>Db: Load from DB, compact
    Db-->>Chat: history (within budget)

    Chat->>Agent: run(query)
    Agent->>Agent: Assemble: system + history + pinned query

    loop Each iteration (non-streaming)
        Agent->>CG: check_and_compact(messages, hint="react_iteration")
        CG->>CG: Truncate oversized, estimate, compact if needed
        CG-->>Agent: messages (within budget)
        Agent->>LLM: chat(messages)
        LLM-->>Agent: response
        alt tool_call
            Agent->>Agent: Execute tool, append observation
            opt cycle detected
                Agent->>Agent: Inject warning message into context
            end
        else final_answer
            opt tools ≥ threshold, not yet checked
                Agent->>Agent: Inject checklist prompt into context
                Agent->>Agent: Continue loop (one more iteration)
            end
        end
    end

    Agent-->>Chat: AgentResult (tool trace + brief answer)
    Chat->>Agent: stream_answer(query, result)
    Agent->>LLM: stream_chat(synthesis prompt)
    LLM-->>Agent: token stream
    Agent-->>Chat: streamed answer → SSE to frontend
    Chat->>Chat: Persist, emit compact SSE if compacted
```

ツール反復は速度のために非ストリーミング`chat()`を使用し、回答合成はストリーミング`stream_chat()`を`stream_answer()`経由で使用します。この2フェーズの分割 — 高速ツールループの後にストリーミング合成 — はレイテンシとユーザー体験の両方を最適化します。デュアルモード実行とツール選択を含むReActエンジンアーキテクチャの詳細については、[ReActエンジン](/architecture/react-engine)を参照してください。

重要な洞察: **DbMemoryは履歴コンテキストの問題（前のリクエストからのターン）を処理し、ContextGuardはリクエスト内の成長の問題（エージェントループ中に蓄積するツール結果）を処理します。** これらは異なる時間スケールで動作し、異なる障害モードをキャッチします。

ユーザーの現在のクエリは常に`pinned=True`としてマークされます。これにより、すべての圧縮 — `smart_truncate`と`llm_compact`の両方 — を通じて生き残ることが保証されます。履歴がどれほど積極的に圧縮されても、ユーザーの実際の質問は決して失われません。

## コンテキストがDAGを通じてどのように流れるか

DAGモードはReActとは根本的に異なるコンテキスト形状を持っています。1つの長い会話スレッドの代わりに、ツリー構造を持ちます：計画フェーズ、複数の並列実行ステップ、分析フェーズです。各フェーズには独自のコンテキスト管理戦略があります。

```mermaid theme={null}
flowchart TD
    A["1. Load history via DbMemory"] --> B["2. Build enriched_query"]
    B --> C{"enriched_query > 16K tokens?"}
    C -->|Yes| D["LLM summarize with planner_input prompt"]
    C -->|No| E["Use as-is"]
    D --> F["3. DAGPlanner (structured_llm_call)"]
    E --> F
    F --> G["4. Execution: parallel steps"]
    G --> H["Step 1: independent ReAct"]
    G --> I["Step 2: independent ReAct"]
    G --> J["Step N: independent ReAct"]
    H --> K["5. PlanAnalyzer (structured_llm_call)"]
    I --> K
    J --> K
    K --> L{"Achieved?"}
    L -->|No, low confidence| M["6. Replan: 500-char step summaries"]
    M --> F
    L -->|Yes| N["stream_synthesize → Final answer"]
```

**フェーズ1 — 履歴の読み込み。** DbMemoryは会話履歴を読み込んでコンパクト化します。これはReActと同じです。コンパクト化された履歴は`"Previous conversation:"`というプレフィックス付きのテキストブロックにフォーマットされます。

**フェーズ2 — エンリッチされたクエリの構築。** 履歴テキストと現在のクエリが`enriched_query`に結合されます。これが16Kトークンを超える場合、`planner_input`ヒントプロンプトを使用してLLMで要約されます。16Kの閾値が選ばれた理由は、プランナーが単一パスでクエリ全体を読む必要があるためです — ReActとは異なり、計画中の反復的なコンパクト化はありません。

**フェーズ3 — 計画。** プランナーは2メッセージのプロンプトを受け取ります：システムプロンプトとエンリッチされたクエリです。ここではContextGuardはありません — エンリッチされたクエリは既に16Kチェックによってサイズ制御されています。

**フェーズ4 — ステップ実行。** 各DAGステップは独自のContextGuardを持つ独立したReActエージェントとして実行されます。重要なのは、これらのステップエージェントは**メモリを持たない**ということです — タスク説明と依存関係コンテキストのみで新たに開始します。これは設計による意図です：DAGステップは自己完結した作業単位であるべきです。依存関係の結果は`_build_step_context`を介して注入され、ContextGuardの`max_message_chars`制限である50Kで文字単位で切り詰められます。

**フェーズ5 — 分析。** ステップ結果はアナライザーLLM用にフォーマットされ、ステップごとに10K文字で切り詰められます。これにより、単一ステップの冗長な出力が分析コンテキストを支配するのを防ぎます。

**フェーズ6 — 再計画。** アナライザーが目標が達成されず、信頼度が閾値を下回ると判断した場合、ステップ結果は再計画コンテキスト用に各500文字に切り詰められます。再計画は*何が起きたか*と*何が間違ったか*を知る必要がありますが、すべてのステップの出力の完全な詳細は必要ありません。この積極的な切り詰めにより、再計画プロンプトはプランナーが効率的に処理できるほどコンパクトに保たれます。

DAGパイプラインアーキテクチャ全体、LLMコールマップ、再計画ロジックについては、[DAGエンジン](/architecture/dag-engine)を参照してください。

## ピン留めされたメッセージ

ピン留めメカニズムは、圧縮によって破棄されてはならないメッセージを保護します。2つのカテゴリのメッセージがピン留めされます:

1. **現在のユーザークエリ** — 常にピン留めされます。ユーザーが質問をして履歴が長すぎる場合、システムは履歴を圧縮し、質問は圧縮しません。

2. **ストリーム中に注入されたメッセージ** — ユーザーがエージェント実行中にフォローアップを送信すると、注入されたメッセージはピン留めされ、エージェントは次の反復でそれを認識します。

ピン留めのリスクは蓄積です。多くの注入されたメッセージがある長いセッションでは、ピン留めされたコンテンツが予算の大部分を消費し、実際の会話履歴の余地がなくなる可能性があります。ContextGuardはハードキャップでこれに対処します: **ピン留めされたトークンが予算の50%を超える場合、最も古い注入されたメッセージはピン留めが解除され、圧縮可能なプールに移動されます。** 最新のピン留めされたメッセージ(現在のクエリ)のみが保持されます。

これはトレードオフです。古い注入されたメッセージのピン留めを解除すると、それらが要約または切り詰められる可能性があります。しかし、別の方法 — ピン留めされたメッセージが他のすべてのコンテキストを圧倒することを許可する — はさらに悪いです。システムは最新のコンテキストを保持することを優先し、これはほぼ常に古い注入よりも関連性があります。

## トークン推定

FIM One は実際のトークナイザーではなくヒューリスティックなトークン推定を使用しています。これは明確なトレードオフを伴う意図的な選択です。

**実際のトークナイザーを使わない理由は？** 3つの理由があります：

1. **依存関係のコスト。** `tiktoken`（OpenAI のトークナイザー）は 15MB のコンパイル済み Rust バインディングです。`sentencepiece`（一部のオープンソースモデルで使用）には独自のビルド要件があります。複数の LLM プロバイダーをターゲットとするフレームワークの場合、単一の正しいトークナイザーは存在しません — 各モデルファミリーは異なるものを使用しています。

2. **速度。** ヒューリスティック推定は文字列に対する単一パスです。実際のトークン化には語彙検索、BPE マージ操作、特殊トークン処理が含まれます。ContextGuard は反復ごとに推定を呼び出し、時には複数回呼び出します — 速度の差は重要です。

3. **十分な精度。** ヒューリスティックは混合言語テキスト用に調整されています（ASCII/CJK の分割は 2 つの主要なケースをカバーしています）。エッジケース（句読点が多いコード、異常な Unicode）では 1.5～2 倍ずれる可能性がありますが、コンテキスト管理は本質的に近似的です。64K の予算で 30% ずれていても、快適なマージンが残ります。

具体的なヒューリスティック：

| コンテンツタイプ         | 比率            | 根拠                                                 |
| ---------------- | ------------- | -------------------------------------------------- |
| ASCII テキスト       | 約 4 文字/トークン   | 英語の散文とコードは GPT/Claude トークナイザー全体で平均 3.5～4.5 文字/トークン |
| CJK / 非 ASCII    | 約 1.5 文字/トークン | 各 CJK 文字は通常 1～2 トークン。1.5 は幾何平均                     |
| 画像               | 765 トークン/画像   | ビジョン API のベース 64 エンコード画像の概算コスト                     |
| メッセージあたりのオーバーヘッド | 4 トークン        | ロールマーカー、デリミタ、フォーマット                                |

推定は空でないコンテンツに対して常に最低 1 トークンを返します。これにより、予算計算でのゼロ除算のエッジケースを防ぎます。

## ユーザーが見るもの

コンテキスト管理は、一般的なケースでは見えないように設計されており、アクティベートされるときは最小限の干渉に留まります。ユーザーに見える信号は以下の通りです：

**CompactDivider。** `DbMemory` が読み込み時に履歴をコンパクト化すると、フロントエンドは「Earlier context (N messages) was summarized by AI.」というテキスト付きの破線区切り線をレンダリングします。これは要約と保持された最近のメッセージの間に表示され、ユーザーに古いコンテキストが圧縮されたことを視覚的に示しながら、会話フローを中断しません。

**トークン使用量表示。** 各応答の最後の `done` カードには「X.Xk in / X.Xk out」が表示されます。これは消費された入出力トークンの合計です。これにはコンパクト化に費やされたトークン（要約のための高速 LLM 呼び出し）が含まれます。トークン消費を監視するユーザーは、コンパクト化がどのようなオーバーヘッドを追加しているかを確認できます。

**グレースフルなエラーハンドリング。** コンテキスト管理が完全に失敗した場合（フォールバックチェーンを考えると起こらないはずのシナリオですが、理論的には可能です）、エラーは応答内のエージェントエラーテキストとして表示され、システムクラッシュとしては表示されません。会話は続行され、ユーザーは再試行または言い換えることができます。

目標は、ほとんどのユーザーがコンテキスト管理について考えないようにすることです。長い会話ができ、システムが予算を透過的に処理し、唯一の目に見える成果物は時々表示されるコンパクト区切り線です。トークン効率を気にするパワーユーザーとオペレーターにとって、使用量表示と設定可能な予算パラメーターは必要な制御を提供します。
