はじめに
本記事は「Context Engineering 完全入門」シリーズの第 4 回です。今回は 履歴圧縮(History Compaction) を取り上げます。
長時間セッションでは会話履歴が肥大化し、コンテキストウィンドウを圧迫します。本記事では、ベンダーネイティブ API(OpenAI・Anthropic)による圧縮と、クライアント側フォールバックを組み合わせた「ハイブリッド設計」について解説します。実装コストを最小化しながら、レガシーモデルにも対応できる実践的な設計パターンをご紹介します。
前回:第 3 回 結果ハンドリング
次回:第 5 回 サブエージェント🗺️ 単独でも読めます:本記事だけで Compaction の 3 パターンとハイブリッド設計の判断軸が理解できます。
📝 本記事は、生成 AI で作成した草案をベースに、筆者が加筆・修正し、技術的な正確性を確認した上で公開しています。
📝 本記事の要点
- 問題: 長時間セッションで会話履歴が肥大化、
古い順から会話を削除すると最初のタスク定義が消えてエージェントが迷走する- 解決: ベンダー native(パターン A)を最優先、それが使えない時のみクライアント側 fallback(パターン C)を使うハイブリッド設計
- サンプル実装の要点:
ModelCapabilityDetectorで分岐し、AdditionalProperties経由でベンダー固有のcontext_managementを渡す急ぐ方は §1 の Reversible vs Irreversible 表 と §2 の A/B/C パターン比較 だけでもアーキ判断ができます。
🎯 本記事の対象者
- 長時間セッションで会話履歴が肥大化する問題に直面している
- 「Microsoft Agent Framework (MAF) の Compaction を使うべきか、OpenAI / Anthropic のネイティブ Compaction API を使うべきか」を判断したい
- VS Code Copilot Chat のように、モデルごとに最適な方式を選ぶ設計を MAF で再現したい
- Reversible Compaction を優先する設計思想を学びたい
- 業務系の監査要件・ZDR 要件と Compaction を両立させたい
1. ⭐ 原則:履歴圧縮は「会話ログ」ではなく「状態」を残す
エージェントに必要なのは、会話履歴そのものではなく、作業を継続するための state(状態) です。
履歴圧縮はユーザーとのやり取りをすべて保存することが目的ではなく、重要なのは「何をするはずだったのか」「どこまで終わったのか」「どのような判断をしたのか」を残すことです。
古い会話を単純に順番に削除すると、最初に与えられたタスク定義や意思決定の経緯が失われ、エージェントが混乱・迷走してしまいます。
表形式で履歴圧縮設計の方針整理すると以下の通りです。#1 は前述のもので、#2・#3 は本記事の後半で詳しく解説します。
| # | 方針 | 概要 | 参照項目 |
|---|---|---|---|
| 1 | 状態を残す | 会話ログではなくタスク状態を保持する | §1 |
| 2 | ネイティブ機能を優先する | 利用可能ならモデル提供元の Compaction を使う | §2 パターンA・B |
| 3 | 復元経路を確保する | 常にフォールバック手段を用意する | §2 パターンC |
履歴圧縮の 3 つの実装パターン
実装には 3 つの異なるパターン が検討可能です。従来は履歴圧縮の手段は「パターン C(自前要約)」しか存在しませんでしたが、2025年後半頃よりベンダーネイティブなAPIがリリースされ、2026 年現在はパターン A・B が主流 になりつつあります。
| パターン | 名称 | 実装場所 | コスト |
|---|---|---|---|
| A | Server-side native | ベンダー側 | 最小 |
| B | Standalone compact endpoint | ベンダー側(明示呼出) | 小 |
| C | Client-side fallback | クライアント側で自前要約 | 大 |
Reversible vs Irreversible の使い分け
第 3 回で示した区別は履歴圧縮にも適用され、まず Reversible で逃せないか検討し、不可逆な要約は最後の手段 です。
| 観点 | Reversible Compaction | Irreversible Summarization |
|---|---|---|
| 古いツール結果 | resource_id に退避、<saved to /tmp/result_abc.json> 等 |
「ユーザーは X を求めた」と要約 |
| 古い会話 | 詳細を退避ストアへ、検索可能化 | LLM で要約 |
| 復元 | LLM が必要時に検索 | 不可能 |
📌 ベンダーネイティブ Compaction(パターン A)は基本的に Irreversible(要約) です。監査要件がある業務系では、これに加えて自社側で Reversible な退避(§4-5 の履歴退避ストア)を併用する二層構造が推奨されます。詳細は §5 で解説します。
2. 📐 パターン
ここでは実装パターンについて詳述します。最近のモデルではパターン A が利用できますが、過去のモデルやオンプレ LLM までサポートする場合は、VS Code Copilot Chat と同様に複数の方式を使い分けるハイブリッド設計を取り入れる必要があります。
前提:ステートフル API とステートレス API の違い
パターン A を理解する前に、OpenAI と Anthropic では API の状態管理モデルが根本的に異なる点に注意ください。両者とも「閾値超過で自動的にサーバ側圧縮が実行される」点は共通ですが、会話履歴を誰が保持するかが異なります。
| 観点 | OpenAI Responses API | Anthropic Messages API |
|---|---|---|
| API の状態管理 |
ステートフル(サーバが会話状態を保持でき、previous_response_id で差分送信が可能) |
ステートレス(毎リクエストで全履歴を送信。サーバは状態を保持しない) |
| 圧縮の実行場所 | サーバ側 | サーバ側 |
| 圧縮結果の保持責任 | サーバ or クライアント(store で選択) |
常にクライアント(compaction ブロックを受け取り、次回再送する) |
| 自動トリガー | 閾値超過で自動発火 | 閾値超過で自動発火 |
| ZDR 対応 |
store=false を明示指定して初めて対応 |
既定で対応(追加設定は不要) |
ここが混乱しやすいポイントです。 「サーバ側で圧縮が走り、自動トリガーされる」という挙動は両者で似ていますが、
-
OpenAI は既定でサーバが会話状態を保持できる設計(ステートフル)。ZDR にするには
store=falseの明示指定が必要。 - Anthropic は元からサーバに何も残さない設計(ステートレス)。圧縮結果すらクライアントが受け取って次回再送するため、ZDR は追加設定なしで満たされる。
この違いが、後述する「パターン B の有無」や「ZDR 対応方法」に直結します。
パターン A:Server-side native(ベンダー API)
「閾値を超えたら、ベンダーのサーバ側で自動的に Compaction が走る」パターン。リクエストにパラメータを 1 つ追加するだけで済みます。前節の通り、OpenAI(ステートフル)と Anthropic(ステートレス)で状態保持の挙動が異なる点に注意してください。
A-1. OpenAI Responses API(ステートフル型)
OpenAI の Responses API は ステートフル です。既定(store=true)ではサーバが会話状態を保持し、previous_response_id を使えば全履歴を再送せずに続きから会話できます。ZDR 要件下では store=false を指定し、サーバに状態を残さない運用にします。
"(訳) レンダリングされたトークン数が設定された閾値を超えると、サーバーはサーバーサイドのコンパクションを実行します。このモードでは、別途 /responses/compact への呼び出しは必要ありません。"
| 指定項目 | 役割 | 例 |
|---|---|---|
type |
圧縮種別の識別子 | compaction |
compact_threshold |
圧縮を発動するトークン閾値 |
200000(本番運用の推奨値。256K context window モデル向け) |
store |
サーバ側に会話状態を残すか(ステートフル制御) |
false で ZDR フレンドリー(サーバ非保持) |
📌
store=falseは compaction を無効化しません。store=falseでも server-side compaction は実行され、圧縮結果(compaction item)はクライアントが受け取って次回再送します。この点では Anthropic のステートレス方式と同じ挙動になり、両者の本質的な違いは「既定がステートフルか、常にステートレスか」だけに収斂します(OpenAI 公式ガイドのサンプルコード自体がstore=false+context_managementの組み合わせです)。
| 観点 | 内容 |
|---|---|
| API の状態管理 |
ステートフル(store=true が既定) |
| クライアント側コード | 不要(パラメータ追加のみ) |
| レイテンシ | 最小(推論内部で処理) |
| コスト | 最小(ベンダー最適化済み) |
| ZDR コンプライアンス | store=false の明示指定が必要 |
A-2. Anthropic Messages API(ステートレス型)
AnthropicのMessages APIは前述の通りステートレスです。毎リクエストでクライアントが全履歴を送信し、サーバは処理後に何も残しません。Compaction もこの原則を踏襲しており、サーバは要約(compaction ブロック)を生成してレスポンスに含めて返すだけで、状態はサーバに残りません。クライアントはそのブロックを履歴に追加し、次回リクエストで再送します。
この設計により、Anthropic の Compaction は追加設定なしで ZDR 対応です(OpenAI の store=false に相当する指定は不要)。
| 設定項目 | 役割 |
|---|---|
anthropic-beta ヘッダ |
β 機能を opt-in するため必須(値:compact-2026-01-12) |
context_management.edits 配列 |
圧縮ルールを複数指定できる構造 |
edits[].type |
圧縮種別(例:compact_20260112)で世代管理 |
edits[].trigger |
発動条件(例:{type: "input_tokens", value: 100000}) |
Anthropic 側は 日付付き世代管理(compact_20260112)を採用しており、将来の新世代が出ても古い実装を残せます。
📌 対応モデル(2026 年 6 月時点):Claude Opus 4.6 / 4.7 / 4.8、Sonnet 4.6 / 5、Fable 5、Mythos 5 / Preview。公式デフォルト閾値は 150,000 トークン ですが、256K context window モデルでは 200,000 トークン が本番運用の推奨値です。最新リストは Claude Platform Docs「Compaction」 を参照してください。
📌 補足:Anthropic には Compaction とは別に「Context Editing」機能も存在します。
Anthropic のコンテキスト管理には 2 系統あります。
- Compaction(beta:
compact-2026-01-12):要約ベース。本節で扱う自動要約機能(Irreversible 寄り)。- Context Editing(beta:
context-management-2025-06-27):削除ベース。clear_tool_uses_20250919(古いツール結果を削除してプレースホルダに置換)やclear_thinking_20251015(思考ブロック削除)。プレースホルダで「削除された」ことを Claude に伝えるため、第 3 回の Reversible 思想(要約ではなく退避+参照)に近い運用が可能です。監査要件が厳しい場合は Context Editing の併用も検討できます。
後述する VS Code Copilot Chat が Anthropic 向けに実装しているのは、この Context Editing(削除ベース) の方であり、要約ベースの Compaction ではない点に注意してください(§2「VS Code のハイブリッド設計」参照)。
パターン A 共通の注意点
閾値とモデルの実効 context window を一致させること:
| モデル | 実効 context window | 推奨 compact_threshold
|
|---|---|---|
| Claude Opus 4.6 以降 | ~256K | 200,000 |
| GPT-5.5 | ~256K | 200,000 |
| Claude Opus 4.5 以前 | ~128K | 100,000 |
| GPT-4o(レガシー) | ~64K | 50,000 |
※ Anthropic 公式のデフォルト閾値は 150,000 トークン です。上記の推奨値は本番運用での経験則に基づくチューニング値です。Anthropic Compaction は Opus 4.6 以降に加えて、Opus 4.7 / 4.8、Sonnet 4.6 / 5、Fable 5、Mythos 5 / Preview でも利用可能(2026/6 時点)。
パターン B:Standalone compact endpoint(明示呼出)
「Compaction を 明示的なタイミングで、推論とは切り離して呼び出したい」場合のパターン。OpenAI の /responses/compact という独立エンドポイントを直接実行することで実現します。
| ユースケース | 説明 |
|---|---|
| 夜間バッチ処理 | 「過去 24 時間の長時間セッションを朝までに全部 compact しておく」(推論を伴わず圧縮のみ実行) |
| 明示的なフェーズ区切り | ユーザーが「フェーズ 1 完了」と宣言したタイミングで圧縮 |
| ZDR 要件 | 履歴をサーバ側に保持できない場合 |
注意:Anthropic には 2026年6月末時点では /messages/compact 相当のエンドポイントは存在しません。
(補足) ZDR 要件下での Compaction の使い分け
ZDR(Zero Data Retention:ベンダー側にデータを残さない要件)が必要な場合、ステートフル/ステートレスの違いがそのまま設定方法の違いになります。
| ベンダー | ZDR 対応方法 | 補足 |
|---|---|---|
| OpenAI |
store=false を明示指定
|
既定はステートフル(store=true)のため、指定を忘れるとサーバに残る |
| Anthropic | 設定不要(既定で対応) | 元からステートレス。圧縮結果もクライアントが保持・再送する |
いずれの場合も、ZDR が保証するのは「ベンダーのサーバにデータが残らない」ことだけです。ベンダーネイティブ Compaction は要約(Irreversible)のため、ZDR 環境では圧縮で失われた元履歴がどこにも残らないリスクがあります。監査要件がある業務系では、送信前に自社側で PII マスキングを行い、元履歴を Reversible な退避ストア(§4-5)に保存する二層構造が必須です。
パターン C:Client-side fallback(自前要約)
クライアント側で別の LLM リクエストを投げて要約を作るパターン。ベンダー API が使えないモデル(ChatCompletions のみ対応のレガシー等)のフォールバックとして位置付けます。なお Anthropic の SDK には tool_runner 利用時のクライアント側 Compaction も用意されており、これもパターン C に相当します。
VS Code Copilot Chat のハイブリッド設計
📌 VS Code Copilot Chat の圧縮処理の詳細 (クリックで展開)
- OpenAI(パターン A、条件付き):
/responsesのリクエストボディにcontext_management(type:"compaction"、閾値modelMaxPromptTokens × 0.9、store:false無条件)を同梱する実装が存在します。ただし実験設定chat.responsesApiContextManagement.enabled(既定 false)が有効で、かつ対象モデル(gpt-5/gpt-5.1/gpt-5.2は除外)の場合のみ発火し、off 時はパターン C へフォールバックします(A と C は排他制御)。独立エンドポイント/responses/compact(パターン B)は一切呼んでいません。- Anthropic(パターン C + 削除型 Context Editing): Anthropic モデルの「要約」は他モデル共通のクライアント側要約(パターン C)経由です。Anthropic 純正の要約型 Compaction(
compact_*、anthropic-beta: compact-*)を呼ぶコードは存在しません。代わりに削除型の Context Editing(clear_tool_uses_20250919/clear_thinking_20251015、anthropic-beta: context-management-2025-06-27、設定chat.anthropic.contextEditing.mode既定'off')を併用可能です。これは第 3 回の Reversible 思想に近く、パターン A(要約 native)ではありません。- 下地はパターン C: ChatCompletions / BYOK / レガシーモデルは常にクライアント側要約(
SummarizedConversationHistory/BackgroundSummarizer)を使用します。バックグラウンド要約はトークン使用率 0.80 前後(ジッタ付き)で発火します。つまり VS Code の「ハイブリッド設計」の実体は、要約型ネイティブ(パターン A)が使えるのは OpenAI Responses だけ(しかも実験フラグ既定 off)、Anthropic ネイティブは削除型 Context Editing のみ、普遍的な下地はパターン C、という構成です。
A・B の使い分け早見表
| シナリオ | 推奨パターン | 理由 |
|---|---|---|
| リアルタイム長時間会話 | A | レイテンシ最小、API 呼び出し 1 回 |
| バッチ処理での再圧縮(推論なし) | B | 明示制御、コスト最適化可能(OpenAI のみ) |
| Anthropic モデル利用 | A | ステートレスかつ ZDR ネイティブ。B 相当の独立エンドポイントは無いが、A で明示制御可能 |
| ZDR 要件あり | A | OpenAI は store=false、Anthropic は既定対応。加えて自社側 Reversible 退避を併用 |
| 監査要件あり | A + JSON 退避ストア | パターン単独では不足(元履歴の保全が必要) |
| オンプレ LLM 利用 | C | 唯一の選択肢 |
3. 💡 実装の方針と設計判断
サンプルコードの実装方針は以下の通りです。
設計判断 1:「ハイブリッド」による履歴圧縮
ここでは業務系での採用を考慮し、まずベンダーネイティブ Compaction を利用し、利用できない場合のみ Client-side fallback へ切り替える構成を採用しています。
「ノウハウのある過去のモデル」や「顧客が指定したオンプレ LLM」、「テスト用ローカルモデル」など、ネイティブ API が利用できないケースでも動作させることを目的としています。
設計判断 2:ModelCapabilityDetector を抽象化する理由
文字列パターンで IsGptFamily() 等を判定する簡易な実装にします。ベンダーの supported_endpoints は SDK からは殆どのケースで利用できず、また文字列パターン判定は新モデル登場時の対応コストが低いという保守性メリットがあります。
設計判断 3:AdditionalProperties でベンダー固有設定を渡す
MAF の ChatOptions.AdditionalProperties に入れたキー/値は そのまま HTTP リクエストに含まれる ため、MAF の抽象を保ちつつベンダー機能をフル活用できます。OpenAI では store、Anthropic では anthropic-beta ヘッダ相当の指定も、この仕組みで渡します。
設計判断 4:Client-side fallback でも Reversible を優先
PipelineCompactionStrategy は Reversible が先・Irreversible が後という順序を意図しています:
-
ToolResultCompactionStrategy(Reversible:古いツール結果を軽量な要約メッセージに置換) -
SummarizationCompactionStrategy(Irreversible:要約用の別 LLM で会話を集約) -
SlidingWindowCompactionStrategy(直近 N ターンのみ保持) -
TruncationCompactionStrategy(緊急バックストップ:最低保持メッセージ数を確保)
設計判断 5:CompactionProvider の登録先(ChatClientAgentOptions vs ChatClientBuilder)
CompactionProvider の登録方法には 2 通りあります。両方とも動作はしますが、適用スコープ(実行される階層)が異なる点に注意が必要です(Microsoft Learn「Compaction」より):
| 登録方法 | 適用スコープ(実行される階層) | 用途 |
|---|---|---|
ChatClientAgentOptions.AIContextProviders |
エージェント呼び出し単位(RunAsync 1 回につき 1 度。ツール呼び出しループの内部では再実行されない) |
シンプルだが、ツール呼び出しが連続するターン中は圧縮されない |
chatClient.AsBuilder().UseAIContextProviders(...) |
IChatClient 呼び出し単位(ツール呼び出しループ内で LLM を呼ぶたびに再実行される) |
本番運用ではこちらを推奨 |
⚠️ 公式ドキュメントには次の注記があります:「(訳)
ChatClientAgentOptions経由で登録した場合、CompactionProviderはツール呼び出しループ中は動作しません。[中略] in-flight のリクエストコンテキストのみを圧縮し、保存済みの元の履歴はそのまま残したい場合は、代わりにChatClientBuilder側でUseAIContextProviders(...)を使って登録してください。」
HybridCompactionStrategy.BuildWithBestCompaction はモデル能力判定と設定値算出のみを責務とするため、フォールバック用の CompactionProvider を検出した呼び出し側が chatClient.AsBuilder().UseAIContextProviders(...) でラップしてからエージェントを組み立てる必要があります(§4-7 参照)。
4. 💻 実装:C# / Microsoft Agent Framework (MAF、ハイブリッド設計)
⚠️ MAF Compaction は experimental。#pragma warning disable MAAI001 が必要です。
4-1. VS Code Copilot Chat のモデル能力検出(参考)
VS Code Copilot Chat の IChatModelInformation インタフェースには以下のフィールドがあります:
💻 IChatModelInformation — モデル能力情報の参考コード(TypeScript・約15行、クリックで展開)
interface IChatModelInformation {
id: string;
family: string;
supports: {
tool_calls: boolean;
vision: boolean;
thinking: boolean;
streaming: boolean;
// ...
};
// 🔑 重要:このフィールドが Compaction 手法を分岐させる
supported_endpoints: ("ChatCompletions" | "Messages" | "Responses")[];
}
supported_endpoints がモデルごとに定義されている ことが決定的に重要です。VS Code 側では useResponsesApi / useMessagesApi / useWebSocketResponsesApi の系統に分岐して扱われます。
4-2. モデル能力検出レイヤ(C# 実装)
💻 ModelCapabilityDetector — モデル能力検出レイヤのサンプル実装(C#・約45行、クリックで展開)
using Microsoft.Extensions.AI;
namespace ContextEngineering.History;
public enum CompactionEndpoint { Responses, Messages, ChatCompletions, Unknown }
public record ModelCapability(
string ModelId,
string Family,
CompactionEndpoint PreferredEndpoint,
bool SupportsNativeCompaction);
public static class ModelCapabilityDetector
{
public static ModelCapability Detect(string modelId)
{
// 🔑 文字列パターンで判定(ベンダー SDK の公開情報に依存しない)
// → 新モデル登場時の対応コストが低い(コードに 1 行追加するだけ)
// OpenAI Responses API 対応(GPT-5 系、Codex)
if (IsGpt5PlusFamily(modelId) || IsGptCodexFamily(modelId))
return new(modelId, "openai-responses", CompactionEndpoint.Responses, true);
// Anthropic Messages API 対応(Claude 系)
if (IsAnthropicFamily(modelId))
return new(modelId, "anthropic-messages", CompactionEndpoint.Messages, true);
// OpenAI ChatCompletions のみ対応(レガシー)
if (IsGptFamily(modelId))
return new(modelId, "openai-chatcompletions", CompactionEndpoint.ChatCompletions, false);
return new(modelId, "unknown", CompactionEndpoint.Unknown, false);
}
private static bool IsGpt5PlusFamily(string id) =>
id.StartsWith("gpt-5", StringComparison.OrdinalIgnoreCase);
private static bool IsGptCodexFamily(string id) =>
id.Contains("codex", StringComparison.OrdinalIgnoreCase);
private static bool IsAnthropicFamily(string id) =>
id.StartsWith("claude-", StringComparison.OrdinalIgnoreCase);
private static bool IsGptFamily(string id) =>
id.StartsWith("gpt-", StringComparison.OrdinalIgnoreCase);
}
4-3. ハイブリッド Compaction 戦略
実装の要点:
- モデル能力に応じて OpenAI / Anthropic ネイティブ Compaction かクライアント側フォールバックかを分岐
- フォールバック時は
PipelineCompactionStrategyをCompactionProviderでラップしAIContextProvidersに登録する(options.CompactionStrategyという直接プロパティは存在しない) - 要約用に別の(より安価な)
IChatClientを差し込めるようにしている
💻 HybridCompactionStrategy — ハイブリッドCompaction戦略のサンプル実装(C#・約100行、クリックで展開)
#pragma warning disable MAAI001 // MAF Compaction は experimental API
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
namespace ContextEngineering.History;
// 🔑 フォールバック用 CompactionProvider は ChatClientAgentOptions とは別の戻り値として分離する。
// options.AIContextProviders に入れてしまうと、呼び出し側が気づかず AsAIAgent(options) を
// 直接呼んだ場合にツール呼び出しループ内で圧縮が効かないバグを再発させてしまうため(設計判断 5 参照)。
public sealed record CompactionSetup(ChatClientAgentOptions Options, CompactionProvider? FallbackCompactionProvider);
public static class HybridCompactionStrategy
{
public static CompactionSetup BuildWithBestCompaction(
IChatClient chatClient,
string modelId,
int compactThresholdTokens = 100000,
// 🔑 要約専用の(より安価な)チャットクライアントを差し込めるようにする
// → 指定がなければメインの chatClient を再利用する
IChatClient? summarizerChatClient = null)
{
// 🔑 まずモデル能力を検出
var capability = ModelCapabilityDetector.Detect(modelId);
var options = new ChatClientAgentOptions
{
ChatOptions = new ChatOptions { Instructions = "Long-running assistant." },
};
CompactionProvider? fallbackCompactionProvider = null;
// 🔑 能力に応じて分岐
// → A: ベンダー native(最優先)
// → C: クライアント側 fallback(その他全て)
switch (capability.PreferredEndpoint)
{
case CompactionEndpoint.Responses:
ConfigureOpenAINativeCompaction(options, compactThresholdTokens);
break;
case CompactionEndpoint.Messages:
ConfigureAnthropicNativeCompaction(options, compactThresholdTokens);
break;
default:
// 🔑 ChatCompletions / Unknown → fallback
// → PipelineCompactionStrategy を CompactionProvider でラップし、options ではなく専用の戻り値として返す
var pipeline = ClientSideFallback.Build(summarizerChatClient ?? chatClient);
fallbackCompactionProvider = new CompactionProvider(pipeline);
break;
}
return new CompactionSetup(options, fallbackCompactionProvider);
}
private static void ConfigureOpenAINativeCompaction(
ChatClientAgentOptions options, int threshold)
{
options.ChatOptions!.AdditionalProperties ??= new AdditionalPropertiesDictionary();
// 🔑 OpenAI Responses API の context_management パラメータ
// → 1 パラメータ追加するだけで server-side compaction が走る
options.ChatOptions.AdditionalProperties["context_management"] = new[]
{
new { type = "compaction", compact_threshold = threshold },
};
// 🔑 store=false で ZDR (Zero Data Retention) フレンドリーに
options.ChatOptions.AdditionalProperties["store"] = false;
}
private static void ConfigureAnthropicNativeCompaction(
ChatClientAgentOptions options, int threshold)
{
options.ChatOptions!.AdditionalProperties ??= new AdditionalPropertiesDictionary();
// 🔑 ベータヘッダで Compaction API 有効化
options.ChatOptions.AdditionalProperties["anthropic-beta"] = "compact-2026-01-12";
// 🔑 edits 配列で Compaction を定義
options.ChatOptions.AdditionalProperties["context_management"] = new
{
edits = new[]
{
new
{
type = "compact_20260112",
trigger = new { type = "input_tokens", value = threshold },
},
},
};
}
}
4-4. Client-side fallback(パターン C)
💻 ClientSideFallback — 4段階Compactionパイプラインのサンプル実装(C#・約40行、クリックで展開)
📌 閾値の累進性:Step 1(2000 トークン)で早めに Reversible な圧縮を発動し、それでも収まらない場合のみ Step 2(15000 トークン)で Irreversible な要約に降格します。これにより、Reversible 優先の原則を段階的に守れます。
#pragma warning disable MAAI001 // MAF Compaction は experimental API
using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
namespace ContextEngineering.History;
/// <summary>
/// ベンダーネイティブ Compaction が使えないモデル向けの 4 段階パイプライン(パターン C)。
/// Reversible が先、Irreversible が後の順序。
/// </summary>
public static class ClientSideFallback
{
public static CompactionStrategy Build(IChatClient summarizerChatClient)
{
return new PipelineCompactionStrategy(
// 🔑 Step 1: Reversible
// 古いツール結果を要約メッセージに置き換える(第 3 回のパターンに相当)
new ToolResultCompactionStrategy(
CompactionTriggers.TokensExceed(2000)),
// 🔑 Step 2: Irreversible(最後の手段)
// → 要約用チャットクライアントを第一引数で必須指定
new SummarizationCompactionStrategy(
summarizerChatClient,
CompactionTriggers.TokensExceed(15000),
4),
// 🔑 Step 3: 直近 N user turn のみ保持
new SlidingWindowCompactionStrategy(
CompactionTriggers.TurnsExceed(10)),
// 🔑 Step 4: 緊急バックストップ(強制 truncate、最低 10 メッセージは保持)
new TruncationCompactionStrategy(
CompactionTriggers.TokensExceed(30000),
10));
}
}
⚠️ 2026 年 7 月時点の
Microsoft.Agents.AI.Compaction(experimental)では、各戦略はTrigger = ...のようなオブジェクト初期化子構文ではなく、コンストラクタの位置引数でtrigger等を渡す設計です。またSummarizationCompactionStrategyは要約用のIChatClientを第一引数で必須受け取り、SlidingWindowCompactionStrategyにKeepRecentTurns相当のパラメータは存在せず、TruncationCompactionStrategyの第二引数は「トークン予算」ではなく「最低保持メッセージ数(minimumPreserved)」です。以下はこの実際の API 形状に合わせています。
4-5. JSON 退避ストア(全パターン共通)
以下の実装は、サンプル用の PoC 実装です。本番運用では、IHistoryStorage を実装した永続化バックエンドへの置き換えが推奨されます(プロセス再起動でセッション継続不能になるアンチパターンの回避も兼ねる)。
💻 IHistoryStorage — 履歴永続化バックエンドの抽象化(C#・約10行、クリックで展開)
永続化の抽象化(インターフェース定義):
// 🔑 サンプルではインメモリ実装、本番では Redis / Blob Storage 等に置換
public interface IHistoryStorage
{
Task SaveAsync(string archiveId, DateTime timestamp,
IReadOnlyList<ChatMessage> messages, CancellationToken ct = default);
Task<IReadOnlyList<(string ArchiveId, DateTime Timestamp, IReadOnlyList<ChatMessage> Messages)>> GetAllAsync(
CancellationToken ct = default);
}
💻 HistoryStore — 履歴退避と全文検索のサンプル実装(C#・約60行、クリックで展開)
本体実装:
using System.Text.Json;
using Microsoft.Extensions.AI;
namespace ContextEngineering.History;
public record HistoryEntry(string ArchiveId, DateTime Timestamp, List<ChatMessage> Messages);
/// <summary>
/// 圧縮で消えた履歴を「クライアントから見える形」で保存。
/// 業務系の監査要件・検索要件に対応するために必須のレイヤ。
/// </summary>
public class HistoryStore
{
private readonly IHistoryStorage _storage;
public HistoryStore(IHistoryStorage storage) => _storage = storage;
// 🔑 タプル (ArchiveId, Snippet) を返すと System.Text.Json が
// ValueTuple のフィールド(Item1/Item2)をシリアライズしないため、LLM に渡す JSON が
// 空オブジェクト({})になってしまう。record にすることで確実にプロパティとしてシリアライズされる。
public record SearchResult(string ArchiveId, string Snippet);
public async Task<string> ArchiveMessagesAsync(
IEnumerable<ChatMessage> messages, CancellationToken cancellationToken = default)
{
var archiveId = Guid.NewGuid().ToString("N")[..8];
await _storage.SaveAsync(archiveId, DateTime.UtcNow, messages.ToList(), cancellationToken);
return archiveId;
}
/// <summary>
/// 退避した履歴を全文検索(簡易実装:本番では BM25 / ベクトル検索を推奨)
/// </summary>
public async Task<List<SearchResult>> SearchAsync(
string query, int topK = 5, CancellationToken cancellationToken = default)
{
var q = query.ToLowerInvariant();
var results = new List<SearchResult>();
var entries = await _storage.GetAllAsync(cancellationToken);
foreach (var entry in entries)
{
foreach (var msg in entry.Messages)
{
// 🔑 簡易:msg を JSON シリアライズして部分文字列検索
// → 本番は Lucene / Elasticsearch / Vector DB へ
var content = JsonSerializer.Serialize(msg).ToLowerInvariant();
if (content.Contains(q))
{
results.Add(new SearchResult(entry.ArchiveId, content.Length > 200 ? content[..200] : content));
if (results.Count >= topK) return results;
}
}
}
return results;
}
}
監査要件への対応:HistoryArchivingProvider(圧縮パターンに依存しない設計)
HistoryStore を実際に監査要件へ役立てるには、「圧縮によってモデルに送られなくなるメッセージ」をこのストアへ書き込む仕組みが別途必要です。ここで注意すべきなのは、圧縮が実際に何を除外したかを観測する必要は一切ないという点です。
AIContextProvider.StoreAIContextAsync は AIAgent の呼び出し境界(RunAsync 1 回につき 1 度)で実行され、そのターンで使われたリクエスト/レスポンスメッセージ全体を受け取れるため、毎ターンのやり取りをまるごと複製しておくだけで済みます。
この設計により、ベンダーネイティブ Compaction(パターン A/B。圧縮はベンダーのサーバ側で行われ、クライアントからは中身が見えない)・クライアント側フォールバック(パターン C)のどちらが選ばれても同じ 1 つの仕組みで監査要件を満たせます。圧縮の実装詳細(何をどう除外したか)を意識する必要がないため、パターンを跨いだ移行や切り替えにも影響を受けません。
💻 HistoryArchivingProvider — 毎ターンの完全履歴を退避するサンプル実装(C#・約20行、クリックで展開)
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace ContextEngineering.History;
public sealed class HistoryArchivingProvider(HistoryStore historyStore) : AIContextProvider
{
protected override async ValueTask StoreAIContextAsync(
InvokedContext context, CancellationToken cancellationToken = default)
{
// 🔑 呼び出しが失敗した場合(ResponseMessages が null)は退避しない
if (context.ResponseMessages is null)
return;
var turnMessages = context.RequestMessages.Concat(context.ResponseMessages).ToList();
if (turnMessages.Count > 0)
await historyStore.ArchiveMessagesAsync(turnMessages, cancellationToken);
}
}
このプロバイダは、圧縮設定(§4-3)とは独立に ChatClientAgentOptions.AIContextProviders へ追加するだけで機能します。圧縮によってどのメッセージが実際にモデルへ送られなくなったかを確認したい場合でも、退避ストア側に全ターンの完全な履歴があるため、必要な範囲を後から突き合わせて確認できます。
4-6. 検索ツール(LLM が使う)
💻 HistorySearchTools — 圧縮済み履歴を検索するツールのサンプル実装(C#・約20行、クリックで展開)
public class HistorySearchTools
{
private readonly HistoryStore _store;
public HistorySearchTools(HistoryStore store) => _store = store;
[System.ComponentModel.Description(
// 🔑 description で「compact された履歴を検索する道具」と明示
"Search the conversation history that has been compacted out of " +
"the active context. Use to recall details from earlier in the conversation."
)]
public async Task<string> SearchHistoryAsync(string query, int topK = 5)
{
var results = await _store.SearchAsync(query, topK);
return System.Text.Json.JsonSerializer.Serialize(new { results });
}
}
4-7. エージェント組み立て
以下は、HistoryStore を外部から注入する形でエージェントを組み立てる例です。テストや本番環境でのモック差し替えを容易にするため、依存性注入(DI)パターンを採用しています。
⚠️ 設計判断 5 で記載した通り、フォールバック(パターン C)ではツール呼び出し中のリクエストにも圧縮を効かせるため chatClient.AsBuilder().UseAIContextProviders(...) を用いて履歴圧縮用のプロバイダを登録しています。
💻 Example.BuildAgent — ハイブリッドCompaction対応エージェントの組み立て例(C#・約40行、クリックで展開)
public static class Example
{
public static AIAgent BuildAgent(IChatClient chatClient, string modelId, HistoryStore historyStore)
{
var searchTools = new HistorySearchTools(historyStore);
// 🔑 モデルごとにハイブリッド設定を取得
var setup = HybridCompactionStrategy.BuildWithBestCompaction(chatClient, modelId);
var options = setup.Options;
// 🔑 検索ツールを追加(圧縮された履歴へのアクセスパス)
options.ChatOptions ??= new ChatOptions();
options.ChatOptions.Tools =
[
AIFunctionFactory.Create(searchTools.SearchHistoryAsync, name: "SearchHistory"),
];
// 🔑 フォールバック時の CompactionProvider は IChatClient 呼び出し単位で適用させたいため、
// chatClient 側に UseAIContextProviders(...) でラップしてから AsAIAgent する
IChatClient effectiveChatClient = setup.FallbackCompactionProvider is { } fallbackProvider
? chatClient.AsBuilder().UseAIContextProviders(fallbackProvider).Build()
: chatClient;
// 🔑 監査要件対応: HistoryArchivingProvider を登録する。Compaction のパターン(A/B/C)に
// 関わらず毎ターン自動的に呼ばれるため、ここに登録する
options.AIContextProviders = [new HistoryArchivingProvider(historyStore)];
return effectiveChatClient.AsAIAgent(options);
}
}
#pragma warning restore MAAI001
5. 📊 ケーススタディ
主要 4 実装の Compaction 設計比較
主要なエージェント実装を比較すると、ベンダーネイティブ機能を優先しつつ、利用できない場合にフォールバックする設計 が業界の主流になりつつあります。
| 実装 | パターン A | パターン B | パターン C | 評価 |
|---|---|---|---|---|
| OpenAI Codex CLI | ◎ Responses 専用 | ○ /responses/compact
|
× | 単一ベンダー前提で最適 |
| VS Code Copilot Chat | △ OpenAI のみ(実験フラグ既定 off) | × 未使用 | ◎ 下地(普遍的) | ハイブリッド設計だが実体は C 中心 |
| Claude Code | ◎ context_management
|
× | × | 単一ベンダー前提で最適 |
| MAF | ◎ 利用可能なら採用 | ○ 拡張可能 | ◎ フォールバック | 業務系向けに実装しやすい |
📌 VS Code Copilot Chat 行の補足(実コード確認済み): パターン A は OpenAI Responses に対してのみ実装され、実験設定
chat.responsesApiContextManagement.enabled(既定 off)でゲートされます。独立エンドポイント(パターン B)は未使用。Anthropic に対しては要約型 Compaction を呼ばず、クライアント側要約(パターン C)+削除型 Context Editing(chat.anthropic.contextEditing.mode、既定'off')の併用です。したがって「洗練されたハイブリッド」というより、普遍的な下地はパターン C、OpenAI のみ条件付きで A へ昇格という理解が正確です。
教訓:
- OpenAI Codex CLI や Claude Code は単一ベンダーのネイティブ機能を前提としており、その環境では非常にシンプルかつ効率的です
- VS Code Copilot Chat は利用中のモデル能力を判定して手法を切り替えますが、要約型ネイティブが使えるのは OpenAI(しかも実験フラグ既定 off)のみで、普遍的な下地はクライアント側要約(パターン C)です
- 業務系システムでは将来的なモデル入れ替えや複数ベンダー利用が一般的なため、単一パターンに依存する設計よりも ハイブリッド設計 が長期運用に適しています
- MAF 実装では、まずパターン A を試し、利用できない場合のみパターン B・C に降格する構成が現実的です
つまり、「ネイティブ機能を優先しつつ、常にフォールバック経路を持つ」 ことが、2026 年時点の Compaction 設計におけるベストプラクティスと言えます。
業務系での運用指針
業務系システムでは、まずベンダーが提供するネイティブ Compaction 機能を利用し、利用できない場合のみフォールバックする構成が推奨されます。
特にマルチモデル環境では、OpenAI・Anthropic・ローカル LLM が混在するケースも多いため、モデルごとの能力に応じて Compaction 手法を切り替える設計が現実的です。
また、監査要件やコンプライアンス要件・ZDR 要件のあるシステムでは、コンテキスト削減と履歴保持を両立する必要があります。
ベンダーネイティブ Compaction はトークン消費の削減には有効ですが、要約(Irreversible)であるため、圧縮後の状態だけでは過去の詳細な会話履歴を復元できません。とくに ZDR 環境ではベンダー側にも元履歴が残らないため、圧縮前の履歴を自社側の別ストアへ退避する構成が必須となります。
そのため業務系では、Compaction 自体はどのパターン(A/B/C いずれ)に任せつつ、圧縮の実装詳細とは独立に毎ターンの完全な履歴を別ストアへ複製しておく構成(§4-5 の HistoryArchivingProvider)が現実的な選択となります。この方式は圧縮が何を除外したかを観測しないため、パターン A/B/C のどれを採用していても同じ仕組みで監査要件を満たせます。
| ケース | Compaction | 履歴退避 | 推奨 |
|---|---|---|---|
| 単一ベンダー利用 | パターン A | 任意 | ベンダーネイティブを利用 |
| マルチモデル利用 | モデル能力に応じて切替 | 推奨 | モデルごとに最適方式を選択 |
| オンプレ LLM | パターン C | 推奨 | クライアント側で自前要約を実装 |
| 監査要件 / ZDR あり | パターン A/B/C いずれでも可 | 必須 | 全履歴を別ストアへ保存 |
教訓:
監査要件・ZDR 要件のあるシステムでは、Compaction と履歴保持を両立する必要があります。
ベンダーネイティブ Compaction はトークン削減の観点では最適ですが、圧縮後の状態だけでは過去の詳細な会話履歴を復元できない場合があります。
そのため業務系では、Compaction 機能の有無だけでなく、圧縮前の履歴をどこに保存し、必要時にどう復元するかまで含めて設計することが重要です。
6. ⚠️ アンチパターン
本記事で扱った履歴圧縮の要点を、実装時のセルフレビュー用チェックリスト(アンチパターン)としてまとめました。
実装時は本表を「やってはいけないことリスト」としてご参照ください。特に #2・#3・#5・#7 は業務系での長期運用やモデル切替、監査・ZDR 要件への対応に直結する重要項目です。
| # | アンチパターン | 問題点 |
|---|---|---|
| 1 | 「全部自前でやろうとする」 | ベンダー API ネイティブ最適化を享受できない |
| 2 | 「単一ベンダー固定でハードコード」 | 業務系では将来モデル切替が必要になる |
| 3 | 「フォールバックを持たない」 | BYOK・ローカル LLM 対応で破綻する |
| 4 | 「圧縮アルゴリズムをセッション中に変える」 | LLM の検索戦略が破綻する |
| 5 | 「退避ストアを永続化しない」 | プロセス再起動でセッション継続不能に |
| 6 | 「閾値を雑に設定する」 | モデルの実効 context window を超過する可能性 |
| 7 | 「ステートフル前提のまま ZDR 要件を見落とす」 | OpenAI で store=false を忘れるとサーバに履歴が残る/要約のみで元履歴を退避せず監査に対応できない |
おわりに
本記事では、会話履歴の圧縮について「ベンダーネイティブ API を優先し、それが使えない場合にクライアント側フォールバックを使う」ハイブリッド設計をご紹介しました。
パターン A(Server-side native)は実装コストが最も低く、コストとレイテンシの両面で優れています。まずはモデルの対応状況を確認し、対応しているなら積極的に活用することをお勧めします。ただしベンダー Compaction は要約(Irreversible)のため、監査・ZDR 要件がある業務系では自社側の Reversible 退避ストアを必ず併用してください。
また注意点として、OpenAI(ステートフル)と Anthropic(ステートレス)で状態保持の挙動が異なる点を挙げさせていただきました。両者とも「閾値超過で自動圧縮」という挙動は似ていますが、OpenAI は store=false を明示しないとサーバに履歴が残り、Anthropic は元から何も残しません。なお store=false を指定しても compaction 自体は動作する点も押さえておいてください。ZDR 要件がある場合はこの違いに注意ください。
次回は「サブエージェント分離」として、コンテキストを時間的に分割するのではなく空間的に分離する手法を解説します。
参考文献
OpenAI 公式
-
OpenAI API Docs「Compaction」(ガイド)
- 補足: standalone エンドポイント仕様は OpenAI API Reference「Compact a response」(
POST /responses/compact) を参照。
- 補足: standalone エンドポイント仕様は OpenAI API Reference「Compact a response」(
-
OpenAI API Reference「Responses API」
- 補足: Azure OpenAI 版は Microsoft Learn「Use the Azure OpenAI Responses API」 を参照。
Anthropic 公式
- Claude Platform Docs「Context editing」(
clear_tool_uses_20250919/clear_thinking_20251015ほか) - Claude Platform Docs「Compaction」(Messages API /
context_managementパラメータ、beta header:compact-2026-01-12)
VS Code ソース解析
📌 standalone の
microsoft/vscode-copilot-chatは 2026/5/20 にアーカイブ(read-only) され、エージェント関連コードは現在microsoft/vscodeのextensions/copilot/配下で更新されています。以下は移管後のアクティブなソースを指します。
- microsoft/vscode
chatModelCapabilities.ts(supported_endpointsによるモデル能力分岐) - microsoft/vscode
summarizedConversationHistory.tsx(会話圧縮ロジック本体) -
DeepWiki: microsoft/vscode-copilot-chat
- 補足: DeepWiki はアーカイブ版リポジトリを索引しているため、移管後の
extensions/copilot実装をトレースした解説として Alex Op「How VS Code Copilot Chat Compacts Your Conversation」(2026/6/27) も参照。
- 補足: DeepWiki はアーカイブ版リポジトリを索引しているため、移管後の
Microsoft Agent Framework
- Microsoft Learn「Microsoft Agent Framework — Compaction」
-
microsoft/agent-framework
dotnet/samples/02-agents/Agents/Agent_Step18_CompactionPipeline- 補足: Python 版サンプル群は
python/samples/02-agents/compactionを参照。
- 補足: Python 版サンプル群は