LangChain Tutorial 2025: Build AI Applications with Chains and Agents
LangChain tutorial 2025 — learn chains, agents, memory, and RAG with practical Python examples for building production AI applications from scratch.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
LangChain Tutorial 2025: Build AI Applications with Chains and Agents
When I first encountered LangChain, I was skeptical. Why abstract over an API that's already simple? After building a few real applications, the answer became clear: the complexity isn't the API call — it's everything around it. Managing conversation history, connecting to vector databases, chaining multiple LLM calls, building agents with tools.
LangChain handles this scaffolding. By 2025, it's the most widely used framework for AI application development, with a much cleaner architecture than earlier versions thanks to LangChain Expression Language (LCEL).
This tutorial covers the components you'll actually use in production, with working code for each.
Installation and Setup
# Core LangChain packages (modular in v0.2+)
pip install langchain langchain-openai langchain-anthropic langchain-community
pip install langchain-chroma # Vector store
pip install langgraph # For agents
pip install langsmith # Observability (optional)
import os
os.environ["OPENAI_API_KEY"] = "your-key"
# Or use python-dotenv: from dotenv import load_dotenv; load_dotenv()
Core Concept: LangChain Expression Language (LCEL)
LCEL composes chains with the pipe operator:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Components
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
output_parser = StrOutputParser()
# Simple chain: prompt | llm | parser
prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert in {domain}."),
("human", "{question}")
])
chain = prompt | llm | output_parser
# Invoke
result = chain.invoke({
"domain": "Python",
"question": "What are the best practices for error handling?"
})
print(result)
# Stream
for chunk in chain.stream({"domain": "machine learning", "question": "Explain overfitting"}):
print(chunk, end="", flush=True)
# Batch (parallel execution)
results = chain.batch([
{"domain": "Python", "question": "What is a generator?"},
{"domain": "SQL", "question": "When should I use indexes?"},
{"domain": "Docker", "question": "What is a multi-stage build?"},
])
Prompts and Templates
from langchain_core.prompts import (
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
MessagesPlaceholder
)
# Few-shot prompting
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
]
example_prompt = ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}"),
])
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=examples,
)
final_prompt = ChatPromptTemplate.from_messages([
("system", "You are an antonyms generator."),
few_shot_prompt,
("human", "{input}"),
])
chain = final_prompt | llm | output_parser
print(chain.invoke({"input": "big"})) # "small"
# Dynamic prompts with message history
with_history_prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"), # Inject message history
("human", "{input}"),
])
Conversation Memory with Message History
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# Session-based chat history
store = {}
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])
chain = prompt | llm | output_parser
# Wrap with message history
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
# Invoke with session tracking
config = {"configurable": {"session_id": "user-123"}}
print(chain_with_history.invoke({"input": "Hi, my name is Alice."}, config=config))
print(chain_with_history.invoke({"input": "What's my name?"}, config=config))
# Output: "Your name is Alice, as you told me!"
RAG Chain
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.runnables import RunnablePassthrough
# 1. Load and index documents
loader = WebBaseLoader("https://example.com/docs")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = splitter.split_documents(docs)
vectorstore = Chroma.from_documents(splits, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 2. RAG prompt
rag_prompt = ChatPromptTemplate.from_messages([
("system", """Answer the question using only the provided context.
If the answer isn't in the context, say "I don't have that information."
Context: {context}"""),
("human", "{question}")
])
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 3. RAG chain with LCEL
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| llm
| output_parser
)
answer = rag_chain.invoke("What are the main features?")
print(answer)
# With sources
from langchain_core.runnables import RunnableParallel
rag_chain_with_sources = RunnableParallel(
{"context": retriever | format_docs, "question": RunnablePassthrough()}
).assign(answer=rag_prompt | llm | output_parser)
result = rag_chain_with_sources.invoke("What are the main features?")
print(f"Answer: {result['answer']}")
Tool Use and Agents
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# Define tools
@tool
def search_web(query: str) -> str:
"""Search the web for current information."""
# In practice: integrate with SerpAPI, Tavily, etc.
return f"Search results for: {query}"
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression."""
try:
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"Error: {e}"
@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
# In practice: call a weather API
return f"Weather in {city}: 72°F, partly cloudy"
tools = [search_web, calculate, get_weather]
# Create agent with LangGraph (modern approach)
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(model, tools)
# Run the agent
response = agent.invoke({
"messages": [("human", "What is the square root of 144, and what's the weather like in Paris?")]
})
for message in response["messages"]:
print(f"{message.type}: {message.content}")
Structured Output
from pydantic import BaseModel, Field
from typing import List
class MovieReview(BaseModel):
title: str = Field(description="Movie title")
rating: float = Field(description="Rating from 1-10")
pros: List[str] = Field(description="Positive aspects")
cons: List[str] = Field(description="Negative aspects")
summary: str = Field(description="One-sentence summary")
# Structured output with Pydantic
structured_llm = llm.with_structured_output(MovieReview)
review = structured_llm.invoke(
"Review the movie 'Inception' by Christopher Nolan"
)
print(f"Title: {review.title}")
print(f"Rating: {review.rating}/10")
print(f"Pros: {review.pros}")
print(f"Summary: {review.summary}")
LangSmith Observability
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "your-langsmith-key"
os.environ["LANGCHAIN_PROJECT"] = "my-chatbot"
# All LangChain calls are now traced to LangSmith dashboard
# View: latency, token usage, inputs/outputs, errors
# No code changes needed beyond setting environment variables
chain = prompt | llm | output_parser
result = chain.invoke({"question": "What is machine learning?"})
# This call appears in your LangSmith project dashboard
Conclusion
LangChain's LCEL architecture makes AI application composition genuinely elegant — the pipe operator creates readable, composable pipelines. For production systems, the most valuable parts are the retriever integrations, memory management, and LangSmith observability.
Start simple: use LCEL chains for most tasks, add agents only when you genuinely need dynamic tool selection. Most AI applications don't need agents — they need well-designed chains.
For the RAG component that makes chatbots know your data, see our RAG system tutorial. For deploying LangChain applications, see our production deployment guide.
Frequently Asked Questions
What is LangChain and what is it used for?
LangChain is a Python framework for building LLM applications. It provides abstractions for chaining LLM calls, conversation memory, connecting to vector databases, and building AI agents. Key use cases: RAG systems, conversational AI, multi-step workflows, AI agents with tools.
What is LCEL?
LangChain Expression Language — compose chains with the pipe operator (|). prompt | llm | output_parser creates a chain where each step's output becomes the next step's input. Built-in streaming, batch execution, and parallel processing. The modern recommended approach for building LangChain applications.
How do LangChain agents work?
Agents use an LLM to decide which tools to call and when. The LLM sees available tools, the user's request, and decides what to do. The framework executes tool calls, returns results to the LLM, and repeats until the LLM produces a final answer. Modern LangChain uses LangGraph for complex agent workflows.
What is the difference between LangChain and LlamaIndex?
LangChain is broader: covers agents, chains, tools, and RAG. LlamaIndex focuses on data ingestion and complex retrieval. Many production systems use both: LlamaIndex for retrieval, LangChain for the application layer.
Is LangChain good for production?
Yes, with caveats: pin to specific versions (frequent breaking changes), don't use abstractions where plain SDK calls are simpler, use LangSmith for observability. The retrieval integrations and agent framework genuinely save production engineering time.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
AI API Cost Management: How to Cut LLM Costs by 80% Without Losing Quality
AI API cost management — practical strategies to reduce OpenAI, Claude, and Gemini API costs by 80% using model selection, caching, RAG, prompt optimization, and batch processing.
Build an AI Chatbot with Python: Complete Guide from Scratch to Deployment
Build an AI chatbot with Python — complete tutorial from OpenAI API integration to conversation memory, streaming responses, and deploying a production-ready chatbot application.
Build a Personal AI Assistant: Complete Python Project with Memory and Tools
Build a personal AI assistant in Python with persistent memory, web search, file access, and calendar integration — a complete project from architecture to working prototype.
CrewAI Tutorial: Build Multi-Agent AI Systems That Work Together
CrewAI tutorial — build multi-agent AI systems where specialized agents collaborate to complete complex tasks, with practical Python examples for research, coding, and content workflows.