Overview: Data Sources
RAG requires pulling documents from various sources. They can be:
File-based
PDFs, Word, TXT, JSON, CSV, HTML
Web-based
Websites, APIs, URLs
Database
PostgreSQL, MySQL, MongoDB
Knowledge
Notion, Confluence, Google Docs
Loading Documents
LangChain and LlamaIndex provide loaders for dozens of formats. They all return Document objects with page_content and metadata.
LangChain Example
from langchain_community.document_loaders import (
PyPDFLoader,
TextLoader,
WebBaseLoader,
UnstructuredExcelLoader
)
# Load a PDF
loader = PyPDFLoader("report.pdf")
docs = loader.load() # Returns list of Document objects
# Load a webpage
loader = WebBaseLoader("https://example.com/docs")
docs = loader.load()
# Load plain text
loader = TextLoader("notes.txt")
docs = loader.load()
# Load Excel
loader = UnstructuredExcelLoader("data.xlsx")
docs = loader.load()
Document Structure
Document {
page_content: "Tax deductions include...",
metadata: {
"source": "tax_guide.pdf",
"page": 3,
"author": "IRS",
"date": "2024-01-01"
}
}
Extracting and Enriching Metadata
Metadata is crucial for filtering, attribution, and debugging. Always preserve and enhance it.
Key Metadata to Preserve:
Where the document came from (filename, URL, table name)
When it was created/modified (for versioning)
Who created it (for trust/permissions)
Type/topic (FAQ, policy, research, etc)
Who can access it (for multi-tenant RAG)
Pro tip: Add a chunk_id field when chunking. It helps trace which chunk led to the final answer and enables precise deletions during updates.
Handling Different File Formats
📄 PDFs
Challenge: Images, tables, forms, multiple columns.
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("document.pdf")
docs = loader.load()
# Problem: Loses formatting, images as "Document page X"
Better: Use PyMuPDF or pdfplumber for layout-aware extraction. For scanned PDFs, use OCR (pytesseract).
📊 Excel / CSV
Challenge: Tabular data, headers, types.
import pandas as pd
df = pd.read_csv("data.csv")
# Convert to documents
docs = [
Document(
page_content=f"Name: {row['name']}, Age: {row['age']}",
metadata={"source": "data.csv", "row": i}
)
for i, row in df.iterrows()
]
Tip: For structured data, consider storing in a database and querying by filters, not RAG.
🌐 Web Pages
Challenge: HTML/CSS noise, JavaScript content, dynamic pages.
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://example.com/docs")
docs = loader.load()
# Often includes navigation, ads, etc.
Better: Use BeautifulSoup to clean HTML. Extract only main content. For JavaScript-heavy sites, use Playwright.
🗄️ Databases
Challenge: Queries, joins, permissions, realtime updates.
import psycopg2
conn = psycopg2.connect("dbname=mydb")
cursor = conn.cursor()
cursor.execute("SELECT * FROM articles")
docs = [
Document(
page_content=row['content'],
metadata={"id": row['id'], "title": row['title']}
)
for row in cursor.fetchall()
]
Note: Keep chunk_id aligned with DB row IDs for easy deletion on update.
Preprocessing & Cleaning
Raw documents often contain noise that hurts embedding quality. Clean them before ingestion.
import re
def clean_text(text):
# Remove extra whitespace
text = re.sub(r'\s+', ' ', text).strip()
# Remove URLs (optional, depends on use case)
text = re.sub(r'https?://\S+', '', text)
# Remove email addresses (optional)
text = re.sub(r'\S+@\S+', '', text)
# Remove special characters (keep some for code)
text = re.sub(r'[^\w\s\.\,\!\?\-]', '', text)
return text
# Apply to documents
for doc in documents:
doc.page_content = clean_text(doc.page_content)
Common Issues to Fix:
Extra whitespace/newlines from PDF extraction
Page headers/footers repeated on every page
Navigation text from web pages (menu items, etc)
Encoding issues (special characters, mojibake)
Private information that shouldn't be indexed
Notes
Loader selection matters more than you think
PyPDFLoader is convenient but loses layout information — tables become scrambled text, multi-column layouts merge incorrectly. Use pdfplumber or PyMuPDF when your PDFs have structured content. For scanned PDFs, budget for an OCR step; pytesseract works locally but AWS Textract or Google Document AI produce far better results on complex layouts.
Hash documents before embedding to avoid duplicates
Store a SHA-256 hash of each document's content in a simple key-value store (Redis or even a SQLite table) before calling the embedding API. On re-ingestion, skip any document whose hash already exists. Without this, incremental refreshes silently double-embed the same content, degrading retrieval precision and burning API budget.
Web loaders need aggressive cleaning
WebBaseLoader returns the full DOM text including navigation menus, cookie banners, and footer links. Always strip boilerplate with BeautifulSoup's get_text(separator=' ', strip=True) targeting only the main content element. For JavaScript-rendered pages, swap to PlaywrightURLLoader — async rendering adds 2–3 seconds per page but is unavoidable for SPAs.
Attach a chunk_id before the vector store, not after
Set a deterministic chunk_id (e.g., f"{source_hash}_{chunk_index}") during the loading step, before chunking. If you try to reconstruct IDs later, you lose the ability to do targeted deletes when a single document is updated — you end up re-ingesting everything instead.
RAG Document Ingestion FAQ
What file formats can a RAG system ingest?
Most RAG frameworks support PDF, DOCX, HTML, Markdown, plain text, CSV, and JSON out of the box. For images and tables within PDFs, you need document AI tools like AWS Textract or Azure Document Intelligence.
How do I extract text from PDFs for RAG?
Use PyPDF2 or pdfplumber for text-only PDFs. For scanned PDFs, use OCR via Tesseract or a hosted API. LangChain's PyPDFLoader and LlamaIndex's PDFReader handle most cases with one line of code.
What is document metadata and why does it matter in RAG?
Metadata includes source URL, file name, author, date, and page number. Storing it alongside chunks lets you filter retrieval results (e.g., "only search documents from 2024"), cite sources, and debug which documents were retrieved.
How should I handle duplicate documents in RAG ingestion?
Hash the document content before ingestion. If the hash already exists in your tracking store, skip re-embedding. This prevents duplicate chunks from cluttering retrieval results and wasting embedding API costs.
Can I ingest web pages into a RAG pipeline?
Yes. Use LangChain's WebBaseLoader or a Firecrawl API integration to scrape and clean web pages. Strip navigation, ads, and boilerplate before chunking, and store the URL as metadata for source citation.