import OpenAI from 'openai';
const openai = new OpenAI();
const tools: OpenAI.ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'get_user_orders',
description: 'Află ultimele comenzi ale unui client după ID.',
parameters: {
type: 'object',
properties: {
userId: { type: 'string', description: 'ID-ul unic al utilizatorului' },
limit: { type: 'number', default: 5 }
},
required: ['userId']
}
}
}
];
async function runAgent(userPrompt: string) {
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: 'system', content: 'Ești un asistent util. Folosește uneltele oferite pentru date reale.' },
{ role: 'user', content: userPrompt }
];
let response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools
});
let message = response.choices[0].message;
while (message.tool_calls) {
messages.push(message);
for (const toolCall of message.tool_calls) {
if (toolCall.function.name === 'get_user_orders') {
const args = JSON.parse(toolCall.function.arguments);
// AICI apelezi funcția ta sigură de DB (ex: repository/ORMs)
const dbResult = await db.orders.findMany({
where: { userId: args.userId },
take: args.limit || 5
});
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(dbResult)
});
}
}
response = await openai.chat.completions.create({
model: 'gpt-4o',
messages
});
message = response.choices[0].message;
}
return message.content;
}Toată lumea vrea «chat cu baza de date», dar majoritatea aruncă tot schema-ul în system prompt și se roagă să nu primească un SQL Injection în producție. Am aplicat pattern-ul ăsta de function calling pe un proiect cu peste 12k de interogări lunare și mi-a scăzut rata de erori la sub 1%. Dacă vrei ceva robust, nu-i dai AI-ului liber la scris SQL, ci îi dai unelte (tools) stricte.
Problema cu Text-to-SQL direct
Când îi lași modelului libertatea de a scrie SQL brut (SELECT * FROM...), te lovești rapid de trei probleme masive:
- Halucinații pe coloane: Chiar și
gpt-4omai inventează nume de câmpuri din când în când. - Query-uri nesăbuite: Un
JOINneinspirat pe 6 tabele mari fărăLIMITîți prăjește baza de date în producție. - Securitate fragilă: Prompt injection prin datele existente deja în baza ta de date.
De aceea, abordarea sanitarizată este să încapsulezi accesul la date în funcții backend clasice, iar LLM-ul doar să le "apeleze" extrăgând parametrii corecți.
Arhitectura pattern-ului de Agent Loop
Fluxul pe care îl folosesc e determinist și rigid:
- Trimiți mesajul utilizatorului împreună cu definiția uneltelor (
tools) scrise sub formă de JSON Schema. - Modelul decide dacă are nevoie de date. Dacă da, returnează un
tool_callscu numele funcției și argumentele parsate (de ex:{ userId: "usr_123", limit: 5 }). - Aplicația ta execută funcția locală (care rulează un query SQL pregătit, pe o conexiune de DB read-only, cu timeout scurt).
- Trimiți rezultatul înapoi în conversație cu rolul
tool. - Modelul interpretează datele și formulează răspunsul final în limbaj natural.
Am observat o reducere de peste 30% la build-time pe partea de agenți și o latență îmbunătățită când am filtrat strict proprietățile din JSON Schema. Dacă îi dai un schemă uriașă cu 20 de unelte, modelul devine lent și începe să le încurce.
Trade-off-uri reale
Nimic nu e gratuit în producție. Pattern-ul ăsta vine cu dezavantaje clare:
- Latență (RTT-uri multiple): Fiecare apel de unealtă înseamnă minim încă un round-trip HTTP către OpenAI. Pentru un flux care necesită două unelte secvențiale, utilizatorul așteaptă lejer 3-4 secunde.
- Cost de tokens pe context: Dacă returnezi din DB un JSON imens ca răspuns de
tool, depășești rapid limita de context. Întotdeauna mapează și trunchiază datele din DB la strictul necesar înainte de a le trimite înapoi la GPT.
Concluzie
Codul din secțiunea de mai jos arată loop-ul de bază în TypeScript. Dacă ai un caz de utilizare unde utilizatorii cer rapoarte flexibile, ăsta e singurul mod în care poți dormi liniștit noaptea fără să te temi că un prompt malțios îți dă DROP TABLE.
Voi ce măsuri de siguranță folosiți ca să nu vă prăjească agenții baza de date?