Building Production-Grade Multi-Agent Workflows with LangGraph
Multi-agent systems represent a paradigm shift in how we build LLM-powered applications. Instead of relying on a single, monolithic agent to handle multiple tools and tasks, we break the system down into discrete, specialized agents. Each agent acts as a specialist with its own prompt, system instructions, and toolsets.
In this guide, we will look at how to orchestrate these agents using LangGraph, a framework designed to build stateful, multi-actor applications with LLMs.
Why LangGraph?
Traditional agent frameworks (like LangChain AgentExecutor or AutoGPT) utilize a simple loop: decide action $\rightarrow$ execute tool $\rightarrow$ observe result $\rightarrow$ repeat. However, this lack of control makes them prone to loops, hallucination, and unpredictable behavior in production.
LangGraph solves this by allowing developers to define workflows as graphs:
Defining the State Graph
A state graph begins with a schema of the shared data. Here is how we define a multi-agent system state in Python:
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
# add_messages accumulates new messages into a list
messages: Annotated[Sequence[BaseMessage], add_messages]
current_agent: str
needs_review: boolConstructing the Agents (Nodes)
Each node in LangGraph is a simple Python function that receives the current state and returns an update. Let's construct a Writer Agent and an Editor Agent:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o")
def writer_agent(state: AgentState):
messages = state["messages"]
prompt = ChatPromptTemplate.from_template(
"You are an expert tech writer. Write a blog draft about the history of AI based on this context: {context}"
)
# Get context from state, invoke model...
response = llm.invoke(prompt.format(context=messages[-1].content))
return {"messages": [response], "current_agent": "editor"}
def editor_agent(state: AgentState):
messages = state["messages"]
# Editorial review logic...
if "looks good" in messages[-1].content.lower():
return {"needs_review": False}
return {"messages": ["Draft needs revision."], "needs_review": True}Wiring it Together with Conditional Edges
We connect our agents by building the graph and adding edges:
from langgraph.graph import StateGraph, END
workflow = StateGraph(AgentState)
# Add Nodes
workflow.add_node("writer", writer_agent)
workflow.add_node("editor", editor_agent)
# Set Entry Point
workflow.set_entry_point("writer")
# Define Routing Edges
def route_after_edit(state: AgentState):
if state["needs_review"]:
return "writer" # Go back to writer for revision
return END
workflow.add_conditional_edges(
"editor",
route_after_edit,
{
"writer": "writer",
END: END
}
)
app = workflow.compile()Bringing Human-in-the-Loop Validation
In production systems, autonomous agent loops can run wild. Adding a human-in-the-loop checkpoint is critical before sensitive actions like publishing or sending emails.
LangGraph supports this out-of-the-box using the interrupt_before parameter:
# Compile the graph with a memory saver and an interrupt
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
app = workflow.compile(
checkpointer=memory,
interrupt_before=["editor"] # Pause execution before editor runs
)When compiled with an interrupt, LangGraph will execute the workflow up to the writer's completion, persist the state in memory, and pause. A human operator can review the generated text and resume execution when approved.
Key Design Patterns for Production
When scaling multi-agent systems, keep these principles in mind: