AI-agenter · Lektion

Förståelse och injektion av scheman

Extrahera och formatera databasscheman för LLM-kontext: tabeller, kolumner och relationer.

Lektion 2 av 413 steg

Förståelse och injektion av scheman är en gratis lektion i AI-agenter på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för AI-agenter, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i AI-agenter innehåller totalt 4 lektioner.

Varför schemakontext är viktig

LLM:en kan SQL-syntax men vet ingenting om er databas. Utan schemakontext kommer den att hitta på tabell- och kolumnnamn.

Schema-injektion innebär att programmatiskt extrahera databasens struktur och inkludera den i varje prompt — så att LLM:en känner till era exakta tabeller, kolumner och datatyper.

Fråga INFORMATION_SCHEMA

Alla större relationsdatabaser tillhandahåller metadata via INFORMATION_SCHEMA. Det kan frågas för att hämta alla tabeller, kolumnnamn och datatyper utan att applikationskoden behöver ändras.

Detta fungerar i PostgreSQL, MySQL, SQL Server och SQLite, med mindre skillnader.

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()

Gruppera kolumner efter tabell

Det råa resultatet från INFORMATION_SCHEMA är en platt lista med rader. Gruppera raderna efter tabellnamn för att skapa en strukturerad representation som är enklare att formatera till en prompt.

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

Formatera schemat för LLM-promptar

LLM:en läser schemat som vanlig text. Använd ett kortfattat och lättläst format: en tabell per rad med kolumnnamn och typer inom parentes.

Om primärnycklar (PK) och främmande nycklar (FK) tas med blir det enklare för LLM:en att skriva korrekta JOIN-satser.

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))

Inkludera primärnycklar och främmande nycklar

Relationer mellan främmande nycklar är den viktigaste delen av schemakontexten — de talar om för LLM:en hur JOIN-satser ska skrivas. Fråga information_schema.table_constraints och key_column_usage för att extrahera dem.

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

Schemakomprimering: problemet

En verklig företagsdatabas kan ha fler än 200 tabeller. Om hela schemat infogas överskrids GPT-4:s kontextfönster och pengar slösas på tokens.

Ett schema med 200 tabeller och 20 kolumner i varje innehåller ungefär 40 000+ tokens — för dyrt att skicka med varje fråga.

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

Schemakomprimering: selektiv injektion

Den mest effektiva komprimeringsstrategin är att endast infoga tabeller som är relevanta för frågan. Använd ett tillvägagångssätt i två faser — fråga först LLM:en vilka tabeller den behöver och infoga sedan endast schemana för dessa tabeller.

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}

Schemakomprimering: utesluta br就kolumner

Många tabeller har granskningskolumner som created_at, updated_at, deleted_at, version och created_by som sällan är relevanta för affärsfrågor. Ta bort dem för att minska antalet tokens.

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

Lägga till tabellbeskrivningar

Kolumnnamn är inte alltid självförklarande. Att lägga till beskrivningar på naturligt språk av vad varje tabell representerar förbättrar kvaliteten på SQL-genereringen avsevärt.

Lagra beskrivningarna i en konfigurationsfil eller som PostgreSQL-tabellkommentarer.

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))

Cachning av schemat

Databasscheman ändras sällan. Att hämta INFORMATION_SCHEMA vid varje fråga ökar fördröjningen och belastningen. Cachelagra den formaterade schemasträngen och ogiltigförklara den när schemat ändras eller när en tidsbaserad TTL löper ut.

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)

Flöde för fullständig schemainjektion

Kombinera alla tekniker: cachelagra det komprimerade schemat, infoga det i systemprompten och använd selektiv tabellfiltrering för stora databaser.

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

Kunskapskontroll

När bör du använda selektiv tabellinjektion i stället för att injicera hela schemat?

Sammanfattning: Schemaförståelse och schemainjektion

Effektiv schemainjektion är grunden för tillförlitliga NL-till-SQL-agenter. Extrahera strukturen från INFORMATION_SCHEMA, inkludera relationer mellan primärnycklar och främmande nycklar och formatera informationen som kompakt text för LLM:en.

För stora databaser: cachelagra schemat, ta bort granskningskolumner och använd selektiv injektion så att endast tabeller som är relevanta för varje fråga skickas. Tabellbeskrivningar på naturligt språk förbättrar dessutom frågekvaliteten.

Gratis att börja

Lär dig AI-agenter med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
60
Lektioner
239

Vanliga frågor

Är lektionen ”Förståelse och injektion av scheman” gratis?

Ja – hela texten till ”Förståelse och injektion av scheman” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i AI-agenter, kan Ni uppgradera till CoddyKit PRO. Kursen i AI-agenter innehåller totalt 4 lektioner.

Vad lär jag mig i ”Förståelse och injektion av scheman”?

Extrahera och formatera databasscheman för LLM-kontext: tabeller, kolumner och relationer. Ni övar på AI-agenter med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig AI-agenter?

Du behöver inga förkunskaper. Utbildningen i AI-agenter på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”Förståelse och injektion av scheman”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här AI-agenter-lektionen?

Ja. Varje AI-agenter-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Så fungerar NL-to-SQL-agenter
  2. Förståelse och injektion av scheman
  3. Generera och validera SQL-frågor
  4. Hantera tvetydiga databasfrågor
← Tillbaka till AI-agenter