LangChain 1.0 Masterclass — agents, tools, memory, RAG & middleware

One framework, one import path, every provider — build a real agent from a weather tool to a RAG retriever to custom middleware.

Swap OpenAI for Anthropic tomorrow and your agent code barely changes — that is the whole point of LangChain 1.0.

What it is

LangChain 1.0 is a Python framework for building and working with AI agents. Its core promise is abstraction: regardless of whether you call OpenAI, Anthropic, Mistral, or Google, you get the same classes and methods. A system built on LangChain that uses vector stores, embedding models, chat models, and agents can swap the underlying provider and keep the code almost identical. In 1.0 the framework pivoted hard toward being an agent library — create_agent is now the standard entry point, and middleware is the defining feature.

Why it matters

Before 1.0, building an agent meant juggling langchain-core, langchain-community, and per-provider packages with separate import paths (from langchain_core import ..., from langchain_openai import ...). The agent side lived in LangGraph. In 1.0 everything funnels through the main langchain package: from langchain.agents import create_agent, from langchain.chat_models import init_chat_model, from langchain.tools import tool. You still can use langchain-core and langchain-community, but for most work the main package is enough. The failure mode 1.0 removes is provider lock-in: your retry logic, your tool wiring, and your memory setup stop caring which vendor backs the call.

Examples

A minimal agent with one tool — a weather lookup that pings a free API
import requests
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.tools import tool

load_dotenv()

@tool
def get_weather(city: str) -> str:
    """Return weather information for a given city."""
    response = requests.get(f"https://wttr.in/{city}?format=j1")
    return response.json()

agent = create_agent(
    model="gpt-4.1-mini",
    tools=[get_weather],
    system_prompt="You are a helpful weather assistant who always cracks jokes and is humorous while remaining helpful.",
)

response = agent.invoke({
    "messages": [{"role": "user", "content": "What is the weather like in Vienna?"}]
})
print(response["messages"][-1].content)

Expected output: a joke-laden weather report for Vienna — partly cloudy, ~15°C, with wind and humidity. The model string "gpt-4.1-mini" is auto-recognized as OpenAI, so langchain-openai must be installed and an OPENAI_API_KEY present.

Structured output + context + memory — the agent locates the user, returns a typed response, and remembers the thread
from dataclasses import dataclass
from langchain.chat_models import init_chat_model
from langchain.tools import tool, ToolRuntime
from langgraph.checkpoint.memory import InMemorySaver

@dataclass
class Context:
    user_id: str

@dataclass
class ResponseFormat:
    summary: str
    temperature_celsius: float
    temperature_fahrenheit: float
    humidity: float

@tool
def locate_user(runtime: ToolRuntime[Context]) -> str:
    """Look up a user's city based on the context."""
    match runtime.context.user_id:
        case "ABC123": return "Vienna"
        case "XYZ456": return "London"
        case "HJKL111": return "Paris"
        case _: return "unknown"

model = init_chat_model("gpt-4.1-mini", temperature=0.3)
checkpointer = InMemorySaver()

agent = create_agent(
    model=model,
    tools=[get_weather, locate_user],
    system_prompt="You are a helpful weather assistant.",
    context_schema=Context,
    response_format=ResponseFormat,
    checkpointer=checkpointer,
)

config = {"configurable": {"thread_id": "1"}}
response = agent.invoke(
    {"messages": [{"role": "user", "content": "What is the weather like?"}]},
    config=config,
    context=Context(user_id="ABC123"),
)
print(response["structured_response"].summary)
print(response["structured_response"].temperature_celsius)

Expected output: "The current weather in Vienna is partly cloudy" then 15.0. The agent resolves the city from the user_id in context, is forced into the ResponseFormat shape, and — because of the InMemorySaver checkpointer — remembers the thread. Change thread_id to "2" and it forgets the earlier conversation.

RAG as a tool — turn a vector store into a retriever the agent can call
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.tools import create_retriever_tool
from langchain.agents import create_agent

embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
texts = [
    "I love apples", "I enjoy oranges", "I think pears taste very good",
    "I hate bananas", "I dislike raspberries", "I despise mangoes",
    "I love Linux", "I hate Windows",
]
vector_store = FAISS.from_texts(texts, embedding=embeddings)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})

retriever_tool = create_retriever_tool(
    retriever,
    name="knowledge_base_search",
    description="Search the small product/fruit knowledge base for information.",
)

agent = create_agent(
    model="gpt-4.1-mini",
    tools=[retriever_tool],
    system_prompt="You are a helpful assistant. If there are any questions about fruits, use the retriever tool first, retrieve the context, and answer concisely. You may need to use the tool multiple times.",
)

response = agent.invoke({
    "messages": [{"role": "user", "content": "What three fruits does the person like and what three fruits does the person dislike?"}]
})
print(response["messages"][-1].content)

Expected output: "The person likes oranges, apples, and pears. The person dislikes mangoes, raspberries, and bananas." The agent makes two separate similarity-search calls to the tool — one for likes, one for dislikes — then combines them. This is the old-school RAG path: langchain-community + FAISS + create_retriever_tool.

Middleware — swap the system prompt by user role, and swap the model by message count
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import ModelRequest, ModelResponse, dynamic_prompt, wrap_model_call
from langchain.chat_models import init_chat_model

@dataclass
class Context:
    user_role: str

@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
    base = "You are a helpful and very concise assistant."
    match request.runtime.context.user_role:
        case "expert": return base + " Provide detailed technical responses."
        case "beginner": return base + " Keep your explanations simple and basic."
        case "child": return base + " Explain everything as if you were literally talking to a 5-year-old."
        case _: return base

agent = create_agent(
    model="gpt-4.1-mini",
    middleware=[user_role_prompt],
    context_schema=Context,
)

response = agent.invoke(
    {"messages": [{"role": "user", "content": "Explain PCA."}]},
    context=Context(user_role="child"),
)
print(response["messages"][-1].content)

Expected output for user_role="child": "Imagine you have a big box of crayons with lots of colors. Sometimes you want to choose just a few crayons that can still help you color many pictures nicely. PCA is like a magic helper." The same middleware pattern — wrap_model_call — lets you pick a stronger model when the message count crosses a threshold.

Flags

FlagMeaning
create_agentThe standard 1.0 way to build an agent; takes model, tools, system_prompt, context_schema, response_format, checkpointer, middleware
init_chat_modelOne function to initialize any provider's chat model from a model string
@toolDecorator that turns a plain function into a tool the agent can call
ToolRuntime[Context]Gives a tool access to runtime context (e.g. user_id) without passing it as a parameter
response_formatForces the agent to answer in a structured dataclass/pydantic shape
InMemorySaverLangGraph checkpointer that remembers conversation history per thread_id
create_retriever_toolWraps a vector-store retriever as a tool the agent can call for RAG
middlewareComposable hooks (dynamic_prompt, wrap_model_call, before/after_agent) that sit between request and response

Origin

LangChain 1.0 shipped stable on October 22, 2025, after an alpha in early September 2025. The 1.0 milestone reframed LangChain as an agent framework: create_agent became the standard builder, middleware became the defining abstraction, and the package surface collapsed into the main langchain package. This tutorial follows NeuralNine's "LangChain Full Crash Course - AI Agents in Python" (published on his channel), which walks the same 1.0 API end to end.

The 1.0 pivot

Before 1.0, LangChain was a general toolkit for models, vector stores, and tools, while the agent side lived in LangGraph. In 1.0 the framework leaned into agents: create_agent is built on top of LangGraph, and middleware gives you dynamic prompts, model selection, summarization, human-in-the-loop, rate limits, and PII redaction as composable layers. The old per-provider import paths (langchain_core, langchain_openai) still exist, but the modern path is one import from langchain.

How create_agent composes everything

create_agent is the single entry point that wires model, tools, system prompt, context, structured output, memory, and middleware into one runnable agent. It is implemented on top of LangGraph, so under the hood you get a graph with nodes for the model and tool calls. The context_schema lets you pass typed runtime data (like a user_id) that tools read through ToolRuntime — so a tool can locate a user without the user_id being a tool parameter. The response_format forces the model into a structured dataclass, and the checkpointer (InMemorySaver) persists message history keyed by thread_id.

The middleware lifecycle

Middleware hooks fire at defined points in the agent cycle: before_agent, before_model, wrap_tool_call, wrap_model_call, after_model, after_agent. The dynamic_prompt decorator rewrites the system prompt from context; wrap_model_call lets you override the model per request (e.g. use a stronger model once a conversation passes N messages). You can also subclass AgentMiddleware and override these hooks directly for custom logic — timing, logging, guardrails. Prebuilt middleware covers summarization, human-in-the-loop, model/tool call limits, model fallback, and PII redaction.

RAG the old-school way still works

For retrieval you can still use the classic path: OpenAIEmbeddings + FAISS.from_texts to build a vector store, then vector_store.as_retriever() and create_retriever_tool to expose it as a tool. The agent can call that tool multiple times in one run — retrieve likes, retrieve dislikes, then combine — which is exactly what a single prompt can't do. This is the pattern that makes RAG feel native to an agent rather than bolted on.

Fun facts

Pros

Cons

Takeaways