Issue 04 | Routing Edges (Edges) and Terminators (END)

Updated on 4/13/2026

🎯 Learning Objectives for this Issue

Welcome back to our "LangGraph Multi-Agent Expert Course", future AI architects! In the last issue, we built the "skeleton" of our Agents—Nodes. But a skeleton alone is not enough; we need "meridians" to connect them and allow energy (data) to flow through. In this issue, we will dive deep into the core of LangGraph—Edges and END. After completing this, you will:

  1. Master the two basic types of edges in LangGraph: Learn how to build fixed routing and condition-based dynamic routing, so your Agent collaboration is no longer just simple "queuing".
  2. Proficiently use add_conditional_edges: Understand the decision-making logic behind it and implement intelligent process distribution based on business rules for our AI content creation agency.
  3. Understand the essence of set_entry_point and set_finish_point (or END): Build an Agent workflow with a clear beginning and end, and rigorous logic, preventing your program from "dying without a cause".
  4. Accurately map complex real-world business logic to LangGraph's routing edges: Enhance your architectural design ability to translate abstract requirements into executable code.

📖 Core Concepts

Imagine you are designing a highly automated factory where each workshop (Node) has its unique function. How do you decide which workshop a product enters, which workshops it goes through for processing, and finally which exit it leaves from? This is the problem that Edges and END solve.

In LangGraph, Nodes are your defined Agents or tool functions; they are the "stations" in the workflow. And Edges, as the name implies, are the "paths" or "tracks" connecting these stations. They define how data and control flow transfer from one node to another.

1. From START to END: The Lifecycle of a Workflow

A complete LangGraph workflow must have a starting point and an ending point:

  • set_entry_point(node_name): This is the "front door" of your workflow. All tasks start here. Just like entering a subway station, there has to be an entrance, right?
  • END: This is the "terminus" of your workflow. When the process reaches here, it means the task is completed and computation stops. It is a special LangGraph keyword that explicitly tells the system: "Hey guys, the work is done, let's wrap it up!"

2. Edges: The Dynamic Skeleton of the Workflow

Edges are divided into two core types:

a) Fixed Edges (add_edge)

The simplest and most direct way. If you are certain that after Node A finishes executing, it always passes control and state to Node B, then use add_edge.

  • Syntax: graph.add_edge("source_node_name", "target_node_name")
  • Application Scenarios: Linear, sequentially executed steps. For example: After the Researcher completes the research, it must hand the results over to the Writer.

b) Conditional Edges (add_conditional_edges)

This is where LangGraph truly shines! It allows your workflow to make decisions based on the current State, thereby dynamically selecting the next node to execute. This is the key to the Planner's intelligent decision-making in our AI content creation agency.

  • Syntax: graph.add_conditional_edges("source_node_name", condition_function, mapping_dict)
    • source_node_name: Which node needs to make a decision after its execution?
    • condition_function: This is a Python function that takes the current state as input and returns a string (or a list of strings). This string is your "decision result".
    • mapping_dict: This is a dictionary that maps the decision result returned by the condition_function to a specific "target node name". For example, {"research_needed": "researcher", "no_research": "writer"}.
  • Application Scenarios: Any scenario that requires dynamically adjusting the workflow based on business logic. For example:
    • The Planner evaluates content requirements and decides whether the Researcher needs to be involved.
    • The Editor checks the draft and decides whether to directly go to END for publishing or return it to the Writer for revision.

3. Mermaid Diagram of Core Concepts

Let's use a diagram to intuitively understand the simplified workflow of our AI content creation agency, especially how the Planner decides the direction through conditional edges:

graph TD
    A[START] --> B(Planner Agent)
    B -- "Needs research?" --> C{Condition: research_needed?}
    C -- "Yes (True)" --> D(Researcher Agent)
    C -- "No (False)" --> E(Writer Agent)
    D --> E
    E --> F(Editor Agent)
    F --> G[END]

    style A fill:#D4EDDA,stroke:#28A745,stroke-width:2px,color:#28A745
    style G fill:#F8D7DA,stroke:#DC3545,stroke-width:2px,color:#DC3545
    style B fill:#E0F2F7,stroke:#17A2B8,stroke-width:2px,color:#17A2B8
    style D fill:#FFF3CD,stroke:#FFC107,stroke-width:2px,color:#FFC107
    style E fill:#D1ECF1,stroke:#007BFF,stroke-width:2px,color:#007BFF
    style F fill:#FEEBFD,stroke:#E83E8C,stroke-width:2px,color:#E83E8C
    style C fill:#FFE5CC,stroke:#FD7E14,stroke-width:2px,color:#FD7E14

Diagram Analysis:

  1. START: The task initiates from here.
  2. Planner Agent: This is our entry node; it receives the initial request and conducts preliminary planning.
  3. Condition: research_needed?: This is the condition_function we will use after the Planner node finishes executing. It checks the research_needed field marked by the Planner in the state.
  4. Conditional Branches:
    • If research_needed is True, the workflow is directed to the Researcher Agent.
    • If research_needed is False, the workflow skips the Researcher and is directed straight to the Writer Agent.
  5. Researcher Agent: Executes the research task.
  6. Writer Agent: Executes the content writing task. Whether it goes through the Researcher or not, it will eventually reach the Writer.
  7. Editor Agent: Executes the content editing task.
  8. END: The task is finally completed.

See? Isn't this just translating the business logic of our "AI Content Creation Agency" into the language of LangGraph? Clear, intuitive, and controllable!

💻 Practical Code Drill (Specific Application in the Agency Project)

Alright, enough theory, let's get our hands dirty with some code. Now, we will implement the business workflow diagram above using LangGraph code.

Project Background Review: Our AI content creation agency receives a content creation request. The Planner agent first evaluates this request to determine if in-depth research is needed. If so, it hands the task over to the Researcher; if not, it hands it directly to the Writer. Finally, after the Editor completes the review, the entire workflow ends.

We will use LangGraph's StateGraph to build this workflow.

import operator
from typing import TypedDict, Annotated, List, Union
from langchain_core.messages import BaseMessage
from langchain_core.agents import AgentFinish
from langgraph.graph import StateGraph, END
from pprint import pprint # Used for pretty-printing the state

# --- 1. Define the shared State of our workflow ---
# This is the "canvas" for passing and sharing information among all Agents
class AgentState(TypedDict):
    """
    TypedDict representing the shared state in multi-agent collaboration.
    Any agent or tool can read and update this state.
    """
    topic: str  # The topic for content creation
    research_needed: bool  # Planner's judgment on whether research is needed
    research_content: Annotated[str, operator.add]  # Research content, aggregated using operator.add
    draft_content: Annotated[str, operator.add]  # Draft content, aggregated using operator.add
    final_content: Annotated[str, operator.add] # Final content, aggregated using operator.add
    # messages: Annotated[List[BaseMessage], operator.add] # Message list for inter-agent communication, simplified and not used in this issue

# --- 2. Define each Agent as a Node ---
# To focus on Edges and END in this issue, we simplify the Agents into standard Python functions,
# which receive the current state, simulate task execution, and return the updated state.

def planner_node(state: AgentState) -> AgentState:
    """
    Planner Agent: Determines if research is needed based on the topic and generates preliminary instructions.
    """
    print("\n--- Planner Agent is executing ---")
    topic = state["topic"]
    
    # Simulate the Planner's complex decision-making logic
    # Assume research is needed if the topic contains keywords like "in-depth", "cutting-edge", "analysis"
    if "in-depth" in topic or "cutting-edge" in topic or "analysis" in topic:
        research_needed = True
        print(f"The topic '{topic}' is complex and requires in-depth research.")
    else:
        research_needed = False
        print(f"The topic '{topic}' is relatively simple and does not require dedicated research.")

    # Update state
    return {
        "research_needed": research_needed,
        "draft_content": f"Planner: Preliminary ideas for the topic '{topic}'.\n"
    }

def researcher_node(state: AgentState) -> AgentState:
    """
    Researcher Agent: Conducts research based on the topic and provides research content.
    """
    print("\n--- Researcher Agent is executing ---")
    topic = state["topic"]
    
    # Simulate the research process
    simulated_research = f"Researcher: Collected the following key information for the topic '{topic}':\n" \
                         f"- Fact 1: Latest developments regarding {topic}.\n" \
                         f"- Fact 2: Industry experts' perspectives on {topic}.\n" \
                         f"- Fact 3: Relevant data and statistics.\n"
    
    print("Research completed.")
    # Update state
    return {"research_content": simulated_research}

def writer_node(state: AgentState) -> AgentState:
    """
    Writer Agent: Writes the first draft based on the planning and research content.
    """
    print("\n--- Writer Agent is executing ---")
    topic = state["topic"]
    planner_draft = state["draft_content"]
    research_content = state.get("research_content", "No research content.") # Empty if there is no researcher

    # Simulate the writing process
    full_draft = f"{planner_draft}\n" \
                 f"Writer: Integrated the following information for writing:\n" \
                 f"--- Research Reference ---\n{research_content}\n" \
                 f"--- Draft Body ---\n" \
                 f"Dear reader, welcome to this article about '{topic}'...\n" \
                 f"These are the core viewpoints and preliminary elaborations of the article.\n" \
                 f"Looking forward to the editor's feedback.\n"
    
    print("Draft writing completed.")
    # Update state
    return {"draft_content": full_draft}

def editor_node(state: AgentState) -> AgentState:
    """
    Editor Agent: Reviews the draft and generates the final content.
    """
    print("\n--- Editor Agent is executing ---")
    draft_content = state["draft_content"]
    
    # Simulate the editing process
    final_version = f"Editor: Polished and proofread the following draft.\n" \
                    f"========== Original Draft ==========\n{draft_content}\n" \
                    f"========== Final Version ==========\n" \
                    f"After careful editing, this article about '{state['topic']}' is ready, with high quality and fluent expression.\n" \
                    f"Ensuring information accuracy and reading experience.\n"
    
    print("Editing and review completed, content is ready for publishing.")
    # Update state
    return {"final_content": final_version}

# --- 3. Define the Condition Function ---
# This is the key for LangGraph to decide the workflow direction!
def should_research(state: AgentState) -> str:
    """
    Determines whether the next step is 'researcher' or directly to 'writer' 
    based on the 'research_needed' flag set by the Planner agent in the state.
    """
    print("\n--- Workflow Decision: Is research needed? ---")
    if state["research_needed"]:
        print("Decision result: Research needed, routing to Researcher Agent.")
        return "research" # Returns a string corresponding to a key in mapping_dict
    else:
        print("Decision result: No research needed, routing to Writer Agent.")
        return "write" # Returns a string corresponding to a key in mapping_dict

# --- 4. Build the LangGraph Workflow ---
workflow = StateGraph(AgentState)

# Add nodes to the graph (corresponding to the previous issue's content)
workflow.add_node("planner", planner_node)
workflow.add_node("researcher", researcher_node)
workflow.add_node("writer", writer_node)
workflow.add_node("editor", editor_node)

# Set the Entry Point
# All content creation requests start from the planner agent
workflow.set_entry_point("planner")

# Add Conditional Edges
# This is the core of this issue! After the planner executes, based on the return result of the should_research function,
# it decides whether to connect to the researcher or the writer.
workflow.add_conditional_edges(
    "planner",           # Source node: planner makes a decision after execution
    should_research,     # Condition function: determines direction based on state
    {
        "research": "researcher", # If should_research returns "research", go to the researcher node
        "write": "writer"         # If should_research returns "write", go to the writer node
    }
)

# Add Fixed Edges
# After the researcher finishes, it always passes the result to the writer
workflow.add_edge("researcher", "writer")

# After the writer finishes, it always passes the draft to the editor
workflow.add_edge("writer", "editor")

# Set the Finish Point
# After the editor finishes, the entire content creation workflow ends
workflow.add_edge("editor", END) # Use the END keyword to explicitly indicate workflow termination

# Compile the workflow
app = workflow.compile()

# --- 5. Run the workflow and observe the results ---

print("\n========== Scenario 1: A topic requiring in-depth research ==========")
initial_state_1 = {"topic": "In-depth exploration of cutting-edge applications of LLMs in multi-agent collaboration", 
                   "research_needed": False, # Planner will overwrite this initial value
                   "research_content": "", 
                   "draft_content": "", 
                   "final_content": ""}
final_state_1 = app.invoke(initial_state_1)
print("\n--- Scenario 1 Final State ---")
pprint(final_state_1)
print("\n----------------------------------------------------\n")


print("\n========== Scenario 2: A relatively simple topic ==========")
initial_state_2 = {"topic": "How to implement a simple Web server using Python", 
                   "research_needed": False, # Planner will overwrite this initial value
                   "research_content": "", 
                   "draft_content": "", 
                   "final_content": ""}
final_state_2 = app.invoke(initial_state_2)
print("\n--- Scenario 2 Final State ---")
pprint(final_state_2)
print("\n----------------------------------------------------\n")

# You can visualize the graph to help with understanding
# try:
#     from IPython.display import Image, display
#     display(Image(app.get_graph().draw_png()))
# except Exception as e:
#     print(f"Unable to generate visualization chart: {e}")
#     print("Please ensure the 'graphviz' library and its system dependencies are installed.")

Code Breakdown:

  1. AgentState: We defined a TypedDict to serve as the shared state of our workflow. Annotated and operator.add are powerful mechanisms in LangGraph for handling state aggregation; here we use them to simulate the gradual accumulation of content.
  2. *_node functions: These are our Agent nodes. For simplicity, they are just standard Python functions that receive the state, print some simulated information, and return a dictionary that updates the state. In real projects, these would be complex Agent classes calling LLMs or tools.
  3. should_research function: This is the core condition function of this issue! It receives the current AgentState and returns "research" or "write" based on the research_needed field set by the Planner agent. This return value will serve as the decision basis for add_conditional_edges.
  4. StateGraph initialization: Creates our graph instance.
  5. add_node: Registers our Agent functions as nodes in the graph.
  6. set_entry_point("planner"): Explicitly specifies that planner is the starting point of the entire workflow.
  7. add_conditional_edges: After planner, we no longer simply use add_edge. We tell LangGraph that after planner, it needs to call the should_research function to decide where to go next.
    • If should_research returns "research", the workflow routes to the researcher node.
    • If should_research returns "write", the workflow routes to the writer node.
    • This perfectly implements the "whether research is needed" decision logic in our business.
  8. add_edge: After researcher it always goes to writer, and after writer it always goes to editor; these are fixed sequential routings.
  9. add_edge("editor", END): This is another key point! After the editor completes its task, we explicitly connect the workflow to END. This tells LangGraph that the entire workflow is complete, execution can stop, and the final state can be returned.
  10. app.invoke(initial_state): Runs our workflow. By passing in different initial_states (mainly the topic), we can see how the planner dynamically guides the workflow down different paths.

Through these two execution scenarios, you will clearly see how add_conditional_edges allows the workflow to automatically choose whether to pass through the Researcher node based on the Planner's decision, eventually converging at the Writer and Editor, and gracefully concluding via END.

Pitfalls and Troubleshooting Guide

As a senior mentor, I've seen too many students make mistakes with Edges and END. Come on, let's fill in these pitfalls in advance!

  1. Mismatch between condition_function return value and mapping

    • Pitfall: Your condition_function returns a string, but this string does not exist as a key in the mapping_dict of add_conditional_edges.
    • Consequence: LangGraph will throw a ValueError, indicating that the corresponding target node cannot be found.
    • How to Avoid: Always ensure that all possible return values of the condition_function have explicit mappings in the mapping_dict. For complex conditions, consider adding a __default__ key in the mapping_dict to route unmapped return values to a default node (e.g., an error handling node or directly to END).
      # Error Example
      # def my_condition(state): return "unknown_path"
      # graph.add_conditional_edges("source", my_condition, {"path_a": "node_a"}) # "unknown_path" is not matched
      
      # Avoidance Example: Using __default__
      # graph.add_conditional_edges("source", my_condition, {
      #     "path_a": "node_a",
      #     "path_b": "node_b",
      #     "__default__": "error_handler_node" # Or directly END
      # })
      
  2. Misunderstanding and Omission of END

    • Pitfall: Some students think that as long as the last node finishes executing, the workflow naturally ends. Or they treat END as a regular node.
    • Consequence: If a path is not explicitly connected to END, when the last active node on that path finishes executing, LangGraph will assume "there are no more edges to traverse" and implicitly stop. While this won't throw an error, it is less clear than explicitly using END. Especially in complex multi-branch workflows, it easily causes misunderstandings or omissions.
    • How to Avoid: Always use END as an explicit terminator. Any "successful" or "completed" business path should ultimately route to END. END is a special keyword, not a regular node you define. It represents the execution boundary of the graph and makes your workflow intent much clearer.
  3. Circular Dependencies and Infinite Loops

    • Pitfall: When designing conditional edges, you accidentally create a loop, causing the workflow to loop infinitely under certain conditions. For example, the Editor always returns the content to the Writer, and the Writer always writes a version that dissatisfies the Editor.
    • Consequence: The program falls into an infinite loop, exhausts resources, and eventually crashes.
    • How to Avoid:
      • Introduce iteration counts or state variables: Add a revision_count field in the AgentState. Every time the Editor returns a task to the Writer, increment revision_count by one. In the Editor's condition check, verify if revision_count exceeds a preset threshold. If it does, force the workflow to route to END or a "human intervention" node.
      • Visual debugging: Use app.get_graph().draw_png() or draw_mermaid() to visualize your workflow. Complex conditional edges and circular paths are hard to spot with the naked eye, but a chart makes them obvious at a glance.
  4. Uniqueness of set_entry_point

    • Pitfall: Attempting to set multiple entry points, or forgetting to set an entry point.
    • Consequence: LangGraph requires a graph to have only one set_entry_point. Forgetting to set it will prevent startup.
    • How to Avoid: A graph can only have one set_entry_point. If your business logic needs to start from different places, it might mean you need to design multiple independent LangGraph graphs, or add a "dispatcher" node before the entry point to decide the actual first business node.
  5. State Modification in Condition Functions

    • Pitfall: Modifying the state inside the condition_function.
    • Consequence: The responsibility of the condition_function is to read the state and make decisions, not to modify the state. Although Python allows you to modify passed mutable objects, doing so breaks LangGraph's design philosophy, making state management chaotic and issues hard to track.
    • How to Avoid: Condition functions should be Pure Functions, only reading the state and returning decision results, never modifying the state. All modifications to the state should occur inside the nodes (functions registered with add_node).

By mastering these, your control over LangGraph workflows will be greatly enhanced, and the systems you build will be more robust and maintainable.

📝 Summary of this Issue

Everyone, in this issue we delved into the key to connecting the lifelines of various agents in LangGraph—Routing Edges and the END node. We learned how to use add_edge to build fixed routing, and how to implement dynamic process distribution based on business logic through add_conditional_edges and condition_function. At the same time, we clarified the important roles of set_entry_point and END in defining the complete lifecycle of a workflow.

Through the practical drill in the "AI Universal Content Creation Agency" project, you should now be able to:

  • Clearly translate business decisions like "whether research is needed" into LangGraph conditional edges.
  • Build a multi-agent collaboration workflow from START to END with explicit starting and ending points.
  • Avoid common pitfalls, such as condition mismatches, missing ENDs, or circular dependencies.

From now on, your LangGraph workflows will no longer be rigid linear executions, but will be able to make judgments based on real-time situations and flexibly adjust processes like an intelligent system with a real "brain". This means your AI Content Agency can better adapt to various content creation needs!

In the next issue, we will continue to dive deeper and explore how to make our Agents not just execute sequentially, but perform Parallelism, further improving our agency's efficiency and ability to handle complex tasks! Stay tuned!