import OpenAI from 'openai';
const openai = new OpenAI();
// Definim tool-ul pe care LLM-ul îl poate apela
const tools = [
{
type: 'function' as const,
function: {
name: 'get_users_by_filters',
description: 'Obține lista de utilizatori filtrați după oraș și status',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: 'Orașul utilizatorului (ex: Cluj, Bucuresti)' },
status: { type: 'string', enum: ['active', 'inactive'], description: 'Statusul contului' }
},
required: ['city']
}
}
}
];
async function handleUserQuery(prompt: string) {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
tools
});
const toolCall = response.choices[0].message.tool_calls?.[0];
if (toolCall && toolCall.function.name === 'get_users_by_filters') {
const args = JSON.parse(toolCall.function.arguments);
// Validare suplimentară cu Zod aici înainte de DB call!
return await db.select().from('users').where({ city: args.city, status: args.status });
}
return response.choices[0].message.content;
}Salutare! Săptămâna trecută m-am lovit de o cerință clasică de la business: „Vrem să putem întreba aplicația câți utilizatori din Cluj au cumpărat ceva în ultimele 30 de zile”. În loc să scriu încă 10 endpoint-uri de filtrare cu zeci de query params pe care oricum le modificau săptămânal, am zis să încerc un agent simplu bazat pe function calling de la OpenAI.
Am implementat chestia asta pe un proiect de e-commerce cu vreo 12.000 de clienți activi. Rezultatul? Am scăpat de scris rute noi de API și am redus timpul de livrare pentru rapoarte custom la zero. Dar nu e totul roz. Dacă nu ești atent, riști să-ți expui toată baza de date sau să te trezești cu facturi uriașe la API.
Cum funcționează de fapt (fără riscuri de securitate)
Cea mai mare greșeală pe care o văd la început e să lași LLM-ul să scrie direct SQL. Este o rețetă sigură pentru dezastru. Un prompt injectat subtil de un utilizator rău intenționat și te trezești cu un DROP TABLE Users executat cu drepturi de admin.
Soluția safe este să-i dai acces LLM-ului doar la funcții predefinite (tools). Modelul nu rulează el codul, ci doar decide ce funcție să apeleze și cu ce parametri. Tu primești parametrii ăia înapoi în backend, îi validezi cu Zod, și rulezi query-ul tău sigur, cu ORM-ul sau query builder-ul tău preferat.
Dacă modelul o ia pe arătură și trimite un format ciudat de dată, backend-ul tău trebuie să crape elegant, nu să dea crash aplicația. De aceea, folosesc Zod pentru runtime validation înainte de a trimite orice către baza de date. Am pățit o singură dată ca modelul să inventeze un ID de oraș care nu exista, iar eroarea a fost prinsă la timp de validare.
Trade-off-urile de care nu-ți zice nimeni în tutoriale
Metoda asta e genială pentru flexibilitate, dar vine cu un cost real.
În primul rând, latența. Un request normal la DB durează 10-50ms. Cu OpenAI în schemă, fluxul arată așa: user prompt -> apel OpenAI (1-2 secunde) -> rulare query local (20ms) -> trimitere rezultate înapoi la OpenAI pentru formatare (încă 1-2 secunde). Te uiți lejer la un timp total de răspuns de 3-4 secunde. Pentru un dashboard intern e ok, dar pentru utilizatorul final e destul de încet.
În al doilea rând, costurile. La un volum mediu, dacă fiecare user rulează câteva căutări pe zi, tokenii se adună rapid. Trebuie să trimiți istoricul conversației și schemele detaliate ale funcțiilor la fiecare apel. Am rezolvat parțial problema folosind gpt-4o-mini, care e surprinzător de bun la function calling și de vreo 20 de ori mai ieftin decât GPT-4o clasic.
Concluzia mea
Pattern-ul ăsta e perfect pentru tooling intern, admin panels și suport tehnic de nivel 1. Îți salvează ore întregi de muncă plictisitoare de dezvoltare. Însă nu îl pune direct în fața userului final fără un rate limiter extrem de agresiv și fără o validare de fier pe parametrii de input.
Voi ați lăsat până acum un LLM să decidă ce date să extragă din DB în producție, sau încă vi se pare prea riscant?