eduardweb.
OpenAI & ClaudeAvansat#nodejs#typescript#openai#function-calling#agents

Cum am construit un agent SQL cu OpenAI Function Calling (fără să-mi sparg baza de date)

De Elena Dumitrescu, 14 iun. 2026 · 15 vizualizări · 3 like-uri

Postat 14 iun. 2026
typescript
import OpenAI from 'openai';

const openai = new OpenAI();

// 1. Definim funcția reală de DB (complet izolată și sigură)
async function getSalesByRegion(region: string, limit: number = 5) {
  // Aici rulează query-ul tău securizat (ex: Prisma, TypeORM)
  // db.query('SELECT * FROM sales WHERE region = $1 LIMIT $2', [region, limit])
  return [
    { product: 'Laptop X', amount: 12000, region },
    { product: 'Phone Y', amount: 8500, region }
  ];
}

// 2. Definim uneltele pe care le punem la dispoziția modelului
const tools: OpenAI.Chat.ChatCompletionTool[] = [{
  type: 'function',
  function: {
    name: 'get_sales_by_region',
    description: 'Aduce vânzările recente dintr-o anumită regiune geografică.',
    parameters: {
      type: 'object',
      properties: {
        region: { type: 'string', description: 'Numele orașului sau regiunii' },
        limit: { type: 'number', description: 'Numărul de rezultate returnate' }
      },
      required: ['region']
    }
  }
}];

async function runAgent(userPrompt: string) {
  const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
    { role: 'user', content: userPrompt }
  ];

  // Pasul 1: Întrebăm modelul ce vrea să facă
  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages,
    tools
  });

  const message = response.choices[0].message;
  
  if (message.tool_calls) {
    messages.push(message); // adăugăm decizia modelului în istoric

    for (const toolCall of message.tool_calls) {
      if (toolCall.function.name === 'get_sales_by_region') {
        const args = JSON.parse(toolCall.function.arguments);
        
        // Pasul 2: Rulăm funcția noastră securizată de DB
        const dbResult = await getSalesByRegion(args.region, args.limit);

        // Pasul 3: Trimitem rezultatul înapoi în conversație
        messages.push({
          role: 'tool',
          tool_call_id: toolCall.id,
          content: JSON.stringify(dbResult)
        });
      }
    }

    // Pasul 4: Generăm răspunsul final în limbaj natural
    const finalResponse = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages
    });
    
    return finalResponse.choices[0].message.content;
  }

  return message.content;
}

Am implementat recent un agent de analytics pentru un magazin online cu peste 15.000 de comenzi active. Clientul voia rapoarte ad-hoc în limbaj natural, dar fără să-și prindă urechile în filtre complicate sau dashboard-uri rigide. În loc să scriu zeci de endpoint-uri de filtrare, am lăsat OpenAI să se ocupe de interpretare prin Function Calling.

Am văzut mulți developeri la început de drum care fac greșeli critice de securitate când vine vorba de AI și baze de date. Vă arăt cum am structurat bucla de execuție ca să fie sigură, predictibilă și fără riscuri de securitate.

De ce Function Calling și nu text-to-SQL direct?

Am văzut destule tutoriale pe net unde lumea îi cere modelului GPT să scrie cod SQL pe care apoi îl rulează direct în baza de date. Să fim serioși, asta e o rețetă sigură pentru dezastru în producție. Fie îți șterge cineva tabela prin prompt injection, fie modelul inventează tabele sau coloane care nu există (halucinație clasică).

Prin Function Calling, noi nu dăm modelului acces la baza de date. Îi dăm doar o listă de unelte (tools) descrise în JSON Schema. Modelul doar decide ce funcție vrea să folosească și cu ce argumente. Noi rulăm codul nostru local (cu ORM-ul nostru, validări de tipuri și query-uri parametrizate), iar modelul primește doar rezultatul curat pentru a-l traduce în text pentru user.

Trade-off-ul de care nu vorbește nimeni

Pattern-ul ăsta merge excelent pentru non-tehnici care vor răspunsuri rapide la întrebări complexe de tipul: "Cât am vândut în București luna trecută comparat cu Cluj?".

Dar are o mare problemă: latența. O singură interogare implică minimum două apeluri la API-ul OpenAI. Primul apel trimite întrebarea userului și primește înapoi numele funcției de apelat. Al doilea apel trimite datele brute extrase din baza de date pentru ca LLM-ul să formuleze răspunsul final în limbaj natural.

Vorbim de 2-4 secunde de așteptare per interogare. Pentru un dashboard interactiv, e enervant de lent. În plus, costurile cresc rapid dacă userii pun aceleași întrebări repetitive pe care le-ai fi putut rezolva cu un cache clasic în Redis.

Cum funcționează bucla de execuție (The Agent Loop)

Ideea de bază e simplă: trimiți promptul la OpenAI împreună cu schema funcțiilor tale. Dacă modelul decide că are nevoie de o funcție, îți întoarce un obiect tool_calls. Rulezi acea funcție în backend-ul tău, adaugi rezultatul ca un mesaj nou în istoric cu rolul tool și trimiți din nou tot istoricul la OpenAI pentru răspunsul final.

În exemplul de cod de mai jos am lăsat o structură curată pe care o folosesc în producție, gata de extins cu propriile voastre funcții de DB.

Cum gestionați voi latența în aplicațiile de tip agent? Ați încercat să puneți un strat de caching semantic sau folosiți modele mai mici direct în producție pentru a reduce timpul de răspuns?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.