LangChain is the most widely used framework for building LLM applications. It provides abstractions for the most common patterns: chaining LLM calls, connecting to tools and data sources, managing memory, and building agents. This guide covers everything you need to be productive with LangChain.
Setup
pip install langchain langchain-anthropic langchain-openai
pip install langchain-community langchain-core python-dotenv
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
# Initialize a model
llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
# Simple invocation
response = llm.invoke("What is the capital of France?")
print(response.content) # "The capital of France is Paris."
Core Concept 1: LangChain Expression Language (LCEL)
LCEL is LangChain's declarative way to compose chains using the pipe | operator. It enables streaming, batching, async, and parallel execution automatically.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
# Basic chain: prompt โ LLM โ parser
prompt = ChatPromptTemplate.from_template("Explain {topic} in one sentence.")
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"topic": "embeddings"})
print(result)
# Streaming
for chunk in chain.stream({"topic": "transformers"}):
print(chunk, end="", flush=True)
# Batch processing
results = chain.batch([
{"topic": "RAG"},
{"topic": "fine-tuning"},
{"topic": "prompt engineering"}
])
# Parallel chains (run simultaneously)
parallel_chain = RunnableParallel(
summary=prompt | llm | StrOutputParser(),
key_points=ChatPromptTemplate.from_template(
"List 3 key points about {topic}"
) | llm | StrOutputParser()
)
result = parallel_chain.invoke({"topic": "vector databases"})
Core Concept 2: Prompt Templates
from langchain_core.prompts import (
ChatPromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate
)
# Simple template
template = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Respond in {language}."),
("human", "{question}")
])
# Template with few-shot examples
from langchain_core.prompts import FewShotChatMessagePromptTemplate
examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
]
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 give antonyms."),
few_shot_prompt,
("human", "{word}")
])
# Template with chat history placeholder
chat_prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
Core Concept 3: Memory & Conversation History
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# Create a basic chain
chain = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
]) | llm | StrOutputParser()
# Wrap with message history
store = {} # In production, use Redis or a database
def get_session_history(session_id: str) -> ChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# Each call maintains context via session_id
config = {"configurable": {"session_id": "user-123"}}
response1 = chain_with_history.invoke({"input": "My name is Alice."}, config=config)
response2 = chain_with_history.invoke({"input": "What's my name?"}, config=config)
print(response2) # "Your name is Alice."
Core Concept 4: Tools & Agents
from langchain_core.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
@tool
def get_company_info(company_name: str) -> str:
"""Get information about a company including founding year and HQ location."""
# In real use, call an API
info = {
"Anthropic": "Founded 2021, HQ San Francisco, CA. AI safety company.",
"OpenAI": "Founded 2015, HQ San Francisco, CA. AI research lab.",
}
return info.get(company_name, f"No data found for {company_name}")
@tool
def calculate_age(founding_year: int) -> str:
"""Calculate how many years old a company is."""
from datetime import datetime
age = datetime.now().year - founding_year
return f"The company is {age} years old."
tools = [get_company_info, calculate_age]
# Create agent
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful business analyst. Use tools to answer questions."),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = agent_executor.invoke({"input": "When was Anthropic founded and how old is it now?"})
print(result["output"])
Core Concept 5: Output Parsers
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
from typing import List
# Structured output with Pydantic
class MovieReview(BaseModel):
title: str = Field(description="Movie title")
year: int = Field(description="Release year")
rating: float = Field(description="Rating from 0.0 to 10.0")
pros: List[str] = Field(description="List of positive aspects")
cons: List[str] = Field(description="List of negative aspects")
parser = JsonOutputParser(pydantic_object=MovieReview)
review_prompt = ChatPromptTemplate.from_messages([
("system", "You are a film critic. {format_instructions}"),
("human", "Review this movie: {movie}")
])
review_chain = review_prompt | llm | parser
result = review_chain.invoke({
"movie": "Inception (2010)",
"format_instructions": parser.get_format_instructions()
})
print(result) # Returns a MovieReview object
Advanced: Custom Runnables & Error Handling
from langchain_core.runnables import RunnableLambda
from langchain_core.runnables.retry import RunnableRetry
# Custom processing step
def uppercase_result(text: str) -> str:
return text.upper()
chain_with_custom = chain | RunnableLambda(uppercase_result)
# Add retry logic to any runnable
chain_with_retry = chain.with_retry(
stop_after_attempt=3,
wait_exponential_jitter=True
)
# Fallback chains
from langchain_core.runnables import RunnableWithFallbacks
expensive_chain = ChatAnthropic(model="claude-opus-4-6") | StrOutputParser()
cheap_chain = ChatAnthropic(model="claude-haiku-4-5-20251001") | StrOutputParser()
# Try expensive model first, fall back to cheap on error
chain_with_fallback = expensive_chain.with_fallbacks([cheap_chain])
LCEL mental model: Every component (LLM, prompt, parser, tool) is a
Runnable. The | operator chains them: output of left becomes input of right. This uniform interface means streaming, batching, async, and tracing work automatically across the entire chain.
Key Takeaways
- LCEL's
|operator chains runnables โ enables streaming/batch/async automatically - Use
ChatPromptTemplatewithMessagesPlaceholderfor dynamic multi-turn prompts RunnableWithMessageHistoryadds session-aware memory to any chain@tooldecorator turns any Python function into a tool the agent can use- Pydantic +
JsonOutputParserenforces structured, typed output with_retryandwith_fallbacksadd resilience to any runnable