CodeGym /Corsi /ChatGPT Apps /Errori, idempotenza e design di strumenti «sicuri»

Errori, idempotenza e design di strumenti «sicuri»

ChatGPT Apps
Livello 4 , Lezione 4
Disponibile

1. Errori e idempotenza in ChatGPT App

Nel web classico molti vivono ancora nella paradigma «l’utente clicca un pulsante → una richiesta HTTP → una risposta». Nel mondo LLM non è più così. Il modello può decidere di chiamare il vostro strumento più volte, può rigenerare la risposta dopo che l’utente ha premuto Regenerate, può fare domande di chiarimento o imbattersi in un errore di rete lungo il percorso. Di conseguenza lo stesso strumento può essere invocato due o tre volte con argomenti molto simili.

Ogni errore improvvisamente ha due destinatari. Da un lato — il modello, cui serve una spiegazione comprensibile e leggibile dalla macchina su cosa non ha funzionato, così da poter correggere gli argomenti e riprovare. Dall’altro — l’UI dell’utente (widget e chat), dove bisogna mostrare un messaggio umano e suggerire azioni successive, non «Errore: 500 (vedi log)».

Un altro punto importante: l’architettura classica raramente prevede che qualcuno prema ripetutamente «ripeti risposta», aumentando così il numero di richieste ripetute (retry). In ChatGPT questo scenario è la norma. Inoltre la piattaforma può effettuare una nuova chiamata in caso di problemi di rete temporanei. Pertanto il concetto di idempotenza in questo ecosistema non è un optional, ma un requisito di base, soprattutto per gli strumenti che fanno qualcosa «sul serio» — creano ordini, addebitano denaro, inviano email, ecc.

Questa lezione riguarda proprio come impedire che una chiamata non riuscita di uno strumento (tool call) rovini l’esperienza all’utente e a voi — la produzione.

Insight

ChatGPT non passa gli argomenti alle vostre funzioni, ma piuttosto indovina il set di argomenti in base al vostro schema. Guarda lo JSON Schema, il contesto del dialogo e seleziona statisticamente i valori — e spesso sbaglia. Errori come «tipo non corretto», «campo obbligatorio mancante», «parametri in conflitto» sono una parte normale della vita dei tool-calls, non un evento eccezionale. Secondo dati pubblici e telemetria, tali errori rappresentano facilmente fino a ~30 % delle chiamate per schemi complessi.

Per il modello non è un problema: interpreta la vostra risposta come un segnale «gli argomenti erano errati» e semplicemente riprova, magari due o tre volte di seguito, modificando leggermente l’input. Per voi significa un’altra cosa: ogni strumento va progettato come se fosse quasi garantito che verrà chiamato più volte con parametri molto simili.

Ecco perché l’idempotenza è così importante. ChatGPT cercherà più e più volte di indovinare con quali parametri deve chiamare le vostre funzioni. 2–3 tentativi per chiamata sono la norma.

2. Configurazione sicura del widget: text/html+skybridge e _meta

Prima di passare ai temi puramente server-side (errori, retry, idempotenza), chiudiamo un punto specifico dell’Apps SDK relativo alla sicurezza dell’UI: come fare in modo che il vostro widget venga renderizzato in chat in sicurezza e non come «una pagina terribile presa da internet».

registerResource e MIME type text/html+skybridge

Il vostro widget dal punto di vista di ChatGPT è una risorsa HTML speciale che finisce nella sandbox del client di ChatGPT, non direttamente nel browser dell’utente. Per indicare alla piattaforma che si tratta di un widget e non di semplice HTML si usa il MIME type text/html+skybridge.

A livello di MCP/server registrate la risorsa con qualcosa del tipo (pseudo‑TS):

// da qualche parte nella configurazione del server MCP
registerResource({
  name: "giftgenius-widget",
  path: "/widget",
  mimeType: "text/html+skybridge", // importante!
});

Questo mimeType è un segnale per il client di ChatGPT: «questo non è semplice HTML, ma un template componentizzato per un widget incorporato da avviare in un ambiente isolato». Se si indica un normale text/html, la piattaforma può mostrare HTML grezzo o rifiutarsi del tutto di renderizzarlo.

_meta e controllo della sicurezza: CSP, dominio e bordo

Entrano poi in gioco i metadati passati insieme alla risposta dello strumento o della risorsa — _meta. Tramite questi controllate quali risorse esterne il widget può caricare, come si comporta visivamente e persino come il modello lo descriverà.

Esempio tipico di struttura:

const toolResult = {
  content: "<!-- HTML del widget -->",
  _meta: {
    "openai/widgetCSP": "default-src 'self'; img-src https://cdn.example.com",
    "openai/widgetDomain": "https://chatgpt.com",
    "openai/widgetPrefersBorder": true,
    "openai/widgetDescription": "GiftGenius mostra consigli regalo sotto forma di schede."
  }
};

Analizziamo i campi chiave.

  • openai/widgetCSP imposta la Content Security Policy per il widget. È un piccolo firewall per il browser all’interno di ChatGPT: elencate esplicitamente da dove è possibile caricare script, stili, immagini, fare XHR ecc. La piattaforma si aspetta una policy rigorosa senza wildcard *; dovete indicare esplicitamente i domini utilizzati (chat, il vostro API, CDN).
  • openai/widgetDomain definisce l’origin nel cui contesto opererà il vostro widget. Di solito è il dominio di ChatGPT; non lo sostituite con il vostro sito, ma informate semplicemente su come dovrebbe apparire nell’ambiente isolato.
  • openai/widgetPrefersBorder — un flag puramente visivo: disegnare o meno un bordo attorno al widget. Per GiftGenius ha senso mantenere il bordo per separare visivamente il blocco delle raccomandazioni dai messaggi normali in chat.
  • openai/widgetDescription — descrizione testuale per il modello. Invece di «inventare» da sola una spiegazione, il modello può usare questa stringa quando spiega all’utente quale interfaccia si è appena aperta. Questo riduce il rischio di commenti strani o ridondanti del modello.

Conclusione pratica: configurando con cura una volta mimeType e _meta, ottenete un’UI sicura e isolata che non accede dove non deve e si comporta in modo prevedibile sia per l’utente sia per la piattaforma. La parte frontend della sicurezza è a posto: il widget vive in sandbox e accede solo dove gli avete permesso. Ora concentriamoci sul lato server — tipi di errori, come descriverli e come rendere gli strumenti idempotenti.

Insight: caching del widget

ChatGPT esegue il caching dell’HTML del widget al momento della registrazione dell’applicazione. L’HTML‑widget di ChatGPT non è un «frontend vivo», ma un artefatto di build fissato. Durante la pubblicazione dell’app (Store o Dev Mode) la piattaforma legge la risorsa HTML (text/html+skybridge) e poi usa sempre quella versione. Qualsiasi modifica — anche una singola riga di testo o un margine in una scheda — equivale di fatto a un nuovo rilascio.

Da qui la conclusione: modifiche alla struttura HTML, agli slot, agli attributi data-* e al contratto structuredContentDOM non sono un «fix veloce», ma una vera migrazione frontend. Se oggi renderizzate un elenco da items[] e domani passate a results[], il vecchio widget non lo saprà: continuerà a ricevere il JSON precedente e funzionerà in modo scorretto.

3. Tipi di errori nel funzionamento degli strumenti

Passiamo ora alla sostanza: quali errori può avere uno strumento e in cosa differiscono dal punto di vista dell’UX e del backend. Conviene pensare a quattro livelli di errori.

Errori di validazione dell’input

Il livello più basilare — quando gli argomenti in ingresso non rispettano affatto il contratto.

Esempi per la nostra app didattica GiftGenius e il suo strumento suggest_gifts (selezione di regali per interessi e budget):

  • età minore di zero o maggiore di 120;
  • budget negativo;
  • manca il campo obbligatorio relationship_type;
  • budget_min > budget_max.

Qui rientra anche il banale JSON non conforme allo schema. Idealmente Apps SDK e JSON Schema filtreranno le chiamate «del tutto sbagliate» prima del vostro codice, ma la validazione business (come il rapporto tra budget_min/budget_max) dovete comunque farla voi.

Errori di logica di business

Qui l’input è apparentemente corretto, ma secondo le regole di dominio non potete fornire un risultato utile.

Svolte tipiche:

  • con interessi e budget indicati non trovate alcun regalo;
  • l’utente ha superato il limite giornaliero di selezioni;
  • l’articolo che il modello chiede di acquistare non è più in vendita.

Non è «server rotto», ma situazioni normali e attese, da presentare all’utente e al modello in forma comprensibile, non come 500 Internal Server Error.

Errori di infrastruttura esterna

Questo livello riguarda il «trambusto tecnico»: database non disponibile, API esterna in timeout, eccezione non gestita all’interno del vostro codice.

Ad esempio:

  • la richiesta al catalogo dei regali restituisce 503 o non risponde;
  • MongoDB decide improvvisamente di mettersi in pausa;
  • nel codice di filtraggio dei regali dividete per zero.

Dal punto di vista UX spesso è il caso di dire: «Servizio temporaneamente non disponibile, riprova più tardi», talvolta — tentare un retry nascosto. Ma è importante non «sparire» in silenzio e non mostrare all’utente uno stack trace grezzo.

Errori della piattaforma/rete

Infine, c’è un livello che può accadere completamente fuori dal vostro codice: la tool‑call non è arrivata, la connessione si è interrotta a metà della risposta, uno scenario di streaming è stato troncato. Succede più spesso di quanto pensiate. Ad esempio, usando un tunnel gratuito, nelle ore di punta la velocità può calare al punto che le tool calls di ChatGPT vanno in timeout.

Non potete controllare tutto questo completamente, ma potete progettare strumenti e widget in modo che chiamate ripetute e interruzioni non trasformino il sistema nel caos. Ecco perché parliamo di idempotenza e gestione accurata degli errori, non semplicemente di «try/catch e via».

4. Come descrivere e restituire gli errori: per il modello e per l’UI

Un importante cambio di mentalità: il vostro errore non è solo ciò che avete loggato in console.error. È parte del contratto dello strumento con cui lavoreranno sia il modello sia l’interfaccia.

Struttura dell’errore

Di solito è comodo attenersi a una struttura semplice:

type ToolError = {
  code: string;        // "VALIDATION_ERROR", "NO_RESULTS", "UPSTREAM_TIMEOUT"
  message: string;     // testo leggibile per l'utente o compatto per il modello
  retryable: boolean;  // ha senso riprovare?
};

E il risultato dello strumento si può incapsulare in una discriminated union:

type SuggestGiftsResult =
  | { ok: true; gifts: GiftCard[] }
  | { ok: false; error: ToolError };

Nel protocollo MCP esiste anche un flag separato «questo è un errore», ma internamente è utile aderire a un formato proprio, in modo che UI e modello possano interpretare allo stesso modo ciò che è accaduto.

Strategia «fail gracefully»

Non ogni situazione spiacevole deve essere trattata come un errore «duro». Talvolta è molto più utile restituire un risultato vuoto, ma senza errore, con una spiegazione.

Ad esempio, se non si trovano regali, ha senso restituire ok: true, un array vuoto gifts: [] e un campo tipo noResultsReason per UI e modello, invece di "NO_RESULTS" come errore. Così il modello può continuare il dialogo: «Non ho trovato nulla con questo budget, vuoi aumentarlo o specificare meglio gli interessi?».

Se invece l’API esterna è completamente giù, allora è più ok: false con code: "UPSTREAM_UNAVAILABLE" e retryable: true, così che il modello possa riprovare più tardi o con altri parametri.

Ricordiamo, dalla sezione 3 abbiamo quattro livelli di errori. Gli errori di validazione vanno di solito come ok: false e retryable: false — il modello non dovrebbe ripetere la stessa chiamata con gli stessi argomenti. Le situazioni di business come «non è stato trovato nulla» si modellano più spesso come ok: true con risultato vuoto e spiegazione. I guasti infrastrutturali di servizi esterni — come ok: false con retryable: true, in modo che il modello possa riprovare in sicurezza. E gli errori di piattaforma/rete possono verificarsi prima o dopo il vostro codice e in pratica spesso si traducono in una chiamata ripetuta dello strumento — ed è proprio per questo che ci serve un’idempotenza accurata, di cui parleremo a breve.

Non esporre dettagli interni

Nel codice server è facile cadere nella tentazione di inoltrare semplicemente error.toString() nella risposta. Per gli strumenti LLM non è una buona idea: otterrete rumore nel dialogo e potenzialmente esporrete dettagli sensibili (URL di servizi interni, stack trace, nomi di tabelle). La raccomandazione è intercettare le eccezioni e convertirle in codici di errore compatti e messaggi accurati.

Esempio di wrapper minimale:

try {
  const gifts = await loadGiftsFromCatalog(input);
  return { ok: true, gifts };
} catch (err) {
  console.error("suggest_gifts failed", err);
  return {
    ok: false,
    error: {
      code: "UPSTREAM_ERROR",
      message: "Catalog service is unavailable",
      retryable: true
    }
  };
}

Il modello vede un segnale pulito, l’UI — un testo comprensibile, e i dettagli rimangono nei log.

Visualizzazione dell’errore nel widget

Dal punto di vista di un widget React il compito è banale: controllare ok e, se è false, mostrare un messaggio amichevole e, se possibile, un modo per proseguire.

function GiftResults({ result }: { result: SuggestGiftsResult }) {
  if (!result.ok) {
    return (
      <div>
        <p>Non è stato possibile trovare regali: {result.error.message}</p>
        {result.error.retryable && <p>Provare a modificare i parametri o ripetere la richiesta.</p>}
      </div>
    );
  }

  if (result.gifts.length === 0) {
    return <p>Non sono stati trovati regali con queste condizioni. Prova a modificare il budget o gli interessi.</p>;
  }

  return <GiftCardsList gifts={result.gifts} />;
}

Questo è proprio il caso in cui un messaggio semplice e onesto rende l’UX molto più gradevole rispetto a «qualcosa è andato storto».

Abbiamo già concordato che parte degli errori può essere onestamente contrassegnata come retryable: true e proporre all’utente di «riprovare». Non appena in sistema compaiono tali retry (espliciti nell’UI o nascosti lato piattaforma), sorge la domanda successiva: cosa succede se lo stesso strumento viene chiamato due volte con gli stessi dati? Questa è una storia di idempotenza.

5. Idempotenza: protezione da «ancora una chiamata uguale»

Passiamo alla parte più divertente. Formalmente l’idempotenza è la proprietà di un’operazione per cui una chiamata ripetuta con gli stessi dati di input non cambia lo stato del sistema e il risultato. In senso stretto riguarda sia l’assenza di effetti collaterali ripetuti sia la risposta identica. Nella pratica delle ChatGPT Apps ci interessa soprattutto la prima: evitare che chiamate ripetute rovinino i dati o creino nuove entità, anche se la risposta stessa può differire leggermente.

Nel contesto delle ChatGPT Apps l’idempotenza è una protezione da tutto ciò che accade con retry, Regenerate e logica imprevedibile dell’LLM.

Dove l’idempotenza è particolarmente importante

Gli strumenti di sola lettura in genere sono sicuri: per quante volte si chiami suggest_gifts con gli stessi parametri, otterrete semplicemente un altro elenco di regali. Anche se cambia leggermente, non modifica lo stato del sistema e non crea effetti collaterali.

Sono critici gli strumenti che modificano lo stato di sistemi esterni:

  • creazione di un ordine (create_order);
  • esecuzione di un pagamento (charge_card, submit_payment);
  • invio di email e notifiche (send_email, send_sms);
  • creazione di entità con effetti collaterali (ad es. prenotazioni).

Se uno strumento del genere viene chiamato due volte di seguito con argomenti praticamente identici, rischiate ordini duplicati, addebiti doppi e altre «gioie» contabili.

Pattern idempotency_key

L’approccio classico: aggiungere allo strumento un parametro aggiuntivo idempotency_key — un identificatore stringa dell’operazione. Se una richiesta con tale chiave è già stata elaborata con successo, il server non esegue di nuovo l’azione, ma restituisce il risultato salvato.

Esempio di schema esteso per lo strumento ipotetico create_checkout_session in GiftGenius:

const CreateCheckoutSchema = {
  type: "object",
  properties: {
    giftId: {
      type: "string",
      description: "ID del regalo selezionato"
    },
    idempotency_key: {
      type: "string",
      description: "Chiave univoca dell'operazione per proteggere dai duplicati"
    }
  },
  required: ["giftId", "idempotency_key"]
} as const;

L’handler sul server fa più o meno così:

async function createCheckoutSession(input: CreateCheckoutInput) {
  const existing = await db.checkoutSessions.findOne({ idempotencyKey: input.idempotency_key });
  if (existing) {
    return existing; // restituiamo il risultato precedente
  }

  const session = await paymentProvider.createSession({ giftId: input.giftId });
  await db.checkoutSessions.insert({ idempotencyKey: input.idempotency_key, session });
  return session;
}

Se il modello per qualche motivo chiamasse lo strumento una seconda volta con lo stesso idempotency_key, l’utente non riceverebbe un secondo addebito, ma vedrebbe semplicemente lo stesso checkout.

Separazione tra prepare e commit

Per azioni particolarmente sensibili (pagamenti, modifiche irreversibili) si usa spesso un approccio in due fasi: uno strumento per la preparazione (prepare_*), un altro per il commit (commit_*).

Ad esempio:

  • prepare_order — controlla la disponibilità del prodotto, calcola il costo, restituisce una «bozza d’ordine»;
  • commit_order — in base all’ID della bozza crea l’ordine vero e proprio e avvia il pagamento.

Questo design offre vari vantaggi. Primo, potete rendere il primo passo completamente idempotente: una prepare_order ripetuta con gli stessi parametri restituirà la stessa bozza. Secondo, potete permettere commit_order solo dopo una conferma esplicita dell’utente, comodo sia per l’UX sia per la sicurezza.

6. Design sicuro degli strumenti

L’idempotenza è necessaria, ma non è l’unico ingrediente della sicurezza. Molto dipende dal design dell’insieme di strumenti che fornite al modello.

Principio del privilegio minimo

L’idea è semplice: ogni strumento deve poter fare esattamente ciò che serve allo scenario, e non una riga di più. Non serve una funzione do_anything_with_user_account che:

  • può leggere, aggiornare e cancellare tutto indistintamente;
  • accetta una stringa operation e un JSON payload «alla buona».

Meglio avere tool separati, ben descritti:

  • get_user_profile;
  • update_user_preferences;
  • create_order;
  • cancel_order.

Stessa logica per GiftGenius: suggest_gifts si limita a proporre opzioni; create_checkout_session non sa come annullare ordini o modificare l’email dell’utente.

Separazione tra strumenti «read» e «write»

Un buon pattern è separare chiaramente gli strumenti che leggono solo dati da quelli che li modificano. Le richieste al catalogo dei regali (search_products, suggest_gifts) sono sicure di per sé, anche se il modello ne abusa. Mentre create_order o charge_payment richiedono maggiore cautela.

Nelle descrizioni di tali strumenti conviene scrivere esplicitamente cosa fanno e in quale contesto possono essere chiamati. Ad esempio:

{
  "name": "create_checkout_session",
  "description": "Crea una nuova sessione di pagamento per un singolo regalo. Chiamare SOLO dopo che l'utente ha confermato esplicitamente la sua scelta.",
  "parameters": { /* ... */ }
}

Non è una protezione al 100 % (l’LLM può comunque sbagliare), ma almeno le date un segnale chiaro sui rischi.

Human-in-the-loop e conferme

Per azioni davvero «pericolose» è utile costruire uno scenario con conferma. Ad esempio, il modello:

  1. Per prima cosa chiama lo strumento che prepara i dati per l’acquisto e li restituisce in una forma adatta all’UI (nome del regalo, prezzo, indirizzo di spedizione).
  2. La piattaforma mostra all’utente il widget con il pulsante «Conferma acquisto».
  3. Solo dopo il clic si chiama lo strumento di commit che effettua il pagamento reale.

Così non date al modello la possibilità di fare un ordine «di nascosto» senza coinvolgere l’utente, anche se decidesse che è una mossa intelligente.

Semantica del rischio in descrizioni e annotazioni

In alcune versioni della piattaforma compaiono annotazioni speciali come destructiveHint, che segnalano che lo strumento può eseguire azioni irreversibili. Anche se tali campi non ci sono o sono instabili, potete incorporare questa semantica direttamente nella description e nei nomi dei parametri.

Ad esempio, invece di:

{
  "name": "delete_user_data",
  "description": "Elimina i dati dell'utente."
}

scrivere:

{
  "name": "request_user_data_deletion",
  "description": "Contrassegna l'account dell'utente per l'eliminazione dei suoi dati personali secondo la policy del servizio. Usa SOLO dopo che l'utente ha richiesto esplicitamente l'eliminazione."
}

E costruire intorno a ciò un UX di conferma a misura d’uomo.

7. Piccola miglioria pratica in GiftGenius

Colleghiamo tutto questo alla nostra app didattica GiftGenius — App per la scelta dei regali. Supponiamo di aggiungere un nuovo strumento — create_checkout_session, così che l’utente possa non solo selezionare un regalo, ma anche procedere al checkout.

Dal punto di vista dello JSON Schema e della sicurezza facciamo quanto segue.

Per prima cosa aggiungiamo idempotency_key e una descrizione accurata:

const CreateCheckoutTool = {
  name: "create_checkout_session",
  description:
    "Crea una sessione di pagamento per un singolo regalo selezionato. " +
    "Chiamare solo dopo che l'utente ha confermato di voler acquistare questo regalo.",
  parameters: {
    type: "object",
    properties: {
      gift_id: {
        type: "string",
        description: "Identificatore del regalo dal risultato di suggest_gifts."
      },
      idempotency_key: {
        type: "string",
        description: "Chiave univoca dell'operazione. Usa la stessa chiave in caso di chiamata ripetuta."
      }
    },
    required: ["gift_id", "idempotency_key"]
  }
} as const;

Poi, sul server, implementiamo un handler idempotente:

async function handleCreateCheckout(input: CreateCheckoutInput) {
  const existing = await db.checkout.findOne({ idempotencyKey: input.idempotency_key });
  if (existing) {
    return { ok: true, checkout: existing };
  }

  const checkout = await payments.createSession({ giftId: input.gift_id });
  await db.checkout.insert({ idempotencyKey: input.idempotency_key, ...checkout });

  return { ok: true, checkout };
}

Infine, consideriamo gli errori:

try {
  return await handleCreateCheckout(input);
} catch (err) {
  console.error("create_checkout_session failed", err);
  return {
    ok: false,
    error: {
      code: "PAYMENT_PROVIDER_ERROR",
      message: "Impossibile creare la sessione di pagamento. Riprova più tardi.",
      retryable: true
    }
  };
}

E nel widget mostriamo uno stato di errore comprensibile e, se possibile, un pulsante «Riprova» a livello di UI, che avvia un nuovo dialogo con il modello.

Così, passo dopo passo, il nostro simpatico progetto didattico smette di essere «un giocattolo da demo» e lentamente diventa qualcosa che teoricamente si può rilasciare in produzione.

8. Errori tipici nella gestione di errori e idempotenza degli strumenti

Errore n. 1: Errore = semplice throw e 500.
Se a ogni guasto il vostro tool lancia un’eccezione che si traduce in «qualcosa è andato storto», il modello e l’UI restano senza informazioni. Il modello non capisce se valga la pena ripetere la chiamata con altri argomenti e l’utente non capisce cosa fare dopo. Molto meglio restituire un errore strutturato con codice, messaggio breve e flag retryable, e loggare i dettagli dentro il server.

Errore n. 2: Nessuna distinzione tra tipi di errori.
Mescolare errori di validazione, business e infrastrutturali in un unico calderone è una cattiva idea. Alla fine, la situazione «non è stato trovato nulla» appare al modello e all’utente come «il database è caduto». Ciò rovina l’UX e impedisce al modello di reagire adeguatamente: invece di proporre di modificare la richiesta entrerà in modalità «scusa, servizio rotto». È particolarmente doloroso quando mescolate, ad esempio, errori di business e errori infrastrutturali della sezione 3.

Errore n. 3: Operazioni non idempotenti in un mondo di retry.
Progettare uno strumento create_order come se venisse sempre chiamato una sola volta è la via diretta ai duplicati di ordini, specialmente quando l’utente preme spesso Regenerate o la connessione si interrompe a metà strada. Se lo strumento ha effetti collaterali, quasi sempre conviene aggiungere idempotency_key e salvare i risultati, così che una chiamata ripetuta non crei nuove entità.

Errore n. 4: Un unico strumento «universale» mostruoso.
A volte gli sviluppatori provano a creare un super‑strumento con il parametro action che sa fare tutto: cercare, creare, modificare e cancellare. Per un’LLM è quasi una garanzia di comportamento imprevedibile: il modello impara più difficilmente cosa chiamare e quando, e le conseguenze degli errori diventano molto più gravi. È meglio suddividere in strumenti piccoli, ben descritti, preferibilmente di sola lettura, e separatamente — strumenti mutating accuratamente progettati con conferme.

Errore n. 5: Fuoriuscita di dettagli interni nelle risposte.
Buttare nel modello e nell’UI uno stack trace grezzo o testi completi di eccezioni è tipica pigrizia ingegneristica. È scomodo per l’utente, può rivelare la struttura interna del sistema e non aiuta il modello a correggersi. Bisogna intercettare le eccezioni, mapparle in codici compatti e messaggi semplici, e lasciare tutti i dettagli nei log e nel sistema di monitoraggio.

Errore n. 6: Nessun collegamento tra errori e UX del widget.
Spesso la parte server restituisce accuratamente i codici di errore, ma il widget UI cade in un spinner infinito o in un blocco vuoto. L’utente vede «non è successo nulla», il modello vede che la tool‑call è terminata e continua il dialogo come se niente fosse. Molto meglio pensare a stati separati error e empty, mostrare messaggi comprensibili e, se possibile, suggerire azioni (modificare i parametri, provare più tardi).

Errore n. 7: Ignorare il principio del privilegio minimo.
Anche se avete implementato idempotenza e buona gestione degli errori, ma avete descritto uno strumento tipo execute_sql_anywhere che può fare tutto, il rischio resta enorme. L’LLM può chiamarlo nel contesto sbagliato o con parametri errati. Ogni strumento deve essere il più ristretto possibile e fare esattamente un’azione chiara — soprattutto quando si parla di denaro o dati personali dell’utente.

1
Sondaggio/quiz
Strumenti App e <code><span class="text-user">callTool</span></code>, livello 4, lezione 4
Non disponibile
Strumenti App e callTool
Strumenti App e callTool: collegamento UI ↔ backend
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION