An autonomous, multi-agent blog generation architecture built with LangGraph, LangChain, and Google Gemini. The system dynamically routes requests based on research needs, generates structured section plans, executes parallel task drafting, stitches sections together, and automatically plans & generates technical diagrams using Gemini 2.5 Flash Image.
This project implements a fully autonomous blog-writing pipeline as a stateful LangGraph agent graph. Rather than a single LLM call, the system:
- Dynamically decides whether a topic needs external research
- Plans a structured, multi-section blog outline
- Drafts sections in parallel via worker agents
- Merges and stitches sections into a cohesive draft
- Plans and generates technical diagrams/images to accompany the content
The result is a modular, production-style agent pipeline rather than a single-shot prompt — built to demonstrate real orchestration patterns (routing, parallelization, reducer subgraphs) in a practical, end-to-end application.
The graph features a dynamic router that directs execution based on whether a topic requires external web research (e.g., modern tech news vs. a timeless concept).
┌─────────────┐
│ __start__ │
└──────┬──────┘
│
▼
┌─────────────┐
│ router │
└──────┬──────┘
│
┌────────────┴────────────┐
│ (needs_research=True) │ (needs_research=False)
▼ │
┌─────────────┐ │
│ research │ (Tavily Search) │
└──────┬──────┘ │
└────────────┬────────────┘
│
▼
┌─────────────┐
│ orchestrator│ (Planner)
└──────┬──────┘
│
▼
┌─────────────┐
│ worker │ (Parallel Section Drafts)
└──────┬──────┘
│
▼
┌─────────────┐
│ reducer │ (Reducer Subgraph)
└──────┬──────┘
│
▼
┌─────────────┐
│ __end__ │
└─────────────┘
The reducer node is itself a compiled stateful subgraph (reducer_subgraph) designed to handle text assembly, dynamic visual planning, and image byte generation.
┌─────────────────┐ ┌─────────────────┐ ┌────────────────────────────┐
│ merge_content │ ────> │ decide_images │ ────> │ generate_and_place_images │
└─────────────────┘ └─────────────────┘ └────────────────────────────┘
(Stitches worker (Evaluates text & (Calls Gemini Flash Image API
markdown sections) generates Global & injects markdown images with
Image Plan) graceful error fallback)
| Stage | Responsibility |
|---|---|
merge_content |
Aggregates and orders individual section drafts produced by the workers |
decide_images |
Uses structured outputs (GlobalImagePlan) to determine if up to 3 technical diagrams/tables are needed, outputting text placeholders ([[IMAGE_1]], etc.) |
generate_and_place_images |
Intercepts placeholders, generates raw bytes via gemini-2.5-flash-image, handles fallback warnings on rate limits (429 RESOURCE_EXHAUSTED), and saves output files locally |
The pipeline relies on strictly typed schemas for agent handoffs and structured LLM outputs:
from typing import List, Literal, Optional
from pydantic import BaseModel, Field
# Router Decision Schema
class RouterDecision(BaseModel):
needs_research: bool
mode: Literal['closed_book', 'hybrid', 'open_book']
queries: List[str]
# Research Evidence Schemas
class EvidenceItem(BaseModel):
title: str
url: str
published_at: Optional[str] = None
snippet: Optional[str] = None
source: Optional[str] = None
class EvidencePack(BaseModel):
evidence: List[EvidenceItem]
# Task Schema (Section Spec)
class Task(BaseModel):
id: int
title: str
goal: str = Field(..., description="One sentence describing section goal.")
bullets: List[str] = Field(..., min_length=3, max_length=5)
target_words: int = Field(..., description="Target word count (120-450).")
tags: List[str]
requires_research: bool
requires_citations: bool
requires_code: bool
section_type: Literal[
"intro", "core", "examples", "checklist", "common_mistakes", "conclusion"
]
# Global Plan Schema
class Plan(BaseModel):
blog_title: str
audience: str
tone: str
blog_kind: Literal['explainer', 'tutorial', 'news_roundup', 'comparison', 'system_design']
constraints: List[str]
tasks: List[Task]- 🧭 Dynamic routing — automatically decides whether a topic needs live web research
- 🧩 Orchestrator–worker pattern — plans structured sections, then drafts them in parallel
- 🔁 Reducer subgraph — merges parallel outputs into a single coherent document
- 🖼️ Automated visual planning — decides when diagrams/images add value and generates them via Gemini 2.5 Flash Image
- 🛡️ Multi-provider fallback chain — gracefully handles rate limits and API failures
- 🎛️ Streamlit dashboard — interactive UI for running and viewing pipeline output
- 📐 Strict structured outputs — Pydantic schemas enforce type-safe handoffs between agents
| Category | Tools |
|---|---|
| Orchestration | LangGraph, LangChain |
| LLM Providers | Google Gemini 2.5 (Flash / Flash Image), OpenRouter (fallback) |
| Research | Tavily Search API |
| Validation | Pydantic |
| UI | Streamlit |
| Language | Python 3.10+ |
.
├── blog_agent.py # Main LangGraph graph definitions and worker logic
├── llm_manager.py # Multi-provider fallback chain wrapper
├── main.py # Pipeline CLI runner
├── streamlit_app.py # Streamlit UI dashboard
├── test_models.py # Diagnostic utility for API endpoint connectivity
├── requirements.txt # Project dependencies
├── blog_basic.ipynb # Prototype & testing notebook
└── README.md # Project documentation
git clone https://github.com/muhammadumarafzaal/BlogWriting-Agent-Langgraph.git
cd BlogWriting-Agent-Langgraph
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txtCreate a .env file in the root folder:
GOOGLE_API_KEY=your_gemini_api_key
TAVILY_API_KEY=your_tavily_search_api_key # For research node
OPENROUTER_API_KEY=your_openrouter_api_key # Optional fallbackstreamlit run streamlit_app.pypython main.py- Add citation validation / fact-checking pass before final output
- Support additional export formats (PDF, HTML)
- Add caching layer for repeated research queries
- Expand diagram generation to support more visual types (flowcharts, tables)
- Add automated evaluation of generated blog quality
Muhammad Umar Afzaal Software Engineering Student | AI & Full-Stack Developer
- GitHub: @muhammadumarafzaal
This project is licensed under the MIT License — see the LICENSE file for details.