AIREITER

AI Image

FLUX.2 ProGPT-Image 2Wan 2.7 Image ProGPT 4o ImageSeedream 5.0 ProSeedream V5 liteSeedream V4.5More

AI Video

Kling 3.0 Motion ControlSora 2 ProKling 3.0 TurboSora 2Kling 3.0Grok Imagine 1.5Veo 3.1More

LLM

Gemini 3.6 FlashGemini 3.1 ProKimi K3Gemini 3 ProGemini 2.5 ProClaude Opus 5Claude Fable 5More
Coming soonSeedance 2.5
Super ResolutionLyric Video GeneratorGPT Image 2 1K GeneratorGPT Image 2 Product Mockup GeneratorUse GPT-5.6 Online
API DOCSPRICING
BlogUpdatesLLM API GuideClaude API GuideKimi K3 API Guide
TEMPLATES
  • AIReiter
  • Blog
  • Feed 300 Competitor Ads to a Model and You Get Four Adjectives: Do This Instead

Feed 300 Competitor Ads to a Model and You Get Four Adjectives: Do This Instead

Last Updated: 2026-07-31 06:39:40

You pull three hundred competitor headlines out of a public ad library and you want to know what your rivals are actually pushing. The lazy move is to paste all three hundred into the chat box and ask "summarize the selling points of these ads." The model comes back fast: they push high value for money, emphasize user experience, build urgency, keep an upbeat emotional tone. You could have written those four lines without reading a single ad.

The data itself is fine. Each platform's ad library and creative center is public, you can browse it with your own account, and nothing is bypassed. The problem is how you asked. You handed the model an aggregate question, so all it can hand back is an aggregate answer. Three hundred ads compressed into four adjectives is a compression ratio so high that every bit of information leaks out, and what's left is a set of statements that are true and useless.

To get something usable out of bulk copy analysis, you have to be clear about what "usable" looks like. "Pushes value for money" is not actionable. "A savings promise plus numeric proof plus a time limit, a combination that runs in 40% of this batch and is aimed at price-sensitive audiences" is actionable, because it tells you directly how to set the variables on your next asset. To get the second kind, you can't do it in one shot. You have to split the one step into two.

Why "summarize the selling points" is always mush

The one-shot failure is structural, not a matter of a badly written prompt.

You're asking the model to do two things of completely different natures at once: extract information from each ad, and classify the extracted information. Extraction is deterministic. What promise did an ad make, did it give proof, the answers are basically unique. Classification is a judgment call. Which promises count as the same category and where the boundary sits both need reasoning. Cram those two into one call and the model takes the shortcut: it skips the ad-by-ad extraction, summarizes a wall of text on impression, and produces a heap of positive adjectives it thinks you want to hear.

One-shot also leaves you no intermediate product. You get "pushes value for money" as a conclusion, and you can't trace which ads it came from, what share they were, or whether there were counterexamples. Run it again on a fresh batch and the conclusion shifts. An analysis with no intermediate product can't be reviewed and can't be iterated.

Split one step into two: extract fields first, then cluster

The right way is two steps, with a layer of structured fields in the middle.

Step one: extract fields from each piece of copy on its own, into a fixed schema. At least four dimensions: the core promise, the evidence type, the urgency device, and the implied audience. This step looks at one ad at a time and outputs strict JSON, no prose.

You will receive one ad headline. Break it apart along the fixed fields below.
Output JSON only, no explanation.

<copy>
{{one piece of copy}}
</copy>

Fields and allowed values (pick only from the given enum; when unsure pick
unknown; do not invent values):

- promise: [save money, save time, look better, get healthier, make money,
  learn a skill, belong, identity, unknown]
- evidence: [testimonial, data/numbers, authority, before/after, demo,
  none, unknown]
- urgency: [time limit, scarcity, price-rise warning, fear of missing out,
  none, unknown]
- audience: a short phrase, inferred from the wording, for who it's talking to
  (e.g. "night owls", "moms with kids", "junior designers")

Output:
{"promise":"...","evidence":"...","urgency":"...","audience":"..."}

Extract three hundred ads and you're holding three hundred structured records instead of three hundred blocks of text. At this point, "summarize the selling points" has turned from a fuzzy semantic task into a data task you can count, group, and plot a distribution over.

Step two is the clustering, and it clusters the fields, not the original text. Cluster the raw text and you're back in the same fog of semantic similarity. Clustering fields groups on a handful of discrete dimensions, with clear boundaries.

The clustering step, and its counterexample requirement

The key to the clustering prompt isn't "group these." It's forcing the model to bring a counterexample.

Below are N ad headlines already extracted into structured records.
The categories are frozen. Do not add categories.

<records>
{{JSON array, each with promise/evidence/urgency/audience}}
</records>

Output three things in order:

1. Combination clustering
   Group by (promise x evidence x urgency), and give each group's record
   count and one representative sample.

2. Counterexample check
   For the top 3 groups by record count, pick one record per group whose
   audience clearly departs from the group's mainstream, and explain why it
   got grouped there. Is it really the same selling point, or did step one
   extract a field wrong?

3. Gaps
   Which combinations that should be common don't appear even once in this
   batch? Are those gaps "nobody's doing it" or "my sample didn't cover it"?

The second section is where this prompt earns its keep. The model naturally wants to please you, and if you don't push it, every cluster it gives is suspiciously self-consistent. Making it pull one record that doesn't fit out of a group it just formed forces it to keep checking past the point where it feels done. Either it exposes a classification boundary that's too coarse, or it exposes that step one extracted a field wrong.

This trick of making the model pick at its own work isn't specific to ad analysis. In code reverse engineering it's called the counter-evidence section: once the model recognizes an algorithm family, you don't take it on faith, you push it to answer "where does this differ from the standard implementation." Same principle, and it's what holds off the model's tendency to agree with you when dealing with obfuscated code and fingerprinting algorithms. In copy clustering, the counterexample check is your counter-evidence section.

Which model for which step

The two steps ask opposite things of a model, so running one model for both is pure waste:

Step

Capability it needs

Pick

model id

Extract fields per ad (hundreds to thousands, one call each)

Cheap, high concurrency, stable structured output

Claude Sonnet 5

claude-sonnet-5

Set the categories (read the whole sample once, induce the enum)

Long context

Kimi K3

kimi-k3

Cluster fields, counterexamples, gaps

Strong reasoning, willing to pick at itself

Claude Opus 5

claude-opus-5

Frequency attribution (is a high count effective, or a copycat chain)

Mid reasoning, explains against data

GPT-5.6 Sol

gpt-5.6-sol

The order-of-magnitude cost difference comes down to this split. Field extraction is the only step that scales linearly with volume: three hundred ads is three hundred calls, a thousand is a thousand. Clustering runs once per batch. Put the most expensive reasoning tier on the clustering that runs once or twice, put the cheapest tier on the extraction that runs hundreds of times, and the total bill differs by a factor of ten. Do it backwards, expensive tier on extraction and cheap tier on clustering, and it's the most common waste I've seen: extraction doesn't need that much reasoning, and clustering falls down exactly where the cheap tier is weakest.

How much the downgrade saves you, don't take my word for it, run one round:

  1. Take 20 to 30 ads from what you've already pulled.

  2. Extract fields: run the same batch through claude-sonnet-5 and claude-opus-5, and compare the four fields record by record for agreement.

  3. Cluster: take the same extracted records, cluster with both tiers, and look at two things. Is the counterexample check actually picking at the groups or restating the candidate, and do the gaps point to an actionable next step.

  4. The result reads straight off: if extraction agreement is high, drop this step to the cheap tier and save money; if the cheap tier can't produce a useful counterexample when clustering, that step stays on the reasoning tier.

Trap one: letting the model set its own categories

The easiest trap is not giving an enum during extraction and letting the model invent categories.

On one batch it looks fine. The model gives you a set of categories that sound reasonable. The problem is the second batch: the same kind of copy, and the category names have changed, the granularity shifted, the boundaries moved. You try to put two batches side by side to see a trend and find they don't line up at all. Is the first batch's "discount" the same as the second's "save money"? Nobody knows. When categories drift per batch, cross-batch comparison is all fake.

The fix is to separate setting categories from using them. First use the long-context tier to read a large batch of samples in one pass (hundreds at a time, exactly the long-context job) and induce a set of enums that's comprehensive and consistent in granularity, then review it by hand and freeze it. After that, all extraction can only pick from that frozen enum, with unknown as the catch-all, and never invent a new category on the spot. That line in the extraction prompt, "pick only from the given enum, do not invent," is this constraint made concrete.

In one sentence: you set the categories, the model only fills the boxes.

Trap two: treating frequency as effectiveness

Once you've extracted and clustered, you'll instinctively sort by record count and treat the most frequent selling point as the "most effective" one. This step is wrong in a way that's easy to miss.

High frequency only says "everybody writes it this way." It doesn't say "writing it this way works." The copycat chain in advertising is real: one ad hits, within a week the whole field piles on, and dozens of identical headlines appear in the creative center overnight. Your frequency count folds all those copycats in and concludes "this selling point is the most popular," when they may be tanking as a group and nobody stopped first. Frequency measures conformity, not effect.

To separate frequency from effectiveness, you have to attach effect data to each record. Every asset in a public ad library carries play counts, likes, CTR, that sort of field. What you should actually sort by is "the share of a given selling-point combination that lands in a high performance percentile," not raw record count. How to read second-by-second retention and CTR percentiles into meaningful signal is the subject of another piece. Here I'll stress one thing: the frequency table and the effect table have to be two tables, and the moment you fold them into one, the conclusion is ruined.

The model's job at this step is attribution, not sorting. Feed a high-frequency combination together with its effect distribution to the mid-reasoning tier and ask "is this frequency high because it works, or because they're copying each other," and have it judge against the numbers. Getting the attribution wrong costs little, since you still validate against real effect data in the end.

Trap three: only analyzing winners

The third trap hides in the data source, and if you're not careful you never notice it: the ad library and creative center show you the ones that ran well by default. Entries like "hot ads" or "Top Ads" are, in essence, survivors the platform already filtered by performance for you.

You cluster on a pile of winners, find "the common features of successful ads," and copy them. That's textbook survivorship bias, because the ads that flopped very likely have the exact same features. Suppose 90% of winners used "time limit," and your conclusion is "time limits work." But if 90% of the failed ads also used time limits, the feature has no discriminating power at all. It's an industry default, unrelated to success or failure.

The fix is to give the winners a control group. A public ad library can usually filter by industry, marketing objective, and time window. Use the same filters to reach the assets that got impressions but had clearly low engagement, extract and cluster them too, and set them beside the winner group. What's valuable isn't "what winners have," it's the difference set, "what winners have that losers don't." Only a feature that shows up in that difference set is worth writing into your next asset.

You won't get a complete loser sample, and that's fine. Even a small low-performing control is far more credible than a conclusion drawn from Top Ads alone.

The output is a variable table, not a summary

Walk through the two steps and dodge the three traps, and the final product should not be a paragraph of "this batch's competitors are pushing XX." That loops back to mush. The product should be a variable table:

  • Which values each dimension (promise, evidence, urgency, audience) takes.

  • Which combinations are already validated as effective (in the difference set, and high in the performance percentile).

  • Which combinations nobody has tried yet (dug out of the gaps section).

This table can go straight in as input to a generation task. The validated-effective combinations go off to bulk-produce variants, the blank combinations go off as low-cost probes. The copy-dimension variables feed a text model to write scripts, the visual and tone variables feed image and video models to produce assets, and the line from competitor copy to your own finished spot closes. That variable table is exactly what the brief stage in the creative pipeline reads.

Clustering isn't for producing a pretty classification chart. It's for producing this table that drives the next batch of production.

One key connects all four tiers

The flow above needs four tiers: the cheap high-concurrency tier for extraction, the long-context tier for setting categories, the reasoning tier for clustering, the mid-reasoning tier for attribution. They come from several vendors, with several SDKs, auth schemes, and error formats. Wiring your client to several interfaces just to switch models between steps isn't worth it, which is the real reason most people end up on one model for everything and then hit the waste from earlier: either an expensive tier burning money on extraction, or a cheap tier that can't produce a counterexample when clustering.

AIReiter flattens that layer: one key, one OpenAI-compatible interface, all four tiers behind it, switching by changing the model field in the request body.

# Extract fields: the cheap high-concurrency tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "messages": [{"role": "user", "content": "<extraction prompt + one piece of copy>"}]
  }'

# Cluster: switch to the reasoning tier, leave the rest
#   "model": "claude-opus-5"
# Frequency attribution:
#   "model": "gpt-5.6-sol"

If you already use the OpenAI SDK, point base_url at https://aireiter.com/api/v1 and change nothing else. On the Anthropic SDK, hit POST /api/v1/messages with the same key.

On price, Claude models run at 30% off list and GPT models at half, and the discount lands right on this flow's main cost. Field extraction is the one step that scales linearly with volume: three hundred ads is three hundred calls, a thousand is a thousand. It runs on the cheapest Sonnet tier with 30% off on top, and it's the bulk of the savings. Clustering and attribution run a few times per batch, so a reasoning tier there doesn't hurt. Setting categories runs on the long-context Kimi K3, available on the same key, once per batch.

  • Get an API key

  • Try it without signing up: run a few ads by hand first, compare the cheap tier and the reasoning tier on extraction agreement and on clustering counterexamples, then script it once it holds.

In closing

Bulk ad-copy analysis comes out as mush not because the model is weak, but because you crammed extraction and classification into one call.

Split it into two steps: extract structured fields per ad against a frozen enum, run it hundreds of times on the cheap tier, then cluster the fields and force the model to bring a counterexample, run once on the reasoning tier. Watch the three traps throughout: don't let the model invent categories on the spot, don't read frequency as effectiveness, give winners a control group. End on a variable table that drives the next batch of production, not a line that says "pushes value for money."

The model in this flow is two different tools: a cheap extractor, and a classifier willing to pick at itself. Use them separately, on the right tiers, and a few hundred ads give you something actionable. Mash them into one model and all you get is adjectives.

>_AIReiter Model Directory

Fast API access to models related to this guide

Claude Sonnet 5

Chat

A balanced Claude model for advanced reasoning, coding, and everyday work.

AnthropicGet API Key >

Claude Opus 5

Chat

A premium Claude model for complex reasoning, coding, and long-context professional work.

anthropicGet API Key >

GPT-5.6 Sol

Chat

A premium GPT-5.6 text model for demanding coding, reasoning, and long-form agent work.

OpenAIGet API Key >

Kimi K3

Chat

A long-context reasoning model for coding, writing, analysis, and agent workflows.

moonshotGet API Key >

Claude Fable 5

Chat

A premium Claude model for deep reasoning and complex long-form work.

AnthropicGet API Key >

Recent Posts

GPT-5.6 Price Cut: What Luna and Terra Really Cost Now

2026-07-31

Invalid API Key: Diagnose 401 and 403 Before You Fix

2026-07-31

Fix OpenRouter 429: Provider Error or Rate Limit?

2026-07-31

DeepSeek V4 Flash vs GLM-5.2: 0731 Update Tested

2026-07-31
AIREITER

Questions? Contact us at
[email protected]

LLM

Gemini 3.6 FlashGemini 3.1 ProKimi K3Gemini 3 ProGemini 2.5 Pro

AI Video

Kling 3.0 Motion ControlSora 2 ProKling 3.0 TurboSora 2Kling 3.0

AI Image

FLUX.2 ProGPT-Image 2Wan 2.7 Image ProGPT 4o ImageSeedream 5.0 Pro

Blog

View All →

Company

Privacy PolicyTerms of ServiceRefund Policy

© 2026 AIReiter. All rights reserved.