LangChain Complete Guide — Build LLM Apps with Python in 2025
Advertisement
Introduction
Why This Matters
LangChain is the most widely adopted framework for building LLM-powered applications in Python. It abstracts away the complexity of managing prompt templates, conversation memory, tool use, and multi-step reasoning — letting you focus on product logic rather than plumbing. As of 2025, LangChain has over 90,000 GitHub stars and powers production applications at thousands of companies.
The framework introduced LCEL (LangChain Expression Language) as its primary API — a declarative, composable approach to building pipelines that is now the standard way to write LangChain code. Whether you are building a customer support bot, a coding assistant, or an autonomous research agent, LangChain provides the building blocks.
Understanding LangChain deeply means you can swap models, debug chains, integrate vector stores, and build agents that use real-world tools — all without rewriting your application from scratch.
Installation and Setup
pip install langchain langchain-openai langchain-community langchain-core python-dotenvConfigure API keys via environment variables:
import os
from dotenv import load_dotenv
load_dotenv() # loads from .env file
# OPENAI_API_KEY=sk-... in your .envCore Concepts
LangChain is built around five abstractions:
- Models: Wrappers around LLM providers (OpenAI, Anthropic, Cohere, local models via Ollama)
- Prompts: Reusable templates that format inputs before sending to the model
- Chains: Sequences of operations composed using the pipe (
|) operator in LCEL - Memory: Mechanisms to persist conversation context across turns
- Agents: LLMs that dynamically decide which tools to call to complete a task
Building Your First Chain with LCEL
LCEL (LangChain Expression Language) is the modern way to compose chains using the | operator:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
prompt = ChatPromptTemplate.from_template(
"Explain {topic} in simple terms for a {audience}."
)
chain = prompt | llm | StrOutputParser()
result = chain.invoke({
"topic": "transformer attention mechanisms",
"audience": "junior developer"
})
print(result)Every component in LCEL is a Runnable. Chains are composable, streamable, and support async out of the box.
Working with Memory
Production chatbots need to remember previous turns. LangChain supports several memory patterns:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful AI assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])
chain = prompt | llm | StrOutputParser()
# Manage history manually for full control
history = []
def chat(user_input: str) -> str:
response = chain.invoke({
"input": user_input,
"history": history
})
history.append(HumanMessage(content=user_input))
history.append(AIMessage(content=response))
return response
print(chat("What is RAG?"))
print(chat("Can you give me a code example?"))For long conversations, use ConversationSummaryMemory to compress history and stay within context limits.
Structured Output Parsing
Extract typed data from LLM responses using Pydantic:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from typing import List
class BlogOutline(BaseModel):
title: str = Field(description="SEO-optimized blog title")
sections: List[str] = Field(description="List of section headings")
target_keywords: List[str] = Field(description="Primary SEO keywords")
llm = ChatOpenAI(model="gpt-4o")
parser = JsonOutputParser(pydantic_object=BlogOutline)
prompt = ChatPromptTemplate.from_template(
"Create a blog outline about {topic}.\n\n{format_instructions}"
).partial(format_instructions=parser.get_format_instructions())
chain = prompt | llm | parser
result = chain.invoke({"topic": "LLM prompt engineering techniques"})
print(result["title"])
print(result["sections"])Alternatively, use .with_structured_output() for cleaner syntax with models that support function calling natively.
Building Agents with Tools
Agents let an LLM decide which tools to invoke to answer a query:
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
@tool
def search_docs(query: str) -> str:
"""Search internal documentation for information."""
# Replace with actual vector store retrieval
return f"Documentation result for: {query}"
@tool
def get_stock_price(ticker: str) -> str:
"""Get the current stock price for a ticker symbol."""
return f"${ticker}: $150.25 (mock)"
tools = [search_docs, get_stock_price]
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant with access to tools."),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = executor.invoke({"input": "What does our doc say about rate limits and what is AAPL price?"})
print(result["output"])RAG Pipeline with Document Loaders
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# Load and chunk documents
loader = PyPDFLoader("company_handbook.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(docs)
# Embed and store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# RAG chain
rag_prompt = ChatPromptTemplate.from_template(
"Answer based only on the context below.\n\nContext: {context}\n\nQuestion: {question}"
)
llm = ChatOpenAI(model="gpt-4o")
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("What is our PTO policy?")
print(answer)Streaming Responses
Streaming improves perceived latency for end users:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o", streaming=True)
prompt = ChatPromptTemplate.from_template("Write a paragraph about {topic}.")
chain = prompt | llm
for chunk in chain.stream({"topic": "the future of AI agents"}):
print(chunk.content, end="", flush=True)Common Mistakes / Pitfalls
- Using deprecated
LLMChaininstead of LCEL — migrate to the|operator pattern - Building unbounded memory buffers that overflow the context window on long conversations
- Not handling rate limit errors with retry logic — use
langchain.globals.set_llm_cacheand tenacity - Running synchronous chains in async FastAPI routes — use
chain.ainvoke()instead - Hardcoding model names — parameterize them so you can A/B test providers
Best Practices
- Use
RunnableParallelto fan out multiple LLM calls in a single step when they are independent - Add
callbacks=[LangSmithTracer()]to every chain in production for observability - Set
max_tokensexplicitly to prevent runaway generation costs - Use
.batch()for processing large datasets efficiently with automatic parallelism - Cache expensive embeddings with
langchain.cachebacked by Redis or SQLite - Version your prompt templates — small wording changes cause large output differences
Key Takeaways
- LangChain is the dominant Python framework for LLM application development with 90K+ GitHub stars as of 2025
- LCEL (LangChain Expression Language) uses the pipe
|operator to compose prompts, models, and parsers declaratively - Agents combine an LLM with tool definitions — the model decides which tools to call at runtime
- RAG pipelines in LangChain involve loaders, splitters, embedding models, vector stores, and retrieval chains
- Memory in LangChain must be managed explicitly — append messages to history and compress when context gets long
- Streaming with
.stream()reduces perceived latency and improves user experience significantly - LangChain supports 100+ integrations including OpenAI, Anthropic, Cohere, Hugging Face, Ollama, and all major vector stores
- Production LangChain apps benefit from LangSmith tracing for debugging prompt failures and measuring chain latency
Advertisement