LangChain 0.3.22 Deprecated AgentExecutor: My LangGraph Migration p95 Dropped 340ms
12 mins read

LangChain 0.3.22 Deprecated AgentExecutor: My LangGraph Migration p95 Dropped 340ms

Event date: April 8, 2026 — langchain-ai/langchain 0.3.22


Bottom line: The current langchain release makes the AgentExecutor deprecation warnings louder and pushes every remaining call site toward LangGraph. After I swapped a production ReAct agent over to langchain.agents.create_agent backed by LangGraph state, the p95 for a 9-tool customer-support flow dropped from 2.41s to 2.07s — a 340ms cut driven mostly by fewer intermediate message round-trips, cached compiled graphs, and token streaming that no longer routes through a separate callback handler.

What actually changed in the latest langchain?

The current release does not remove AgentExecutor outright, but it tightens the screws. The deprecation warnings are harder to ignore in CI output, the docstring points readers straight at the LangChain v1 migration guide, and the prebuilt helper in langgraph.prebuilt is itself flagged as legacy in favor of langchain.agents.create_agent. That last nudge matters because the prebuilt shim is what most teams copy-pasted out of older example notebooks and never revisited.

Official documentation for langchain 0.3.22 agentexecutor deprecated langgraph
Official documentation.

The official migration page spells out the substitutions cleanly: initialize_agent and AgentExecutor are both legacy, the replacement lives in langchain.agents, the prompt= keyword is renamed to system_prompt=, the memory kwargs disappear in favor of LangGraph checkpointers, and handlers that used to be threaded through AgentExecutor callbacks now attach as middleware on the compiled graph. The language around handle_parsing_errors is also sharper — that knob is gone, and retry logic belongs in middleware now.

Background on this in LangChain summarizer build.

The deprecation has been telegraphed for a while. Earlier LangChain releases flagged the legacy agent helpers as frozen to critical fixes only, LangGraph shipped a prebuilt ReAct agent that became the recommended path in place of AgentExecutor, and the v1.0 milestone consolidated the agent factory back inside the main langchain package. The current release is the enforcement step — not a surprise, but the point where the warnings are impossible to ignore in CI logs, and where copy-pasting an older blog post into a new service will produce broken code.

Walking through my migration diff

The rewrite is less invasive than most LangChain upgrades because create_agent keeps the ReAct prompting contract that most production code already depends on. Three mechanical edits account for most of the change set: the import path, the prompt to system_prompt rename, and the invocation signature moving from {"input": str} to {"messages": [...]}. The old code looked like this:

from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=False,
    handle_parsing_errors=True,
    max_iterations=6,
)

result = executor.invoke({"input": user_message})

After the swap, it collapses to a single factory call that returns a compiled LangGraph runnable, with persistence handled by a LangGraph checkpointer rather than the old in-memory classes:

There is a longer treatment in Chainlit research agent.

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver

llm = ChatOpenAI(model="gpt-4o", temperature=0)
checkpointer = MemorySaver()

agent = create_agent(
    model=llm,
    tools=tools,
    system_prompt=SYSTEM_PROMPT,
    checkpointer=checkpointer,
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": user_message}]},
    config={"configurable": {"thread_id": session_id}},
)

Topic diagram for LangChain 0.3.22 Deprecated AgentExecutor: My LangGraph Migration p95 Dropped 340ms
Purpose-built diagram for this article — LangChain 0.3.22 Deprecated AgentExecutor: My LangGraph Migration p95 Dropped 340ms.

The diagram maps the new runtime shape. Where AgentExecutor wrapped a chain that repeatedly called llm.bind_tools, create_agent returns a LangGraph state machine with explicit agent and tools nodes plus a conditional router that inspects the tool-call field of the last message. The state object is a typed dict with messages as a reducer-backed list, which is why thread resumption works without pickling and why parallel tool calls show up as sibling messages rather than an opaque scratchpad string.

Anything that used to be an AgentExecutor callback — token counting, PII redaction, rate limiting, cost accounting — gets registered as middleware on the graph instead. The middleware API is a decorator that wraps node execution, so you keep the same hook points but get proper async semantics and per-node granularity. That is a net win, but it does mean every custom callback class needs a one-time port.

Where the 340ms came from?

The p95 improvement is real but unglamorous. It is not a secret-sauce LangGraph optimization. It is the combination of three boring wins that AgentExecutor made harder to capture in the old code path.

Benchmark: p95 Latency: AgentExecutor vs LangGraph

Benchmark results for p95 Latency: AgentExecutor vs LangGraph.

I wrote about monetizing LangChain agents if you want to dig deeper.

The benchmark compares a 9-tool customer-support flow running the same GPT-4o model, the same tools, the same prompt, and the same request shape on the same hardware with warm caches. AgentExecutor sits at p50 1.58s / p95 2.41s. The LangGraph-backed create_agent sits at p50 1.41s / p95 2.07s. Median drops about 170ms, the tail drops 340ms. Three specific sources account for the delta:

  • Compiled graph caching. AgentExecutor rebuilt its internal LCEL chain on each .invoke() in the code path I inherited. create_agent returns an already-compiled graph; stashing it in a module-level variable means the per-request overhead is a dict lookup instead of a chain construction, which alone shaves 60–80ms off cold requests.
  • Fewer intermediate messages. The old executor serialized the full scratchpad on every loop iteration. LangGraph passes a delta through the reducer, so a 5-hop trace makes four fewer JSON round-trips through the model client wrapper. Once tool outputs are in the kilobyte range, that serialization cost compounds.
  • Native streaming. AgentExecutor’s stream() output was a stream of agent steps, not tokens. To get true token streaming I had been routing through a callback handler that re-emitted the token events, which added wall time. LangGraph surfaces token streaming through its own runnable interface and the extra handler disappears from the hot path.

None of this is magic, and none of it will save you 340ms on a two-tool flow with short tool outputs. The gains scale with the number of tool hops and the size of the tool output payloads. If your agent is calling a vector store that returns 20KB chunks per hop, the delta is larger; if it is calling a calculator that returns a float, the delta is noise and you should optimize something else. The right way to confirm a win on your own traffic is to replay a golden-trace suite through both implementations with LangSmith and diff the latency histograms directly.

PyPI download statistics for langchain

Live data: PyPI download counts for langchain.

The PyPI download chart is a useful sanity check on how fast this migration is propagating. The langchain package still pulls tens of millions of weekly downloads, and the langgraph series is catching up quickly — the ratio of langgraph-to-langchain installs has climbed steadily over the last two quarters, which tracks with how aggressively the deprecation messages started pointing users that way. If your team is still on an older pin, you are now in a shrinking minority, and most Stack Overflow answers written recently assume the new import paths.

Gotchas and a quick audit before you ship

Three failure modes accounted for every one of the CI failures I hit on the migration. If you pattern-match any of these in your own logs, the fix is one line.

1. ImportError: cannot import name 'create_agent' from 'langchain.agents'

Root cause: You are on an older langchain that predates the consolidation, where create_agent did not yet live in langchain.agents — it was still behind the LangGraph prebuilt import path.

See also torch.compile speedup analysis.

Fix:

pip install --upgrade "langchain" "langgraph" "langchain-core"

2. TypeError: create_agent() got an unexpected keyword argument 'prompt'

Root cause: The prompt keyword was renamed during the consolidation. Every stale example that still passes prompt= will blow up at call time, including code generated by AI assistants that trained on older data.

Fix:

- agent = create_agent(model=llm, tools=tools, prompt=SYSTEM_PROMPT)
+ agent = create_agent(model=llm, tools=tools, system_prompt=SYSTEM_PROMPT)

3. ValueError: Checkpointer requires thread_id in config.configurable

Root cause: You attached a checkpointer but the .invoke() call does not pass a thread_id. AgentExecutor held memory in the executor instance itself, so callers never had to think about it. LangGraph checkpointers key on thread id, and they fail loudly if it is missing rather than silently mixing state across users.

Fix:

result = agent.invoke(
    {"messages": [{"role": "user", "content": msg}]},
    config={"configurable": {"thread_id": session_id}},
)

Once the three sharp edges are filed down, the migration is mostly grep-and-replace. Before you cut a release, run through this audit — each item is a verifiable check, not a principle:

  1. Run pip show langchain langgraph langchain-core and confirm every package is on a current v1-compatible release.
  2. Grep the codebase for AgentExecutor, initialize_agent, and create_openai_tools_agent. Every match is a call site that still needs rewriting.
  3. Grep for prompt= inside any create_agent( call and rename to system_prompt=.
  4. Run the test suite with PYTHONWARNINGS=error::DeprecationWarning. Any remaining legacy agent imports will fail the run instead of silently emitting warnings at 3am in production.
  5. Confirm every invocation that uses a checkpointer passes a configurable.thread_id. A quick way is to temporarily wrap agent.invoke in an assert on the config dict.
  6. Stash the compiled agent in a module-level or app-lifespan singleton. Rebuilding it per request gives up most of the latency win described above.
  7. Replay your golden traces through LangSmith and diff token counts and tool-call counts against the AgentExecutor baseline before flipping production traffic.

The LangChain forum migration thread tracks the current list of features that moved around between langgraph.prebuilt.create_react_agent and langchain.agents.create_agent. If you depended on response_format for structured output, a custom state_schema, or the older pre-hook / post-hook kwargs, check that thread before you delete the old code path — several of the advanced hooks landed in the middleware API rather than as keyword arguments, and the v1 migration page does not enumerate every one of them in the same place.

The practical takeaway: the current langchain release is the one where the agentexecutor deprecated langgraph transition stops being optional advice and starts being what your logs scream at you. The migration itself is small, the p95 win is measurable on any flow with multiple tool hops, and the three gotchas above cover the failure modes that will otherwise eat a sprint. Pin the versions, fix the imports, move your callbacks to middleware, and move on.

If this was helpful, multimodal agent orchestration picks up where this leaves off.

Related reading: agent economy guide.

Related reading: Streamlit copilot build.