TL;DR — LangChain is the glue between your code and any LLM. As of v1.0 (GA, Oct 2025) it stopped being a grab-bag of chains and became an agent framework: one function, create_agent(), builds a production agent on the LangGraph runtime, and a middleware system lets you control every step of the loop. Legacy chains moved to langchain-classic. This is the crash course for the v1 era.
What LangChain is
LangChain is an open-source framework for building applications on top of large language models. It gives you one consistent interface over every model provider (OpenAI, Anthropic, Google, AWS Bedrock, local models via Ollama) plus the building blocks an LLM app actually needs — messages, prompts, tools, structured output, retrieval, memory — and a runtime to run agents reliably.
In the AI Native landscape it sits in AI Agent › Agent Framework: the application layer that turns a raw model endpoint into something that retrieves context, calls tools, loops, and makes decisions.
The mental model is three layers:
Fig 1 — You write against LangChain's interfaces; the provider underneath is swappable.
What changed in v1 (read this first)
If you learned LangChain in the 0.x days, almost everything you remember about chains is now legacy. The v1.0 release (October 2025) is the first stable major version — a commitment to no breaking changes until 2.0 — and it re-centered the whole framework on agents. The headlines:
create_agent()is the new front door. It replaceslanggraph.prebuilt.create_react_agentand the oldAgentExecutor. One call gives you a tool-calling agent on the LangGraph runtime.- Middleware. Six hook points around the agent loop let you inject retries, summarization, PII redaction, human-in-the-loop, guardrails — without rewriting the agent.
- Standard content blocks.
message.content_blocksgives a provider-agnostic view of text, reasoning, tool calls, and images. - Structured output in the main loop. The agent can return a typed object without a second LLM call.
- Clean namespace. Core imports live under
langchain.*; deprecated chains/retrievers/indexing moved tolangchain-classic.
LLMChain, ConversationChain, RetrievalQA, initialize_agent. Those still exist — in langchain-classic (pip install langchain-classic). For anything new, use create_agent + middleware instead.Install & setup
Install the core package plus the provider integration you want. New model names work without upgrading LangChain because provider packages pass the name straight through.
# core framework + a provider
pip install langchain langchain-openai
# or: langchain-anthropic, langchain-google-genai, langchain-aws, langchain-ollama
export OPENAI_API_KEY=sk-...
Models — the core primitive
Everything bottoms out in a chat model. init_chat_model builds one for any provider from a single string:
from langchain.chat_models import init_chat_model
model = init_chat_model("gpt-5.4", model_provider="openai")
# shorthand also works: init_chat_model("openai:gpt-5.4")
resp = model.invoke("Why do parrots talk?")
print(resp.text) # convenience accessor for plain text
Every model supports the same four verbs — invoke, stream, batch, and their async a* twins:
for chunk in model.stream("Explain vector databases"):
print(chunk.text, end="", flush=True)
answers = model.batch(["What is RAG?", "What is an embedding?"])
Pass conversation history as a list of role/content dicts (or LangChain message objects):
resp = model.invoke([
{"role": "system", "content": "Translate English to French."},
{"role": "user", "content": "I love building applications."},
])
Common knobs: temperature, max_tokens, timeout, max_retries (default 6).
Content blocks
Different providers return text, reasoning traces, and images in different shapes. v1's .content_blocks normalizes them so your code is provider-agnostic:
resp = model.invoke("Think step by step, then answer.")
for block in resp.content_blocks:
if block["type"] == "reasoning":
print("THINKING:", block["reasoning"])
elif block["type"] == "text":
print("ANSWER:", block["text"])
LCEL — composing with the pipe
The LangChain Expression Language is still the backbone for non-agent pipelines. Every component — prompts, models, parsers, retrievers — implements the Runnable interface (invoke/batch/stream), and the | operator wires them into one Runnable that inherits sync, async, batch, and streaming for free.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template("Explain {topic} in one sentence.")
chain = prompt | model | StrOutputParser()
print(chain.invoke({"topic": "vector databases"}))
Reach for LCEL when you have a fixed sequence of steps (prompt → model → parse, or a retrieval chain). Reach for create_agent when the model needs to decide what to do next.
Structured output
Stop regex-parsing model text. with_structured_output binds a Pydantic schema and gives you a validated object back:
from pydantic import BaseModel, Field
class Movie(BaseModel):
title: str = Field(description="Movie title")
year: int = Field(description="Release year")
rating: float = Field(description="Rating out of 10")
structured = model.with_structured_output(Movie)
movie = structured.invoke("Give me details about Inception")
print(movie.title, movie.year) # -> Inception 2010
Under the hood this uses the provider's native structured-output / tool-calling support. In an agent you get the same thing via response_format (next section) — without a second model call.
Tools & tool calling
A tool is just a Python function the model can choose to call. Decorate it with @tool; the docstring becomes the description the model reads, and the type hints become the argument schema.
from langchain.tools import tool
@tool
def get_weather(location: str) -> str:
"""Get the current weather at a location."""
return f"It's sunny in {location}."
# bind directly to a model for raw tool-calling:
model_with_tools = model.bind_tools([get_weather])
resp = model_with_tools.invoke("What's the weather in Boston?")
for call in resp.tool_calls:
print(call["name"], call["args"]) # get_weather {'location': 'Boston'}
bind_tools only asks the model which tool to call — it doesn't run it. The agent loop (next) is what actually executes tools and feeds results back.
Agents — create_agent
This is the headline of v1. create_agent builds a complete tool-calling agent: it calls the model, runs any requested tools, feeds results back, and loops until the model is done — all on the durable LangGraph runtime.
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
agent = create_agent(
model="openai:gpt-5.4",
tools=[search],
system_prompt="You are a concise research assistant.",
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Find recent news on vLLM"}]}
)
print(result["messages"][-1].content)
Fig 2 — The agent loop and the six middleware hook points around it.
Typed responses with response_format
from pydantic import BaseModel
class Answer(BaseModel):
summary: str
confidence: float
agent = create_agent("openai:gpt-5.4", tools=[search], response_format=Answer)
result = agent.invoke({"messages": [{"role": "user", "content": "Summarize AI infra trends"}]})
print(result["structured_response"]) # Answer(summary=..., confidence=...)
Middleware — the v1 superpower
Middleware is how you control the agent loop without forking it. Each piece can hook six points: before_agent, before_model, wrap_model_call, wrap_tool_call, after_model, after_agent (see Fig 2). Write them as decorators for one-off hooks, or as a class when you need several hooks + async.
from langchain.agents.middleware import before_model, wrap_model_call, AgentState, ModelRequest, ModelResponse
from langgraph.runtime import Runtime
from typing import Callable, Any
@before_model
def log_turn(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
print(f"about to call model with {len(state['messages'])} messages")
return None
@wrap_model_call
def retry(request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]) -> ModelResponse:
for attempt in range(3):
try:
return handler(request)
except Exception:
if attempt == 2:
raise
agent = create_agent("openai:gpt-5.4", tools=[search], middleware=[log_turn, retry])
And the batteries-included middleware you'll reach for constantly:
from langchain.agents.middleware import (
SummarizationMiddleware, # compress long histories to fit context
HumanInTheLoopMiddleware, # pause for approval before risky tools
PIIMiddleware, # redact emails / cards / secrets
)
agent = create_agent(
"openai:gpt-5.4",
tools=[search],
middleware=[SummarizationMiddleware(), PIIMiddleware()],
)
Memory & conversation state
Agents are stateless per call. To remember earlier turns, give the agent a checkpointer and pass a stable thread_id. The runtime persists the message history per thread.
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent("openai:gpt-5.4", tools=[], checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "user-42"}}
agent.invoke({"messages": [{"role": "user", "content": "My name is Shivam."}]}, config=config)
r = agent.invoke({"messages": [{"role": "user", "content": "What's my name?"}]}, config=config)
print(r["messages"][-1].content) # -> "Your name is Shivam."
Swap InMemorySaver for a Postgres/Redis checkpointer in production so state survives restarts. For long-term, cross-session memory across threads, dedicated stores like Mem0 or LangGraph's store layer take over.
Retrieval & RAG
The classic recipe: embed your documents, store the vectors, fetch the relevant ones at query time, stuff them into the prompt.
from langchain.embeddings import init_embeddings
from langchain_core.vectorstores import InMemoryVectorStore
emb = init_embeddings("openai:text-embedding-3-small")
store = InMemoryVectorStore.from_texts(
["LangChain standardizes LLM apps.",
"LangGraph adds durable, stateful agent runtime."],
embedding=emb,
)
retriever = store.as_retriever(search_kwargs={"k": 2})
docs = retriever.invoke("what runtime do agents use?")
In v1 the idiomatic pattern is agentic RAG — wrap retrieval as a tool and let the agent decide when to search, instead of hard-wiring a retrieve-then-answer chain:
from langchain.tools import tool
from langchain.agents import create_agent
@tool
def search_docs(query: str) -> str:
"""Search the internal knowledge base."""
hits = retriever.invoke(query)
return "\n\n".join(d.page_content for d in hits)
agent = create_agent("openai:gpt-5.4", tools=[search_docs],
system_prompt="Answer using the knowledge base. Cite what you used.")
print(agent.invoke({"messages":[{"role":"user","content":"What runtime do agents use?"}]})["messages"][-1].content)
For real corpora swap InMemoryVectorStore for a real engine — Milvus, Qdrant, pgvector, Weaviate — and add a text splitter to chunk documents before embedding.
Streaming
Agents stream too. stream_mode="values" emits the full state after each step; "updates" emits just the deltas; "messages" streams tokens as they generate.
from langchain.messages import AIMessage, HumanMessage
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Search vLLM news and summarize"}]},
stream_mode="values",
):
msg = chunk["messages"][-1]
if isinstance(msg, AIMessage) and msg.tool_calls:
print("calling:", [tc["name"] for tc in msg.tool_calls])
elif isinstance(msg, AIMessage):
print("agent:", msg.content)
Observability — LangSmith
Agents are non-deterministic and multi-step, so "it gave a weird answer" is hard to debug blind. LangSmith traces every model call, tool call, token count, and latency. It's opt-in via env vars — no code change:
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=ls-...
You get a full waterfall of each run, plus evaluation datasets and prompt versioning. It's a separate hosted product, but the tracing SDK is free for solo use and the single highest-leverage thing to turn on early.
The ecosystem around it
| Package / product | What it is |
|---|---|
langchain | The framework: create_agent, models, tools, middleware. |
langchain-core | Runnable interface, messages, base abstractions. Tiny, stable. |
langchain-<provider> | Integrations (-openai, -anthropic, …). Versioned separately. |
| LangGraph | The low-level runtime under create_agent. Drop to it for custom graphs, branching, multi-agent. |
langchain-classic | Legacy chains, old retrievers, indexing API. For migrations only. |
| LangSmith | Tracing, evals, monitoring. The observability layer. |
When to use, when to skip
Use it when you're building an agent — something that calls tools, loops, retrieves, and needs guardrails — and want provider portability plus a huge integration ecosystem. create_agent + middleware is the fastest path to a production-shaped agent.
Skip it for a single dumb completion (the provider SDK is enough). When you outgrow the linear agent loop — complex branching, multiple coordinating agents, explicit state machines — drop down to LangGraph directly. Teams wanting a smaller, strictly-typed surface sometimes prefer Pydantic AI; RAG-first apps sometimes prefer LlamaIndex.
langchain-core + a specific provider over the meta-package to keep your dependency surface small. Turn on LangSmith tracing on day one. Use a durable checkpointer (Postgres/Redis), not InMemorySaver, in prod. And don't reach for an agent when an LCEL chain (or a plain function) would do — the loop costs tokens and latency.vs the alternatives
| Tool | Best for | Trade-off |
|---|---|---|
| LangChain v1 | General agents, middleware, integrations, portability | Big surface; ecosystem churn |
| LangGraph | Custom graphs, branching, multi-agent, max control | More to wire by hand |
| LlamaIndex | RAG / data-indexing-first apps | Lighter agent tooling |
| Pydantic AI | Type-safe, minimal, Pythonic agents | Smaller ecosystem |
| CrewAI / AutoGen | Opinionated multi-agent collaboration | Less low-level control |
| Raw provider SDK | Single calls, total control | You build all the plumbing |
Verified against the official LangChain v1 docs (docs.langchain.com), May 2026. APIs shown target langchain >= 1.0.
Where LangChain fits: the mental model
LangChain is an application-layer component that turns model calls into controlled, multi-step software behavior. The useful question is not simply “can it run the demo?” It is whether the component gives your team a clear ownership boundary, predictable failure behavior, and enough evidence to operate changes safely. Treat it as one replaceable layer in a larger system rather than letting it quietly become the architecture.
Start by drawing the request and data path. Mark where untrusted input enters, where identity is checked, where durable state changes, and where retries can repeat work. That diagram tells you which guarantees belong to LangChain and which still belong to your application, platform, cloud provider, or database. The distinction matters during incidents: a healthy process is not proof that the end-to-end task is correct.
Core concepts you should understand first
The vocabulary below is more important than any single SDK method. It lets application engineers, platform engineers, security reviewers, and incident responders describe the same system without confusing a framework feature with an end-to-end guarantee.
| Concept | Meaning in this layer | Design question |
|---|---|---|
| Control loop | The repeated plan, call, observe, and decide cycle. Bound it with explicit stop conditions and budgets. | Write down how LangChain represents or enforces this before production. |
| Tool contract | A typed name, description, input schema, output schema, timeout, and error model exposed to the model. | Write down how LangChain represents or enforces this before production. |
| State | Data required between steps or turns. Separate durable business state from disposable prompt context. | Write down how LangChain represents or enforces this before production. |
| Context window | The finite model input assembled for a step. Retrieval and summarization are policies, not infinite memory. | Write down how LangChain represents or enforces this before production. |
| Determinism boundary | Code should own authorization, money movement, deletion, and invariants; the model may propose actions. | Write down how LangChain represents or enforces this before production. |
| Checkpoint | A recoverable snapshot used to resume long-running or human-approved work without replaying side effects. | Write down how LangChain represents or enforces this before production. |
From quick start to a production deployment
The earlier quick start proves that the package or service runs. Production readiness is a different exercise. Build the smallest vertical slice that crosses every real boundary—identity, network, persistence, upstream provider, telemetry, and rollback—before broadening the feature set.
- Pin the compatibility envelope. Record the LangChain release, language/runtime version, client SDK version, model or backend version, and—where applicable—Kubernetes API or driver requirements. Use a lock file, immutable image digest, or chart version; floating “latest” tags prevent repeatable rollback.
- Define contracts before configuration. Write the accepted input, successful output, error classes, timeout, idempotency behavior, and ownership of durable state. Validate at the boundary so corrupt work fails early instead of surfacing deep in a workflow.
- Create separate development, staging, and production identities. Do not copy a broad personal API key into every environment. Prefer workload identity or short-lived credentials, scope access by tenant and operation, and verify denial cases as part of deployment.
- Add bounded failure behavior. Every remote call needs a deadline. Retry only transient, idempotent operations with exponential backoff and jitter. Set concurrency and queue limits so an upstream slowdown becomes controlled backpressure rather than resource exhaustion.
- Instrument the complete path. Emit a correlation ID, component and release version, duration, outcome, retry count, and resource or cost dimensions. Keep sensitive prompt, document, and credential values out of ordinary logs.
- Ship through a reversible rollout. Run compatibility and regression tests, deploy to a canary or isolated workload, compare service-level indicators, then increase exposure. Preserve the previous artifact and configuration until rollback has been exercised.
Production configuration checklist
- Pin artifacts by version and, where possible, digest.
- Set connect, request, and total workflow deadlines.
- Bound retries, concurrency, queue length, and payload size.
- Separate read-only operations from mutations.
- Use idempotency keys for replayable mutations.
- Persist canonical state outside disposable workers.
- Encrypt traffic and durable data with managed keys.
- Redact secrets, tokens, prompts, and personal data.
- Apply per-tenant quotas and authorization filters.
- Expose readiness separately from process liveness.
- Back up metadata and test restore, not only backup.
- Document owner, escalation path, RPO, and RTO.
Failure modes and the response you should design
| Failure mode | What you observe | Engineering response |
|---|---|---|
| Unbounded loop | The agent keeps revising or calling tools. | Set maximum steps, token/cost budgets, and a terminal failure state. |
| Duplicate side effect | A retry repeats an email, charge, or write. | Give mutations idempotency keys and persist completion before retrying. |
| Prompt injection | Retrieved or web content instructs the agent to cross a trust boundary. | Treat content as data, allow-list tools, and re-authorize every sensitive action. |
| Context drift | Summaries omit a requirement or stale state wins. | Keep canonical state outside the prompt and rebuild context from versioned records. |
| Provider degradation | Rate limits or model errors stall the workflow. | Use bounded exponential backoff, circuit breakers, and an explicitly tested fallback. |
| Schema mismatch | The model emits arguments a tool cannot accept. | Validate at the boundary and return a small, machine-readable repair error. |
Turn these rows into runbook entries with an alert, first diagnostic query, safe mitigation, and escalation owner. Test at least one failure in staging every release cycle. If the system cannot be forced into a failure safely, it is usually not yet observable or isolated enough.
Security, privacy, and tenant isolation
Place LangChain in a threat model, not just an architecture diagram. Identify human users, workload identities, administrators, upstream services, model providers, artifact registries, and data stores. For each edge, document authentication, authorization, encryption, audit evidence, and the consequence of credential compromise.
Apply least privilege at the operation and resource level. A component that only retrieves documents should not be able to delete the index; an evaluation worker should not inherit production mutation credentials; a model-serving pod should not need cluster-admin. In multi-tenant systems, enforce the tenant boundary before retrieval or execution and include tenant identity in quotas and audit events. Never rely on a prompt instruction, namespace string supplied by the client, or UI filtering as authorization.
Decide what data is permitted in telemetry. Prompts, retrieved chunks, tool arguments, model responses, notebooks, and traces can contain secrets or regulated data. Redact close to collection, keep high-sensitivity payload capture opt-in, encrypt exports, restrict support access, and give each class an explicit retention period. Verify deletion across caches, replicas, indexes, backups, and derived evaluation datasets.
Observability and service-level objectives
A useful dashboard follows the user-visible unit of work and then decomposes it by component, release, tenant tier, backend, and failure class. Start with these signals for LangChain:
- task success rate — graph both rate and distribution, then compare with the previous release and traffic mix.
- steps per successful task — graph both rate and distribution, then compare with the previous release and traffic mix.
- tool-call error rate — graph both rate and distribution, then compare with the previous release and traffic mix.
- model tokens and cost per task — graph both rate and distribution, then compare with the previous release and traffic mix.
- p50/p95/p99 end-to-end latency — graph both rate and distribution, then compare with the previous release and traffic mix.
- human escalation and override rate — graph both rate and distribution, then compare with the previous release and traffic mix.
Choose an SLO at the boundary your users experience, such as “99% of accepted tasks complete correctly within five minutes over 28 days.” Availability alone is insufficient for AI systems because a fast but incorrect or ungrounded result is still a failure. Pair latency and completion objectives with a reviewed quality or policy indicator. Page on rapid error-budget burn; use tickets for slow capacity trends.
Testing and release strategy
Use four layers. Unit tests cover deterministic adapters, schemas, policy, and error mapping without a live external service. Contract tests exercise the pinned integration boundary—API, CLI, SDK, protocol, or ephemeral service—and verify its exact surface. Scenario tests exercise representative end-to-end cases, including permissions and state. Load and resilience tests establish saturation, queue behavior, retry amplification, and recovery after dependency loss.
Keep a small blocking suite for every commit and a broader scheduled suite for expensive or probabilistic checks. Store results with the application version, LangChain version, configuration hash, model/backend version, dataset version, and random seed. A score without that provenance cannot explain a regression. Before upgrading, read the migration notes, run both versions against the same replay set, and explicitly test rollback across any schema or state transition.
How to decide whether LangChain is the right tool
| Question | Evidence to collect | Red flag |
|---|---|---|
| Does it remove a real constraint? | A measured bottleneck, missing guarantee, or repeated custom component. | Adoption is based only on a demo or feature count. |
| Can the team operate it? | Named owner, upgrade path, alerts, runbooks, backup, restore, and on-call skills. | Only the original prototype author understands failure behavior. |
| Is the interface portable? | Your domain contracts wrap vendor-specific APIs; data and state have an export path. | Business objects are inseparable from framework internals. |
| Does it meet the envelope? | Benchmarks using your payloads, concurrency, topology, quality bar, and cost model. | Published benchmark hardware or workload does not resemble production. |
| Is failure affordable? | Tested degraded mode, bounded blast radius, rollback, RPO, and RTO. | A component outage blocks unrelated tenants or irreversible actions. |
Prefer the smallest component that satisfies the required guarantees. A provider SDK, relational table, background job, or standard Kubernetes controller is often better than another platform when the workload is small and predictable. Choose LangChain when its specific abstraction removes sustained engineering work and the team is willing to own its lifecycle.
A focused 90-minute validation lab
- Minutes 0–15: run the documented quick start in a disposable environment with pinned dependencies. Save the exact commands and a known-good input/output fixture.
- Minutes 15–35: replace the toy input with one representative case from your system. Add schema validation, a deadline, and a correlation ID.
- Minutes 35–55: force invalid credentials, a timeout, malformed input, and one dependency failure. Record the observed errors and whether retries are safe.
- Minutes 55–75: run a small concurrency test and capture latency, throughput, saturation, and unit cost. Do not extrapolate beyond the tested range.
- Minutes 75–90: write the adoption decision: required guarantees met, open risks, owner, next experiment, and the simplest credible alternative.
Frequently asked questions
Should we standardize on LangChain for every team?
Standardize the contracts, telemetry, security controls, and release evidence first. Standardizing one implementation is useful only when workloads share requirements and a platform team owns upgrades and support.
Can we use the hosted version and skip operations work?
Hosted service removes part of the control-plane burden, not architecture ownership. You still own identity, tenant isolation, data classification, quotas, dependency failure, observability, export, and an exit plan.
What should be pinned for reproducibility?
Pin the tool/server, client SDK, runtime, configuration, model or backend, container image digest, and test dataset. Record these values with every benchmark and evaluation result.
When is a proof of concept ready for production?
After representative success and failure tests pass, sensitive data paths are approved, limits and SLOs are defined, telemetry and runbooks exist, restore or rollback is rehearsed, and an accountable owner accepts the remaining risk.
Official sources and freshness
This guide was reviewed for architecture and operational guidance on 10 July 2026. Projects evolve quickly: verify installation syntax, supported versions, feature maturity, and upgrade notes against the exact release you deploy.