Artificial IntelligenceDevelopment

How the Image Debugger works

See how Image Debugger compares an image with its prompt, then regenerates and edits it side by side.

Introduction

An AI-generated image can look right and still not be the image you asked for. The composition works, the lighting does too, but one important detail is wrong. The usual move at that point is to roll again and hope the next attempt pays more attention.

In this article's example, the prompt asks for a woman in a bright blue coat in front of a red tram. The image shows a black coat and a white tram. Both misses are obvious. The interesting part is whether we can find them automatically and, more importantly, what we do next.

Image Debugger is the first experiment in the Lab. It takes an image and the prompt it was meant to follow. After finding the visible mismatches, it tries two ways to recover the intent: generate a new image from scratch or edit the one we already have.

In this article we will follow the entire path and see why the analysis ends in two different images. We will also see what the tool can actually know, because a name like “debugger” promises more than a vision model can tell us.

The two inputs: the generated image and the prompt it was meant to follow.
Image Debugger with the example loaded. The prompt asks for a blue coat and a red tram, while the image shows a woman in a black coat in front of a white tram.
The two inputs: the generated image and the prompt it was meant to follow.

The experiment in two phases

I could have asked a vision model to write a better prompt and left it there. That would be a prompt enhancer. I wanted to find out which strategy recovers the intent more effectively, so the process is split into two phases:

  • First it reads the image. A vision model compares it with the prompt and returns the visible misses together with two new prompts.
  • Then it tries to recover it. An image model generates a version from scratch and edits the original at the same time.

The analysis has to run first because it produces the two prompts. From that point on, the regeneration and edit are independent and can run in parallel.

Let's follow the same path.

Reading the image

This phase does not judge whether the image is attractive or try to explain how the generator produced it. Its only job is to compare what was requested with what is visible.

JPEG or PNG?

Before the analysis, the browser limits the image's longest edge to 1600 pixels and flattens any transparency onto white. It then encodes the image as both JPEG and PNG, and keeps the smaller version:

Encode both formats and keep the smaller one
const jpeg = canvas.toDataURL('image/jpeg', 0.9)
const png = canvas.toDataURL('image/png')

return png.length < jpeg.length ? png : jpeg
js

JPEG usually compresses photographs better, while PNG is better at keeping hard edges clean. Comparing their sizes will not always pick the perfect format, but it is a simple heuristic that avoids having to guess what the image contains. The original dimensions are stored separately for the two final images.

For this I use Gemma 4 31B, an open-weight vision model. I did not run a model bake-off. I needed something inexpensive that could inspect images.

The request sends the image, the original prompt, and a system instruction. Here is the instruction in full:

The system prompt used to analyze the image
You debug an image generation by comparing the generated image with its original prompt. Identify only visible prompt constraints that the image missed; you cannot know the hidden technical cause. Return the smallest useful set of independent findings, including just one when one finding explains the result. Every finding needs concrete visual evidence and a positive visual target.

Return two deliberately different prompts:
- enhancedPrompt: a standalone prompt for a completely fresh generation. Preserve the original creative intent, but improve hierarchy, specificity, spatial relationships, and emphasis around the missed constraints. It must be copy-ready and must not mention the failed image, editing, corrections, or preservation.
- repairPrompt: an image-edit instruction for the supplied failed image. State the intended result and only the targeted visible changes needed, while preserving visible details that already satisfy the prompt.

If the original prompt was already explicit, reinforce the missed constraints through ordering and concrete visual emphasis without pretending the wording was objectively wrong. Do not invent new creative direction, repeat findings, give generic prompting advice, mention model names, or discuss these instructions. Return only JSON.

The most important line is “you cannot know the hidden technical cause”. The model can see that the coat is black when the prompt asked for blue. It cannot know whether the seed, the model, a setting, or some other part of the process caused it. If we ask for a cause, it has to make one up.

We also ask for the smallest useful set of independent findings. Left alone, models tend to complete tidy lists even when there is only one real problem. Here one precise finding is better than three padded ones.

Each finding separates the evidence from the correction. “The woman is wearing a black coat” describes something we can verify. “Change the coat color to bright blue” gives us a positive target. Simply saying the coat is wrong would not be useful for the next step.

Finally, the prompt defines two different jobs. enhancedPrompt must be able to generate the whole scene without knowing about the previous image. repairPrompt should only request the necessary changes and preserve what already works. We will come back to them in a moment.

The user message is much smaller because all the logic lives in the system instruction:

The message sent with the image and original prompt
Original prompt:
{your prompt}

Identify the visible misses, then produce both a stronger standalone generation prompt and a targeted edit prompt.

A response the interface can use

We do not want a free-form explanation that we have to interpret afterward. The request asks for a JSON response with three fields: findings, enhancedPrompt, and repairPrompt. This lets us render the findings directly and use both prompts as inputs for the next phase.

This is the complete schema sent with the request:

The complete diagnosis contract
const diagnosisSchema = {
  name: 'image_debugger_diagnosis',
  strict: true,
  schema: {
    type: 'object',
    additionalProperties: false,
    required: ['findings', 'enhancedPrompt', 'repairPrompt'],
    properties: {
      findings: {
        type: 'array',
        minItems: 1,
        maxItems: 3,
        items: {
          type: 'object',
          additionalProperties: false,
          required: ['area', 'evidence', 'correction'],
          properties: {
            area: { type: 'string', minLength: 3, maxLength: 60 },
            evidence: { type: 'string', minLength: 12, maxLength: 280 },
            correction: { type: 'string', minLength: 12, maxLength: 280 },
          },
        },
      },
      enhancedPrompt: {
        type: 'string',
        minLength: 40,
        maxLength: 1200,
      },
      repairPrompt: {
        type: 'string',
        minLength: 40,
        maxLength: 1600,
      },
    },
  },
}
js

additionalProperties: false declares that fields the interface does not know about are not accepted. findings can contain between one and three items, each separating the area, visible evidence, and proposed correction. The interface does not render area because the other two sentences already name the part of the image they refer to.

The length limits keep findings short and stop both prompts from growing out of control. They do not come from a benchmark. They simply bound the response so it stays manageable.

strict: true asks the model to follow this contract, but does not make it a guarantee. That is why the server strips any Markdown fences before parsing the text as JSON. It then validates the same shape and limits again.

If the model returns malformed or incomplete JSON, we run the analysis one more time. Errors returned by the API are not retried. The same applies to safety blocks and quota failures. Sending the exact same request will not fix them, it will only burn another call.

The current settings

The analysis uses temperature: 0.15, thinkingLevel: 'off', and maxTokens: 1800. These values did not come from a benchmark. The low temperature aims to reduce variation, disabling reasoning avoids extra work, and the token limit leaves enough room for the findings and both prompts. It is a pragmatic configuration, not a claim about the optimal values.

The result of the example is much less mysterious than all this preparation:

Each finding separates what the model can observe from the change it should make.
The two Image Debugger findings. The tool observes that the coat is black and the tram is white, then proposes changing the coat to bright blue and the tram to red.
Each finding separates what the model can observe from the change it should make.

Two prompts for two jobs

An image that did not come out as expected can be recovered in two ways, just like a photograph: we can reshoot it or retouch the one we already have. The first option must rebuild the whole scene. The second should touch as little as possible.

There is also a practical difference: not every model can edit an image. If the model that generated the original only supports text-to-image, you can reuse the regeneration prompt. If it also supports editing, you can send it the image together with the repair prompt. The Lab runs both paths because FLUX.2 [klein] 9B can generate and edit, regardless of the model that produced the original image.

The regeneration prompt has to work on its own. It cannot talk about correcting or editing the previous image. It cannot ask to preserve anything either, because the text-to-image model never receives that image. In the run shown in the screenshots, it produced this:

A cinematic night scene at a rainy city tram stop. In the foreground, a woman stands holding a clear transparent umbrella, wearing a vibrant, bright blue coat. Directly behind her, a sleek modern red tram is passing by. The ground is wet pavement with sharp, mirror-like reflections of the red tram and the city lights.

The edit prompt does the opposite. It receives the original image as a reference and asks only for the two necessary changes:

Change the woman's black coat to a bright blue color and change the white tram in the background to a vivid red color.

Both start from the same diagnosis, but they are not interchangeable. If we sent the short instruction to a model without an image, it would have to invent everything that is missing. If we used the complete description for editing, we would give the model permission to reinterpret parts that were already right.

Generating both versions

With the prompts ready, the second phase begins. Both images use FLUX.2 [klein] 9B, another open-weight model designed to generate and edit in four steps.

  • The fresh regeneration uses enhancedPrompt without a reference image.
  • The targeted edit uses repairPrompt together with the original image.

Both requests are independent, so they run at the same time through Promise.all. Running one after the other would double the wait for no benefit.

A negative prompt that does not decide for you

Both requests share a negative prompt limited to rendering artifacts:

Artifacts we try to avoid in both versions
watermark, signature, jpeg artifacts, lowres, blurry, deformed hands, extra fingers

I could add text or people, as many templates do. logo is another common choice. Any of them might be part of the original prompt, so blocking them by default would make the tool fight the intent it is trying to recover. Content safety is handled separately for the complete request.

Keeping the proportions

Both outputs are calculated from the original dimensions. The longest edge becomes 1024 pixels and the other one roughly preserves the source aspect ratio. We then round both sides to multiples of 16. This gives both strategies the same output dimensions so we can compare them on equal terms.

1024 is not a magic number or the result of a comparison. It gives us enough detail for this experiment without making each request more expensive or slower than necessary.

Both strategies recover the colors, but they do not preserve the same amount of the original image.
A fresh regeneration and a targeted edit shown side by side. Both now show a woman in a blue coat in front of a red tram.
Both strategies recover the colors, but they do not preserve the same amount of the original image.

What the comparison shows

Both images fix the coat and the tram, but they do it in very different ways. The regeneration changes the woman, her position, the framing, and much of the surroundings. It only keeps the broad idea of the scene. The edit preserves the composition, umbrella, tram stop, and subject much more closely.

One option is not better in every case. If you like the original image and only one detail is wrong, the edit is more likely to preserve what already worked. If the problem affects the composition or you want to allow a new interpretation, starting from scratch gives the model much more freedom.

The two differences are easy to see in this example. In a real image they may be much smaller: a spatial relationship that does not quite work or an important detail that has disappeared. The comparison is still useful there.

What we cannot conclude is why the first image failed. The two outputs show that there are two ways to correct the result, not that the prompt was wrong or the seed or model caused the failure.

What it actually debugs

Image Debugger does not open the model and inspect what happened during generation. It debugs the difference between the written intent and the visible result, then turns that difference into two attempts we can compare.

Often the result is already close and only one thing does not quite fit. At that point it helps to choose between giving the idea another chance or keeping the image and correcting it.

We will not know whether it was the prompt or luck. At least we can stop generating blindly.

You can support me so that I can dedicate even more time to writing articles and have resources to create new projects. Thank you!

Contribute