Part 04 | Routing Edges and the END Node
🎯 Learning Objectives for This Session
Welcome back to our LangGraph Multi-Agent Masterclass, future AI architects! In our last session, we built the "skeleton" of our Agents—the Nodes. But a skeleton alone isn't enough; we need "veins" to connect them, allowing energy (data) to flow through. In this session, we will dive deep into the core of LangGraph: Routing Edges and the END node. By the end of this session, you will:
- Master the two basic types of
add_edgein LangGraph: Learn how to build fixed routing and condition-based dynamic routing, ensuring your Agent collaboration is more than just "waiting in line." - Master the use of
add_conditional_edges: Understand the decision-making logic behind it and implement business-rule-based intelligent workflow distribution for our AI Content Agency. - Understand the essence of
set_entry_pointandset_finish_point(orEND): Build a well-structured Agent workflow with a clear beginning and end, preventing your programs from terminating silently or unexpectedly. - Accurately map complex real-world business logic to LangGraph routing edges: Elevate your architectural design skills in translating abstract requirements into executable code.
📖 Core Concepts Explained
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 first, which subsequent workshops it passes through for processing, and ultimately which exit it leaves from? This is exactly the problem that Edges and END solve.
In LangGraph, Nodes are your predefined Agents or tool functions—they are the "stations" in your workflow. Routing Edges, as the name suggests, 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 a finishing point:
set_entry_point(node_name): This is the "front door" of your workflow. All tasks begin here. Just like entering a subway station, there has to be an entrance, right?END: This is the "terminus" of your workflow. When the flow reaches here, it means the task is complete and computation stops. It is a special LangGraph keyword that explicitly tells the system: "Hey everyone, the job is done, let's wrap it up!"
2. Routing Edges: The Dynamic Skeleton of the Workflow
Edges are divided into two core types:
a) Fixed Edges (add_edge)
This is the simplest and most direct method. If you are certain that after Node A finishes executing, it must always pass control and state to Node B, use add_edge.
- Syntax:
graph.add_edge("source_node_name", "target_node_name") - Use Case: Linear, sequential steps. For example: After the
Researchercompletes its 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, dynamically choosing the next node to execute. This is the key to the Planner's intelligent decision-making in our AI Content Agency.
- Syntax:
graph.add_conditional_edges("source_node_name", condition_function, mapping_dict)source_node_name: Which node needs to make a decision after it finishes executing?condition_function: 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: A dictionary that maps the decision result returned by thecondition_functionto a specific "target node name." For example,{"research_needed": "researcher", "no_research": "writer"}.
- Use Case: Any scenario requiring dynamic workflow adjustments based on business logic. For example:
- The
Plannerevaluates content requirements and decides whether theResearcherneeds to be involved. - The
Editorreviews the first draft and decides whether to go straight toENDfor publishing or return it to theWriterfor revisions.
- The
3. Visualizing Core Concepts with Mermaid
Let's use a diagram to intuitively grasp the simplified workflow of our AI Content Agency, specifically focusing on how the Planner uses conditional edges to determine the flow's direction:
graph TD
A[START] --> B(Planner Agent)
B -- "Research needed?" --> 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 here.Planner Agent: This is our entry point node. It receives the initial request and performs 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 flow is directed to theResearcher Agent. - If
research_neededisFalse, the flow skips theResearcherand goes directly to theWriter Agent.
- If
Researcher Agent: Executes the research task.Writer Agent: Executes the content writing task. Regardless of whether it passes through theResearcher, the flow ultimately reaches theWriter.Editor Agent: Executes the content editing task.END: The task is finally complete.
Look at that—isn't this just translating the business logic of our "AI Content Agency" into the language of LangGraph? It's clear, intuitive, and controllable!
💻 Hands-On Code Practice (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 described above using LangGraph code.
Project Background Recap: Our AI Content Agency receives a content creation request. The Planner agent first evaluates this request to determine if in-depth research is needed. If yes, it hands the task to the Researcher; if no, it goes straight to the Writer. Finally, after the Editor completes the review, the entire workflow ends.
We will use LangGraph's StateGraph to build this flow.
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 to pretty-print the state
# --- 1. Define the shared State for our workflow ---
# This is the "canvas" where information is passed and shared among all Agents
class AgentState(TypedDict):
"""
A 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 decision 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/omitted for this session)
# --- 2. Define each Agent as a Node ---
# To focus on Edges and END in this session, we simplify Agents into standard Python functions.
# They 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 (规划者) 正在执行 ---")
topic = state["topic"]
# Simulate the Planner's complex decision-making logic
# Assume that if the topic contains keywords like "深入" (in-depth), "前沿" (cutting-edge), or "分析" (analysis), research is needed
if "深入" in topic or "前沿" in topic or "分析" in topic:
research_needed = True
print(f"主题 '{topic}' 复杂,需要深入研究。")
else:
research_needed = False
print(f"主题 '{topic}' 相对简单,无需专门研究。")
# Update state
return {
"research_needed": research_needed,
"draft_content": f"Planner: 针对主题 '{topic}' 的初步构思。\n"
}
def researcher_node(state: AgentState) -> AgentState:
"""
Researcher Agent: Conducts research based on the topic and provides research content.
"""
print("\n--- Researcher Agent (研究者) 正在执行 ---")
topic = state["topic"]
# Simulate the research process
simulated_research = f"Researcher: 为主题 '{topic}' 收集了以下关键信息:\n" \
f"- 事实1:关于 {topic} 的最新进展。\n" \
f"- 事实2:行业专家对 {topic} 的观点。\n" \
f"- 事实3:相关数据和统计。\n"
print("研究完成。")
# 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 (作者) 正在执行 ---")
topic = state["topic"]
planner_draft = state["draft_content"]
research_content = state.get("research_content", "无研究内容。") # Empty if no researcher was involved
# Simulate the writing process
full_draft = f"{planner_draft}\n" \
f"Writer: 整合了以下信息进行撰写:\n" \
f"--- 研究参考 ---\n{research_content}\n" \
f"--- 初稿正文 ---\n" \
f"尊敬的读者,欢迎阅读关于 '{topic}' 的文章...\n" \
f"这是文章的核心观点和初步阐述。\n" \
f"期待编辑的反馈。\n"
print("初稿撰写完成。")
# 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 (编辑者) 正在执行 ---")
draft_content = state["draft_content"]
# Simulate the editing process
final_version = f"Editor: 对以下初稿进行了润色和校对。\n" \
f"========== 原始初稿 ==========\n{draft_content}\n" \
f"========== 最终版本 ==========\n" \
f"经过精心编辑,这篇关于 '{state['topic']}' 的文章已准备就绪,质量上乘,表达流畅。\n" \
f"确保了信息准确性和阅读体验。\n"
print("编辑审校完成,内容可发布。")
# Update state
return {"final_content": final_version}
# --- 3. Define the Condition Function ---
# This is the key to LangGraph determining the flow direction!
def should_research(state: AgentState) -> str:
"""
Based on the 'research_needed' flag set by the Planner agent in the state,
decides whether the next step is to go to the 'researcher' or directly to the 'writer'.
"""
print("\n--- 流程判断: 是否需要研究?---")
if state["research_needed"]:
print("判断结果:需要研究,将转向 Researcher Agent。")
return "research" # Returns a string corresponding to a key in mapping_dict
else:
print("判断结果:无需研究,将转向 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 session'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 session! After the planner finishes, based on the result of the should_research function,
# it decides whether to connect to the researcher or the writer.
workflow.add_conditional_edges(
"planner", # Source node: makes the decision after planner finishes
should_research, # Condition function: determines direction based on state
{
"research": "researcher", # If should_research returns "research", go to researcher node
"write": "writer" # If should_research returns "write", go to writer node
}
)
# Add Fixed Edges
# After researcher finishes, always pass the result to writer
workflow.add_edge("researcher", "writer")
# After writer finishes, always pass the draft to editor
workflow.add_edge("writer", "editor")
# Set the Finish Point
# After 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========== 运行场景 1: 需要深入研究的主题 ==========")
initial_state_1 = {"topic": "深入探讨大语言模型在多智能体协作中的前沿应用",
"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--- 场景 1 最终状态 ---")
pprint(final_state_1)
print("\n----------------------------------------------------\n")
print("\n========== 运行场景 2: 相对简单的主题 ==========")
initial_state_2 = {"topic": "如何用Python实现一个简单的Web服务器",
"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--- 场景 2 最终状态 ---")
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"无法生成可视化图表:{e}")
# print("请确保安装了 'graphviz' 库和其系统依赖。")
Code Breakdown:
AgentState: We defined aTypedDictto serve as the shared state for our workflow.Annotatedandoperator.addform LangGraph's powerful mechanism for handling state aggregation; here, we use it to simulate the progressive 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 then return a dictionary that updates thestate. In a real project, these would be complex Agent classes calling LLMs or tools.should_researchfunction: This is the core conditional logic function for this session! It receives the currentAgentStateand returns"research"or"write"based on theresearch_neededfield set by thePlanneragent. This return value serves 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 flow.add_conditional_edges: Afterplanner, we don't just use a simpleadd_edge. We tell LangGraph that afterplanner, it needs to call theshould_researchfunction to decide where to go next.- If
should_researchreturns"research", the flow goes to theresearchernode. - If
should_researchreturns"write", the flow goes to thewriternode. - This perfectly implements the "is research needed?" decision logic in our business requirements.
- If
add_edge: Afterresearcher, it always goes towriter; afterwriter, it always goes toeditor. These are fixed, sequential flows.add_edge("editor", END): This is another crucial point! After theeditorcompletes its task, we explicitly connect the flow toEND. This tells LangGraph that the entire workflow is finished, execution can stop, and the final state can be returned.app.invoke(initial_state): Runs our workflow. By passing in differentinitial_stateobjects (primarily differenttopics), we can see how theplannerdynamically guides the flow 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, ultimately converging at the Writer and Editor, and gracefully terminating via END.
⚠️ Pitfalls and Best Practices
As a senior mentor, I've seen too many students stumble over Edges and END. Let's fill in these potholes ahead of time!
Mismatch between
condition_functionreturn value andmapping- The Pitfall: Your
condition_functionreturns a string, but this string does not exist as a key in themapping_dictofadd_conditional_edges. - The Consequence: LangGraph will throw a
ValueError, indicating it cannot find the corresponding target node. - Best Practice: Always ensure that all possible return values of your
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).# Incorrect Example # def my_condition(state): return "unknown_path" # graph.add_conditional_edges("source", my_condition, {"path_a": "node_a"}) # "unknown_path" is not matched # Best Practice Example: Using __default__ # graph.add_conditional_edges("source", my_condition, { # "path_a": "node_a", # "path_b": "node_b", # "__default__": "error_handler_node" # Or directly to END # })
- The Pitfall: Your
Misunderstanding and Omitting
END- The Pitfall: Some developers assume that once the last node finishes executing, the flow naturally ends. Or, they treat
ENDas a regular node. - The Consequence: If a path is not explicitly connected to
END, when the last active node on that path finishes, 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. In complex multi-branch workflows, this easily leads to misunderstandings or omissions. - Best Practice: Always use
ENDas an explicit terminator. Any "successful" or "completed" business path should ultimately lead toEND.ENDis a special keyword, not a standard node you define. It represents the execution boundary of the graph and makes your workflow's intent much clearer.
- The Pitfall: Some developers assume that once the last node finishes executing, the flow naturally ends. Or, they treat
Circular Dependencies and Infinite Loops
- The Pitfall: When designing conditional edges, you accidentally create a loop, causing the flow to loop infinitely under certain conditions. For example, the
Editoralways returns the content to theWriter, and theWriteralways produces a version theEditoris unhappy with. - The Consequence: The program enters an infinite loop, exhausts resources, and eventually crashes.
- Best Practice:
- Introduce iteration counters or state variables: Add a
revision_countfield toAgentState. 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 flow toENDor to a "human intervention" node. - Visual debugging: Use
app.get_graph().draw_png()ordraw_mermaid()to visualize your workflow. Complex conditional edges and loop paths are hard to spot with the naked eye, but a diagram makes them obvious at a glance.
- Introduce iteration counters or state variables: Add a
- The Pitfall: When designing conditional edges, you accidentally create a loop, causing the flow to loop infinitely under certain conditions. For example, the
Uniqueness of
set_entry_point- The Pitfall: Attempting to set multiple entry points, or forgetting to set one altogether.
- The Consequence: LangGraph requires a graph to have exactly one
set_entry_point. Forgetting to set it means the graph cannot start. - Best Practice: A graph can only have one
set_entry_point. If your business logic needs to start from different places, it likely means you need to design multiple independent LangGraph graphs, or add a "dispatcher" node before the entry point to determine the actual first business node.
Modifying State Inside the Condition Function
- The Pitfall: Modifying the
stateinside thecondition_function. - The Consequence: The responsibility of the
condition_functionis to read the state and make a decision, not to modify the state. Although Python allows you to modify mutable objects passed in, doing so breaks LangGraph's design philosophy, leading to chaotic state management and making issues hard to track. - Best Practice: The condition function should be a Pure Function. It should only read the
stateand return a decision result, never modifying thestate. All modifications to thestateshould occur inside the nodes (the functions registered viaadd_node).
- The Pitfall: Modifying the
By mastering these points, your control over LangGraph workflows will significantly increase, and the systems you build will be much more robust and maintainable.
📝 Summary of This Session
Everyone, in this session we took a deep dive into the lifelines connecting our Agents in LangGraph—Routing Edges and the END node. We learned how to use add_edge to build fixed flows, and how to implement dynamic, business-logic-based workflow distribution using add_conditional_edges and condition_function. At the same time, we clarified the crucial roles of set_entry_point and END in defining the lifecycle of a complete workflow.
Through our hands-on practice with the "AI Content Agency" project, you should now be able to:
- Clearly translate business decisions like "is research needed?" into LangGraph conditional edges.
- Build a multi-agent collaboration workflow from
STARTtoENDwith explicit starting and finishing points. - Avoid common traps, such as condition mismatches, missing
ENDnodes, or circular dependencies.
From now on, your LangGraph workflows will no longer be rigid, linear executions. Instead, they will act like a true intelligent system with a "brain," making judgments based on real-time situations and flexibly adjusting the flow. This means your AI Content Agency is now better equipped to adapt to a wide variety of content creation demands!
In our next session, we will go even deeper and explore how to make our Agents do more than just execute sequentially. We will introduce Parallelism, further boosting our agency's efficiency and its ability to handle complex tasks! Stay tuned!