0Pricing
AI Engineering Academy · Leçon

Gérer l’état entre les étapes d’exécution

Conservez les variables, les trames de données et les bibliothèques importées entre plusieurs étapes d’exécution du code, afin que l’agent puisse s’appuyer sur les résultats précédents sans relancer les calculs déjà effectués.

Gérer l’état entre les étapes d’exécution est une leçon AI Engineering Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Engineering Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Engineering Academy comprend 4 leçons au total.

Le problème de l’absence d’état

Chaque exécution d’un conteneur Docker démarre avec un nouvel interpréteur Python. Les variables définies lors de l’itération 1 n’existent pas lors de l’itération 2. Cette absence d’état oblige l’agent à tout recalculer ou tout recharger depuis le début à chaque étape d’exécution, sauf si vous mettez en place une stratégie explicite de gestion de l’état pour le conserver et le restaurer entre les itérations. Sans cela, les analyses en plusieurs étapes sont impossibles.

# Iteration 1 - works fine
df = pd.read_csv('data.csv')  # df is in memory
df_cleaned = df.dropna()
print('Rows after cleaning:', len(df_cleaned))

# Iteration 2 - NEW container, df_cleaned is GONE
result = df_cleaned.groupby('category').sum()  # NameError: df_cleaned is not defined
print(result)  # This will fail!

Persistance de l’état basée sur des fichiers

L’approche la plus simple et la plus portable consiste à enregistrer l’état dans des fichiers. À la fin de chaque bloc de code, l’agent enregistre les DataFrames, dictionnaires ou autres objets dans des fichiers du répertoire de travail. L’itération suivante les recharge. Parquet convient parfaitement aux DataFrames, JSON aux dictionnaires et pickle aux objets Python quelconques (bien que pickle provenant d’un code non fiable représente un risque de sécurité).

import pandas as pd
import json
from pathlib import Path

WORKSPACE = Path('/workspace')

# Iteration 1: process and SAVE
df = pd.read_csv(WORKSPACE / 'raw_data.csv')
df_cleaned = df.dropna().reset_index(drop=True)
df_cleaned.to_parquet(WORKSPACE / 'cleaned.parquet')  # save for next iteration

stats = {'rows': len(df_cleaned), 'columns': list(df_cleaned.columns)}
with open(WORKSPACE / 'stats.json', 'w') as f:
    json.dump(stats, f)

print('Saved cleaned data:', len(df_cleaned), 'rows')

# Iteration 2: LOAD and continue
df_cleaned = pd.read_parquet(WORKSPACE / 'cleaned.parquet')  # restore state
with open(WORKSPACE / 'stats.json') as f:
    stats = json.load(f)
result = df_cleaned.groupby('category')['value'].sum()
print(result)

Apprendre à l’agent à utiliser un état basé sur des fichiers

Vous ne pouvez pas simplement implémenter l’état basé sur des fichiers dans votre infrastructure : le LLM doit également connaître cette convention et l’utiliser systématiquement. Incluez des instructions explicites dans l’invite système : après avoir calculé un résultat qui sera nécessaire ultérieurement, enregistrez-le toujours sous un nom de fichier descriptif et, au début de chaque itération, chargez toujours les fichiers produits lors des étapes précédentes avant de poursuivre.

STATE_MANAGEMENT_INSTRUCTIONS = '''
State persistence rules:
- You have a persistent workspace at /workspace/
- After computing any result you will need later, SAVE it to /workspace/
  - DataFrames: use .to_parquet('/workspace/name.parquet')
  - Dicts/lists: use json.dump to /workspace/name.json
  - Text: write to /workspace/name.txt
- At the start of each code block, LOAD the files you need from previous steps
- Use clear, descriptive filenames like 'cleaned_data.parquet', not 'tmp1.parquet'
- When listing files, use: import os; print(os.listdir('/workspace/'))
'''

État basé sur le noyau avec des processus Python persistants

Une autre solution que l’état basé sur des fichiers consiste à conserver un processus Python persistant (comme un noyau Jupyter) en fonctionnement entre les itérations et à y injecter chaque bloc de code avec exec(). Les variables définies lors d’une itération restent accessibles lors de la suivante. Cette approche est plus rapide et plus naturelle, mais elle nécessite l’exécution d’un processus persistant par session d’agent plutôt que de conteneurs éphémères.

import jupyter_client

class PersistentKernel:
    def __init__(self):
        km, self.kc = jupyter_client.manager.start_new_kernel(kernel_name='python3')
        self.kc.wait_for_ready(timeout=30)
        print('Kernel started')

    def execute(self, code: str, timeout=60) -> tuple[str, str]:
        msg_id = self.kc.execute(code)
        outputs, errors = [], []
        
        while True:
            msg = self.kc.get_iopub_msg(timeout=timeout)
            if msg['msg_type'] == 'stream':
                if msg['content']['name'] == 'stdout':
                    outputs.append(msg['content']['text'])
                else:
                    errors.append(msg['content']['text'])
            if msg['msg_type'] == 'status' and msg['content']['execution_state'] == 'idle':
                break
        
        return ''.join(outputs), ''.join(errors)

    def shutdown(self):
        self.kc.shutdown()

# Variables persist across execute() calls!
kernel = PersistentKernel()
kernel.execute('x = 42')  # x is now defined in kernel
output, _ = kernel.execute('print(x)')  # prints 42 - state persists!
kernel.shutdown()

Gérer l’état avec un objet d’état

Pour la couche d’orchestration de l’agent (en dehors du bac à sable), gérez un objet d’état explicite qui suit l’état actuel du travail de l’agent : les fichiers créés, les éléments calculés, l’étape en cours et l’objectif final. Transmettez un résumé de cet objet d’état au LLM à chaque itération afin qu’il sache toujours où il en est dans la tâche globale.

from dataclasses import dataclass, field
from typing import Any

@dataclass
class ExecutionState:
    task: str
    iteration: int = 0
    workspace_files: list[str] = field(default_factory=list)
    computed_values: dict[str, Any] = field(default_factory=dict)
    completed_steps: list[str] = field(default_factory=list)
    last_output: str = ''

    def to_context_summary(self) -> str:
        return f'''Current task: {self.task}
Iteration: {self.iteration}
Completed steps: {', '.join(self.completed_steps) or 'None yet'}
Workspace files: {', '.join(self.workspace_files) or 'None yet'}
Key values: {self.computed_values}
Last output: {self.last_output[:500]}'''

    def after_execution(self, output: str, step_name: str):
        import os
        self.workspace_files = os.listdir('/workspace')
        self.completed_steps.append(step_name)
        self.last_output = output
        self.iteration += 1

Injecter le contexte de l’état dans les invites

Chaque fois que vous appelez le LLM pour obtenir le prochain bloc de code, injectez le contexte actuel de l’état dans le message utilisateur. Le LLM dispose ainsi d’informations exactes sur les fichiers disponibles, les éléments calculés et ce qu’il reste à faire. Sans ce contexte, le LLM peut tenter de recréer des fichiers qui existent déjà ou ignorer des étapes déjà terminées.

def build_iteration_prompt(task: str, state: ExecutionState, last_observation: str) -> str:
    return f'''ORIGINAL TASK: {task}

CURRENT STATE:
{state.to_context_summary()}

LAST EXECUTION OUTPUT:
{last_observation}

What is the next step? Write Python code to continue. 
Remember:
- Load data from workspace files if needed
- Save any results you will need in future steps
- If the task is complete, say TASK COMPLETE and summarize the result'''

# Use in the main loop
for step_num in range(max_iterations):
    context = build_iteration_prompt(task, state, last_observation)
    response = llm.complete(messages + [{'role': 'user', 'content': context}])
    code = extract_code_block(response)
    if not code:
        break  # done
    output, err = execute_in_sandbox(code)
    state.after_execution(output, step_name=f'step_{step_num}')
    last_observation = format_observation(output, err)

Gérer les données intermédiaires volumineuses

Les agents d’analyse de données produisent souvent de grands jeux de données intermédiaires dont le rechargement à chaque itération est coûteux. Appliquez le chargement différé : ne chargez que les données nécessaires à l’étape en cours. Utilisez des formats en colonnes comme Parquet, qui permettent des lectures efficaces limitées à certaines colonnes. Pour les jeux de données véritablement volumineux (100 Mo ou plus), conservez un noyau persistant afin que le DataFrame reste en mémoire entre les itérations au lieu d’être sérialisé et désérialisé à chaque fois.

# Good: load only needed columns
df = pd.read_parquet('/workspace/full_data.parquet', columns=['date', 'revenue', 'region'])

# Bad: load everything even if you only need 2 columns
# df = pd.read_parquet('/workspace/full_data.parquet')  # loads 50 columns you don't need

# Good: filter early before loading full dataset
df = pd.read_parquet('/workspace/full_data.parquet', filters=[('region', '=', 'EMEA')])

# For very large files, tell the LLM about data shape upfront
df_info = {'shape': (1_000_000, 50), 'size_mb': 850, 'columns': [...]}
# Include df_info in state so LLM plans accordingly

Créer des points de contrôle pour les tâches longues

Pour les tâches qui s’étendent sur de nombreuses itérations, implémentez des points de contrôle : enregistrez périodiquement l’état complet de l’agent (étapes terminées, fichiers de l’espace de travail, progression actuelle) dans un stockage durable afin que la tâche puisse reprendre après une interruption. Cela est particulièrement important pour les longues tâches d’analyse de données, qui peuvent durer plus de 30 minutes et être interrompues par des problèmes réseau, des erreurs d’API ou des redémarrages du serveur.

import json
import time

def checkpoint_state(state: ExecutionState, checkpoint_id: str, db):
    data = {
        'task': state.task,
        'iteration': state.iteration,
        'completed_steps': state.completed_steps,
        'workspace_files': state.workspace_files,
        'timestamp': time.time()
    }
    db.execute(
        'INSERT INTO agent_checkpoints (id, state) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET state=excluded.state',
        (checkpoint_id, json.dumps(data))
    )
    print(f'Checkpoint saved at iteration {state.iteration}')

def resume_from_checkpoint(checkpoint_id: str, db) -> ExecutionState | None:
    row = db.execute('SELECT state FROM agent_checkpoints WHERE id = ?', (checkpoint_id,)).fetchone()
    if not row:
        return None
    data = json.loads(row[0])
    state = ExecutionState(task=data['task'])
    state.iteration = data['iteration']
    state.completed_steps = data['completed_steps']
    print(f'Resumed from iteration {state.iteration}')
    return state

Nettoyer l’état après l’achèvement

Les espaces de travail des agents accumulent des fichiers et peuvent devenir volumineux avec le temps. Implémentez toujours une phase de nettoyage qui s’exécute lorsqu’une tâche se termine ou échoue : supprimez les fichiers intermédiaires (cleaned.parquet, tmp_output.csv) et ne conservez que les fichiers de sortie finaux qui intéressent l’utilisateur. Pour le stockage infonuagique, définissez des politiques de cycle de vie qui suppriment automatiquement les fichiers de l’espace de travail après une période de conservation.

import shutil
from pathlib import Path

INTERMEDIATE_PATTERNS = ['*.parquet', 'tmp_*.csv', 'step_*.json', 'debug_*.txt']
FINAL_OUTPUT_PATTERNS = ['report.pdf', 'final_*.csv', 'summary.json']

def cleanup_workspace(workspace_dir: str, keep_final=True):
    workspace = Path(workspace_dir)
    final_outputs = []
    
    if keep_final:
        for pattern in FINAL_OUTPUT_PATTERNS:
            final_outputs.extend(workspace.glob(pattern))
        # Move final outputs to output directory
        output_dir = workspace.parent / 'outputs'
        output_dir.mkdir(exist_ok=True)
        for f in final_outputs:
            shutil.move(str(f), str(output_dir / f.name))
    
    # Delete the workspace
    shutil.rmtree(workspace_dir)
    print(f'Workspace cleaned up. Kept {len(final_outputs)} output files.')
    return [str(f) for f in final_outputs]

Versionner l’état pour le débogage

Lorsqu’un agent de code produit des résultats incorrects, vous devez retracer son historique d’exécution pour trouver l’origine du problème. Implémentez le versionnage de l’état en enregistrant un instantané de l’espace de travail après chaque itération. Vous pourrez ainsi rejouer l’exécution de l’agent, examiner les états intermédiaires et identifier l’itération exacte au cours de laquelle l’erreur s’est produite. Pour économiser de l’espace, ne stockez que les différences (les fichiers modifiés).

Fenêtre de contexte ou état externe

La conception des agents de code comporte une tension fondamentale entre le stockage de l’état dans la fenêtre de contexte du LLM (immédiat mais limité) et son stockage dans un stockage externe (illimité mais nécessitant une gestion explicite). La stratégie optimale consiste à conserver dans le contexte les données de l’étape actuelle, les grands résultats intermédiaires dans des fichiers, l’objectif de la tâche et le plan général dans le contexte, et les données brutes toujours sur le disque. Le LLM gère la tâche ; le système de fichiers gère les données.

Vérification rapide

Vérifiez votre compréhension de la gestion de l’état entre les étapes d’exécution du code présentée dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que la persistance de l’état basée sur des fichiers, avec Parquet et JSON, est la manière la plus portable de partager l’état entre des exécutions de conteneurs éphémères ; que les noyaux persistants éliminent le temps de rechargement en conservant un processus Python actif entre les itérations ; et que l’injection du contexte de l’état fournit au LLM des informations exactes sur les fichiers disponibles et les étapes terminées à chaque itération. Nous allons maintenant créer un agent complet d’analyse de données.

Questions Fréquemment Posées

La leçon « Gérer l’état entre les étapes d’exécution » est-elle gratuite ?

Oui — le texte complet de « Gérer l’état entre les étapes d’exécution » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Engineering Academy, passe à CoddyKit PRO. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Gérer l’état entre les étapes d’exécution » ?

Conservez les variables, les trames de données et les bibliothèques importées entre plusieurs étapes d’exécution du code, afin que l’agent puisse s’appuyer sur les résultats précédents sans relancer… Tu pratiques AI Engineering Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Engineering Academy ?

Aucune expérience préalable n'est requise. AI Engineering Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Gérer l’état entre les étapes d’exécution » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Engineering Academy ?

Oui. Chaque leçon AI Engineering Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. La boucle d’exécution du code
  2. Isoler l’exécution avec Docker et RestrictedPython
  3. Gérer l’état entre les étapes d’exécution
  4. Créer un agent d’analyse de données
← Retour à AI Engineering Academy