import { OpenAI } from 'openai';
const openai = new OpenAI();
// Definim tool-ul pe care OpenAI îl poate apela
export const dbTools = [
{
type: 'function' as const,
function: {
name: 'getUserSpending',
description: 'Obține suma totală cheltuită de un utilizator într-o anumită lună.',
parameters: {
type: 'object',
properties: {
userId: { type: 'number', description: 'ID-ul unic al utilizatorului.' },
month: { type: 'string', description: 'Numele lunii (ex: January, February).' }
},
required: ['userId', 'month']
}
}
}
];
// Funcția reală de DB, apelată doar după ce validăm parametrii
async function getUserSpending(userId: number, month: string): Promise<number> {
// Aici rulăm un query SQL securizat pe o replică Read-Only
// SELECT SUM(amount) FROM orders WHERE user_id = $1 AND month = $2
return 1250.45;
}Am pus recent în producție un agent simplu bazat pe OpenAI function calling pentru un client care dorea rapoarte ad-hoc direct în Slack. Săptămâna trecută, sistemul a rulat peste 1.200 de interogări fără nicio eroare majoră. Vă arăt cum am structurat schema și, mai ales, de ce nu trebuie niciodată să lăsați un LLM să scrie SQL brut de capul lui.
De ce schema clasică cu Text-to-SQL este o bombă cu ceas
Mulți developeri încep prin a trimite schema bazei de date direct în prompt, cerând LLM-ului să returneze cod SQL pe care apoi îl rulează orbește. Am testat asta inițial pe un set de date cu 15.000 de utilizatori activi.
Rezultatul? Halucinații crunte. Agentul a încercat să facă un JOIN pe două coloane inexistente și, cireașa de pe tort, a rulat o interogare neindexată care a blocat baza de date timp de 4 secunde. Dacă aveți tabele mari, un singur query prost optimizat generat de AI vă poate pune producția în cap.
Pattern-ul sigur: Expune funcții, nu tabele
Soluția pe care am aplicat-o și care funcționează perfect este să tratăm LLM-ul ca pe un utilizator de API, nu ca pe un administrator de bază de date. În loc să-i dăm acces direct la SQL, îi punem la dispoziție un set de funcții Node.js predefinite, cu parametri stricți.
OpenAI nu rulează codul. El doar citește descrierea funcției și decide, pe baza întrebării utilizatorului, ce funcție să apeleze și cu ce argumente.
Cum arată fluxul în producție:
- Utilizatorul întreabă: „Cât a cheltuit userul 482 în luna mai?”
- Trimitem întrebarea și definițiile funcțiilor către OpenAI.
- OpenAI returnează un JSON cu numele funcției
get_user_spendingși argumentul{ "userId": "482", "month": "May" }. - Codul nostru validează argumentele, rulează interogarea SQL securizată (folosind query builders sau ORM-uri, feriți de SQL injection) și trimite rezultatul înapoi la OpenAI.
- OpenAI formatează răspunsul final într-un limbaj natural, prietenos.
Trade-off-uri de care trebuie să ții cont
Nimic nu e gratis în producție, iar modelul acesta are limitări clare.
În primul rând, latența este destul de mare. Ai nevoie de cel puțin două apeluri la API-ul OpenAI: primul ca să determine funcția, al doilea ca să interpreteze rezultatul bazei de date. Asta înseamnă un timp total de răspuns de 2-3 secunde. Pentru un dashboard în timp real e de neacceptat, dar pentru un bot de Slack sau un sistem de raportare internă este mai mult decât decent.
În al doilea rând, costurile pot crește rapid dacă trimiți prea multe date înapoi în context. Dacă funcția ta returnează 500 de rânduri de date brute din baza de date ca să le proceseze LLM-ul, vei consuma mii de tokeni inutil. Regula de aur: filtrează și agreghează datele direct în SQL înainte de a le trimite înapoi către model.
Câteva bune practici din tranșee
- Folosește o replică Read-Only: Chiar dacă folosești funcții predefinite, rulează interogările pe o replică de citire a bazei de date. Astfel, ești 100% asigurat că un bug în codul tău sau o interpretare greșită nu va șterge date.
- Validează intrările: Folosește librării precum Zod sau Joi pentru a valida parametrii returnați de OpenAI înainte de a executa interogarea. LLM-urile pot trimite formate ciudate de date sau stringuri în loc de numere.
- Limitează volumul de date: Pune întotdeauna un
LIMITîn query-urile tale interne. Nu lăsa agentul să ceară „toate tranzacțiile din ultimul an” fără paginare.
Voi ce abordare folosiți pentru interogări în limbaj natural? Ați riscat până acum cu Text-to-SQL direct în producție?