Vai al contenuto principale
Agentic SQL e semantic layer con approval - immagine header GinnyTech con visual cosmico editoriale

Agentic SQL e semantic layer con approval

Agentic SQL e semantic layer con approval su GinnyTech: decidere se una query agentica puo diventare modello riusabile o resta esplorazione con controlli, ownership e output revisionabili.

AD
Creato daAndrii Dyshkantiuk
Lezione 230 / 236Livello: AvanzatoDurata: 31 minPrerequisiti: 1

Cosa imparerai

  • Progettare workflow AI per dati con controlli, owner e output revisionabili
  • Applicare AI, AutoML o agentic AI a casi business analytics senza perdere rigore
  • Riconoscere rischi di leakage, drift, costo, privacy e automazione non governata

Agentic SQL e semantic layer con approval

Un product manager scrive in chat quanto vale davvero il fatturato netto del trimestre per canale, al netto dei rimborsi, e un agente risponde in dieci secondi con un numero, un grafico e una query SQL allegata. Il numero sembra giusto, la query compila, il grafico è convincente. Due settimane dopo la finanza scopre che i rimborsi erano conteggiati con un ritardo di trenta giorni, la valuta non era convertita e un canale includeva ordini di test. Il danno non è tecnico: è una decisione di budget presa su una definizione sbagliata. Questo articolo spiega come evitare quel fallimento, mettendo tra l’agente e il database uno strato che conosce il significato dei dati e un cancello umano che approva cosa diventa ufficiale. La lezione appartiene al binario sistemi-llm.

Il problema non è la sintassi SQL. Oggi i modelli linguistici la azzeccano quasi sempre. Il problema è che non conoscono il vostro business: non sanno che ricavo netto da voi significa incassato meno rimborsi entro 30 giorni al cambio BCE del giorno di fatturazione, che la tabella ordini contiene righe duplicate per i pagamenti rateali, o che il segmento enterprise è stato ridefinito a marzo. Quella conoscenza vive nel semantic layer, metriche, dimensioni, join consentiti, regole temporali, e l’approval impedisce all’agente di inventarne una versione propria ogni volta.

Perché il text-to-SQL libero non regge in produzione

Il text-to-SQL diretto, domanda in linguaggio naturale, query generata, esecuzione immediata, funziona nelle demo e crolla nei casi reali per tre motivi ricorrenti. Il primo è l’ambiguità semantica: utenti attivi può voler dire registrati, loggati negli ultimi 28 giorni, paganti, o con almeno un evento chiave. Un agente senza vincoli sceglie l’interpretazione più semplice, tipicamente un conteggio grezzo sulla tabella utenti, e la presenta con sicurezza. Il secondo è il grain sbagliato: appena la query tocca due tabelle con granularità diversa, ordini e righe ordine, utenti e sessioni, il join moltiplica le righe e le somme esplodono. Il terzo è il contesto temporale: filtri su data di creazione invece di data di pagamento, fusi orari mescolati, snapshot confrontati con tabelle live.

C’è poi un effetto organizzativo. Se ogni analista lascia l’agente libero di scrivere SQL sul raw, dopo tre mesi avete quaranta definizioni diverse di churn, nessuna tracciata, e ogni dashboard racconta una storia diversa. Il costo non è solo la query sbagliata di oggi: è l’erosione della fiducia nei dati, che spinge i manager a tornare ai fogli di calcolo personali. Il semantic layer nasce per fermare questa deriva: una sola definizione versionata per metrica, un solo join certificato tra due entità, e l’agente che può comporre solo dentro quei binari.

Un’obiezione comune è che vincolare l’agente ne uccida l’utilità. L’esperienza dice il contrario: un agente vincolato risponde più in fretta perché non deve indovinare lo schema tra centinaia di tabelle, e sbaglia meno perché le scelte pericolose sono già state prese da un umano. La libertà resta dove serve, cioè nell’esplorazione: l’agente può proporre tagli nuovi, segmenti insoliti, correlazioni da verificare. Quello che non può fare è ridefinire da solo cosa significa una metrica ufficiale o eseguire scritture senza permesso.

Come è fatto un semantic layer che un agente può usare davvero

Un semantic layer utile a un agente ha quattro componenti, e se ne manca uno l’agente trova il modo di aggirare gli altri. Il primo è il catalogo delle metriche: nome, definizione in SQL, unità, owner, stato di certificazione. Il secondo è il modello dimensionale: quali tabelle sono fatti, quali sono dimensioni, qual è la chiave di join canonica tra ciascuna coppia. Il terzo è il vocabolario dei sinonimi, perché gli utenti dicono fatturato, ricavi, sales e GMV per cose che per voi sono quattro metriche diverse. Il quarto è il registro delle regole temporali: quale colonna temporale usare per ciascuna metrica, come trattare ritardi e rettifiche, quando una metrica è provvisoria e quando è consolidata.

La forma pratica più diffusa è un file YAML versionato in Git, con una struttura simile a quella di dbt Semantic Layer, Cube o LookML. Ogni metrica dichiara misura, filtro, granularità temporale e dimensioni consentite per lo slicing. Un esempio minimale rende l’idea meglio di mille descrizioni, e lo vediamo nel prossimo blocco di codice: l’agente non riceve lo schema grezzo del warehouse ma questo catalogo, più un sottoinsieme di tabelle fisiche esposte in sola lettura.

# semantic/metrics/revenue_net.yaml — definizioni certificate, versionate in Git
metrics:
  - name: revenue_net
    owner: finanza  # chi approva le modifiche a questa metrica
    status: certified
    unit: EUR
    description: "Incassato meno rimborsi entro 30 giorni, cambio BCE del giorno."
    sql: >
      SUM(o.amount_eur) - COALESCE(SUM(r.refund_eur), 0)
    grain: order_id  # una riga per ordine: vietato joinare righe senza aggregare prima
    time_column: o.paid_at  # mai created_at per questa metrica
    allowed_dimensions: [sales_channel, country, plan_tier]
    freshness_sla_hours: 26  # prima di questa soglia il dato è provvisorio
    synonyms: [fatturato netto, ricavi netti, net revenue]

Con un catalogo così, il prompt di sistema dell’agente cambia natura: invece di sei un esperto SQL, ecco lo schema, diventa puoi usare solo queste metriche e questi join; se la domanda non mappa su nessuna metrica certificata, dichiara che è esplorazione e non citare numeri come ufficiali. È una frase che vale più di dieci pagine di istruzioni generiche, perché sposta l’errore dal silenzio alla trasparenza: l’agente che non trova la metrica lo dice, invece di improvvisare.

Il percorso di una domanda: dalla proposta alla metrica approvata

Ogni richiesta che arriva all’agente cade in uno di tre secchi, e il comportamento cambia radicalmente tra l’uno e l’altro. Il secchio verde è la metrica certificata con slice consentito: fatturato netto Q3 per canale mappa su revenue_net con dimensione sales_channel, l’agente compone la query dai pezzi certificati, la esegue in sola lettura e risponde con numero più link alla definizione. Il secchio giallo è l’esplorazione legittima: c’è correlazione tra ticket di supporto e reso entro 14 giorni. Nessuna metrica certificata copre la domanda, quindi l’agente lavora su una sandbox o su un campione, etichetta l’output come non ufficiale e propone, se il risultato merita, una nuova metrica da certificare. Il secchio rosso è il fuori perimetro: dati personali non aggregati, tabelle non esposte, scritture. Qui l’agente si ferma e chiede a un umano.

Il passaggio dal giallo al verde è l’approval vero e proprio, e conviene trattarlo come una pull request: l’agente apre una proposta con definizione SQL, motivazione, query di validazione e confronto con la metrica più vicina già esistente; un owner di dominio la revisiona, chiede modifiche o la fonde. La tabella sotto riassume cosa deve contenere una proposta per essere revisionabile in meno di dieci minuti, che è la soglia pratica oltre la quale le review non vengono più fatte.

Campo della propostaPerché il revisore ne ha bisogno
Domanda originale e utente richiedenteCapire se la metrica serve a una decisione o a una curiosità
SQL della misura e filtri applicatiRiprodurre il numero senza rieseguire l’agente
Grain dichiarato e join usatiIndividuare duplicazioni e fan-out
Confronto con baseline esistenteEvitare la quarantunesima variante di churn
Soglia di freschezza e provvisorietàSapere se il numero può ancora muoversi
Rischi e dati esclusiDocumentare ordini di test, rimborsi tardivi, cambi

Un dettaglio che distingue i sistemi maturi: la proposta include sempre una query di validazione che deve fallire se la metrica è rotta, per esempio un controllo che revenue_net non superi mai il lordo sullo stesso perimetro. Se l’agente non sa scrivere il test che ucciderebbe la sua stessa metrica, la proposta torna indietro.

Dove gli agenti sbagliano davvero: grain, join e tempo

Tre classi di errore coprono la maggioranza degli incidenti da Agentic SQL, e tutte e tre sono prevenibili con vincoli dichiarativi piuttosto che con prompt più lunghi. Il fan-out da join è la prima: l’agente unisce ordini con righe ordine e poi somma l’importo a livello ordine, moltiplicando il fatturato per il numero medio di righe. La difesa è il vincolo di grain nel catalogo, la metrica dichiara una riga per ordine, più un guardrail di esecuzione che confronta il conteggio righe pre e post join e blocca la query se esplode oltre soglia.

La seconda classe è l’errore temporale. Le metriche di business quasi mai usano la colonna temporale ovvia: il fatturato va per data di pagamento, non di creazione ordine; gli utenti attivi per data di evento, non di registrazione; i rimborsi arrivano con settimane di ritardo e riscrivono il passato. Un agente lasciato libero filtra sulla prima colonna data che trova. La difesa è dichiarare la colonna temporale per metrica e vietare filtri temporali su altre colonne quando si usa quella metrica. Sembra rigido, ed è esattamente il punto: la rigidità qui è conoscenza aziendale codificata.

La terza classe è il denominatore sbagliato nei rapporti. Il tasso di reso per canale richiede che numeratore e denominatore condividano perimetro e finestra: resi di ordini pagati nel trimestre diviso ordini pagati nello stesso trimestre, non resi registrati nel trimestre diviso ordini creati nel trimestre. La regola che il revisore deve verificare è sempre la stessa: se perimetro o finestra differiscono tra sopra e sotto la riga, il tasso è spazzatura anche con SQL perfetto. Il codice sotto mostra il pattern corretto per una metrica con join a rischio fan-out: aggregare prima al grain giusto in due CTE separate, poi unire. È il pattern che l’agente deve usare sempre, e che il guardrail può verificare staticamente.

-- Pattern anti fan-out: aggrega prima, unisci dopo (grain = order_id)
WITH gross AS (
  -- Ricavo lordo aggregato a livello ordine: una riga per ordine
  SELECT o.order_id, o.sales_channel, SUM(o.amount_eur) AS gross_eur
  FROM marts.orders AS o
  WHERE o.paid_at >= '2026-07-01' AND o.paid_at < '2026-10-01'
    AND o.is_test_order = FALSE  -- esclusione ordini di test: mai dimenticarla
  GROUP BY o.order_id, o.sales_channel
),
refunds AS (
  -- Rimborsi entro 30 giorni, aggregati allo stesso grain
  SELECT r.order_id, SUM(r.refund_eur) AS refund_eur
  FROM marts.refunds AS r
  WHERE r.refund_at < '2026-11-01'  -- finestra estesa: copre i rimborsi tardivi del Q3
  GROUP BY r.order_id
)
SELECT g.sales_channel,
       SUM(g.gross_eur) - COALESCE(SUM(r.refund_eur), 0) AS revenue_net_eur
FROM gross AS g
LEFT JOIN refunds AS r USING (order_id)  -- join su chiave al grain corretto: nessun fan-out
GROUP BY g.sales_channel;

Costi, permessi e guardrail che girano in produzione

Un agente SQL senza limiti di esecuzione è un assegno in bianco al warehouse. Una scansione accidentale di una tabella eventi da decine di terabyte può costare centinaia di euro e bloccare slot di computazione per un’ora. I guardrail operativi minimi sono cinque e vanno applicati nell’ordine in cui una query li incontra: parsing statico (solo letture, nessuna DDL o DML, tabelle tutte nella allowlist), stima del costo (se supera la soglia, serve approvazione esplicita), limiti di esecuzione (timeout, righe massime, slot dedicati a bassa priorità per le query degli agenti), mascheramento dei dati sensibili (email, nominativi e identificativi mai in chiaro all’agente, solo aggregati con soglia minima di 10 soggetti per cella), e audit completo (domanda originale, SQL generato, righe lette, costo, utente richiedente).

I permessi meritano un’attenzione separata perché qui si gioca la sicurezza vera. L’agente interroga il warehouse con un ruolo dedicato in sola lettura, limitato alle viste e ai mart esposti, mai alle tabelle raw con PII. Le scritture, creare una vista, materializzare una metrica, aggiornare il catalogo, passano sempre per una proposta Git che un umano fonde, mai per esecuzione diretta. Quando la domanda tocca dati personali non aggregabili, la risposta corretta dell’agente è un rifiuto motivato con alternativa, per esempio il tasso di rimborso per canale sopra soglia di anonimato invece dell’elenco clienti, non un tentativo creativo di aggirare il divieto.

Far rispondere l’agente senza dargli le chiavi del warehouse

L’architettura che funziona meglio separa tre ruoli: l’agente ragiona e compone, un validatore deterministico controlla, il warehouse esegue. L’agente non ha mai credenziali dirette al database: chiama un tool che accetta solo SQL già verificato, oppure propone SQL che il validatore riscrive vincolandolo alle definizioni certificate. Il validatore è codice normale, non un altro modello linguistico: parser SQL che rifiuta tutto ciò che non è lettura, controllo allowlist su tabelle e colonne, riscrittura automatica dei filtri temporali sulla colonna corretta, iniezione di limiti e timeout, dry-run con stima dei byte scansionati.

Il codice sotto mostra lo scheletro del validatore in Python, volutamente semplificato ma fedele nella logica: ogni rifiuto spiega il motivo in modo che l’agente possa correggersi da solo al tentativo successivo, invece di ricevere un generico errore.

# validatore deterministico: l'agente propone, questo codice decide (niente LLM qui)
import sqlglot  # parser SQL: trasforma la query in albero verificabile

ALLOWLIST = {"marts.orders", "marts.refunds", "marts.dim_channel"}
MAX_BYTES = 50_000_000_000  # dry-run oltre 50 GB: serve approvazione umana
FORBIDDEN = {"insert", "update", "delete", "drop", "grant", "copy", "unload"}

def valida(sql: str, metrica: dict) -> tuple[bool, str]:
    """Controlla la query dell'agente contro catalogo e policy. Ritorna (ok, motivo)."""
    albero = sqlglot.parse_one(sql)  # se il SQL non compila, si ferma qui
    # 1. Solo letture: blocca scritture e comandi amministrativi
    verbi = {n.key for n in albero.walk() if n.key in FORBIDDEN}
    if verbi:
        return False, f"verbi vietati rilevati: {verbi}"
    # 2. Solo tabelle esposte all'agente, mai raw con PII
    usate = {t.db + "." + t.name for t in albero.find_all(sqlglot.exp.Table)}
    if not usate.issubset(ALLOWLIST):
        return False, f"tabelle fuori perimetro: {usate - ALLOWLIST}"
    # 3. Il filtro temporale deve usare la colonna certificata della metrica
    if metrica["time_column"] not in sql:
        return False, f"usa la colonna temporale {metrica['time_column']}"
    return True, "ok: passa a dry-run e esecuzione con timeout"

Il punto architetturale è che il validatore non deve essere intelligente: deve essere prevedibile. Quando l’agente scopre un nuovo modo di sbagliare, aggiungete un controllo deterministico, non una riga al prompt. Dopo qualche mese avrete una suite di controlli che documenta tutti gli incidenti mai accaduti, molto più utile di un prompt di sistema di tremila parole che nessuno legge più.

Metriche versionate e lineage che un revisore capisce davvero

Una metrica senza versione è un’opinione. Nel momento in cui il ricavo netto cambia definizione, perché la finestra dei rimborsi passa da 30 a 45 giorni o la fonte dei cambi passa dal gestionale alla BCE, ogni numero storico calcolato con la vecchia formula diventa non confrontabile con i nuovi. La pratica corretta è versionare il catalogo in Git con changelog obbligatorio: ogni modifica dichiara cosa cambia, da quando vale, se i valori storici vengono ricalcolati e chi ha approvato. L’agente cita sempre la versione, per esempio ricavo netto versione 14 consolidato al 4 settembre, così il lettore del report sa esattamente cosa sta guardando.

Il lineage completa il quadro: per ogni numero, poter risalire a query eseguita, versione della metrica, snapshot dei dati e costo. In pratica significa salvare a ogni risposta dell’agente un record con domanda originale, SQL finale, hash del catalogo, timestamp di esecuzione e stato di freschezza. Sembra burocrazia finché non arriva il giorno in cui la finanza chiede perché il numero di martedì differisce da quello di venerdì, e la risposta è nei log, non nella memoria di qualcuno. La tabella sotto mostra il contenuto minimo di quel record di lineage.

Campo del recordEsempio concreto
Domanda originale”Fatturato netto Q3 per canale, al netto rimborsi?”
Metrica e versionerevenue_net v14, owner finanza
SQL eseguitoHash della query più testo completo archiviato
Finestra e freschezzaQ3 su paid_at, rimborsi fino al 31 ott, provvisorio
Costo e volume8,2 GB scansionati, 1.140 righe lette, 4 secondi
Esito approvalRisposta diretta verde, oppure link alla PR gialla

Un corollario importante: i numeri provvisori vanno etichettati come tali nell’output, non in una nota a piè di pagina. Se i rimborsi tardivi possono ancora muovere il Q3 del 2-3 per cento, l’agente lo scrive nel messaggio, con oscillazione attesa, non lo nasconde. La fiducia nei dati si costruisce mostrando l’incertezza, non fingendo precisione.

Mettere tutto in produzione senza perdere il controllo

L’adozione conviene procedere per fasi, ciascuna con un criterio di uscita misurabile. La fase uno espone all’agente cinque-dieci metriche certificate su un singolo dominio, tipicamente vendite o prodotto, con esecuzione in sola lettura e dry-run obbligatorio. Il criterio di uscita è zero incidenti semantici per un mese: nessun numero contestato dalla finanza, nessun superamento di budget di scansione. La fase due aggiunge il flusso di proposta: l’agente può suggerire nuove metriche via pull request e gli owner iniziano a revisionarle. Il criterio di uscita è un tempo mediano di review sotto i due giorni lavorativi, altrimenti le proposte si accumulano e l’agente viene bypassato. La fase tre estende a nuovi domini e introduce il monitoraggio continuo: ogni notte un job riesegue le metriche certificate su finestre fisse e segnala derive oltre soglia, perché un cambio upstream nello schema o nei job dbt può rompere definizioni che ieri erano corrette.

Il monitoraggio merita due controlli specifici per i sistemi agentici. Il primo confronta le risposte dell’agente con ricalcoli deterministici sulle stesse versioni di metrica: se divergono, qualcuno ha modificato il prompt o il catalogo senza passare dall’approval. Il secondo traccia la quota di domande che cadono nel secchio giallo: se l’esplorazione cresce oltre il 30-40 per cento del totale, il catalogo è troppo povero e gli utenti stanno prendendo decisioni su numeri non certificati senza saperlo. Entrambi i segnali vanno su una dashboard che il data team guarda davvero, non in un log che nessuno apre.

Ci sono casi in cui questo intero impianto è eccessivo e va detto chiaramente. Per un prototipo interno su dati sintetici, per un’analisi una tantum che un analista revisiona riga per riga, o per un team di tre persone dove l’owner delle metriche siede alla scrivania accanto, il catalogo YAML e l’approval formale aggiungono attrito senza ridurre rischi reali. Il criterio è proporzionalità: appena un numero prodotto con aiuto dell’agente finisce in una decisione di budget, in un report esterno o in una dashboard che altri consultano senza parlare con voi, la certificazione smette di essere burocrazia e diventa l’unica cosa che separa un dato da un’opinione ben formattata. Fino ad allora, un prompt onesto che dichiara i limiti e un revisore umano attento sono un punto di partenza dignitoso.

Per orientarsi sugli strumenti, i riferimenti tecnici essenziali restano la documentazione di dbt Semantic Layer per la modellazione delle metriche, di Cube o LookML come alternative di catalogo, e delle guide OpenAI Agents SDK e LangGraph per il disegno del loop agente-validatore. La tecnologia specifica cambierà; l’ordine dei fattori no: prima le definizioni, poi i permessi, poi l’agente. Chi parte dall’agente e promette di aggiungere i controlli dopo, di solito scopre che il dopo non arriva mai.

Verdetto: metrica certificata con slice consentito risponde subito, esplorazione nuova lavora in sandbox con etichetta non ufficiale, e tutto ciò che tocca perimetro, PII o produzione passa da proposta Git firmata dall’owner.

Il modello in una frase

Il semantic layer con approval decide se una domanda agentica usa una metrica certificata versionata oppure resta esplorazione non ufficiale in sandbox.

La sequenza operativa in cinque passi

  1. Classifica la domanda in metrica certificata, esplorazione legittima o fuori perimetro.
  2. Componi la query solo da metriche, join e colonne temporali certificate.
  3. Valida il SQL con parser deterministico, allowlist e dry-run sotto soglia.
  4. Apri una proposta versionata per ogni metrica nuova con test che la ucciderebbe.
  5. Registra lineage con versione, freschezza e costo, ed etichetta i numeri provvisori.

Un caso reale: quanto vale una definizione unica

Nel giugno 2019 Google ha annunciato l’acquisizione di Looker per 2,6 miliardi di dollari, operazione chiusa nel febbraio 2020. Looker aveva costruito il suo valore su LookML, un layer semantico centralizzato dove ogni metrica ha una sola definizione condivisa. Il prezzo pagato mostra quanto vale una definizione unica: senza layer semantico ogni team riscrive le metriche e i numeri divergono. Con l’approval sulle definizioni, la query agentica resta dentro binari certificati invece di improvvisare.

Domande per chiudere la lezione

  1. Quando una domanda cade nel secchio verde, giallo o rosso?
  2. Cosa deve contenere una proposta di metrica per essere revisionata in dieci minuti?
  3. Come impedisci fan-out da join e filtri sulla colonna temporale sbagliata?
  4. Quale record di lineage alleghi a ogni numero consegnato?
Serve una mano concreta?

Bloccato su questo argomento o vuoi applicarlo al tuo caso? Prenota una call di 15 minuti con un analista esperto.

Prenota una call