AI Engineering Academy · Les

Een interface voor databases in natuurlijke taal bouwen

Maak een systeem waarin gebruikers vragen in gewone taal stellen, het model via function calling SQL genereert, uw app de query veilig uitvoert en het model de resultaten toelicht.

Les 4 van 413 stappen

Een interface voor databases in natuurlijke taal bouwen is een gratis AI Engineering Academy-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject AI Engineering Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus AI Engineering Academy bevat in totaal 4 lessen.

Van natuurlijke taal naar SQL: de visie

Stel dat je aan je database vraagt: 'Welke klanten hebben vorige maand meer dan $1.000 uitgegeven?' en een antwoord krijgt — zonder ook maar één SQL-query te schrijven. Een database-interface in natuurlijke taal gebruikt function calling om de LLM SQL te laten genereren, je toepassing voert die veilig uit en het model beschrijft de resultaten in gewone taal. Dankzij dit patroon krijgen ook niet-technische gebruikers toegang tot gegevens.

Overzicht van de systeemarchitectuur

De NL-naar-SQL-pijplijn bestaat uit vier componenten die samenwerken:

  • Schematische context: De LLM ontvangt het schema van je database, zodat deze weet welke tabellen en kolommen bestaan.
  • SQL-generatie: Het model genereert een SQL-query als argument van een function call.
  • Veilige uitvoering: Je app valideert en voert de query uit en stuurt vervolgens de resultaten terug.
  • Beschrijving van resultaten: Het model ontvangt de queryresultaten en legt ze uit in natuurlijke taal.

De databasetool voor query's definiëren

Definieer een query_database-functie die een SQL SELECT-instructie accepteert. De schemabeschrijving in de functiedefinitie leert het model welke tabellen en kolommen beschikbaar zijn, zodat het nauwkeurige query's genereert zonder te hoeven gokken.

query_db_tool = {
    'type': 'function',
    'function': {
        'name': 'query_database',
        'description': '''Execute a read-only SQL query on the company database.
Use this to answer questions about customers, orders, and products.
Only SELECT statements are allowed. Never use DROP, DELETE, UPDATE, or INSERT.

Available tables:
- customers (id, name, email, created_at, country)
- orders (id, customer_id, total_amount, status, created_at)
- order_items (id, order_id, product_id, quantity, unit_price)
- products (id, name, category, price, stock_quantity)
''',
        'parameters': {
            'type': 'object',
            'properties': {
                'sql': {
                    'type': 'string',
                    'description': 'A valid PostgreSQL SELECT statement.'
                },
                'explanation': {
                    'type': 'string',
                    'description': 'One-sentence explanation of what this query does.'
                }
            },
            'required': ['sql', 'explanation']
        }
    }
}

Veilige uitvoering van SQL

Voer nooit onbewerkte SQL van het model uit zonder validatie. Implementeer een beveiligingslaag die alleen SELECT-instructies toestaat, gevaarlijke trefwoorden afwijst, het aantal resultaatrijen beperkt om geheugenproblemen te voorkomen en de query uitvoert in een alleen-lezen-databasetransactie. Defense in depth is cruciaal bij het uitvoeren van door een LLM gegenereerde code.

import re
import psycopg2

DANGEROUS_KEYWORDS = ['DROP', 'DELETE', 'UPDATE', 'INSERT', 'TRUNCATE', 'ALTER', 'CREATE', 'EXEC']

def execute_safe_query(sql: str, max_rows: int = 100) -> list:
    '''Execute a read-only SQL query with safety guards.'''
    sql_upper = sql.upper().strip()

    # Only allow SELECT
    if not sql_upper.startswith('SELECT'):
        raise ValueError('Only SELECT statements are allowed.')

    # Block dangerous keywords
    for keyword in DANGEROUS_KEYWORDS:
        if re.search(r'\b' + keyword + r'\b', sql_upper):
            raise ValueError(f'Forbidden keyword: {keyword}')

    conn = psycopg2.connect('postgresql://readonly_user:pass@localhost/appdb')
    with conn:
        with conn.cursor() as cur:
            # Enforce read-only transaction
            cur.execute('SET TRANSACTION READ ONLY')
            cur.execute(sql)
            columns = [desc[0] for desc in cur.description]
            rows = cur.fetchmany(max_rows)
    return [dict(zip(columns, row)) for row in rows]

Schematische context in de systeemprompt invoegen

Het model genereert betere SQL wanneer het het volledige databaseschema kan zien. Bouw een systeemprompt met tabeldefinities, kolomnamen en typen en voorbeeldwaarden voor categorische kolommen. Zo weet het model of het country = 'US' of country_code = 'US' moet gebruiken, zonder te hoeven gokken.

SYSTEM_PROMPT = '''You are a data analyst assistant with access to the company database.
When users ask data questions, use the query_database tool to look up the answer.
Always explain your query in plain English before executing it.

Database schema:

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name VARCHAR NOT NULL,
    email VARCHAR UNIQUE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    country VARCHAR(2)  -- ISO 2-letter code: 'US', 'UK', 'DE', etc.
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id),
    total_amount NUMERIC(10,2),
    status VARCHAR  -- 'pending', 'shipped', 'delivered', 'cancelled'
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Only use columns that exist in the schema above.
'''

Queryresultaten voor het model opmaken

Onbewerkte databaseresultaten (lijsten met dictionaries) moeten als leesbare tekst worden opgemaakt voordat je ze naar het model terugstuurt. Zet de resultatenset om in een compacte weergave — een tabel of JSON-samenvatting — waarnaar het model kan verwijzen bij het beschrijven van het antwoord. Stuur geen duizenden rijen; vat grote resultatensets samen.

import json

def format_results(rows: list, max_display: int = 20) -> str:
    if not rows:
        return 'The query returned no results.'

    total = len(rows)
    display = rows[:max_display]

    # Format as a simple table
    if display:
        columns = list(display[0].keys())
        lines = [' | '.join(columns)]
        lines.append('-' * len(lines[0]))
        for row in display:
            lines.append(' | '.join(str(row[col]) for col in columns))

    result = '\n'.join(lines)
    if total > max_display:
        result += f'\n... ({total - max_display} more rows not shown)'
    return result

De volledige pijplijn implementeren

Alles samenvoegen: de functie die een gebruikersvraag verwerkt, het model aanroept om SQL te genereren, de query veilig uitvoert en de resultaten terugstuurt om ze te laten beschrijven. Het model ontvangt zowel de oorspronkelijke vraag als de queryresultaten en produceert vervolgens een antwoord in gewone taal.

from openai import OpenAI
import json

client = OpenAI()

def answer_data_question(user_question: str) -> str:
    messages = [
        {'role': 'system', 'content': SYSTEM_PROMPT},
        {'role': 'user', 'content': user_question}
    ]

    # First call: get SQL from model
    resp = client.chat.completions.create(
        model='gpt-4o', messages=messages, tools=[query_db_tool]
    )
    assistant_msg = resp.choices[0].message
    messages.append(assistant_msg)

    if resp.choices[0].finish_reason == 'tool_calls':
        tc = assistant_msg.tool_calls[0]
        args = json.loads(tc.function.arguments)
        print(f'Executing: {args["explanation"]}')
        print(f'SQL: {args["sql"]}')

        try:
            rows = execute_safe_query(args['sql'])
            result_text = format_results(rows)
        except ValueError as e:
            result_text = f'Query blocked: {str(e)}'

        messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result_text})

        # Second call: narrate results
        final = client.chat.completions.create(model='gpt-4o', messages=messages)
        return final.choices[0].message.content

    return assistant_msg.content

Gegevensvragen in meerdere stappen afhandelen

Voor complexe vragen kunnen meerdere query's nodig zijn. Voor 'Wie zijn onze vijf klanten met de hoogste omzet en wat zijn hun meest recente bestellingen?' zijn twee query's nodig: één om de belangrijkste klanten te vinden en vervolgens één om hun bestellingen op te halen. Laat het model meerdere opeenvolgende tool calls uitvoeren door de dispatch-lus meerdere keren te draaien totdat finish_reason='stop' is bereikt.

def answer_complex_question(user_question: str) -> str:
    messages = [
        {'role': 'system', 'content': SYSTEM_PROMPT},
        {'role': 'user', 'content': user_question}
    ]

    for _ in range(5):  # Max 5 query rounds
        resp = client.chat.completions.create(
            model='gpt-4o', messages=messages, tools=[query_db_tool]
        )
        msg = resp.choices[0].message
        messages.append(msg)

        if resp.choices[0].finish_reason == 'stop':
            return msg.content  # Model is done

        # Process tool call and loop
        tc = msg.tool_calls[0]
        args = json.loads(tc.function.arguments)
        try:
            rows = execute_safe_query(args['sql'])
            result = format_results(rows)
        except Exception as e:
            result = f'Error: {str(e)}'

        messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result})

    return 'Could not complete the analysis within the step limit.'

Risico's op SQL-injectie voorkomen

Zelfs met de beveiliging die alleen SELECT toestaat, kan een sluw model (of een kwaadwillende gebruiker) proberen gegevens te stelen via subquery's of trucs met commentaar. Aanvullende beveiligingen zijn onder andere: een alleen-lezen-databasegebruiker gebruiken die uitsluitend SELECT-rechten heeft, de uitvoering in een afzonderlijke verbindingspool laten plaatsvinden en controleren of de tabelnamen in de query overeenkomen met je lijst met toegestane schema-items.

ALLOWED_TABLES = {'customers', 'orders', 'order_items', 'products'}

def validate_tables_in_sql(sql: str) -> bool:
    '''Check that only whitelisted tables are referenced in the query.'''
    import sqlparse
    parsed = sqlparse.parse(sql)[0]
    table_names = set()
    from_seen = False
    for token in parsed.flatten():
        if token.ttype is sqlparse.tokens.Keyword and token.value.upper() in ('FROM', 'JOIN'):
            from_seen = True
        elif from_seen and token.ttype is sqlparse.tokens.Name:
            table_names.add(token.value.lower())
            from_seen = False
    unknown = table_names - ALLOWED_TABLES
    if unknown:
        raise ValueError(f'References unknown tables: {unknown}')
    return True

Veelgebruikte query's cachen

Veel bedrijfsvragen worden herhaaldelijk gesteld met hetzelfde antwoord: 'Hoeveel klanten hebben we?' 'Wat was de omzet van vorige maand?' Sla deze resultaten met een korte TTL op in Redis. Controleer de cache voordat je de query uitvoert — zo verminder je de belasting van de database en versnel je antwoorden op veelgestelde analytische vragen.

import redis
import hashlib
import json

r = redis.Redis.from_url('redis://localhost:6379')

def cached_query(sql: str, ttl_seconds: int = 300) -> list:
    cache_key = 'nl_query:' + hashlib.sha256(sql.encode()).hexdigest()
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    rows = execute_safe_query(sql)
    r.setex(cache_key, ttl_seconds, json.dumps(rows, default=str))
    return rows

Query's aan gebruikers uitleggen

Wek vertrouwen door gebruikers naast het antwoord in natuurlijke taal ook de gegenereerde SQL-query te tonen. Wanneer gebruikers 'Ik heb deze query uitgevoerd: SELECT COUNT(*) FROM customers WHERE country = ?UK?' kunnen zien, kunnen ze controleren of het antwoord klopt en SQL-patronen leren. Het veld explanation in ons toolschema is hiervoor ideaal.

Korte controle

Test je begrip van het bouwen van een database-interface in natuurlijke taal.

Samenvatting van de les

In deze les heb je geleerd: het toolschema van query_database voegt schematische context toe, zodat het model nauwkeurige SQL genereert, veiligheidsvalidatie niet-SELECT-instructies en gevaarlijke trefwoorden vóór uitvoering moet blokkeren en een lus met modelaanroepen analyse in meerdere stappen mogelijk maakt waarvoor opeenvolgende query's nodig zijn. Hierna verkennen we het Model Context Protocol (MCP), de open standaard om AI met externe tools te verbinden.

Gratis beginnen

Leer Python met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
30
Lessen
120

Veelgestelde vragen

Is de les “Een interface voor databases in natuurlijke taal bouwen” gratis?

Ja — de volledige tekst van “Een interface voor databases in natuurlijke taal bouwen” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus AI Engineering Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus AI Engineering Academy bevat in totaal 4 lessen.

Wat leer ik in “Een interface voor databases in natuurlijke taal bouwen”?

Maak een systeem waarin gebruikers vragen in gewone taal stellen, het model via function calling SQL genereert, uw app de query veilig uitvoert en het model de resultaten toelicht. Je oefent met AI Engineering Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met AI Engineering Academy te beginnen?

Ervaring vooraf is niet nodig. AI Engineering Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.

Hoe lang duurt de les “Een interface voor databases in natuurlijke taal bouwen”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over AI Engineering Academy?

Ja. Elke les over AI Engineering Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Functieschema's voor de API definiëren
  2. Toolaanroepen in uw applicatie verwerken
  3. Parallelle functieaanroepen
  4. Een interface voor databases in natuurlijke taal bouwen
← Terug naar AI Engineering Academy