From 3a292211f755af9d89f2cab8c4c4a3ed4a7f86c4 Mon Sep 17 00:00:00 2001 From: sachin-patro Date: Sun, 5 Apr 2026 22:44:22 -0400 Subject: [PATCH 1/3] Update langgraph vendor agent to support new create_agent API The LangChain agents API has moved from langgraph.prebuilt.create_react_agent to langchain.agents.create_agent with a different kwarg (system_prompt= instead of prompt=). Add an import fallback so the cookbook works with both old and new LangChain versions. Co-Authored-By: Claude Opus 4.6 (1M context) --- cookbook/langgraph-vendor-agent/README.md | 2 +- .../plan_and_execute.py | 31 ++++--- .../tests/test_create_agent_compat.py | 83 +++++++++++++++++++ 3 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py diff --git a/cookbook/langgraph-vendor-agent/README.md b/cookbook/langgraph-vendor-agent/README.md index 313c8b7..8245d1e 100644 --- a/cookbook/langgraph-vendor-agent/README.md +++ b/cookbook/langgraph-vendor-agent/README.md @@ -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 diff --git a/cookbook/langgraph-vendor-agent/src/langgraph_vendor_agent/plan_and_execute.py b/cookbook/langgraph-vendor-agent/src/langgraph_vendor_agent/plan_and_execute.py index bf8304b..ee7fe53 100644 --- a/cookbook/langgraph-vendor-agent/src/langgraph_vendor_agent/plan_and_execute.py +++ b/cookbook/langgraph-vendor-agent/src/langgraph_vendor_agent/plan_and_execute.py @@ -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): @@ -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]: diff --git a/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py new file mode 100644 index 0000000..df962a5 --- /dev/null +++ b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py @@ -0,0 +1,83 @@ +"""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.""" + import importlib + 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 = fake_import_module("langchain.agents").create_agent + use_new = True + except (ModuleNotFoundError, AttributeError): + _create_agent_fn = 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 From a5f38b77cdfcebadd9931a835d6262f311262831 Mon Sep 17 00:00:00 2001 From: sachin-patro Date: Mon, 6 Apr 2026 10:05:28 -0400 Subject: [PATCH 2/3] Remove unused import to fix ruff lint Co-Authored-By: Claude Opus 4.6 (1M context) --- .../langgraph-vendor-agent/tests/test_create_agent_compat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py index df962a5..b9e8d3d 100644 --- a/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py +++ b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py @@ -58,7 +58,6 @@ def test_build_graph_calls_legacy_fn_with_prompt_kwarg() -> None: def test_import_fallback_to_langgraph_prebuilt() -> None: """When langchain.agents has no create_agent, we fall back to langgraph.prebuilt.""" - import importlib from importlib import import_module as real_import_module def fake_import_module(name: str) -> object: From 1544f90433a55ea97c5328ff439bb29578160922 Mon Sep 17 00:00:00 2001 From: sachin-patro Date: Mon, 6 Apr 2026 10:26:34 -0400 Subject: [PATCH 3/3] Fix mypy attr-defined error in compat test Use getattr() instead of direct attribute access so mypy doesn't complain about accessing .create_agent on an object return type. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../langgraph-vendor-agent/tests/test_create_agent_compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py index b9e8d3d..58d83cf 100644 --- a/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py +++ b/cookbook/langgraph-vendor-agent/tests/test_create_agent_compat.py @@ -70,10 +70,10 @@ def fake_import_module(name: str) -> object: with patch("langgraph_vendor_agent.plan_and_execute.import_module", fake_import_module): # Re-run the import logic try: - _create_agent_fn = fake_import_module("langchain.agents").create_agent + _create_agent_fn = getattr(fake_import_module("langchain.agents"), "create_agent") use_new = True except (ModuleNotFoundError, AttributeError): - _create_agent_fn = real_import_module("langgraph.prebuilt").create_react_agent + _create_agent_fn = getattr(real_import_module("langgraph.prebuilt"), "create_react_agent") use_new = False assert not use_new