Note:This article was written in July 2026 using LangChain 0.3.x. APIs may change in future versions — check the[LangChain docs]if something doesn't work.
When I started learning LangChain, I got overwhelmed. There are chains, agents, memory, retrievers, output parsers — dozens of abstractions. Then I realized: most of them are deprecated.
LangGraph (by the same team) has replaced the orchestration layer. But you still need LangChain's core building blocks inside LangGraph nodes. So I figured out the minimum you actually need to learn.
Here's everything, in one file.
| # | Topic | Why It Matters for LangGraph |
|---|---|---|
| 1 | LLM Setup | You need to talk to a model |
| 2 | Prompt Templates + LCEL | The LCEL pipe syntax carries over |
| 3 | Structured Output | Pydantic models replace the old output parsers |
| 4 | Tool Calling | |
| This is the big one — LangGraph agents are built around the tool-calling loop |
Let's walk through each one.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="llama-3.3-70b-versatile",
temperature=0.0,
api_key=os.environ["GROQ_API_KEY"],
base_url="https://api.groq.com/openai/v1",
)
ChatOpenAI
works with any OpenAI-compatible API. Here I'm using Groq for free, fast inference with Llama 3.3.
from langchain_core.prompts import ChatPromptTemplate
template_string = """Translate the text that is delimited by triple backticks \
into a style that is {style}.
text: ```
{text}
"""
prompt_template = ChatPromptTemplate.from_template(template_string)
chain = prompt_template | llm
response = chain.invoke({"style": "formal English", "text": "yo what's up"})```
The pipe (|
) syntax is called LCEL (LangChain Expression Language). It replaces the old LLMChain
, SequentialChain
, etc. Simple and composable.
The old way required ResponseSchema
, StructuredOutputParser
, and injecting format instructions into your prompt. The new way is just Pydantic:
from pydantic import BaseModel, Field
class ReviewInfo(BaseModel):
"""Information extracted from a product review."""
gift: bool = Field(description="Was the item purchased as a gift?")
delivery_days: int = Field(description="How many days to arrive? -1 if unknown.")
price_value: list[str] = Field(description="Sentences about value or price.")
structured_llm = llm.with_structured_output(ReviewInfo, method="function_calling")
result = (prompt | structured_llm).invoke({"text": review})
print(result.gift) # True (a real bool, not the string "true")
print(result.delivery_days) # 2 (a real int)
Who does what:
| Step | Who |
|---|---|
| Converting Pydantic → JSON schema | LangChain |
| Understanding schema & producing JSON | The LLM |
| Parsing JSON back into Pydantic obj | LangChain |
No more format instructions in your prompt. The LLM never sees "return JSON" — it uses function calling under the hood.
This is what LangGraph automates. Understanding it manually first makes LangGraph click instantly.
Define tools — just Python functions with the @tool
decorator:
from langchain_core.tools import tool
@tool
def get_current_weather(city: str) -> str:
"""Get the current weather for a given city."""
return {"berlin": "17°C, cloudy"}.get(city.lower(), "No data")
@tool
def get_population(city: str) -> int:
"""Get the approximate population of a city."""
return {"berlin": 3_700_000}.get(city.lower(), -1)
The @tool
decorator transforms these into BaseTool
objects. The docstring becomes the description the LLM reads to decide when to call it.
Bind tools and call:
tools = [get_current_weather, get_population]
llm_with_tools = llm.bind_tools(tools)
messages = [HumanMessage("What's the weather and population in Berlin?")]
ai_response = llm_with_tools.invoke(messages)
print(ai_response.tool_calls)
The LLM does not execute the tools. It returns a structured request asking you to run them.
Execute tools and send results back:
tool_map = {t.name: t for t in tools}
messages.append(ai_response)
for tc in ai_response.tool_calls:
result = tool_map[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))
final_response = llm_with_tools.invoke(messages)
print(final_response.content)
This loop — LLM decides → you execute → send result back → repeat — is exactly what LangGraph's ToolNode
automates.
If you're heading to LangGraph, don't bother learning these legacy LangChain abstractions:
LLMChain
→ replaced by LCEL (prompt | llm
)SequentialChain
→ replaced by LCEL pipesRouterChain
→ replaced by LangGraph branchingConversationChain
→ replaced by LangGraph stateAgentExecutor
→ replaced by LangGraph agent loopLangGraph is not a replacement for LangChain — it builds on top of it:
LangGraph came in early 2024 because LangChain's original agent/chain abstractions were too rigid — hard to customize, no support for cycles or branching, and memory was bolted on rather than built in.
The entire thing is one file: github.com/santanu2908/langchain-essentials
git clone https://github.com/santanu2908/langchain-essentials.git
cd langchain-essentials
uv sync
uv run main.py
Next up: LangGraph. If this helped you, follow along — I'll be sharing that journey too.
I'm Santanu Mohanta — connect with me on LinkedIn or check out my projects on GitHub.