1. Che cos’è un run multistep e in cosa si differenzia da una richiesta «una tantum»
Quando lavoravate solo con ChatGPT App e con gli strumenti MCP, lo schema era piuttosto lineare: arrivava la richiesta dell’utente → GPT decideva di chiamare uno o più strumenti → restituivate la risposta all’utente. Questo si può ancora considerare «un unico passo logico», anche se all’interno dello strumento facevate qualcosa di più complesso.
Per un agente, run è obiettivo + una serie di passi. Non pensiamo più in termini di «un prompt — una risposta», ma vediamo il compito come un mini‑progetto che l’agente conduce dall’inizio alla fine.
La differenza si può vedere così:
| Tipo di interazione | Cosa fa il modello | Dove risiede la logica |
|---|---|---|
| Chiamata normale di uno strumento in ChatGPT App | Decide se chiamare lo strumento, inserisce gli argomenti, compone la risposta in base al risultato | La logica di business principale e la sequenza delle azioni — in un unico strumento o nel backend |
| Run agentico (Agents SDK) | Pianifica più passi, decide quando e quale tool chiamare, analizza i risultati intermedi, può rivedere il piano | La logica «come procedere verso l’obiettivo» è in parte nelle istruzioni di sistema dell’agente, in parte nasce nella «testa» del modello |
Qui è importante: non siete obbligati a delegare completamente la pianificazione al modello. Di solito viene fuori un approccio ibrido: codificate in modo rigido le fasi macro dello scenario (per esempio, «prima raccogliere i requisiti, poi selezionare i regali, poi preparare la scheda»), e dentro ciascuna fase lasciate all’agente una certa libertà di usare i propri strumenti.
Mini‑analogia
Una chiamata singola di uno strumento è come chiamare un corriere: «ritira un documento e portalo in ufficio».
Un run agentico multistep è come un assistente personale: «Preparami un regalo per un collega per il compleanno: scopri cosa gli piace, scegli alcune opzioni, verifica la consegna e raccogli tutto in una presentazione elegante». L’assistente decide autonomamente quali azioni compiere lungo il percorso.
Più avanti nella lezione vedremo anche come questi run multistep si inseriscono nello stack che già conoscete, Apps SDK → MCP → backend, così che per ChatGPT e per il widget la logica agentica appaia come un normale, pulito strumento MCP.
2. Come il modello pianifica da sé i passi: vista dall’alto
In termini di Agents SDK, è comodo rappresentare ogni run come una terna:
- Goal (obiettivo): descrizione testuale del compito, che finisce nelle istruzioni di sistema/utente dell’agente.
- Tools: insieme di strumenti disponibili con buone descrizioni e JSON Schema.
- State: storia dei passi e stato strutturato che conservate all’esterno (DB, Redis, qualsiasi storage).
Poi entra in gioco il ciclo di run: il modello guarda l’obiettivo e gli strumenti disponibili e a ogni passo decide:
- «Adesso ho informazioni sufficienti — posso restituire il risultato finale all’utente»;
- oppure «Devo chiamare lo strumento X con questi argomenti»;
- oppure «Ho ricevuto il risultato dello strumento, ora devo interpretarlo, filtrarlo e forse chiamarne un altro».
A livello di pseudocodice l’idea è questa (ricordate: è un modello mentale, non una API reale):
while (!done && steps < MAX_STEPS) {
const modelResponse = await callModel({
system: agentPolicy,
messages: history,
tools,
});
if (modelResponse.type === "tool_call") {
const toolResult = await callTool(modelResponse.toolName, modelResponse.args);
history.push({ role: "tool", content: toolResult });
} else {
// risposta finale
done = true;
return modelResponse.content;
}
steps++;
}
Nell’Agents SDK reale, tutto questo ciclo è già implementato e «nascosto» nella libreria. Voi descrivete l’agente in modo dichiarativo e l’SDK mette in loop il modello e gli strumenti finché non ottiene la risposta finale o non urta i limiti di passi/tempo.
Il compito dell’architetto è:
- formulare goal e istruzioni di sistema in modo che il modello pianifichi passi ragionevoli;
- costruire un set di strumenti senza sovrapposizioni di significato;
- impostare limitazioni su passi e tempo;
- riflettere su quali passi possono essere paralleli.
Quando abbiamo un obiettivo, strumenti e una rappresentazione dello stato, la domanda successiva è quali passi compiere per arrivare all’obiettivo. Non tutti i passi sono uguali: alcuni sono strettamente sequenziali, altri si possono parallelizzare.
3. Passi sequenziali e paralleli
Ora, avendo compreso il ciclo di run dell’agente, è importante capire quali tipi di passi esistono in un processo del genere. Nel workflow agentico ce ne sono due: sequenziali e paralleli.
Passi sequenziali
Sono quelli in cui il risultato del passo A è critico per il passo B. Per esempio, nel nostro GiftGenius didattico:
- Per prima cosa bisogna capire chi è il destinatario del regalo: collega, parente, età, interessi.
- Poi selezionare un insieme di candidati tramite lo strumento search_gifts.
- Poi filtrarli in base al budget e ai vincoli.
- Quindi formattare in modo gradevole le schede per il widget.
- E solo dopo, eventualmente, proporre il passaggio al checkout.
Ogni passo successivo dipende dai dati del precedente, quindi l’esecuzione è strettamente sequenziale.
In pseudocodice del comportamento dell’agente può apparire come «piano interno» del modello:
1. Porre domande all’utente su destinatario e budget
2. Chiamare lo strumento search_gifts(profile, budget)
3. Chiamare lo strumento filter_by_constraints(gifts, constraints)
4. Formare l’elenco finale e la descrizione
Il modello non scrive davvero un elenco simile in codice, ma possiamo indirizzarlo verso questa struttura tramite istruzioni di sistema, esempi di dialoghi e descrizioni degli strumenti.
Passi paralleli
A volte i passi possono essere eseguiti in modo indipendente. Ad esempio, vogliamo confrontare le proposte di regali da tre negozi contemporaneamente:
- search_gifts_amazon
- search_gifts_etsy
- search_gifts_local_store
Dal punto di vista dell’agente sono tre chiamate indipendenti degli strumenti, che si possono lanciare in parallelo per ridurre il tempo complessivo di risposta.
Nell’Agents SDK (e in generale nei framework agentici moderni) spesso c’è il supporto integrato per le chiamate parallele degli strumenti, se il modello in un singolo passo propone più chiamate contemporaneamente. Scenario canonico: il modello descrive nell’output l’elenco di tali chiamate, l’SDK le esegue in concorrenza, raccoglie i risultati e li inserisce come insieme di messaggi tool nel passo successivo del modello.
Dal punto di vista della pianificazione appare così:
// Step dell’agente: il modello ha deciso di chiamare tre strumenti
const calls = [
{ name: "search_gifts_amazon", args: {...} },
{ name: "search_gifts_etsy", args: {...} },
{ name: "search_gifts_local_store", args: {...} },
];
const results = await Promise.all(
calls.map(c => callTool(c.name, c.args))
);
// In seguito tutti i risultati vengono aggiunti al contesto prima del passo successivo del modello
Se avete scritto frontend in JS/TS, avete già incontrato l’idea delle richieste parallele: per esempio quando con Promise.all avviate più fetch() contemporaneamente. Ora la stessa idea compare all’interno del ciclo di run dell’agente, solo che la decisione su che cosa sia eseguibile in parallelo è in gran parte presa dal modello stesso.
4. Esempio di workflow per GiftGenius: passi, obiettivi e strumenti
Nella sezione sui passi sequenziali abbiamo già suddiviso intuitivamente il comportamento di GiftGenius in fasi. Ora formalizziamo lo stesso scenario multistep come workflow agentico: descriviamo l’obiettivo, i passi e li colleghiamo agli strumenti e alla configurazione dell’agente. Per ora non ci leghiamo a una API specifica dell’Agents SDK; descriveremo la struttura e aggiungeremo un po’ di TypeScript «condizionale» per fissare le idee.
Obiettivo (goal)
Definiamo l’obiettivo così:
Aiutare l’utente a scegliere 3–5 opzioni di regalo per un destinatario specifico, tenendo conto del budget, delle occasioni e dei vincoli di consegna, e restituire un elenco strutturato di schede regalo per il widget GiftGenius.
Passi principali
Descriviamo una versione minima in 4 passi:
- Raffinamento del contesto del destinatario
Obiettivo: raccogliere informazioni su chi riceverà il regalo (età, genere, interessi, relazione con chi regala), nonché budget e data dell’evento.
Strumenti: eventualmente nessuno, puro dialogo modello ↔ utente. - Ricerca e preselezione dei regali
Obiettivo: ottenere un campione «grezzo» di regali.
Strumenti: search_gifts(profile, budget) — tool che interroga il nostro catalogo/sistema di ricerca e restituisce un elenco di candidati. - Filtraggio e ranking
Obiettivo: scartare le opzioni non adatte (nessuna consegna nella regione, sforamento del budget, vincoli non rispettati) e ordinare per rilevanza.
Strumenti: filter_and_score_gifts(candidates, constraints) — strumento puro e idempotente. - Formattazione del risultato per il widget
Obiettivo: portare i dati in un formato comodo per la UI: titolo, descrizione breve, immagine, prezzo, CTA.
Strumenti: format_gift_cards(gifts) — può essere uno strumento «di codice» (generazione della struttura) oppure un LLM‑tool (testi estetici).
Come può apparire nella configurazione dell’agente
Immaginiamo di avere un costruttore di agenti (pseudocodice):
import { createAgent } from "@acme/agents-sdk";
import { tools } from "./gift-tools";
export const giftAgent = createAgent({
name: "gift-guru",
system: `
Sei l’agente GiftGenius; aiuti a scegliere i regali.
Obiettivo: proporre 3–5 opzioni che si possano davvero acquistare,
tenendo conto del profilo del destinatario, del budget e dei vincoli di consegna.
Prima chiarisci i dettagli importanti, poi usa gli strumenti di ricerca e filtraggio.
Non chiamare gli strumenti se non conosci ancora il budget o gli interessi chiave.
Concludi il lavoro quando hai un elenco chiaro di schede regalo.
`,
tools, // qui ci saranno search_gifts, filter_and_score_gifts, format_gift_cards
maxSteps: 12,
timeoutMs: 15000,
});
Notate alcuni dettagli:
- Nelle istruzioni di sistema indichiamo esplicitamente che l’agente deve prima chiarire i dettagli, e solo poi usare gli strumenti di ricerca. Questo riduce il rischio che il modello inizi a richiamare strumenti con un contesto troppo vago.
- Abbiamo limitato maxSteps, così l’agente non cade in loop infiniti.
- Il timeout timeoutMs serve affinché l’intero run non duri «mezza vita» dell’utente.
5. Auto‑orchestrazione da parte del modello: cosa «lasciare alla libertà del modello», e cosa fissare in modo rigido
Un agente è un equilibrio tra la libertà del modello e la struttura rigida che impostate.
Se date troppa libertà al modello e non definite confini, otterrete «caos creativo»: chiamate di tool superflue, passi ripetuti, cicli non ovvi. Se invece codificate tutto in modo rigido nel backend come un automa a stati finiti, il modello diventa un decoratore di testo, non un esecutore intelligente di compiti.
Cosa di solito si lascia al modello
Nel contesto di GiftGenius e scenari simili è ragionevole affidare al modello:
- la formulazione delle domande all’utente (come chiarire gli interessi, come chiedere in modo corretto il budget);
- la decisione su quando le informazioni sono sufficienti per avviare la ricerca;
- la scelta di quali strumenti usare all’interno di una fase (per esempio quale tool di ricerca negozio usare, se ce ne sono più di uno);
- la generazione di testi di descrizioni, spiegazioni, confronti.
Cosa è meglio fissare in modo rigido
Al contempo conviene fissare in anticipo:
- le fasi macro dello scenario («Raccolta informazioni» → «Ricerca» → «Filtraggio» → «Formattazione» → «Finale»);
- i limiti su passi e tempo;
- le condizioni in cui l’agente deve «fermarsi» e dire onestamente all’utente che il compito è irrisolvibile (per esempio, se il budget è 5 dollari ma serve un costoso gadget elettronico con consegna per domani);
- la politica di idempotenza degli strumenti e le strategie di retry.
Esempio ibrido: fasi come stato, dettaglio demandato al modello
Si può introdurre nello state dell’agente il campo phase, che può assumere i valori "collect_profile" | "search" | "filter" | "format" | "done". Allora il vostro backend (o lo stesso Agents SDK, se supporta una state machine personalizzata) controllerà quali strumenti sono disponibili in quale fase.
Pseudocodice:
type Phase = "collect_profile" | "search" | "filter" | "format" | "done";
interface GiftAgentState {
phase: Phase;
profile?: UserProfile;
candidates?: GiftCandidate[];
finalGifts?: GiftCard[];
}
L’istruzione di sistema per l’agente può includere una breve descrizione delle fasi, e nel codice limiterete l’elenco dei tools mostrati al modello in base alla fase corrente. Questo è un esempio di tool gating, trattato più in dettaglio nel modulo sul workflow.
6. Controllo dei loop infiniti e delle ripetizioni inutili
Se lasciate all’agente un ciclo di run incontrollato, prima o poi inizierà a comportarsi come uno studente alla vigilia della scadenza: «chiarire e riscrivere» all’infinito pur di non consegnare. Il nostro compito è non farlo bloccare.
Ci sono tre fonti tipiche dei loop infiniti:
- Il modello non è sicuro della risposta e continua a riformulare la stessa richiesta allo strumento con modifiche irrilevanti.
- Lo strumento restituisce stabilmente un errore o un risultato vuoto, e l’agente continua ostinatamente a «riprovare».
- L’agente resta incastrato tra due strumenti, chiamando prima l’uno poi l’altro, senza avvicinarsi alla risposta finale.
Limite dei passi (maxSteps)
Il meccanismo più semplice e obbligatorio è il limite sul numero di passi. Nella maggior parte delle implementazioni di Agents SDK potete indicare maxSteps all’avvio del run o nella configurazione dell’agente. Appena il limite viene raggiunto, l’SDK termina il run con uno status speciale (per esempio, aborted_by_max_steps). Poi decidete come mostrarlo all’utente.
In GiftGenius possiamo ritenere che una selezione adeguata di regali stia in ~10 passi (un paio di chiarimenti, un paio di ricerche, filtraggio, formattazione). Impostiamo, per esempio, 12–15 passi con margine e trattiamo con cura la situazione in cui si raggiunge il limite:
const run = await giftAgent.run({
input: userGoal,
maxSteps: 12, // sovrascriviamo il default
});
if (run.status === "max_steps_exceeded") {
// Mostriamo all’utente un messaggio onesto
}
Limite di tempo (timeout)
A volte il problema non è nel numero dei passi, ma nella durata complessiva. Gli strumenti possono essere lenti, la rete — instabile. È quindi utile indicare timeoutMs sia a livello della singola chiamata di tool, sia a livello dell’intero run.
Per esempio, potete decidere che:
- ogni chiamata a una API esterna (ricerca di regali presso un partner) non debba superare 3–5 secondi;
- l’intero run per la scelta dei regali debba rientrare in 15 secondi.
Se scatta il timeout, chiudete con garbo il run, magari mostrando all’utente un risultato parziale e una spiegazione onesta, tipo «alcune fonti non hanno risposto in tempo».
Rilevamento delle ripetizioni
Un pattern più avanzato (ma utile) è rilevare chiamate ripetute agli strumenti con gli stessi argomenti. Se vedete che l’agente ha chiamato tre volte di fila search_gifts(profile, budget) con gli stessi parametri, è il segnale che si è incastrato.
Potete aggiungere nello state un contatore delle chiamate per la chiave (toolName, argsHash) e, se il contatore supera la soglia, o:
- interrompere il run e restituire all’utente un errore comprensibile;
- oppure fornire al modello un’istruzione aggiuntiva: «hai già provato tre volte a chiamare questo strumento con gli stessi parametri, prova a cambiare strategia o chiedi all’utente».
Pseudocodice:
function shouldAbortToolCall(toolName: string, args: unknown, state: GiftAgentState) {
const key = `${toolName}:${hashArgs(args)}`;
const count = state.toolCallCounts[key] ?? 0;
if (count >= 3) return true;
state.toolCallCounts[key] = count + 1;
return false;
}
Dove hashArgs è una funzione deterministica di serializzazione degli argomenti (ad esempio, JSON.stringify con ordinamento delle chiavi).
7. Criteri chiari di completamento del compito
Una delle differenze chiave tra un agente «da giocattolo» e uno da produzione è la presenza di criteri di completamento chiari. Se mancano, il modello può o chiudere il compito troppo presto («ecco qualche regalo, poi ve la cavate voi»), o al contrario continuare a «migliorare» all’infinito.
In GiftGenius si può stabilire una regola semplice:
- L’agente si conclude quando ha da 3 a 5 regali con campi compilati: id, title, shortDescription, price, imageUrl, purchaseUrl, e questi hanno superato il filtraggio per budget e consegna.
- Se dopo un massimo di N tentativi di ricerca e filtraggio i regali idonei sono meno di 3, l’agente informa onestamente l’utente che non è stato possibile trovare opzioni adeguate e propone di aumentare il budget o allentare i vincoli.
Questi criteri possono essere codificati direttamente nelle istruzioni di sistema dell’agente e/o nella verifica del risultato dopo il run.
Esempio di controllo del risultato dopo il run:
if (run.status === "completed") {
const gifts = run.output.gifts; // poniamo che il nostro agente restituisca un JSON strutturato
if (!gifts || gifts.length < 3) {
// L’agente si è "concluso", ma il risultato è debole — si può:
// 1) mostrare una spiegazione onesta,
// 2) proporre all’utente di modificare i requisiti.
} else {
// Tutto ok — mostriamo il widget con i regali
}
}
È importante non aspettarsi dal modello una comprensione magica del successo di business. In quanto sviluppatori dovete formulare esplicitamente le condizioni del risultato «soddisfacente» e verificarle.
8. Dove si realizza l’orchestrazione: agente, backend, widget
Abbiamo già detto che l’orchestrazione può vivere su livelli diversi: nell’agente, nel backend, nel widget.
Dal punto di vista dei processi multistep la logica è approssimativamente questa.
L’agente (Agents SDK) è responsabile del workflow «mentale»:
- come suddividere l’obiettivo in passi;
- quali strumenti chiamare e in quale ordine;
- quali ulteriori domande porre all’utente.
Il backend di solito garantisce:
- l’implementazione degli strumenti (ricerca, filtro, commerce, ecc.);
- la conservazione dello stato e dei checkpoint;
- vincoli di business rigidi (limiti di budget, permessi, disponibilità per regione).
Il widget (Apps SDK) gestisce:
- la visualizzazione dell’avanzamento (stepper, barra di progresso, «passo 2 di 4»);
- i moduli di input;
- i dettagli di UX, come i pulsanti disabilitati quando non tutti i dati sono compilati.
Buona pratica: pensare così: l’agente dirige il lavoro degli strumenti e il dialogo, mentre il widget UI dirige l’esperienza visiva dell’utente. Si coordinano tramite dati strutturati (ToolOutput, agent run output).
9. Mini‑esempio di codice: avvio dell’agente multistep GiftGenius da uno strumento MCP
Ora, come promesso all’inizio della lezione, colleghiamo il nuovo concetto con lo stack già noto Apps SDK → MCP → backend e mostriamo un piccolo esempio di come uno strumento MCP possa avviare un run agentico.
Immaginate che nel vostro app/mcp/route.ts ci sia il tool run_gift_workflow, che:
- accetta la richiesta testuale dell’utente (il suo obiettivo);
- avvia l’agente giftAgent;
- restituisce un risultato strutturato per il widget.
Il codice è semplificato e indicativo, ma rende l’idea dell’integrazione:
// app/mcp/route.ts
import { server } from "@modelcontextprotocol/sdk/server";
import { z } from "zod";
import { giftAgent } from "@/agents/giftAgent";
server.registerTool(
"run_gift_workflow",
{
title: "Selezionare regali",
description: "Avvia un agente multistep per la scelta dei regali",
inputSchema: {
userGoal: z
.string()
.describe("Obiettivo dell’utente, ad es.: voglio un regalo per un collega fino a $50"),
},
},
async ({ userGoal }) => {
const run = await giftAgent.run({ // qui avviamo l’agente con 12 passi e timeout di 15 s
input: userGoal,
maxSteps: 12,
timeoutMs: 15000,
});
return {
status: run.status,
gifts: run.output?.gifts ?? [],
debug: run.debugInfo, // si può rimuovere in seguito
};
}
);
Poi ChatGPT App può chiamare questo MCP‑tool come qualsiasi altro, e il vostro widget GiftGenius può costruire la UI in base a gifts. Avete ottenuto un workflow multistep «sotto il cofano», mentre esternamente per ChatGPT tutto appare come un unico tool pulito.
10. Errori tipici nella progettazione di processi multistep
Errore n. 1: «Lasciamo che il modello se la cavi da solo, gli do solo tutti gli strumenti».
Quando all’agente è disponibile una decina di tools che si sovrappongono per significato, senza istruzioni di sistema e fasi chiare, il modello inizia a oscillare: chiamare la stessa cosa in modi diversi, duplicare richieste, entrare in cicli. Meglio investire nel design: dividere lo scenario in fasi, limitare l’elenco degli strumenti in ciascuna fase e dichiarare esplicitamente la strategia nel system prompt.
Errore n. 2: Assenza di limiti su passi e tempo.
Se non impostate maxSteps e timeout, in produzione avrete rapidamente run «vaganti» che consumano risorse e gli utenti non vedono nulla. I limiti non sono «opzionali», ma igiene di base. È importante gestire in modo sensato le situazioni di superamento dei limiti, non semplicemente cadere con un 500 silenzioso.
Errore n. 3: Nessun criterio esplicito di completamento.
Il modello termina il run quando gli sembra che «basti», ma la sua idea di «basta» può essere lontana dai requisiti di business. Se non formalizzate i criteri di successo (quanti regali, quali campi, quali filtri superati) e non li verificate, otterrete un UX instabile: oggi cinque ottime opzioni, domani una «così così» e tre duplicati.
Errore n. 4: Mancato tracciamento delle chiamate ripetute degli strumenti.
L’agente può bloccarsi nel pattern «ha ricevuto un errore → ha riformulato la richiesta di due parole → ha chiamato di nuovo lo stesso strumento». Se non tracciate le chiamate ripetute per (toolName, args), questi cicli resteranno invisibili finché non guarderete i log. Semplici contatori e hash degli argomenti aiutano molto.
Errore n. 5: Mescolare orchestrazione e logica di business in un unico strumento.
A volte si prova a nascondere un intero workflow dentro un unico tool MCP o funzione dell’agente: ricerca, filtro, formattazione e decisione. Alla fine l’agente perde di senso — il modello non può controllare il processo passo dopo passo, perdete trasparenza e possibilità di riutilizzare parti dello scenario. Meglio estrarre le fasi in tools autonomi e dare all’agente la loro composizione.
Errore n. 6: Assenza di collegamento con stato e checkpoint.
Un processo multistep senza salvataggio dello stato intermedio e dei checkpoint si trasforma in un fragile monolite: se qualcosa cade a metà, l’utente deve ricominciare da capo. È particolarmente critico per scenari in cui l’utente salta avanti e indietro tra i passi o torna dopo tempo. Usate uno state store, salvate fase, profilo, candidati e date all’agente la possibilità di riprendere dal punto giusto.
Errore n. 7: Ignorare il layer UX.
Talvolta gli sviluppatori si appassionano al workflow interno dell’agente e dimenticano che l’utente vede solo il widget e i messaggi in chat. Se nella UI non c’è un progresso comprensibile, stati «stiamo cercando i regali…», «stiamo filtrando le opzioni…», l’utente penserà che l’app «si sia bloccata» o «non stia facendo nulla», anche se l’agente sta orchestrando un processo complesso. Pianificando un run multistep, pensate da subito a come si rifletterà nell’interfaccia.
GO TO FULL VERSION