Cum construiești un sistem RAG cu Next.js și Vercel AI SDK
Construiești un sistem RAG complet pe stack-ul tău: Next.js, Vercel AI SDK și MongoDB Atlas. Cod real, pas cu pas, de la ingestion la răspuns.

Un model lingvistic nu știe nimic despre documentele tale interne. RAG (Retrieval-Augmented Generation) rezolvă asta: cauți bucățile relevante din baza ta de cunoștințe, le pui în context și lași modelul să răspundă pe baza lor.
Multe ghiduri adaugă o bază de date vectorială separată — Pinecone, Qdrant sau pgvector. Nu e obligatoriu. Dacă folosești deja MongoDB, Atlas Vector Search ține embeddings în aceeași colecție cu datele tale: mai puțină infrastructură, un singur loc de administrat. Tot stack-ul: Next.js 16, Vercel AI SDK și MongoDB. Cod complet, pas cu pas.
Dacă nu ești sigur ce e RAG sau dacă e alegerea potrivită, citește mai întâi ce este RAG și când îl folosești.
Ce construiești (arhitectura pe scurt)
Sistemul are două fluxuri.
Ingestion (o dată, sau când adaugi documente):
- Spargi documentul în bucăți mici (chunks).
- Generezi un embedding pentru fiecare bucată.
- Stochezi bucata + vectorul în MongoDB.
Query (la fiecare întrebare):
- Generezi un embedding pentru întrebare.
- Cauți semantic în Mongo cu
$vectorSearch. - Pui bucățile găsite în system prompt și apelezi
streamText.
Atât. Restul e cod.
Pentru vederea de ansamblu — harta completă de componente, contractele dintre ele și deciziile care se iau o singură dată — vezi arhitectura unui sistem RAG.

Ce-ți trebuie în stack
- MongoDB Atlas — vector search rulează doar pe Atlas (clusterul M0 gratuit merge pentru test), nu pe un MongoDB community obișnuit.
- Cheie OpenAI — pentru embeddings și generare.
- Pachetele AI SDK:
Mongoose îl ai deja în proiect. Folosim modelul text-embedding-3-small (1536 dimensiuni) — ieftin și suficient pentru majoritatea cazurilor.
Pasul 1 — Stochezi embeddings în MongoDB
Întâi, un model pentru bucăți. Câmpul embedding e un array de numere.
Apoi, indexul vectorial. Atenție: acesta nu se creează din Mongoose. Îl definești în Atlas (UI → Atlas Search → Create Index, tip vectorSearch), cu acest JSON:
numDimensions trebuie să fie exact 1536. Dacă schimbi modelul de embedding, schimbi și numărul aici — altfel indexul respinge vectorii. Câmpul filter pe source îți permite mai târziu să cauți doar într-un anumit document.
Pasul 2 — Ingestion: chunking și embedding
Modelul de embedding pierde calitate pe texte lungi. Deci spargi documentul în bucăți. O variantă simplă, cu overlap ca să nu tai ideile la mijloc:
Pentru producție, un chunking pe paragrafe sau propoziții dă rezultate mai bune. Pentru un MVP, varianta de mai sus e suficientă.
Acum generezi embeddings pentru toate bucățile dintr-un singur apel cu embedMany și le salvezi:
embedMany trimite toate bucățile odată și păstrează ordinea — embeddings[i] corespunde lui chunks[i].
Pasul 3 — Retrieval: cauți semantic cu $vectorSearch
La query, generezi embedding pentru întrebare cu embed, apoi rulezi un pipeline de agregare. $vectorSearch trebuie să fie prima etapă:
Câteva detalii care contează:
indexe numele exact al indexului din Atlas. Greșești numele → zero rezultate, fără eroare.numCandidates(câți vectori scanează) trebuie să fie mai mare decâtlimit. 100 e un punct bun de plecare.vectorSearchScoremerge de la 0 la 1. Filtrul$gte: 0.5aruncă bucățile slabe, ca să nu bagi zgomot în context.

Pasul 4 — Generation: streamText cu context
Endpoint-ul de chat. Extragi întrebarea, faci retrieval, pui contextul în system prompt și dai drumul la streaming:
Două lucruri specifice AI SDK 5:
convertToModelMessagestransformă mesajele din format UI (cele de lauseChat) în formatul pe care îl așteaptă modelul.- Răspunsul se întoarce cu
toUIMessageStreamResponse(). Dacă ai văzuttoDataStreamResponse()în tutoriale vechi — a dispărut în v5.
System prompt-ul care îi spune modelului să nu inventeze e partea cea mai importantă. Fără el, modelul completează golurile din memoria lui și pierzi tot rostul RAG-ului.
Pasul 5 — Frontend cu useChat
Componenta de client. În AI SDK 5, useChat vine din @ai-sdk/react, gestionezi tu input-ul, iar mesajele sunt array de parts:
sendMessage({ text: input }) trimite întrebarea către /api/chat. Răspunsul curge înapoi token cu token. Gata — ai un sistem RAG funcțional.
Capcane de producție
MVP-ul de mai sus merge. Înainte să-l pui în fața clienților, ai grijă la:
- Indexul nu e gata instant. După ce-l creezi în Atlas, durează câteva secunde până se construiește. Până atunci,
$vectorSearchîntoarce zero rezultate. Verifică statusul în Atlas. - Nepotrivirea de dimensiuni.
text-embedding-3-smallare 1536,text-embedding-3-largeare 3072. Dacă schimbi modelul fără să refaci indexul și să re-embeddezi tot, totul se rupe în tăcere. - Costul la ingestion. Re-embeddarea unei baze mari de documente costă. Salvează vectorii o dată; nu-i regenera la fiecare deployment.
- Mărimea chunk-ului. Bucăți prea mari diluează relevanța, prea mici pierd contextul. 500–1000 de caractere e un interval rezonabil de la care pornești și măsori.
- Filtrarea pe sursă. Câmpul
filterdin index îți permite să restrângi căutarea la un singur document sau client. Esențial pentru aplicații multi-tenant. - Evaluarea. Fără un set de întrebări de test, nu știi dacă o schimbare a îmbunătățit sau a stricat retrieval-ul. Măsoară, nu ghici.
Pentru când abordarea asta nu e suficientă, compară cu alternativa în RAG vs fine-tuning. Și dacă vrei tot ecosistemul de implementare, ai mai multe articole în categoria Implementare & Stack Tehnic.
Întrebări frecvente
Pot folosi MongoDB local, fără Atlas?
Nu pentru $vectorSearch. Vector search e o funcție Atlas. Poți rula un deployment Atlas local prin Atlas CLI pentru dezvoltare, dar nu merge pe un MongoDB community obișnuit.
Ce model de embedding aleg?
text-embedding-3-small (1536 dimensiuni) e ieftin și bun pentru majoritatea cazurilor. Treci la text-embedding-3-large doar dacă măsori o nevoie reală de acuratețe mai mare — și nu uita să schimbi numDimensions în index.
Câte rezultate să returnez din retrieval?
Începe cu 5. Prea puține și modelul n-are context; prea multe și umpli fereastra de context cu zgomot și plătești mai mulți tokeni. Ajustează în funcție de mărimea chunk-urilor.
De ce primesc zero rezultate de la $vectorSearch?
Trei cauze frecvente: numele indexului e greșit, indexul încă se construiește, sau numDimensions nu se potrivește cu vectorii salvați. Verifică-le în ordinea asta.
Funcționează cu alt model decât OpenAI?
Da. Vercel AI SDK e agnostic de provider. Schimbi openai(...) cu anthropic(...) sau alt adapter pentru generare. Pentru embeddings, asigură-te că numDimensions din index se potrivește cu modelul ales.
Cât costă să rulezi un astfel de sistem?
Depinde de volumul de documente și de trafic. Costurile principale sunt embeddings (o dată, la ingestion) și apelurile de generare (la fiecare întrebare). Detaliem bugetul în cât costă un sistem RAG.
Pornește
Ai acum un sistem RAG complet pe stack-ul tău, fără infrastructură vectorială separată. Construim astfel de sisteme end-to-end — cu evaluare, filtrare pe surse și deployment în producție — ca parte din serviciul de Integrări AI & RAG. Hai să vorbim despre proiectul tău.
Andrei Badulescu
Fondator & Software ArchitectConstruiește sisteme B2B la BaseTech — ERP la comandă, platforme SaaS, agenți AI și arhitecturi programmatic SEO. Scrie despre deciziile tehnice din spatele lor: stack, trade-off-uri și ce ține la scară.
Vezi profilul autorului →Articole conexe

RAG pe procedurile de urgență: documentul se execută
Planul de intervenție se aplică în minute, prin fum și fără curent. Ce cere asta de la un sistem de retrieval: mod degradat, rol pe tură, cronometru.

RAG pe nomenclatorul arhivistic: ștergerea ca obligație
Pe o arhivă, răspunsul corect poate fi că documentul nu mai trebuie să existe. Cum distinge sistemul o absență legitimă de o pierdere reală.

RAG pe documentația SSM: absența dovezii e chiar fapta
„Nu găsesc fișa" acoperă trei fapte diferite, cu consecințe diferite. Pe documentația SSM, absența unei înregistrări e ea însăși contravenția.
Insights pentru companii
care construiesc
Articole noi despre ERP, AI, agenți și pSEO, direct pe email. Fără spam.