LangChain Tutorial 2026: Build Your First Chain and Agent
If you've tried to learn LangChain by reading the docs top to bottom, you've probably closed the tab more confused than when you opened it. The library has grown fast, the terminology overlaps with "agents" everywhere else in the AI world, and half the tutorials online are already out of date. This one is different: we're going to build two real things — a simple chain and a tool-using agent — and explain every piece as we go, so you actually understand what's happening instead of copy-pasting code you can't debug.
By the end of this tutorial you'll know what a chain is, why LCEL composition looks the way it does, how a model "decides" to call a tool, and where memory actually fits in. No hand-waving, no magic. Let's get into it.
What LangChain Actually Is
LangChain is a framework for building applications powered by large language models. That's the one-sentence pitch, but it undersells what makes it useful. On its own, an LLM is a function: text goes in, text comes out. Everything interesting — connecting it to your data, letting it call APIs, giving it memory across a conversation, chaining multiple reasoning steps together — is glue code you'd otherwise have to write yourself.
LangChain standardizes that glue code. It gives you:
- A consistent interface for calling different model providers (OpenAI, Anthropic, local models via Ollama, and others) so switching providers doesn't mean rewriting your app
- Prompt templates for building reusable, parameterized prompts
- Output parsers for turning raw model text into structured data your code can use
- Chains for composing multiple steps (prompt, model, parser, retrieval, etc.) into a single pipeline
- Tools and agents for letting the model decide to call functions, search the web, query a database, or run code
The reason it clicked for so many teams is that these pieces compose. You write a chain once, and it becomes a building block for a bigger chain. You write a tool once, and any agent can use it. Once you see the pattern, the framework mostly gets out of your way.
That said — LangChain is not the only way to do this, and it's not always the right choice for production systems that need tight control over every model call. But as a way to *learn* the core concepts of building with LLMs, it's still one of the best on-ramps in 2026, which is exactly why we teach it early in our own courses.
Installing and Setting Up Your Environment
Let's get a working environment before touching any code. You'll need Python 3.10 or newer.
python -m venv venv
source venv/bin/activate
pip install langchain langchain-openai langchain-coreIf you're using Anthropic's models instead, swap in langchain-anthropic. The pattern is identical across providers — that's the whole point of LangChain's model abstraction.
You'll also need an API key from whichever provider you're using. Set it as an environment variable rather than hardcoding it into your script:
export OPENAI_API_KEY="sk-...your-key-here"One quick note on versions: LangChain went through a fairly disruptive restructuring a couple of years back, splitting into langchain-core, langchain-community, and provider-specific packages like langchain-openai. If you're following an older tutorial and imports don't match what's shown here, that's almost always why. Stick with the import paths in this article and you'll be on the current, stable structure.
Quick sanity check that everything's wired up correctly:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke("Say hello in one short sentence.")
print(response.content)If that prints a greeting, you're set up correctly. Everything from here builds on this.
The Core Abstractions You Need to Know
Before writing a chain, it helps to name the pieces you'll be assembling. There are really only four you need for beginner-level work.
Prompt templates. Instead of hardcoding a string every time you call the model, a prompt template lets you define a reusable structure with placeholders, then fill those placeholders in at call time. This matters because real applications don't have one fixed prompt — they have a template that gets filled with user input, retrieved context, or prior conversation turns.
Models. LangChain wraps chat models behind a common interface. Whether you're calling ChatOpenAI, ChatAnthropic, or a local model, the .invoke() method behaves the same way, and you get back a message object rather than a raw string. This uniformity is what lets you swap providers without rewriting your chain logic.
Output parsers. Models return text (or message objects wrapping text). An output parser converts that into whatever shape your application actually needs — a plain string, a JSON object, a list, a specific Python type. Without a parser, you're stuck manually string-processing model output, which is fragile and annoying.
Runnables and LCEL. This is the piece that trips people up first, so it's worth slowing down on.
Understanding LCEL: Why the Pipe Syntax Exists
LangChain Expression Language, or LCEL, is the composition syntax that lets you chain components together with the | (pipe) operator:
chain = prompt | llm | output_parserIf you've used Unix pipes, this will feel familiar — the output of one step becomes the input to the next. Under the hood, every component in that chain (the prompt template, the model, the parser) implements a shared Runnable interface. That interface guarantees each piece supports .invoke(), .batch(), and .stream(), which is why you can snap them together like this regardless of what they are internally.
Why does this matter instead of just calling three functions in sequence yourself? A few reasons:
- Streaming works automatically. If your model supports token streaming,
chain.stream()streams through the whole pipeline, not just the model call. - Batching works automatically.
chain.batch([...])runs multiple inputs efficiently, including running independent steps concurrently where possible. - Chains compose into bigger chains. A chain built with LCEL is itself a Runnable, so you can pipe it into something else, or use it as one branch of a larger pipeline.
You could absolutely write parser(llm(prompt(input))) and get the same single result. LCEL earns its keep once you need streaming, batching, retries, or composition — which in practice is almost immediately once you move past a toy script.
Building Your First Chain
Let's build something real: a chain that takes a topic and explains it like you're teaching a total beginner, in a fixed number of bullet points.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_messages([
("system", "You are a patient teacher who explains concepts simply."),
("human", "Explain {topic} in exactly {num_points} bullet points, "
"aimed at a complete beginner.")
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
result = chain.invoke({
"topic": "how neural networks learn",
"num_points": 4
})
print(result)Walk through what's happening step by step:
ChatPromptTemplate.from_messagesbuilds a template with a system message (setting the model's behavior) and a human message with two placeholders,{topic}and{num_points}.prompt | llm | output_parserwires the three pieces together into a single Runnable.chain.invoke({...})fills the placeholders, sends the resulting messages to the model, and passes the model's response throughStrOutputParser, which extracts the plain string content instead of returning a message object.
That's a complete, working chain. Notice it doesn't "decide" anything — it always runs prompt, then model, then parser, in that fixed order. That's the key distinction we'll come back to in a minute: a chain is a predetermined sequence of steps, not a system that makes decisions about what to do next.
You can make the output structured instead of plain text by swapping the parser. If you wanted JSON back, you'd use a JsonOutputParser or define a Pydantic model and use with_structured_output() on the model itself — but plain text is the right place to start.
Giving the Model a Tool to Call
A chain is fixed. An agent is a chain-like loop where the model itself decides, at each step, whether to call a tool, and which one. Before we can build an agent, we need something for it to call.
In LangChain, a tool is just a Python function with a description attached, decorated so the framework knows how to expose it to the model. Here's a small tool that does something a language model genuinely can't do reliably on its own: arithmetic.
from langchain_core.tools import tool
@tool
def calculate(expression: str) -> str:
"""Evaluate a basic arithmetic expression, e.g. '12 * (4 + 3)'."""
try:
# A tiny, safe-ish evaluator for basic math only
allowed_chars = set("0123456789+-*/(). ")
if not all(c in allowed_chars for c in expression):
return "Error: expression contains unsupported characters."
return str(eval(expression))
except Exception as e:
return f"Error: {e}"Two things matter a lot here and are easy to gloss over:
- The docstring is not decoration — it's the description the model reads to decide when this tool is relevant and how to format its input. If the docstring is vague, the model will call the tool incorrectly or not at all.
- The type hints (
expression: str, returnstr) tell LangChain how to build the tool's input schema, which is what gets sent to the model alongside the description.
You'd define a second tool the same way — say, a search_web tool or a get_current_weather tool — but one tool is enough to demonstrate the pattern, so let's move on to using it.
Wrapping It as a Basic Agent
Here's the same calculate tool wired into a minimal agent using LangChain's prebuilt agent constructor:
from langchain.agents import create_agent
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_agent(
model=llm,
tools=[calculate],
system_prompt="You are a helpful assistant. Use the calculate tool "
"for any arithmetic instead of computing it yourself."
)
response = agent.invoke({
"messages": [("human", "What is 847 * 23, plus 15?")]
})
print(response["messages"][-1].content)Run this and inspect what actually happens (most tutorials skip this part, and it's the most instructive bit): the model receives your question, recognizes it needs arithmetic, emits a tool call with something like {"expression": "847 * 23 + 15"}, LangChain executes your Python function with that input, feeds the result back to the model as a tool message, and *then* the model writes the final answer using that result. That whole loop — think, call a tool, observe the result, think again, respond — is what an agent is. The model is looping over its own reasoning with tools available at each turn, rather than following one fixed path.
If you print the full response["messages"] list instead of just the last one, you'll see every step: the human message, an AI message containing the tool call, a tool message with the result, and the final AI message. That transparency is genuinely useful when you're debugging why an agent did something unexpected — always look at the full message trace before assuming the model is "wrong."
Chain vs. Agent: The Confusion Point Everyone Hits
This is the single most common point of confusion for beginners, so let's be blunt about the distinction.
A chain is a fixed sequence you define in code. Prompt, then model, then parser — always in that order, every time, regardless of the input. You know exactly what will execute before you run it.
An agent is a loop where the model decides the sequence at runtime. It might call zero tools, one tool, or five tools in a row, and you don't know which in advance — it depends on what the model decides is needed for that specific input.
A useful way to decide which one you need: if you can draw the exact sequence of steps on a whiteboard *before* running anything, and that sequence never changes based on the input, you want a chain. If the right sequence of steps genuinely depends on what the user asks — sometimes you need a web search, sometimes you need a calculation, sometimes you need nothing extra at all — you want an agent.
Beginners frequently reach for an agent when a chain would do, mostly because "agent" sounds more capable. But agents are slower (multiple model round-trips instead of one), less predictable, and harder to debug, because the model is making decisions you didn't explicitly program. Default to a chain. Upgrade to an agent only when you have a concrete case where the model genuinely needs to choose between multiple actions based on the input.
Do You Actually Need Memory?
The other recurring beginner question: "how do I add memory?" Usually the honest answer is that you don't need it yet.
Both the chain and the agent above are stateless — every .invoke() call starts fresh, with no knowledge of previous calls. For a huge number of use cases, that's completely fine: a one-off summarizer, a classification chain, a tool that answers a single question. Adding memory here would be pure overhead.
You need memory when you're building an actual multi-turn conversation, where the model needs to recall what the user said three messages ago. In LangChain, the current pattern for this is to manage conversation history explicitly as a list of messages and pass it back in on every call, rather than relying on older built-in memory classes (many of which have been deprecated in favor of this simpler, more explicit approach):
conversation_history = []
def chat(user_input: str) -> str:
conversation_history.append(("human", user_input))
response = agent.invoke({"messages": conversation_history})
ai_message = response["messages"][-1]
conversation_history.append(("ai", ai_message.content))
return ai_message.content
print(chat("My name is Priya."))
print(chat("What's my name?"))Notice what's actually happening: there's no special "memory" object. It's just a list you append to and pass in again. That's genuinely the core idea behind conversation memory in modern LangChain — explicit state you control, not a hidden black box. Once you're building something long-running (multi-session agents, workflows that need to resume after interruption), you'll want to look at LangGraph, which handles persistent state more robustly — but that's a step beyond this tutorial, and you don't need it to understand the fundamentals.
Common Mistakes Beginners Make
A handful of issues account for most of the confusion we see from people learning LangChain for the first time:
- Vague tool docstrings. If your tool's docstring doesn't clearly say what it does and when to use it, the model will misuse it or ignore it. Treat the docstring as the actual interface, not an afterthought.
- Reaching for an agent by default. As covered above — start with a chain, upgrade only when the input genuinely requires branching logic the model itself must decide.
- Not inspecting intermediate messages. When an agent does something odd, print the full message list before assuming something is broken. The tool call arguments and tool outputs are almost always visible and almost always explain the behavior.
- Mixing up package versions.
langchain,langchain-core, and provider packages likelangchain-openaiversion independently. If an import fails, check that all your installed packages are reasonably current and from matching release windows. - Skipping `temperature=0` for tool-calling agents. A lower temperature makes tool selection far more consistent. Save higher temperatures for creative, free-text generation tasks, not for deciding which function to call.
- Forgetting error handling in tools. Tools should return a readable error string on failure (as our
calculatetool does) rather than raising an exception that crashes the whole agent loop.
Where to Go From Here
You now have the actual mental model: prompt templates and models compose into chains via LCEL, tools are typed functions with a description the model reads, and an agent is a loop that lets the model choose which tools to call and when. That's not a toy simplification — it's genuinely the foundation everything more advanced in LangChain sits on top of, including retrieval-augmented generation, multi-agent systems, and LangGraph workflows.
The natural next step is combining these pieces into something that solves a real problem for you — a research assistant, a coding helper, a personal knowledge tool — rather than isolated demo snippets. That's exactly the gap between "I understand the syntax" and "I can build things," and it's where most self-taught learners stall out.
If you want a structured path through that gap, our course "Introduction to AI Agents" picks up right where this tutorial leaves off, walking through multi-tool agents, planning patterns, and the failure modes you'll hit once your agents are doing real work instead of toy arithmetic. And if you're interested in applying agents to your own knowledge and workflows rather than generic demos, "Building a Second Brain with AI Agents" goes deeper into that specific, very practical direction. Either is a solid next stop once the code in this article feels comfortable rather than mysterious.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.