0Pricing
AI Engineering Academy · Lektion

Eine natürlichsprachliche Datenbankschnittstelle entwickeln

Erstellen Sie ein System, in dem Nutzer Fragen in natürlicher Sprache stellen, das Modell per Function Calling SQL generiert, Ihre Anwendung die Abfrage sicher ausführt und das Modell die Ergebnisse erläutert.

Eine natürlichsprachliche Datenbankschnittstelle entwickeln ist eine kostenlose AI Engineering Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Engineering Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Von natürlicher Sprache zu SQL: Die Vision

Stellen Sie sich vor, Sie fragen Ihre Datenbank: „Welche Kunden haben im letzten Monat mehr als 1.000 $ ausgegeben?“ – und erhalten eine Antwort, ohne eine einzige SQL-Abfrage zu schreiben. Eine natürlichsprachliche Datenbankschnittstelle verwendet Function Calling, damit das LLM SQL generieren kann. Ihre Anwendung führt die Abfrage sicher aus, und das Modell erläutert die Ergebnisse in verständlicher Sprache. Dieses Muster macht den Datenzugriff auch für nicht technische Benutzer möglich.

Überblick über die Systemarchitektur

Die NL-to-SQL-Pipeline besteht aus vier Komponenten, die zusammenarbeiten:

  • Schema-Kontext: Das LLM erhält das Schema Ihrer Datenbank und weiß dadurch, welche Tabellen und Spalten vorhanden sind.
  • SQL-Generierung: Das Modell erzeugt eine SQL-Abfrage als Argument eines Function Calls.
  • Sichere Ausführung: Ihre Anwendung validiert und führt die Abfrage aus und gibt anschließend die Ergebnisse zurück.
  • Ergebnisbeschreibung: Das Modell erhält die Abfrageergebnisse und erläutert sie in natürlicher Sprache.

Das Tool für Datenbankabfragen definieren

Definieren Sie eine query_database-Funktion, die eine SQL-SELECT-Anweisung entgegennimmt. Die Schemainformationen in der Funktionsdefinition zeigen dem Modell, welche Tabellen und Spalten verfügbar sind. Dadurch kann es präzise Abfragen generieren, ohne raten zu müssen.

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']
        }
    }
}

Sichere SQL-Ausführung

Führen Sie niemals ungeprüftes SQL aus dem Modell ohne Validierung aus. Implementieren Sie eine Sicherheitsschicht, die ausschließlich SELECT-Anweisungen zulässt, gefährliche Schlüsselwörter ablehnt, die Anzahl der Ergebniszeilen zur Vermeidung von Speicherproblemen begrenzt und die Abfrage in einer schreibgeschützten Datenbanktransaktion ausführt. Defense in Depth ist beim Ausführen von durch LLMs generiertem Code entscheidend.

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]

Schema-Kontext in den System-Prompt einfügen

Das Modell generiert besseres SQL, wenn es das vollständige Datenbankschema sehen kann. Erstellen Sie einen System-Prompt, der Tabellendefinitionen, Spaltennamen und -typen sowie Beispielwerte für kategoriale Spalten enthält. So weiß das Modell, ob es country = 'US' oder country_code = 'US' verwenden soll, ohne raten zu müssen.

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.
'''

Abfrageergebnisse für das Modell formatieren

Rohe Datenbankergebnisse (Listen von Dicts) müssen vor dem Zurücksenden an das Modell als lesbarer Text formatiert werden. Wandeln Sie die Ergebnismenge in eine kompakte Darstellung um – etwa eine Tabelle oder eine JSON-Zusammenfassung –, auf die das Modell beim Erläutern der Antwort zurückgreifen kann. Vermeiden Sie das Senden von Tausenden Zeilen und fassen Sie große Ergebnismengen stattdessen zusammen.

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

Die vollständige Pipeline implementieren

Alles zusammenführen: Die Funktion verarbeitet eine Benutzerfrage, ruft das Modell zur SQL-Generierung auf, führt die Abfrage sicher aus und übergibt die Ergebnisse zur Erläuterung zurück. Das Modell erhält sowohl die ursprüngliche Frage als auch die Abfrageergebnisse und erzeugt anschließend eine Antwort in verständlicher Sprache.

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

Mehrstufige Datenfragen verarbeiten

Komplexe Fragen können mehrere Abfragen erfordern. Für die Frage „Wer sind unsere fünf umsatzstärksten Kunden und welche Bestellungen haben sie zuletzt aufgegeben?“ sind zwei Abfragen nötig: eine, um die umsatzstärksten Kunden zu ermitteln, und eine weitere, um deren Bestellungen abzurufen. Ermöglichen Sie dem Modell mehrere sequenzielle Tool-Aufrufe, indem Sie die Dispatch-Schleife wiederholt ausführen, bis finish_reason='stop' erreicht ist.

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.'

Risiken durch SQL-Injection verhindern

Selbst mit der Einschränkung auf SELECT könnte ein raffiniertes Modell (oder ein böswilliger Benutzer) versuchen, über Unterabfragen oder Tricks mit Kommentaren Daten auszuschleusen. Zusätzliche Schutzmaßnahmen sind: ein schreibgeschützter Datenbankbenutzer mit ausschließlich SELECT-Berechtigung, die Ausführung über einen separaten Connection Pool und die Validierung, dass die Tabellennamen in der Abfrage mit Ihrer Schema-Whitelist übereinstimmen.

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

Häufige Abfragen zwischenspeichern

Viele geschäftliche Fragen werden wiederholt mit derselben Antwort gestellt: „Wie viele Kunden haben wir?“ oder „Wie hoch war der Umsatz im letzten Monat?“ Speichern Sie diese Ergebnisse mit einer kurzen TTL in Redis zwischen. Prüfen Sie den Cache vor der Ausführung der Abfrage. Dadurch verringern Sie die Datenbanklast und beschleunigen Antworten auf häufige analytische Fragen.

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

Abfragen für Benutzer erläutern

Schaffen Sie Vertrauen, indem Sie Benutzern neben der natürlichsprachlichen Antwort auch die generierte SQL-Abfrage anzeigen. Wenn Benutzer sehen können: „Ich habe diese Abfrage ausgeführt: SELECT COUNT(*) FROM customers WHERE country = ?UK?“ können sie die Richtigkeit der Antwort überprüfen und SQL-Muster kennenlernen. Das Feld explanation in unserem Tool-Schema eignet sich dafür hervorragend.

Schnelltest

Testen Sie Ihr Verständnis beim Aufbau einer natürlichsprachlichen Datenbankschnittstelle.

Zusammenfassung der Lektion

In dieser Lektion haben Sie gelernt: Das Schema des query_database-Tools fügt den Schema-Kontext ein, damit das Modell präzises SQL generiert, die Sicherheitsvalidierung muss nicht zulässige SELECT-Anweisungen und gefährliche Schlüsselwörter vor der Ausführung blockieren und eine Schleife aus Modellaufrufen ermöglicht mehrstufige Datenanalysen, für die sequenzielle Abfragen erforderlich sind. Als Nächstes erkunden wir das Model Context Protocol (MCP), den offenen Standard für die Verbindung von KI mit externen Tools.

Häufig gestellte Fragen

Ist die Lektion „Eine natürlichsprachliche Datenbankschnittstelle entwickeln“ kostenlos?

Ja — der vollständige Text von „Eine natürlichsprachliche Datenbankschnittstelle entwickeln“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Engineering Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Engineering Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Eine natürlichsprachliche Datenbankschnittstelle entwickeln“?

Erstellen Sie ein System, in dem Nutzer Fragen in natürlicher Sprache stellen, das Modell per Function Calling SQL generiert, Ihre Anwendung die Abfrage sicher ausführt und das Modell die Ergebnisse… Du übst AI Engineering Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Engineering Academy zu starten?

Keine Vorkenntnisse erforderlich. AI Engineering Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Eine natürlichsprachliche Datenbankschnittstelle entwickeln“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Engineering Academy-Lektion Code schreiben und ausführen?

Ja. Jede AI Engineering Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Funktionsschemas für die API definieren
  2. Tool-Aufrufe in Ihrer Anwendung verarbeiten
  3. Parallele Funktionsaufrufe
  4. Eine natürlichsprachliche Datenbankschnittstelle entwickeln
← Zurück zu AI Engineering Academy