LangChain¶
LangChain is the composable framework for building LLM applications. It provides abstractions for models, prompts, output parsers, chains, tools, and agents — letting you mix and match components like LEGO blocks. LangChain handles the "single-step or few-step" logic; LangGraph (separate package) handles complex multi-step workflows with state.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
pip install langchain langchain-openai langchain-anthropic langchain-community
# or: uv add langchain langchain-openai
Key packages:
| Package | Purpose |
|---|---|
langchain |
Core abstractions: models, prompts, chains, parsers |
langchain-openai |
OpenAI / Azure OpenAI models + embeddings |
langchain-anthropic |
Anthropic Claude models |
langchain-community |
Community integrations (tools, loaders, etc.) |
langchain-core |
Low-level primitives (usually auto-installed) |
Core Syntax¶
Models¶
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
# OpenAI
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
# Anthropic
llm = ChatAnthropic(model="claude-sonnet-4-20250514", temperature=0.7)
# invoke (single call)
response = llm.invoke("What is machine learning?")
print(response.content)
| Method | Purpose |
|---|---|
llm.invoke(input) |
Single call, returns AIMessage |
llm.batch([input1, input2]) |
Parallel calls |
llm.stream(input) |
Generator of chunks |
Messages¶
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
messages = [
SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="What is RAG?"),
]
response = llm.invoke(messages)
print(response.content)
# multi-turn
messages.append(AIMessage(content=response.content))
messages.append(HumanMessage(content="How does it differ from fine-tuning?"))
response = llm.invoke(messages)
| Class | Role |
|---|---|
SystemMessage |
Sets behavior / instructions |
HumanMessage |
User input |
AIMessage |
Previous model output |
ToolMessage |
Result from a tool call |
Prompts and templates¶
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
# ChatPromptTemplate (recommended for chat models)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a {role}. Respond in {style} style."),
("human", "{question}"),
])
# format produces a list of messages
messages = prompt.format(role="data scientist", style="concise", question="What is overfitting?")
response = llm.invoke(messages)
Output parsers¶
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
# simple string parser
parser = StrOutputParser()
response = llm.invoke("Say hello")
text = parser.invoke(response) # just the string
# JSON parser (structured output)
class Sentiment(BaseModel):
label: str = Field(description="positive, negative, or neutral")
confidence: float = Field(description="0-1 confidence")
parser = JsonOutputParser(pydantic_object=Sentiment)
prompt = ChatPromptTemplate.from_messages([
("system", "Classify sentiment. {format_instructions}"),
("human", "{text}"),
])
chain = prompt | llm | parser
result = chain.invoke({
"text": "I love this product!",
"format_instructions": parser.get_format_instructions(),
})
print(result) # {"label": "positive", "confidence": 0.95}
Chains (LCEL — LangChain Expression Language)¶
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# pipe operator builds a chain
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("human", "{question}"),
])
chain = prompt | llm | StrOutputParser()
# invoke
result = chain.invoke({"question": "What is Python?"})
print(result)
The | pipe is the core composition mechanism: prompt → model → parser.
Tools¶
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
return f"The weather in {city} is 72°F and sunny."
# check tool metadata
print(get_weather.name) # "get_weather"
print(get_weather.description) # "Get the current weather..."
Tool calling¶
# bind tools to a model
llm_with_tools = llm.bind_tools([get_weather])
response = llm_with_tools.invoke("What's the weather in London?")
print(response.tool_calls)
# [{'name': 'get_weather', 'args': {'city': 'London'}, 'id': '...'}]
# execute tools manually
from langchain_core.messages import ToolMessage
tool_results = []
for tc in response.tool_calls:
result = get_weather.invoke(tc["args"])
tool_results.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
# continue the conversation with tool results
messages = [HumanMessage(content="What's the weather in London?"), response] + tool_results
final = llm.invoke(messages)
print(final.content)
Agents (prebuilt)¶
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
llm = ChatOpenAI(model="gpt-4o")
agent = create_react_agent(llm, [search])
result = agent.invoke({"messages": [("human", "What's the weather in Paris?")]})
print(result["messages"][-1].content)
Common Operations¶
Streaming¶
# stream from a chain
for chunk in chain.stream({"question": "Tell me a joke"}):
print(chunk, end="", flush=True)
Batch processing¶
questions = ["What is Python?", "What is Rust?", "What is Go?"]
results = chain.batch([{"question": q} for q in questions])
Structured output directly on a model¶
from langchain_openai import ChatOpenAI
from langchain_core.pydantic_v1 import BaseModel, Field
class Recipe(BaseModel):
name: str
ingredients: list[str]
steps: list[str]
llm = ChatOpenAI(model="gpt-4o")
structured_llm = llm.with_structured_output(Recipe)
recipe = structured_llm.invoke("Give me a recipe for pasta carbonara")
print(recipe.name, recipe.ingredients)
Memory (conversation history)¶
from langchain_core.messages import SystemMessage
# manual memory — just maintain a messages list
messages = [SystemMessage(content="You are a helpful assistant.")]
def chat(user_input):
messages.append(HumanMessage(content=user_input))
response = llm.invoke(messages)
messages.append(AIMessage(content=response.content))
return response.content
LangChain removed the old ConversationBufferMemory classes — maintain the messages list yourself. It's simpler and more explicit.
Practical Examples¶
Example 1 — RAG chain¶
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_community.vectorstores import FAISS
# vector store (from documents)
vectorstore = FAISS.from_texts(documents, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# prompt
prompt = ChatPromptTemplate.from_messages([
("system", "Answer based on context:\n{context}"),
("human", "{question}"),
])
# chain
def format_docs(docs):
return "\n\n".join(d.page_content for d in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("What is RAG?")
Example 2 — Tool-calling agent¶
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def calculator(expression: str) -> str:
"""Evaluate a math expression. Example: '2 + 2'"""
return str(eval(expression))
@tool
def lookup_stock(ticker: str) -> str:
"""Look up a stock price."""
return f"{ticker}: $150.00 (simulated)"
llm = ChatOpenAI(model="gpt-4o")
agent = create_react_agent(llm, [calculator, lookup_stock])
result = agent.invoke({
"messages": [("human", "What's 15% of the AAPL stock price?")]
})
print(result["messages"][-1].content)
Example 3 — Parallel chain¶
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
llm = ChatOpenAI(model="gpt-4o-mini")
summarize = ChatPromptTemplate.from_template("Summarize in 2 sentences:\n{text}") | llm | StrOutputParser()
translate = ChatPromptTemplate.from_template("Translate to French:\n{text}") | llm | StrOutputParser()
chain = RunnableParallel(summary=summarize, french=translate)
result = chain.invoke({"text": "LangChain is a framework for building LLM applications."})
print(result["summary"])
print(result["french"])
Common Patterns¶
Pattern: routing by input type¶
from langchain_core.runnables import RunnableLambda
def route(input_dict):
if "code" in input_dict:
return code_chain
return general_chain
router = RunnableLambda(route)
result = router.invoke({"code": "def foo(): pass"})
Pattern: retry on failure¶
Pattern: LCEL chain composition¶
# everything is a Runnable that can be piped
chain = (
prompt # RunnableTemplate
| llm # RunnableChatModel
| parser # RunnableParser
| RunnableLambda(process_output) # custom function
)
Best Practices¶
- Use
ChatPromptTemplatewithfrom_messages— cleaner than string templates. - Always use
with_structured_output()instead of asking for JSON in the prompt. - Maintain conversation history as a plain
messageslist — the old Memory classes are deprecated. - Use LCEL (
|pipe) for composition — it's the standard. invokefor single calls,batchfor parallel,streamfor real-time.- Use
create_react_agentfromlanggraph.prebuiltfor agents — it's the modern way. - Bind tools with
llm.bind_tools()— the model decides when to call them. - Keep chains small and composable — one responsibility per chain.
Common Mistakes¶
| Mistake | Fix |
|---|---|
Using old LLMChain / ConversationChain |
Use LCEL: prompt \| llm \| parser |
Using ConversationBufferMemory |
Maintain a messages list manually |
| Asking for JSON in the prompt | Use with_structured_output() or JsonOutputParser |
| Not streaming for user-facing apps | Use chain.stream() for real-time output |
| Hard-coding model names | Pass model as a parameter or env var |
| Overcomplicating chains | Start simple, add complexity only when needed |
Troubleshooting¶
| Symptom | Cause / Fix |
|---|---|
ImportError: No module named 'langchain' |
pip install langchain langchain-openai |
AuthenticationError |
Missing API key — set OPENAI_API_KEY env var |
Chain returns raw AIMessage |
Add StrOutputParser() at the end |
| Tool calls not executing | Use create_react_agent or handle tool_calls manually |
with_structured_output fails |
Ensure pydantic model has proper Field descriptions |
| Slow batch processing | Use batch() instead of looping with invoke() |
Related Cheat Sheets¶
- langgraph — complex workflows and stateful agents
- llamaindex — data framework for RAG
- generative-ai.md — AI landscape overview
- openai-api — direct OpenAI API usage
- anthropic-api — direct Anthropic API
- RAG — retrieval concepts
- FastAPI — serving LLM apps
- litellm — multi-provider API
Official Documentation¶
- LangChain docs — https://python.langchain.com/
- LCEL guide — https://python.langchain.com/docs/how_to/#langchain-expression-language-lcel
- LangChain tutorials — https://python.langchain.com/docs/tutorials/
Last Updated¶
2026-09-06 — Initial version.