What is LangGraph?
LangGraph is an open-source framework from LangChain designed to build stateful, multi-step applications with large language models (LLMs). At its core, it lets you define a computation as a directed graph—where nodes represent actions or LLM calls, and edges represent the flow of data and logic between those actions. The graph maintains a shared state that is passed and updated at each node, enabling complex, agent-like workflows that can branch, loop, and make decisions dynamically.
Unlike simple chains that follow a linear sequence, LangGraph allows you to create cycles (e.g., for iterative refinement), conditional branching, and even human-in-the-loop checkpoints. It's perfect for orchestrating an AI agent that must coordinate multiple sub-tasks, such as researching a topic, planning an outline, drafting text, and editing the result.
Why Use LangGraph for Content Writing Agents?
Writing high-quality content is rarely a single-shot generation task. It involves multiple stages: gathering information, structuring ideas, drafting, and polishing. A content writing agent built with LangGraph provides:
- Structured Workflows: Break down the writing process into discrete, manageable steps (nodes) that can be developed and tested independently.
- Stateful Coordination: Pass the evolving document (topic, research notes, outline, draft) through each stage via a centralized state object, avoiding messy prompt chaining.
- Iterative Refinement: Easily add cycles—for example, loop back from an editing node to drafting if the quality check fails, automatically improving the output.
- Tool Integration: Nodes can call external APIs or search tools (via LangChain tool integrations) to gather real-time data, ensuring factual and up-to-date content.
- Observability and Control: The graph structure gives you clear visibility into the agent's decision-making, making debugging and optimization straightforward.
Step-by-Step: Building a Content Writing Agent
Let’s build a complete content writing agent from scratch using LangGraph. We'll create a four-stage workflow: Research → Outline → Draft → Edit, with an optional refinement loop.
Step 1: Setting Up Your Environment
First, install the required packages. You'll need langgraph, langchain-core, and an LLM provider (we'll use OpenAI). In your terminal:
pip install langgraph langchain langchain-openai python-dotenv
Set your OpenAI API key as an environment variable or pass it directly. For simplicity, we'll load it from a .env file.
Step 2: Defining the Agent State
In LangGraph, the state is a shared data structure that flows through the graph. We'll use a Python TypedDict to define a strongly-typed state with all the fields our agent needs.
from typing import TypedDict, List, Optional
class ContentState(TypedDict):
topic: str # The main topic for the content
research_notes: str # Raw research findings
outline: str # Structured article outline
draft: str # First draft of the content
final_content: Optional[str] # Polished final version
editor_feedback: Optional[str] # Feedback from the editing stage
Each node will return a dictionary with keys that should be updated in the state. LangGraph merges these partial updates automatically. For example, the research node returns {"research_notes": "..."}.
Step 3: Creating Node Functions
Nodes are Python functions (or LangChain runnables) that receive the current state and return an update. We'll build four nodes, each calling an LLM with a specific prompt.
Research Node
This node uses an LLM to gather background information about the topic. In a production agent, you might plug in a real search tool; here we simulate with an LLM call.
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
def research_node(state: ContentState) -> dict:
prompt = f"Research the topic '{state['topic']}' thoroughly. Provide key facts, statistics, and interesting angles."
response = llm.invoke([HumanMessage(content=prompt)])
return {"research_notes": response.content}
Outline Node
Based on research notes, the agent creates a structured outline with sections and subsections.
def outline_node(state: ContentState) -> dict:
prompt = f"""Using the following research notes, create a detailed outline for an article.
Research notes:
{state['research_notes']}
The outline should include an introduction, main sections (each with bullet points), and a conclusion."""
response = llm.invoke([HumanMessage(content=prompt)])
return {"outline": response.content}
Drafting Node
This node writes the full first draft by expanding the outline into prose.
def drafting_node(state: ContentState) -> dict:
prompt = f"""Write a complete, engaging first draft of an article based on the following outline and research notes.
Topic: {state['topic']}
Outline:
{state['outline']}
Research notes:
{state['research_notes']}
Write in a natural, informative style. The draft should flow smoothly from introduction to conclusion."""
response = llm.invoke([HumanMessage(content=prompt)])
return {"draft": response.content}
Editing Node
The editor reviews the draft, provides feedback, and optionally produces a polished final version. We'll also generate a quality score to decide whether to loop back for revisions.
def editing_node(state: ContentState) -> dict:
prompt = f"""You are a meticulous editor. Review the following draft and provide constructive feedback.
Also decide if the draft meets quality standards: output a score from 1-10, where 1-7 means 'needs revision' and 8-10 means 'ready to publish'.
Draft:
{state['draft']}
Output format:
Feedback: (your detailed feedback)
Score: (number)
Final version: (if score >= 8, rewrite the draft incorporating fixes; if score < 8, output just the feedback without rewriting)"""
response = llm.invoke([HumanMessage(content=prompt)])
content = response.content
# Simple parsing (in practice, use structured output)
feedback = ""
score = 0
final_version = None
if "Feedback:" in content:
feedback = content.split("Feedback:")[1].split("Score:")[0].strip()
if "Score:" in content:
try:
score_str = content.split("Score:")[1].split("\n")[0].strip()
score = int(score_str)
except:
score = 0
if score >= 8 and "Final version:" in content:
final_version = content.split("Final version:")[1].strip()
updates = {"editor_feedback": feedback}
if final_version:
updates["final_content"] = final_version
return updates
Step 4: Building the Graph
Now we assemble the graph using LangGraph's StateGraph. We'll define nodes, a linear sequence, and a conditional edge for refinement.
from langgraph.graph import StateGraph, END
# Initialize graph with our state schema
builder = StateGraph(ContentState)
# Add nodes
builder.add_node("research", research_node)
builder.add_node("outline", outline_node)
builder.add_node("drafting", drafting_node)
builder.add_node("editing", editing_node)
# Add edges for the main pipeline
builder.add_edge("research", "outline")
builder.add_edge("outline", "drafting")
builder.add_edge("drafting", "editing")
# Conditional edge: after editing, decide next step
def should_revise(state: ContentState) -> str:
# If final_content exists, we're done; else loop back to drafting
if state.get("final_content"):
return "end"
return "drafting"
builder.add_conditional_edges(
"editing",
should_revise,
{
"end": END,
"drafting": "drafting"
}
)
# Set the entry point
builder.set_entry_point("research")
# Compile the graph
app = builder.compile()
Here, after editing, the graph evaluates the state: if a final version is already present, the process ends; otherwise it routes back to the drafting node to incorporate feedback. The should_revise function acts as the router. Note that the drafting node will run again with the updated state (including editor feedback) — you could modify drafting_node to use that feedback to improve the draft.
Step 5: Compiling and Running the Agent
With the graph compiled, you can invoke it like any runnable. Pass an initial state with at least the topic field.
initial_state = {"topic": "The Future of Renewable Energy in 2025"}
result = app.invoke(initial_state)
print("Final Content:", result.get("final_content"))
print("Editor Feedback:", result.get("editor_feedback"))
The agent will automatically execute the nodes in the defined order, possibly looping through drafting and editing multiple times until a publishable final content is produced. You can inspect the full state history by enabling tracing or simply printing intermediate states in each node.
Best Practices for LangGraph Content Agents
- Use typed state: A
TypedDictor Pydantic model prevents accidental field misuse and enables auto-completion. - Keep nodes focused: Each node should perform one logical step. Avoid “god nodes” that do everything; this keeps the graph modular and testable.
- Handle errors gracefully: Wrap node logic in try/except and return an error key in state. You can add a dedicated error-handling node that routes based on error state.
- Implement human-in-the-loop: Use LangGraph’s
interruptfeature before critical steps (like publishing) to allow human review and approval. - Leverage structured outputs: Instead of parsing raw text (as we did with score), use LangChain’s
with_structured_outputor tool-calling to get reliable data from the LLM. - Optimize prompts: Iterate on prompts for each node; even small changes can drastically improve the quality of outlines, drafts, and edits.
- Integrate real tools for research: Replace the simulated research node with a Tavily, SerpAPI, or web scraper tool to gather live data and boost factual accuracy.
- Monitor and log: Use LangGraph’s built-in tracing or integrate with LangSmith to observe state transitions and debug complex loops.
Conclusion
You’ve now built a complete, multi-stage content writing agent using LangGraph. The graph orchestrates research, outlining, drafting, and editing in a stateful, iterative workflow that can automatically refine drafts until they meet a quality threshold. This approach brings order to the otherwise chaotic process of generating long-form content with LLMs, making your application more reliable, maintainable, and capable of producing consistently high-quality writing. With the foundation in place, you can extend the agent with custom tools, human approval steps, and even parallel branches for different article sections — all while keeping a clean, graph-based architecture.