James Galley Software engineer - SaaS, AI automation & business operations

Why AI image generators ignore the word "not"

A diffusion model can't draw a negation - "does not wear armour" produces armour. What I learnt wiring Stability's Stable Image Core into a character builder, and the small LLM I put in front of it.

AI • AWS • Development

Recently I added portrait generation to a role-playing themed web app, using Stability's Stable Image Core model running on Amazon Bedrock. The first round of testing produced my favourite bug in a long time: a character described as someone who "does not wear armour" came back in gleaming full plate. Rewording the description or being more emphatic about the lack of armour didn't help.

Diffusion models can't draw "not"

What I learnt was a diffusion model treats the prompt as a description of what to draw, and every word in the prompt pulls the image towards those words - with the exception of non-visual words. Somewhere in the encoding, "does not wear armour" simply becomes armour: the negation contributes nothing visual. It seems this is a known and well-studied failure across vision-language systems generally - researchers at MIT call it affirmation bias, models skipping over negation words and latching onto the objects instead - and it shows up in image generators specifically: one paper testing logic in them found that "a man without glasses" reliably produces a man wearing glasses. My working mental model, from a bit of reading rather than any depth in diffusion internals, is that the prompt gets compressed into a numerical representation of visual concepts which steers each step of the generation, and "not" is not a visual concept. There's also no instruction channel: nowhere to put a rule the model must obey, the way a system prompt works on a chat model. Everything you say ends up in the picture.

What Stable Image Core actually wants

The negation problem was one of two things wrong with my first attempt. The other was that the prompt had been written like a brief for a chat model - polite second-person sentences, "you are creating a portrait of...". Stable Image Core wants a dense descriptive phrase: comma-separated clauses, nouns and adjectives, with anything empty dropped rather than explained. I also discovered it has one channel where an exclusion genuinely works - a separate negative_prompt field of keywords the generation is steered away from.

The problem is that the instruction arrived in a language the model doesn't speak, buried in prose that also carried backstory, motivation and history - none of which can be drawn either.

Put a small LLM in front of it

You can't ask users to write diffusion prompts, and you shouldn't: the description field is a piece of creative writing, and in a game built around inventing characters, writing it is part of the fun. So we translate. Before the image model is called, the description passes through a small, fast LLM - Claude Haiku on Bedrock, with the temperature at zero so its answers are as repeatable as possible - with one job: return what should be drawn, and what must be kept out.

The request body for a Bedrock InvokeModel call ends up looking like this:

{
  "prompt": "Fantasy character portrait of an original fictional character, male, human, wandering scholar, lean build, patched travel-worn robes, ink-stained fingers, head and shoulders framing, single subject, plain uncluttered background",
  "negative_prompt": "armour, sword, real person, celebrity, public figure, likeness of a real person",
  "aspect_ratio": "1:1",
  "output_format": "png"
}

The rules are roughly: keep only what can be seen, drop anything that can't be drawn - history, motivation, why something is the way it is. Write comma-separated clauses, and invent nothing the description doesn't support. Everything the description rules out goes into the negative list, along with anything that appears only in past events: the sword above came from a character who "gave up the sword years ago", which a diffusion model would otherwise cheerfully strap to his back.

The translation call costs a fraction of a penny and adds around a second to an image generation that takes three or four itself, so it's a negligible addition to a waiting user experience. This pattern isn't a Bedrock-specific thing either as it would work identically against Stability's and Anthropic's own APIs.

The famous person problem

One more thing the testing turned up. Fresh from a family movie night watching The Greatest Showman, I named a test character Zendaya, and the generated image came back as recognisably Zendaya. I realised a name belonging to a real person is read as a request for their likeness, even though a name carries no visual information. Of course asking for "not THE Zendaya" is useless too for the reasons above.

So my fixes here were firstly to avoid passing the specific name property into the prompt, and secondly to use the translation step again with rules to keep only what can be seen, so a name is never passed to the diffusion model through a backdoor of the description.

Behind those sit the two more layers of defence visible in the example above: the prompt introduces the subject positively, as "an original fictional character", rather than trying to say who it isn't, and a standing negative prompt held on the server ("real person, celebrity, public figure, likeness of a real person") which any per-request negatives are added to rather than allowed to replace.

AI in front of AI

It turns out this is the industry's answer as well: DALL-E 3 does the same thing with a GPT model rewriting every prompt before the image model sees it. Doing it at the application layer instead means we can decide the rules - what gets kept, what gets dropped, and what lands in the negative prompt.

This is a pattern I've seen in many places within AI application development - using AI to format the input for the next model in the chain. Text written by a human and a prompt written for a diffusion model are different languages, and a small model translating between them is cheap, fast, and easily worth it.