Build a LangGraph Content Agent That Auto-Publishes to Your Blog

Build a LangGraph Content Agent That Auto-Publishes to Your Blog

Key Takeaways

  • LangGraph is ideal for building multi-step content agents because it supports stateful execution and conditional logic, allowing for complex workflows that simple chains can't handle.
  • This tutorial walks through building a five-node agent that fully automates the content pipeline: researching a topic, creating an outline, drafting sections, reviewing the output, and publishing the final post.
  • The agent uses the Tavily Search API for research, GPT-4o for generation, and a typed state object to pass information between each step of the process.
  • Connecting the agent to a headless CMS like Wisp via its REST API lets you automate your entire content workflow, from a single topic prompt to a live blog post.

You've got LangGraph installed, you've read through the documentation, and you've run the "hello world" examples. But every tutorial you find stops at the toy demo, a single-node agent that summarizes text or answers a question. What you actually want to build is something that does real work: researches a topic, writes a structured post, checks its own output, and publishes it to your blog without you touching a keyboard.

That's exactly what this tutorial builds. You'll construct a multi-step LangGraph content agent that runs a full pipeline from topic to published post, with Wisp CMS as the publishing destination via its REST API.

Here's how the agent graph is structured and what each node does.

Why LangGraph Is the Right Tool for Content Agents

A content pipeline isn't a linear chain. It involves research, structured generation, iterative drafting, conditional review, and a final write to an external API. Simple LangChain chains don't cut it here. They lack the state management and branching logic you need.

LangGraph treats your workflow as a directed graph. Each node is a discrete Python function. A shared State object flows between them, so the research from Node 1 is still accessible in Node 5 without you wiring it through function arguments manually. The LangGraph overview from LangChain describes this balance: the framework gives agents enough structure to be reliable while preserving the flexibility for conditional logic and loops.

Two features make LangGraph especially suited for this:

  • Stateful execution. Every node reads from and writes to a central typed dictionary. No global variables, no passing dictionaries by hand.
  • Conditional edges. You can route between nodes based on the current state — useful for a quality review step that can either approve or flag a draft.

The Content Agent Architecture

The agent graph has five nodes connected in sequence, with an optional conditional branch at the review step:

  1. Research: Queries Tavily Search for relevant content on the topic.
  2. Outline: Uses an LLM to structure the research into H2 headings.
  3. Draft: Iterates over the outline and writes each section.
  4. Review: Runs a quick quality check and sets a feedback flag.
  5. Publish: POSTs the assembled post to Wisp via the REST API.

The state schema ties them together.

Prerequisites for Building Your Agent

Get your environment ready before writing any graph code:

  • Python 3.9+
  • Required packages: Install with:
    pip install langgraph langchain-openai tavily-python python-dotenv requests
    
  • OpenAI API key: For GPT-4o or GPT-4-turbo as the LLM backbone.
  • Tavily API key. Tavily's Search Application Programming Interface (API) is built for Language Model (LLM) agents and returns clean, structured results. It offers 1,000 free searches per month.
  • Wisp account + credentials: A WISP_BLOG_ID and a WISP_API_KEY. Wisp's free plan includes unlimited posts, so there's no cost to get started.

Still Writing Posts Manually?

Store your keys in a .env file and load them with python-dotenv.

Step-by-Step Guide to Building the Graph

This section walks through each node. Every function receives the current state and returns a dictionary of updates to merge back in.

Step 1: Defining the Agent's State Schema

LangGraph requires you to define a typed state up front. This is a TypedDict that every node reads from and writes to:

from typing import List, TypedDict

class ContentAgentState(TypedDict):
    topic: str
    research: str
    outline: List[str]
    drafts: List[str]
    final_post: str
    review_feedback: str

Every field is optional at the start except topic, which you'll pass in as the initial input. The graph populates the rest as it runs.

Step 2: The Research Node with Tavily

This node takes the topic, runs a Tavily search, and dumps the results into research:

from tavily import TavilyClient
import os

tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])

def research_node(state: ContentAgentState) -> dict:
    print("--- researching topic ---")
    topic = state["topic"]
    response = tavily.search(
        query=f"Key points to cover in a blog post about: {topic}",
        search_depth="advanced"
    )
    research_summary = "\n".join([r["content"] for r in response["results"]])
    return {"research": research_summary}

Tavily's search_depth="advanced" triggers a deeper crawl and returns more structured content, which is worth the extra credit cost on complex topics.

Step 3: The Outline Generation Node

This node passes the research to an LLM and asks for a clean list of H2 headings:

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

def outline_node(state: ContentAgentState) -> dict:
    print("--- generating outline ---")
    prompt = f"""Based on the following research, create a blog post outline 
with 4-6 H2 headings for the topic: {state['topic']}.

Research:
{state['research']}

Return only the headings, one per line. No numbering, no extra text."""
    response = llm.invoke(prompt)
    outline = [h.strip() for h in response.content.strip().split("\n") if h.strip()]
    return {"outline": outline}

With the outline ready, the agent can now draft each section.

Step 4: The Section Drafting Node

This is where the agent does the heavy lifting. It loops over each heading in the outline and drafts content for it, then assembles everything into a single Markdown post:

def draft_node(state: ContentAgentState) -> dict:
    print("--- drafting sections ---")
    drafts = []
    for heading in state["outline"]:
        prompt = f"""Write 2-3 paragraphs for this blog post section: '{heading}'.

Use this research as your source material:
{state['research']}

Write clearly and concisely. No fluff."""
        section = llm.invoke(prompt).content
        drafts.append(section)

    final_post = ""
    for i, draft in enumerate(drafts):
        final_post += f"## {state['outline'][i]}\n\n{draft}\n\n"

    return {"drafts": drafts, "final_post": final_post}

The loop here is a simple Python for. LangGraph doesn't require special syntax for iteration within a node. If you need the loop itself to be a graph edge (for human-in-the-loop review of each section), you'd model it differently, but for a fully automated pipeline this approach keeps things clean.

Step 5: The Quality Review Node

A lightweight check before publishing. This version flags posts that are too short, but you can extend it with LLM-based tone checks or duplication detection:

def review_node(state: ContentAgentState) -> dict:
    print("--- reviewing draft ---")
    feedback = []
    word_count = len(state["final_post"].split())
    if word_count < 500:
        feedback.append(f"Post is too short ({word_count} words). Expand key sections.")
    return {"review_feedback": "; ".join(feedback)}

In a production pipeline, you'd add a conditional edge here: if review_feedback is non-empty, route back to the draft node. For this tutorial, the agent logs the feedback and publishes regardless.

Step 6: The Publishing Node for Wisp CMS

This is where your LangGraph agent connects to the CMS to publish content. The node assembles the title, generates a slug, and sends a POST request to Wisp's REST API:

import requests, json

def publish_node(state: ContentAgentState) -> dict:
    print("--- publishing to wisp ---")
    blog_id = os.environ["WISP_BLOG_ID"]
    api_key = os.environ["WISP_API_KEY"]

    # Refer to Wisp's REST API docs for the current endpoint structure
    # https://www.wisp.blog/docs/advance-concepts/rest-api
    url = f"https://www.wisp.blog/api/v1/{blog_id}/posts"

    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    slug = state["topic"].lower().replace(" ", "-").replace(",", "")

    payload = {
        "title": state["topic"],
        "slug": slug,
        "content": state["final_post"],
        "status": "published"
    }

    response = requests.post(url, headers=headers, data=json.dumps(payload))

    if response.status_code == 201:
        print(f"Published: {state['topic']}")
    else:
        print(f"Publish failed: {response.status_code}, {response.text}")

    return {}

Check Wisp's REST API documentation for the exact endpoint structure and accepted payload fields. The Content API is read-only; publishing goes through the REST API with your API key in the Authorization header.

Assembling and Running the LangGraph Agent

With all five nodes defined, wire them together in a StateGraph:

from langgraph.graph import StateGraph, END

workflow = StateGraph(ContentAgentState)

workflow.add_node("research", research_node)
workflow.add_node("outline", outline_node)
workflow.add_node("draft", draft_node)
workflow.add_node("review", review_node)
workflow.add_node("publish", publish_node)

workflow.set_entry_point("research")
workflow.add_edge("research", "outline")
workflow.add_edge("outline", "draft")
workflow.add_edge("draft", "review")
workflow.add_edge("review", "publish")
workflow.add_edge("publish", END)

app = workflow.compile()

# Run the agent
inputs = {"topic": "The Future of AI in Web Development"}
for output in app.stream(inputs):
    for key in output:
        print(f"Completed node: {key}")

app.stream() runs the graph and yields output after each node completes. This makes it easy to add logging or progress tracking without restructuring the graph.

Scheduling Your Agent for Autonomous Runs

Once the script works end-to-end, you have two straightforward options for automation.

For simple scheduled runs on a Linux server, a cron job works fine:

0 6 * * 1 /usr/bin/python3 /path/to/content_agent.py

This runs the agent every Monday at 6am. Pass different topics dynamically by reading from a CSV or a queue.

For a more production-grade setup, LangServe lets you deploy the compiled graph as a REST endpoint. You can then trigger it from any scheduler, CI pipeline, or external service. This approach is worth it if you want to accept topics via a webhook or integrate the agent into a broader content workflow.

Ready to Publish Automatically?

Your Automated Content Pipeline Awaits

You now have a complete blueprint for an agent that does real work. It’s a production-ready system for turning a single topic into a published blog post, demonstrating that LangGraph’s stateful graphs are perfect for complex content workflows. Connecting your agent to a CMS API automates the entire pipeline from start to finish.

Your next step? Run the code with a topic you know well and tweak the prompts in the draft node to match your brand’s voice.

An agent is only as good as the tools it connects to. If a clunky CMS API is slowing you down, the automation stops short of the finish line. Wisp was built for developers who need a clean, reliable API for programmatic publishing. Explore the REST API docs and try it out on the free plan, which includes full API access.

FAQs

Why use LangGraph for content agents over simple chains?

LangGraph is ideal for content agents because it handles complex, multi-step workflows with state and conditional logic. Simple chains lack the ability to manage a process like research, drafting, and conditional review.

What is the purpose of the state object in this agent?

The state object in this agent acts as a shared memory that passes information between nodes. Each step, like research or drafting, reads from and writes to this central state, ensuring data is consistently available throughout the workflow.

How does the agent research the topic?

The agent researches the topic using the Tavily Search API. The research node queries Tavily, which returns clean, structured search results specifically designed for LLMs to use as a source for generating accurate content.

Can I swap out the LLM or CMS used in this tutorial?

Yes, you can easily swap out the LLM or CMS. The agent's modular node-based design means you can replace GPT-4o with another model or change the publishing node to post to a different API, like WordPress or Ghost, with minimal code changes.

What does the review node do?

The review node performs a quality check on the drafted post. This tutorial uses a simple word count check, but you can easily extend it to include LLM-based reviews for tone and accuracy or add a human-in-the-loop approval step.

How can I run this agent automatically?

You can run this agent automatically using a standard cron job for scheduled posts. For more advanced use cases, you can deploy the compiled graph as a REST API using LangServe, allowing it to be triggered by webhooks or other services.

Raymond Yeh

Raymond Yeh

Published on 24 July 2026

Choosing a CMS?

Wisp is the most delightful and intuitive way to manage content on your website. Integrate with any existing website within hours!

Choosing a CMS
Related Posts
How to Build an AI Agent That Publishes Blog Posts (Next.js + Wisp CMS)

How to Build an AI Agent That Publishes Blog Posts (Next.js + Wisp CMS)

Build an AI agent that generates and publishes blog posts to a Next.js blog via the Wisp Content API — with draft mode, slug generation, and a human-in-the-loop review step built in.

Read Full Story
Build a Multi-Agent Blog Writing Team with CrewAI That Publishes to Wisp CMS

Build a Multi-Agent Blog Writing Team with CrewAI That Publishes to Wisp CMS

Build a CrewAI multi-agent blog writing crew in Python — Researcher, Writer, Editor, Publisher — with a full Wisp CMS integration via REST API. Includes production tips.

Read Full Story
How to Auto-Generate and Publish Blog Posts Using the OpenAI API and Wisp CMS

How to Auto-Generate and Publish Blog Posts Using the OpenAI API and Wisp CMS

Learn how to auto-generate and publish blog posts using the OpenAI API and Wisp CMS with a single Node.js script. Covers prompt design, draft workflow, and error handling.

Read Full Story
Loading...