๐ŸŸก Intermediate

Getting Started with LangChain:
Chains, Tools, Memory & LCEL

โ›“๏ธ LangChainโฑ 17 min read๐Ÿ—“ May 2026

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