lesson-30

20 MIN READ | UPDATED: 2026-05-07

From receiving instructions, conducting web-wide research, drafting, and editorial review, to final typesetting—the skills we've built over thirty lessons will culminate perfectly in this 500-line Graph.

Welcome, everyone, to the grand finale of the LangGraph Multi-Agent Masterclass! I'm your instructor, your old friend who has been with you for the past thirty lessons.

Looking back at our journey, we started from the most basic single-node LLM calls and leveled up step by step. We conquered Tool Calling, Memory mechanisms, Human-in-the-loop, and various complex Routing logics. Do you remember the goal we set in Part 1? We promised to build a "Universal AI Content Agency" from scratch.

Today is the moment of truth.

In real-world enterprise AI architectures, lone-wolf Agents can no longer survive. What you need is a highly collaborative "virtual company." Today, we will take the scattered modules we built over the previous 29 lessons—the strategic Planner, the tireless Researcher, the sharp-penned Writer, the nitpicking Editor, and the layout-savvy Publisher—and wire them all into one massive StateGraph.

Take a deep breath, open your IDE, and let's place this final puzzle piece.


🎯 Learning Objectives for This Session

In this capstone showcase, you will gain the following advanced skills:

  1. Master Global State Management: Define a "data bus" capable of carrying the entire content production lifecycle, allowing 5 independent Agents to share and safely modify the context.
  2. Build Complex Conditional Routing & Circuit Breakers: Implement the back-and-forth dynamic (rejection and rewriting) between the Editor and the Writer, and set a maximum revision limit to prevent infinite loops.
  3. Complete End-to-End Orchestration: Transform thirty lessons of theory into runnable, industry-grade Capstone project code.

📖 Architecture Breakdown

Before writing code, as always, let's look at the architecture diagram. Don't just roll up your sleeves and start typing—senior architects spend 70% of their time drawing diagrams and designing State.

Our AI Content Agency workflow is as follows:

  1. User: Submits a broad topic (e.g., "Write an in-depth analysis on the dismal sales of Apple Vision Pro").
  2. Planner: Receives the instruction and breaks it down into a detailed writing outline and core research questions.
  3. Researcher: Uses the outline to call search engine tools (mocked here) to gather the latest data across the web.
  4. Writer: Takes the outline and research data, and works hard to draft the initial article.
  5. Editor: Steps in to review the draft with extremely strict standards. If it falls short, they provide Review Comments and send it back to the Writer for a rewrite.
  6. Publisher: Once the Editor approves (or the maximum retry limit is reached, forcing a compromise), the Publisher takes over for final Markdown typesetting and image placement (mocked), outputting the final draft.

Let's visualize this grand workflow using Mermaid:

graph TD
    %% Define styles
    classDef human fill:#f9f,stroke:#333,stroke-width:2px;
    classDef agent fill:#bbf,stroke:#333,stroke-width:2px;
    classDef router fill:#fbf,stroke:#333,stroke-width:2px;
    classDef endnode fill:#bfb,stroke:#333,stroke-width:2px;

    START((Start)) --> UserInput[User Inputs Topic]:::human
    UserInput --> Planner[Planner: Break Down Outline & Research Direction]:::agent
    Planner --> Researcher[Researcher: Execute Web Research]:::agent
    Researcher --> Writer[Writer: Draft/Revise Article]:::agent
    Writer --> Editor[Editor: Quality Review]:::agent
    
    Editor --> EditorRouter{Approved?}:::router
    
    EditorRouter -- No (Send Back for Revision) --> Writer
    EditorRouter -- Yes (Or Max Revisions Reached) --> Publisher[Publisher: Typesetting & Finalization]:::agent
    
    Publisher --> END((End)):::endnode

    %% State explanation box
    subgraph State [Global State (Data Bus)]
        direction LR
        topic[Topic]
        outline[Outline]
        research[Research Data]
        draft[Current Draft]
        comments[Review Comments]
        revision_count[Revision Count]
    end

Instructor's Sharp Insight: Looking at this diagram, many of you might ask: "Instructor, why not let the Researcher search again when the Writer is revising?" That is an excellent question! In the industry, this depends entirely on your cost and latency budgets. To ensure the clarity of today's 500 lines of code, we configured the Researcher to conduct deep research only once at the beginning. Subsequent revisions by the Writer are based solely on the Editor's feedback. Knowing how to subtract from an architecture is the mark of a true master.


💻 Hands-On Code Walkthrough

Enough talk, let's look at the code. This snippet is the crystallization of our thirty lessons of hard work. To ensure you can run it out of the box, I used langchain_openai and mocked some of the time-consuming Tools.

Please read the comments in the code carefully; they hide a wealth of practical details.

import operator
from typing import TypedDict, Annotated, List
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END

# ==========================================
# 1. Define the Global State
# ==========================================
# Instructor's Note: State is the central database of our Agency.
# Note the use of Annotated for revision_count; it will automatically +1 upon each rejection.
class AgencyState(TypedDict):
    topic: str                      # Initial topic input by the user
    outline: str                    # Outline generated by the Planner
    research_data: str              # Research materials gathered by the Researcher
    draft: str                      # Article draft written by the Writer
    review_comments: str            # Revision comments provided by the Editor
    revision_count: Annotated[int, operator.add] # Tracks the number of times rejected by the Editor
    final_article: str              # Final article produced by the Publisher

# Initialize LLM (GPT-4o or Claude-3.5-Sonnet is recommended for best multi-agent results)
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

# ==========================================
# 2. Define Agent Nodes
# ==========================================

def planner_node(state: AgencyState):
    """Planner: Responsible for breaking down a broad topic into a structured outline"""
    print("👨‍💼 [Planner] Breaking down the topic and creating an outline...")
    sys_msg = SystemMessage(content="You are a senior media editor-in-chief. Based on the user's topic, output a detailed writing outline containing 3-4 core paragraphs.")
    user_msg = HumanMessage(content=f"Topic: {state['topic']}")
    
    response = llm.invoke([sys_msg, user_msg])
    # State update: Write the outline to State and initialize revision_count to 0
    return {"outline": response.content, "revision_count": 0}

def researcher_node(state: AgencyState):
    """Researcher: Gathers materials based on the outline (using mock data here instead of a real Search Tool)"""
    print("🕵️‍♂️ [Researcher] Scouring the web for in-depth materials...")
    # In a real project, you would bind_tools(SearchTool) here and run a loop.
    # To ensure the Capstone runs smoothly, we let the LLM generate pseudo-research data directly based on the outline.
    sys_msg = SystemMessage(content="You are an ace researcher. Based on the editor's outline, provide rich data, case studies, and facts as writing materials.")
    user_msg = HumanMessage(content=f"Outline:\n{state['outline']}")
    
    response = llm.invoke([sys_msg, user_msg])
    return {"research_data": response.content}

def writer_node(state: AgencyState):
    """Writer: Drafts the article combining the outline, materials, and any potential review comments"""
    print(f"✍️ [Writer] Writing furiously (Current revision count: {state.get('revision_count', 0)})...")
    
    sys_prompt = "You are a gold-medal lead writer. Please write an engaging article based on the outline and research data."
    # If there are review comments from the Editor, the Writer must follow them
    if state.get("review_comments"):
        sys_prompt += f"\n\nAttention! These are the editor-in-chief's revision comments, you must comply:\n{state['review_comments']}"
        
    sys_msg = SystemMessage(content=sys_prompt)
    user_msg = HumanMessage(content=f"Outline:\n{state['outline']}\n\nMaterials:\n{state['research_data']}")
    
    response = llm.invoke([sys_msg, user_msg])
    return {"draft": response.content}

def editor_node(state: AgencyState):
    """Editor: Strict quality controller. Decides whether the article moves to the next stage or is sent back for a rewrite"""
    print("🧐 [Editor] Reviewing the draft with a magnifying glass...")
    sys_msg = SystemMessage(content="""
    You are an extremely strict editorial director. Please review the draft.
    If you think it's perfect, reply ONLY with "APPROVED".
    If it needs improvement, list specific revision comments (do not rewrite it yourself, just point out the issues).
    """)
    user_msg = HumanMessage(content=f"Current Draft:\n{state['draft']}")
    
    response = llm.invoke([sys_msg, user_msg])
    comments = response.content
    
    if "APPROVED" in comments.upper():
        print("   ✅ [Editor] Approved!")
        return {"review_comments": "APPROVED"}
    else:
        print("   ❌ [Editor] Rejected, sent back for rewrite!")
        # Key point: When rejecting, besides recording comments, increment revision_count by 1
        return {"review_comments": comments, "revision_count": 1}

def publisher_node(state: AgencyState):
    """Publisher: Final typesetting and beautification"""
    print("🖨️ [Publisher] Performing exquisite Markdown typesetting and generating the final draft...")
    sys_msg = SystemMessage(content="You are a typesetting master. Please add appropriate Markdown headings, bold key points, and insert [Image Placeholder] in suitable places.")
    user_msg = HumanMessage(content=f"Finalized Content:\n{state['draft']}")
    
    response = llm.invoke([sys_msg, user_msg])
    return {"final_article": response.content}

# ==========================================
# 3. Core Routing Logic (Conditional Routing)
# ==========================================
def editor_router(state: AgencyState) -> str:
    """
    Decides the path after the Editor.
    Circuit Breaker: If revision count exceeds 2, force approval to prevent infinite loops.
    """
    if state["review_comments"] == "APPROVED":
        return "to_publisher"
    elif state["revision_count"] >= 2:
        print("   ⚠️ [System] Max revisions reached, circuit breaker triggered, forcing entry into typesetting phase!")
        return "to_publisher"
    else:
        return "back_to_writer"

# ==========================================
# 4. Build the Capstone Graph
# ==========================================
workflow = StateGraph(AgencyState)

# Add all nodes
workflow.add_node("Planner", planner_node)
workflow.add_node("Researcher", researcher_node)
workflow.add_node("Writer", writer_node)
workflow.add_node("Editor", editor_node)
workflow.add_node("Publisher", publisher_node)

# Define edges (workflow sequence)
workflow.add_edge(START, "Planner")
workflow.add_edge("Planner", "Researcher")
workflow.add_edge("Researcher", "Writer")
workflow.add_edge("Writer", "Editor")

# Add conditional edges (Editor's decision)
workflow.add_conditional_edges(
    "Editor",
    editor_router,
    {
        "to_publisher": "Publisher",
        "back_to_writer": "Writer"
    }
)

workflow.add_edge("Publisher", END)

# Compile Graph
agency_app = workflow.compile()

# ==========================================
# 5. The Moment of Truth: Run the Demo
# ==========================================
if __name__ == "__main__":
    print("\n🚀 Welcome to the AI Content Agency! System booting up...\n" + "="*50)
    
    initial_state = {
        "topic": "Analyzing real-world use cases and challenges of Large AI Models in the healthcare sector in 2024"
    }
    
    # Run in stream mode so we can see the execution process step-by-step
    for output in agency_app.stream(initial_state):
        # Print the name of the node that just finished executing
        for key, value in output.items():
            print(f"--- Node [{key}] execution completed ---")
            
    print("\n" + "="*50 + "\n🎉 Final draft generated!\n")
    # Get the final state and print the final draft
    final_state = agency_app.get_state(config={"configurable": {"thread_id": "1"}}).values 
    # Note: If not using a checkpointer, you can get it directly from the last output of the stream.
    # For simplicity, we can grab it like this:
    print(value.get("final_article", "Generation failed"))

Pitfalls & Survival Guide (High-Level Troubleshooting Experience)

Everyone, just because the code runs doesn't mean everything is perfect. In the past, while leading teams to implement this kind of multi-agent architecture, I've fallen into countless traps. Today, as a graduation gift, I'm giving you my bottom-of-the-box survival guide:

💣 Pitfall 1: The Editor and Writer's "Infinite Death Loop"

Symptom: The Editor thinks the Writer's work is garbage and never grants "APPROVED"; meanwhile, the Writer stubbornly sticks to its style, or the LLM has simply hit its capability ceiling and cannot produce the effect the Editor wants. As a result, the Graph loops frantically between these two nodes until your OpenAI API balance is burned to ashes. Solution: You must implement a Circuit Breaker. In the code above, we introduced revision_count and forced a breakout in editor_router when it evaluates to >= 2. In enterprise-grade projects, you can even introduce a Human_Intervention node—when revisions exceed 3 times, it directly sends a Slack/Teams message to bring a human editor-in-chief into the loop.

💣 Pitfall 2: Infinite State Bloat Blowing Up the Context Window

Symptom: If you store every single draft in the State as an array using Annotated[list, operator.add], after a few revisions, the Prompt length will grow exponentially, directly triggering an LLM Token limit error. Solution: Differentiate between "states that need appending" and "states that need overwriting." In our AgencyState, both draft and review_comments are of type str, meaning every execution will overwrite the old values. The Writer only needs to know the "current latest draft" and the "latest review comments"; it doesn't need to know about scrapped drafts from ancient history.

💣 Pitfall 3: Prompt Drift

Symptom: After receiving extremely harsh criticism from the Editor, the Writer node not only revises the article but also adds a sentence at the beginning: "Understood, Editor-in-Chief. I am very sorry. I have revised the article according to your requests. Here is the text..." This results in the final text sent to the Publisher containing this nonsense. Solution: This is caused by the LLM's "people-pleasing personality." In the Writer's System Prompt, you must add extremely strict constraint instructions: "You are ONLY allowed to output the main body of the article. You are NEVER allowed to output any explanatory sentences, apologies, or conversational text unrelated to the main body!" You can even use LangChain's Structured Output (Pydantic) to strictly enforce the output format.


📝 Session Summary

Everyone, along with that 🎉 Final draft generated! printed in the console, our 30-part LangGraph Multi-Agent Masterclass officially comes to a close.

From stumbling through the concept of a Graph in Part 1, to today, where we used just a few hundred lines of code to build a complete AI agency encompassing instruction dispatch, web-wide retrieval, content creation, quality review, iterative revision, and typesetting. You haven't just mastered the underlying logic of LangGraph; you've built a high-level "multi-agent architectural mindset."

Please remember: LangGraph is just a framework; it gives the Agent its skeleton. But the State transitions you design and the Prompts you meticulously tune are the keys that give this system its soul.

Graduation is not the end, but a new beginning. You now possess the ability to refactor complex enterprise-grade workflows. Whether you're building a Financial Research Agent, a Code Review Agent, or the Content Agency we built today, the underlying "Tao" (principles) remains the same.

Take the arsenal you've gathered over these 30 lessons and go conquer the AI landscape! And don't forget, when you run into a Bug you just can't squash, come back and take a look at your instructor's tutorials. Until we meet again in the tech world! 🚀