Windows workflow to set up and run the Python AI agent that generates posts with OpenAI and publishes them to WordPress.

1) Project layout (what your folder will look like)

wp-ai-agent/
├─ ai_agent.py
├─ requirements.txt
├─ .env
├─ .gitignore
└─ venv/        (created by python -m venv venv)

2) Files to create

requirements.txt

openai>=1.0.0
requests>=2.31.0
python-dotenv>=1.0.0

.gitignore

venv/
.env
__pycache__/
*.pyc

.env (create this and fill in your real values)

WP_URL=https://your-site.com/wp-json/wp/v2/posts
WP_USER=your_wp_username
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx
OPENAI_API_KEY=sk-...
DEFAULT_STATUS=draft
MODEL=gpt-4o-mini

Security note: Never commit .env to version control.

ai_agent.py

Copy this file exactly (it uses the modern OpenAI client object pattern and robust parsing):

"""
ai_agent.py
Generates blog posts with OpenAI and publishes them to WordPress via REST API.
Usage: python ai_agent.py
"""

import os
import time
import json
import logging
from typing import Dict
import requests
from requests.auth import HTTPBasicAuth
from dotenv import load_dotenv
from openai import OpenAI

# --------- Setup logging ----------
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")

# --------- Load config ----------
load_dotenv()
WP_URL = os.getenv("WP_URL")  # example: https://your-site.com/wp-json/wp/v2/posts
WP_USER = os.getenv("WP_USER")
WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
DEFAULT_STATUS = os.getenv("DEFAULT_STATUS", "draft")
MODEL = os.getenv("MODEL", "gpt-4o-mini")

if not (WP_URL and WP_USER and WP_APP_PASSWORD and OPENAI_API_KEY):
    logging.error("Missing required environment variables. Check .env file.")
    raise SystemExit(1)

# --------- OpenAI client ---------
client = OpenAI(api_key=OPENAI_API_KEY)

# --------- Helpers ---------
def generate_post(topic: str) -> Dict[str, str]:
    """
    Ask OpenAI to produce a JSON object: {"title": "...", "content": "..."}
    Content should be Markdown-ready.
    """
    system = (
        "You are a helpful blogging assistant. "
        "When asked to write a blog post, reply with a JSON object only, with two fields: "
        '"title" (string) and "content" (string, can include markdown). '
        "Do not include any extra explanation or text outside the JSON."
    )
    prompt = f"Write a detailed, useful blog post about: {topic}\n\nMake it informative and reader-friendly."

    logging.info("Requesting content from OpenAI for topic: %s", topic)
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt}
        ],
        temperature=0.7,
        max_tokens=1200
    )

    raw = resp.choices[0].message.content.strip()
    logging.debug("Raw OpenAI output: %s", raw[:300])

    # Try to parse JSON out of the response robustly
    try:
        # If the model returned JSON directly:
        data = json.loads(raw)
    except Exception:
        # fallback: try to find a JSON substring
        start = raw.find("{")
        end = raw.rfind("}")
        if start != -1 and end != -1 and end > start:
            try:
                data = json.loads(raw[start:end+1])
            except Exception as e:
                logging.error("Failed to parse JSON from model output: %s", e)
                data = {"title": topic, "content": raw}
        else:
            logging.warning("Model did not return JSON, using raw text as content.")
            data = {"title": topic, "content": raw}

    title = data.get("title", topic)
    content = data.get("content", data.get("body", data.get("text", "")))
    return {"title": title, "content": content}

def publish_post(post: Dict[str, str], status: str = DEFAULT_STATUS) -> Dict:
    """
    Publish or create a draft post on WordPress via REST API.
    Returns the WP API JSON on success, raises otherwise.
    """
    payload = {
        "title": post["title"],
        "content": post["content"],
        "status": status
    }
    logging.info("Publishing post: %s (status=%s)", post["title"], status)

    response = requests.post(
        WP_URL,
        auth=HTTPBasicAuth(WP_USER, WP_APP_PASSWORD),
        json=payload,
        timeout=30
    )

    if response.status_code in (200, 201):
        logging.info("Published successfully: %s", response.json().get("link"))
        return response.json()
    else:
        logging.error("Failed to publish: %s -> %s", response.status_code, response.text)
        response.raise_for_status()

# --------- Main runner ----------
def main(topics):
    for topic in topics:
        try:
            post = generate_post(topic)
            wp_resp = publish_post(post, status=DEFAULT_STATUS)
            logging.info("WP response id=%s link=%s", wp_resp.get("id"), wp_resp.get("link"))
            time.sleep(1)  # polite pause between posts
        except Exception as e:
            logging.exception("Error processing topic '%s': %s", topic, e)

if __name__ == "__main__":
    # Example topics — replace or load from file/db
    topics = [
        "The Future of AI in Web Development",
        "Top 5 Python Tips for Beginners",
        "How WordPress Powers Small Business Websites"
    ]
    main(topics)

3) Windows step-by-step commands

Open Command Prompt (easier for venv activation), or use PowerShell with the ExecutionPolicy fix shown below.

A — Create project and venv (Command Prompt)

cd G:\python
mkdir wp-ai-agent
cd wp-ai-agent

python -m venv venv
venv\Scripts\activate.bat

When activated you’ll see (venv) in the prompt.

B — (PowerShell users)

If you prefer PowerShell and see the execution policy error, run once in an elevated PowerShell (or in your current session for one-off) before activating:

# temporary for this session (safe)
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

# then activate
.\venv\Scripts\Activate.ps1

C — Install dependencies

With the venv active:

pip install -r requirements.txt

If you hit the earlier “Failed to write executable” error outside venv, it’s because you weren’t in a venv. The above fixes that.

D — Create .env

Open Notepad or your editor and paste the .env content shown earlier. Save it in the project root.

E — Run the script

python ai_agent.py

It will generate posts from topics list and create drafts (or published posts if you set DEFAULT_STATUS=publish but keep default as draft while testing).

4) Scheduling (Windows Task Scheduler)

  1. Open Task Scheduler → Create Basic Task.
  2. Trigger: Daily / Weekly / At startup — whatever you want.
  3. Action: Start a program
    • Program/script: G:\python\wp-ai-agent\venv\Scripts\python.exe
    • Add arguments: G:\python\wp-ai-agent\ai_agent.py
    • Start in: G:\python\wp-ai-agent
  4. (Optional) Under General: check “Run whether user is logged on or not” and provide credentials (and “Run with highest privileges” if needed).

5) Troubleshooting quick guide

  • Permission / pip errors: Always activate venv before installing. If installing globally, use --user or run terminal as Administrator (not recommended).
  • Activate.ps1 blocked: Use activate.bat or run Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass in PowerShell.
  • WP REST auth 401/403:
    • Make sure your WP_APP_PASSWORD is copied exactly and that the user has rights to create posts.
    • Confirm WP URL is correct: https://your-site.com/wp-json/wp/v2/posts (no trailing slash issues).
    • Test with curl: curl -u "username:application-password" -X POST "https://your-site.com/wp-json/wp/v2/posts" -H "Content-Type: application/json" -d "{\"title\":\"Test\",\"content\":\"Hello\",\"status\":\"draft\"}"
  • OpenAI errors: check OPENAI_API_KEY. If you get rate limit or access errors, check billing & model availability.
  • Model output not JSON: This script attempts robust parsing. If the model still returns unexpected text, try lowering temperature or adjust the system prompt to emphasize strict JSON output.

6) Next steps / enhancements (optional)

  • Automatically upload and attach featured images (use WordPress /wp/v2/media endpoint) — can add later.
  • Store generated posts metadata in a local DB (SQLite) to avoid duplicates.
  • Use a topics queue (read from CSV or DB) instead of inline topics list.
  • Add retry/backoff for network errors (e.g., with tenacity).

Leave a Reply