# Migrating from langgraph.prebuilt.create\_react\_agent to langchain.agents.create\_agent missing feature

**URL:** <https://forum.langchain.com/t/migrating-from-langgraph-prebuilt-create-react-agent-to-langchain-agents-create-agent-missing-feature/1985>\
**Category:** LangChain\
**Tags:** python-help\
**Created:** [October 28, 2025, 8:06pm UTC](https://forum.langchain.com/t/migrating-from-langgraph-prebuilt-create-react-agent-to-langchain-agents-create-agent-missing-feature/1985 "2025-10-28T20:06:20Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![aerickson-clt](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/aerickson-clt/32/501_2.png) [@aerickson-clt](https://forum.langchain.com/u/aerickson-clt)\
**Post date:** [October 28, 2025, 8:06pm UTC](https://forum.langchain.com/t/migrating-from-langgraph-prebuilt-create-react-agent-to-langchain-agents-create-agent-missing-feature/1985/1 "2025-10-28T20:06:21Z")

</div>

I was using `langgraph.prebuilt.create_react_agent` which allows me to rewrite the message history as a function of the state. This functionality seems to be gone in the recommended replacement `langchain.agents.create_agent`. Example usage below. How do I get this to work now?

```python

def build_planner_agent_prompt(state: PlanExecuteState) -> list:
    """Build planner agent prompt with full context of the current state."""
    current_plan = state.get("plan", [])
    past_steps = state.get("past_steps", [])
    messages = state.get("messages", [])
    
    base_prompt = """You are a planning expert responsible for creating and updating plans to accomplish objectives.

Your tool:
- create_plan: Create or update the execution plan

blah blah blah...
"""
    
    # Add current state context
    if past_steps:
        past_context = "\n\nCOMPLETED WORK:\n" + "\n".join([
            f"✓ {step}\n Result: {result[:150]}..."
            for step, result in past_steps
        ])
        base_prompt += past_context
    
    if current_plan:
        plan_context = "\n\nCURRENT PLAN:\n" + "\n".join([
            f"{i+1}. {step}" for i, step in enumerate(current_plan)
        ])
        base_prompt += plan_context
    else:
        base_prompt += "\n\nCURRENT PLAN: (none - you need to create one or signal completion)"
    
    # Return system message + full conversation history
    return [{"role": "system", "content": base_prompt}] + messages

planner_agent = create_agent(
    model=LLM,
    tools=[create_plan],
    name="planner_expert",
    prompt=build_planner_agent_prompt, # <--- this argument is gone. and `system_prompt` only accepts a string.
)

```

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [October 28, 2025, 8:17pm UTC](https://forum.langchain.com/t/migrating-from-langgraph-prebuilt-create-react-agent-to-langchain-agents-create-agent-missing-feature/1985/2 "2025-10-28T20:17:27Z")

</div>

Hi @aerickson-clt

have you tried that migration guide [LangChain v1 migration guide - Docs by LangChain](https://docs.langchain.com/oss/python/migrate/langchain-v1#prompts) ?

In v1, dynamic prompts and message rewriting are handled via middleware. Use `dynamic_prompt` to compute a system prompt from state, and/or `before_model` to rewrite `messages` before the model is called.  
The `system_prompt` parameter is static; dynamic behavior moved into middleware. This is called out in the v1 migration guide.

##### Option A: Dynamic system prompt via middleware

```python
from typing import Any
from typing_extensions import Annotated

from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain.agents import create_agent
from langchain.agents.middleware.types import AgentState, dynamic_prompt, ModelRequest

# Extend the agent state with your custom fields
class PlanExecuteState(AgentState[Any]):
    plan: list[str]
    past_steps: list[tuple[str, str]] # (step, result)

# Build the system prompt from the current state (v1 idiomatic)
@dynamic_prompt
def build_planner_agent_prompt(req: ModelRequest) -> str:
    state = req.state # type: ignore[assignment]
    current_plan = state.get("plan", [])
    past_steps = state.get("past_steps", [])

    base_prompt = (
        "You are a planning expert responsible for creating and updating plans to accomplish objectives.\n\n"
        "Your tool:\n- create_plan: Create or update the execution plan\n"
    )

    if past_steps:
        base_prompt += "\nCOMPLETED WORK:\n" + "\n".join(
            [f"✓ {step}\n Result: {result[:150]}..." for step, result in past_steps]
        )

    if current_plan:
        base_prompt += "\n\nCURRENT PLAN:\n" + "\n".join(
            [f"{i+1}. {step}" for i, step in enumerate(current_plan)]
        )
    else:
        base_prompt += "\n\nCURRENT PLAN: (none – you need to create one or signal completion)"

    return base_prompt

# Initialize model and create the agent with middleware
model = init_chat_model("openai:gpt-4o-mini")

planner_agent = create_agent(
    model=model,
    tools=[create_plan], # your tool(s)
    state_schema=PlanExecuteState, # expose plan/past_steps in state
    middleware=[build_planner_agent_prompt], # dynamic system prompt
)

# Invocation
result = planner_agent.invoke(
    {
        "messages": [HumanMessage("Plan how to achieve the goal")],
        "plan": [],
        "past_steps": [("researched", "Found three relevant sources.")],
    }
)

```

**What this does** : `@dynamic_prompt` sets `request.system_prompt` each call using the live `state` (which includes your custom fields via `state_schema`). This replicates your v0 function-based prompt builder, but in v1’s middleware form.

##### Option B: Rewrite/trim messages in `before_model`

```python
from langchain.agents.middleware.types import AgentMiddleware, AgentState
from langchain.messages import SystemMessage

class PlannerPromptMiddleware(AgentMiddleware[PlanExecuteState, Any]):
    state_schema = PlanExecuteState

    def before_model(self, state: PlanExecuteState, runtime) -> dict[str, Any] | None:
        # Optionally inject a computed SystemMessage and/or trim history
        current_plan = state.get("plan", [])
        past_steps = state.get("past_steps", [])
        messages = state.get("messages", [])

        base_prompt = "You are a planning expert..." # build same as above
        if past_steps:
            base_prompt += "\n\nCOMPLETED WORK:\n" + "\n".join(
                [f"✓ {s}\n Result: {r[:150]}..." for s, r in past_steps]
            )
        if current_plan:
            base_prompt += "\n\nCURRENT PLAN:\n" + "\n".join(
                [f"{i+1}. {step}" for i, step in enumerate(current_plan)]
            )
        else:
            base_prompt += "\n\nCURRENT PLAN: (none – you need to create one or signal completion)"

        return {
            # Replace message list with fresh system + existing messages
            "messages": [SystemMessage(content=base_prompt), *messages]
        }

planner_agent = create_agent(
    model=model,
    tools=[create_plan],
    state_schema=PlanExecuteState,
    middleware=[PlannerPromptMiddleware()],
)

```

**What this does** : returns a state update that replaces `messages` before the model call, effectively prepending a dynamic `SystemMessage` (and letting you trim/transform history if needed).

---

<div class="post-metadata">

**Author:** ![aerickson-clt](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/aerickson-clt/32/501_2.png) [@aerickson-clt](https://forum.langchain.com/u/aerickson-clt)\
**Post date:** [October 28, 2025, 8:25pm UTC](https://forum.langchain.com/t/migrating-from-langgraph-prebuilt-create-react-agent-to-langchain-agents-create-agent-missing-feature/1985/3 "2025-10-28T20:25:55Z")

</div>

OK, thanks I will look at that. I hadn’t seen the migration guide but was just acting on my IDE telling me that create\_react\_agent was deprecated.
