# Cum construim integrare AI: un spike de două săptămâni, un set de date de referință și o factură pe care o poți prezice

Trei sisteme AI construite de noi sunt astăzi în producție. Un asistent de cercetare pentru un client din SUA, care rulează pe AWS Bedrock cu un orchestrator și zece sub-agenți specializați, douăzeci și cinci de unelte și retrieval peste un corpus indexat de granturi și publicații. Bob, asistentul nostru multi-tenant, cu memorie conversațională, retrieval peste documente, voce și o integrare cu emailul. Și generatorul de întrebări din tenSurvey, care scrie întrebări de sondaj în peste treizeci de limbi.

Niciunul nu a început cu un model. Au început cu un tabel de cazuri reale și cu o întrebare: funcționează suficient de bine, pe datele noastre, ca să merite construit? Acest articol e metoda pe care o folosim ca să răspundem la întrebare și ca să transformăm un „da” într-o funcționalitate care supraviețuiește contactului cu utilizatorii.

## Pasul 1: spike-ul, două săptămâni pe date reale

Fiecare colaborare începe cu un spike, iar spike-ul începe cu date. Cerem cincizeci până la două sute de exemple reale ale sarcinii: documentele care trebuie rezumate, tichetele care trebuie clasificate, întrebările pe care oamenii chiar le pun. Nu cele curate. Cele cu greșeli de tastare, câmpuri lipsă și cazul-limită pe care îl știe doar persoana care face treaba asta de cinci ani.

Apoi construim cel mai subțire pipeline posibil și măsurăm. Pentru o sarcină de extragere din documente asta înseamnă: model, prompt, ieșire structurată, un script care rulează toate cele două sute de cazuri și le compară cu ce a spus un om. Pentru asistentul de cercetare a însemnat: poate retrieval-ul să găsească pasajul corect în corpus, și îl citează răspunsul? Rezultatul spike-ului e un tabel cu acuratețe, latență și cost per caz, și o recomandare care uneori e „nu construi asta încă”.

Recomandarea aceea s-a întâmplat. O sarcină de clasificare în care modelul era de acord cu revizorii umani în 82% din cazuri sună impresionant până afli că revizorii erau de acord între ei în 84% din cazuri. Sarcina era ambiguă, nu modelul. Am petrecut a doua săptămână ajutând clientul să clarifice categoriile în loc să ajustăm prompt-uri, iar funcționalitatea finală a fost mai bună datorită asta.

## Pasul 2: setul de referință, înaintea arhitecturii

Cele două sute de cazuri din spike devin setul de date de referință: intrări împerecheate cu ce înseamnă „corect”, agreat cu clientul. Stă în repository lângă cod și rulează în CI la fiecare modificare a unui prompt, a unei versiuni de model, a unei setări de retrieval sau a unei definiții de unealtă.

E cel mai important artefact dintr-un proiect AI și e cel pe care majoritatea echipelor îl sar. Fără el, fiecare schimbare de prompt e o impresie. Cu el, o schimbare fie trece poarta, fie nu, iar cifra e vizibilă pentru toată lumea în pull request. Când Anthropic lansează un model nou, rulăm setul de referință pe el în aceeași după-amiază și știm într-o oră dacă schimbăm. Exact asta am făcut când [Fable 5.1 a înlocuit Fable 5](/ro/blog/claude-fable-5-1-vs-fable-5).

<div class="article-figure">
<svg viewBox="0 0 900 300" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Arhitectura unui sistem de agenți: cererea utilizatorului, orchestrator, sub-agenți specializați, unelte prin MCP, retrieval peste documente, guardrails și jurnal de audit">
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<text x="40" y="34" fill="#f1f3ff" font-size="15" font-weight="700">Forma unui sistem de agenți pe care îl livrăm</text>
<rect x="40" y="120" width="110" height="46" rx="8" fill="#151b2e" stroke="#9aa3c7"/>
<text x="95" y="148" text-anchor="middle" fill="#f1f3ff">Utilizator / app</text>
<rect x="190" y="120" width="130" height="46" rx="8" fill="#151b2e" stroke="#7b8cff"/>
<text x="255" y="141" text-anchor="middle" fill="#f1f3ff">Guardrails</text>
<text x="255" y="157" text-anchor="middle" fill="#9aa3c7" textLength="120" lengthAdjust="spacingAndGlyphs">verificări intrare + ieșire</text>
<rect x="360" y="120" width="140" height="46" rx="8" fill="#151b2e" stroke="#4fffb0"/>
<text x="430" y="141" text-anchor="middle" fill="#f1f3ff">Orchestrator</text>
<text x="430" y="157" text-anchor="middle" fill="#9aa3c7" textLength="130" lengthAdjust="spacingAndGlyphs">planifică, deleagă, oprește</text>
<rect x="540" y="60" width="140" height="40" rx="8" fill="#151b2e" stroke="#ffd166"/>
<text x="610" y="85" text-anchor="middle" fill="#f1f3ff">Sub-agent: căutare</text>
<rect x="540" y="123" width="140" height="40" rx="8" fill="#151b2e" stroke="#ffd166"/>
<text x="610" y="148" text-anchor="middle" fill="#f1f3ff">Sub-agent: analiză</text>
<rect x="540" y="186" width="140" height="40" rx="8" fill="#151b2e" stroke="#ffd166"/>
<text x="610" y="211" text-anchor="middle" fill="#f1f3ff">Sub-agent: redactare</text>
<rect x="720" y="60" width="140" height="40" rx="8" fill="#151b2e" stroke="#ff6b8a"/>
<text x="790" y="79" text-anchor="middle" fill="#f1f3ff">Unelte prin MCP</text>
<text x="790" y="93" text-anchor="middle" fill="#9aa3c7" textLength="130" lengthAdjust="spacingAndGlyphs">API-urile tale, cu scopuri</text>
<rect x="720" y="123" width="140" height="40" rx="8" fill="#151b2e" stroke="#ff6b8a"/>
<text x="790" y="142" text-anchor="middle" fill="#f1f3ff">Retrieval</text>
<text x="790" y="156" text-anchor="middle" fill="#9aa3c7">pgvector / OpenSearch</text>
<rect x="720" y="186" width="140" height="40" rx="8" fill="#151b2e" stroke="#ff6b8a"/>
<text x="790" y="205" text-anchor="middle" fill="#f1f3ff">Aprobare umană</text>
<text x="790" y="219" text-anchor="middle" fill="#9aa3c7">acțiuni ireversibile</text>
<g stroke="#2a3150" stroke-width="2" fill="none">
<line x1="150" y1="143" x2="190" y2="143"/><line x1="320" y1="143" x2="360" y2="143"/>
<line x1="500" y1="143" x2="540" y2="80"/><line x1="500" y1="143" x2="540" y2="143"/><line x1="500" y1="143" x2="540" y2="206"/>
<line x1="680" y1="80" x2="720" y2="80"/><line x1="680" y1="143" x2="720" y2="143"/><line x1="680" y1="206" x2="720" y2="206"/>
</g>
<rect x="190" y="230" width="670" height="34" rx="8" fill="#151b2e" stroke="#2a3150"/>
<text x="525" y="252" text-anchor="middle" fill="#9aa3c7">Fiecare apel e urmărit: prompt, model, tokeni, latență, unelte. Evaluările rulează pe același format.</text>
</g>
</svg>
</div>

## Pasul 3: o arhitectură care presupune că modelul va greși

Odată ce spike-ul spune da, proiectăm sistemul în jurul unei singure presupuneri: modelul va greși uneori, iar arhitectura trebuie să facă asta sigur și vizibil.

**Accesul la model.** Pe AWS folosim Claude prin Bedrock: datele rămân în contul și regiunea clientului, nimic nu e folosit la antrenare, iar IAM controlează cine poate apela ce. În afara AWS folosim direct API-ul Anthropic. Când datele nu pot părăsi deloc o rețea privată, am rulat modele open-weight pe hardware-ul clientului; notele noastre despre [lansarea open mixture-of-experts de la Mistral](/ro/blog/mistral-small-4-open-source-moe) și despre [rularea Claude Code pe un model local](/ro/blog/kimi-k3-local-remote-claude-code) vin din munca aceea.

**Ieșiri structurate, validate de cod.** Fiecare răspuns al modelului care alimentează un sistem e un obiect tipat, validat înainte de a fi folosit. Un rezumat e un string; o factură extrasă e o schemă cu sume care trebuie să se adune corect. Dacă validarea eșuează, codul reîncearcă cu eroarea, apoi cade pe o coadă umană. Modelul nu scrie niciodată direct într-o bază de date.

**Retrieval cu citări.** Pentru orice răspuns construit din documente, răspunsul poartă pasajele din care a fost construit, iar interfața le arată. Căutarea hibridă (cuvinte-cheie plus embeddings) pe PostgreSQL cu pgvector acoperă majoritatea corpusurilor; OpenSearch când corpusul e mare sau tiparele de interogare sunt complexe. Ajustăm chunking-ul și re-ranking-ul pe setul de referință, nu pe un benchmark.

**Agenți care întreabă înainte să acționeze.** Asistentul de cercetare folosește un orchestrator care planifică și deleagă către sub-agenți specializați, fiecare cu un set restrâns de unelte expuse prin servere MCP cu scopuri explicite. Cititul e liber; orice schimbă starea în afara sistemului așteaptă un click uman. Jurnalul de audit înregistrează fiecare apel de unealtă cu intrările lui, așa că „de ce a făcut asta” are întotdeauna un răspuns. Am scris despre platforma din spate în [platforma Agent Factory](/ro/blog/agent-factory-platform) și despre tiparul general în [agenți AI enterprise la scară](/ro/blog/enterprise-ai-agents-at-scale).

**Prompt injection e o problemă de intrare.** Documentele, emailurile și paginile web date unui model nu sunt de încredere. Instrucțiunile găsite în ele sunt date, niciodată comenzi. Guardrails-urile verifică intrările pentru tipare de injecție și ieșirile pentru secrete scurse sau conținut în afara politicii, iar sub-agenții care citesc conținut extern nu au unelte care scriu.

## Pasul 4: un cost pe care îl poți pune într-un buget

În timpul spike-ului știm deja tokenii per caz. Înainte să înceapă construcția, clientul primește un cost per document, per conversație sau per tichet, la volumul așteptat, pentru două-trei variante de model. De obicei ăsta e momentul în care câștigă cel mai mic model care trece evaluările, și în care caching-ul de prompt-uri și procesarea în loturi sunt proiectate de la început, nu adăugate după.

În producție, fiecare apel își duce numărul de tokeni în sistemul de tracing. Există bugete per funcționalitate și alerte când cheltuiala unei zile depășește prognoza. Am văzut o singură buclă de retry dublând o factură lunară într-un sistem moștenit; o alertă de buget ar fi prins-o într-o oră. Pentru costurile propriilor noastre unelte am făcut același exercițiu public în [ce cumpără de fapt 200 de dolari de Claude](/ro/blog/claude-max-vs-api-what-200-dollars-really-buys).

<div class="article-figure">
<svg viewBox="0 0 900 250" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Bucla de evaluare: o schimbare de prompt, model sau retrieval rulează setul de referință în CI, trece o poartă, e lansată în etape, monitorizată, iar eșecurile noi intră înapoi în setul de referință">
<g font-family="Inter,system-ui,sans-serif" font-size="12">
<text x="40" y="34" fill="#f1f3ff" font-size="15" font-weight="700">Bucla care ține calitatea să nu alunece</text>
<rect x="40" y="90" width="130" height="50" rx="8" fill="#151b2e" stroke="#7b8cff"/>
<text x="105" y="111" text-anchor="middle" fill="#f1f3ff">Schimbare</text>
<text x="105" y="128" text-anchor="middle" fill="#9aa3c7">prompt · model · RAG</text>
<rect x="210" y="90" width="130" height="50" rx="8" fill="#151b2e" stroke="#4fffb0"/>
<text x="275" y="111" text-anchor="middle" fill="#f1f3ff">Evaluări în CI</text>
<text x="275" y="128" text-anchor="middle" fill="#9aa3c7">set de referință</text>
<rect x="380" y="90" width="110" height="50" rx="8" fill="#151b2e" stroke="#ffd166"/>
<text x="435" y="111" text-anchor="middle" fill="#f1f3ff">Poartă</text>
<text x="435" y="128" text-anchor="middle" fill="#9aa3c7">scor ≥ referință</text>
<rect x="530" y="90" width="130" height="50" rx="8" fill="#151b2e" stroke="#ff6b8a"/>
<text x="595" y="111" text-anchor="middle" fill="#f1f3ff">Lansare în etape</text>
<text x="595" y="128" text-anchor="middle" fill="#9aa3c7">1% → 10% → toți</text>
<rect x="700" y="90" width="160" height="50" rx="8" fill="#151b2e" stroke="#9aa3c7"/>
<text x="780" y="111" text-anchor="middle" fill="#f1f3ff">Monitorizare</text>
<text x="780" y="128" text-anchor="middle" fill="#9aa3c7">calitate · latență · cost</text>
<g stroke="#2a3150" stroke-width="2" fill="none">
<line x1="170" y1="115" x2="210" y2="115"/><line x1="340" y1="115" x2="380" y2="115"/><line x1="490" y1="115" x2="530" y2="115"/><line x1="660" y1="115" x2="700" y2="115"/>
<path d="M780 140 L780 190 L105 190 L105 140" stroke="#4fffb0" stroke-dasharray="6 4"/>
</g>
<text x="442" y="212" text-anchor="middle" fill="#4fffb0">eșecurile noi din producție devin cazuri noi de referință</text>
</g>
</svg>
</div>

## Pasul 5: operează-l ca pe software, pentru că asta e

Funcționalitatea se lansează în etape: un procent din trafic, apoi zece, apoi toată lumea, cu evaluările și tracing-ul urmărind fiecare etapă. Prompt-urile sunt versionate în repository, nu într-un dashboard. Versiunile de model sunt fixate și actualizate deliberat, prin setul de referință. Eșecurile din producție sunt triate săptămânal, iar cele interesante devin cazuri noi de referință, așa crește setul de la două sute de cazuri la două mii fără ca cineva să scrie date de test de mână.

Tot aici suntem onești despre ce nu repară AI-ul. Un proces stricat cu un model în față e un proces stricat mai rapid. Am refuzat mai mult de un proiect anul acesta din motivul ăsta și am spus de ce în [AI nu îți va repara aplicația stricată](/ro/blog/ai-wont-fix-your-broken-app).

## Ce folosim și ce nu

Construim pe Claude pentru că e modelul care s-a comportat cel mai bine pe evaluările clienților noștri pentru muncă agentică și cu multe documente, și pentru că Bedrock face ușor de răspuns la întrebările enterprise (rezidență, antrenare, IAM). Am folosit framework-ul de agenți Strands pe Bedrock pentru sisteme multi-agent, MCP pentru unelte, pgvector și OpenSearch pentru retrieval și PostgreSQL simplu pentru tot ce produce modelul. Nu avem relație de reseller cu nimeni; când un model open trece evaluările la o fracțiune din cost, o spunem.

Ce nu facem sunt demo-uri. Dacă o funcționalitate nu poate fi măsurată pe date reale, nu vom pretinde că funcționează. Dacă ai un proces bazat pe text pe care bănuiești că l-ar putea prelua un model, [trimite-ne cincizeci de exemple reale](/contact) și îți spunem în două săptămâni dacă poate.
