Partager via


Compactage

À mesure que les conversations grandissent, le nombre de jetons de l'historique des conversations peut dépasser les fenêtres contextuelles du modèle, ou bien entraîner une augmentation des coûts. Les stratégies de compactage réduisent la taille de l’historique des conversations tout en préservant un contexte important, afin que les agents puissent continuer à fonctionner sur des interactions longues.

Important

L’infrastructure de compactage est actuellement expérimentale. Pour l’utiliser, vous devez ajouter #pragma warning disable MAAI001.

Important

L’infrastructure de compactage est actuellement expérimentale en Python. Importer des stratégies à partir de agent_framework._compaction.

Pourquoi le compactage est important

Chaque appel à un LLM inclut l’historique complet des conversations. Sans compactage :

  • Limites de jetons : les conversations dépassent finalement la fenêtre de contexte du modèle, ce qui provoque des erreurs.
  • Coût : les invites plus volumineuses consomment davantage de jetons, ce qui augmente les coûts d’API.
  • Latence : plus de jetons d’entrée signifient des temps de réponse plus lents.

Le compactage résout ces problèmes en supprimant, en réduisant ou en récapitulant les parties plus anciennes de la conversation.

Concepts de base

Applicabilité : agents d’historique en mémoire uniquement

Le compactage s’applique uniquement aux agents qui gèrent leur propre historique de conversations en mémoire. Les agents qui s’appuient sur un contexte ou un état de conversation gérés par le service ne bénéficient pas du compactage, car ce dernier en assure déjà la gestion. Voici quelques exemples d’agents gérés par le service :

  • Agents Foundry : le contexte est géré côté serveur par le service Azure AI Foundry.
  • API de Réponses avec stockage activé (valeur par défaut) : l’état de conversation est stocké et géré par le service OpenAI.
  • Agents Copilot Studio : le contexte de conversation est géré par le service Copilot Studio.

Pour ces types d’agents, la configuration d’une stratégie de compactage n’a aucun effet. Le compactage n’est pertinent que lorsque l’agent conserve sa propre liste de messages en mémoire et passe l’historique complet au modèle sur chaque appel.

Le compactage fonctionne sur une MessageIndex — une vue structurée d'une liste plate de messages qui regroupe les messages en unités atomiques, appelées instances de MessageGroup. Chaque groupe suit son nombre de messages, son nombre d’octets et son nombre estimé de jetons.

Groupes de messages

Un MessageGroup représente les messages liés logiquement qui doivent être conservés ou supprimés ensemble. Par exemple, un message d’assistant contenant des appels d’outil et ses messages de résultat d’outil correspondants forment un groupe atomique : la suppression d’un message sans l’autre entraînerait des erreurs d’API LLM.

Chaque groupe a un MessageGroupKind:

Type Description
System Un ou plusieurs messages système. Est toujours conservé pendant le compactage.
User Message d’utilisateur unique qui démarre un nouveau tour.
AssistantText Réponse texte simple de l’Assistant (aucun appel d’outil).
ToolCall Un message d'assistant avec des appels d’outils et les messages de résultat de l’outil correspondants, traités en tant qu'unité atomique.
Summary Message condensé produit par un compactage de synthèse.

Triggers

Un CompactionTrigger est un délégué qui évalue si le compactage devrait se poursuivre en fonction des métriques actuelles MessageIndex.

public delegate bool CompactionTrigger(MessageIndex index);

La CompactionTriggers classe fournit des méthodes de fabrique courantes :

Déclencheur Se déclenche quand
CompactionTriggers.Always Chaque fois (sans condition).
CompactionTriggers.Never Jamais (désactive le compactage).
CompactionTriggers.TokensExceed(maxTokens) Le nombre de jetons inclus dépasse le seuil.
CompactionTriggers.MessagesExceed(maxMessages) Le nombre de messages inclus dépasse le seuil.
CompactionTriggers.TurnsExceed(maxTurns) Le nombre de tours d'utilisateur inclus dépasse le seuil.
CompactionTriggers.GroupsExceed(maxGroups) Le nombre de groupes inclus dépasse le seuil.
CompactionTriggers.HasToolCalls() Il existe au moins un groupe d’appels d’outil qui n’est pas exclu.

Combiner des déclencheurs avec CompactionTriggers.All(...) (AND logique) ou CompactionTriggers.Any(...) (OR logique) :

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

Déclencheur et cible

Chaque stratégie a deux prédicats :

  • Déclencheur : contrôle quand commence le compactage. Si le déclencheur retourne false, la stratégie est ignorée entièrement.
  • Cible : contrôle quand le compactage s’arrête. Les stratégies excluent de façon incrémentielle les groupes et réévaluent la cible après chaque étape, s’arrêtant dès que la cible retourne true.

Lorsqu’aucun objectif n’est spécifié, il est défini par défaut sur l’inverse du déclencheur : le compactage s’arrête dès que la condition du déclencheur n’est plus remplie.

Le compactage fonctionne sur une liste plate d’objets Message. Les messages sont annotés avec des métadonnées de groupe léger et les stratégies mutent ces annotations en place pour marquer les groupes comme exclus avant que la liste des messages ne soit projetée vers le modèle.

Groupes de messages

Les messages sont regroupés en unités atomiques. Chaque groupe est affecté à un GroupKind:

Type Description
system Messages système. Est toujours conservé pendant le compactage.
user Message d’utilisateur unique.
assistant_text Réponse de texte d’assistant brut (aucun appel de fonction).
tool_call Un message assistant avec des appels de fonction ainsi que les messages de résultat de l’outil correspondants, traités comme une unité atomique.

Stratégies de compactage

Un CompactionStrategy est un protocole : tout async appelable qui accepte un list[Message] et le modifie sur place, en retournant True lorsqu'il a modifié quoi que ce soit.

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

Générateur de jetons

Les stratégies conscientes des jetons acceptent une TokenizerProtocol implémentation. L'outil intégré CharacterEstimatorTokenizer utilise une heuristique de 4 caractères par jeton :

from agent_framework._compaction import CharacterEstimatorTokenizer

tokenizer = CharacterEstimatorTokenizer()

Transmettez un tokenizer personnalisé lorsque vous avez besoin d’un nombre de jetons précis pour l’encodage d’un modèle spécifique.

Stratégies de compactage

Toutes les stratégies héritent de la classe de base abstraite CompactionStrategy . Chaque stratégie préserve les messages systèmes et respecte un MinimumPreserved seuil qui protège les groupes non-systémiques les plus récents contre la suppression.

Les stratégies de compactage sont importées à partir de agent_framework._compaction.

StratégieDeCompactageParTroncature

StratégieDeTroncation

Approche la plus simple : supprime les groupes de messages non système les plus anciens jusqu’à ce que la condition cible soit remplie.

  • Respecte les limites des groupes atomiques (les messages d’appel d’outil et de résultat sont supprimés ensemble).
  • Idéal pour les backstops de budget de jetons durs.
  • MinimumPreserved a la valeur par défaut 32.
// Drop oldest groups when tokens exceed 32K, keeping at least 10 recent groups
TruncationCompactionStrategy truncation = new(
    trigger: CompactionTriggers.TokensExceed(0x8000),
    minimumPreserved: 10);
  • Lorsqu’un tokenizer est fourni, la métrique est le nombre de jetons ; sinon, la métrique est le nombre de messages.
  • preserve_system a la valeur par défaut True.
from agent_framework._compaction 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

Supprime le contenu de conversation plus ancien pour conserver uniquement la fenêtre la plus récente des échanges, respectant les unités de conversation logiques plutôt que le nombre de messages arbitraires. Les messages système sont conservés dans l’ensemble.

  • Idéal pour limiter la longueur des conversations de manière prévisible.

Supprime les utilisateurs les plus anciens et leurs groupes de réponse associés, fonctionnant sur des limites de tour logique plutôt que sur des groupes individuels.

  • Un tour commence par un message utilisateur et inclut tous les groupes d’assistants et d’appels d’outils suivants jusqu’au message utilisateur suivant.
  • MinimumPreserved utilise par défaut 1 (ce qui préserve au moins le groupe non système le plus récent).
// Keep only the last 4 user turns
SlidingWindowCompactionStrategy slidingWindow = new(
    trigger: CompactionTriggers.TurnsExceed(4));

Conserve uniquement les groupes non système les plus récents keep_last_groups , à l’exclusion de tout ce qui est plus ancien.

  • preserve_system a la valeur par défaut True.
from agent_framework._compaction import SlidingWindowStrategy

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

StratégieDeCompactionDesRésultatsOutil

Réduit les groupes d'appels d'outils plus anciens en messages récapitulatifs compacts, en conservant une trace lisible sans la lourdeur complète des messages.

  • Ne touche pas les messages utilisateur ni les réponses d’assistant simple.
  • Il est préférable d'adopter une stratégie préliminaire pour récupérer de l'espace à partir des résultats détaillés des outils.
  • Remplace les groupes d’appels d’outils multi-messages (appel d’assistant + résultats de l’outil) par un résumé court comme [Tool calls: get_weather, search_docs].
  • MinimumPreserved par défaut à 2, ce qui garantit que les interactions de l'outil actuel restent visibles.
// Collapse old tool results when tokens exceed 512
ToolResultCompactionStrategy toolCompaction = new(
    trigger: CompactionTriggers.TokensExceed(0x200));
  • Se réduit à des messages récapitulatifs compacts tels que [Tool results: get_weather: sunny, 18°C].
  • Les groupes d’appels d’outils les plus récents keep_last_tool_call_groups sont laissés inchangés.
from agent_framework._compaction import ToolResultCompactionStrategy

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

StratégieDeCompactageParRésumé

StratégieDeRésumé

Utilise un LLM pour synthétiser les anciennes parties de la conversation, en les remplaçant par un seul message de synthèse.

  • Une invite par défaut conserve les faits clés, les décisions, les préférences utilisateur et les résultats des appels d’outil.
  • Nécessite un client LLM distinct pour résumer : un modèle plus petit et plus rapide est recommandé.
  • Il est idéal pour préserver le contexte conversationnel tout en réduisant considérablement le nombre de tokens.
  • Vous pouvez fournir une commande de synthèse personnalisée.
  • Protège les messages système et les groupes non système les plus récents MinimumPreserved (par défaut : 4).
  • Envoie les anciens messages à un autre IChatClient avec une instruction de synthèse, puis insère le résumé en tant que groupe 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);

Vous pouvez fournir une demande de synthèse personnalisée :

SummarizationCompactionStrategy summarization = new(
    chatClient: summarizerChatClient,
    trigger: CompactionTriggers.TokensExceed(0x500),
    summarizationPrompt: "Summarize the key decisions and user preferences only.");
  • Se déclenche quand le nombre de messages non système inclus dépasse target_count + threshold.
  • Conserve les messages les plus target_count récents ; récapitule tout ce qui est plus ancien.
  • Nécessite un SupportsChatGetResponse client.
from agent_framework._compaction import SummarizationStrategy

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

Fournissez une invite de synthèse personnalisée :

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

StratégieDeCompactageDuPipeline

Compose plusieurs stratégies dans un pipeline séquentiel. Chaque stratégie opère sur le résultat de la précédente, permettant une compaction progressive par couches, allant de légère à agressive.

  • Le propre déclencheur du pipeline est CompactionTriggers.Always : chaque stratégie enfant évalue son propre déclencheur indépendamment.
  • Les stratégies s’exécutent dans l’ordre, donc mettre les stratégies les plus douces en premier.
PipelineCompactionStrategy pipeline = new(
    new ToolResultCompactionStrategy(CompactionTriggers.TokensExceed(0x200)),
    new SummarizationCompactionStrategy(summarizerChatClient, CompactionTriggers.TokensExceed(0x500)),
    new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(4)),
    new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(0x8000)));

Ce pipeline :

  1. Décroît les anciens résultats de l’outil (léger).
  2. Résume les anciennes étendues de conversation (modérées).
  3. Conserve uniquement les 4 dernières interactions utilisateur (mode agressif).
  4. Supprime les groupes les plus anciens s’ils dépassent encore le budget (mesure de secours d’urgence).

SelectiveToolCallCompactionStrategy

Exclut entièrement les anciens groupes d'appels d'outils, en conservant uniquement le dernier keep_last_tool_call_groups.

  • Ne touche pas aux messages d'utilisateur ou de l'assistant basique.
  • Convient mieux lorsque le phénomène de vibration de l'outil prédomine l'utilisation des jetons et que l'historique complet de l'outil n'est pas nécessaire.
from agent_framework._compaction import SelectiveToolCallCompactionStrategy

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

StratégieComposéeDeBudgetDeJetons

Compose plusieurs stratégies dans un pipeline séquentiel piloté par un budget de jeton. Chaque stratégie enfant s'exécute dans l'ordre et s'arrête dès que le budget est rempli. Un mécanisme de repli intégré exclut les groupes les plus anciens si les stratégies seules ne peuvent pas atteindre l'objectif.

  • Les stratégies s’exécutent dans l’ordre ; placez d’abord les stratégies les plus douces.
  • early_stop=True (valeur par défaut) s’arrête dès que le budget du jeton est satisfait.
from agent_framework._compaction 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),
    ],
)

Ce pipeline :

  1. Réduit les anciens résultats de l’outil (doux).
  2. Résume les anciens segments de conversation (modérée).
  3. Garde uniquement les 20 derniers groupes (agressifs).
  4. Revient à l'exclusion la plus ancienne si le budget est encore dépassé (mécanisme de sauvegarde d'urgence).

Utilisation du compactage avec un agent

Encapsulez une stratégie de compactage dans un CompactionProvider et enregistrez-la en tant que AIContextProvider. Passez soit une stratégie unique, soit un PipelineCompactionStrategy au constructeur.

Inscription auprès de l’API builder

Inscrivez le fournisseur sur ChatClientBuilder en utilisant UseAIContextProviders. Le fournisseur s’exécute à l’intérieur de la boucle d’appel d’outils, compactant les messages avant chaque appel 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));

Conseil / Astuce

Utilisez un modèle plus petit et moins cher (par exemple gpt-4o-mini) pour le client de conversation de synthèse afin de réduire les coûts tout en conservant la qualité de résumé.

Si une seule stratégie est nécessaire, transmettez-la directement à CompactionProvider sans l’encapsuler dans un PipelineCompactionStrategy:

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

Inscription à travers ChatClientAgentOptions

Le fournisseur peut également être spécifié directement sur ChatClientAgentOptions.AIContextProviders:

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

Note

Lors de l'enregistrement via ChatClientAgentOptions, le CompactionProvidern'est pas engagé pendant la boucle d’appel d’outils. Les fournisseurs de contexte au niveau de l’agent s’exécutent avant le stockage de l’historique des conversations. Par conséquent, tous les messages de synthèse synthétiques générés par CompactionProvider peuvent faire partie de l’historique persistant lors de l’utilisation ChatHistoryProvider. Pour compacter uniquement le contexte de demande en cours tout en conservant l’historique stocké d’origine, inscrivez le fournisseur sur le ChatClientBuilder par UseAIContextProviders(...) à la place.

Compactage ad hoc

CompactionProvider.CompactAsync applique une stratégie à une liste de messages arbitraires sans session d’agent active :

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

CompactionProvider est un fournisseur de contexte qui applique des stratégies de compactage avant et après l’exécution de chaque agent. Ajoutez-le en parallèle avec un fournisseur d'historique dans la liste de l'agent context_providers.

  • before_strategy — s’exécute avant l’appel du modèle, compactant les messages déjà chargés dans le contexte.
  • after_strategy — s’exécute après l’appel du modèle, en compactant les messages stockés par le fournisseur d’historique afin que le tour suivant démarre plus petit.
  • history_source_id — le source_id fournisseur d’historique dont les messages after_strategy stockés "in_memory"doivent être compactés (par défaut).

Inscription auprès d’un agent

from agent_framework import Agent, CompactionProvider, InMemoryHistoryProvider
from agent_framework._compaction import (
    CharacterEstimatorTokenizer,
    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),
    ],
)

history = InMemoryHistoryProvider()
compaction = CompactionProvider(
    before_strategy=pipeline,
    history_source_id=history.source_id,
)

agent = Agent(
    client=client,
    name="ShoppingAssistant",
    instructions="You are a helpful shopping assistant.",
    context_providers=[history, compaction],
)

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

Conseil / Astuce

Utilisez un modèle plus petit et moins cher (par exemple gpt-4o-mini) pour le client de synthèse afin de réduire les coûts tout en conservant la qualité de résumé.

Si une seule stratégie est nécessaire, transmettez-la directement sous la forme de before_strategy.

compaction = CompactionProvider(
    before_strategy=SlidingWindowStrategy(keep_last_groups=20),
    history_source_id=history.source_id,
)

Compression de l'historique persistant après chaque exécution

Permet after_strategy de compacter les messages stockés par le fournisseur d’historique afin que les futurs tours commencent par un contexte réduit :

compaction = CompactionProvider(
    before_strategy=SlidingWindowStrategy(keep_last_groups=20),
    after_strategy=ToolResultCompactionStrategy(keep_last_tool_call_groups=1),
    history_source_id=history.source_id,
)

Compactage ad hoc

apply_compaction applique une stratégie à une liste de messages arbitraire en dehors d’une session d’agent active :

from agent_framework._compaction 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,
)

Choix d’une stratégie

Stratégie Agressivité Conserve le contexte Nécessite LLM Idéal pour
ToolResultCompactionStrategy Faible Élevé : réduit uniquement les résultats de l’outil Non Récupération d’espace depuis la sortie verbeuse de l’outil
SummarizationCompactionStrategy Moyenne Moyen : remplace l’historique par un résumé Oui Conversations longues où le contexte importe
SlidingWindowCompactionStrategy Élevé Faible : supprime les cycles entiers Non Limites strictes de comptage de tours
TruncationCompactionStrategy Élevé Faible — supprime les groupes les plus vieux Non Backstops de budget de jeton d’urgence
PipelineCompactionStrategy Paramétrable Dépend des stratégies des enfants Ça dépend Compactage en couches avec plusieurs solutions de repli
Stratégie Agressivité Conserve le contexte Nécessite LLM Idéal pour
ToolResultCompactionStrategy Faible Élevé : réduit les résultats de l’outil en messages récapitulatifs Non Récupération d’espace de la sortie verbeuse de l’outil
SelectiveToolCallCompactionStrategy Faible-moyenne Moyenne : supprime complètement les anciens groupes d'appels d'outils Non Suppression de l’historique des outils lorsque les résultats ne sont plus nécessaires
SummarizationStrategy Moyenne Moyen : remplace l’historique par un résumé Oui Conversations longues où le contexte importe
SlidingWindowStrategy Élevé Faible : supprime les groupes les plus anciens Non Limites fixes du nombre de groupes
TruncationStrategy Élevé Faible : supprime les groupes les plus anciens Non Mesures de soutien pour message d'urgence ou budget de jetons
TokenBudgetComposedStrategy Paramétrable Dépend des stratégies des enfants Ça dépend Compactage en couches avec un objectif de budget de jetons et plusieurs solutions de repli

Étapes suivantes