Tarek.
← All posts

Building a RAG Pipeline with OpenAI and Next.js

· 2 min read · Admin

What is RAG?

Retrieval-Augmented Generation (RAG) is a technique that combines the power of large language models with external knowledge retrieval. Instead of relying solely on the model's training data, RAG fetches relevant documents from your own knowledge base and injects them into the prompt context.

Why RAG Over Fine-Tuning?

Fine-tuning a model is expensive and slow. RAG lets you update your knowledge base in real-time without retraining. It's also more transparent — you can trace exactly which documents influenced the output.

  • No GPU costs for training

  • Real-time knowledge updates

  • Transparent source attribution

  • Works with any LLM provider

Architecture Overview

Our pipeline has three main stages: document ingestion, vector storage, and query-time retrieval with generation.

Document Ingestion

We'll split documents into chunks using a sliding window approach. Each chunk gets embedded and stored with metadata.

typescript
import { OpenAI } from "openai";​const openai = new OpenAI();​async function embedChunk(text: string): Promise<number[]> {  const response = await openai.embeddings.create({    model: "text-embedding-3-small",    input: text,  });  return response.data[0].embedding;}

Vector Storage with pgvector

PostgreSQL with the pgvector extension is an excellent choice for vector storage. It lets you combine traditional SQL queries with vector similarity search.

sql
CREATE EXTENSION IF NOT EXISTS vector;​CREATE TABLE documents (  id SERIAL PRIMARY KEY,  content TEXT NOT NULL,  metadata JSONB,  embedding VECTOR(1536));

Building the Query Pipeline

At query time, we embed the user's question, find the most similar documents, and pass them as context to the LLM.

typescript
async function queryRAG(question: string) {  const questionEmbedding = await embedChunk(question);  const results = await prisma.$queryRaw`\n    SELECT content, 1 - (embedding <=> ${questionEmbedding}::vector) as similarity\n    FROM documents\n    ORDER BY similarity DESC\n    LIMIT 5\n  `;  const context = results.map(r => r.content).join('\n\n');  const response = await openai.chat.completions.create({\n    model: 'gpt-4o',\n    messages: [\n      { role: 'system', content: 'Answer based on this context:\n' + context },\n      { role: 'user', content: question },\n    ],\n    stream: true,\n  });\n  return response;}

Best Practices

  • Chunk size of 500-1000 tokens works well for most use cases

  • Use overlapping chunks (50-100 token overlap) to avoid losing context

  • Store metadata alongside embeddings

  • Implement hybrid search: combine vector similarity with keyword matching

  • Use reranking models to improve retrieval quality

Conclusion

RAG is the most practical way to ground LLM responses in your own data. With pgvector and Next.js, you can build a complete pipeline that scales well and stays maintainable.