+1 (570) 533-9287

Migrating to LangChain 1.0: create_agent, Middleware, and the Code You Get to Delete

Most teams running LangChain in production are running a version of it that predates the 1.0 line: AgentExecutor or a hand-rolled LangGraph loop, a pile of callback handlers, and a chains/ directory nobody wants to open. The 1.0 releases changed the recommended shape of an agent, and the changes are the useful kind — fewer abstractions, clearer state, and a defined place to put the cross-cutting logic that used to be smeared across your node functions.

This is a migration guide with a bias: move for the parts that delete your code, and leave the rest alone until you have a reason.

What actually changed

Three things matter for a production app.

One agent constructor. create_agent (formerly create_react_agent in LangGraph prebuilt) is now the default entry point: a model, a set of tools, a system prompt, and a compiled graph you can still inspect and extend. The older AgentExecutor and the initialize_agent family are legacy. They still run in many setups, but they are not where fixes and features land.

Middleware. Agent middleware is the headline. Instead of subclassing, wrapping, or forking the prebuilt loop to insert behavior, you attach hooks around defined points in the agent's lifecycle: before the model call, after the model call, around tool execution, and on errors. Middleware can rewrite the messages going to the model, summarize history, redact untrusted content, veto a tool call, or swap the model mid-run.

Content blocks on messages. Message content is a typed list of blocks — text, reasoning, tool calls, citations, server-side tool results — rather than a string plus provider-specific extras buried in additional_kwargs. If you have provider-conditional parsing code, this is where it goes away.

Everything legacy did not vanish; much of it moved into langchain-classic. That split is the real signal about where maintenance attention will be.

The migration in the order that hurts least

Do it in slices, each shippable, each behind your eval suite.

  1. Pin and read your surface area. Before touching anything, list every LangChain import your app uses and mark each one: core runtime, legacy chain, or community integration. The legacy list is your actual work queue, and it is usually shorter than people fear — most apps use four or five abstractions, not forty.
  2. Replace the agent loop. Swap AgentExecutor for create_agent with the same model, tools, and prompt. Change nothing else. Run your evals. Expect small trajectory differences in tool-calling order; expect no change in task success. If success moves, stop and find out why before continuing.
  3. Convert custom loop surgery into middleware. Anything you patched into the loop — history trimming, a retry wrapper, a tool-argument sanitizer, a token budget check — becomes a hook. This is where the diff gets satisfying: wrappers and subclasses collapse into small functions with an obvious call site.
  4. Delete legacy chains rather than port them. A RetrievalQA or LLMChain that formats a prompt, calls a model, and parses output is three lines of explicit code. Porting it to a compatibility import preserves indirection you no longer need. Rewriting it plainly is usually less work than the port and leaves something a new engineer can read.
  5. Simplify provider-specific parsing last. Once the loop is stable, rip out the branching that dug reasoning traces or citations out of raw provider payloads and read content blocks instead.

Middleware is a boundary, not a free-for-all

Middleware is powerful enough to be abused. The pattern we hold to: each middleware does one thing, names the invariant it enforces, and is independently testable.

Good candidates, roughly in the order teams need them:

  • History management. Summarize or trim older turns before the model call, with the policy in one place instead of inside three nodes.
  • Untrusted-content handling. Mark and fence text that came from retrieval or tool output, so injected instructions never arrive looking like operator instructions.
  • Tool gating. Require approval for the two or three tools that write to the world; auto-approve reads. This is human-in-the-loop as a policy object rather than a branch in your graph.
  • Model routing and fallback. Route cheap turns to a small model, escalate on failure or on a complexity signal, and record which model served each turn.
  • Budget guards. Cap tokens or tool calls per run and fail loudly. Runaway agent loops are a cost incident and a latency incident at the same time.

Two rules keep this from becoming a new tangle. First, middleware must not silently change semantics — if a hook drops a tool call, it emits a trace event saying so. Second, order is part of the contract: write down the intended sequence, because a redaction hook after a summarization hook does something different from the reverse.

What not to migrate

Framework honesty applies to framework upgrades too.

A pipeline that makes one model call with a fixed prompt and parses JSON does not need an agent, and it does not need middleware. If your service is a single-shot classifier or extractor, the 1.0 migration for it is deleting the dependency and calling the provider SDK directly. We have shipped that refactor more than once and never regretted it.

Likewise, if a legacy chain is stable, tested, and untouched for a year, leaving it pinned on langchain-classic is a defensible decision — as long as you write down when you will revisit it. Unbounded "we'll upgrade later" is how a codebase ends up on two major versions at once.

Guard the migration with evals, not vibes

This is a behavior-preserving refactor with a behavior-changing surface: prompt assembly, message formatting, and tool-call plumbing all shift underneath you. Run the same checks you would for a model swap.

  • Freeze an eval run on the current code as the baseline — task success, retrieval recall if you have RAG, and trajectory checks on tool sequences.
  • After each slice, diff per example. Look specifically at tool-call counts and at anything using structured output; those are the two places we see silent drift.
  • Watch p95 latency and tokens per run, not just averages. Middleware that summarizes history can cut tokens sharply and add a model call on some turns — worth knowing which trade you actually made.
  • Keep the old path runnable for a week. A feature flag that switches agent implementations is cheap insurance and makes rollback a config change.

The payoff

The reason to do this is not that the new API is newer. It is that the agent loop, the policy around it, and your prompt assembly become three separate readable things, and that cross-cutting behavior stops being copy-pasted into nodes. The typical migration we run removes more code than it adds. That is the result worth aiming for — if your version of this upgrade adds abstraction rather than removing it, something has gone wrong in the plan.