Bygg ett databasingränssnitt på naturligt språk
Skapa ett system där användare ställer frågor på vanlig engelska, modellen genererar SQL genom funktionsanrop, applikationen kör frågan säkert och modellen beskriver resultaten.
Bygg ett databasingränssnitt på naturligt språk är en gratis lektion i AI Engineering Academy på CoddyKit. Detta är lektion 4 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 Engineering Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i AI Engineering Academy innehåller totalt 4 lektioner.
Naturligt språk till SQL: Visionen
Föreställ dig att du frågar databasen ”Vilka kunder spenderade mer än 1 000 dollar förra månaden?” och får ett svar – utan att skriva en enda SQL-fråga. Ett databasgränssnitt på naturligt språk använder function calling för att låta LLM-modellen generera SQL, medan din applikation kör frågan på ett säkert sätt och modellen beskriver resultaten på vanlig engelska. Det här mönstret gör dataåtkomst tillgänglig även för användare utan teknisk bakgrund.
Översikt över systemarkitekturen
NL-till-SQL-pipelinen består av fyra komponenter som arbetar tillsammans:
- Schema kontext: LLM-modellen får databasschemat så att den vet vilka tabeller och kolumner som finns.
- SQL-generering: Modellen genererar en SQL-fråga som ett argument i ett funktionsanrop.
- Säker körning: Din app validerar och kör frågan och returnerar sedan resultaten.
- Resultatbeskrivning: Modellen tar emot frågeresultaten och förklarar dem på naturligt språk.
Definiera verktyget för databasfrågor
Definiera en funktion query_database som tar emot en SQL SELECT-sats. Schemainformationen i funktionsdefinitionen lär modellen vilka tabeller och kolumner som är tillgängliga, så att den kan generera korrekta frågor utan att behöva gissa.
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']
}
}
}Säker SQL-körning
Kör aldrig SQL direkt från modellen utan validering. Implementera ett säkerhetslager som endast tillåter SELECT-satser, avvisar farliga nyckelord, begränsar antalet resultatrader för att förhindra minnesproblem och kör frågan i en skrivskyddad databastransaktion. Försvar i flera lager är avgörande när kod som genererats av en LLM körs.
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]Infoga schemakontext i systemprompten
Modellen genererar bättre SQL när den kan se hela databasschemat. Skapa en systemprompt som innehåller tabelldefinitioner, kolumnnamn och datatyper samt exempelvärden för kategoriska kolumner. Då vet modellen om den ska använda country = 'US' eller country_code = 'US' utan att behöva gissa.
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.
'''Formatera frågeresultat för modellen
Rådatabasresultat (listor med dict-objekt) måste formateras som läsbar text innan de skickas tillbaka till modellen. Konvertera resultatuppsättningen till en kompakt representation – en tabell eller en JSON-sammanfattning – som modellen kan använda när den beskriver svaret. Undvik att skicka tusentals rader; sammanfatta stora resultatuppsättningar.
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 resultImplementera hela pipelinen
Nu sätter vi ihop allt: funktionen som bearbetar en användarfråga, anropar modellen för att generera SQL, kör frågan på ett säkert sätt och skickar resultaten tillbaka för beskrivning. Modellen får både den ursprungliga frågan och frågeresultaten och skapar sedan ett svar på vanlig engelska.
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.contentHantera datafrågor i flera steg
Komplexa frågor kan kräva flera frågor. ”Vilka är våra fem främsta kunder sett till intäkter, och vilka är deras senaste beställningar?” kräver två frågor: en för att hitta de främsta kunderna och en för att hämta deras beställningar. Låt modellen göra flera sekventiella verktygsanrop genom att köra dispatch-loopen flera gånger tills 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.'Förebygg risker med SQL-injektion
Även med skyddet som endast tillåter SELECT kan en listig modell (eller en angripande användare) försöka exfiltrera data via underfrågor eller trick med kommentarer. Ytterligare skydd omfattar att använda en skrivskyddad databasanvändare som endast har SELECT-behörighet, köra frågorna i en separat anslutningspool och validera att tabellnamnen i frågan stämmer överens med din lista över godkända scheman.
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 TrueCacha vanliga frågor
Många affärsfrågor ställs upprepade gånger och har samma svar: ”Hur många kunder har vi?” ”Vilka var intäkterna förra månaden?” Cacha resultaten i Redis med en kort TTL. Kontrollera cachen innan frågan körs – det minskar belastningen på databasen och snabbar upp svaren på vanliga analysfrågor.
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 rowsFörklara frågor för användare
Bygg förtroende genom att visa användarna den genererade SQL-frågan tillsammans med svaret på naturligt språk. När användarna kan se ”Jag körde den här frågan: SELECT COUNT(*) FROM customers WHERE country = ?UK?” kan de kontrollera att svaret är korrekt och lära sig SQL-mönster. Fältet explanation i vårt verktygsschema passar perfekt för detta.
Snabbtest
Testa dina kunskaper om hur man bygger ett databasgränssnitt på naturligt språk.
Sammanfattning av lektionen
I den här lektionen har du lärt dig att verktygsschemat för query_database infogar schemakontext så att modellen genererar korrekt SQL, att säkerhetsvalideringen måste blockera satser som inte är SELECT samt farliga nyckelord innan körning och att en loop med modellanrop möjliggör flerstegsdataanalys som kräver sekventiella frågor. Härnäst utforskar vi Model Context Protocol (MCP), den öppna standarden för att ansluta AI till externa verktyg.
Lär dig Python 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
- 30
- Lektioner
- 120
Vanliga frågor
Är lektionen ”Bygg ett databasingränssnitt på naturligt språk” gratis?
Ja – hela texten till ”Bygg ett databasingränssnitt på naturligt språk” 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 Engineering Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i AI Engineering Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Bygg ett databasingränssnitt på naturligt språk”?
Skapa ett system där användare ställer frågor på vanlig engelska, modellen genererar SQL genom funktionsanrop, applikationen kör frågan säkert och modellen beskriver resultaten. Ni övar på AI Engineering Academy 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 Engineering Academy?
Du behöver inga förkunskaper. Utbildningen i AI Engineering Academy 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 4 av 4.
Hur lång tid tar lektionen ”Bygg ett databasingränssnitt på naturligt språk”?
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 Engineering Academy-lektionen?
Ja. Varje AI Engineering Academy-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
- Definiera funktionsscheman för API:et
- Bearbeta verktygsanrop i er applikation
- Parallella funktionsanrop
- Bygg ett databasingränssnitt på naturligt språk