LangChain
The complete guide
A Python framework for building LLM-based applications. Connecting language models to data sources, tools and Agents — from simple RAG to autonomous Agents in production.
What is LangChain?
LangChain is an open-source framework for building applications based on large language models (LLMs). It was created in November 2022 by Harrison Chase and has since become one of the fastest-growing projects on GitHub, with over 90,000 stars and hundreds of active contributors. LangChain is available for both Python and JavaScript/TypeScript.
The problem it solves is significant: language models like GPT-4o, Claude and Gemini are powerful tools, but on their own they're limited to the knowledge in their Training and their Context window. LangChain lets you connect them to external data sources — PDF files, databases, APIs — and give them tools to act in the real world, like searching the internet, doing calculations and sending emails.
LangChain's architecture is built from 4 main layers: Models — uniform wrappers for different LLM models; Prompts — management and templates for prompts; Chains — logical chains that connect models and actions; andAgents — autonomous systems that decide which tool to run and when. Version v0.3 brought LCEL (LangChain Expression Language) — a clean, asynchronous pipe syntax that made building Chains much more intuitive.
Installation & environment
It's recommended to start with a Virtual Environment. LangChain is split into separate packages so you install only what your project needs.
Installing the basic packages
# create a virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# install LangChain + model providers + useful tools
pip install langchain langchain-openai langchain-anthropic langchain-community python-dotenv
# for working with Vector Stores and RAG
pip install langchain-chroma chromadb pypdf
# for working with Pinecone
pip install langchain-pinecone pinecone-client
.env file — managing API keys
Create a .env file in the project directory and don't commit it to Git:
# .env
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__...
LANGCHAIN_PROJECT=my-project
# loading the keys in Python code
from dotenv import load_dotenv
load_dotenv()
# now you can use all the tools
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
Add .env to .gitignore before the first commit. Usingpython-dotenv is the right way to manage keys in development. In production — use environment variables directly or a tool like AWS Secrets Manager.
Chains — logical chains
A Chain is a sequence of operations connected together. The input of each step is the output of the previous step. LangChain v0.3 presents two main modes: the old API of LLMChain and the new LCEL.
LLMChain — the classic way
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
# defining the components
llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert at summarizing texts. Summarize concisely in 3 points."),
("human", "{text}")
])
output_parser = StrOutputParser()
# a chain with LCEL — pipe syntax
chain = prompt | llm | output_parser
# run
result = chain.invoke({"text": "your text here..."})
print(result)
LCEL — LangChain Expression Language
LCEL is the pipe syntax (|) that lets you chain components in a readable, asynchronous way. Any component that implements the Runnable interface can be linked in a chain.
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.schema import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# a document-summarization chain
summarize_prompt = PromptTemplate.from_template(
"Summarize the following document in 3 to 5 sentences:\n\n{document}"
)
translate_prompt = PromptTemplate.from_template(
"Translate the following summary to Spanish:\n\n{summary}"
)
llm = ChatOpenAI(model="gpt-4o")
parser = StrOutputParser()
# a two-step chain: summarize → translate
chain = (
{"document": RunnablePassthrough()}
| summarize_prompt
| llm
| parser
| {"summary": RunnablePassthrough()}
| translate_prompt
| llm
| parser
)
result = chain.invoke("the text you want to summarize and translate goes here...")
print(result)
Sequential Chains — complex chains
For complex chains with several steps and multiple variables, you can build nested Chains:
from langchain_core.runnables import RunnableParallel
# parallel processing — two computations at once
parallel_chain = RunnableParallel(
summary=summarize_prompt | llm | parser,
keywords=keyword_prompt | llm | parser
)
# parallel run — faster
result = parallel_chain.invoke({"document": document_text})
print("Summary:", result["summary"])
print("Keywords:", result["keywords"])
Every Chain built with LCEL automatically supportsawait chain.ainvoke() and chain.astream() for streaming. This is essential for building responsive user interfaces that display text as it's generated.
RAG with LangChain — Retrieval Augmented Generation
RAG is a technique that lets a language model answer questions based on information from documents that weren't in its Training. LangChain provides all the necessary tools: loading documents, splitting, creating Embeddings and retrieval.
Step 1 — loading documents
from langchain_community.document_loaders import (
PyPDFLoader,
WebBaseLoader,
DirectoryLoader,
TextLoader
)
# loading a PDF
loader = PyPDFLoader("document.pdf")
pages = loader.load() # a list of Document objects
# loading a website
web_loader = WebBaseLoader("https://example.com/article")
docs = web_loader.load()
# loading all the PDF files in a folder
dir_loader = DirectoryLoader("./documents/", glob="**/*.pdf", loader_cls=PyPDFLoader)
all_docs = dir_loader.load()
print(f"Loaded {len(all_docs)} documents")
Step 2 — Text Splitting
from langchain.text_splitter import RecursiveCharacterTextSplitter
# important parameters:
# chunk_size — the size of each chunk in characters
# chunk_overlap — overlap between adjacent chunks (prevents loss of context)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
separators=["\n\n", "\n", ".", " ", ""]
)
chunks = splitter.split_documents(all_docs)
print(f"Created {len(chunks)} chunks")
Step 3 — Embeddings and Vector Store
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
# creating Embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# saving to Chroma (local, no server needed)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# reloading in future runs
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
Step 4 — a full RetrievalQA Chain
A full example: RAG over a PDF file with answers:
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# --- loading and processing ---
loader = PyPDFLoader("hebrew_document.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=150)
chunks = splitter.split_documents(docs)
# --- Vector Store ---
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# --- the Prompt ---
hebrew_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""Answer the following question based only on the provided context.
If the answer isn't in the document, say "The information is not found in the document".
Context:
{context}
Question: {question}
Answer:"""
)
# --- Chain ---
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": hebrew_prompt},
return_source_documents=True
)
# --- query ---
result = qa_chain.invoke({"query": "What are the main conclusions of the document?"})
print("Answer:", result["result"])
print("Sources:", [doc.metadata for doc in result["source_documents"]])
For long-form texts, chunk_size=800-1200 works well. Chunks that are too short lose context; too long slow down Retrieval and raise costs. Anchunk_overlap of 15-20% of the chunk size prevents information loss at the boundaries.
Agents & Tools
An Agent is a system that uses an LLM as a reasoning engine to decide on a sequence of actions. Unlike a Chain, whose sequence of actions is known in advance, an Agent dynamically chooses which Tools to run and when, based on the intermediate results.
A basic Agent with built-in Tools
from langchain_openai import ChatOpenAI
from langchain.agents import initialize_agent, AgentType
from langchain_community.tools import WikipediaQueryRun, DuckDuckGoSearchRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.tools import Tool
import math
# built-in Tools
wikipedia_tool = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
search_tool = DuckDuckGoSearchRun()
# a custom Tool with a regular function
def calculator(expression: str) -> str:
"""Evaluates a math expression. Input: a string like '2 + 2' or 'sqrt(16)'"""
try:
result = eval(expression, {"__builtins__": {}}, {"sqrt": math.sqrt, "pi": math.pi})
return str(result)
except Exception as e:
return f"Error: {e}"
calc_tool = Tool(
name="Calculator",
func=calculator,
description="Evaluates math expressions. Useful for numeric calculations."
)
# defining the Agent
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = initialize_agent(
tools=[wikipedia_tool, search_tool, calc_tool],
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True, # show the reasoning process
max_iterations=5
)
# run
result = agent.invoke({
"input": "What is the population of Israel? Compute the square of the number."
})
print(result["output"])
A Custom Tool with the @tool Decorator
from langchain.tools import tool
from typing import Optional
@tool
def search_company_data(company_name: str) -> str:
"""
Searches for company information from the internal database.
קלט: שם החברה בעברית או אנגלית.
Output: the company data as JSON.
"""
# your real logic goes here
companies_db = {
"Apple": {"founder": "Steve Jobs", "founded": 1976, "value": "3T$"},
"Google": {"founder": "Larry Page", "founded": 1998, "value": "2T$"}
}
return str(companies_db.get(company_name, "Company not found in the database"))
@tool
def send_report_email(recipient: str, subject: str, body: str) -> str:
"""Sends a report by email. Input: address, subject and content."""
# the email-sending logic goes here
print(f"Sending email to {recipient}")
return f"The email was sent successfully to {recipient}"
# using a custom Tool in an Agent
agent_with_custom_tools = initialize_agent(
tools=[search_company_data, send_report_email],
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True
)
Modern Agent — LangGraph (recommended for production)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import create_react_agent
llm = ChatOpenAI(model="gpt-4o")
tools = [wikipedia_tool, search_tool, calc_tool]
# create_react_agent from LangGraph — the modern way
agent = create_react_agent(llm, tools)
# run
result = agent.invoke({
"messages": [HumanMessage(content="What is the weather in Tel Aviv today?")]
})
print(result["messages"][-1].content)
For production applications with complex Agents, LangGraph (part of the LangChain family) offers better control over State Management, loops, and Human-in-the-loop. Installation: pip install langgraph.
LangSmith — Monitoring and Debugging
LangSmith is LangChain's official Observability platform. It lets you track every LLM call, debug, measure Latency and costs, and manage experiments. Essential for any application going to production.
Setting up LangSmith Tracing
# .env — add these variables
LANGCHAIN_TRACING_V2=true
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_API_KEY=ls__your_key_here
LANGCHAIN_PROJECT=my-production-app
import os
from dotenv import load_dotenv
load_dotenv()
# no additional code change needed — Tracing works automatically!
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_template("Answer: {question}")
chain = prompt | llm | StrOutputParser()
# every run is automatically sent to the LangSmith Dashboard
result = chain.invoke({"question": "What is LangChain?"})
print(result)
What does LangSmith show?
- Trace View: A visual timeline of every step in the Chain — Prompt, LLM Call, Output Parser
- Token Usage: How many Tokens were consumed and at what cost — per call and cumulatively
- Latency: Response time per step — helps identify bottlenecks
- Error Tracking: Error capture with full context without needing extra logs
- Playground: Replaying a specific run and editing the Prompt directly in the dashboard
- Datasets: Building a Test Case set to compare versions
LangSmith Evaluation — quality testing
from langsmith.evaluation import evaluate, LangChainStringEvaluator
# defining an Evaluator to compare answers
evaluator = LangChainStringEvaluator("qa", config={"llm": ChatOpenAI(model="gpt-4o")})
# running Evaluation on a Dataset you created in LangSmith
results = evaluate(
lambda inputs: chain.invoke(inputs),
data="my-test-dataset",
evaluators=[evaluator],
experiment_prefix="v1.2-test"
)
print(results.to_pandas())
5 advanced tips
Tip 1 — Streaming for a better user experience
Instead of waiting for the model to finish the full answer, Streaming lets you display the text as it's generated. This dramatically improves the user experience in chat applications.
import asyncio
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import StrOutputParser
llm = ChatOpenAI(model="gpt-4o", streaming=True)
chain = ChatPromptTemplate.from_template("Explain: {topic}") | llm | StrOutputParser()
# synchronous streaming
for chunk in chain.stream({"topic": "LangChain"}):
print(chunk, end="", flush=True)
# asynchronous streaming (for use with FastAPI / Async frameworks)
async def stream_response():
async for chunk in chain.astream({"topic": "LangChain"}):
print(chunk, end="", flush=True)
asyncio.run(stream_response())
Tip 2 — Caching to reduce costs
LangChain includes a Cache mechanism that prevents duplicate API calls. If the same question is asked again, the answer is returned from the cache at no cost.
from langchain.globals import set_llm_cache
from langchain.cache import InMemoryCache, SQLiteCache
# in-memory Cache — cleared when the program shuts down
set_llm_cache(InMemoryCache())
# a SQLite-file Cache — persisted between runs
set_llm_cache(SQLiteCache(database_path=".langchain.db"))
# now every Chain will use the Cache automatically
llm = ChatOpenAI(model="gpt-4o")
# the first call → a call to the API
result1 = llm.invoke("What is the capital of Israel?")
# the second call → returned from the Cache, at no cost!
result2 = llm.invoke("What is the capital of Israel?")
Tip 3 — Fallback to alternative models
In production, it's important to configure a Fallback — if the primary model is unavailable, the system automatically switches to an alternative model.
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# primary model: GPT-4o
primary_llm = ChatOpenAI(model="gpt-4o")
# backup model: Claude
fallback_llm = ChatAnthropic(model="claude-3-5-sonnet-20241022")
# an automatic Fallback chain
llm_with_fallback = primary_llm.with_fallbacks([fallback_llm])
# if GPT-4o returns an error, Claude runs automatically
result = llm_with_fallback.invoke("an important question")
Tip 4 — Structured Output with Pydantic
Instead of parsing free text, you can define a Pydantic Schema and LangChain will ensure the model returns valid JSON exactly matching the structure.
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
from typing import List
class ProductReview(BaseModel):
rating: int = Field(description="a rating from 1 to 5", ge=1, le=5)
pros: List[str] = Field(description="the product's advantages")
cons: List[str] = Field(description="the product's disadvantages")
summary: str = Field(description="a short summary")
llm = ChatOpenAI(model="gpt-4o")
structured_llm = llm.with_structured_output(ProductReview)
review = structured_llm.invoke(
"Analyze the review: the product is good but too expensive and slow to start up"
)
print(f"Rating: {review.rating}/5")
print(f"Pros: {review.pros}")
print(f"Cons: {review.cons}")
Tip 5 — Conversational Memory for chatbots
Building a chatbot that remembers the conversation history — including managing the Context window to save Tokens.
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "אתה עוזר AI ידידותי. ענה תמיד בעברית."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm
# managing memory by session_id
store = {}
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
chatbot = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# a conversation with memory
config = {"configurable": {"session_id": "user_123"}}
r1 = chatbot.invoke({"input": "My name is David"}, config=config)
r2 = chatbot.invoke({"input": "What is my name?"}, config=config)
# r2 will answer "Your name is David" — it remembers!
Models — ChatOpenAI, ChatAnthropic, Gemini
LangChain wraps all LLM providers in a uniform interface. You can switch between models by changing a single line — a huge advantage for testing and flexibility.
Comparing providers
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.llms import Ollama # a local model
# Anthropic Claude
claude = ChatAnthropic(
model="claude-3-5-sonnet-20241022",
temperature=0.3,
max_tokens=4096
)
# OpenAI GPT-4o
gpt4o = ChatOpenAI(
model="gpt-4o",
temperature=0.3
)
# Google Gemini
gemini = ChatGoogleGenerativeAI(
model="gemini-2.0-flash",
temperature=0.3
)
# Ollama — a local model (Llama 3.1, Mistral, etc.)
local_llm = Ollama(model="llama3.1")
# the same interface for all of them!
for llm, name in [(claude, "Claude"), (gpt4o, "GPT-4o"), (gemini, "Gemini")]:
response = llm.invoke("What is the capital of Israel? Answer in one sentence.")
print(f"{name}: {response.content}")
Advanced RAG — Re-ranking and Hybrid Search
Basic RAG (Vector Search + Context Injection) works well but isn't perfect. Advanced RAG combines several techniques to dramatically improve accuracy.
Multi-Query Retrieval
Instead of sending a single question to the Vector Store, Multi-Query generates 3-5 different phrasings of the question and searches with all of them. Significantly increases Recall.
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# an existing Vector Store
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(embedding_function=embeddings, persist_directory="./db")
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Multi-Query Retriever — automatically generates 5 different phrasings
multi_retriever = MultiQueryRetriever.from_llm(
retriever=base_retriever,
llm=llm,
prompt=PromptTemplate(
template="""Generate 5 different versions of this question in Hebrew and English:
Question: {question}
Output (5 lines, one per question):""",
input_variables=["question"]
)
)
# usage is identical to any other Retriever
docs = multi_retriever.get_relevant_documents("What is the company's policy on vacations?")
Hybrid Search — BM25 + Vector
Hybrid Search combines keyword-based search (BM25) with semantic search (Vector). Excellent when results depend on specific terms (names, numbers, brands).
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
# BM25 Retriever — keyword-based search
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 4
# Vector Retriever — semantic search
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# Hybrid — 50% keywords, 50% semantic
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
# a RAG Chain with Hybrid Search
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
rag_prompt = ChatPromptTemplate.from_template("""Answer based only on the following context.
If there is no answer in the context, say "Not enough information".
Context:
{context}
Question: {question}
""")
llm = ChatOpenAI(model="gpt-4o")
# Full RAG Chain with Hybrid Search
hybrid_rag = (
{"context": hybrid_retriever | (lambda docs: "\n\n".join(d.page_content for d in docs)),
"question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
answer = hybrid_rag.invoke("What is the company's policy on Remote employees?")
5 practical projects
From simple Q&A to a full Customer Support system — here are five projects you can build with LangChain.
Q&A over a single document (PDF / TXT). Basic RAG with FAISS.
Extracts structured information (dates, parties, terms) from legal contracts. Returns JSON.
RAG over dozens of documents, Notion, Confluence and Google Drive simultaneously.
An Agent that answers customer questions, opens Tickets in Zendesk, and escalates to a human when needed.
A full Graph: Plan → Research → Critique → Refine → Publish. Human approval before publishing.
Cheat sheet — Component Reference
Document Loaders — data sources
| Source | Loader | Installation |
|---|---|---|
| PyPDFLoader | pip install pypdf | |
| Word | Docx2txtLoader | pip install docx2txt |
| Web Page | WebBaseLoader | pip install beautifulsoup4 |
| CSV | CSVLoader | built into langchain |
| Notion | NotionDBLoader | pip install notion-client |
| YouTube | YoutubeLoader | pip install youtube-transcript-api |
Vector Stores — comparison
| Vector Store | Best for | Price |
|---|---|---|
| FAISS | Prototyping, small files, no Server | Free (local) |
| Chroma | Medium projects, simple to deploy | Free (self-hosted) |
| Pinecone | Production, millions of documents, Scale | Serverless free up to 2GB |
| Weaviate | Built-in Hybrid Search, Multi-tenancy | Cloud / Self-hosted |
Common errors and solutions
from tenacity import retry, wait_exponential and wrap the LLM calls.chunk_size to 500 and k to 3 in the Retriever.k.Summary — LangChain vs LlamaIndex vs Direct API
| Criterion | LangChain | LlamaIndex | Direct API |
|---|---|---|---|
| Complexity | Medium | Medium | Low 🏆 |
| RAG | Excellent 🏆 | Excellent 🏆 | Manual |
| Agents | Excellent 🏆 | Good | Manual |
| Community | Huge 🏆 | Large | Small |
| Performance | Good | Good | Fast 🏆 |
| Model support | The broadest 🏆 | Broad | Specific |
| Monitoring | LangSmith 🏆 | LlamaTrace | Manual |
Conclusion: LangChain is the right choice for most projects — especially when you need complex Agents, connection to a variety of models, and Monitoring in production. LlamaIndex is better when the Use Case is RAG only over complex documents. Direct API suits small projects that use a single model and want full control.
Useful links
- Official documentation: python.langchain.com/docs/get_started
- LangSmith: smith.langchain.com — monitoring and Evaluation
- LangGraph: langchain-ai.github.io/langgraph — complex Agents
- GitHub: github.com/langchain-ai/langchain — source code and Issues
- LangChain Hub: smith.langchain.com/hub — a library of ready-made Prompts
- Discord: discord.gg/langchain — an active community for questions
The next step
After you understand LangChain — the natural deep dive is to learn RAG in depth: how to build advanced Retrieval systems with Re-ranking, Hybrid Search and Multi-vector Retrieval. The next step is also LangGraph for building complex Agents with State Management.