Implementasi Text-to-SQL menggunakan Large Language Model (LLM) sering memicu silent failure di level backend. Query yang dihasilkan model seringkali lolos validasi sintaksis database engine tanpa melempar syntax error, namun mengembalikan empty set atau data korup secara logis. Masalah ini berakar pada kegagalan semantik: model membuat asumsi nama kolom (hallucinated column) atau relasi join yang tidak pernah ada di skema database riil.
Gejala: Silent Failure pada Runtime Eksekusi
Berbeda dengan kesalahan ketik query oleh developer yang langsung ditolak parser SQL engine dengan kode error deskriptif, query LLM sering menghasilkan kondisi logis valid yang salah sasaran. Kasus umum terjadi pada agregasi data analitik atau filter relasional.
Perhatikan contoh query yang digenerate oleh LLM saat diminta mengambil transaksi berhasil untuk pengguna aktif:
SELECT
u.id AS user_id,
SUM(o.total_amount) AS revenue
FROM users u
JOIN orders o ON u.account_id = o.user_account_id
WHERE o.status = 'COMPLETED'
AND u.is_deleted = FALSE
GROUP BY u.id;Database mengembalikan status eksekusi sukses (HTTP 200 / zero exit code), namun hasilnya kosong. Dua anomali terjadi:
- Tabel
usersmenggunakan primary keyid, bukanaccount_id. Join predicateu.account_id = o.user_account_idmengikat kolom yang salah atau bersifat nullable default. - Kolom soft delete pada tabel
userssebenarnya bernamadeleted_at(tipe dataTIMESTAMP), bukan kolom booleanis_deleted. Database mengevaluasi predikat yang salah atau mengembalikan0 rows affectedtanpa memicu exception driver.
Analisis Root Cause: Absence of Semantic Pre-execution Validation
Akar masalah utama adalah ketiadaan layer verifikasi semantik deterministik sebelum query dikirim ke database connection pool. Arsitektur backend tipikal langsung meneruskan raw output model ke driver:
LLM Output --> Sanitize String --> DB Connection Pool --> Empty / Corrupted ResultDriver database relasional (seperti PostgreSQL atau MySQL client) hanya bertanggung jawab memastikan koneksi network dan validitas parsing dialek SQL native. Driver tidak melakukan pencocokan intensi natural language terhadap katalog metadata skema aktif aplikasi secara mendalam sebelum query diproses engine.
Ketika context prompt LLM menyertakan DDL skema yang besar, LLM cenderung melakukan halusinasi nama kolom berbasis konvensi umum (misal: is_active, account_id, created_by) alih-alih merujuk pada skema eksak yang didefinisikan.
Solusi: Deterministic Semantic Verification Layer Berbasis AST
Untuk mencegah query korup mencapai database, sistem memerlukan automated gatekeeper middleware. Solusi deterministik mengadopsi prinsip verifikasi skema AST (sebagaimana pendekatan pada tooling seperti Sqlsure): mengurai query menjadi Abstract Syntax Tree (AST), mengekstrak semua identifier (tabel, alias, kolom), lalu memvalidasinya terhadap katalog skema in-memory yang tersinkronisasi.
1. Isolasi AST Traversal Parser
Parser mengekstrak seluruh referensi kolom dan tabel tanpa mengeksekusi query. Berikut contoh implementasi validator semantik menggunakan TypeScript dan library AST parser standar:
import { Parser } from 'node-sql-parser';
interface SchemaCatalog {
[table: string]: Set<string>;
}
// Metadata skema aktif dari database migration catalog
const activeSchema: SchemaCatalog = {
users: new Set(['id', 'email', 'name', 'deleted_at', 'created_at']),
orders: new Set(['id', 'user_id', 'total_amount', 'status', 'created_at'])
};
export class SemanticValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'SemanticValidationError';
}
}
export function validateQuerySemantics(sql: string, catalog: SchemaCatalog): void {
const parser = new Parser();
let ast: any;
try {
ast = parser.astify(sql);
} catch (err: any) {
throw new SemanticValidationError(`Syntax Error: ${err.message}`);
}
const statements = Array.isArray(ast) ? ast : [ast];
for (const stmt of statements) {
if (stmt.type !== 'select') {
throw new SemanticValidationError(`Forbidden operation: Only SELECT allowed`);
}
// Mapping alias ke nama tabel riil
const tableAliasMap = new Map<string, string>();
if (stmt.from) {
for (const fromItem of stmt.from) {
const tableName = fromItem.table;
const alias = fromItem.as || tableName;
if (!catalog[tableName]) {
throw new SemanticValidationError(`Hallucinated table: '${tableName}'`);
}
tableAliasMap.set(alias, tableName);
}
}
// Traversal kolom pada klausa WHERE dan JOIN
const validateColumnRef = (colExpr: any) => {
if (!colExpr || typeof colExpr !== 'object') return;
if (colExpr.type === 'column_ref') {
const table = tableAliasMap.get(colExpr.table) || colExpr.table;
const column = colExpr.column;
if (table && catalog[table]) {
if (!catalog[table].has(column)) {
throw new SemanticValidationError(
`Hallucinated column '${column}' detected on table '${table}'`
);
}
}
}
// Rekursif untuk nested binary expressions (AND, OR, join predicates)
if (colExpr.left) validateColumnRef(colExpr.left);
if (colExpr.right) validateColumnRef(colExpr.right);
};
if (stmt.where) validateColumnRef(stmt.where);
if (stmt.from) {
for (const fromItem of stmt.from) {
if (fromItem.on) validateColumnRef(fromItem.on);
}
}
}
}2. Gatekeeper Middleware Sebelum Connection Pool
Integrasikan layer validasi ke dalam alur eksekusi service sebelum query dikirim ke database pool.
import { Pool } from 'pg';
import { validateQuerySemantics, SemanticValidationError } from './semanticValidator';
import { activeSchema } from './catalog';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export async function executeAiQuery(generatedSql: string) {
// Gatekeeper: Hentikan query sebelum menyentuh socket network DB
try {
validateQuerySemantics(generatedSql, activeSchema);
} catch (error) {
if (error instanceof SemanticValidationError) {
// Log kegagalan dan trigger self-correction prompt ke LLM
console.error(`[BLOCKED AI QUERY]: ${error.message}`);
return {
success: false,
retryable: true,
reason: error.message
};
}
throw error;
}
// Safe path: Eksekusi query deterministik
const client = await pool.connect();
try {
const result = await client.query(generatedSql);
return { success: true, data: result.rows };
} finally {
client.release();
}
}Trade-off dan Limitasi
Penerapan deterministik AST validation memiliki sejumlah batasan teknis:
- Overhead Latensi: Parsing AST menambah pemrosesan sebesar 1–5ms pada layer backend. Namun, angka ini jauh lebih efisien dibandingkan biaya eksekusi database round-trip untuk query yang menghasilkan data korup.
- Query Dinamis Kompleks: Validasi untuk nested subquery berlapis dan correlated queries membutuhkan parser AST rekursif yang lebih komprehensif, mencakup
WITH (CTE)definitions dan aliasing fungsional (sepertiCOALESCEatau window functions). - Sinkronisasi Skema: Katalog skema memory harus terhubung ke pipeline migrasi database aplikasi (CI/CD) agar validator tidak menolak kolom baru yang valid.
Memasukkan gatekeeper semantik deterministik mengeliminasi bug silent empty set dan melindungi database dari konsumsi resource akibat join predikat invalid.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!