The retrieval stack begins with the Document class, a tiny data wrapper with two fields: page_content as a string and metadata as a dict. Every loader, splitter, retriever, and vector store in LangChain works with Documents, so understanding this class is foundational. DocumentLoader implementations exist for dozens of formats — TextLoader for plain text, PyPDFLoader for PDFs, WebBaseLoader for HTML pages — and each returns a list of Documents. Once loaded, large documents are typically split into smaller chunks with a TextSplitter because embedding models and LLM context windows have finite capacity. RecursiveCharacterTextSplitter is the workhorse: it tries a list of separators in order — paragraph breaks, then newlines, then spaces, then empty string — and picks the first that yields pieces under chunk_size, with chunk_overlap characters carried over between chunks to avoid losing context at boundaries. Other splitters specialize in code (using a Language enum to respect function and class boundaries), HTML (grouping by header tags), Markdown (preserving header paths and code blocks as metadata), tokens (using tiktoken for token-precise splits), and semantics (detecting breakpoints by embedding distance between consecutive sentences).
Embeddings convert text into numerical vectors, and LangChain's Embeddings interface is intentionally asymmetric: embed_documents returns a list of vectors (where providers can cache), while embed_query returns a single vector for a search query. OpenAI's third-generation embeddings, text-embedding-3-small with 1536 dimensions and text-embedding-3-large with 3072 dimensions, support a dimensions parameter that lets you truncate to a smaller size for cheaper storage with minimal recall loss. To build a custom integration, subclass the Embeddings base and implement both methods; the protocol is intentionally small so any vector-capable model can plug in.
Vector stores index these embeddings for similarity search. Common choices include FAISS — fast, in-memory, with save_local and load_local for on-disk persistence — Chroma (open-source with persistent local storage via a persist_directory), InMemoryVectorStore (numpy-based, zero extra dependencies, good for tests and small corpora), and managed services such as Pinecone, Weaviate, and Qdrant, plus PGVector for staying inside Postgres. To build a custom VectorStore, subclass the base and implement from_texts, add_texts, similarity_search, and optionally delete and the async variants. Vector stores expose a uniform Retriever via as_retriever, which works in LCEL chains and supports search_type configurations of plain similarity, mmr (maximal marginal relevance, picking documents that are relevant and diverse), and similarity_score_threshold (dropping documents below a normalized cosine score).
More sophisticated retrievers solve specific retrieval problems. MultiQueryRetriever has an LLM generate several paraphrased queries, retrieves documents for each, and merges the results to improve recall. SelfQueryRetriever translates a natural-language question into a structured query with both semantic and metadata filters, requiring a vector store that supports metadata filtering. ParentDocumentRetriever indexes small chunks for precise matching but returns the larger parent document for richer LLM context. EnsembleRetriever combines results from multiple retrievers — typically a BM25 keyword retriever and a vector retriever — using reciprocal rank fusion with configurable weights, which is a powerful default for hybrid search. You can also subclass BaseRetriever to implement custom retrieval logic, setting model_config to allow arbitrary types and lc_namespace for serialization.