Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cookbook/langgraph-vendor-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ START → planner → executor (react) → reviewer → (loop or compiler) → E
```

- **Planner**: Reads the task and produces a JSON plan of 4-5 steps
- **Executor**: A `create_react_agent` subgraph that handles multi-round tool calling per step
- **Executor**: A LangGraph react subgraph that handles multi-round tool calling per step
- **Reviewer**: Marks step done, routes back to executor or forward to compiler
- **Compiler**: Combines all step results into the final formatted response

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@

import json
import operator
from importlib import import_module
from typing import Annotated, Any, TypedDict

from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.tools import BaseTool
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import create_react_agent

# Prefer the new langchain.agents.create_agent; fall back to the legacy
# langgraph.prebuilt.create_react_agent for older installs.
_USE_NEW_CREATE_AGENT = False
try:
_create_agent_fn = import_module("langchain.agents").create_agent
_USE_NEW_CREATE_AGENT = True
except (ModuleNotFoundError, AttributeError):
_create_agent_fn = import_module("langgraph.prebuilt").create_react_agent


class PlanStep(TypedDict):
Expand Down Expand Up @@ -84,17 +93,17 @@ def build_plan_and_execute_graph(
else:
exec_tools = list(tools)

react_executor = create_react_agent(
model,
exec_tools,
prompt=SystemMessage(
content=(
"You are an execution agent. Use the available tools to "
"complete the step you are given. Call as many tools as needed. "
"After gathering data, summarize your findings."
)
),
executor_prompt = SystemMessage(
content=(
"You are an execution agent. Use the available tools to "
"complete the step you are given. Call as many tools as needed. "
"After gathering data, summarize your findings."
)
)
if _USE_NEW_CREATE_AGENT:
react_executor = _create_agent_fn(model, tools=exec_tools, system_prompt=executor_prompt)
else:
react_executor = _create_agent_fn(model, exec_tools, prompt=executor_prompt)

# ---- planner ----
def planner(state: PlanExecState) -> dict[str, Any]:
Expand Down
82 changes: 82 additions & 0 deletions cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Tests for create_agent / create_react_agent compatibility in plan_and_execute."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from langchain_core.messages import SystemMessage


def test_build_graph_calls_create_fn_with_correct_kwarg() -> None:
"""The executor node should pass system_prompt= or prompt= depending on the resolved API."""
from langgraph_vendor_agent import plan_and_execute as module

mock_fn = MagicMock(return_value=MagicMock())
original_fn = module._create_agent_fn
original_flag = module._USE_NEW_CREATE_AGENT

# --- new API path (system_prompt=) ---
module._create_agent_fn = mock_fn
module._USE_NEW_CREATE_AGENT = True
try:
model = MagicMock()
tool = MagicMock()
tool.name = "server__tool1"
tools = [tool]
module.build_plan_and_execute_graph(model, tools, safe_tool_filter=False)
_, kwargs = mock_fn.call_args
assert "system_prompt" in kwargs
assert isinstance(kwargs["system_prompt"], SystemMessage)
finally:
module._create_agent_fn = original_fn
module._USE_NEW_CREATE_AGENT = original_flag


def test_build_graph_calls_legacy_fn_with_prompt_kwarg() -> None:
"""When falling back to create_react_agent, prompt= should be used."""
from langgraph_vendor_agent import plan_and_execute as module

mock_fn = MagicMock(return_value=MagicMock())
original_fn = module._create_agent_fn
original_flag = module._USE_NEW_CREATE_AGENT

module._create_agent_fn = mock_fn
module._USE_NEW_CREATE_AGENT = False
try:
model = MagicMock()
tool = MagicMock()
tool.name = "server__tool1"
tools = [tool]
module.build_plan_and_execute_graph(model, tools, safe_tool_filter=False)
_, kwargs = mock_fn.call_args
assert "prompt" in kwargs
assert isinstance(kwargs["prompt"], SystemMessage)
finally:
module._create_agent_fn = original_fn
module._USE_NEW_CREATE_AGENT = original_flag


def test_import_fallback_to_langgraph_prebuilt() -> None:
"""When langchain.agents has no create_agent, we fall back to langgraph.prebuilt."""
from importlib import import_module as real_import_module

def fake_import_module(name: str) -> object:
if name == "langchain.agents":
mod = MagicMock(spec=[]) # spec=[] means no attributes
del mod.create_agent # ensure AttributeError
return mod
return real_import_module(name)

with patch("langgraph_vendor_agent.plan_and_execute.import_module", fake_import_module):
# Re-run the import logic
try:
_create_agent_fn = getattr(fake_import_module("langchain.agents"), "create_agent")
use_new = True
except (ModuleNotFoundError, AttributeError):
_create_agent_fn = getattr(real_import_module("langgraph.prebuilt"), "create_react_agent")
use_new = False

assert not use_new
# The fallback should resolve to the real create_react_agent
from langgraph.prebuilt import create_react_agent
assert _create_agent_fn is create_react_agent
Loading