← Back to DevBytes

Building a SEO Content Agent with LangChain: Complete Guide

What Is an SEO Content Agent?

An SEO Content Agent is an autonomous AI system that combines large language models (LLMs) with specialized SEO tools to research, plan, and generate search-engine-optimized content. Built on LangChain, it acts as an intelligent assistant that can perform keyword research, analyze SERP competitors, generate content briefs, and produce fully optimized articles — all while following your brand guidelines and SEO strategy.

Unlike a simple chatbot that generates text from a single prompt, an agent reasons through tasks step-by-step. It decides which tools to call, in what order, and iteratively refines its output based on intermediate results. Think of it as giving an experienced SEO specialist and content writer access to your entire tool stack, then asking them to collaborate on producing the perfect piece of content.

Why Building a Custom SEO Agent Matters

Generic AI content generators produce surface-level articles that often miss critical SEO elements like target keyword density, semantic LSI keywords, proper heading structure, and competitive differentiation. A purpose-built SEO agent addresses these gaps by:

Prerequisites and Setup

Before building the agent, install the required dependencies. This tutorial uses LangChain with OpenAI's GPT-4o model, but you can swap in any LangChain-compatible LLM.

# Create a new project directory
mkdir seo-content-agent
cd seo-content-agent

# Create a virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install core dependencies
pip install langchain langchain-openai langchain-community
pip install requests beautifulsoup4 python-dotenv
pip install serpapi-client  # For SERP data (or use your preferred provider)

Create a .env file to store your API keys securely:

OPENAI_API_KEY=your-openai-api-key-here
SERPAPI_API_KEY=your-serpapi-key-here

Building the Core Components

Step 1: Defining Custom Tools for SEO Research

LangChain agents rely on tools — functions that the agent can call to interact with external systems. Each tool has a name, description, and a well-typed input schema. The description is crucial because the agent uses it to decide which tool to invoke for a given subtask.

Let's build three essential SEO tools: a keyword research tool, a SERP scraper, and a content outline generator.

import os
import json
import requests
from typing import List, Dict, Optional
from bs4 import BeautifulSoup
from dotenv import load_dotenv
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

# -------------------------------------------------------------------
# Tool 1: Keyword Research — uses SerpAPI to get related keywords
# -------------------------------------------------------------------
@tool
def keyword_research(seed_keyword: str, max_results: int = 10) -> str:
    """
    Perform keyword research for a given seed keyword.
    Returns a JSON string containing related keywords, search volume estimates,
    and competition levels. Use this BEFORE writing any content.
    """
    from serpapi import GoogleSearch  # requires serpapi-client
    
    params = {
        "q": seed_keyword,
        "api_key": os.getenv("SERPAPI_API_KEY"),
        "num": max_results,
        "hl": "en",
        "gl": "us",
    }
    
    try:
        search = GoogleSearch(params)
        results = search.get_dict()
        
        keywords_data = []
        # Extract organic results to infer keyword themes
        if "organic_results" in results:
            for result in results["organic_results"][:max_results]:
                title = result.get("title", "")
                snippet = result.get("snippet", "")
                keywords_data.append({
                    "source_title": title,
                    "source_snippet": snippet[:200],
                    "implied_keywords": extract_keywords_from_text(title + " " + snippet)
                })
        
        # Also check related searches
        related_searches = results.get("related_searches", [])
        related_queries = []
        for item in related_searches:
            if isinstance(item, dict):
                related_queries.append(item.get("query", str(item)))
        
        output = {
            "seed_keyword": seed_keyword,
            "competitor_themes": keywords_data,
            "related_searches": related_queries,
            "total_organic_results": results.get("search_information", {}).get("total_results", 0)
        }
        return json.dumps(output, indent=2)
    except Exception as e:
        return json.dumps({"error": str(e), "message": "Tool failed, try a different query"})


def extract_keywords_from_text(text: str) -> List[str]:
    """Simple helper to extract meaningful phrases from text."""
    # In production, replace with NLP-based keyword extraction
    words = text.lower().split()
    # Filter out common stop words and short words
    stop_words = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
                  "of", "with", "by", "from", "is", "are", "was", "were", "be", "been",
                  "being", "have", "has", "had", "do", "does", "did", "will", "would",
                  "can", "could", "may", "might", "shall", "should", "this", "that"}
    keywords = [w for w in words if len(w) > 3 and w not in stop_words]
    return list(set(keywords))[:15]


# -------------------------------------------------------------------
# Tool 2: SERP Content Scraper — analyzes competitor content
# -------------------------------------------------------------------
@tool
def scrape_competing_content(url: str) -> str:
    """
    Scrape and extract the main content, headings, and meta information
    from a competing URL. Use this to understand what top-ranking pages cover.
    """
    try:
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/120.0.0.0 Safari/537.36"
        }
        response = requests.get(url, headers=headers, timeout=15)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, "html.parser")
        
        # Extract meta title and description
        meta_title = ""
        title_tag = soup.find("title")
        if title_tag:
            meta_title = title_tag.get_text(strip=True)
        
        meta_description = ""
        meta_desc_tag = soup.find("meta", attrs={"name": "description"})
        if meta_desc_tag and meta_desc_tag.get("content"):
            meta_description = meta_desc_tag["content"]
        
        # Extract headings hierarchy
        headings = []
        for tag in ["h1", "h2", "h3"]:
            for heading in soup.find_all(tag):
                text = heading.get_text(strip=True)
                if text:
                    headings.append({"level": tag, "text": text})
        
        # Extract main content (simplified — production would use readability extractors)
        article_body = soup.find("article") or soup.find("main") or soup.body
        paragraphs = []
        if article_body:
            for p in article_body.find_all("p"):
                text = p.get_text(strip=True)
                if len(text) > 50:  # Skip short paragraphs
                    paragraphs.append(text)
        
        # Word count estimate
        full_text = " ".join(paragraphs)
        word_count = len(full_text.split())
        
        result = {
            "url": url,
            "meta_title": meta_title,
            "meta_description": meta_description,
            "headings": headings,
            "paragraph_count": len(paragraphs),
            "estimated_word_count": word_count,
            "content_sample": full_text[:1500]  # First 1500 chars for analysis
        }
        return json.dumps(result, indent=2)
    except Exception as e:
        return json.dumps({"error": str(e), "url": url})


# -------------------------------------------------------------------
# Tool 3: SEO Content Score Evaluator — checks optimization level
# -------------------------------------------------------------------
@tool
def evaluate_seo_score(content: str, target_keyword: str) -> str:
    """
    Evaluate a piece of content for SEO optimization.
    Checks keyword density, heading structure, readability, and content length.
    Returns a score from 0-100 with actionable recommendations.
    """
    import re
    
    content_lower = content.lower()
    keyword_lower = target_keyword.lower()
    
    # 1. Keyword density check
    total_words = len(content.split())
    keyword_count = content_lower.count(keyword_lower)
    keyword_density = (keyword_count / total_words * 100) if total_words > 0 else 0
    
    # 2. Check if keyword appears in first 100 words
    first_100 = " ".join(content.split()[:100]).lower()
    keyword_in_intro = keyword_lower in first_100
    
    # 3. Heading structure check
    h2_count = len(re.findall(r'^##\s', content, re.MULTILINE))
    h3_count = len(re.findall(r'^###\s', content, re.MULTILINE))
    has_proper_structure = h2_count >= 3 and h3_count >= 2
    
    # 4. Content length check
    length_score = min(100, total_words / 15)  # Scales to 100 at 1500 words
    
    # 5. Readability proxy (sentence length)
    sentences = re.split(r'[.!?]+', content)
    avg_sentence_length = sum(len(s.split()) for s in sentences if s.strip()) / max(1, len([s for s in sentences if s.strip()]))
    readability_ok = 10 <= avg_sentence_length <= 25
    
    # Composite score
    score = 0
    score += min(25, keyword_density * 25)  # Up to 25 points for density
    score += 15 if keyword_in_intro else 0
    score += 15 if has_proper_structure else 5
    score += min(25, length_score)
    score += 10 if readability_ok else 3
    score += 10  # Base score for having content
    
    recommendations = []
    if keyword_density < 0.5:
        recommendations.append("Increase keyword density to 0.5-2%")
    if keyword_density > 3:
        recommendations.append("Reduce keyword density — appears stuffed")
    if not keyword_in_intro:
        recommendations.append("Include target keyword in the first 100 words")
    if not has_proper_structure:
        recommendations.append("Add more H2 and H3 headings for structure")
    if total_words < 800:
        recommendations.append("Expand content to at least 800-1500 words")
    if not readability_ok:
        recommendations.append("Adjust sentence length for better readability")
    
    result = {
        "overall_score": round(score),
        "total_words": total_words,
        "keyword_density_percent": round(keyword_density, 2),
        "keyword_in_intro": keyword_in_intro,
        "h2_count": h2_count,
        "h3_count": h3_count,
        "recommendations": recommendations
    }
    return json.dumps(result, indent=2)

Step 2: Assembling the LangChain Agent

Now we create the agent using LangChain's OpenAI tools agent pattern. This agent receives a system prompt that defines its role, a list of tools it can call, and an execution loop that handles tool calling and response generation.

# -------------------------------------------------------------------
# Create the LLM and Agent
# -------------------------------------------------------------------
def create_seo_agent(verbose: bool = True):
    """
    Build and return an SEO Content Agent executor.
    """
    # Initialize the LLM — GPT-4o is recommended for complex reasoning
    llm = ChatOpenAI(
        model="gpt-4o",
        temperature=0.7,
        max_tokens=4096,
    )
    
    # Define all tools the agent can use
    tools = [
        keyword_research,
        scrape_competing_content,
        evaluate_seo_score,
    ]
    
    # System prompt that defines the agent's identity and workflow
    system_prompt = """You are an expert SEO Content Agent. Your job is to create high-quality, 
search-engine-optimized content following a structured methodology.

WORKFLOW YOU MUST FOLLOW:
1. RESEARCH PHASE: Use the keyword_research tool to understand the topic landscape.
   Then use scrape_competing_content on 2-3 top-ranking URLs to analyze what performs well.
   
2. PLANNING PHASE: Based on research, create a detailed content outline with:
   - Target keyword and 3-5 LSI/semantic keywords
   - Article structure with H2 and H3 headings
   - Estimated word count (typically 1000-2000 words for competitive topics)
   - Key points to cover that differentiate from competitors
   
3. WRITING PHASE: Write the complete article in markdown format with:
   - A compelling H1 title containing the target keyword
   - An engaging introduction that hooks readers
   - Well-structured H2 sections with supporting H3 subsections
   - Natural keyword placement throughout (not forced or stuffed)
   - Actionable insights, examples, and data points
   
4. OPTIMIZATION PHASE: Run evaluate_seo_score on your draft and refine based on recommendations.
   Iterate until the score exceeds 80/100.

RULES:
- Always perform research BEFORE writing. Never skip the research phase.
- Write in clear, authoritative, helpful prose.
- Use markdown formatting for all content output.
- Include specific data points and examples from your research.
- Do not fabricate statistics — only use data from your tool results.
- When a tool fails, try an alternative approach or proceed with available data.
"""
    
    # Build the prompt template
    prompt = ChatPromptTemplate.from_messages([
        ("system", system_prompt),
        MessagesPlaceholder(variable_name="chat_history", optional=True),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ])
    
    # Create the OpenAI tools agent
    agent = create_openai_tools_agent(llm, tools, prompt)
    
    # Wrap in an executor with error handling
    executor = AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=verbose,
        handle_parsing_errors=True,
        max_iterations=15,
        max_execution_time=180,  # 3 minutes max
        return_intermediate_steps=False,
    )
    
    return executor

Step 3: Building a Content Pipeline Wrapper

While the agent handles the reasoning, wrapping it in a pipeline class gives you control over logging, caching research results, and post-processing the output. This makes the system production-ready.

import time
import hashlib
from datetime import datetime
from typing import Dict, Any

class SEOContentPipeline:
    """
    Production wrapper around the SEO Content Agent.
    Adds caching, structured output parsing, and logging.
    """
    
    def __init__(self, agent_executor: AgentExecutor):
        self.executor = agent_executor
        self.research_cache = {}  # Simple in-memory cache (use Redis in production)
    
    def _cache_key(self, topic: str) -> str:
        return hashlib.md5(topic.lower().strip().encode()).hexdigest()
    
    def run_research_only(self, topic: str, competitor_urls: List[str] = None) -> Dict:
        """
        Run only the research phase and return structured data.
        Useful when you want to review research before generating content.
        """
        cache_key = self._cache_key(topic)
        if cache_key in self.research_cache:
            print(f"[CACHE HIT] Using cached research for: {topic}")
            return self.research_cache[cache_key]
        
        prompt = f"""Please conduct thorough SEO research on the topic: "{topic}"
        
Phase 1: Use keyword_research to analyze this topic.
Phase 2: Scrape these competing URLs if provided: {competitor_urls or 'find top-ranking pages from your research'}
Phase 3: Provide a detailed research summary with:
- Primary keyword target
- 5-10 LSI/semantic keywords
- Average word count of top-ranking pages
- Common headings and content gaps
- Recommended content angle/differentiator

Do NOT write the full article yet — only provide the research summary."""
        
        result = self.executor.invoke({"input": prompt})
        self.research_cache[cache_key] = result
        return result
    
    def generate_content(self, topic: str, research_data: Dict = None,
                         content_type: str = "comprehensive_guide",
                         target_word_count: int = 1500) -> str:
        """
        Generate a complete SEO-optimized article based on research.
        """
        research_context = ""
        if research_data:
            research_context = f"\n\nPREVIOUS RESEARCH:\n{json.dumps(research_data, indent=2)}"
        
        prompt = f"""Create a complete SEO-optimized article about: "{topic}"

Content Type: {content_type}
Target Word Count: {target_word_count}{research_context}

Follow your full workflow: research (if not already provided), plan, write, optimize.
Use evaluate_seo_score to check your work and refine until the score exceeds 80.
Output the final article in clean markdown format."""
        
        result = self.executor.invoke({"input": prompt})
        return result.get("output", str(result))
    
    def generate_content_brief(self, topic: str) -> str:
        """
        Generate a structured content brief (outline + SEO specs) without the full article.
        """
        prompt = f"""Create a detailed SEO content brief for the topic: "{topic}"

Include:
1. Target keyword and 5 semantic keywords
2. Recommended URL slug
3. Meta title (under 60 chars) and meta description (under 155 chars)
4. Full article outline with H2 and H3 headings
5. Key questions to answer in each section
6. Internal and external linking recommendations
7. Target word count and content type

Output as structured markdown."""
        
        result = self.executor.invoke({"input": prompt})
        return result.get("output", str(result))

Putting It All Together — Complete Implementation

Here is the complete, runnable main.py that ties everything together. This script initializes the agent, runs the pipeline, and outputs the final article.

# main.py — Complete SEO Content Agent Runner
import os
import json
from dotenv import load_dotenv

load_dotenv()

def main():
    print("=" * 60)
    print("SEO CONTENT AGENT — Powered by LangChain")
    print("=" * 60)
    
    # Initialize the agent
    agent = create_seo_agent(verbose=True)
    pipeline = SEOContentPipeline(agent)
    
    # Example: Generate a comprehensive guide
    topic = "Serverless Architecture Best Practices for SaaS Applications"
    
    print(f"\nšŸŽÆ Target Topic: {topic}\n")
    
    # Step 1: Run research
    print("[PHASE 1] Running SEO research...")
    research = pipeline.run_research_only(
        topic=topic,
        competitor_urls=[
            "https://aws.amazon.com/serverless/",
            "https://cloud.google.com/serverless",
        ]
    )
    print("[PHASE 1] Research complete.\n")
    
    # Step 2: Generate content brief (optional review step)
    print("[PHASE 2] Generating content brief...")
    brief = pipeline.generate_content_brief(topic)
    print("[PHASE 2] Brief ready.\n")
    
    # Step 3: Generate full article
    print("[PHASE 3] Generating optimized article...")
    article = pipeline.generate_content(
        topic=topic,
        research_data=research,
        content_type="comprehensive_guide",
        target_word_count=1800
    )
    
    print("\n" + "=" * 60)
    print("FINAL ARTICLE OUTPUT")
    print("=" * 60 + "\n")
    print(article)
    
    # Save to file
    output_filename = f"article_{topic[:30].replace(' ', '_').lower()}.md"
    with open(output_filename, "w", encoding="utf-8") as f:
        f.write(article)
    print(f"\nāœ… Article saved to: {output_filename}")


if __name__ == "__main__":
    main()

How to Use the SEO Content Agent

Once your agent is built, there are several ways to integrate it into your workflow:

Option 1: Command-Line Content Generator

# Run directly from the terminal
python main.py

# Customize by passing arguments
python main.py --topic "Kubernetes Security Best Practices" --word-count 2000 --type tutorial

To support command-line arguments, extend main.py with argparse:

import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="SEO Content Agent")
    parser.add_argument("--topic", type=str, required=True, help="Article topic")
    parser.add_argument("--word-count", type=int, default=1500, help="Target word count")
    parser.add_argument("--type", type=str, default="comprehensive_guide", 
                       choices=["tutorial", "guide", "listicle", "comparison"])
    args = parser.parse_args()
    
    agent = create_seo_agent(verbose=False)
    pipeline = SEOContentPipeline(agent)
    article = pipeline.generate_content(
        topic=args.topic,
        content_type=args.type,
        target_word_count=args.word_count
    )
    print(article)

Option 2: REST API Endpoint

Wrap the agent in a FastAPI server for integration with content management systems:

# api_server.py
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import uvicorn

app = FastAPI(title="SEO Content Agent API")

class ContentRequest(BaseModel):
    topic: str
    content_type: str = "comprehensive_guide"
    word_count: int = 1500
    webhook_url: str = None  # For async delivery

# Initialize agent once at startup
agent = create_seo_agent(verbose=False)
pipeline = SEOContentPipeline(agent)

@app.post("/generate")
async def generate_content(request: ContentRequest):
    """Generate SEO-optimized content synchronously."""
    article = pipeline.generate_content(
        topic=request.topic,
        content_type=request.content_type,
        target_word_count=request.word_count
    )
    return {"topic": request.topic, "content": article}

@app.post("/generate-async")
async def generate_async(request: ContentRequest, background_tasks: BackgroundTasks):
    """Fire-and-forget generation with webhook callback."""
    def generate_and_notify():
        article = pipeline.generate_content(
            topic=request.topic,
            content_type=request.content_type,
            target_word_count=request.word_count
        )
        if request.webhook_url:
            import requests as req
            req.post(request.webhook_url, json={"topic": request.topic, "content": article})
    
    background_tasks.add_task(generate_and_notify)
    return {"status": "processing", "topic": request.topic}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Option 3: Integration with Content Workflows

For teams using platforms like Contentful, WordPress, or Notion, build a connector that triggers the agent via webhook when a new content request is created, then automatically populates the draft with the generated article and SEO metadata.

Best Practices for Production SEO Agents

1. Build a Reliable Tool Layer

The quality of your agent's output depends entirely on the quality of data its tools return. Invest in robust error handling, rate limiting, and fallback mechanisms for each tool. If keyword_research fails, the agent should gracefully degrade — perhaps using its internal knowledge — rather than crashing the entire pipeline.

# Example: Tool with retry logic and fallback
@tool
def robust_keyword_research(seed_keyword: str) -> str:
    """Keyword research with retry and fallback."""
    max_retries = 3
    for attempt in range(max_retries):
        try:
            # Primary API call
            result = call_keyword_api(seed_keyword)
            return json.dumps(result)
        except Exception as e:
            if attempt == max_retries - 1:
                # Fallback: return a structured prompt for the LLM to reason about
                return json.dumps({
                    "fallback": True,
                    "seed_keyword": seed_keyword,
                    "message": "API unavailable. Use your knowledge of this topic to "
                               "infer common keywords and search intent."
                })
            time.sleep(2 ** attempt)  # Exponential backoff

2. Implement Human-in-the-Loop Review

For high-stakes content, insert a review checkpoint between the research/planning phase and the writing phase. The agent can output a structured brief, wait for human approval (or edits), then proceed with writing only after confirmation. This prevents wasted API calls on incorrect direction.

# Pseudo-code for human-in-the-loop
def generate_with_review(topic: str):
    # Phase 1: Research + Brief
    brief = pipeline.generate_content_brief(topic)
    
    # Present to human for review
    print("šŸ“‹ Content Brief:")
    print(brief)
    approval = input("Approve brief? (y/n/edit): ").strip().lower()
    
    if approval == 'y':
        return pipeline.generate_content(topic, content_type="guide")
    elif approval.startswith('edit'):
        # Incorporate human feedback into the prompt
        feedback = input("Enter your feedback: ")
        return pipeline.generate_content(topic + f"\nHuman feedback: {feedback}")
    else:
        print("Brief rejected. Restarting with different parameters.")
        return None

3. Monitor and Log Agent Decisions

Agents make autonomous decisions about which tools to call and in what order. Logging these decisions is critical for debugging and optimization. LangChain's verbose=True mode provides console output, but for production, capture structured logs:

import logging

# Configure structured logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s | %(name)s | %(levelname)s | %(message)s',
    handlers=[
        logging.FileHandler("agent_decisions.log"),
        logging.StreamHandler()
    ]
)

class LoggedAgentExecutor(AgentExecutor):
    """Extended executor that logs each tool call and decision."""
    
    def _perform_tool_call(self, tool_name, tool_input):
        logging.info(f"šŸ”§ Calling tool: {tool_name} with input: {tool_input[:200]}")
        result = super()._perform_tool_call(tool_name, tool_input)
        logging.info(f"āœ… Tool {tool_name} returned {len(str(result))} chars")
        return result

4. Guard Against Hallucinations with Grounding

LLMs can fabricate statistics and facts. Combat this by requiring the agent to cite sources from tool results. Modify your system prompt to enforce citation:

# Enhanced system prompt excerpt
ADDITIONAL RULES:
- When citing statistics or data points, reference which tool result provided them
- If a statistic is not available from your tools, explicitly state "based on available research"
  rather than fabricating numbers
- Use inline citations like [Source: keyword_research tool] when presenting data
- Never invent competitor URLs — only reference URLs you've actually scraped

5. Optimize Cost with Smart Model Selection

Not every step requires GPT-4o. Use a cheaper model for mechanical tasks like initial scraping synthesis, and reserve the powerful model for final writing and optimization:

from langchain_openai import ChatOpenAI

# Multi-model strategy
def create_cost_optimized_agent():
    research_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)  # Cheaper, more deterministic
    writing_llm = ChatOpenAI(model="gpt-4o", temperature=0.8)       # Creative writing
    
    # Use research_llm for tool result synthesis
    # Use writing_llm for final article generation
    # (Implementation requires custom agent routing logic)

6. Cache Aggressively

SEO research for a given keyword doesn't change minute-by-minute. Cache keyword research and SERP analysis results for at least 24 hours. This reduces API costs and speeds up repeat content operations:

import hashlib
import pickle
from pathlib import Path

class PersistentCache:
    def __init__(self, cache_dir: str = "./seo_cache"):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
    
    def get(self, key: str) -> Optional[Dict]:
        cache_file = self.cache_dir / f"{key}.pkl"
        if cache_file.exists():
            with open(cache_file, "rb") as f:
                data = pickle.load(f)
                # Check if cache is still fresh (24 hours)
                if time.time() - data.get("timestamp", 0) < 86400:
                    return data.get("result")
        return None
    
    def set(self, key: str, result: Dict):
        cache_file = self.cache_dir / f"{key}.pkl"
        with open(cache_file, "wb") as f:
            pickle.dump({"timestamp": time.time(), "result": result}, f)

7. Handle Edge Cases Gracefully

Your agent will encounter ambiguous topics, tool failures, and content that's difficult to optimize. Build defensive patterns:

8. Validate Output Before Delivery

Run automated checks on the final article before returning it to the user. Check for minimum word count, presence of target keyword in the first paragraph, proper heading hierarchy, and absence of hallucinated URLs:

def validate_article(content: str, expected_keyword: str) -> Dict[str, bool]:
    """Run validation checks on generated content."""
    checks = {
        "min_word_count": len(content.split()) >= 800,
        "keyword_in_first_100": expected_keyword.lower() in " ".join(content.split()[:100]).lower(),
        "has_h2_headings": "## " in content,
        "has_conclusion": any(phrase in content.lower()[-500:] for phrase in 
                              ["conclusion", "summary", "wrapping up", "final thoughts"]),
        "no_placeholder_text": "lorem ipsum" not in content.lower(),
    }
    return checks

Conclusion

Building an SEO Content Agent with LangChain transforms content creation from an art-and-guesswork process into a systematic, data-driven operation. By equipping an LLM with specialized tools for keyword research, competitor analysis, and SEO scoring, you create a system that reasons about content strategy before writing — producing articles that are genuinely optimized for search performance rather than merely keyword-stuffed.

The architecture outlined here — custom tools, a reasoning agent, a production pipeline wrapper, and robust error handling — serves as a foundation you can extend with additional tools like internal linking analysis, readability scoring, image alt-text optimization, or integration with your CMS. Start with the core implementation, observe where the agent's output falls short of your standards, and iteratively add tools and prompt refinements to close those gaps. The result is a content engine that scales your SEO efforts while maintaining the quality and strategic thinking of your best human writers.

— Ad —

Google AdSense will appear here after approval

← Back to all articles