Issue 04 | Routing Edges (Edges) and Terminators (END)
🎯 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:
- 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".
- 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. - Understand the essence of
set_entry_pointandset_finish_point(orEND): Build an Agent workflow with a clear beginning and end, and rigorous logic, preventing your program from "dying without a cause". - 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
Researchercompletes the research, it must hand the results over to theWriter.
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 currentstateas 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 thecondition_functionto 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
Plannerevaluates content requirements and decides whether theResearcherneeds to be involved. - The
Editorchecks the draft and decides whether to directly go toENDfor publishing or return it to theWriterfor revision.
- The
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:#FD7E14Diagram Analysis:
START: The task initiates from here.Planner Agent: This is our entry node; it receives the initial request and conducts preliminary planning.Condition: research_needed?: This is thecondition_functionwe will use after thePlannernode finishes executing. It checks theresearch_neededfield marked by thePlannerin thestate.- Conditional Branches:
- If
research_neededisTrue, the workflow is directed to theResearcher Agent. - If
research_neededisFalse, the workflow skips theResearcherand is directed straight to theWriter Agent.
- If
Researcher Agent: Executes the research task.Writer Agent: Executes the content writing task. Whether it goes through theResearcheror not, it will eventually reach theWriter.Editor Agent: Executes the content editing task.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:
AgentState: We defined aTypedDictto serve as the shared state of our workflow.Annotatedandoperator.addare powerful mechanisms in LangGraph for handling state aggregation; here we use them to simulate the gradual accumulation of content.*_nodefunctions: These are our Agent nodes. For simplicity, they are just standard Python functions that receive thestate, print some simulated information, and return a dictionary that updates thestate. In real projects, these would be complex Agent classes calling LLMs or tools.should_researchfunction: This is the core condition function of this issue! It receives the currentAgentStateand returns"research"or"write"based on theresearch_neededfield set by thePlanneragent. This return value will serve as the decision basis foradd_conditional_edges.StateGraphinitialization: Creates our graph instance.add_node: Registers our Agent functions as nodes in the graph.set_entry_point("planner"): Explicitly specifies thatplanneris the starting point of the entire workflow.add_conditional_edges: Afterplanner, we no longer simply useadd_edge. We tell LangGraph that afterplanner, it needs to call theshould_researchfunction to decide where to go next.- If
should_researchreturns"research", the workflow routes to theresearchernode. - If
should_researchreturns"write", the workflow routes to thewriternode. - This perfectly implements the "whether research is needed" decision logic in our business.
- If
add_edge: Afterresearcherit always goes towriter, and afterwriterit always goes toeditor; these are fixed sequential routings.add_edge("editor", END): This is another key point! After theeditorcompletes its task, we explicitly connect the workflow toEND. This tells LangGraph that the entire workflow is complete, execution can stop, and the final state can be returned.app.invoke(initial_state): Runs our workflow. By passing in differentinitial_states (mainly thetopic), we can see how theplannerdynamically 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!
Mismatch between
condition_functionreturn value andmapping- Pitfall: Your
condition_functionreturns a string, but this string does not exist as a key in themapping_dictofadd_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_functionhave explicit mappings in themapping_dict. For complex conditions, consider adding a__default__key in themapping_dictto route unmapped return values to a default node (e.g., an error handling node or directly toEND).# 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 # })
- Pitfall: Your
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
ENDas 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 usingEND. Especially in complex multi-branch workflows, it easily causes misunderstandings or omissions. - How to Avoid: Always use
ENDas an explicit terminator. Any "successful" or "completed" business path should ultimately route toEND.ENDis a special keyword, not a regular node you define. It represents the execution boundary of the graph and makes your workflow intent much clearer.
- Pitfall: Some students think that as long as the last node finishes executing, the workflow naturally ends. Or they treat
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
Editoralways returns the content to theWriter, and theWriteralways writes a version that dissatisfies theEditor. - 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_countfield in theAgentState. Every time theEditorreturns a task to theWriter, incrementrevision_countby one. In theEditor's condition check, verify ifrevision_countexceeds a preset threshold. If it does, force the workflow to route toENDor a "human intervention" node. - Visual debugging: Use
app.get_graph().draw_png()ordraw_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.
- Introduce iteration counts or state variables: Add a
- Pitfall: When designing conditional edges, you accidentally create a loop, causing the workflow to loop infinitely under certain conditions. For example, the
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.
State Modification in Condition Functions
- Pitfall: Modifying the
stateinside thecondition_function. - Consequence: The responsibility of the
condition_functionis 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
stateand returning decision results, never modifying thestate. All modifications to thestateshould occur inside the nodes (functions registered withadd_node).
- Pitfall: Modifying the
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
STARTtoENDwith 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!