PRODSoeverein Europees BaaS-platformOpen Dashboard →

Native AI · 10 min gelezen

Beveiligde Postgres-agent met functieaanroep (tutorial)

Affane Daylami · Fondateur · 21 maart 2026

Terug naar blog

Een agent die Postgres ondervraagt, stelt een specifieke beveiligingsvraag vóór de eerste regel code: Welke functie stelt u bloot aan het model? Als de tool die de LLM kan aanroepen rechtstreeks de SQL uitvoert die hij zelf heeft geschreven, is een dubbelzinnige vraag of een snelle injectie voldoende om elke tabel in het project te lezen.

Deze Engelse tekst is automatisch gegenereerd op basis van het Franse origineel en is nog niet beoordeeld.
Deze pagina is automatisch vertaald. De Engelse versie is gezaghebbend.

In deze zelfstudie wordt een agent gebouwd met functieaanroepen waarbij de tool die aan het model wordt blootgesteld nooit willekeurige SQL uitvoert. Het combineert twee mechanismen die al zijn geverifieerd in de Aurabase-code: de NL2SQL-validator en een alleen-lezen Postgres-transactie, twee bouwstenen van denative AI geïntegreerd in debackend. Vereisten: een Aurabase-project, de service_role-sleutel en een account bij een van de drie native LLM-providers (OpenAI, Anthropic, Gemini).

De essentie

  • Het echte risico is niet de functie die zichzelf aanroept, maar de tool die aan het model wordt blootgesteld: een onbewerkte execute_sql(query) geeft het volledige SQL-toegang.
  • De veilige architectuur beschikt over een query_database(question)-tool die delegeert aan een syntaxisboomvalidator (alleen SELECT, begrensd LIMIT, geïsoleerd schema) in plaats van directe uitvoering.
  • Aurabase stelt deze validator native beschikbaar (/nl2sql): door deze te hergebruiken als een implementatie van de tool vermijdt u dat u de SQL-validatie zelf opnieuw hoeft te coderen.
  • De vastgelegde SQL wordt vervolgens uitgevoerd via aura.db.sql() in de readOnly: true-modus, een daadwerkelijke alleen-lezen Postgres-transactie, en niet een eenvoudig tekstueel filter.
  • Het oorspronkelijke /chat-eindpunt van Aurabase accepteert nog geen tool-rol, noch een tools-parameter (geverifieerd in de code): de agentlus loopt momenteel via de SDK van de LLM-provider, niet via de Aurabase-proxy.
  • De sleutel service_role omzeilt RLS door zijn ontwerp: hij mag uw backend nooit verlaten en de agent erft bredere toegang dan een typische geverifieerde gebruiker.
#
Doelstelling

Wat je gaat bouwen

U bouwt een agent die vragen in natuurlijke taal over gegevens in een Postgres-project beantwoordt, zonder het model ooit SQL te laten schrijven die wordt uitgevoerd zoals het is. Het model roept een tool aan genaamd query_database, deze tool vertaalt de vraag naar SQL gevalideerd via NL2SQL, voert vervolgens deze alleen-lezen SQL uit en stuurt de regels terug naar het model zodat het zijn antwoord formuleert.

Info

Deze tutorial gebruikt de @aurabase/aurabase-js JavaScript SDK aan de serverzijde (nooit aan de browserzijde, de service_role sleutel mag niet zichtbaar zijn voor de client) en de OpenAI-functieaanroep-API voor de agentlus. Hetzelfde principe is van toepassing op de Anthropic of Gemini SDK.

#
Onder de motorkap

Waarom een “run this SQL”-tool gevaarlijk is

De meeste tutorials voor agenten van Postgres, inclusief enkele officiële handleidingen, definiëren één enkele tool: een execute_sql-functie die een SQL-tekenreeks als argument neemt en deze uitvoert zoals hij is. Het model schrijft deze string zelf, op basis van de vraag van de gebruiker en het schema dat eraan wordt gegeven in de context.

tool-schema-dangereux.json (anti-patroon)json
{
  "name": "execute_sql",
  "parameters": {
    "query": { "type": "string" }  // het model schrijft SQL rechtstreeks
  }
}

Deze keuze draagt ​​een verantwoordelijkheid over aan het model die het niet op betrouwbare wijze kan vervullen. Een promptinjectie in de vraag kan destructieve SQL opleveren die de tool zonder onderscheid uitvoert, omdat deze geen idee heeft hoe een 'legitieme' query eruit zou moeten zien. Ons speciale artikel beschrijft deze aanvalsvector: NL2SQL beveiligen tegen SQL-injectie.

Het alternatief dat in deze tutorial is ingebouwd, maakt gebruik van een smallere tool, query_database(question). Het model kan niet langer rechtstreeks SQL schrijven: het kan alleen een vraag stellen in zijn eigen tool call. Het is de Aurabase NL2SQL-engine die deze vraag in SQL vertaalt, voordat deze door een syntactische boomvalidator wordt geleid (alleen SELECT, geen subquery's, tien geautoriseerde functies, beperkte LIMIT).

De systeemprompt is geen veiligheidscontrole

Een execute_sql(query: string)-tool geeft het model volledige SQL-toegang, ongeacht hoe goed uw systeemprompt is. Een instructie ("voert alleen SELECT's uit") blijft een instructie die het model kan volgen, verkeerd interpreteren of omzeild kan zien door een injectie in de vraag van de gebruiker.

#
Stap 1

Definieer het schema van de tool die aan het model wordt blootgesteld

De drie native LLM-providers van Aurabase (OpenAI, Anthropic, Gemini) accepteren een tabel met tooldefinities in JSON Schema-indeling. Eén enkele tool is voldoende voor deze agent: query_database, waarmee een vraag in natuurlijke taal kan worden gesteld en niets anders. Het model ziet noch het SQL-schema, noch een query-veld dat het zelf zou kunnen invullen.

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
      },
    },
  },
]
#
Stap 2

Implementeer de tool: NL2SQL en daarna alleen-lezen

De toolhandler draait op uw backend, nooit in de browser. Het bevat de projectsleutel service_role, die RLS door het ontwerp omzeilt en daarom nooit aan een client mag worden blootgesteld. Er worden twee oproepen gedaan naar de Aurabase SDK.

De eerste aanroep vertaalt de vraag in SQL, gevalideerd via aura.ai.nl2sql(): alleen SELECT, LIMIT beperkt, geen toegang tot de systeemcatalogus. De tweede voert deze SQL uit die al is gevalideerd via aura.db.sql(), met de optie readOnly: true: Postgres weigert vervolgens zelf elk schrijven in deze transactie, onafhankelijk van de tekstuele validatie die NL2SQL upstream al heeft toegepast.

server/tools/query-database.tstypescript
// Client geïnitialiseerd met de service_role-sleutel, nooit aan de browserzijde
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 activeert een echte alleen-lezen Postgres-transactie: de engine weigert het schrijven, het is geen filter dat op de verzoektekst wordt toegepast. Gecombineerd met de SELECT-only-validatie van NL2SQL heeft de agent twee onafhankelijke lagen: als er in de ene een fout zit, blijft de andere dat ook doen.

#
Stap 3

De agentlus: functie die de SDK-kant van de leverancier aanroept

Aurabase stelt drie native LLM-providers beschikbaar, maar het /chat-eindpunt geeft nog geen tools-parameter of tool-rol door. ChatOptions bevat alleen temperature, max_tokens en model, en geaccepteerde rollen zijn beperkt tot system, user en assistant (geverifieerd in llm/mod.rs en handlers/chat.rs). De functieaanroepende lus loopt daarom vandaag rechtstreeks via de SDK van de provider, niet via de Aurabase-proxy.

Huidige beperking, geen definitieve keuze

Zolang Aurabase niet native tooloproepen orkestreert, moet uw backend de lus zelf beheren met de OpenAI, Anthropic of Gemini SDK. NL2SQL- en SQL-uitvoering blijven klassieke Aurabase-aanroepen binnen deze lus.

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
}

Het principe blijft hetzelfde als u de agent orkestreert met LangChain of een service zoals Azure AI Agent: de tool die in het raamwerk is gedeclareerd, moet dezelfde query_databaseblijven, nooit een onbewerkte SQL-uitvoerder. Onze vergelijkingsdetails laten zien waar LangChain en LlamaIndex echte waarde bieden op Postgres, en waar ze vooral complexiteit toevoegen: Postgres-agents met LangChain of LlamaIndex.

#
Stap 4

Test met een echte vraag

Vraag verzonden naar agent: “Hoeveel premiumklanten hebben deze maand een bestelling geplaatst?” ". De sjabloon roept query_database aan met deze vraag zoals deze is, zonder ooit een SQL te zien of te schrijven. Hier is het resultaat van de twee interne oproepen die door de tool zijn geactiveerd.

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

Het uiteindelijke antwoord van het model is gebaseerd op deze werkelijke lijnen, en niet op een gok. Als de tool nul rijen retourneert, wordt een getallenhallucinatie aanzienlijk minder waarschijnlijk dan bij een model dat zou reageren zonder geverifieerde gegevens.

#
Beveiliging

Beveilig de agent voordat deze in productie gaat

  • De sleutel service_role verlaat nooit uw backend: noch in de prompt die naar het model wordt verzonden, noch in een logbestand, noch in een omgevingsvariabele aan de clientzijde.
  • readOnly: true blijft actief op aura.db.sql() voor deze specifieke tool, zelfs als uw project elders in de applicatie moet worden geschreven.
  • service_role omzeilt RLS door zijn ontwerp. Als de agent anders moet reageren, afhankelijk van de gebruiker die de vraag stelt, filter dan expliciet de SQL in of val terug op klassieke PostgREST-eindpunten, die RLS respecteren. Zie RLS-isolatie voor meerdere tenants.
  • Registreer elke tooloproep (stelde vraag, SQL gevalideerd, aantal regels): dit is de enige bruikbare tracering als een vraag een onverwacht resultaat oplevert.
  • De tarieflimiet en het maandelijkse quotum van Aurabase gelden al per project op /nl2sql: een spraakzame agent kan niet stilletjes uw AI-budget overschrijden.
#
Eerlijkheid

Huidige limieten waar u rekening mee moet houden

De tool query_database erft alle beperkingen van de NL2SQL-validator: geen subquery's, geen CTE/WITH, geen UNION en een gesloten lijst van tien SQL-functies. Een vraag die uiteraard om een ​​subquery vraagt ​​(“klanten die nog nooit besteld hebben”) moet opnieuw geformuleerd worden, of verwerkt worden door een tweede speciale tool, in plaats van in NL2SQL geforceerd te worden.

Er bestaat momenteel geen orkestratie van tooloproepen binnen de Aurabase /chat proxy: de hier beschreven agentlus bevindt zich in uw applicatiecode, niet in een beheerde service. Als de agent meerdere tools aan elkaar moet koppelen (bijvoorbeeld database en documentaire RAG), is het uw backend die de twee oproepen orkestreert.

#
Ga verder

RAG en functieaanroepen gecombineerd

Deze tutorial behandelt gestructureerde vragen over relationele gegevens. Voor vragen over ongestructureerde inhoud (documenten, tickets, notities) kan dezelfde agent een tweede tool beschikbaar stellen die is verbonden met de native RAG van Aurabase (pgvector, HNSW search). De twee mogelijkheden en hun articulatie worden gedetailleerd beschreven op de pagina Native AI op Postgres.

#
Veelgestelde vragen

Veelgestelde vragen

Kan ik de agent schrijftoegang geven (INSERT/UPDATE)?+
Technisch gezien wel, door de readOnly-optie te verwijderen en naar een aparte tool te verwijzen, maar dat is niet wat NL2SQL vandaag de dag doet: de validator staat alleen SELECT-query's toe, ongeacht de gekozen uitvoeringsoptie aan de clientzijde. Een schrijfagent vraagt ​​om een ​​aparte validator, met een eigen witte lijst met functies en waarschijnlijk menselijke bevestiging vóór uitvoering.
Is het compatibel met LangChain, LlamaIndex of een service zoals Azure AI Agent?+
Ja: deze raamwerken orkestreren de functieaanroepende lus voor u, maar de implementatie van de tool blijft van u. Dezelfde handler (NL2SQL en vervolgens alleen-lezen uitvoering) bedient zichzelf als een functie van de tool die is gedeclareerd in LangChain of in de Azure-agent, in plaats van ze onbewerkte SQL te laten uitvoeren.

KLAAR VOOR IMPLEMENTATIE?

Uw backend in vijf minuten.

Geen creditcard vereist · 500 MB gratis · 50.000 MAU