From ReAct to Multi-Agent: How LangGraph Achieves Seamless Collaboration Among Agents?

As the LangChain ecosystem matures, the AI applications we build are evolving from simple “question-answering bots” to complex “agent collaboration systems.” LangGraph, as a powerful tool in the LangChain ecosystem for building stateful, multi-step AI applications, offers elegant support for multi-agent systems as one of its core values.

Why Multi-Agent?

  • What is an Agent? Essentially, it is an “autonomous entity” that can perceive the environment and act based on strategies to achieve goals. In LangChain/LangGraph, even the simplest dialogue loop can be considered an agent.

    • It possesses capabilities for perception (input), decision-making (strategy/reasoning), action (tools/calls), and learning (memory/updating).
    • Formally, it can be a software role, a robot, a business microservice, or even a combination of an “LLM + tools”.
  • Why Move Towards Multi-Agent? Single agents encounter three types of bottlenecks when dealing with complex problems:

    • Tool Overload: Too many tools lead to difficulties in deciding “which one to call”.
    • Context Burden: Long dialogues lead to reasoning degradation.
    • Domain Specialization Conflicts: Planning, retrieval, computation, execution, and other specialized capabilities are hard to balance in a single prompt. The solution is “modularization + specialization”: breaking the system into multiple small agents with single responsibilities, combined into medium/large systems.
  • Core Benefits of Multi-Agent

    • Modularity: Lower costs for development, testing, and maintenance.
    • Specialization: The robustness of expert agents is stronger.
    • Controllability: Communication/hand-off paths and strategies can be explicitly defined, rather than left entirely to the LLM’s improvisation.

Five Typical Architectures

  • Fully-connected Network Any agent can communicate with other agents. Flexible but prone to “flooding”, suitable for exploratory and loosely constrained scenarios.

  • Supervisor Introduces a “scheduling/routing” agent that decides which expert to call. Clear structure, beneficial for auditing and rate limiting.

  • Supervisor (Tool Call Variant) Abstracts experts as “tools”, with the supervisor as a ReAct agent routing through tool calls. Facilitates rapid implementation.

  • Hierarchical Multiple teams each have supervisors, with a top-level supervisor coordinating. Suitable for large-scale systems or multiple product lines.

  • Custom Workflow (Deterministic/Dynamic Mix) Some edges are in fixed order, while others are dynamically routed by the LLM using <span>Command</span>. A common compromise in engineering.

If you are unsure which architecture to choose, start with the “Supervisor (Tool Call)” and iterate according to complexity.

Handoffs & Command

  • Handoff Concept After an agent completes its current responsibilities, it decides to “end/continue/hand off to others”. The key to handoff is explicit description:
    • Target Agent: <span>goto</span>
    • Payload (State Update): <span>update</span>
    • Graph Domain (Where it takes effect): <span>graph</span>, commonly the current graph or <span>Command.PARENT</span> (jumping back from a child graph to the parent graph)
  • Minimal Command Mode
    • Agent node functions return <span>Command</span>, used to control the next routing step; otherwise, return state updates to end the current round.
    • In tool call scenarios, be sure to insert paired “tool result messages” to meet the protocol constraints of most LLM providers (each <span>ai_msg</span> tool call must be followed by a <span>tool</span> message).
  • Common Handoff Techniques
    • Decide directly in the agent node, returning <span>Command</span>.
    • Wrap the handoff as a “tool”, triggered by the LLM as a tool call (significantly improves uniformity).
    • When handing off to other agents in the parent graph from a child graph, set <span>graph=Command.PARENT</span>.

Agent Communication and State Design

  • Unified State (Shared Messages) Every step in the graph receives and produces <span>state</span>, usually containing <span>messages</span>. Sharing the complete “draft” (reasoning process) can enhance overall reasoning capability, but be wary of context explosion.

  • Heterogeneous State (Private Draft + Shared Summary) Each agent maintains its own format of internal state, interfacing with the parent graph state through input/output conversions.

    • Advantages: Clear boundaries, allows for “minimal information sharing”.
    • Technique: During handoff, only share “the last AI response + tool receipt”, rather than the entire draft.
  • Tool Calls and Payload When the supervisor acts as a ReAct node, the parameters of the tool are the payload. LangGraph supports injecting the parent graph state into the tool (e.g., <span>InjectedState</span>), achieving “memory-enabled handoffs”.

Comparison of Three Handoff Modes and Minimal Code Snippets

1. Direct Command Handoff (Node Routing)

  • Applicable: Network architectures with two or a few agents, simple logic, clear routing.
  • Key Point: When <span>ai_msg.tool_calls</span> is not empty, insert the tool result message, then <span>goto</span> the next agent.
from typing_extensions import Literal
from langgraph.types import Command
from langgraph.graph import MessagesState
from langchain_core.tools import tool

@tool
def transfer_to_multiplication_expert():
    """Seek help from the multiplication agent (only to indicate handoff intention)"""
    return

def addition_expert(state: MessagesState) -> Command[Literal["multiplication_expert", "__end__"]]:
    system_prompt = "You are the addition expert. If multiplication is needed, please complete addition first, then hand off."
    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    ai_msg = model.bind_tools([transfer_to_multiplication_expert]).invoke(messages)

    if ai_msg.tool_calls:
        tool_call_id = ai_msg.tool_calls[-1]["id"]
        tool_msg = {"role": "tool", "content": "Successfully handed off", "tool_call_id": tool_call_id}
        return Command(goto="multiplication_expert", update={"messages": [ai_msg, tool_msg]})

    return {"messages": [ai_msg]}
  • Common Pitfalls

    • Missing Tool Result Message: Leads to provider errors or context desynchronization.
    • Infinite Handoffs: Set step limits or termination conditions.
  • It is recommended to introduce step budgets:

MAX_STEPS = 8
def guard_and_return(cmd_or_update, state):
    steps = state.get("steps", 0) + 1
    if steps > MAX_STEPS:
        return {"messages": [{"role": "assistant", "content": "Exceeded step budget, ending."}]}
    if isinstance(cmd_or_update, Command):
        cmd_or_update.update = {**cmd_or_update.update, "steps": steps}
        return cmd_or_update
    return {"messages": cmd_or_update["messages"], "steps": steps}

2. Handoff Tool

  • Applicable: Each agent is a “subgraph”, handing control back to the parent graph and routing to the target agent via tools.
  • Key Point: Return <span>Command(goto=..., graph=Command.PARENT, update=...)</span> in the tool.
from typing import Annotated
from langchain_core.tools.base import InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langchain_core.tools import tool
from langgraph.types import Command

def make_handoff_tool(*, agent_name: str):
    tool_name = f"transfer_to_{agent_name}"

    @tool(tool_name)
    def handoff_to_agent(
        state: Annotated[dict, InjectedState],
        tool_call_id: Annotated[str, InjectedToolCallId],
    ):
        tool_message = {
            "role": "tool",
            "content": f"Successfully handed off to {agent_name}",
            "name": tool_name,
            "tool_call_id": tool_call_id,
        }
        return Command(
            goto=agent_name,
            graph=Command.PARENT,
            update={"messages": state["messages"] + [tool_message]},
        )

    return handoff_to_agent
  • Advantages:
    • Routing logic is unified in the “tool layer”, making agent code cleaner.
    • Facilitates internal agreements on handoff standards and auditing.

3. Pre-built ReAct Agent (create_react_agent)

  • Applicable: Scenarios that do not require custom ToolNode/loops, for rapid setup.
  • Key Point: Just add the handoff tool to the tool list.
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    return a * b

addition_expert = create_react_agent(
    model,
    [add, make_handoff_tool(agent_name="multiplication_expert")],
    prompt="You are the addition expert. Hand off to the multiplication expert when necessary.",
)

multiplication_expert = create_react_agent(
    model,
    [multiply, make_handoff_tool(agent_name="addition_expert")],
    prompt="You are the multiplication expert. Hand off to the addition expert when necessary.",
)

Function API

When you want “each agent to communicate with all other agents” but want to temporarily avoid defining nodes/edges in the graph, you can use the LangGraph Function API:

  • Use <span>@entrypoint()</span> to define the main process loop;
  • Use <span>@task</span> to encapsulate calls to each agent;
  • Determine the handoff target by identifying the most recent <span>AIMessage.tool_calls</span>;
  • On tools, use <span>@tool(return_direct=True)</span>, so that once the agent calls the handoff tool, it immediately “jumps out” of its own loop, returning control to the main process.

In engineering, it is recommended to include in the main loop:

  • Step limits, exception retries, and handoff tool whitelist checks (to prevent LLM from “imagining” undefined tool names).

Key Points for Engineering Implementation

  • Prompts

    • “First do your own duties, then hand off” should be included in the system prompt.
    • Clearly define output formats, whether in Chinese, and whether structured information (JSON/Markdown) is needed.
  • Termination Conditions and Budget Control

    • Set step budgets, call budgets, and time budgets; exceeding limits should trigger <span>__end__</span>.
    • Track <span>steps/cost</span> in the state and update in each round.
  • State Design and Memory Management

    • Distinguish between “shared messages” and “private drafts”; use converters to expose only necessary context.
    • Introduce “summary tools/cropping strategies” (e.g., only keep the last N rounds + summary).
  • Tool Safety

    • Strong typing of tool parameters (Pydantic validation);
    • Add “confirmation tools” (dual review type) before important operations;
    • Set a whitelist for “handoff tool names”, rejecting unknown routes.
  • Observability and Debugging

    • Use <span>graph.stream(..., subgraphs=True)</span> to subscribe to events;
    • Unify <span>pretty_print_messages</span> to align and print <span>ai/tool</span> message streams;
    • Log the corresponding relationships of <span>tool_call_id</span> for easier problem retrospection.
  • Performance and Parallelism

    • Subtasks that can be “divided and conquered” should use parallel map, then aggregate reduce;
    • Enable asynchronous for “cold tools” (slow IO);
    • Stress test different architectures for latency/cost distribution, forming a “selection matrix”.
  • Stability and Testing

    • Construct “golden sample dialogue flows” to regression test handoff paths and final answers;
    • Add <span>try/except</span> + “safety degradation” (e.g., switch to local rules/caching) at hotspots of exceptions.

Common Errors and Quick Troubleshooting

  • Missing Tool Result Message: Each <span>ai_msg</span> tool call must be followed by one <span>tool</span> message.
  • Child Graph Handoff Not Specified <span>Command.PARENT</span>: Leads to handoff being “stuck in the child graph”.
  • <span>update</span> Key Name or Structure Mistake: <span>{"messages": [...]}</span> must not be missing.
  • tool_call_id Mismatch: The ID in the tool receipt must correspond to the call just triggered.
  • START Edge Not Set or Incorrect Routing Name: <span>builder.add_edge(START, "node")</span><span> must not be omitted.</span>
  • Infinite Loop: Missing step control, or prompts not enforcing “first do your own, then hand off”.

Conclusion

Multi-agent systems are not about “putting larger models with more prompts”, but about “breaking complex problems into manageable autonomous units” and orchestrating them through observable and controllable handoff protocols. Command is the core of orchestration; state is the medium of communication; tools are the interface for action; hierarchy/network/supervisor/custom workflows are architectural options. When running code, it is recommended to first run the simplest mode end-to-end, then introduce handoff tools for unified routing, and finally evolve to hierarchical or mixed architectures according to scale.

We have created a WeChat group for LangGraph & LangChain agents. Interested friends can like and follow to join the group for discussions.

Leave a Comment