Gestire lo stato tra i passaggi di esecuzione
Renda persistenti variabili, data frame e librerie importate tra più passaggi di esecuzione del codice, così che l'agente possa basarsi sui risultati precedenti senza ripetere i calcoli già eseguiti.
Gestire lo stato tra i passaggi di esecuzione è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Engineering Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Engineering Academy include 4 lezioni in totale.
Il problema della mancanza di stato
Ogni esecuzione di un container Docker avvia un interprete Python nuovo. Le variabili definite nell'iterazione 1 non esistono nell'iterazione 2. Questa assenza di stato costringe l'agente a ricalcolare o ricaricare tutto da zero a ogni passaggio di esecuzione, a meno che non implementi una strategia esplicita di gestione dello stato per conservarlo e ripristinarlo tra le iterazioni. Senza questa strategia, le analisi in più passaggi sono impossibili.
# 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!Persistenza dello stato basata sui file
L'approccio più semplice e portabile consiste nel salvare lo stato in file. Al termine di ogni blocco di codice, l'agente salva DataFrame, dizionari o altri oggetti in file nella directory dello spazio di lavoro. L'iterazione successiva li ricarica. Parquet è ideale per i DataFrame, JSON per i dizionari e pickle per oggetti Python arbitrari (anche se usare pickle con codice non attendibile comporta un rischio per la sicurezza).
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)Insegnare all'agente a usare lo stato basato sui file
Non è sufficiente implementare lo stato basato sui file nell'infrastruttura: anche l'LLM deve conoscere questa convenzione e usarla in modo coerente. Includa istruzioni esplicite nel prompt di sistema: dopo aver calcolato un risultato che servirà in seguito, lo salvi sempre con un nome file descrittivo e, all'inizio di ogni iterazione, carichi sempre i file prodotti nei passaggi precedenti prima di procedere.
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/'))
'''Stato basato sul kernel con processi Python persistenti
Un'alternativa allo stato basato sui file consiste nel mantenere in esecuzione un processo Python persistente (come un kernel Jupyter) tra un'iterazione e l'altra e nell'iniettare al suo interno ogni blocco di codice con exec(). Le variabili definite in un'iterazione rimangono accessibili in quella successiva. Questo approccio è più rapido e naturale, ma richiede l'esecuzione di un processo persistente per ogni sessione dell'agente anziché di container effimeri.
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()Gestire lo stato con un oggetto di stato
Per il livello di orchestrazione dell'agente (esterno alla sandbox), mantenga un oggetto di stato esplicito che tenga traccia dello stato corrente del lavoro dell'agente: quali file sono stati creati, cosa è stato calcolato, a quale passaggio si trova l'agente e qual è l'obiettivo finale. Passi all'LLM un riepilogo di questo oggetto di stato a ogni iterazione, così saprà sempre a che punto si trova nell'attività complessiva.
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 += 1Iniettare il contesto dello stato nei prompt
Ogni volta che chiama l'LLM per il blocco di codice successivo, inserisca il contesto corrente dello stato nel messaggio dell'utente. In questo modo l'LLM riceve informazioni accurate sui file disponibili, sui calcoli già eseguiti e sulle attività ancora da completare. Senza questo contesto, l'LLM potrebbe tentare di ricreare file già esistenti o saltare passaggi già completati.
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)Gestire grandi quantità di dati intermedi
Gli agenti per l'analisi dei dati producono spesso grandi dataset intermedi, il cui ricaricamento a ogni iterazione è costoso. Applichi il caricamento lazy: carichi solo i dati necessari per il passaggio corrente. Utilizzi formati colonnari come Parquet, che supportano letture efficienti limitate alle colonne necessarie. Per dataset davvero grandi (100 MB o più), mantenga un kernel persistente, così il DataFrame rimane in memoria tra le iterazioni invece di essere serializzato e deserializzato ogni volta.
# 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 accordinglyCreare checkpoint per le attività di lunga durata
Per le attività che si estendono su molte iterazioni, implementi il checkpointing: salvi periodicamente l'intero stato dell'agente (passaggi completati, file dello spazio di lavoro, avanzamento corrente) in un archivio durevole, così l'attività potrà essere ripresa dopo un'interruzione. Questo è particolarmente importante per i processi di analisi dei dati di lunga durata, che possono richiedere più di 30 minuti e venire interrotti da problemi di rete, errori dell'API o riavvii del server.
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 statePulire lo stato al termine
Gli spazi di lavoro degli agenti accumulano file e possono diventare molto grandi nel tempo. Implementi sempre una fase di pulizia da eseguire quando un'attività termina o non riesce: elimini i file intermedi (cleaned.parquet, tmp_output.csv) e conservi solo i file di output finali importanti per l'utente. Per l'archiviazione cloud, imposti policy del ciclo di vita che eliminino automaticamente i file dello spazio di lavoro dopo un determinato periodo di conservazione.
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]Versionamento dello stato per il debug
Quando un agente di codice produce risultati errati, deve poter ripercorrere la cronologia dell'esecuzione per individuare il punto in cui si è verificato l'errore. Implementi il versionamento dello stato salvando un'istantanea dello spazio di lavoro dopo ogni iterazione. In questo modo potrà riprodurre l'esecuzione dell'agente, esaminare gli stati intermedi e identificare l'iterazione esatta in cui si è verificato l'errore. Per risparmiare spazio, memorizzi solo le differenze (i file modificati).
Finestra di contesto e stato esterno
Nella progettazione degli agenti di codice esiste una tensione fondamentale tra l'inserimento dello stato nella finestra di contesto dell'LLM (immediato ma limitato) e la sua conservazione in un archivio esterno (illimitato ma da gestire esplicitamente). La strategia ottimale è la seguente: mantenga nel contesto i dati del passaggio corrente, conservi i risultati intermedi di grandi dimensioni nei file, mantenga nel contesto l'obiettivo dell'attività e il piano di alto livello e tenga sempre i dati grezzi su disco. L'LLM gestisce l'attività; il file system gestisce i dati.
Verifica rapida
Verifichi la Sua comprensione della gestione dello stato tra i passaggi di esecuzione del codice trattati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che la persistenza dello stato basata sui file, usando Parquet e JSON, è il modo più portabile per condividere lo stato tra esecuzioni effimere di container; i kernel persistenti eliminano il sovraccarico del ricaricamento mantenendo un processo Python attivo tra le iterazioni; e l'iniezione del contesto dello stato fornisce all'LLM informazioni accurate sui file disponibili e sui passaggi completati a ogni iterazione. Nella prossima lezione costruiremo un agente completo per l'analisi dei dati.
Domande Frequenti
La lezione «Gestire lo stato tra i passaggi di esecuzione» è gratuita?
Sì — il testo completo di «Gestire lo stato tra i passaggi di esecuzione» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Engineering Academy, passa a CoddyKit PRO. Il corso AI Engineering Academy include 4 lezioni in totale.
Cosa imparerò in «Gestire lo stato tra i passaggi di esecuzione»?
Renda persistenti variabili, data frame e librerie importate tra più passaggi di esecuzione del codice, così che l'agente possa basarsi sui risultati precedenti senza ripetere i calcoli già eseguiti. Eserciti AI Engineering Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare AI Engineering Academy?
Non è richiesta alcuna esperienza precedente. AI Engineering Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Gestire lo stato tra i passaggi di esecuzione»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione AI Engineering Academy?
Sì. Ogni lezione AI Engineering Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Il loop di esecuzione del codice
- Sandboxing con Docker e RestrictedPython
- Gestire lo stato tra i passaggi di esecuzione
- Creare un agente per l'analisi dei dati