Issue 03 | Business Nodes (Nodes): The True Executors of the Graph

Updated on 4/13/2026

🎯 Learning Objectives for This Issue

Hello students, I am your old friend Lao Zhang, someone who has been navigating the AI architecture circle for ten years, losing quite a bit of hair but with undiminished passion. In the last issue, we got acquainted with LangGraph's worldview and learned how it uses graph structures to orchestrate complex AI workflows. But having just the skeleton of a graph is not enough; the skeleton needs flesh and blood, and workers capable of getting things done. In this issue, we will dive deep into the core execution units of LangGraph—Business Nodes.

After completing this issue, you will:

  1. Thoroughly understand the essence of a Node in LangGraph: It is not just a function, but rather a "department" or "expert" in our AI agency, responsible for executing specific business logic.
  2. Master how to write basic Worker Nodes: Learn to encapsulate Large Language Model (LLM) reasoning logic so that our Planner Agent can truly spring into action and start thinking.
  3. Be proficient in data modification and state management within nodes: Understand how a Node interacts with the Graph's global State, ensuring smooth information flow between different Agents.
  4. Build the first core node of the Agency project: Construct the Planner node for our "AI Universal Content Creation Agency," taking the first step toward building a complex multi-agent workflow.

📖 Principle Analysis

Students, imagine our "AI Universal Content Creation Agency" as a high-tech company. The company has various departments: Planning, R&D, Content Creation, Editorial, and so on. Each department has its unique functions: receiving tasks, processing information, and then passing the results to the next department.

In the world of LangGraph, these "departments" or "experts" are our Nodes (Business Nodes).

At its core, a Node is simply a Callable. This function receives the current Graph State (which you can think of as a "project progress report" or "task board" shared across the entire company), executes its own business logic (such as calling an LLM to think, querying a database, or invoking external tools), and then returns an update to the State.

Core Idea: A Node receives the State, processes it, and returns a "delta" (difference) of the State.

Why a "delta"? This is a brilliant aspect of LangGraph and many functional programming paradigms: State Immutability. Each node does not directly modify the global State; instead, it generates a new State fragment, and the LangGraph framework is responsible for merging these fragments into the global State. This greatly simplifies the complexity of concurrency and debugging. You always know "what changes" each node made, rather than "what it mutated."

The Node in Our AI Content Agency

In our "AI Universal Content Creation Agency," the first heavyweight character to make an appearance is the Planner. Its responsibility is to receive the client's raw requirements and break them down into actionable, specific content creation tasks.

  • Input: The initial client requirement (topic).
  • Processing: The Planner node will call a Large Language Model (LLM), asking it to conceptualize a content outline, identify the target audience, extract core viewpoints, and plan subsequent creation steps based on the topic.
  • Output: Update the State by adding plan_outline, target_audience, research_tasks (list of research tasks), and other information.

This process perfectly embodies the lifecycle of a Node.

Mermaid Diagram: Introduction of the Planner Node

Let's use a Mermaid diagram to visually see how the Planner node operates within our Graph. It will be our first core node, laying the foundation for the entire content creation workflow.

graph TD
    A[User Input: Initial Creation Requirement] --> B{Initialize Graph State};
    B --> C(Planner Node);
    
    subgraph Planner Node Internal Logic
        C --> C1[Receive Current State];
        C1 --> C2[Call LLM for Content Planning];
        C2 --> C3[Generate Content Outline, Research Tasks, etc.];
        C3 --> C4[Return State Update];
    end
    
    C --> D{Graph State Update};
    D --> E[Prepare for Next Stage: e.g., Research Node];
    
    style A fill:#f9f,stroke:#333,stroke-width:2px;
    style B fill:#bbf,stroke:#333,stroke-width:2px;
    style C fill:#ccf,stroke:#333,stroke-width:2px;
    style C1 fill:#fcf,stroke:#333,stroke-width:1px;
    style C2 fill:#fcf,stroke:#333,stroke-width:1px;
    style C3 fill:#fcf,stroke:#333,stroke-width:1px;
    style C4 fill:#fcf,stroke:#333,stroke-width:1px;
    style D fill:#bbf,stroke:#333,stroke-width:2px;
    style E fill:#afa,stroke:#333,stroke-width:2px;

Diagram Explanation:

  • User Input: The starting point of everything, the raw requirement given to us by the client.
  • Initialize Graph State: LangGraph will create an initial state based on the State structure we defined.
  • Planner Node: This is the node we will focus on implementing in this issue. It receives the current State.
  • Planner Node Internal Logic:
    • Receive Current State: Obtain all context information of the current workflow.
    • Call LLM for Content Planning: This is the core business logic where we interact with the LLM, letting it play the role of a "Planner."
    • Generate Content Outline, Research Tasks, etc.: The LLM's output will be structured and become the input for our subsequent Agents.
    • Return State Update: The node will generate a dictionary containing the new information to be added to the State.
  • Graph State Update: The LangGraph framework is responsible for merging the updates returned by the Planner Node into the global State.
  • Prepare for Next Stage: The updated State will serve as input and be passed to the next node in the graph (for example, the future Researcher Node).

Once you understand this flow, you grasp the essence of LangGraph: Each node is a pure function that receives a state and returns a state update, and the entire system achieves complex workflows through the routing and aggregation of these states.

💻 Practical Code Walkthrough

Alright, enough theory, let's roll up our sleeves and get to work. Now, we will write the first core business node for our "AI Universal Content Creation Agency": the Planner Node.

First, we need to define our Graph State. This State will be the shared information hub for all Agents. For convenience, we use TypedDict to define it, which acts as a data structure with explicitly defined fields and types.

# agency_state.py
from typing import TypedDict, List, Dict, Optional

class AgencyState(TypedDict):
    """
    Shared state of the AI Content Creation Agency.
    This TypedDict defines the data structure shared and manipulated by all Agents throughout the LangGraph workflow.
    """
    topic: str  # Initial content creation topic
    plan_outline: Optional[str]  # Content outline generated by the Planner
    target_audience: Optional[str] # Target audience identified by the Planner
    research_tasks: Optional[List[str]] # List of research tasks planned by the Planner
    research_results: Optional[Dict[str, str]] # Research results collected by the Researcher
    draft_content: Optional[str] # First draft written by the Writer
    editor_feedback: Optional[str] # Revision feedback provided by the Editor
    final_content: Optional[str] # Finalized content

    # You can also add other fields, for example:
    # current_agent_name: str # Name of the currently executing Agent, used for debugging or logging
    # error_message: Optional[str] # Error message, used for error handling

Next, let's write the code for the Planner Node. It will be a Python function that receives the AgencyState and returns a dictionary representing the updates to the AgencyState.

We will use ChatOpenAI to simulate the LLM's capabilities. If you don't have an OpenAI API Key, you can use other LangChain-compatible LLMs, or simply return hardcoded mock data.

# nodes.py
import os
from typing import Dict, Any, List
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv

# Load environment variables from the .env file, e.g., OPENAI_API_KEY
load_dotenv()

# Import our defined AgencyState
from agency_state import AgencyState

# Initialize the LLM
# Here we use GPT-4o, you can choose other models as needed
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

# --- 1. Planner Node ---
def planner_node(state: AgencyState) -> Dict[str, Any]:
    """
    Planner Node: Generates a content outline, target audience, and research tasks based on the initial topic.
    This node encapsulates the LLM reasoning logic used to translate high-level requirements into specific execution plans.
    
    Args:
        state (AgencyState): The current global state.
                              It is expected that the state contains a 'topic' field.

    Returns:
        Dict[str, Any]: A dictionary containing updates to the state.
                        It will add 'plan_outline', 'target_audience', 'research_tasks'.
    """
    print("\n--- Executing Planner Node ---")
    topic = state.get("topic")
    if not topic:
        raise ValueError("The 'topic' field is missing in AgencyState; the Planner cannot work.")

    # 1. Define the LLM Prompt Template
    # We want the LLM to not only provide an outline but also think about the audience and subsequent research points
    planner_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", "You are an experienced content strategist and planner. Your task is to develop a detailed content creation plan based on the given topic."),
            ("human", """
            Please generate a detailed content creation plan for the following topic. The plan should include:
            1.  **Plan Outline**: Clearly structured, containing at least 3-5 main sections and key points for each section.
            2.  **Target Audience**: A detailed description of who the ideal reader for this content is, their interests, pain points, and knowledge level.
            3.  **Research Tasks**: List at least 3-5 specific research tasks to ensure the depth and accuracy of the content. These tasks should be completed by the subsequent Researcher Agent.
            
            Topic: {topic}
            
            Please return the result in JSON format, for example:
            ```json
            {{
                "plan_outline": "...",
                "target_audience": "...",
                "research_tasks": ["...", "...", "..."]
            }}
            ```
            """)
        ]
    )

    # 2. Build the LLM Chain
    # Use .with_structured_output(dict) to ensure the LLM outputs a structured dictionary
    # Note: with_structured_output depends on the model's capabilities. If the model doesn't support it, a custom parser might be needed.
    # For models like GPT-4o, it usually follows JSON format instructions very well.
    planner_chain = planner_prompt | llm.with_structured_output(
        schema={
            "type": "object",
            "properties": {
                "plan_outline": {"type": "string", "description": "Detailed content outline"},
                "target_audience": {"type": "string", "description": "Target audience description"},
                "research_tasks": {"type": "array", "items": {"type": "string"}, "description": "List of research tasks to be conducted"},
            },
            "required": ["plan_outline", "target_audience", "research_tasks"],
        }
    )

    # 3. Execute LLM Reasoning
    try:
        planning_output = planner_chain.invoke({"topic": topic})
        print("Planner successfully generated the plan.")
        # Directly return the LLM's output as the state update
        # with_structured_output already returns a dictionary, so it can be used directly
        return planning_output
    except Exception as e:
        print(f"Planner Node execution failed: {e}")
        # If the LLM call fails, we can choose to raise an exception or return an update containing the error message
        # Here we choose to return an error message so the Graph can handle it
        return {"error_message": f"Planner failed: {str(e)}"}

# --- Simulate running the Planner Node ---
if __name__ == "__main__":
    print("--- Simulating Planner Node Run ---")
    
    # 1. Simulate the initial state
    initial_state: AgencyState = {
        "topic": "Exploring the core advantages and practical use cases of LangGraph in building multi-agent applications",
        "plan_outline": None,
        "target_audience": None,
        "research_tasks": None,
        "research_results": None,
        "draft_content": None,
        "editor_feedback": None,
        "final_content": None,
    }
    
    print(f"Initial State: {initial_state['topic']}")
    
    # 2. Call the Planner Node
    updated_fields = planner_node(initial_state)
    
    # 3. Merge updates into the state (The LangGraph framework will do this step)
    # Here we manually simulate the merge
    final_state = initial_state.copy()
    for key, value in updated_fields.items():
        # If it's a list or dict, more complex merge logic is needed; here we simplify by directly overwriting
        final_state[key] = value

    print("\n--- State Update After Planner Node Execution ---")
    print(f"Content Outline: \n{final_state.get('plan_outline')}")
    print(f"Target Audience: {final_state.get('target_audience')}")
    print(f"Research Tasks: {final_state.get('research_tasks')}")
    print(f"Complete Final State: {final_state}")

    # Try another topic
    print("\n--- Simulating Planner Node Run Again (New Topic) ---")
    initial_state_2: AgencyState = {
        "topic": "AI Ethics and Regulation: Challenges and Opportunities",
        "plan_outline": None,
        "target_audience": None,
        "research_tasks": None,
        "research_results": None,
        "draft_content": None,
        "editor_feedback": None,
        "final_content": None,
    }
    updated_fields_2 = planner_node(initial_state_2)
    final_state_2 = initial_state_2.copy()
    for key, value in updated_fields_2.items():
        final_state_2[key] = value

    print("\n--- State Update After Planner Node Execution (New Topic) ---")
    print(f"Content Outline: \n{final_state_2.get('plan_outline')}")
    print(f"Target Audience: {final_state_2.get('target_audience')}")
    print(f"Research Tasks: {final_state_2.get('research_tasks')}")

Code Analysis:

  1. AgencyState Definition: We first defined AgencyState as a TypedDict. This is the "shared memory" of the entire agency, through which all Agents read and write information. Clearly defining the state structure is the cornerstone of building a robust multi-agent system.
  2. planner_node Function:
    • Input: It receives a state: AgencyState parameter. This is the current global state passed in by LangGraph.
    • Business Logic Encapsulation:
      • We extract the topic from the state.
      • We build a ChatPromptTemplate, which is the core of interacting with the LLM. We carefully designed the Prompt, asking the LLM to act as a "content strategist" and return the results in a structured JSON format containing the outline, audience, and research tasks.
      • Using llm.with_structured_output is a very powerful technique. It guides the LLM to directly output data that conforms to our defined JSON Schema, greatly simplifying subsequent parsing work. It's like putting a pair of "structured output" glasses on the LLM, allowing it to see more clearly and articulate better.
      • Call planner_chain.invoke() to execute the LLM reasoning.
    • Output: The function returns a dictionary. The key-value pairs in this dictionary will be used to update the AgencyState. Note that we only returned the fields that need to be added or modified, not the entire AgencyState. LangGraph will automatically merge these updates into the current global state.
    • Error Handling: A try-except block is added to handle potential errors during the LLM call, providing better robustness.
  3. Simulated Run (if __name__ == "__main__":):
    • This part of the code demonstrates how to test our planner_node independently without the complete LangGraph framework.
    • We created an initial_state and then called planner_node.
    • Finally, we manually simulated the process of LangGraph merging the updates returned by the node into the initial_state, and printed the final state to verify the node's functionality.

Through this Planner Node, we have successfully encapsulated a complex "content planning" task into an executable, reusable, and testable LangGraph node. It receives input, leverages the capabilities of a large model for complex reasoning, and then outputs results in a structured manner, updating our agency's shared project state. This perfectly embodies the Node as the true executor of the Graph!

Common Pitfalls and How to Avoid Them

As a senior AI architect, I have seen too many pitfalls. Now, I will share some of the most common and easily triggered "mines" with you, so you can avoid taking detours.

  1. Chaotic State Management: Misunderstanding Node Return Values

    • Pitfall: A common mistake made by beginners is directly returning a complete AgencyState object in a Node, or returning a dictionary that does not match the AgencyState structure.
    • Consequence: This leads to incorrect state updates, ranging from data loss to the collapse of the entire Graph logic. LangGraph expects you to return a dictionary where the key-value pairs are meant to be added to or overwrite the corresponding fields in the current State.
    • Avoidance Guide:
      • Only return the delta: Your Node function should only return a dictionary containing the State fields you wish to update or add. For example, planner_node only returns {"plan_outline": "...", "target_audience": "...", "research_tasks": [...]}.
      • Type Matching: Ensure the types of the fields in the returned dictionary match the types defined in AgencyState. Strict type definitions using TypedDict or Pydantic Models can help you avoid many issues.
      • Understand Merge Logic: LangGraph's default StateGraph performs a shallow merge on dictionaries. This means if you return {"research_results": {"new_key": "value"}}, it will directly replace the entire old research_results dictionary, rather than merging the internal keys. If you need a deep merge, you must customize the reducer method of the StateGraph.
  2. Tight Coupling of LLM Reasoning Logic and Business Logic

    • Pitfall: Stuffing all LLM calls, Prompt engineering, output parsing, and data processing logic into one massive Node function.
    • Consequence: The code becomes hard to read, maintain, and test. If the Prompt needs adjustment or the LLM model needs to be switched, the entire Node might need to be rewritten.
    • Avoidance Guide:
      • Single Responsibility Principle (SRP): A Node should only be responsible for one core business function. For example, the Planner Node is only responsible for planning, not researching or writing.
      • Modular Encapsulation: Encapsulate LLM-related logic (Prompt definition, Chain construction, parsers) into independent functions or classes. Just like planner_prompt | llm.with_structured_output(...) in our code, chained calls make the logic clearer.
      • Externalize Prompts: Consider storing complex Prompts in external files or configurations for easier management and iteration.
  3. Lack of Robustness: Inadequate Error Handling

    • Pitfall: Failing to handle exceptions such as LLM call failures, API timeouts, or data parsing errors.
    • Consequence: In a production environment, once a network issue occurs or the LLM returns an unexpected format, the entire workflow will be interrupted, resulting in a terrible user experience.
    • Avoidance Guide:
      • try-except is your friend: Always wrap LLM calls and other potentially failing operations with try-except, just like we did in planner_node.
      • Graceful Degradation or Error States: When an error occurs, do not let the program crash directly. You can return a special State update (e.g., {"error_message": "..."}) and let subsequent Nodes or the Graph's conditional_edge evaluate and handle these errors, such as jumping to an error handling flow or retrying.
      • Logging: Detailed logs can help you quickly locate problems. Print logs at the beginning and end of each Node, as well as at key steps.
  4. Environment Configuration and Dependency Management

    • Pitfall: Hardcoding API keys or failing to load environment variables correctly.
    • Consequence: Poor code security, deployment difficulties, and inconsistent behavior across different environments.
    • Avoidance Guide:
      • Use Environment Variables: All sensitive information (like API keys) should be managed via environment variables. We used python-dotenv to load the .env file, which is a good practice.
      • requirements.txt: Always maintain a clear requirements.txt file to ensure all dependencies can be installed correctly.

Remember, building a multi-agent system is like constructing a high-rise building; each Node is a brick. Only when every brick is solid and every connection is secure can the building stand tall.

📝 Summary of This Issue

Congratulations, future AI architects! In this issue, we not only deeply understood the core concept of the LangGraph Node—how it acts as the true executor of the Graph by receiving state, executing business logic, and returning state updates—but more importantly, we built the first crucial department for our "AI Universal Content Creation Agency" with our own hands: the Planner Node.

We learned how to:

  • Define a clear shared state AgencyState, which is the foundation for all our Agents to collaborate.
  • Write a fully functional Node function, encapsulating the LLM's reasoning logic and making it play the role of a "content strategist" through a carefully designed Prompt.
  • Utilize llm.with_structured_output to ensure the structured and reliable output of the LLM.
  • Understand the mechanism of a Node returning state updates, rather than directly modifying the global state.

Through the practical exercise of the Planner Node, you have taken the first step toward building complex multi-agent workflows. It translates the user's high-level requirements into specific, actionable creation plans, paving the way for subsequent Agents like the Researcher and Writer.

Of course, we also discussed common "pitfalls" when building Nodes and how to gracefully avoid them. These are valuable experiences that will enable you to take charge of large-scale projects in the future.

In the next issue, we will dive deeper, truly integrating our Planner Node into LangGraph's StateGraph, and start defining the Edges of the graph, allowing our Planner to seamlessly pass its generated work plans to the next agent. Stay tuned!