Stratégies de contexte pour les agents
Mis à jour le 28 juillet 2026
Le défi du contexte pour les agents
Un agent autonome enchaîne des dizaines d’appels API : il réfléchit, utilise des outils, lit des résultats, et itère. À chaque étape, le contexte grandit. La différence avec une conversation ordinaire est que la croissance ne vient pas de l’utilisateur mais de l’agent lui-même : une requête SQL qui retourne trois cents lignes, une page web récupérée en entier, un fichier de logs lu jusqu’au bout. Sans stratégie de gestion, un agent atteint la limite de contexte en quelques itérations, souvent avant d’avoir terminé la tâche qu’on lui a confiée.
Concevoir un agent qui gère efficacement sa mémoire revient donc à trancher trois questions : ce qui doit rester intact, ce qui peut être résumé, et ce qui n’aurait jamais dû entrer dans le contexte. Nous les prendrons dans cet ordre.
Une mémoire à trois niveaux
La structure retenue s’inspire de la distinction classique entre mémoire de travail, mémoire épisodique et mémoire sémantique. La mémoire de travail contient les dernières actions dans leur forme brute, parce que l’agent en a besoin telles quelles pour décider de la suite. La mémoire épisodique conserve des résumés des étapes passées : l’agent sait qu’il a exploré une piste et ce qu’elle a donné, sans relire le détail. La mémoire sémantique, enfin, accumule les faits établis une fois pour toutes — l’identifiant du client, la version du schéma de base, la contrainte budgétaire — qui ne doivent jamais être perdus dans un résumé.
from dataclasses import dataclass, field
@dataclass
class MemoireAgent:
"""Système de mémoire à trois niveaux pour un agent."""
# Niveau 1 : mémoire de travail (contexte courant)
travail: list[dict] = field(default_factory=list)
# Niveau 2 : mémoire épisodique (résumés des étapes passées)
episodes: list[str] = field(default_factory=list)
# Niveau 3 : mémoire sémantique (faits permanents)
faits: list[str] = field(default_factory=list)
max_tokens_travail: int = 20_000
max_episodes: int = 10
def ajouter_action(self, action: str, resultat: str):
"""Ajoute une action à la mémoire de travail."""
self.travail.append({
"action": action,
"résultat": resultat[:2000], # Tronquer les résultats longs
})
self._verifier_compaction()
def ajouter_fait(self, fait: str):
"""Ajoute un fait permanent."""
if fait not in self.faits:
self.faits.append(fait)
def _verifier_compaction(self):
tokens = sum(
len(str(e)) // 4 for e in self.travail
)
if tokens > self.max_tokens_travail:
self._compacter()
def _compacter(self):
"""Compacte la mémoire de travail en épisode."""
import openai
client = openai.OpenAI()
texte = "\n".join(
f"Action: {a['action']}\nRésultat: {a['resultat']}"
for a in self.travail[:-2]
)
response = client.responses.create(
model="gpt-5.6-terra",
input=(
"Résumez ces actions d'agent en 3-5 points clés. "
"Gardez les faits, résultats et décisions importants.\n\n"
f"{texte}"
),
max_output_tokens=300,
)
self.episodes.append(response.output_text)
self.travail = self.travail[-2:] # Garder les 2 dernières actions
# Limiter le nombre d'épisodes
if len(self.episodes) > self.max_episodes:
self.episodes = self.episodes[-self.max_episodes:]
def construire_contexte(self) -> str:
"""Construit le contexte complet pour le prochain appel."""
parties = []
if self.faits:
parties.append(
"Faits établis :\n" + "\n".join(f"- {f}" for f in self.faits)
)
if self.episodes:
parties.append(
"Historique résumé :\n" + "\n---\n".join(self.episodes)
)
if self.travail:
parties.append(
"Actions récentes :\n" + "\n".join(
f"- {a['action']} → {a['resultat'][:500]}"
for a in self.travail
)
)
return "\n\n".join(parties)
Deux paramètres méritent d’être discutés avant d’être copiés tels quels. La troncature à 2000 caractères dans ajouter_action protège contre l’outil bavard, mais elle coupe aveuglément : si vos outils retournent du JSON, vous obtiendrez un objet invalide au milieu du contexte. Et max_episodes: 10 impose une amnésie bornée — au onzième résumé, le premier disparaît. Sur un agent qui travaille longtemps, remontez les faits importants vers faits avant qu’ils ne sortent de la fenêtre épisodique, sinon vous les perdrez définitivement.
Discipliner les résultats d’outils
Les outils retournent souvent des résultats volumineux : pages web, résultats SQL, contenus de fichiers. C’est de loin la première cause de saturation du contexte d’un agent, et c’est aussi la plus facile à corriger, parce qu’elle se traite à l’entrée plutôt qu’après coup.
La fonction ci-dessous propose trois stratégies de troncature. Le mode debut convient à un document dont l’essentiel est en tête, comme un article ou un rapport. Le mode debut_fin sert quand la fin porte l’information décisive : une trace d’exécution où l’erreur apparaît en dernier, un fichier de logs, une réponse paginée dont le total figure à la fin. Le mode resume coûte un appel API supplémentaire, ce qui ne se justifie que pour des contenus longs et vraiment utiles à la suite du raisonnement.
def tronquer_resultat_outil(
resultat: str,
max_tokens: int = 2000,
strategie: str = "debut_fin",
) -> str:
"""Tronque un résultat d'outil en préservant l'information clé."""
tokens_estimes = len(resultat) // 4
if tokens_estimes <= max_tokens:
return resultat
max_chars = max_tokens * 4
match strategie:
case "debut":
return resultat[:max_chars] + "\n[... tronqué]"
case "debut_fin":
moitie = max_chars // 2
return (
resultat[:moitie]
+ "\n[... milieu tronqué ...]\n"
+ resultat[-moitie:]
)
case "resume":
import openai
client = openai.OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
input=f"Résumez ces données en gardant l'essentiel :\n{resultat}",
max_output_tokens=max_tokens,
)
return response.output_text
return resultat[:max_chars]
Un résultat tabulaire demande un traitement particulier. Couper un tableau SQL en son milieu retire l’en-tête ou fausse la perception du volume : l’agent voit vingt lignes et conclut que la table en contient vingt. Le filtre suivant conserve l’en-tête, un échantillon de tête et de queue, et — c’est le point essentiel — annonce explicitement le nombre de lignes omises et le total. L’agent sait alors qu’il regarde un extrait, et peut décider de relancer une requête agrégée plutôt que de raisonner sur un échantillon.
def filtrer_resultat_sql(resultat: str, question: str) -> str:
"""Filtre un résultat SQL pour ne garder que le pertinent."""
lignes = resultat.strip().split("\n")
if len(lignes) <= 20:
return resultat
# Garder l'en-tête et les premières/dernières lignes
en_tete = lignes[0]
premieres = lignes[1:6]
dernieres = lignes[-5:]
return (
f"{en_tete}\n"
+ "\n".join(premieres)
+ f"\n[... {len(lignes) - 11} lignes omises ...]\n"
+ "\n".join(dernieres)
+ f"\n\nTotal : {len(lignes) - 1} lignes"
)
Quand plusieurs agents travaillent ensemble
Quand plusieurs sous-agents travaillent en parallèle, la tentation est de leur donner à tous le contexte complet, par prudence. C’est le meilleur moyen de multiplier la facture par le nombre d’agents tout en dégradant leurs performances, un contexte encombré rendant le modèle moins précis sur sa propre tâche. Partagez donc le contexte minimal : l’objectif global pour que l’agent comprenne à quoi il contribue, son rôle propre, les contraintes communes, et un extrait borné des résultats produits par ses pairs.
@dataclass
class ContextePartage:
"""Contexte partagé entre sous-agents."""
objectif: str
contraintes: list[str]
resultats_partiels: dict[str, str] = field(default_factory=dict)
def contexte_pour_sous_agent(self, role: str) -> str:
"""Génère un contexte ciblé pour un sous-agent."""
return (
f"Objectif global : {self.objectif}\n"
f"Votre rôle : {role}\n"
f"Contraintes : {', '.join(self.contraintes)}\n"
f"Résultats des autres agents :\n"
+ "\n".join(
f"- {k}: {v[:200]}"
for k, v in self.resultats_partiels.items()
)
)
La limite à 200 caractères par résultat partiel traduit une intention : un sous-agent doit savoir ce que les autres ont trouvé, pas comment ils l’ont trouvé. Si vos agents ont besoin du détail complet des résultats voisins, c’est probablement que le découpage des rôles est à revoir.
Points clés à retenir
- Structurez la mémoire de l’agent en trois niveaux : travail, épisodique, sémantique
- Compactez automatiquement quand la mémoire de travail dépasse le seuil
- Tronquez les résultats d’outils avant de les injecter dans le contexte
- Signalez toujours qu’un résultat a été tronqué, et de combien
- Pour les multi-agents, partagez un contexte minimal et ciblé par rôle