PRODPiattaforma BaaS europea sovranaApri Dashboard →

IA nativa · 10 lettura minima

Agente sicuro Postgres con chiamata di funzione (tutorial)

Affane Daylami · Fondateur · 21 marzo 2026

Torniamo al blog

Un agente che interroga Postgres pone una domanda di sicurezza specifica prima della prima riga di codice: quale funzione stai esponendo al modello? Se lo strumento che il LLM può chiamare esegue direttamente l'SQL che ha scritto da solo, è sufficiente una domanda ambigua o un prompt injection per leggere qualsiasi tabella del progetto.

Questo testo inglese è stato generato automaticamente dall'originale francese e non è stato ancora rivisto.
Questa pagina è stata tradotta automaticamente. Fa fede la versione inglese.

Questo tutorial crea un agente con chiamate di funzioni in cui lo strumento esposto al modello non esegue mai SQL arbitrario. Combina due meccanismi già verificati nel codice Aurabase: il validatore NL2SQL e una transazione Postgres di sola lettura, due mattoni dell'intelligenza artificiale nativaintegrata nel backend. Prerequisiti: un progetto Aurabase, la sua chiave service_rolee un account con uno dei tre fornitori LLM nativi (OpenAI, Anthropic, Gemini).

L'essenziale

  • Il rischio reale non è la funzione che chiama se stessa, ma lo strumento esposto al modello: un execute_sql(query) grezzo gli fornisce pieno accesso SQL.
  • L'architettura sicura espone uno strumento query_database(question) che delega a un validatore dell'albero della sintassi (solo SELECT, LIMIT limitato, schema isolato) anziché all'esecuzione diretta.
  • Aurabase espone questo validatore in modo nativo (/nl2sql): riutilizzarlo come implementazione dello strumento evita di dover ricodificare personalmente la validazione SQL.
  • L'SQL impegnato viene quindi eseguito tramite aura.db.sql() in modalità readOnly: true, un'effettiva transazione Postgres di sola lettura, non un semplice filtro testuale.
  • L'endpoint nativo /chat di Aurabase non accetta ancora un ruolo tool né un parametro tools (verificato nel codice): il loop dell'agente attualmente viene eseguito tramite l'SDK del provider LLM, non tramite il proxy Aurabase.
  • La chiave service_role ignora la RLS in base alla progettazione: non deve mai lasciare il backend e l'agente eredita un accesso più ampio rispetto a un tipico utente autenticato.
#
Obiettivo

Cosa costruirai

Costruirai un agente che risponde a domande in linguaggio naturale sui dati in un progetto Postgres, senza mai lasciare che il modello scriva SQL che viene eseguito così com'è. Il modello chiama uno strumento denominato query_database, questo strumento traduce la domanda in SQL convalidato tramite NL2SQL, quindi esegue questo SQL di sola lettura e restituisce le righe al modello in modo che formuli la sua risposta.

Informazioni

Questo tutorial utilizza @aurabase/aurabase-js JavaScript SDK sul lato server (mai sul lato browser, la chiave service_role non deve essere esposta al client) e la funzione OpenAI che chiama l'API per il loop dell'agente. Lo stesso principio si applica all'SDK Anthropic o Gemini.

#
Sotto il cofano

Perché uno strumento "esegui questo SQL" è pericoloso

La maggior parte dei tutorial sugli agenti Postgres, incluse alcune guide ufficiali, definiscono un singolo strumento: una funzione execute_sql che accetta una stringa SQL come argomento e la esegue così com'è. Il modello scrive questa stringa da solo, in base alla domanda dell'utente e allo schema fornito nel contesto.

tool-schema-dangereux.json (anti-modello)json
{
  "name": "execute_sql",
  "parameters": {
    "query": { "type": "string" }  // il modello scrive direttamente SQL
  }
}

Questa scelta trasferisce al modello una responsabilità che non può adempiere in modo affidabile. Una pronta iniezione inserita nella domanda può produrre SQL distruttivo che lo strumento esegue indiscriminatamente, poiché non ha idea di come dovrebbe apparire una query "legittima". Il nostro articolo dedicato descrive in dettaglio questo vettore di attacco: proteggere NL2SQL dall'iniezione SQL.

L'alternativa creata in questo tutorial espone uno strumento più ristretto, query_database(question). Il modello non può più scrivere direttamente SQL: può solo porre una domanda nella propria chiamata allo strumento. È il motore Aurabase NL2SQL che traduce questa domanda in SQL, prima di passarla attraverso un validatore di albero sintattico (solo SELECT, nessuna sottoquery, dieci funzioni autorizzate, LIMITE limitato).

La richiesta di sistema non è un controllo di sicurezza

Uno strumento execute_sql(query: string) fornisce al modello l'accesso SQL completo, indipendentemente dalla qualità del prompt del sistema. Un'istruzione ("esegue solo SELECT") rimane un'istruzione che il modello può seguire, interpretare erroneamente o vedere aggirata da un'iniezione inserita nella domanda dell'utente.

#
Passaggio 1

Definire lo schema dello strumento esposto al modello

I tre fornitori LLM nativi di Aurabase (OpenAI, Anthropic, Gemini) accettano una tabella di definizioni di strumenti in formato JSON Schema. A questo agente basta un unico strumento: query_database, che accetta una domanda in linguaggio naturale e nient'altro. Il modello non vede né lo schema SQL né un campo query che potrebbe compilare da solo.

lib/agent-tools.tstypescript
export const tools = [
  {
    type: 'function',
    function: {
      name: 'query_database',
      description:
        "Interroge les données du projet en langage naturel. N'accepte pas de SQL : posez une question.",
      parameters: {
        type: 'object',
        properties: {
          question: {
            type: 'string',
            description: 'Question en français sur les données du projet.'
          },
        },
        required: ['question'],
        additionalProperties: false
      },
    },
  },
]
#
Passaggio 2

Implementare lo strumento: NL2SQL quindi di sola lettura

Il gestore dello strumento viene eseguito sul tuo backend, mai nel browser. Contiene la chiave del progetto service_role, che ignora RLS in base alla progettazione e pertanto non dovrebbe mai essere esposta a un client. Effettua due chiamate all'SDK di Aurabase.

La prima chiamata traduce la domanda in SQL convalidato tramite aura.ai.nl2sql(): solo SELECT, LIMIT limitato, nessun accesso al catalogo di sistema. Il secondo esegue questo SQL già convalidato tramite aura.db.sql(), con l'opzione readOnly: true: Postgres stesso rifiuta quindi qualsiasi scrittura in questa transazione, indipendentemente dalla convalida testuale già applicata da NL2SQL upstream.

server/tools/query-database.tstypescript
// Client inizializzato con la chiave service_role, mai sul lato browser
import { aura } from '@/lib/aurabase'

export async function queryDatabase(question: string) {
  const { data: validated, error } = await aura.ai.nl2sql(
    question,
    undefined,
    { limit: 50 },
  )
  if (error) return { error: error.message }

  const { data: rows, error: execError } = await aura.db.sql(
    validated.sql,
    [],
    { readOnly: true },
  )
  if (execError) return { error: execError.message }

  return { sql: validated.sql, rows }
}
Astuce

readOnly: true innesca una vera e propria transazione Postgres di sola lettura: il motore rifiuta la scrittura, non è un filtro applicato al testo della richiesta. In combinazione con la convalida solo SELECT di NL2SQL, l'agente ha due livelli indipendenti: se uno presenta un difetto, l'altro è ancora valido.

#
Passaggio 3

Il ciclo dell'agente: funzione che richiama il lato SDK del fornitore

Aurabase espone tre provider LLM nativi, ma il suo endpoint /chat non inoltra ancora un parametro tools o un ruolo tool. ChatOptions trasporta solo temperature, max_tokens e modele i ruoli accettati sono limitati a system, user e assistant (verificati in llm/mod.rs e handlers/chat.rs). Il ciclo di chiamata delle funzioni viene quindi eseguito oggi direttamente tramite l'SDK del provider, non tramite il proxy Aurabase.

Limitazione attuale, non una scelta definitiva

Finché Aurabase non orchestra nativamente le chiamate agli strumenti, il tuo backend deve gestire il loop stesso con OpenAI, Anthropic o Gemini SDK. NL2SQL e l'esecuzione SQL rimangono le classiche chiamate Aurabase all'interno di questo ciclo.

server/agent.tstypescript
import OpenAI from 'openai'
import { tools } from './lib/agent-tools'
import { queryDatabase } from './tools/query-database'

const openai = new OpenAI()

export async function askAgent(question: string) {
  const messages = [{ role: 'user', content: question }]

  const first = await openai.chat.completions.create({
    model: 'gpt-4.1', messages, tools,
  })

  const call = first.choices[0].message.tool_calls?.[0]
  if (!call) return first.choices[0].message.content

  const args = JSON.parse(call.function.arguments)
  const result = await queryDatabase(args.question)

  const second = await openai.chat.completions.create({
    model: 'gpt-4.1',
    messages: [
      ...messages,
      first.choices[0].message,
      { role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) },
    ],
  })

  return second.choices[0].message.content
}

Il principio rimane lo stesso se si orchestra l'agente con LangChain o un servizio come Azure AI Agent: lo strumento dichiarato nel framework deve rimanere lo stesso query_database, mai un esecutore SQL grezzo. Il nostro confronto descrive in dettaglio dove LangChain e LlamaIndex forniscono valore reale su Postgres e dove aggiungono complessità in particolare: Agenti Postgres con LangChain o LlamaIndex.

#
Passaggio 4

Prova con una domanda reale

Domanda inviata all'agente: "Quanti clienti premium hanno effettuato un ordine questo mese?" ". Il modello chiama query_database con questa domanda così com'è, senza mai vedere o scrivere alcun codice SQL. Ecco il risultato delle due chiamate interne attivate dallo strumento.

risultato dello strumento (estratto)json
{
  "sql": "SELECT count(*) FROM orders WHERE customer_plan = 'premium' AND created_at >= date_trunc('month', now()) LIMIT 50",
  "rows": [{ "count": 128 }]
}

La risposta finale del modello si basa su queste linee effettive, non su un'ipotesi. Se lo strumento restituisce zero righe, un'allucinazione numerica diventa significativamente meno probabile rispetto a un modello che risponderebbe senza dati verificati.

#
Sicurezza

Proteggi l'agente prima di entrare in produzione

  • La chiave service_role non lascia mai il tuo backend: né nel prompt inviato al modello, né in un log, né in una variabile di ambiente lato client.
  • readOnly: true rimane attivo su aura.db.sql() per questo strumento specifico, anche se il tuo progetto necessita di essere scritto altrove nell'applicazione.
  • service_role ignora RLS in base alla progettazione. Se l'agente dovesse rispondere in modo diverso a seconda dell'utente che pone la domanda, filtrare esplicitamente l'SQL o ricorrere agli endpoint PostgREST classici, che rispettano la RLS. Vedere isolamento RLS multi-tenant.
  • Registra ogni chiamata dello strumento (domanda posta, SQL convalidato, numero di righe): questa è l'unica traccia utilizzabile se una domanda produce un risultato inaspettato.
  • La limitazione della tariffa e la quota mensile di Aurabase si applicano già al progetto su /nl2sql: un agente loquace non può superare silenziosamente il budget dell'IA.
#
Onestà

Limiti attuali di cui essere consapevoli

Lo strumento query_database eredita tutte le limitazioni del validatore NL2SQL: nessuna sottoquery, nessuna CTE/WITH, nessuna UNION e un elenco chiuso di dieci funzioni SQL. Una domanda che richiede naturalmente una sottoquery (“clienti che non hanno mai ordinato”) deve essere riformulata o elaborata da un secondo strumento dedicato anziché forzata in NL2SQL.

Oggi non esiste alcuna orchestrazione delle chiamate agli strumenti all'interno del proxy Aurabase /chat: il loop dell'agente qui descritto risiede nel codice dell'applicazione, non in un servizio gestito. Se l'agente deve concatenare diversi strumenti (database e RAG documentari, ad esempio), è il tuo backend che orchestra le due chiamate.

#
Vai oltre

RAG e chiamata di funzione combinati

Questo tutorial copre domande strutturate sui dati relazionali. Per domande su contenuti non strutturati (documenti, ticket, note), lo stesso agente può esporre un secondo strumento collegato al RAG nativo di Aurabase (pgvector, ricerca HNSW). Le due funzionalità e la loro articolazione sono dettagliate nella pagina Native AI on Postgres.

#
Domande frequenti

Domande frequenti

Posso concedere all'agente l'accesso in scrittura (INSERT/UPDATE)?+
Tecnicamente sì, rimuovendo l'opzione readOnly e puntando a uno strumento separato, ma non è quello che fa oggi NL2SQL: il validatore consente solo query SELECT, indipendentemente dall'opzione di esecuzione scelta lato client. Un agente di scrittura richiede un validatore separato, con la propria lista bianca di funzioni e probabilmente la conferma umana prima dell'esecuzione.
È compatibile con LangChain, LlamaIndex o un servizio come Azure AI Agent?+
Sì: questi framework orchestrano il ciclo di chiamate di funzione per te, ma l'implementazione dello strumento rimane tua. Lo stesso gestore (NL2SQL quindi esecuzione di sola lettura) si collega in funzione dello strumento dichiarato in LangChain o nell'agente di Azure, anziché consentire loro di eseguire SQL non elaborato.

PRONTO PER L'IMPLEMENTAZIONE?

Il tuo backend in cinque minuti.

Nessuna carta di credito richiesta · 500 MB gratuiti · 50.000 MAU