Senior-grade implementation of a Naive RAG pipeline using Vercel AI SDK v6, LangChain Recursive splitters, and unpdf extraction.
Naive RAG (Basic RAG) is the architectural baseline for grounding Large Language Models in private data. It operates as a linear "Retrieve-then-Generate" bridge. The system identifies relevant document snippets from a local depository and injects them into the model context window before inference. This creates a factual anchor, mitigating the risk of hallucinations and providing clear data provenance.
Engineering Trade-offs
Feature
Technical Impact
Pros
Low architectural complexity; stateless execution; minimal latency overhead for small datasets.
Cons
Linear memory scaling limits; sensitive to chunk boundaries; lacks support for multi-document relational reasoning.
Architecture Overview
The transformation from binary document data to a grounded response follows this data flow:
We implement RecursiveCharacterTextSplitter. This algorithm prioritizes splits on paragraphs and sentences over raw character counts, preserving the logical structure of the data.
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 200,});const segments = await splitter.splitText(fullText);console.log(`[DATA] Segmentation complete. Total chunks generated: ${segments.length}`);
High-Dimensional Vectorization
Transform text segments into 1536-dimensional embeddings. Batch vectorization via embedMany reduces total HTTP overhead compared to sequential processing.
Execute the search using cosineSimilarity. This calculates the angular distance between the query and every document in the local store.
import { embed, cosineSimilarity } from 'ai';const query = "What are the primary safety concerns?";const { embedding: queryVector } = await embed({ model: openai.embedding('text-embedding-3-small'), value: query,});const matches = vectorStore .map(item => ({ ...item, similarity: cosineSimilarity(queryVector, item.vector) })) .sort((a, b) => b.similarity - a.similarity) .slice(0, 3);console.log(`[SEARCH] Query vectorized. Top match similarity: ${matches[0].similarity.toFixed(4)}`);
Grounded Response Generation
The final phase involves prompt augmentation. We inject the top matches as context fragments, forcing the LLM to restrict its response to the provided dataset.
import { generateText } from 'ai';const context = matches.map(m => m.content).join('\n\n---\n\n');const { text: answer } = await generateText({ model: openai('gpt-4o-mini'), system: "Answer using only the provided context snippets. Respond with 'Data not found' if context is insufficient.", prompt: `Context:\n${context}\n\nQuestion: ${query}`,});console.log(`[GEN] Response generated via gpt-4o-mini.`);
Complete Production Script
This script consolidates the logic into a single execution unit with verbose logging.