压 实

随着聊天的增长,聊天历史记录的令牌计数可能会超过模型上下文窗口或增加成本。 压缩策略可减少聊天历史记录的大小,同时保留重要上下文,因此代理可以在长时间运行的交互中继续运行。

Important

压缩框架目前是实验性的。 若要使用它,需要添加 #pragma warning disable MAAI001

Important

压缩框架目前在 Python 中是实验性的。 从 agent_framework中导入压缩类型。

Go 支持通过 agent/compaction 包压缩。 将压缩策略注册为一种 agent.ContextProvider ,以便在每次运行之前压缩会话历史记录。

为什么压缩很重要

每次调用 LLM 都包括完整的对话历史记录。 不压缩:

  • 令牌限制 - 对话最终超过模型的上下文窗口,导致错误。
  • 成本 - 较大的提示消耗更多令牌,增加 API 成本。
  • 延迟 - 更多的输入令牌意味着响应时间变慢。

压缩通过选择性地删除、折叠或汇总对话的较旧部分来解决这些问题。

核心概念

适用性:仅限内存型历史记录代理程序

压缩仅适用于在内存中管理其自己的聊天历史记录的代理。 依赖于服务托管上下文或会话状态的代理不会从精简中受益,因为服务已经处理了上下文管理。 服务托管代理的示例包括:

  • Foundry 代理 - 上下文由 Microsoft Foundry 服务并行管理。
  • 启用了存储的响应 API (默认值) - 聊天状态由 OpenAI 服务存储和管理。
  • Copilot Studio 代理 - 聊天上下文由 Copilot Studio 服务维护。

对于这些代理类型,配置压缩策略不起作用。 仅当代理维护自己的内存中消息列表,并在每次调用时将完整历史记录传递给模型时,压缩才相关。

压缩对 MessageIndex 平面消息列表的结构化视图进行作,该视图将消息分组为称为 MessageGroup 实例的原子单元。 每个组跟踪其消息计数、字节计数和估计令牌计数。

消息组

一个 MessageGroup 表示必须一起保留或删除的逻辑相关消息。 例如,包含工具调用的助手消息及其相应的工具结果消息形成一个原子组,删除其中一个而不删除另一个就会导致 LLM API 错误。

每个组都有一个 MessageGroupKind

种类 Description
System 一个或多个系统消息。 在压缩期间始终保留。
User 启动新轮次的单个用户消息。
AssistantText 纯助理文本响应(无工具调用)。
ToolCall 带有工具调用和相应工具结果消息的助理消息,被视为原子单元。
Summary 汇总压缩生成的简化消息。

触发器

一个 CompactionTrigger 是用于评估是否应根据当前 MessageIndex 指标继续压缩的委托。

public delegate bool CompactionTrigger(MessageIndex index);

CompactionTriggers 类提供常见的工厂方法:

Trigger 触发时间
CompactionTriggers.Always 每次(无条件)。
CompactionTriggers.Never 从不(禁用压缩)。
CompactionTriggers.TokensExceed(maxTokens) 包含的标识符计数超过阈值。
CompactionTriggers.MessagesExceed(maxMessages) 包含的消息计数超过阈值。
CompactionTriggers.TurnsExceed(maxTurns) 包含的用户轮次计数超过阈值。
CompactionTriggers.GroupsExceed(maxGroups) 包含的组计数超过阈值。
CompactionTriggers.HasToolCalls() 至少有一个非排除的工具调用组存在。

将触发器与CompactionTriggers.All(...)(逻辑 AND)或CompactionTriggers.Any(...)(逻辑 OR)组合在一起。

// Compact only when there are tool calls AND tokens exceed 2000
CompactionTrigger trigger = CompactionTriggers.All(
    CompactionTriggers.HasToolCalls(),
    CompactionTriggers.TokensExceed(2000));

触发器与目标

每个策略都有两个谓词:

  • 触发器 — 控制压缩何时开始。 如果触发器返回 false,则完全跳过该策略。
  • 目标 —— 控制压缩 停止时机。 策略以增量方式排除组,并在每个步骤后重新评估目标,一旦目标返回 true,就会停止。

如果未指定目标,它将默认为触发器的反函数 — 一旦触发器条件不再触发,压缩就会停止。

压缩在一个对象的平面列表Message上进行操作。 消息被用轻量级的组元数据进行注释,并且策略会在投影消息列表到模型之前,直接在这些注释中进行修改,以标记某些组为排除状态。

消息组

消息分组为原子单元。 为每个组分配一个 GroupKind

种类 Description
system 系统消息。 在压缩期间始终保留。
user 单个用户消息。
assistant_text 纯助理文本响应(无函数调用)。
tool_call 具有函数调用的助手消息加上相应的工具结果消息,被视为原子单元。

压缩策略

CompactionStrategy 是一种协议 —— 任何可调用的对象接受一个 async,并在原地修改它,如果改变了任何内容,则返回 list[Message]

class CompactionStrategy(Protocol):
    async def __call__(self, messages: list[Message]) -> bool: ...

分词器

令牌感知策略接受 TokenizerProtocol 实现。 内置 CharacterEstimatorTokenizer 使用每令牌 4 个字符的启发式:

from agent_framework import CharacterEstimatorTokenizer

tokenizer = CharacterEstimatorTokenizer()

当您需要特定模型编码的准确令牌计数时,请传递自定义分词器。

压缩充当上下文提供程序。 策略可以在每次运行之前检查会话历史记录,并减少较旧的消息组,同时保留最新的上下文。

Go 包包括策略类型,例如 ToolResultStrategySlidingWindowStrategyPipelineStrategy,以及触发器辅助函数,例如 MessagesExceedTurnsExceed

压缩策略

所有策略都继承自抽象 CompactionStrategy 基类。 每个策略都会保留系统消息,并尊重一个 MinimumPreserved 可保护最新非系统组免受删除的底层。

压缩策略是从 agent_framework中导入的。

压缩策略是从 github.com/microsoft/agent-framework-go/agent/compaction中导入的。

TruncationCompactionStrategy

截断策略

最直接的方法:在满足目标条件之前删除最早的非系统消息组。

  • 尊重原子组边界(工具调用和结果消息一起删除)。
  • 最适合硬令牌预算的安全保护。
  • MinimumPreserved 默认为 32
// Drop oldest groups when tokens exceed 32K, keeping at least 10 recent groups
TruncationCompactionStrategy truncation = new(
    trigger: CompactionTriggers.TokensExceed(0x8000),
    minimumPreserved: 10);
  • 提供指标 tokenizer 时,指标为令牌计数;否则将包含消息计数。
  • preserve_system 默认为 True
from agent_framework import CharacterEstimatorTokenizer, TruncationStrategy

# Exclude oldest groups when tokens exceed 32 000, trimming to 16 000
truncation = TruncationStrategy(
    max_n=32_000,
    compact_to=16_000,
    tokenizer=CharacterEstimatorTokenizer(),
)

SlidingWindowCompactionStrategy

SlidingWindowStrategy(滑动窗口策略)

删除较旧的聊天内容以仅保留最近的交换窗口,尊重逻辑对话单元而不是任意消息计数。 系统消息将在整个过程中保留。

  • 最适合在可预测的范围内限制对话长度。

删除最早的用户 轮次 及其关联的响应组,在逻辑轮次边界而不是单个组上运行。

  • 轮次从用户消息开始,并包括所有后续助理和工具呼叫组,直到下一个用户消息。
  • MinimumPreserved 默认为 1 (至少保留最新的非系统组)。
// Keep only the last 4 user turns
SlidingWindowCompactionStrategy slidingWindow = new(
    trigger: CompactionTriggers.TurnsExceed(4));

仅保留最新的 keep_last_groups 非系统组,不包括较旧的所有内容。

  • preserve_system 默认为 True
from agent_framework import SlidingWindowStrategy

# Keep only the last 20 non-system groups
sliding_window = SlidingWindowStrategy(keep_last_groups=20)

工具结果压缩策略

将较旧的工具调用组折叠为压缩摘要消息,从而保留可读跟踪,而不会造成完整的消息开销。

  • 不更改用户消息或普通助手回应。
  • 最好作为初步策略,从冗长的工具结果中回收空间。
  • 将多消息工具呼叫组(助理呼叫 + 工具结果)替换为简短摘要,例如 [Tool calls: get_weather, search_docs]
  • MinimumPreserved 默认为 2,确保当前轮次的工具交互保持可见。
// Collapse old tool results when tokens exceed 512
ToolResultCompactionStrategy toolCompaction = new(
    trigger: CompactionTriggers.TokensExceed(0x200));
  • 折叠成紧凑的摘要消息,例如 [Tool results: get_weather: sunny, 18°C]
  • 最新的 keep_last_tool_call_groups 工具调用组保持不变。
from agent_framework import ToolResultCompactionStrategy

# Collapse all but the newest tool-call group
tool_result = ToolResultCompactionStrategy(keep_last_tool_call_groups=1)

汇总压缩策略

总结策略

使用 LLM 来汇总对话的较旧部分,并将其替换为单个摘要消息。

  • 默认提示会保留关键事实、决策、用户首选项和工具调用结果。
  • 需要单独的 LLM 客户端进行汇总 - 建议使用更小、更快的模型。
  • 最适合保留会话上下文,同时显著减少令牌计数。
  • 可以提供自定义摘要提示。
  • 保护系统消息和最新的 MinimumPreserved 非系统组(默认值: 4)。
  • 使用摘要提示将旧消息发送到单独的 IChatClient 环境,然后将摘要作为 MessageGroupKind.Summary 组插入。
// Summarize older messages when tokens exceed 1280, keeping the last 4 groups
SummarizationCompactionStrategy summarization = new(
    chatClient: summarizerChatClient,
    trigger: CompactionTriggers.TokensExceed(0x500),
    minimumPreserved: 4);

可以提供自定义摘要提示:

SummarizationCompactionStrategy summarization = new(
    chatClient: summarizerChatClient,
    trigger: CompactionTriggers.TokensExceed(0x500),
    summarizationPrompt: "Summarize the key decisions and user preferences only.");
  • 当包含的非系统消息计数超过 target_count + threshold时触发 。
  • 保留最近的 target_count消息,受消息组边界限制;汇总符合汇总器输入预算的最旧完整组。
  • 需要客户端 SupportsChatGetResponse
  • 默认情况下,将摘要生成器提示和脚本绑定到 8,000 个估计令牌。 它选择完整的邮件组,因此永远不会拆分组以适应预算。
  • 设置为 max_summary_input_tokens=None 禁用摘要生成器输入绑定,或在需要特定于模型的令牌计数时传递 tokenizer= 。 如果没有完整的组拟合,则会跳过汇总,并且现有历史记录保持不变。
from agent_framework import SummarizationStrategy

# Summarize when non-system message count exceeds 6, retaining the 4 newest
summarization = SummarizationStrategy(
    client=summarizer_client,
    target_count=4,
    threshold=2,
)

提供自定义摘要提示:

summarization = SummarizationStrategy(
    client=summarizer_client,
    target_count=4,
    prompt="Summarize the key decisions and user preferences only.",
)

独立于保留历史记录目标控制摘要生成器请求预算:

from agent_framework import CharacterEstimatorTokenizer, SummarizationStrategy

summarization = SummarizationStrategy(
    client=summarizer_client,
    target_count=4,
    threshold=2,
    max_summary_input_tokens=16_000,
    tokenizer=CharacterEstimatorTokenizer(),
)

PipelineCompactionStrategy

将多个策略组合到一个顺序管道中。 每个策略都基于上一个策略的结果进行操作,实现从柔和到强势的分层压缩。

  • 管道的触发器为 CompactionTriggers.Always — 每个子策略独立评估自己的触发器。
  • 策略按顺序执行,因此将最温柔的策略放在第一位。
PipelineCompactionStrategy pipeline = new(
    new ToolResultCompactionStrategy(CompactionTriggers.TokensExceed(0x200)),
    new SummarizationCompactionStrategy(summarizerChatClient, CompactionTriggers.TokensExceed(0x500)),
    new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(4)),
    new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(0x8000)));

此管线:

  1. 折叠旧工具结果(轻柔)。
  2. 总结较旧的对话内容(适度)。
  3. 仅保留最后 4 个用户轮次(激进)。
  4. 如果预算仍然超出限制(紧急后备措施),将移除最早的组。

选择性工具调用压缩策略

完全排除较旧的工具调用组,只保留最后 keep_last_tool_call_groups一个。

  • 不处理用户或纯助手消息。
  • 当工具聊天主宰令牌使用情况且不需要完整的工具历史记录时,最好。
from agent_framework import SelectiveToolCallCompactionStrategy

# Keep only the most recent tool-call group
selective_tool = SelectiveToolCallCompactionStrategy(keep_last_tool_call_groups=1)

TokenBudgetComposedStrategy

将多个策略组合到由令牌预算驱动的顺序流程中。 每个子策略按顺序执行,一旦预算得到满足,就会提前终止。 如果仅策略无法达到目标,则内置的回退机制将排除最旧的一组。

  • 策略按顺序执行;首先放置最温和的策略。
  • early_stop=True (默认值)在满足令牌预算后立即停止。
from agent_framework import (
    CharacterEstimatorTokenizer,
    SelectiveToolCallCompactionStrategy,
    SlidingWindowStrategy,
    SummarizationStrategy,
    TokenBudgetComposedStrategy,
    ToolResultCompactionStrategy,
)

tokenizer = CharacterEstimatorTokenizer()

pipeline = TokenBudgetComposedStrategy(
    token_budget=16_000,
    tokenizer=tokenizer,
    strategies=[
        ToolResultCompactionStrategy(keep_last_tool_call_groups=1),
        SummarizationStrategy(client=summarizer_client, target_count=4, threshold=2),
        SlidingWindowStrategy(keep_last_groups=20),
    ],
)

此管线:

  1. 折叠旧工具结果(轻柔)。
  2. 总结较旧的对话内容(适度)。
  3. 只保留最后 20 个组(咄咄逼人)。
  4. 如果仍然超过预算(紧急后备),则回退到最早的排除。

将压缩与代理程序配合使用

将压缩策略包装在一个 CompactionProvider 中,并将其注册为一个 AIContextProvider。 将单个策略或 PipelineCompactionStrategy 传递给构造函数。

注册在生成器 API 上

ChatClientBuilder上使用 UseAIContextProviders注册提供程序。 提供者在工具调用循环内运行,在每次调用 LLM 之前压缩消息。

IChatClient agentChatClient = openAIClient.GetChatClient(deploymentName).AsIChatClient();
IChatClient summarizerChatClient = openAIClient.GetChatClient(deploymentName).AsIChatClient();

PipelineCompactionStrategy compactionPipeline =
    new(
        new ToolResultCompactionStrategy(CompactionTriggers.TokensExceed(0x200)),
        new SummarizationCompactionStrategy(summarizerChatClient, CompactionTriggers.TokensExceed(0x500)),
        new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(4)),
        new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(0x8000)));

AIAgent agent =
    agentChatClient
        .AsBuilder()
        .UseAIContextProviders(new CompactionProvider(compactionPipeline))
        .BuildAIAgent(
            new ChatClientAgentOptions
            {
                Name = "ShoppingAssistant",
                ChatOptions = new()
                {
                    Instructions = "You are a helpful shopping assistant.",
                    Tools = [AIFunctionFactory.Create(LookupPrice)],
                },
            });

AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("What's the price of a laptop?", session));

Tip

对摘要聊天客户端使用更小、更便宜的模型 gpt-4o-mini来使成本降低,同时使摘要质量得以保持。

如果只需要一个策略,请直接将其传递给 CompactionProvider,而无需将其包装在 PipelineCompactionStrategy 中:

agentChatClient
    .AsBuilder()
    .UseAIContextProviders(new CompactionProvider(
        new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(20))))
    .BuildAIAgent(...);

通过 ChatClientAgentOptions 注册

还可以直接在ChatClientAgentOptions.AIContextProviders上指定提供程序。

AIAgent agent = agentChatClient
    .AsBuilder()
    .BuildAIAgent(new ChatClientAgentOptions
    {
        AIContextProviders = [new CompactionProvider(compactionPipeline)]
    });

注释

注册通过 ChatClientAgentOptions时,工具调用循环期间,CompactionProvider不参与。 代理级上下文提供程序在存储聊天历史记录之前运行,因此使用CompactionProvider时,生成ChatHistoryProvider的任何综合摘要消息都可以成为持久历史记录的一部分。 若要在保留原始存储历史记录的同时仅压缩进行中的请求上下文,请改为将提供程序注册在ChatClientBuilder上通过UseAIContextProviders(...)

专用压缩

CompactionProvider.CompactAsync 将策略应用于没有活动代理会话的任意消息列表:

IEnumerable<ChatMessage> compacted = await CompactionProvider.CompactAsync(
    new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(8000)),
    existingMessages);

直接配置 Agent所选压缩策略和 tokenizer。 代理级值会替代基础聊天客户端上配置的默认值,单个 agent.run(...) 调用可以再次替代这两个值。

向代理注册

from agent_framework import (
    Agent,
    CharacterEstimatorTokenizer,
    SlidingWindowStrategy,
    SummarizationStrategy,
    TokenBudgetComposedStrategy,
    ToolResultCompactionStrategy,
    TruncationStrategy,
)

tokenizer = CharacterEstimatorTokenizer()

strategy = TokenBudgetComposedStrategy(
    token_budget=16_000,
    tokenizer=tokenizer,
    strategies=[
        ToolResultCompactionStrategy(keep_last_tool_call_groups=1),
        SummarizationStrategy(client=summarizer_client, target_count=4, threshold=2),
        SlidingWindowStrategy(keep_last_groups=20),
    ],
)

agent = Agent(
    client=client,
    name="ShoppingAssistant",
    instructions="You are a helpful shopping assistant.",
    compaction_strategy=strategy,
    tokenizer=tokenizer,
)

session = agent.create_session()
print(await agent.run("What's the price of a laptop?", session=session))

Tip

对摘要客户端使用更小、更便宜的模型 gpt-4o-mini来降低成本,同时保持摘要质量。

如果只需要一个策略,请直接传递它:

agent = Agent(
    client=client,
    compaction_strategy=SlidingWindowStrategy(keep_last_groups=20),
    tokenizer=CharacterEstimatorTokenizer(),
)

替代一次运行的压缩

在一个请求需要不同的策略时传递 compaction_strategytokenizer 传入 agent.run(...)

response = await agent.run(
    "Summarize the rollout risks.",
    compaction_strategy=TruncationStrategy(max_n=8_000, compact_to=4_000),
    tokenizer=tokenizer,
)

专用压缩

apply_compaction 将策略应用于活动代理会话外部的任意消息列表:

from agent_framework import apply_compaction, TruncationStrategy, CharacterEstimatorTokenizer

tokenizer = CharacterEstimatorTokenizer()

compacted = await apply_compaction(
    messages,
    strategy=TruncationStrategy(
        max_n=8_000,
        compact_to=4_000,
        tokenizer=tokenizer,
    ),
    tokenizer=tokenizer,
)

创建压缩上下文提供程序并将其添加到代理的上下文提供程序:

import (
    "github.com/microsoft/agent-framework-go/agent"
    "github.com/microsoft/agent-framework-go/agent/compaction"
    "github.com/microsoft/agent-framework-go/provider/foundryprovider"
)

compactionProvider := compaction.NewContextProvider(compaction.ContextProviderConfig{
    Strategy: &compaction.PipelineStrategy{
        Strategies: []compaction.Strategy{
            &compaction.ToolResultStrategy{
                Trigger:                compaction.MessagesExceed(7),
                MinimumPreservedGroups: 4,
            },
            &compaction.SlidingWindowStrategy{
                Trigger:               compaction.TurnsExceed(4),
                MinimumPreservedTurns: 4,
            },
        },
    },
})

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Config: agent.Config{
        ContextProviders: []agent.ContextProvider{compactionProvider},
    },
})

Tip

请参阅压实管道示例,获取完整的可运行示例。

将压缩与 Harness 代理配合使用

上述手动设置可完全控制压缩提供程序运行的位置。 Harness Agent 而是将其每服务调用历史记录管道中的压缩连接起来,因此,长时间的工具调用循环可以在模型调用之间压缩。

压缩默认处于关闭状态。 HarnessAgentOptions.MaxContextWindowTokens设置和MaxOutputTokens创建默认值ContextWindowCompactionStrategy,或设置为CompactionStrategy提供自己的策略。

HarnessAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
    MaxContextWindowTokens = 128_000,
    MaxOutputTokens = 16_384,
});

该工具在函数调用循环中的每个模型调用之前运行已解析的策略。 当它还创建默认值 InMemoryChatHistoryProvider时,它会使用策略的聊天化简器配置该提供程序。 自定义 ChatHistoryProvider 用作提供。 DisableCompaction = true重写令牌设置;CompactionStrategyMaxOutputTokens在启用或禁用压缩时仍设置ChatOptions.MaxOutputTokens

chatClient.AsHarnessAgent(options)new HarnessAgent(chatClient, options) 使用相同的 HarnessAgentOptions

压缩默认处于关闭状态,除非 max_context_window_tokens 同时设置并 max_output_tokens 已设置,否则会提供自定义阶段策略。

agent = create_harness_agent(
    client,
    max_context_window_tokens=128_000,
    max_output_tokens=16_384,
)

使用令牌设置,该工具将重复使用这两个阶段的一个 ContextWindowCompactionStrategy 阶段:在工具循环中每个模型调用之前运行前的阶段运行,而后阶段压缩运行后的历史记录。 使用或after_compaction_strategy;为自定义标记器、history_provider=另一个历史记录存储disable_compaction=True设置tokenizer=或禁用这两个阶段来独立before_compaction_strategy替代这两个阶段。 max_output_tokens 还提供默认 max_tokens 聊天选项。

Harness 代理目前在 Go SDK 中不可用。 手动注册压缩上下文提供程序,如上所示。

选择策略

Strategy 攻击性 保留上下文 需要 LLM 最适用于
ToolResultCompactionStrategy 高 - 仅折叠工具结果 从冗长的工具输出中回收空间
SummarizationCompactionStrategy 中等 中 - 将历史记录替换为摘要 上下文很重要的长对话
SlidingWindowCompactionStrategy 低 - 下降整个轮次 硬轮次计数限制
TruncationCompactionStrategy 低 - 删除最早的组 紧急令牌预算后盾
PipelineCompactionStrategy 可配置性 取决于子策略 视情况而定 具有多种备选机制的分层压缩
Strategy 攻击性 保留上下文 需要 LLM 最适用于
ToolResultCompactionStrategy 高级 - 将工具结果折叠为摘要信息 从冗长的工具输出中回收空间
SelectiveToolCallCompactionStrategy 低-中 中等—完全排除旧的工具调用组 不再需要结果时删除工具历史记录
SummarizationStrategy 中等 中 - 将历史记录替换为摘要 上下文很重要的长对话
SlidingWindowStrategy 低 - 删除最早的组 固定组数量限制
TruncationStrategy 低 - 删除最早的组 紧急消息与令牌预算后援措施
TokenBudgetComposedStrategy 可配置性 取决于子策略 视情况而定 分层压实与令牌分配目标及多个回退机制
Strategy 攻击性 保留上下文 需要 LLM 最适用于
ToolResultStrategy 高 - 折叠冗长的工具结果 从冗长的工具输出中回收空间
SlidingWindowStrategy 低 - 保留最近的轮次 硬轮次计数限制
PipelineStrategy 可配置性 取决于子策略 视情况而定 具有多种备选机制的分层压缩

后续步骤