eduardweb.
OpenAI & ClaudeAvansat#typescript#postgresql#backend#openai#agents

Cum am legat OpenAI Function Calling la baza de date fără să risc un dezastru

De Sorin Tudor, 9 sept. 2026 · 19 vizualizări · 2 like-uri

Postat 9 sept. 2026
typescript
async function runAgent(userPrompt: string) {
  const messages: ChatCompletionMessageParam[] = [
    { role: 'system', content: 'Ești un asistent util. Folosește uneltele puse la dispoziție pentru a verifica DB-ul.' },
    { role: 'user', content: userPrompt }
  ];

  while (true) {
    const response = await openai.chat.completions.create({
      model: 'gpt-4o',
      messages,
      tools: dbTools,
      tool_choice: 'auto'
    });

    const message = response.choices[0].message;
    messages.push(message);

    if (!message.tool_calls) return message.content;

    for (const toolCall of message.tool_calls) {
      const toolName = toolCall.function.name;
      const args = JSON.parse(toolCall.function.arguments);
      
      // Execuți codul tău curat și validat
      const result = await executeInternalTool(toolName, args);
      
      messages.push({
        role: 'tool',
        tool_call_id: toolCall.id,
        content: JSON.stringify(result)
      });
    }
  }
}

Am văzut prea multe tutoriale care dau prompt la model cu schema bazei de date și îi cer să scrie direct query-ul SQL. Dacă faci asta în producție, e doar o chestiune de timp până când cineva face prompt injection sau modelul trântește un full table scan pe tabele de milioane de rânduri. Hai să vedem cum construiești un agent determinist cu OpenAI Tools API, fără să dai acces direct la motorul de baze de date.

De ce funcții discrete și nu Text-to-SQL

Anul trecut am lucrat la un tool intern pentru o echipă de suport la un magazin B2B cu vreo 40.000 de comenzi lunare în PostgreSQL. Oamenii pierdeau ore întregi căutând manual statusuri de livrare, awb-uri sau detalii despre comenzi anulate.

Tentația inițială a echipei a fost: „dăm prompt la GPT-4o cu DDL-ul tabelelor și lăsăm modelul să ruleze SELECT-uri”. Greșit din două motive majore:

  1. Nu ai control pe complexitatea query-ului generat. Am pățit ca la un test simplu să bage un query cu 4 JOIN-uri inutile și să țină conexiunea blocată 12 secunde.
  2. Securitatea e un coșmar. Oricât îi spui în system prompt „rulează doar SELECT”, e suficient un text malițios în câmpul de observații al clientului ca să manipuleze LLM-ul.

Pattern-ul corect e simplu: definești funcții RPC stricte (ex. getOrderStatus({ orderId }), searchOrdersByEmail({ email, limit })) și lași OpenAI doar să decidă ce funcție apelează și cu ce argumente.

Bucla agentului (The Execution Loop)

Arhitectura unui agent de genul ăsta nu are nevoie de framework-uri greoaie ca LangChain. În esență, e un simplu loop while în TypeScript:

  1. Trimiți istoricul de mesaje și lista de unelte (tools) către API.
  2. Dacă modelul returnează finish_reason: 'tool_calls', iei argumentele parsate, le validezi prin Zod (obligatoriu, nu te încrezi orbește în JSON-ul returnat de OpenAI) și execuți codul tău curat din backend (Prisma, Drizzle, query direct cu pg).
  3. Adaugi răspunsul bazei de date în mesaje cu rolul tool și ID-ul apelului.
  4. Re-trimiți întreg istoricul către model.
  5. Când modelul consideră că are datele necesare, returnează text normal pentru utilizator, iar bucla se oprește.

Trade-off-uri reale

Sistemul ăsta e excelent pentru interogări de business cunoscute. E predictibil, folosește indexurile pe care le-ai definit deja și nu lasă loc de surprize în baza de date.

Nasol e când utilizatorii încep să ceară analize complexe sau ad-hoc: „Care a fost valoarea medie a comenzilor de pe județul Cluj în zilele de marți din ultimele 6 luni?”. Aici pattern-ul de funcții discrete eșuează lamentabil, fiindcă nu poți anticipa fiecare combinație de filtre. Pentru cazuri analitice grele, o replică Postgres strict read-only izolată într-un sandbox cu limită strictă de timeout rămâne singura opțiune viabilă.

Alt detaliu: latența. Dacă modelul are nevoie de 2 apeluri succesive de funcții pentru a răspunde (ex. caută userul după email, apoi caută comenzile după userId), pierzi lejer 2.5 - 3 secunde doar în roundtrip-uri de rețea cu OpenAI. Pune neapărat un streaming UI intermediar ca să vadă utilizatorul că „agentul caută comanda...”.

Voi ce abordare folosiți pentru rapoarte dinamice din LLM: tool-uri predefinite sau text-to-SQL pe replică dedicată?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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