AIチャットは最初は通常とてもスムーズですが、対話が長くなるにつれて問題も徐々に現れます。入力トークンがどんどん増え、呼び出しコストも上昇し続け、モデルの応答速度にも影響が出る可能性があります。
さらに厄介なのは、古いメッセージを単純に削除することもできない点です。ユーザーが前半で触れた予算、好み、製品情報は、後半でも使う可能性があります。
そんなときは、AI に「記憶の要約」機構を追加できます。
記憶の要約とは
要するに、チャット記録が一定の長さを超えたとき、別のモデルに前の対話を整理して短い要約にしてもらう、というものです。
元のチャット記録は圧縮できますが、その中の重要な情報は引き続き保持されます。後続の対話では、すべての履歴メッセージを持ち込むのではなく、要約と直近数ターンのメッセージだけを持ち込みます。
これにより、コンテキスト長を制御しつつ、AI に重要な内容を覚えさせ続けることができます。
全体の考え方
以下の例では、Azure OpenAI をメインの対話モデルとして使用し、ユーザーの質問に通常どおり回答させます。
ローカルの Ollama モデルは、履歴対話の整理を担当します。要約タスクは比較的単純であり、すべてをメインモデルに任せる必要はありません。ローカルモデルを使えば、呼び出しコストも削減できます。
コンテキストが 300 トークンを超えると、SummarizationCompactionStrategy が自動的に要約をトリガーします。
ここでは 300 に小さめの値を設定していますが、主な目的はデモを分かりやすくするためです。実際のプロジェクトでは、モデルのコンテキストウィンドウや業務コストに応じて調整できます。
minimumPreservedGroups は 2 に設定されており、履歴メッセージを圧縮する際に、少なくとも直近 2 つのメッセージグループを保持することを意味します。これにより、短期コンテキストを残しつつ、履歴が無限に増え続けるのを防げます。
AI が「何を覚えるか」を実際に決めるのは summarizationPrompt です。
例では、最初の製品、すべての価格、ユーザーの予算、ユーザーの好み、そして今後使う可能性のある情報を保持するよう明確に指示しています。要約モデルはすべての文を覚える必要はなく、本当に価値のある内容だけを残せば十分です。
完全なコード
using Azure.AI.OpenAI;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Compaction;
using Microsoft.Extensions.AI;
using OllamaSharp;
using OllamaSharp.Models;
using System.ClientModel;
using System.Text.Json;
#pragma warning disable
var aiConfig = new AIConfig.AzureOpenAIConfig("gpt-5.6-luna");
var credential = new ApiKeyCredential(aiConfig.Key);
var options = new JsonSerializerOptions()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
// メインの対話モデル。ユーザー入力への応答を担当する。
var agentChatClient = new AzureOpenAIClient(new Uri(aiConfig.Endpoint), credential)
.GetChatClient(aiConfig.DeploymentName)
.AsIChatClient();
var aiConfigOllama = new AIConfig.OllamaConfig("qwen");
// 要約モデル。コンテキスト圧縮時の要約生成専用。
var summarizerChatClient = new ThinkDisabledChatClient(
new OllamaApiClient(aiConfigOllama.Endpoint, aiConfigOllama.Model)
);
// 要約には Azure OpenAI を使うこともできる。
// var summarizerChatClient = new AzureOpenAIClient(
// new Uri(aiConfig.Endpoint),
// credential
// )
// .GetChatClient(aiConfig.DeploymentName)
// .AsIChatClient();
var compactionStrategy = new SummarizationCompactionStrategy(
chatClient: summarizerChatClient,
trigger: CompactionTriggers.TokensExceed(300),
minimumPreservedGroups: 2,
summarizationPrompt: """
购物助手のために、これまでの対話内容を要約してください。
以下の情報を保持してください:
- ユーザーが最初に質問した製品
- すべての製品価格
- ユーザーの予算
- ユーザーの好み
- 今後の対話に必要な重要情報
簡潔で明確にし、内容は事実に基づいてください。
"""
);
// インテリジェントエージェントにコンテキスト圧縮機能を接続し、長い対話によるコンテキスト膨張を防ぐ。
var agent = agentChatClient
.AsBuilder()
.UseAIContextProviders(new CompactionProvider(compactionStrategy))
.BuildAIAgent(
new ChatClientAgentOptions
{
Name = "Assistant",
ChatOptions = new()
{
// システムプロンプトでエージェントの役割と記憶の重点を制約する。
Instructions = "あなたは有能なショッピングアシスタントです。ユーザーの好みと製品情報を覚えてください。",
},
}
);
var session = await agent.CreateSessionAsync();
// コンテキスト圧縮が発生するショッピング対話を、多段入力でシミュレーションする。
var list = new List<string>
{
"1000ドル以下のノートパソコンを買いたいです。",
"主にプログラミングとビデオ通話に使います。",
"見つけたそのノートパソコンは999.99ドルです。",
"キーボードも必要です。キーボードは79.99ドルです。",
"RGBバックライトは好きではありません。静かなキーボードの方が好きです。",
"マウスも必要です。マウスは29.99ドルです。",
"携帯性は私にとって重要だと覚えておいてください。",
"最初に質問した製品は何でしたか?"
};
foreach (var input in list)
{
Console.WriteLine("あなた: " + input);
var response = await agent.RunAsync(input, session);
Console.WriteLine("アシスタント:");
Console.WriteLine(response.Text);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(
$"合計トークン数: {response.Usage.TotalTokenCount}\n" +
$"入力トークン数: {response.Usage.InputTokenCount}\n" +
$"出力トークン数: {response.Usage.OutputTokenCount}\n" +
$"キャッシュ入力トークン数: {response.Usage.CachedInputTokenCount}\n" +
$"推論トークン数: {response.Usage.ReasoningTokenCount}"
);
Console.ResetColor();
}
var serializedSession = await agent.SerializeSessionAsync(session);
PrintMessageGroups(
serializedSession,
options,
"SummarizationCompactionStrategy"
);
void PrintMessageGroups(
JsonElement serializedSession,
JsonSerializerOptions options,
string propertyName)
{
if (serializedSession.TryGetProperty("stateBag", out var stateBag) &&
stateBag.TryGetProperty(propertyName, out var strategy) &&
strategy.TryGetProperty("messagegroups", out var messageGroups) &&
messageGroups.ValueKind == JsonValueKind.Array)
{
foreach (var item in messageGroups.EnumerateArray())
{
if (item.TryGetProperty("isExcluded", out var isExcluded) &&
isExcluded.GetBoolean())
{
Console.ForegroundColor = ConsoleColor.Red;
}
else
{
Console.ForegroundColor = ConsoleColor.Green;
}
Console.WriteLine(JsonSerializer.Serialize(item, options));
}
}
Console.ResetColor();
}
// Ollama クライアントを簡単にラップし、think モードを統一的にオフにする。
// 要約時に余計な推論内容が出力されるのを防ぐ。
sealed class ThinkDisabledChatClient(IChatClient inner) : IChatClient
{
public Task<ChatResponse> GetResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
options ??= new ChatOptions();
options.AddOllamaOption(OllamaOption.Think, false);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("要約を開始します……");
Console.ResetColor();
return inner.GetResponseAsync(
messages,
options,
cancellationToken
);
}
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
options ??= new ChatOptions();
options.AddOllamaOption(OllamaOption.Think, false);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("要約を開始します……");
Console.ResetColor();
return inner.GetStreamingResponseAsync(
messages,
options,
cancellationToken
);
}
public object? GetService(
Type serviceType,
object? serviceKey = null)
=> inner.GetService(serviceType, serviceKey);
public void Dispose()
=> inner.Dispose();
}
実行結果
プログラムはショッピング対話をシミュレーションします。ユーザーは順に、ノートパソコン、キーボード、マウス、予算、使用の好みについて話します。
トークン数が制限を超えると、ローカルモデルが古い対話の要約を開始します。
最終的に、初期メッセージが圧縮されていても、ユーザーが「最初に質問した製品は何でしたか」と尋ねると、メインモデルは依然として「ノートパソコン」と答えることができます。
プログラムは最後に会話内のメッセージグループも出力します。赤は活動中のコンテキストから除外された古いメッセージ、緑は現在も保持されている内容を示しており、圧縮結果を確認するのに便利です。
さいごに
AI の記憶とは、すべてのチャット記録を常にコンテキストへ詰め込むことではありません。
より実用的な方法は、直近の対話を保持し、それより前の内容を要約しておくことです。
メインモデルが会話を担当し、小さなモデルが記憶の整理を担当する。コンテキストは短くなり、重要な情報は残る。これが「記憶の要約」の核心です。
(Translated by GPT)
元のリンク:https://mp.weixin.qq.com/s/95255JSAzwRoEgr0Tnu2bQ?wt.mc_id=MVP_325642