Large Language Models (LLMs) like GPT-4 are incredibly intelligent, but they suffer from two fatal flaws: they hallucinate facts, and their knowledge is frozen in time. If you ask an LLM about your company's proprietary internal documents, it will fail. Retrieval-Augmented Generation (RAG) solves this by allowing your backend to 'read' your private database, find the relevant information, and inject it into the LLM's prompt before it answers.
Module 1: What is an Embedding?
Computers do not understand words; they understand numbers. An embedding is a process that converts a sentence into a massive array of floating-point numbers (a Vector) that represents the semantic meaning of that sentence.
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function generateEmbedding(text) {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
});
// Returns an array of 1536 floating point numbers: [-0.012, 0.443, -0.992...]
return response.data[0].embedding;
}Module 2: The Vector Database
Standard SQL databases (PostgreSQL) are designed to find exact matches (WHERE name = 'John'). Vector Databases (like Pinecone, Weaviate, or pgvector) are designed to find mathematical similarity between embeddings using algorithms like Cosine Similarity or K-Nearest Neighbors (KNN).
import { Pinecone } from '@pinecone-database/pinecone';
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pc.index('company-docs');
async function ingestDocument(docId, textContext) {
const vector = await generateEmbedding(textContext);
await index.upsert([
{
id: docId,
values: vector,
// Store the original text as metadata so we can retrieve it later
metadata: { text: textContext, author: 'HR Dept' }
}
]);
}Module 3: The RAG Pipeline Architecture
When a user asks a question, we don't send it straight to the LLM. Instead, we execute the RAG Pipeline.
The 4 Steps of RAG
- 1. Embed the Query: Convert the user's question into a Vector.
- 2. Search: Query the Vector DB for the top 3 most mathematically similar vectors.
- 3. Augment: Extract the original text from the database results and inject it into a strict System Prompt.
- 4. Generate: Send the Augmented Prompt to the LLM.
async function askQuestion(userQuery) {
// 1. Embed the user's question
const queryVector = await generateEmbedding(userQuery);
// 2. Search Pinecone for the Top 3 most relevant documents
const searchResults = await index.query({
vector: queryVector,
topK: 3,
includeMetadata: true
});
// 3. Extract the text
const contextText = searchResults.matches.map(m => m.metadata.text).join('\n\n');
// 4. Augment the Prompt
const systemPrompt = `
You are a helpful company assistant. Answer the user's question ONLY using the context provided below.
If the answer is not in the context, say "I don't know."
CONTEXT:
${contextText}
`;
// 5. Generate Answer
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userQuery }
]
});
return completion.choices[0].message.content;
}Module 4: Advanced Document Chunking
You cannot embed a 500-page PDF as a single vector. You must split it into "Chunks". If chunks are too small (1 sentence), you lose context. If chunks are too large (10 pages), the embedding becomes mathematically diluted.