1. Perché misurare un workflow
In breve: senza analitica vivete in modalità «mi sembra», non in modalità «so».
Nelle lezioni precedenti di questo modulo abbiamo già scomposto lo scenario in step, assegnato i ruoli a GPT, al widget e all’MCP, discusso il tool‑gating e la persistenza dello stato tra gli step. Ora guardiamo alla stessa costruzione con gli occhi dell’analitica: tutto funziona come avevamo previsto e dove, in realtà, gli utenti si bloccano.
Nel web tradizionale tutti sono abituati ai funnel: quante persone arrivano sul landing, quante mettono il prodotto nel carrello, quante arrivano al pagamento. In ChatGPT App è lo stesso, solo che al posto delle pagine ci sono gli step del workflow e invece del «clic sul pulsante Acquista» c’è una combinazione di battuta dell’utente, chiamata di uno strumento e interazione con il widget.
Quando costruite uno scenario complesso senza metriche, non vedete:
- a quale step gli utenti «abbandonano» più spesso;
- dove rimangono bloccati e leggono per un minuto (o semplicemente sono andati a prendere un tè e non sono tornati);
- quale step non porta alcun valore e solo irrita;
- come le modifiche ai prompt o al tool gating influenzano il comportamento.
L’obiettivo dell’analitica per step è semplice: aumentare la quota di scenari completati, ridurre il time‑to‑result e diminuire il numero di errori e richieste al supporto.
Da questo momento il workflow non è solo un oggetto architetturale o una quest UX: è anche qualcosa di misurabile, con numeri.
2. Il funnel dello scenario in ChatGPT App
Nel web classico il funnel è lineare: Landing → Product → Cart → Checkout. In ChatGPT App l’immagine è un po’ più vivace: l’utente può «saltare» uno step a parole, il modello talvolta può saltarlo, e il widget e il testo del dialogo possono andare fuori sincrono.
Tuttavia l’idea di base è la stessa: abbiamo una sequenza di step e a ciascuno una parte degli utenti prosegue e un’altra no.
Prendiamo il nostro GiftGenius:
- collect_recipient — ChatGPT e il widget raccolgono i dati di base sul destinatario (sesso, età, relazione, interessi).
- collect_budget — rifiniamo il budget e la valuta.
- suggest_ideas — MCP / agente seleziona le idee e restituisce nel widget le card dei regali.
- review_selection — l’utente mette like/nasconde le idee e sceglie 1–2 preferiti.
- checkout — si crea un commerce intent e si effettua l’ordine.
In forma di funnel si può disegnare così:
flowchart TD
A[Inizio workflow] --> B["1\. Destinatario"]
B --> C["2\. Budget"]
C --> D["3\. Idee regalo"]
D --> E["4\. Scelta del regalo"]
E --> F["5\. Checkout"]
Ma è importante ricordare: l’utente può scrivere in chat «Andiamo subito al pagamento» o «Fammi vedere prima le opzioni costose», e il modello può decidere di saltare alcuni step. Perciò l’analitica per step in ChatGPT App non riguarda solo le schermate UI, ma anche il comportamento del modello: quali step vengono davvero attraversati, in quale ordine e chi ha iniziato la transizione — utente, widget o GPT.
3. Metriche di base per step
Partiamo dalla classica product analytics e adattiamola un po’ a ChatGPT App.
Per ogni workflow servono almeno quattro indicatori di base.
Per comodità riassumiamoli in una tabella:
| Metrica | Significato | Domanda tipica |
|---|---|---|
| Start rate | Quanti utenti hanno avviato lo scenario | La nostra App viene mostrata a qualcuno? |
| Completion rate | Quanti utenti sono arrivati fino alla fine | Quanto lo scenario porta al risultato? |
| Conversion per step | Quota di utenti passati dallo step N allo step N+1 | Quale step perde più utenti? |
| Drop-off per step | Quota di utenti che abbandonano allo step N | A quale step gli utenti rinunciano più spesso? |
Di solito a queste si aggiungono metriche di sforzo:
- tempo medio per step (dove gli utenti «si incagliano»);
- numero di interazioni nello step (quanti messaggi/click sono serviti);
- quota di step terminati con errore o che hanno richiesto un nuovo tentativo.
Nel contesto degli scenari LLM si aggiungono cose ancora più specifiche, come l’accuratezza della scelta degli strumenti da parte del modello o la quota di risposte «allucinatorie» in uno step specifico; ma è materiale avanzato e ci arriveremo nei moduli finali.
Per gli scenari di commerce, sopra gli step si aggiungono metriche di business:
- conversione al pagamento dal momento di avvio del workflow;
- conversione al pagamento a partire da uno specifico step (per esempio da «selezione idee»);
- valore medio dell’ordine;
- quota di cancellazioni/resi.
Importante: questi numeri non vivono da soli, tra loro c’è una storia causale. Uno step con alto drop‑off non è sempre «cattivo»: forse filtra gli utenti non adatti e poi proseguono solo quelli per cui lo scenario è davvero utile. Quindi l’analitica non è solo «calcolare percentuali», ma saper raccontare una storia con i dati.
Per poter calcolare percentuali e funnel, servono eventi grezzi: chi, quando e quale step ha superato (o non ha superato). Nel prossimo paragrafo concordiamo il formato di tali eventi.
4. Come appaiono gli eventi di analitica
Prima di scrivere il codice, bisogna concordare il formato dell’«evento» (event) che invieremo dal widget e dal backend.
Di solito un evento di analitica contiene:
- chi: identificatore dell’utente o almeno della sessione;
- quale workflow e quale versione;
- quale step;
- che cosa è successo (tipo di evento);
- se è andato a buon fine, quanto tempo ha richiesto;
- un po’ di metadati (locale, device, ecc.).
Uno schema semplificato degli eventi per il workflow può essere descritto così:
export type WorkflowEventType =
| "workflow_started"
| "workflow_finished"
| "step_started"
| "step_completed"
| "step_failed";
export interface WorkflowAnalyticsEvent {
eventId: string; // uuid
timestamp: string; // stringa ISO
userId?: string; // se è possibile deanonimizzare
conversationId?: string; // id del dialogo ChatGPT (se disponibile)
workflowId: string; // nostro identificatore interno
workflowType: "gift_selection";
workflowVersion: string; // ad esempio, "1.2.0" o "1.2.0-A"
stepName?: string; // collect_budget, suggest_ideas ecc.
eventType: WorkflowEventType;
toolName?: string; // se relativo a una tool-call
success?: boolean;
errorCode?: string | null;
durationMs?: number;
metadata?: Record<string, unknown>;
}
Alcune note:
Primo, workflowVersion è molto importante se intendete fare A/B test: senza di essa non saprete mai quale variante dello scenario produce numeri migliori.
Secondo, conversationId o un altro correlation ID consente di collegare gli eventi: gli step nel widget, le chiamate degli strumenti nell’MCP e il dialogo testuale. Nei moduli successivi parleremo ancora di tracing e osservabilità, ma l’abitudine di pensare da subito agli identificatori end‑to‑end è molto utile.
Terzo, non serve portare nell’evento tutto e di più: testi completi dei messaggi, e‑mail, indirizzi e altre PII è meglio evitarli o anonimizzarli rigorosamente — ne parleremo più avanti.
5. Strumentazione nel widget (Next.js + Apps SDK)
E ora la parte interessante: come fare in modo che il nostro widget GiftGenius invii in modo trasparente gli step mentre l’utente percorre lo scenario.
Supponiamo che nelle lezioni precedenti abbiate già fatto qualcosa del genere:
// components/GiftWizard.tsx
type StepId = "recipient" | "budget" | "ideas" | "review" | "checkout";
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
// ... qui si renderizzano i vari step
}
Aggiungiamo un piccolo «strato di analitica» sotto forma di hook.
Hook useWorkflowAnalytics
Creiamo un wrapper che conosce workflowId, workflowVersion e sa inviare un evento alla nostra route API Next.js /api/workflow-analytics.
// lib/useWorkflowAnalytics.ts
import { useCallback } from "react";
import type { WorkflowAnalyticsEvent, WorkflowEventType } from "./types";
const WORKFLOW_VERSION = "1.0.0";
export function useWorkflowAnalytics(
workflowId: string,
workflowType: WorkflowAnalyticsEvent["workflowType"] = "gift_selection"
) {
const sendEvent = useCallback(
async (payload: Omit<WorkflowAnalyticsEvent, "eventId" | "timestamp" | "workflowType" | "workflowVersion" | "workflowId">) => {
const event: WorkflowAnalyticsEvent = {
eventId: crypto.randomUUID(),
timestamp: new Date().toISOString(),
workflowId,
workflowType,
workflowVersion: WORKFLOW_VERSION,
...payload,
};
// invio semplice all’API; in produzione si può aggiungere buffer/debounce
await fetch("/api/workflow-analytics", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
});
},
[workflowId, workflowType]
);
const trackStepEvent = useCallback(
async (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => {
await sendEvent({ stepName, eventType, ...extra });
},
[sendEvent]
);
return { sendEvent, trackStepEvent };
}
Qui è importante che l’hook non dipenda da uno specifico step dell’UI. Sa solo che cosa sono stepName ed eventType. I componenti specifici gli diranno: «ho iniziato lo step», «l’ho completato» e così via.
Inviare workflow_started e workflow_finished
Nel componente GiftWizard si può registrare l’inizio e la fine dello scenario al mount e all’unmount:
// components/GiftWizard.tsx
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
const { sendEvent } = useWorkflowAnalytics(workflowId);
useEffect(() => {
void sendEvent({ eventType: "workflow_started" });
return () => {
void sendEvent({ eventType: "workflow_finished" });
};
}, [sendEvent]);
// ...
}
Certo, considerare il completamento all’unmount è un’approssimazione grossolana: l’utente può semplicemente minimizzare la chat o andare a un’altra conversazione. Ma anche una metrica così grezza dà già il senso di quanti scenari «arrivano da qualche parte».
Tracciamo gli eventi per step
Ora facciamo in modo che ogni step segnali se stesso all’analitica. Per cominciare aggiungiamo un semplice wrapper:
interface StepProps {
stepId: StepId;
onNext: () => void;
trackStepEvent: (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => Promise<void>;
}
function StepRecipient({ stepId, onNext, trackStepEvent }: StepProps) {
useEffect(() => {
void trackStepEvent(stepId, "step_started");
}, [stepId, trackStepEvent]);
const handleSubmit = async () => {
// ... validazione, salvataggio in widgetState
await trackStepEvent(stepId, "step_completed");
onNext();
};
return (
<div>
{/* campi del form del destinatario */}
<button onClick={handleSubmit}>Avanti</button>
</div>
);
}
In GiftWizard passiamo trackStepEvent:
export function GiftWizard() {
// ...
const { trackStepEvent } = useWorkflowAnalytics(workflowId);
const goToNext = () => {
setCurrentStep((prev) => NEXT_STEP[prev]);
};
if (currentStep === "recipient") {
return (
<StepRecipient
stepId="recipient"
onNext={goToNext}
trackStepEvent={trackStepEvent}
/>
);
}
// gli altri step...
}
Allo stesso modo, negli step dove esistono potenziali errori (ad esempio una richiesta a un’API esterna in suggest_ideas), in caso di insuccesso si può inviare "step_failed" con errorCode, e in caso di caricamento riuscito delle opzioni — "step_completed".
Così otteniamo:
- un elenco chiaro di eventi: quando gli step iniziano e finiscono;
- la possibilità di calcolare il tempo dello step: differenza tra "step_started" e "step_completed";
- visibilità su quali step terminano più spesso con "step_failed".
6. Strumentazione su backend / MCP
L’analitica lato client è utile, ma il widget vive in un mondo fragile: browser dell’utente, iframe, sandbox e tutto ciò che ne consegue. Quindi in parallelo conviene loggare gli eventi lato server — negli strumenti MCP o nelle API backend della vostra App.
Per esempio, avete uno strumento suggest_gifts che fa davvero il lavoro pesante: interroga il product feed, applica filtri e restituisce i regali. All’interno di tale strumento potete loggare sia la business logic sia gli eventi di analitica.
Un handler MCP ipotetico in TypeScript può apparire così:
// mcp/tools/suggestGifts.ts
import type { SuggestGiftsArgs } from "../schemas";
import { logWorkflowEvent } from "../analytics/log";
export async function handleSuggestGifts(args: SuggestGiftsArgs, context: { workflowId: string; stepName: string }) {
const startedAt = Date.now();
try {
// ... logica principale di selezione delle idee
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_completed",
toolName: "suggest_gifts",
success: true,
durationMs: Date.now() - startedAt,
});
return {
content: [{ type: "text", text: "Ho trovato 5 idee regalo." }],
_meta: {
// dati grezzi per il widget
},
};
} catch (e) {
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_failed",
toolName: "suggest_gifts",
success: false,
errorCode: "SUGGEST_FAILED",
durationMs: Date.now() - startedAt,
});
throw e;
}
}
E logWorkflowEvent può scrivere nella stessa tabella/memorizzazione in cui finiscono gli eventi dal front, semplicemente con l’etichetta "source": "backend".
Perché l’analitica lato server è più affidabile
Primo, una chiamata di tool o avviene o no — è un fatto oggettivo, non l’euristica «l’utente sembra aver cliccato il bottone».
Secondo, sul server è più semplice aggregare i dati: potete contare quante volte è stato chiamato ogni strumento, qual è la sua media di durationMs, e quale quota di chiamate termina con errore.
Terzo, così vedete la differenza tra un problema di UX (l’utente non arriva allo step in cui viene chiamato lo strumento) e un problema tecnico (ci arrivano, ma lo strumento cade spesso).
7. Come leggere i dati: troviamo i colli di bottiglia
Supponiamo che abbiate già implementato l’invio degli eventi "workflow_started", "step_started", "step_completed" e "step_failed" dal widget e dall’MCP, e che si sia accumulata una quantità sufficiente di dati. Immaginiamo di aver raccolto alcuni dati per GiftGenius e ottenuto una statistica di sintesi per step. Nella tabella seguente ci sono cifre ipotetiche per 1000 workflow avviati:
| Step | Inizio step | Step completato | Drop‑off nello step | Tempo medio (s) |
|---|---|---|---|---|
| recipient | 1000 | 950 | 5% | 12 |
| budget | 950 | 700 | 26% | 35 |
| ideas | 700 | 680 | 3% | 8 |
| review | 680 | 500 | 26% | 40 |
| checkout | 500 | 420 | 16% | 20 |
A cosa guardare qui:
Primo, lo step budget è un collo di bottiglia evidente. Drop‑off elevato (26%) e tempo medio sensibilmente più alto. Forse chiedete troppo su valute/tasse, le formulazioni non sono chiare, oppure gli utenti non sono sicuri del budget. È un buon candidato per semplificare lo step, dividerlo in due sotto‑step o cambiare la formulazione delle domande.
Secondo, anche review dà un forte abbandono. Forse l’UI delle card dei regali è sovraccarica, o all’utente non è chiaro cosa significhi «mettere like» a un regalo. Magari il modello restituisce troppe opzioni e il widget sembra un elenco infinito. Qui conviene guardare non solo i numeri, ma anche screenshot/registrazioni di sessione (se le avete), o almeno percorrere lo scenario come un utente.
Terzo, checkout perde il 16% — per uno scenario di commerce è molto denaro. Ma bisogna capire dove si perdono: nella finalizzazione dell’ordine, negli errori del payment provider, o perché l’utente ha semplicemente cambiato idea. Non è più una questione puramente di UX, ma una combinazione di UX + vincoli di business.
È importante saper distinguere i problemi di UI e quelli del modello.
- Se gli utenti tornano spesso allo step precedente e cambiano le risposte — è un segnale di domanda poco chiara o mal formulata.
- Se uno step si conclude rapidamente ma la chiamata dello strumento in quello step fallisce spesso — è un problema di backend/MCP.
- Se uno step dura a lungo e non ci sono né errori né ritorni, forse l’utente sta semplicemente leggendo un long‑read di testo di cui non ha davvero bisogno.
8. Esperimenti e A/B test del workflow
I numeri da soli non migliorano nulla. Perché l’analitica sia utile, bisogna saper impostare esperimenti: cambiare gli step e confrontare se è andata meglio.
Nel contesto di ChatGPT App l’esperimento tipico è il confronto tra due versioni di uno step o della sequenza di step:
- wizard lungo con più schermate semplici contro un unico form complesso;
- diverse formulazioni delle domande;
- ordine diverso degli step (ad esempio chiedere il budget prima o dopo);
- strategie diverse di tool gating (meno tools nel primo step, di più nel secondo).
Buona prassi: fissare la versione dello scenario in workflowVersion e aggiungere l’identificatore dell’esperimento, per esempio "1.3.0-A" e "1.3.0-B".
Split A/B più semplice nel widget
Ovviamente in produzione vorrete un assignment stabile a livello di utente o sessione (via backend), ma per un esempio didattico basta una scelta casuale.
// lib/useWorkflowVariant.ts
import { useMemo } from "react";
export type WorkflowVariant = "A" | "B";
export function useWorkflowVariant(): WorkflowVariant {
return useMemo(() => {
return Math.random() < 0.5 ? "A" : "B";
}, []);
}
In GiftWizard determiniamo la variante e la passiamo all’analitica:
export function GiftWizard() {
const [workflowId] = useState(() => crypto.randomUUID());
const variant = useWorkflowVariant();
const { sendEvent, trackStepEvent } = useWorkflowAnalytics(
workflowId,
"gift_selection"
);
useEffect(() => {
void sendEvent({
eventType: "workflow_started",
metadata: { variant },
});
}, [sendEvent, variant]);
// poi si possono cambiare testi/struttura degli step in base a variant
}
Sul server potete sostituire il rigido "1.0.0" con qualcosa come "1.1.0-A" e "1.1.0-B" oppure semplicemente loggare metadata.variant e poi raggruppare in analitica.
Il senso principale di un A/B test: scegliere in anticipo la metrica obiettivo. Ad esempio: «vogliamo alzare il completion rate dello scenario dal 42% al 50%» oppure «ridurre del 20% il tempo nello step budget». Senza una metrica obiettivo, qualsiasi ristrutturazione del workflow somiglierà a un restyling a caso: «abbiamo spostato l’armadio, sembra più bello».
9. Privacy ed etica dei dati
Abbiamo già accennato che in metadata e negli eventi di analitica è meglio non portare PII in chiaro. Quando si discutono metriche è facile farsi prendere la mano e iniziare a loggare di tutto. Ma ricordate che lavorate dentro ChatGPT e l’utente può ragionevolmente aspettarsi che i suoi messaggi personali non vengano spediti all’analitica esterna in forma grezza.
Alcune regole semplici, da seguire già ora, prima dei moduli su sicurezza e Store:
- Primo, non loggate i testi completi dei messaggi dell’utente. Al loro posto si possono salvare la lunghezza del messaggio, il tipo di risposta (numero, «sì/no», scelta da elenco), o segnali anonimizzati come «risposta vuota/incompleta/modificata».
- Secondo, non loggate informazioni chiaramente identificanti (PII) se non servono alla business logic: e‑mail, telefoni, indirizzi, nomi completi. Se è inevitabile, conservatele in un altro perimetro protetto e limitate l’accesso in modo rigoroso.
- Terzo, trattate con cura il contesto del dialogo. Se conservate conversationId, assicuratevi che nell’analitica non proviate a «fondere» conversazioni separate in super‑profili senza una ragione valida e una base legale.
- Quarto, fate attenzione alle policy di OpenAI e ai requisiti dello Store (ne parleremo in dettaglio nei moduli su pubblicazione e sicurezza), in cui è esplicitato quali dati possono uscire da ChatGPT, e quali — no. In fase di progettazione dell’analitica è utile prevedere subito anonimizzazione e minimizzazione dei dati, per non dover riscrivere mezza piattaforma dopo.
Infine, ricordate che l’analitica UX non è sorveglianza totale e certamente non è il Big Brother. L’obiettivo è migliorare gli scenari e ridurre la frustrazione dell’utente, non costruire un cruscotto alla Big Brother «chi alle 2:37 di notte non è arrivato al checkout».
10. Errori tipici nell’analitica UX del workflow
Errore n. 1: «Non siamo ancora in produzione, le metriche dopo».
Molto spesso gli sviluppatori iniziano a pensare all’analitica quando l’App è già usata da persone reali. Di conseguenza gli eventi vengono introdotti a posteriori, i dati risultano discontinui e confrontare la vecchia e la nuova versione dello scenario è quasi impossibile. Meglio prevedere subito il minimo funnel ("workflow_started", "step_started", "step_completed", "workflow_finished") quando il codice è ancora relativamente semplice.
Errore n. 2: loggare solo i successi e ignorare gli errori.
A volte nei log c’è solo "step_completed", mentre "step_failed" nessuno lo scrive, perché «be’, non dovrebbe fallire». Di conseguenza vedete che poche persone arrivano a uno step, ma non capite se se ne sono andate loro o se le ha espulse un errore. Loggate sempre sia il completamento riuscito dello step sia quello non riuscito, con almeno un errorCode grossolano.
Errore n. 3: assenza totale di un collegamento alla versione del workflow.
Cambiate i testi, l’ordine degli step, introducete tool gating, ma negli eventi figura sempre workflowVersion: "1.0.0". Dopo un mese guardate i grafici e non riuscite a capire cosa fosse prima delle modifiche e cosa dopo. Fissare la versione dello scenario e, se necessario, la variante A/B è un elemento obbligatorio dell’analitica.
Errore n. 4: analitica troppo dettagliata senza motivo.
L’estremo opposto è costruire subito lo «schema perfetto» di 50 campi, loggare ogni click al pixel e ogni ribattitura di carattere. Primo, può violare la privacy. Secondo, sono dati difficili da analizzare e annegherete nel rumore. Meglio partire da un set piccolo di eventi e metriche che rispondono davvero a specifiche domande di prodotto, e poi ampliare il sistema secondo necessità.
Errore n. 5: incoerenza nei nomi degli step e degli scenari.
A volte nel codice lo step si chiama budget, nell’analitica — collect_budget, e nel report — «lo step con la domanda sui soldi». Dopo qualche settimana nessuno ricorda cosa sia cosa. In fase di progettazione del workflow è utile concordare identificatori stabili degli step (stepName) e usarli in UI, log e report.
Errore n. 6: metriche che nessuno usa.
La storia più triste: avete raccolto diligentemente molti dati, configurato l’invio degli eventi dal widget e dall’MCP, ma poi nessuno apre i dashboard e prende decisioni. L’analitica fine a se stessa non serve; chiedetevi sempre: «Quale decisione posso prendere in base a questa metrica?». Se non c’è risposta — quella metrica per ora non vi serve.
GO TO FULL VERSION