Clearwateranalytics.com is now cwan.com. Your Clearwater and Enfusion credentials have not changed.
Blog
26 min read

Building multi-agent systems with LangGraph

huge
By Rany ElHousieny

Introduction to LangGraph

In the previous article, we explored the concept of multi-agent systems and how Clearwater Analytics pioneered this approach with CWIC Flow. We also demonstrated how to build a simple multi-agent system without specialized frameworks. Now, let’s explore LangGraph, a powerful library that provides structured workflows for building sophisticated multi-agent systems.

LangGraph is a library built on top of LangChain that allows you to create stateful, multi-agent applications with Large Language Models (LLMs). It provides a structured way to compose chains into graphs, manage state across multiple LLM calls, and build complex workflows with conditional routing.

Understanding the Basics of LangGraph

Before diving into implementation, let’s understand some key concepts in LangGraph:

  • State: The information that persists between different steps of your graph
  • Nodes: Functions that process the state and return a new state
  • Edges: Define how the state flows between nodes
  • Graph: The overall workflow that connects nodes via edges

This structured approach allows for more complex interactions between agents, including conditional routing, feedback loops, and persistent memory.

Visual LangGraph Workflow

Let’s visualize how a basic LangGraph works:

This diagram illustrates the key concepts:

  • State (blue boxes): Contains data that flows through the graph
  • Nodes (purple boxes): Functions that process state and produce new state
  • Edges (arrows): Define the flow from one node to the next
  • Entry/Exit Points (green ovals): Where the workflow begins and ends

Environment Setup

First, let’s make sure we have all the necessary packages installed. If you’re running this code for the first time, you’ll need to install the required packages.

Install required packages

# Install required packages with latest versions
!pip install langchain langchain-openai langgraph python-dotenv

Now, let’s set up our environment variables. We’ll need an API key for OpenAI to use their models. Create a .env file in the same directory as this notebook with your OpenAI API key:

OPENAI_API_KEY=your_api_key_here
# Load environment variables from .env file
import os
from dotenv import load_dotenv
load_dotenv()  # Load API keys from .env file
# Verify that the API key is loaded
if os.getenv("OPENAI_API_KEY") is None:
    print("Warning: OPENAI_API_KEY not found in environment variables.")
else:
    print("OPENAI_API_KEY found in environment variables.")

Importing Required Libraries

Now, let’s import the libraries we’ll need for our multi-agent system:

# Import necessary libraries
from typing import Dict, List, TypedDict, Annotated, Sequence, Any, Optional, Literal, Union
import json
# Modern imports for langchain and langgraph
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

Creating a Simple LLM Helper Function

Before we dive into multi-agent systems, let’s create a simple helper function to interact with an LLM. This will help us understand the basic building blocks of LangGraph.

def ask_llm(prompt, model="gpt-4o", temperature=0.7):
    """A simple function to get a response from an LLM."""
    # Create a ChatOpenAI instance
    llm = ChatOpenAI(model=model, temperature=temperature)
    # Create a message with the prompt
    messages = [HumanMessage(content=prompt)]
    # Get a response from the LLM
    response = llm.invoke(messages)
    # Return the content of the response
    return response.content
# Let's test our function
response = ask_llm("What is LangGraph and how does it relate to LangChain?")
print(response)

Response output:

LangGraph is an open-source library designed for building and managing complex applications that use large language models (LLMs). It is a framework that offers tools and components to create, customize, and execute workflows involving LLMs, making it easier to integrate these models into various applications. LangGraph is closely related to LangChain, as it can be thought of as an extension or complementary toolset to LangChain. LangChain is another framework specifically aimed at developing applications with LLMs, focusing on chaining together different components or tasks to create more complex workflows. LangGraph builds upon the principles of LangChain by providing additional functionalities and a more structured way to define and manage LLM-based applications. In essence, while LangChain focuses on the chaining of language model-powered tasks, LangGraph provides a more comprehensive framework that includes graph-based execution flows, enhanced control over tasks, and improved management of the interactions between different components. Together, these tools can be used to streamline the development and deployment of sophisticated applications leveraging the capabilities of LLMs.

Let’s create a very simple graph with just one agent to understand these concepts:

# Define the state type
class SimpleState(TypedDict):
    messages: List[BaseMessage]  # The conversation history
    next: str  # Where to go next in the graph
# Define a simple agent node
def simple_agent(state: SimpleState) -> SimpleState:
    """A simple agent that responds to the last message."""
    messages = state["messages"]
    llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
    response = llm.invoke(messages)
    return {"messages": messages + [response], "next": "output"}
# Define an output node
def output(state: SimpleState):
    """Return the final state. This marks the end of the workflow."""
    return {"messages": state["messages"], "next": END}
# Build the graph
def build_simple_graph():
    """Build a simple LangGraph workflow."""
    # Create a new graph
    workflow = StateGraph(SimpleState)
    # Add nodes
    workflow.add_node("agent", simple_agent)
    workflow.add_node("output", output)
    # Add edges
    workflow.add_edge("agent", "output")
    # Set the entry point
    workflow.set_entry_point("agent")
    # Compile the graph
    return workflow.compile()

Now, let’s run our simple graph:

# Create the graph
simple_graph = build_simple_graph()
# Initialize the state with a test message
initial_state = {
    "messages": [HumanMessage(content="Explain what a multi-agent system is in simple terms.")],
    "next": ""
}
# Run the graph
result = simple_graph.invoke(initial_state)
# Print the conversation
for message in result["messages"]:
    if isinstance(message, HumanMessage):
        print(f"Human: {message.content}")
    elif isinstance(message, AIMessage):
        print(f"AI: {message.content}")

Output:

Human: Explain what a multi-agent system is in simple terms.
AI: A multi-agent system is a collection of independent “agents” that interact with each other. Think of each agent as an individual with its own ability to make decisions, similar to how people can think and act on their own. These agents work together or compete to achieve specific goals or complete tasks.
In simple terms, imagine a group of robots that clean your house. Each robot is an agent. One might vacuum the floors, another might dust the furniture, and another might take out the trash. They all work independently but communicate and coordinate with each other to make sure the entire house is cleaned efficiently. This is the essence of a multi-agent system: multiple entities working together, often in a coordinated manner, to perform complex tasks or solve problems.

Zoom image will be displayed

This diagram shows:

  1. The overall flow from Start → Agent → Output → End
  2. The Agent Process subgraph showing the internal steps of the simple_agent function
  3. The State subgraph showing the components of the SimpleState TypedDict
  4. Color-coded nodes to distinguish different types of components

The workflow starts with an initial state containing messages and next fields, processes it through the Agent node which adds a response using the LLM, then passes it to the Output node which returns the final state, ending the workflow.

Now, let’s understand what just happened:

  1. We defined a state schema (SimpleState) with the fields we want to track.
  2. We created nodes for the agent and output functions.
  3. We connected these nodes with edges to define the flow of information.
  4. We set up an entry point and compiled the graph.
  5. We initialized the state with a human message and ran the graph.

This simple example illustrates the basic concepts of LangGraph. In real-world scenarios, you can create much more complex graphs with multiple nodes and conditional edges.

What’s Next

In the next step, we’ll build our first specialized agent: the researcher agent. We’ll dive deeper into agent design patterns, learn how to define agent roles and capabilities, and see how to make the agent perform research tasks effectively.

Creating a Specialized Researcher Agent

In the previous step, we set up our environment and created a simple graph with a single agent. Now, we’ll build our first specialized agent for our collaborative research assistant system: the researcher agent.

In this step, we’ll focus on building the Researcher Agent — the component responsible for gathering and analyzing information.

What is a Researcher Agent?

A Researcher Agent is an AI system designed to thoroughly investigate topics and provide comprehensive, well-structured information. It serves as the information-gathering component in our multi-agent system with capabilities to:

  1. Understand research queries deeply
  2. Collect relevant information comprehensively
  3. Organize findings in a structured, accessible format
  4. Present information objectively and accurately
  5. Identify limitations and gaps in available information

Building a Modern Researcher Agent with LangGraph

The latest versions of LangGraph and LangChain offer improved approaches to building agents. Let’s explore how to create a Researcher Agent using these modern tools.

Step 1: Setting Up the Environment

First, we need to import the necessary libraries. Note the modern import paths for LangChain and LangGraph:

from typing import Dict, List, TypedDict, Any, Optional
import json
import os
from dotenv import load_dotenv
# Modern imports for langchain and langgraph
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
# Load environment variables
load_dotenv()

Step 2: Defining the Researcher’s Role

A clear system prompt is crucial for shaping the agent’s behavior:

RESEARCHER_SYSTEM_PROMPT = """
You are a skilled research agent tasked with gathering comprehensive information on a given topic. 
Your responsibilities include:
1. Analyzing the research query to understand what information is needed
2. Conducting thorough research to collect relevant facts, data, and perspectives
3. Organizing information in a clear, structured format
4. Ensuring accuracy and objectivity in your findings
5. Citing sources or noting where information might need verification
6. Identifying potential gaps in the information
Present your findings in a well-structured format with clear sections and bullet points where appropriate.
Your goal is to provide comprehensive, accurate, and useful information that fully addresses the research query.
"""

Step 3: Creating the Researcher Function

Now we’ll implement the researcher agent as a function:

def create_researcher_agent(model="gpt-4o", temperature=0.7):
    """Create a researcher agent using the specified LLM."""
    # Initialize the model
    llm = ChatOpenAI(model=model, temperature=temperature)
    def researcher_function(messages):
        """Function that processes messages and returns a response from the researcher agent."""
        # Add the system prompt if it's not already there
        if not messages or not isinstance(messages[0], SystemMessage) or messages[0].content != RESEARCHER_SYSTEM_PROMPT:
            messages = [SystemMessage(content=RESEARCHER_SYSTEM_PROMPT)] + (messages if isinstance(messages, list) else [])
        # Get response from the LLM
        response = llm.invoke(messages)
        return response
    return researcher_function

Integrating with LangGraph: The Modern Approach

The latest version of LangGraph uses TypedDict for state management, providing better type safety and clearer state structure:

# Define the state type for our research workflow
class ResearchState(TypedDict):
    """Type definition for our research workflow state."""
    messages: List[BaseMessage]  # The conversation history
    query: str  # The research query
    research: Optional[str]  # The research findings
    next: Optional[str]  # Where to go next in the graph

Creating a Researcher Node

Next, we implement a node for our LangGraph workflow:

def researcher_node(state: ResearchState) -> ResearchState:
    """A node in our graph that performs research on the query."""
    # Get the query from the state
    query = state["query"]
    # Create a message specifically for the researcher
    research_message = HumanMessage(content=f"Please research the following topic thoroughly: {query}")
    # Get the researcher agent
    researcher = create_researcher_agent()
    # Get response from the researcher agent
    response = researcher([research_message])
    # Update the state with the research findings
    new_messages = state["messages"] + [research_message, response]
    # Return the updated state
    return {
        **state,
        "messages": new_messages,
        "research": response.content,
        "next": "output"  # In a multi-agent system, this would go to the next agent
    }

Building the Research Workflow

Now we can build a complete LangGraph workflow:

def build_research_graph():
    """Build a simple research workflow using LangGraph."""
    # Create a new graph with our state type
    workflow = StateGraph(ResearchState)
    # Add nodes
    workflow.add_node("researcher", researcher_node)
    workflow.add_node("output", output_node)
    # Add edges
    workflow.add_edge("researcher", "output")
    # Set the entry point
    workflow.set_entry_point("researcher")
    # Compile the graph
    return workflow.compile()

Here’s a visual representation of our workflow:

Enhancing the Researcher Agent with Structured Output

For better integration with other agents, we can enhance our researcher to provide structured output:

ENHANCED_RESEARCHER_PROMPT = """
You are a skilled research agent tasked with gathering comprehensive information on a given topic. 
Your responsibilities include:
1. Analyzing the research query to understand what information is needed
2. Conducting thorough research to collect relevant facts, data, and perspectives
3. Organizing information in a clear, structured format
4. Ensuring accuracy and objectivity in your findings
5. Citing sources or noting where information might need verification
6. Identifying potential gaps in the information
Present your findings in the following structured format:
SUMMARY: A brief overview of your findings (2-3 sentences)
KEY POINTS:
- Point 1
- Point 2
- Point 3
DETAILED FINDINGS:
1. [Topic Area 1]
   - Details and explanations
   - Supporting evidence
   - Different perspectives if applicable
2. [Topic Area 2]
   - Details and explanations
   - Supporting evidence
   - Different perspectives if applicable
GAPS AND LIMITATIONS:
- Identify any areas where information might be incomplete
- Note any contradictions or areas of debate
- Suggest additional research that might be needed
Your goal is to provide comprehensive, accurate, and useful information that fully addresses the research query.
"""

This structured format makes it easier for subsequent agents (like critics and writers) to process and build upon the researcher’s findings.

A More Robust Research Workflow

With our enhanced researcher agent, we can create a more robust workflow:

We implement this workflow with a specialized state type:

class EnhancedResearchState(TypedDict):
    """Enhanced type definition for our research workflow state."""
    messages: List[BaseMessage]
    query: str
    structured_research: Optional[str]
    next: Optional[str]

Researcher Agent in Action: An Example Query

Let’s see how our enhanced researcher agent handles a complex query:

Query: “What are the latest advancements in renewable energy technologies?”

Response (structured format):

SUMMARY: Recent advancements in renewable energy technologies include significant improvements in solar cell efficiency, innovative wind turbine designs, enhanced energy storage solutions, breakthroughs in green hydrogen production, and developments in advanced biofuels.
KEY POINTS:
– Solar PV technology has achieved record efficiency levels with perovskite-silicon tandem cells exceeding 30% efficiency
– Floating offshore wind turbines are expanding deployment possibilities in deeper waters
– Grid-scale battery storage costs have declined dramatically while energy density has increased
– Green hydrogen production via electrolysis has become more efficient and economically viable
– Advanced biofuels from non-food biomass sources are approaching commercial viability
DETAILED FINDINGS:
1. Solar Energy Advancements
— Perovskite solar cells have rapidly improved, with lab efficiencies exceeding 25%
— Perovskite-silicon tandem cells have reached 31.25% efficiency, approaching the theoretical limit
— Bifacial solar panels that capture light from both sides increase energy yield by 5–30%
— Building-integrated photovoltaics (BIPV) have advanced with transparent solar cells for windows
— Agrivoltaics systems that combine farming with solar production have shown promising results
2. Wind Energy Innovations
— Larger turbines with blades exceeding 100 meters in length have increased capacity factors
— Floating offshore wind platforms enable deployment in waters 60+ meters deep
— Airborne wind energy systems (kite and drone-based) are in advanced testing
— Bladeless wind turbines using oscillation technology reduce wildlife impacts and maintenance costs
— Digital twin technology and AI improving predictive maintenance and output optimization
[additional sections omitted for brevity]
GAPS AND LIMITATIONS:
– Long-duration energy storage (10+ hours) remains a significant challenge despite advancements
– Most breakthrough technologies mentioned are still scaling from lab to commercial deployment
– Cost comparisons between emerging technologies often lack standardized metrics
– Environmental impacts of some newer technologies (like certain rare earth mining for magnets) need further study
– Regional differences in technology adoption and policy support create uneven advancement landscapes

Benefits of the Modern Approach

The modern approach to building researcher agents with LangGraph offers several advantages:

  1. Type Safety: TypedDict provides better type checking and documentation
  2. Structured State: Clearly defined state structure improves maintainability
  3. Modular Design: Functions and components can be easily reused and tested
  4. Modern APIs: Using the latest LangChain and LangGraph APIs ensures compatibility

Next Steps: Building a Complete Multi-Agent System

The researcher agent is just the first step in building a comprehensive multi-agent system. In future articles, we’ll explore:

  1. Building a Critic Agent to evaluate and challenge the researcher’s findings
  2. Creating a Writer Agent to synthesize information into cohesive content
  3. Implementing a Coordinator Agent to manage workflow between agents
  4. Developing advanced routing logic for more dynamic agent interactions

Building a modern researcher agent with LangGraph provides a powerful foundation for multi-agent systems. By leveraging the latest APIs and structured approaches to agent development, we can create more effective, maintainable, and powerful AI systems that collaborate to solve complex problems.

The researcher agent we’ve built demonstrates how specialized agents can be designed to fulfill specific roles within a larger system — gathering and organizing information that can then be evaluated, refined, and presented by other agents in the workflow.

As we continue to explore multi-agent systems, we’ll see how these specialized components can work together to create AI solutions that surpass what any single model could accomplish alone.

Adding a Critic Agent

In the previous steps, we set up our environment and created a researcher agent. Now, we’ll add a second specialized agent to our collaborative research assistant system: the critic agent.

The Concept of a Critic Agent

A Critic Agent is designed to evaluate, challenge, and improve the work of other agents. Just as human work benefits from peer review and constructive criticism, AI outputs can be significantly enhanced by dedicated evaluation.

The Critic Agent serves critical functions in a multi-agent workflow:

  1. Quality Assurance: Identifies inaccuracies, gaps, or flaws in the research
  2. Bias Detection: Highlights potential biases in the provided information
  3. Completeness Check: Ensures all aspects of a topic are adequately covered
  4. Alternative Perspectives: Introduces different viewpoints or interpretations
  5. Constructive Feedback: Provides actionable suggestions for improvement

Modern Implementation with LangGraph

The latest versions of LangGraph and LangChain provide powerful tools for building more robust and maintainable multi-agent systems. Let’s explore how to implement a Critic Agent using these modern frameworks.

Core Components

Our implementation focuses on three key elements:

  1. Type Safety: Using TypedDict for state management
  2. Structured Output: Enhancing the critic with structured JSON feedback
  3. Modular Design: Creating reusable agent functions

The Critic’s System Prompt

The foundation of our Critic Agent is a well-crafted system prompt that establishes its role and responsibilities:

CRITIC_SYSTEM_PROMPT = """
You are a Critic Agent, part of a collaborative research assistant system. Your role is to evaluate 
and challenge information provided by the Researcher Agent to ensure accuracy, completeness, and objectivity.
Your responsibilities include:
1. Analyzing research findings for accuracy, completeness, and potential biases
2. Identifying gaps in the information or logical inconsistencies
3. Asking important questions that might have been overlooked
4. Suggesting improvements or alternative perspectives
5. Ensuring that the final information is balanced and well-rounded
Be constructive in your criticism. Your goal is not to dismiss the researcher's work, but to strengthen it.
Format your feedback in a clear, organized manner, highlighting specific points that need attention.
Remember, your ultimate goal is to ensure that the final research output is of the highest quality possible.
"""

Modern State Management

A significant improvement in recent versions of LangGraph is better state management through TypedDict:

class CollaborativeResearchState(TypedDict):
    """State type for our collaborative research assistant."""
    messages: List[BaseMessage]  # The conversation history
    next: Optional[str]  # Where to go next in the graph

This approach provides clear type hints and makes the code more maintainable.

The Workflow: Researcher → Critic

Let’s visualize the basic flow of information between our Researcher and Critic agents:

Zoom image will be displayed

Our LangGraph implementation defines this workflow with modern syntax:

def build_collaborative_research_assistant():
    """Build a collaborative research assistant with researcher and critic agents."""
    # Create a new graph with our state type
    workflow = StateGraph(CollaborativeResearchState)
    # Add nodes
    workflow.add_node("researcher", researcher_node)
    workflow.add_node("critic", critic_node)
    workflow.add_node("output", output_node)
    # Add edges
    workflow.add_edge("researcher", "critic")
    workflow.add_edge("critic", "output")
    # Set the entry point
    workflow.set_entry_point("researcher")
    # Compile the graph
    return workflow.compile()

Enhancing the Critic with Structured Output

To make our Critic Agent more useful for downstream processes, we can enhance it to provide structured feedback in JSON format. This makes it easier for other agents (like a Writer Agent) to process and incorporate the criticism.

Here’s how we implement structured critique:

class CriticEvaluation(BaseModel):
    """Structured format for critic evaluations."""
    quality_score: int = Field(description="Overall quality score from 1-10")
    strengths: List[str] = Field(description="Key strengths of the research")
    areas_for_improvement: List[str] = Field(description="Areas that need improvement")
    missing_information: List[str] = Field(description="Important information that was not included")
    bias_assessment: str = Field(description="Assessment of potential biases in the research")
    additional_questions: List[str] = Field(description="Questions that should be addressed")

This structured approach allows us to build a more robust workflow:

Zoom image will be displayed

The Complete Multi-Agent Research Process

When we put it all together, our multi-agent system follows this process:

  1. User Input: The user submits a research question
  2. Research Phase: The Researcher Agent gathers and organizes information
  3. Critique Phase: The Critic Agent evaluates the research for quality and completeness
  4. Structured Feedback: The system provides organized, actionable criticism
  5. Final Output: The combined research and critique provide a comprehensive response

Zoom image will be displayed

Benefits of the Modern Approach

The modern implementation using the latest LangGraph and LangChain APIs offers several advantages:

  1. Type Safety: Properly typed states reduce errors and improve code maintainability
  2. Structured Output: JSON-formatted criticism enables easier integration with other components
  3. Better Maintainability: Clear separation of concerns with modular agent design
  4. Visualization Support: Built-in tools for workflow visualization and understanding

By adding a Critic Agent to our multi-agent system, we create a powerful check-and-balance mechanism that improves the quality, accuracy, and completeness of our AI-generated research. This approach mimics human collaborative processes, where peer review and constructive criticism lead to better outcomes.

The structured criticism approach also provides a foundation for the next step in our multi-agent journey: adding a Writer Agent that can synthesize the research and critique into a coherent, well-crafted final response.

In our next section, we’ll explore how to build this Writer Agent and complete our multi-agent research assistant system.

The Writer Agent

In the previous steps of this article, we explored how to build a collaborative research assistant with specialized agents: a researcher agent to gather information and a critic agent to evaluate and challenge that information. Now, it’s time to add the third essential component: a writer agent that will synthesize this information into a coherent, well-written response. This section explores how to implement a Writer Agent that synthesizes information and produces coherent, comprehensive outputs — creating a complete collaborative research workflow that mirrors human teams of researchers, editors, and writers.

The Concept of a Writer Agent

A Writer Agent serves as the final communicator in a multi-agent system, transforming raw research and critical analysis into polished, coherent content optimized for human consumption.

The Writer Agent fulfills several crucial functions in the workflow:

  1. Information Synthesis: Combines insights from both the researcher’s findings and the critic’s evaluation
  2. Organization: Structures information in a logical, accessible way for the end user
  3. Style Adaptation: Presents information in clear, engaging language appropriate to the context
  4. Perspective Integration: Balances different viewpoints to create a comprehensive response
  5. Clarity Enhancement: Simplifies complex concepts without sacrificing accuracy

Modern Implementation with LangGraph

The latest versions of LangGraph and LangChain provide sophisticated tools for building integrated multi-agent systems. Let’s explore how to implement a Writer Agent using these modern frameworks.

The Writer’s System Prompt

The foundation of our Writer Agent is a carefully designed system prompt:

WRITER_SYSTEM_PROMPT = """
You are a Writer Agent, part of a collaborative research assistant system. Your role is to synthesize 
information from the Researcher Agent and feedback from the Critic Agent into a coherent, well-written response.
Your responsibilities include:
1. Analyzing the information provided by the researcher and the feedback from the critic
2. Organizing the information in a logical, easy-to-understand structure
3. Presenting the information in a clear, engaging writing style
4. Balancing different perspectives and ensuring objectivity
5. Creating a final response that is comprehensive, accurate, and well-written
Format your response in a clear, organized manner with appropriate headings, paragraphs, and bullet points.
Use simple language to explain complex concepts, and provide examples where helpful.
Remember, your goal is to create a final response that effectively communicates the information to the user.
"""

Modern State Management and Node Implementation

One of the key improvements in recent versions of LangGraph is the use of TypedDict for state management, providing better code organization and type safety:

class CollaborativeResearchState(TypedDict):
    """State type for our collaborative research assistant."""
    messages: List[BaseMessage]  # The conversation history
    next: Optional[str]  # Where to go next in the graph

The Writer node implementation follows a clean, functional approach:

def writer_node(state: CollaborativeResearchState) -> CollaborativeResearchState:
    """Node function for the writer agent."""
    # Extract messages from the state
    messages = state["messages"]
    # Create writer messages with the system prompt
    writer_messages = [SystemMessage(content=WRITER_SYSTEM_PROMPT)] + messages
    # Initialize the LLM with a balance of creativity and accuracy
    llm = ChatOpenAI(model="gpt-4o", temperature=0.6)
    # Get the writer's response
    response = llm.invoke(writer_messages)
    # Return the updated state
    return {
        "messages": messages + [response],
        "next": "output"
    }

The Complete Multi-Agent Workflow

When we integrate the Writer Agent with our Researcher and Critic Agents, we create a sophisticated workflow that mimics a professional research and writing team:

Zoom image will be displayed

The implementation of this workflow in LangGraph is clean and maintainable:

def build_complete_research_assistant():
    """Build a complete research assistant with researcher, critic, and writer agents."""
    # Create a new graph with our state type
    workflow = StateGraph(CollaborativeResearchState)
    # Add nodes
    workflow.add_node("researcher", researcher_node)
    workflow.add_node("critic", critic_node)
    workflow.add_node("writer", writer_node)
    workflow.add_node("output", output_node)
    # Add edges
    workflow.add_edge("researcher", "critic")
    workflow.add_edge("critic", "writer")
    workflow.add_edge("writer", "output")
    # Set the entry point
    workflow.set_entry_point("researcher")
    # Compile the graph
    return workflow.compile()

Enhanced Features for Advanced Applications

For more complex applications, we can enhance our Writer Agent and overall workflow with advanced features:

Rich Metadata Tracking

We can create an enhanced state type that tracks metadata about each agent’s contribution:

class EnhancedResearchState(TypedDict):
    """Enhanced state type with metadata for the research process."""
    messages: List[BaseMessage]  # The conversation history
    metadata: Dict[str, Any]  # Metadata about each step in the process
    next: Optional[str]  # Where to go next in the graph

This allows us to capture information like processing time, token counts, and model parameters, which is valuable for analysis and optimization:

Zoom image will be displayed

Flexible Architecture Patterns

The three-agent structure we’ve built (Researcher → Critic → Writer) can be adapted to various application needs:

1. Hierarchical Organization:

  • Research Team (Multiple specialized researchers) → Editor → Writer

2. Parallel Processing:

  • Multiple researchers working on different aspects → Critics evaluating each part → Writer synthesizing everything

3. Hybrid Structures:

  • Some agents working sequentially, others in parallel, based on the task

Benefits of the Writer Agent

Adding a Writer Agent to your multi-agent system provides several key benefits:

  1. Improved Communication: Content is presented in a way that’s optimized for human understanding
  2. Consistency: The writing style and structure remain consistent, even when research covers diverse topics
  3. Integration: Various perspectives and pieces of information are woven together cohesively
  4. Efficiency: The final output is ready to use without additional editing or reformatting
  5. Adaptability: The Writer Agent can adjust its style and structure based on the context and audience

By adding a Writer Agent to our system, we’ve completed a powerful collaborative workflow that mirrors how humans work together on complex information tasks. Each agent in our system has a specialized role:

  • The Researcher Agent gathers comprehensive information
  • The Critic Agent evaluates and ensures quality
  • The Writer Agent communicates effectively to the end user

This separation of concerns allows each agent to excel at its specific task while contributing to a cohesive whole. Using modern LangGraph and LangChain features like TypedDict for state management, we’ve built a system that’s not only powerful but also maintainable and extensible.

Multi-agent systems represent a significant advancement in AI application design, moving beyond the limitations of single-agent approaches to create more robust, balanced, and effective solutions.

The Coordinator Agent

In our journey to build sophisticated multi-agent systems with LangGraph, we’ve explored specialized agents for research, criticism, and writing. Now, let’s focus on the critical component that transforms a fixed, sequential workflow into a truly dynamic, intelligent solution: the coordinator agent.

The Need for Coordination in Multi-Agent Systems

As multi-agent systems grow in complexity, coordinating the interactions between agents becomes increasingly important. A well-designed coordinator can:

  • Analyze user queries to determine the optimal workflow
  • Skip unnecessary steps for simple requests
  • Initiate additional research cycles when needed
  • Ensure all relevant specialists contribute appropriately
  • Adapt the workflow based on intermediate results

The Coordinator Agent Architecture

The diagram above illustrates our dynamic multi-agent architecture. Unlike previous implementations where agents operated in a fixed sequence, this system uses a Coordinator Agent to determine the optimal path at each stage. For simple queries, it can bypass research altogether, while for complex ones, it orchestrates the full research cycle.

Building the Coordinator in Modern LangGraph

Modern LangGraph provides powerful tools for implementing this dynamic architecture:

def coordinator_node(state: ResearchState) -> ResearchState:
    """Coordinator node that decides the workflow path."""
    # Extract messages from the state
    messages = state["messages"]
    # Create coordinator messages with the system prompt
    coordinator_messages = [SystemMessage(content=COORDINATOR_SYSTEM_PROMPT)] + messages
    # Initialize the LLM with a lower temperature for consistent decision-making
    llm = ChatOpenAI(model="gpt-4o", temperature=0.2)
    # Get the coordinator's response
    response = llm.invoke(coordinator_messages)
    # Parse the JSON response to determine next steps
    try:
        decision = json.loads(response.content)
        next_step = decision.get("next", "researcher")  # Default to researcher if not specified
    except Exception:
        # If there's an error parsing the JSON, default to the researcher
        next_step = "researcher"
    # Return the updated state
    return {"messages": messages, "next": next_step}

The key improvement in our implementation is the use of conditional edges in the workflow graph:

# Add conditional edges from the coordinator
workflow.add_conditional_edges(
    "coordinator",
    lambda state: state["next"],
    {
        "researcher": "researcher",
        "done": "output"
    }
)

Dynamic Decision Making in Action

Let’s examine how our Coordinator Agent’s decision-making process works:

Zoom image will be displayed

For simple factual queries, the Coordinator can provide a direct answer, avoiding unnecessary work. For complex topics requiring expertise, it orchestrates a full research cycle involving all specialized agents.

Comparison: Static vs. Dynamic Workflows

The comparison above highlights the key difference between static and dynamic workflows. The dynamic approach with a Coordinator Agent offers:

  • Efficiency: Simple queries get immediate responses
  • Adaptability: Workflow changes based on query complexity
  • Intelligence: Decision-making at key junctures
  • Recursion: Ability to loop back for further research when needed

Technical Implementation Details

Our implementation leverages several modern LangGraph features:

  1. TypedDict for State Management: Using ResearchState with proper typing
  2. Conditional Edges: Dynamic routing based on agent decisions
  3. Structured Output: JSON formatting with reasoning and next steps
  4. Error Handling: Robust parsing with fallbacks
  5. LLM Integration: I am using GPT-4o through those examples.

This approach provides a clean, maintainable, and extensible architecture that can be adapted for various applications.

Extending the Coordinator Pattern

The Coordinator pattern can be extended in several ways:

Zoom image will be displayed

Building a Dynamic Multi-Agent System

With our coordinator agent implemented, we can now transform our system from a fixed, sequential workflow to a dynamic one where the coordinator decides which path to take:

def build_dynamic_research_assistant():
    """Build a dynamic research assistant with a coordinator agent managing the workflow."""
    # Create a new graph
    workflow = Graph()
     
    # Add nodes
    workflow.add_node("coordinator", coordinator_agent)
    workflow.add_node("researcher", researcher_agent)
    workflow.add_node("critic", critic_agent)
    workflow.add_node("writer", writer_agent)
    workflow.add_node("output", output)
     
    # Add conditional edges from the coordinator
    workflow.add_conditional_edges(
        "coordinator",
        lambda state: state["next"],
        {
            "researcher": "researcher",
            "done": "output"
        }
    )
     
    # Add the rest of the edges
    workflow.add_edge("researcher", "critic")
    workflow.add_edge("critic", "writer")
    workflow.add_edge("writer", "coordinator")
    workflow.add_edge("output", END)
     
    # Set the entry point
    workflow.set_entry_point("coordinator")
     
    # Compile the graph
    return workflow.compile()

This graph creates a dynamic workflow where:

  1. The coordinator agent analyzes the user’s query and decides whether to engage the research team or provide a direct response
  2. If research is needed, the query flows through our specialized agents: researcher → critic → writer
  3. After the writer completes its work, control returns to the coordinator, which can either send the response to the user or initiate additional research
  4. This creates a loop where complex queries can be refined through multiple iterations of research, critique, and writing

Visual Workflow Diagrams

Dynamic Multi-Agent System Architecture

Here’s a visual representation of our dynamic multi-agent system with the coordinator agent:

Zoom image will be displayed

The diagram illustrates the dynamic nature of our system:

  1. The coordinator agent receives all user queries first
  2. For simple queries, the coordinator can provide responses directly
  3. For complex queries, the coordinator routes them through our specialist agents
  4. After the writer agent produces a response, the coordinator reviews it
  5. The coordinator can either deliver the final response or request additional research
  6. This creates a feedback loop that can handle complex, multi-step queries

Decision Flow for Different Query Types

Let’s also visualize how different types of queries flow through the system:

Zoom image will be displayed

This diagram illustrates the different paths queries can take:

  • Simple, factual queries get direct responses
  • Complex research queries go through our specialized agents
  • Some queries might require multiple research iterations

Seeing the Dynamic System in Action

Let’s see how our dynamic system handles different types of queries:

Query 1: “What is the capital of France?”

Coordinator:

{
  "reasoning": "This is a simple factual question asking for the capital of France. The answer is well-known and doesn't require in-depth research, critical analysis, or specialized writing.",
  "next": "done"
}

System Response: “The capital of France is Paris.”

Query 2: “What are the implications of quantum computing for cybersecurity?”

Coordinator:

{
  "reasoning": "This query asks for complex information about quantum computing and its relationship to cybersecurity. It requires gathering detailed information, evaluating different perspectives, and synthesizing a comprehensive response.",
  "next": "researcher"
}

[The system then processes this query through the researcher, critic, and writer agents before returning to the coordinator…]

Writer[Produces comprehensive, well-structured content about quantum computing’s implications for encryption, security protocols, etc.]

Coordinator (after reviewing the writer’s output):

{
  "reasoning": "The query has been thoroughly researched, critiqued, and synthesized into a comprehensive response that addresses the implications of quantum computing for cybersecurity from multiple angles.",
  "next": "done"
}

System Response[The writer’s comprehensive response is delivered to the user]

The Value of the Coordinator Agent

Adding a coordinator agent transforms our system in several important ways:

  1. Efficiency: Simple queries can bypass unnecessary processing, providing faster responses
  2. Adaptability: The system can take different paths based on the nature of each query
  3. Iteration: Complex queries can go through multiple rounds of research and refinement
  4. Intelligence: The system makes high-level decisions about how to process information
  5. Scalability: New specialized agents can be added to the system more easily

Perhaps most importantly, the coordinator makes our system feel more like interacting with a team of intelligent experts rather than a fixed, mechanical process.

Advanced Coordinator Implementations

While our implementation is powerful, there are several ways to make the coordinator even more sophisticated:

  1. More Routing Options: Allow the coordinator to route directly to any agent, not just the researcher
  2. Feedback Loops: Enable the coordinator to identify gaps and request specific additional research
  3. Memory Management: Have the coordinator maintain a summary of what’s been learned so far
  4. Meta-Learning: Allow the coordinator to learn which paths work best for different query types
  5. Multi-Query Planning: Enable the coordinator to break complex queries into sub-questions

The coordinator agent completes our multi-agent research assistant, transforming it from a sequential workflow into a dynamic, intelligent system. By orchestrating the specialized agents we’ve built — researcher, critic, and writer — the coordinator creates a solution that’s more than the sum of its parts.

This approach mirrors how effective human teams work: specialized experts collaborate under thoughtful coordination to tackle complex problems. The result is a system that can provide simple answers quickly, tackle complex questions thoroughly, and adapt its approach to each unique situation.

As you build your own multi-agent systems with LangGraph, remember that the coordinator is what transforms a collection of agents into a truly intelligent, adaptable system.

Benefits of Using LangGraph

Using LangGraph for multi-agent systems provides several advantages:

  1. Structured Workflows: LangGraph provides a clear structure for defining how agents interact, making complex systems easier to design and maintain.
  2. State Management: The framework handles state management across multiple LLM calls, ensuring that information flows correctly between agents.
  3. Conditional Routing: LangGraph allows for dynamic decision-making about which agent should act next, enabling more adaptive and responsive systems.
  4. Checkpointing: Built-in checkpointing capabilities make it easier to debug and resume long-running processes.
  5. Scalability: The graph-based approach makes it easier to add new agents or modify existing workflows as your system evolves.

Comparison with CWIC Flow

While LangGraph provides an excellent framework for building multi-agent systems, it’s worth noting the similarities and differences with Clearwater’s CWIC Flow:

Similarities:

  • Both use a graph-based approach to orchestrate multiple specialized agents
  • Both maintain state across agent interactions
  • Both support conditional routing based on agent outputs

Differences:

  • CWIC Flow is specifically designed for financial services, with domain-specific agents and tools
  • CWIC Flow includes production-ready governance features not found in LangGraph
  • CWIC Flow has deeper integration with cloud services and data sources

Conclusion

LangGraph provides a powerful framework for building sophisticated multi-agent systems, with structured workflows, state management, and conditional routing. While Clearwater Analytics developed CWIC Flow before LangGraph became available, we continue to evaluate emerging technologies like LangGraph to enhance our capabilities.

The multi-agent approach, whether implemented through custom solutions like CWIC Flow or frameworks like LangGraph, represents the future of AI systems. By orchestrating specialized agents with distinct roles and responsibilities, we can create more capable, robust, and adaptable AI systems that deliver greater value to users.

As AI technology evolves, Clearwater remains committed to adopting innovations that enhance our ability to deliver actionable insights to our clients while maintaining our industry-leading standards for security, compliance, and accuracy.


About the Author

Rany ElHousieny is an Engineering Leader at Clearwater Analytics with over 30 years of experience in software development, machine learning, and artificial intelligence. He has held leadership roles at Microsoft for two decades, where he led the NLP team at Microsoft Research and Azure AI, contributing to advancements in AI technologies. At Clearwater, Rany continues to leverage his extensive background to drive innovation in AI, helping teams solve complex challenges while maintaining a collaborative approach to leadership and problem-solving.