We are going to build a multi-pass marketing copy generator that turns a short product brief into three ready-to-use assets: an email, a tweet, and an ad headline. It runs two separate Oxlo.ai models in sequence to draft and then refine the copy, which keeps quality high without the token-cost surprises you get from long context windows on metered providers.
What you'll need
Before starting, grab the following:
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Verify the connection
Before we write prompts, we should confirm the API key and base URL are wired correctly. I always start with a smoke test against a reliable general-purpose model. This script calls Oxlo.ai and prints a short confirmation.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Confirm the API is working with one sentence."},
],
)
print(response.choices[0].message.content)
If you see a response, your environment is ready. Oxlo.ai serves this with no cold start, so the first request should return as fast as any subsequent one.
Step 2: Define the system prompt and generate structured copy
The core of the tool is a system prompt that constrains the model to output valid JSON containing three channels. I use DeepSeek V3.2 here because it handles structured generation and coding tasks cleanly, and it sits on Oxlo.ai's free tier, so iterating on the prompt costs nothing per request.
Here is the prompt I landed on after a few iterations:
SYSTEM_PROMPT = """You are a senior growth copywriter.
The user will provide a one-sentence product brief.
Write three marketing assets in JSON format:
- email_subject: a compelling subject line under 60 characters
- email_body: a paragraph under 120 words
- tweet: under 280 characters, with a strong hook
- ad_headline: under 40 characters
Respond ONLY with valid JSON. Do not include markdown fences."""
Now wire it into a function that returns parsed Python dictionaries.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a senior growth copywriter.
The user will provide a one-sentence product brief.
Write three marketing assets in JSON format:
- email_subject: a compelling subject line under 60 characters
- email_body: a paragraph under 120 words
- tweet: under 280 characters, with a strong hook
- ad_headline: under 40 characters
Respond ONLY with valid JSON. Do not include markdown fences."""
def generate_copy(brief: str):
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": brief},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 3: Add a critique pass with a reasoning model
Raw first drafts usually have weak hooks or bland adjectives. I run a second pass with Kimi K2.6, which excels at chain-of-thought reasoning, to audit the JSON and return specific rewrite instructions rather than rewriting the copy itself. Keeping the critique separate from the rewrite preserves the original structure while improving substance.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
CRITIQUE_PROMPT = """You are a ruthless copy editor.
Review the provided marketing JSON.
Return a JSON object with exactly two keys:
- strengths: a list of what works
- improvements: a list of specific, actionable rewrite instructions
for each asset that would increase conversion
Respond ONLY with valid JSON."""
def critique_copy(copy_json: dict):
response = client.chat.completions.create(
model="kimi-k2.6",
messages=[
{"role": "system", "content": CRITIQUE_PROMPT},
{"role": "user", "content": json.dumps(copy_json, indent=2)},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 4: Apply the revisions
Now we merge the original brief, the first draft, and the critique into a final revision prompt. I switch back to Llama 3.3 70B for this step because it follows complex instructions faithfully and produces polished, natural prose. Because Oxlo.ai charges per request rather than per token, running three separate model calls for one brief still means three flat charges, which makes this multi-agent pipeline practical. You can see current plan details at https://oxlo.ai/pricing.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
REVISION_PROMPT = """You are a senior growth copywriter.
You will receive:
1. The original product brief
2. A JSON draft of marketing assets
3. A JSON critique with improvement instructions
Produce a revised JSON object containing the same keys
(email_subject, email_body, tweet, ad_headline)
incorporating every valid improvement.
Maintain the original length constraints.
Respond ONLY with valid JSON."""
def revise_copy(brief: str, draft: dict, critique: dict):
user_content = f"Brief: {brief}\n\nDraft:\n{json.dumps(draft, indent=2)}\n\nCritique:\n{json.dumps(critique, indent=2)}"
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": REVISION_PROMPT},
{"role": "user", "content": user_content},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 5: Wire everything into a CLI
Finally, we chain the three functions and add a thin command-line interface so you can pipe in a brief from your release notes or CRM.
import json
import sys
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a senior growth copywriter.
The user will provide a one-sentence product brief.
Write three marketing assets in JSON format:
- email_subject: a compelling subject line under 60 characters
- email_body: a paragraph under 120 words
- tweet: under 280 characters, with a strong hook
- ad_headline: under 40 characters
Respond ONLY with valid JSON. Do not include markdown fences."""
CRITIQUE_PROMPT = """You are a ruthless copy editor.
Review the provided marketing JSON.
Return a JSON object with exactly two keys:
- strengths: a list of what works
- improvements: a list of specific, actionable rewrite instructions
for each asset that would increase conversion
Respond ONLY with valid JSON."""
REVISION_PROMPT = """You are a senior growth copywriter.
You will receive:
1. The original product brief
2. A JSON draft of marketing assets
3. A JSON critique with improvement instructions
Produce a revised JSON object containing the same keys
(email_subject, email_body, tweet, ad_headline)
incorporating every valid improvement.
Maintain the original length constraints.
Respond ONLY with valid JSON."""
def generate_copy(brief: str):
response = client.chat.completions.create(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": brief},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
def critique_copy(copy_json: dict):
response = client.chat.completions.create(
model="kimi-k2.6",
messages=[
{"role": "system", "content": CRITIQUE_PROMPT},
{"role": "user", "content": json.dumps(copy_json, indent=2)},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
def revise_copy(brief: str, draft: dict, critique: dict):
user_content = f"Brief: {brief}\n\nDraft:\n{json.dumps(draft, indent=2)}\n\nCritique:\n{json.dumps(critique, indent=2)}"
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": REVISION_PROMPT},
{"role": "user", "content": user_content},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
if __name__ == "__main__":
brief = sys.argv[1] if len(sys.argv) > 1 else "AI-powered code review that runs in seconds"
print(f"Brief: {brief}\n")
draft = generate_copy(brief)
print("--- First Draft ---")
print(json.dumps(draft, indent=2))
critique = critique_copy(draft)
print("\n--- Critique ---")
print(json.dumps(critique, indent=2))
final = revise_copy(brief, draft, critique)
print("\n--- Final Copy ---")
print(json.dumps(final, indent=2))
Run it
Save the full script as copy_generator.py and run it with a real product brief. Here is what I got when I pointed it at Oxlo.ai's own value proposition:
python copy_generator.py "AI inference API with flat per-request pricing for open-source LLMs"
Output:
Brief: AI inference API with flat per-request pricing for open-source LLMs
--- First Draft ---
{
"email_subject": "Cut your LLM bill in half today",
"email_body": "Stop paying per token. Oxlo.ai gives you flat per-request pricing across 45+ open-source models, so your costs stay predictable even when prompts grow long. Switch in minutes with our OpenAI-compatible SDK.",
"tweet": "Your LLM costs scale with prompt length. Ours don't. Flat per-request pricing for 45+ open-source models on Oxlo.ai. Predictable bills, zero cold starts.",
"ad_headline": "Flat Pricing, Powerful Models"
}
--- Critique ---
{
"strengths": [
"Clear value proposition in email_body",
"Tweet uses a strong contrast hook"
],
"improvements": [
"Make the email_subject more specific to open-source models",
"Add a concrete savings metric or customer persona to the email_body",
"Shorten the tweet hashtag-style claim and add a CTA",
"Sharpen the ad_headline with a verb"
]
}
--- Final Copy ---
{
"email_subject": "Slash open-source LLM costs with flat pricing",
"email_body": "Engineers running long-context workloads often see token bills spike overnight. Oxlo.ai replaces metered tokens with flat per-request pricing across 45+ open-source and proprietary models. Migrate in minutes using our drop-in OpenAI SDK and get predictable invoices, even for agentic pipelines.",
"tweet": "Token bills spiking? Oxlo.ai charges flat per request, not per token. 45+ models, zero cold starts, OpenAI-compatible API. Try the free tier today.",
"ad_headline": "Cut LLM Costs Instantly"
}
The pipeline took three requests. On Oxlo.ai, that means three flat charges, regardless of how long the critique or revision context grew. If you are iterating on dozens of briefs a day, that predictability matters.
Wrap-up
Two concrete ways to extend this. First, replace the CLI argument with a webhook that listens to your CMS and auto-generates social assets on every product update. Second, add a fourth pass using Qwen 3 32B to produce a Mandarin or Spanish variant of each asset, since it handles multilingual reasoning well. Both are cheap to experiment with because Oxlo.ai does not penalize long prompts or multi-turn context.
Top comments (0)