Creare un'interfaccia al database in linguaggio naturale
Crei un sistema in cui gli utenti pongono domande in linguaggio naturale, il modello genera SQL tramite function calling, l'applicazione esegue la query in sicurezza e il modello illustra i risultati.
Creare un'interfaccia al database in linguaggio naturale è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 4 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 Engineering Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Engineering Academy include 4 lezioni in totale.
Dal linguaggio naturale a SQL: la visione
Immagini di chiedere al database «Quali clienti hanno speso più di 1.000 $ il mese scorso?» e di ricevere una risposta, senza scrivere una sola query SQL. Un'interfaccia per database in linguaggio naturale usa il function calling per consentire all'LLM di generare SQL; l'applicazione lo esegue in modo sicuro e il modello illustra i risultati in inglese corrente. Questo approccio rende i dati accessibili anche agli utenti non tecnici.
Panoramica dell'architettura del sistema
La pipeline da linguaggio naturale a SQL comprende quattro componenti che lavorano insieme:
- Contesto dello schema: l'LLM riceve lo schema del database, così sa quali tabelle e colonne esistono.
- Generazione di SQL: il modello genera una query SQL come argomento di una chiamata di funzione.
- Esecuzione sicura: l'applicazione convalida ed esegue la query, quindi restituisce i risultati.
- Presentazione dei risultati: il modello riceve i risultati della query e li spiega in linguaggio naturale.
Definizione dello strumento per interrogare il database
Definisca una funzione query_database che accetti un'istruzione SQL SELECT. La descrizione dello schema nella definizione della funzione insegna al modello quali tabelle e colonne sono disponibili, permettendogli di generare query accurate senza procedere per tentativi.
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']
}
}
}Esecuzione sicura di SQL
Non esegua mai SQL grezzo generato dal modello senza convalidarlo. Implementi un livello di sicurezza che: consenta solo istruzioni SELECT, rifiuti le parole chiave pericolose, limiti il numero di righe del risultato per evitare problemi di memoria ed esegua l'operazione in una transazione di database in sola lettura. La difesa in profondità è fondamentale quando si esegue codice generato da un LLM.
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]Inserimento del contesto dello schema nel prompt di sistema
Il modello genera SQL migliore quando può vedere lo schema completo del database. Costruisca un prompt di sistema che includa le definizioni delle tabelle, i nomi e i tipi delle colonne e valori di esempio per le colonne categoriche. In questo modo il modello sa se deve usare country = 'US' oppure country_code = 'US', senza doverlo dedurre.
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.
'''Formattazione dei risultati della query per il modello
I risultati grezzi del database (elenchi di dict) devono essere formattati come testo leggibile prima di essere inviati al modello. Converta il set di risultati in una rappresentazione compatta, ad esempio una tabella o un riepilogo JSON, a cui il modello possa fare riferimento per illustrare la risposta. Eviti di inviare migliaia di righe: riepiloghi i set di risultati molto grandi.
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 resultImplementazione della pipeline completa
Mettiamo insieme tutti i componenti: la funzione che elabora la domanda dell'utente, chiama il modello per generare SQL, esegue la query in modo sicuro e restituisce i risultati al modello affinché li illustri. Il modello riceve sia la domanda originale sia i risultati della query, quindi produce una risposta in inglese corrente.
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.contentGestione delle domande sui dati in più passaggi
Le domande complesse possono richiedere più query. «Chi sono i nostri 5 clienti principali per fatturato e quali sono i loro ordini più recenti?» richiede due query: una per trovare i clienti principali e una per recuperare i relativi ordini. Consenta al modello di emettere più chiamate sequenziali agli strumenti eseguendo più volte il ciclo di dispatch, fino a quando finish_reason='stop'.
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.'Prevenzione dei rischi di SQL injection
Anche con il controllo che consente solo SELECT, un modello ingegnoso (o un utente malevolo) potrebbe tentare di esfiltrare dati tramite sottoquery o tecniche basate sui commenti. Tra le protezioni aggiuntive rientrano: l'uso di un utente del database in sola lettura con la sola autorizzazione SELECT, l'esecuzione in un pool di connessioni separato e la convalida del fatto che i nomi delle tabelle nella query corrispondano alla whitelist dello schema.
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 TrueMemorizzazione nella cache delle query comuni
Molte domande aziendali vengono poste ripetutamente e hanno sempre la stessa risposta: «Quanti clienti abbiamo?» oppure «Qual è stato il fatturato del mese scorso?». Memorizzi questi risultati in Redis con un TTL breve. Controlli la cache prima di eseguire la query: in questo modo ridurrà il carico sul database e velocizzerà le risposte alle domande analitiche più comuni.
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 rowsSpiegazione delle query agli utenti
Costruisca fiducia mostrando agli utenti la query SQL generata insieme alla risposta in linguaggio naturale. Quando gli utenti possono vedere «Ho eseguito questa query: SELECT COUNT(*) FROM customers WHERE country = ?UK?» possono verificare che la risposta sia corretta e imparare gli schemi SQL. Il campo explanation nello schema dello strumento è perfetto a questo scopo.
Verifica rapida
Verifichi la sua comprensione della costruzione di un'interfaccia per database in linguaggio naturale.
Riepilogo della lezione
In questa lezione ha imparato che: lo schema dello strumento query_database inserisce il contesto dello schema, consentendo al modello di generare SQL accurato, la convalida di sicurezza deve bloccare le istruzioni diverse da SELECT e le parole chiave pericolose prima dell'esecuzione e un ciclo di chiamate al modello consente analisi dei dati in più passaggi che richiedono query sequenziali. Ora esploreremo il Model Context Protocol (MCP), lo standard aperto per collegare l'IA agli strumenti esterni.
Domande Frequenti
La lezione «Creare un'interfaccia al database in linguaggio naturale» è gratuita?
Sì — il testo completo di «Creare un'interfaccia al database in linguaggio naturale» è 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 Engineering Academy, passa a CoddyKit PRO. Il corso AI Engineering Academy include 4 lezioni in totale.
Cosa imparerò in «Creare un'interfaccia al database in linguaggio naturale»?
Crei un sistema in cui gli utenti pongono domande in linguaggio naturale, il modello genera SQL tramite function calling, l'applicazione esegue la query in sicurezza e il modello illustra i risultati. Eserciti AI Engineering Academy 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 Engineering Academy?
Non è richiesta alcuna esperienza precedente. AI Engineering Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Creare un'interfaccia al database in linguaggio naturale»?
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 Engineering Academy?
Sì. Ogni lezione AI Engineering Academy 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
- Definire gli schemi delle funzioni per l'API
- Elaborare le chiamate agli strumenti nell'applicazione
- Chiamate di funzioni in parallelo
- Creare un'interfaccia al database in linguaggio naturale