0Pricing
AI Agents · Lezione

Comprensione e iniezione dello schema

Estrazione e formattazione dello schema del database nel contesto dell’LLM: tabelle, colonne e relazioni

Comprensione e iniezione dello schema è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.

Perché il contesto dello schema è importante

L'LLM conosce la sintassi SQL, ma non sa nulla del database dell'applicazione. Senza il contesto dello schema, inventerà nomi di tabelle e colonne.

L'iniezione dello schema consiste nell'estrarre programmaticamente la struttura del DB e includerla in ogni prompt, rendendo l'LLM consapevole delle tabelle, delle colonne e dei tipi esatti in uso.

Interrogare INFORMATION_SCHEMA

Tutti i principali database relazionali espongono i metadati tramite INFORMATION_SCHEMA. È possibile interrogarlo per ottenere ogni tabella, nome di colonna e tipo di dato senza modificare il codice dell'applicazione.

Questo funziona in PostgreSQL, MySQL, SQL Server e SQLite, con piccole differenze.

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

Raggruppare le colonne per tabella

Il risultato grezzo di INFORMATION_SCHEMA è un elenco piatto di righe. Le raggruppi per nome della tabella per creare una rappresentazione strutturata, più facile da formattare in un 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'}]
# }

Formattare lo schema per i prompt dell'LLM

L'LLM legge lo schema come testo semplice. Utilizzi un formato conciso e leggibile: una tabella per riga, con i nomi delle colonne e i tipi tra parentesi.

L'inclusione delle chiavi primarie (PK) e delle chiavi esterne (FK) aiuta l'LLM a scrivere istruzioni JOIN corrette.

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

Includere chiavi primarie ed esterne

Le relazioni tra chiavi esterne sono la parte più importante del contesto dello schema: indicano all'LLM come scrivere le JOIN. Interroghi information_schema.table_constraints e key_column_usage per estrarle.

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

Compressione dello schema: il problema

Un vero database aziendale può contenere più di 200 tabelle. Se inserisce lo schema completo, supererà la finestra di contesto di GPT-4 e sprecherà denaro per i token.

Uno schema di 200 tabelle con 20 colonne ciascuna contiene all'incirca 40.000 o più token: è troppo costoso inviarlo a ogni query.

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

Compressione dello schema: iniezione selettiva

La strategia di compressione più efficace consiste nell'iniettare solo le tabelle pertinenti alla domanda. Utilizzi un approccio in due fasi: chieda prima all'LLM di quali tabelle ha bisogno, quindi inserisca solo gli schemi di quelle tabelle.

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}

Compressione dello schema: escludere le colonne non pertinenti

Molte tabelle hanno colonne di audit come created_at, updated_at, deleted_at, version, created_by, raramente pertinenti alle query aziendali. Le rimuova per ridurre il numero di token.

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

Aggiungere descrizioni delle tabelle

I nomi delle colonne da soli non sono sempre autoesplicativi. Aggiungere descrizioni in linguaggio naturale di ciò che rappresenta ogni tabella migliora notevolmente la qualità della generazione SQL.

Conservi le descrizioni in un file di configurazione o nei commenti delle tabelle PostgreSQL.

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

Caching dello schema

Gli schemi dei database cambiano raramente. Recuperare INFORMATION_SCHEMA a ogni query aggiunge latenza e carico. Memorizzi nella cache la stringa dello schema formattata e la invalidi quando si verificano eventi di modifica dello schema o allo scadere di un TTL basato sul tempo.

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)

Flusso completo di iniezione dello schema

Combinando tutte le tecniche: memorizzi nella cache lo schema compresso, lo inserisca nel system prompt e utilizzi il filtraggio selettivo delle tabelle per i database di grandi dimensioni.

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

Verifica delle conoscenze

Quando è opportuno utilizzare l'iniezione selettiva delle tabelle invece di iniettare lo schema completo?

Riepilogo: comprensione e iniezione dello schema

Un'iniezione efficace dello schema è la base per agenti NL-to-SQL affidabili. Estragga la struttura da INFORMATION_SCHEMA, includa le relazioni tra chiavi primarie ed esterne e formatti il tutto come testo compatto per l'LLM.

Per i database di grandi dimensioni: memorizzi lo schema nella cache, rimuova le colonne di audit e utilizzi l'iniezione selettiva per inviare solo le tabelle pertinenti a ciascuna domanda. Le descrizioni delle tabelle in linguaggio naturale migliorano ulteriormente la qualità delle query.

Domande Frequenti

La lezione «Comprensione e iniezione dello schema» è gratuita?

Sì — il testo completo di «Comprensione e iniezione dello schema» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.

Cosa imparerò in «Comprensione e iniezione dello schema»?

Estrazione e formattazione dello schema del database nel contesto dell’LLM: tabelle, colonne e relazioni Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Agents?

Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Comprensione e iniezione dello schema»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Agents?

Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Come funzionano gli agenti NL-to-SQL
  2. Comprensione e iniezione dello schema
  3. Generazione e convalida delle query SQL
  4. Gestione delle domande ambigue sui database
← Torna a AI Agents