Skip to content

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

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

chain_with_retry = chain.with_retry(stop_after_attempt=3)

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 ChatPromptTemplate with from_messages — cleaner than string templates.
  • Always use with_structured_output() instead of asking for JSON in the prompt.
  • Maintain conversation history as a plain messages list — the old Memory classes are deprecated.
  • Use LCEL (| pipe) for composition — it's the standard.
  • invoke for single calls, batch for parallel, stream for real-time.
  • Use create_react_agent from langgraph.prebuilt for 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()

Official Documentation

Last Updated

2026-09-06 — Initial version.