// Schema trimisă către OpenAI
const tools = [{
type: 'function' as const,
function: {
name: 'getUserOrders',
description: 'Aduce istoricul de comenzi pentru un utilizator pe baza emailului',
parameters: {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
limit: { type: 'number', default: 5 }
},
required: ['email']
}
}
}];
// Handler-ul nostru securizat din backend
async function executeTool(name: string, args: { email: string; limit?: number }) {
if (name === 'getUserOrders') {
// Query parametrizat clasic, control total asupra securității
const query = 'SELECT id, total, status FROM orders WHERE user_email = $1 LIMIT $2';
return db.query(query, [args.email, args.limit ?? 5]);
}
throw new Error('Tool necunoscut');
}Să fim sinceri: să-i dai unui LLM acces direct să scrie SQL pe baza ta de date e ca și cum ai lăsa un intern nesupravegheat în producție vineri la ora 17:00. Am testat abordarea asta acum câteva luni pe un proiect intern cu vreo 1.500 de utilizatori activi și m-am prins rapid ce funcționează și ce te trimite direct la backups.
Nu faceți greșeala să generați SQL din prompt-uri libere. Pe lângă riscul evident de SQL injection (sau prompt injection în cazul ăsta), modelele tind să o ia pe arătură când structura bazei de date se complică. O tabelă de join mai ciudată și gata, crapă tot. Soluția pe care am mers noi? Function calling.
De ce funcționează mai bine function calling?
În loc să ceri modelului direct cod SQL pe care apoi să-l rulezi orbește, îi expui o serie de unelte (tools) stricte. Practic, transformăm LLM-ul într-un router inteligent care doar alege ce funcție predefinită să apeleze și cu ce parametri.
OpenAI primește schema uneltei, analizează intenția userului ("arată-mi ce a cumpărat Popescu în ultimele 3 zile") și îți returnează un JSON curat. Codul tău de backend preia acest JSON, rulează un query parametrizat, sigur, scris de tine, și trimite rezultatul înapoi la model pentru formatare. Am redus erorile de interpretare cu peste 80% trecând de la raw SQL generation la pattern-ul ăsta.
Cum arată fluxul în practică
Pentru implementare, ai nevoie de doi pași mari: definirea funcțiilor în API-ul OpenAI și rularea efectivă a query-ului în codul tău după ce modelul decide ce vrea să facă.
Cel mai mare avantaj aici este că baza de date rămâne complet izolată. LLM-ul nu are credențiale de DB, nu știe dialectul de SQL din spate și nu poate modifica structura tabelelor. El vede doar o descriere semantică a ceea ce poate cere.
Trade-off-urile de care te lovești rapid
Nimic nu e perfect în producție, așa că hai să vorbim despre minusurile reale pe care le-am simțit pe pielea mea.
În primul rând, latența o ia în sus. Ai nevoie de cel puțin două hopuri de rețea. Primul hop: trimiți întrebarea userului, OpenAI decide ce funcție să apeleze. Al doilea hop: rulezi tu query-ul în DB, trimiți datele brute înapoi la OpenAI și modelul generează răspunsul final în limbaj natural. Timpul total de răspuns a sărit de la sub 200ms (un query clasic) la vreo 2.2 secunde în medie.
În al doilea rând, ai costul de tokeni. Trebuie să trimiți descrierile tuturor funcțiilor la fiecare request. Dacă ai 15-20 de unelte complexe cu scheme JSON stufoase, consumi tokeni la greu doar pe contextul inițial.
Pentru rapoarte rapide, căutări filtrate sau un asistent de suport care verifică statusul unei comenzi, pattern-ul ăsta e de departe cea mai sigură variantă. Pentru dashboard-uri analitice grele, rămân la API-urile clasice.
Voi cum ați abordat problema asta? Ați lăsat vreodată un LLM să scrie query-uri direct în baza de date sau mergeți tot pe unelte predefinite?