0Pricing
AI Agents · Lektion

Schemaverständnis und -injektion

Datenbankschemata für den LLM-Kontext extrahieren und formatieren: Tabellen, Spalten und Beziehungen.

Schemaverständnis und -injektion ist eine kostenlose AI Agents-Lektion auf CoddyKit. Dies ist Lektion 2 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 Agents-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Agents-Kurs umfasst insgesamt 4 Lektionen.

Warum der Schema-Kontext wichtig ist

Das LLM kennt die SQL-Syntax, weiß aber nichts über Ihre Datenbank. Ohne Schema-Kontext wird es Tabellen- und Spaltennamen halluzinieren.

Schema-Injektion bedeutet, Ihre DB-Struktur programmgesteuert zu extrahieren und in jeden Prompt aufzunehmen – dadurch kennt das LLM Ihre exakten Tabellen, Spalten und Datentypen.

INFORMATION_SCHEMA abfragen

Alle wichtigen relationalen Datenbanken stellen Metadaten über INFORMATION_SCHEMA bereit. Sie können diese Metadaten abfragen, um jede Tabelle, jeden Spaltennamen und jeden Datentyp zu erhalten, ohne Anwendungscode zu ändern.

Das funktioniert in PostgreSQL, MySQL, SQL Server und SQLite, jeweils mit geringfügigen Unterschieden.

import psycopg2

def get_schema(conn):
    query = '''
        SELECT table_name, column_name, data_type
        FROM information_schema.columns
        WHERE table_schema = 'public'
        ORDER BY table_name, ordinal_position
    '''
    with conn.cursor() as cur:
        cur.execute(query)
        return cur.fetchall()

Spalten nach Tabelle gruppieren

Das rohe Ergebnis von INFORMATION_SCHEMA ist eine flache Liste von Zeilen. Gruppieren Sie die Zeilen nach dem Tabellennamen, um eine strukturierte Darstellung zu erstellen, die sich leichter in einen Prompt formatieren lässt.

from collections import defaultdict

def build_schema_dict(conn):
    rows = get_schema(conn)
    schema = defaultdict(list)
    for table_name, column_name, data_type in rows:
        schema[table_name].append({
            'name': column_name,
            'type': data_type
        })
    return dict(schema)

# Result:
# {
#   'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'character varying'}],
#   'orders': [{'name': 'id', 'type': 'integer'}, {'name': 'user_id', 'type': 'integer'}]
# }

Schema für LLM-Prompts formatieren

Das LLM liest das Schema als Klartext. Verwenden Sie ein kompaktes, übersichtliches Format: eine Tabelle pro Zeile mit Spaltennamen und Datentypen in Klammern.

Die Angabe von Primärschlüsseln (PK) und Fremdschlüsseln (FK) hilft dem LLM, korrekte JOIN-Anweisungen zu schreiben.

def format_schema_for_prompt(schema_dict, pk_info=None, fk_info=None):
    lines = []
    for table, columns in schema_dict.items():
        col_parts = []
        for col in columns:
            label = col['name']
            if pk_info and (table, col['name']) in pk_info:
                label += ' PK'
            if fk_info and (table, col['name']) in fk_info:
                label += f' FK->{fk_info[(table, col["name"])]}'
            col_parts.append(f"{label} ({col['type']})")
        lines.append(f"Table {table}: {', '.join(col_parts)}")
    return '\n'.join(lines)

# Output:
# Table users: id PK (integer), email (varchar), created_at (timestamp)
# Table orders: id PK (integer), user_id FK->users.id (integer), total (float)

if __name__ == '__main__':
    demo_schema = {'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'varchar'}]}
    demo_pk = {('users', 'id')}
    print(format_schema_for_prompt(demo_schema, pk_info=demo_pk))

Primär- und Fremdschlüssel einbeziehen

Fremdschlüsselbeziehungen sind der wichtigste Teil des Schema-Kontexts – sie zeigen dem LLM, wie JOINs geschrieben werden. Fragen Sie information_schema.table_constraints und key_column_usage ab, um diese Beziehungen zu extrahieren.

def get_foreign_keys(conn):
    query = '''
        SELECT
            kcu.table_name,
            kcu.column_name,
            ccu.table_name AS foreign_table,
            ccu.column_name AS foreign_column
        FROM information_schema.table_constraints AS tc
        JOIN information_schema.key_column_usage AS kcu
            ON tc.constraint_name = kcu.constraint_name
        JOIN information_schema.constraint_column_usage AS ccu
            ON ccu.constraint_name = tc.constraint_name
        WHERE tc.constraint_type = 'FOREIGN KEY'
    '''
    with conn.cursor() as cur:
        cur.execute(query)
        return {
            (row[0], row[1]): f'{row[2]}.{row[3]}'
            for row in cur.fetchall()
        }

if __name__ == '__main__':
    class FakeCursor:
        def __enter__(self): return self
        def __exit__(self, *a): return False
        def execute(self, query): pass
        def fetchall(self):
            return [('orders', 'user_id', 'users', 'id')]
    class FakeConn:
        def cursor(self): return FakeCursor()

    fks = get_foreign_keys(FakeConn())
    print('Foreign keys found:')
    for (table, col), ref in fks.items():
        print(f'  {table}.{col} -> {ref}')

Schemakomprimierung: Das Problem

Eine echte Unternehmensdatenbank kann mehr als 200 Tabellen enthalten. Wenn Sie das vollständige Schema einfügen, überschreiten Sie das Kontextfenster von GPT-4 und verschwenden Geld für Tokens.

Ein Schema mit 200 Tabellen und jeweils 20 Spalten umfasst ungefähr 40.000+ Tokens – zu teuer, um es bei jeder Abfrage zu übertragen.

def estimate_schema_tokens(schema_dict):
    text = format_schema_for_prompt(schema_dict)
    # Rough estimate: 1 token per 4 characters
    estimated_tokens = len(text) // 4
    print(f'Tables: {len(schema_dict)}')
    print(f'Estimated schema tokens: {estimated_tokens}')
    return estimated_tokens

# 200 tables * 15 columns * 25 chars/col = 75,000 chars = ~18,750 tokens
# Plus user question + system prompt = easily over context limit

Schemakomprimierung: Selektive Injektion

Die effektivste Komprimierungsstrategie: nur für die Frage relevante Tabellen injizieren. Verwenden Sie einen zweistufigen Ansatz – fragen Sie zuerst das LLM, welche Tabellen benötigt werden, und fügen Sie anschließend nur deren Schemas ein.

def select_relevant_tables(question, all_table_names, n=5):
    table_list = ', '.join(all_table_names)
    prompt = f'''Database tables: {table_list}

Question: {question}

List the {n} most relevant table names as a JSON array.
Example: ["users", "orders", "products"]'''

    response = llm_call(prompt)
    import json
    return json.loads(response)

def compressed_schema(question, conn):
    all_tables = list(build_schema_dict(conn).keys())
    relevant = select_relevant_tables(question, all_tables)
    full_schema = build_schema_dict(conn)
    return {t: full_schema[t] for t in relevant if t in full_schema}

Schemakomprimierung: Rauschende Spalten ausschließen

Viele Tabellen enthalten Auditspalten wie created_at, updated_at, deleted_at, version und created_by, die für fachliche Abfragen selten relevant sind. Entfernen Sie diese, um die Anzahl der Tokens zu reduzieren.

AUDIT_COLUMNS = {
    'created_at', 'updated_at', 'deleted_at', 'created_by',
    'updated_by', 'version', 'is_deleted', 'modified_at'
}

def compress_schema(schema_dict, exclude_audit=True):
    compressed = {}
    for table, columns in schema_dict.items():
        # Skip internal/system tables
        if table.startswith('_') or table.startswith('pg_'):
            continue
        if exclude_audit:
            columns = [c for c in columns if c['name'] not in AUDIT_COLUMNS]
        if columns:  # only include if columns remain
            compressed[table] = columns
    return compressed

if __name__ == '__main__':
    demo_schema = {
        'users': [{'name': 'id', 'type': 'INT'}, {'name': 'email', 'type': 'VARCHAR'}, {'name': 'created_at', 'type': 'TIMESTAMP'}],
        'pg_stat': [{'name': 'x', 'type': 'INT'}],
    }
    compressed = compress_schema(demo_schema)
    print('Tables kept:', list(compressed.keys()))
    print('users columns after compression:', [c['name'] for c in compressed['users']])

Tabellenbeschreibungen hinzufügen

Spaltennamen allein sind nicht immer selbsterklärend. Das Hinzufügen von Beschreibungen in natürlicher Sprache dazu, wofür die einzelnen Tabellen stehen, verbessert die Qualität der SQL-Generierung erheblich.

Speichern Sie die Beschreibungen in einer Konfigurationsdatei oder als PostgreSQL-Tabellenkommentare.

TABLE_DESCRIPTIONS = {
    'users': 'Registered app users with authentication info',
    'orders': 'Customer purchase orders',
    'order_items': 'Individual line items within an order',
    'products': 'Product catalog with pricing',
    'payments': 'Payment transactions linked to orders'
}

def format_schema_with_descriptions(schema_dict):
    lines = []
    for table, columns in schema_dict.items():
        desc = TABLE_DESCRIPTIONS.get(table, '')
        col_str = ', '.join(f"{c['name']} ({c['type']})" for c in columns)
        if desc:
            lines.append(f"Table {table} ({desc}): {col_str}")
        else:
            lines.append(f"Table {table}: {col_str}")
    return '\n'.join(lines)

if __name__ == '__main__':
    demo_schema = {'users': [{'name': 'id', 'type': 'INT'}], 'orders': [{'name': 'id', 'type': 'INT'}]}
    print(format_schema_with_descriptions(demo_schema))

Schema zwischenspeichern

Datenbankschemas ändern sich selten. Das Abrufen von INFORMATION_SCHEMA bei jeder Abfrage verursacht zusätzliche Latenz und Last. Speichern Sie den formatierten Schema-String im Cache und invalidieren Sie ihn bei Schemaänderungsereignissen oder nach Ablauf einer zeitbasierten TTL.

import time

class SchemaCache:
    def __init__(self, ttl_seconds=300):
        self._cache = None
        self._timestamp = 0
        self.ttl = ttl_seconds

    def get(self, conn):
        now = time.time()
        if self._cache is None or (now - self._timestamp) > self.ttl:
            print('Refreshing schema cache...')
            schema_dict = build_schema_dict(conn)
            fk_info = get_foreign_keys(conn)
            self._cache = format_schema_for_prompt(schema_dict, fk_info=fk_info)
            self._timestamp = now
        return self._cache

schema_cache = SchemaCache(ttl_seconds=300)

Ablauf der vollständigen Schema-Injektion

Kombinieren Sie alle Techniken: Cachen Sie das komprimierte Schema, injizieren Sie es in den System-Prompt und verwenden Sie bei großen Datenbanken eine selektive Tabellenfilterung.

def build_sql_agent_prompt(question, conn, large_db=False):
    if large_db:
        schema = compressed_schema(question, conn)
        schema_text = format_schema_with_descriptions(schema)
    else:
        schema_text = schema_cache.get(conn)

    system = f'''You are a PostgreSQL expert.
Return ONLY a valid SELECT query based on this schema:

{schema_text}

Rules:
- Use only SELECT statements
- Use table aliases for clarity
- Limit results to 100 rows unless asked for all
'''
    return system

Wissensüberprüfung

Wann sollten Sie statt des vollständigen Schemas nur ausgewählte Tabellen injizieren?

Zusammenfassung: Schema verstehen und injizieren

Eine effektive Schema-Injektion bildet die Grundlage zuverlässiger NL-to-SQL-Agenten. Extrahieren Sie die Struktur aus INFORMATION_SCHEMA, nehmen Sie Primär- und Fremdschlüsselbeziehungen auf und formatieren Sie sie als kompakten Text für das LLM.

Bei großen Datenbanken: Cachen Sie das Schema, entfernen Sie Audit-Spalten und verwenden Sie eine selektive Injektion, um nur die für die jeweilige Frage relevanten Tabellen zu senden. Tabellenbeschreibungen in natürlicher Sprache verbessern die Abfragequalität zusätzlich.

Häufig gestellte Fragen

Ist die Lektion „Schemaverständnis und -injektion“ kostenlos?

Ja — der vollständige Text von „Schemaverständnis und -injektion“ 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 Agents-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Agents-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Schemaverständnis und -injektion“?

Datenbankschemata für den LLM-Kontext extrahieren und formatieren: Tabellen, Spalten und Beziehungen. Du übst AI Agents 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 Agents zu starten?

Keine Vorkenntnisse erforderlich. AI Agents 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 2 von 4.

Wie lange dauert die Lektion „Schemaverständnis und -injektion“?

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 Agents-Lektion Code schreiben und ausführen?

Ja. Jede AI Agents-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. Funktionsweise von NL-to-SQL-Agenten
  2. Schemaverständnis und -injektion
  3. SQL-Queries generieren und validieren
  4. Mehrdeutige Datenbankfragen verarbeiten
← Zurück zu AI Agents