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)
- Open Task Scheduler → Create Basic Task.
- Trigger: Daily / Weekly / At startup — whatever you want.
- 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
- Program/script:
- (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 runSet-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\"}"
- Make sure your
- 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
).