ARTICOLO

Approvare uno strumento non è approvare un dato: cosa cambia (e cosa no) con Laravel AI SDK 1.0

LaravelAIprivacy

Il 23 settembre è uscito Laravel AI SDK 1.0, e fra le novità c'è il contratto Approvable: uno strumento che lo implementa mette in pausa l'agente finché una persona non approva, rifiuta o riscrive gli argomenti della chiamata. È la cosa giusta da avere nel framework. Ma dopo un anno passato a costruire un prodotto che manda immagini di clienti a un modello, la mia reazione è stata più fredda di quanto mi aspettassi: l'approvazione di uno strumento è un controllo sull'azione, e il problema che mi toglie il sonno è un controllo sul dato. Non sono lo stesso confine, e metterli nello stesso punto è il modo più elegante per sbagliare.

Cosa fa davvero il contratto

Il meccanismo è pulito. Uno strumento dichiara Approvable, usa il trait InteractsWithApprovals, e quando il modello decide di chiamarlo la conversazione si ferma restituendo la lista delle chiamate pendenti con gli argomenti che il modello ha scelto:

use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;

class DeleteFile implements Approvable, Tool
{
    use InteractsWithApprovals;
    // ...
}

Si riprende passando una decisione per ciascuna chiamata: approva, rifiuta con una motivazione che il modello legge, oppure modifica gli argomenti prima dell'esecuzione. Funziona con prompt, stream, queue e i metodi di broadcast, quindi regge anche il caso in cui l'agente gira in coda e l'approvazione arriva venti minuti dopo da un'altra sessione HTTP. Quel pezzo lì — la persistenza dello stato di un agente sospeso — è la parte noiosa che nessuno ha voglia di riscrivere, ed è la ragione vera per cui vale la pena usarla.

Il modello mentale che suggerisce, però, è: l'agente propone, l'umano conferma, l'azione parte. Va benissimo per DeleteFile, per un rimborso, per un'email che esce. Va molto meno bene quando l'azione rischiosa non è quella che lo strumento fa, ma quella che il prompt porta con sé.

In Miraviso il confine sta più in basso

In Miraviso, lo specchio virtuale per saloni, la pipeline ha due trattamenti della privacy dichiarati come tali, perché sono davvero due meccanismi diversi.

La prova colore gira interamente sul tablet: segmentazione con MediaPipe on-device, composizione del colore in locale, nessun frame che lascia l'apparecchio. L'anteprima del taglio no: per quella serve un modello generativo, e gira su Gemini in Vertex AI in regione UE, previo consenso esplicito della persona seduta sulla poltrona, e il risultato non viene mai scritto su disco.

Prendiamo quella seconda strada e proviamo a modellarla come uno strumento approvabile. L'approvazione dovrebbe suonare così: «il modello vuole chiamare generate_haircut_preview con questa immagine — confermi?». Ma chi conferma? L'operatore del salone, cioè l'unico con il tablet in mano e l'unico che il flusso dell'agente può interrogare. E il consenso che serve a me non è il suo. È quello della cliente, che non tocca l'interfaccia e che deve poter dire di no prima che la fotocamera faccia qualunque cosa di utile.

Da qui viene la regola che mi sono dato: l'approvazione umana dentro l'agente protegge chi guida l'agente. Il consenso al trattamento protegge chi è nel dato. Se l'unico cancello è nel ciclo dell'agente, ho costruito un'interfaccia di conferma per la persona sbagliata.

Il gate che sta prima del modello

Nella mia pipeline il consenso non è un passaggio del dialogo: è uno stato della sessione, e il client non è in grado di comporre una richiesta generativa finché quello stato non c'è. L'endpoint FastAPI che parla con Vertex rifiuta la chiamata a monte, non dopo, e non perché si fida di un flag mandato dal tablet:

@router.post("/preview/cut")
async def preview_cut(req: CutRequest, session: Session = Depends(current_session)):
    if not session.consent.generative_preview:
        raise HTTPException(403, "consenso mancante per l'anteprima generativa")
    # da qui in poi il frame esiste solo in memoria
    ...

Tre conseguenze pratiche, tutte più noiose di un contratto PHP e tutte più importanti:

Su quest'ultimo punto ho già scritto separatamente, perché è un errore molto più comune di quanto sembri: i dati sensibili che finiscono nei log ci arrivano quasi sempre da una query fallita o da un APM, non da una riga di codice che qualcuno ha scritto apposta.

Dove invece lo userei domani

Non è una critica al pacchetto: è una divisione dei compiti. Sul lato gestionale — quello Laravel, dove finiscono anagrafiche, appuntamenti e fatturazione — un agente che propone azioni e aspetta conferma è esattamente lo strumento giusto. Unire due schede cliente duplicate, spostare una serie di appuntamenti, emettere una nota di credito: sono operazioni dove l'argomento scelto dal modello è la cosa che voglio poter correggere, e la possibilità di riscrivere gli argomenti prima dell'esecuzione vale più della possibilità di dire no.

Segnalo anche il middleware per step, che nella 1.0 gira a ogni generazione invece che una volta per prompt: ricevi un PendingStep e puoi cambiare modello, togliere strumenti o abbassare i token man mano che la conversazione va avanti. Per un solo founder che paga i token di tasca propria è una leva di costo concreta — togliere lo strumento caro dopo il primo uso è una riga, e prima era un problema di architettura.

La regola che mi porto via, e che vale ben oltre questo SDK: quando un framework ti offre un cancello, chiediti chi sta dalla parte della maniglia. Se la risposta non è la persona i cui dati stanno passando, quel cancello è utile ma non è il tuo.

Se vi interessa il resto di come sono fatte queste cose, trovate gli altri articoli qui — e se volete vedere del codice scritto senza framework, i giochi di logica sono tutti a mano.

← Tutti gli articoli