Ad Copy Generator
A Python CLI tool that takes a product name and target audience as input and returns a structured JSON object containing a social media caption, ad headline, and CTA — powered by Groq's free API (LLaMA 3.3 70B).
Quickstart
# 1. Install dependencies
pip install groq
# 2. Set your API key (get one free at console.groq.com)
# Windows PowerShell:
$env:GROQ_API_KEY="your_key_here"
# Mac/Linux:
export GROQ_API_KEY="your_key_here"
# 3. Run
python generate.py --product "Matcha latte mix" --audience "Health-conscious millennials"
Example Output
==================================================
Product : Smart water bottle with hydration tracking
Audience: Health-conscious working professionals aged 25-35
==================================================
{
"caption": "Stay on top of your hydration goals, even on the busiest days. Our smart bottle tracks your intake and sends reminders to drink up.",
"headline": "Hydrate smarter. Perform better.",
"cta": "Get your smart bottle now"
}
Options
| Flag | Description |
|---|
--product | Product name or description |
--audience | Target audience description |
--verbose | Print debug info and raw model output |
Prompt Design Rationale
1. Separating System and User Prompts
The system prompt handles everything that does not change between calls: the model's persona, the output schema, tone rules, and hard constraints. The user turn carries only the two variable inputs — product and audience.
Why this matters: Mixing structural instructions with variable data in a single prompt makes the model treat them with equal weight. Separating them creates a clear hierarchy — the system prompt is the contract, and the user turn is the data that fulfils it. This also mirrors how production systems work, where system prompts are pre-loaded once and user turns are dynamic per request.
2. Few-Shot Examples
Three input/output examples are included directly in the conversation history before the actual request. Each example covers a different audience archetype:
- Remote workers (25–40) → professional, direct tone
- Gen Z fitness enthusiasts (18–24) → casual, emoji-friendly tone
- C-suite executives → authoritative, ROI-focused tone
Why this matters: Few-shot prompting is one of the most reliable ways to reduce variance in LLM outputs. Rather than describing what good copy looks like (which the model must interpret), you show it. The model pattern-matches tone, structure, and length from the examples rather than guessing. Without examples, the same model might produce identical-sounding captions for a teen skincare brand and an enterprise SaaS product.
Why three examples: One example risks the model treating it as a rigid template. Two creates an implicit binary. Three examples give the model enough signal to interpolate across a full spectrum of tones — which is exactly what a tool handling many audience types needs.
3. Explicit Output Schema in the System Prompt
The system prompt includes the exact JSON structure the model must return, with placeholder descriptions for each field, along with a hard instruction to return nothing else — no preamble, no markdown fences, no commentary.
Why this matters: LLMs default to being conversational. Without an explicit schema and a strict "nothing else" instruction, models often wrap the JSON in explanatory text like "Here is your ad copy:" — which breaks automated parsing. Showing the exact shape of the expected output inside the prompt is far more reliable than describing it in words alone.
4. Defensive JSON Parsing
Despite the strict output instruction, the parser defensively strips ```json ``` markdown fences and validates that all three required keys are present before returning the result.
Why this matters: LLMs sometimes add wrapping even when told not to, especially across different models or prompt contexts. Defensive parsing makes the tool robust rather than brittle — it won't crash on minor model non-compliance. This is the difference between a demo script and a deployable tool.
5. Negative Constraints
The system prompt explicitly bans specific clichés ("game-changer", "revolutionary", "unlock your potential") and structural anti-patterns (leading with features instead of benefits, weak CTA verbs).
Why this matters: LLMs trained on marketing copy have absorbed enormous volumes of generic ad-speak. Without explicit exclusions, the model gravitates toward the statistical average of what it has seen — which is forgettable filler. Negative constraints push the model away from its defaults toward more original, higher-quality output.
6. Audience-Tone Coupling
The system prompt explicitly instructs the model to derive tone from the audience description (e.g. "Gen Z → casual/playful; C-suite → authoritative"). This is reinforced by the few-shot examples, meaning tone adaptation happens automatically from natural language — the user never needs to specify a separate tone parameter.
Why this matters: In a real marketing workflow, users think in terms of who they're targeting, not abstract tone labels. Deriving tone from audience description reduces input friction and produces more coherent results. It also avoids contradictory inputs like specifying "formal tone" for a Gen Z audience.
7. Model Choice: LLaMA 3.3 70B via Groq
LLaMA 3.3 70B is used rather than a smaller/faster model. For a creative writing task where output quality directly determines usefulness, a capable 70B model produces noticeably better copy — more natural phrasing, better tone calibration, stronger CTAs. Groq's inference speed means even the large model responds in under a second, so there is no practical cost to choosing quality here.
Project Structure
ad-copy-generator/
├── generate.py # Main script
├── requirements.txt # Dependencies (groq only)
└── README.md # This file
Error Handling
| Error | Cause | Behaviour |
|---|
| Missing API key | GROQ_API_KEY not set | Prints setup instructions and exits |
| Invalid JSON | Model returned malformed output | Shows raw output for debugging |
| Missing keys | Response missing caption/headline/cta | Identifies which keys are absent |