Stratégies de contexte pour les agents
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. Sans stratégie de gestion, un agent atteint la limite de contexte en quelques itérations. Cette leçon vous montre comment concevoir des agents qui gèrent efficacement leur mémoire.
Architecture mémoire d’un agent
Mémoire à trois niveaux
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,
"resultat": 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.3",
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)
Stratégies pour les résultats d’outils
Tronquer intelligemment
Les outils retournent souvent des résultats volumineux (pages web, résultats SQL, fichiers). Tronquez-les intelligemment :
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.3",
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]
Filtrer les résultats avant injection
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"
)
Gestion du contexte multi-agents
Quand plusieurs sous-agents travaillent en parallèle, partagez le contexte minimal :
@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()
)
)
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
- Pour les multi-agents, partagez un contexte minimal et ciblé par rôle