Skip to content

Generate images through generateContent, not Imagen - #10

Merged
MarcelloDuarte merged 1 commit into
mainfrom
fix/image-generation-without-imagen
Jul 31, 2026
Merged

Generate images through generateContent, not Imagen#10
MarcelloDuarte merged 1 commit into
mainfrom
fix/image-generation-without-imagen

Conversation

@MarcelloDuarte

Copy link
Copy Markdown
Contributor

The problem

generateImage() is broken. It builds an Imagen-shaped request (instances / parameters posted to :predict) and its default model, imagen-4.0-fast-generate-001, has already dropped off Google's published model list. The whole Imagen line shuts down on 17 August 2026, and its separate :predict endpoint goes with it.

The fix

The Gemini image models that replace Imagen are reached through generateContent, opting into an image modality:

{
  "contents": [{"parts": [{"text": "..."}]}],
  "generationConfig": {
    "responseModalities": ["TEXT", "IMAGE"],
    "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"}
  }
}

That is exactly the shape editImage() has always sent and parseImageResponse() has always parsed, so both methods now share one private seam, requestImage(), where the image wire format lives.

Verified against Google's current generateContent reference before writing anything, given how the last two model swaps went. responseModalities and imageConfig (aspectRatio, imageSize of 1K/2K/4K) are both documented there, and all four image model IDs appear in the published model list.

Behaviour changes

Neither method invents an aspect ratio or size any more. editImage() used to force 1:1 and 2K on every call, which reshaped whatever the caller passed in: editing a 16:9 photo returned a square. Omitting them lets the model keep the source proportions. This is the same defect as the Agent's unconditional temperature of 0.7, fixed in 0.15.

numberOfImages above 1 now throws. The Gemini image models have no equivalent of Imagen's sampleCount and return one image per request. The alternative was handing back a single image while the caller believed they had asked for four. Same posture as Cohere refusing to fake a named tool.

Also

  • The IMAGEN_* constants stay, all @deprecated. Nothing defaults to them now, so model-watch no longer reports a deprecated constant in use as a default.
  • The README documented IMAGEN_3 and IMAGEN_3_FAST. Neither exists as a constant, so copying that example was a fatal error. It also led with MODEL_3_0_PRO, which was shut down in March. Both docs now list the models the class actually ships.

Checks

composer ci green: lint clean, Psalm level 4 zero errors, 83 tests passing, 76.6% coverage.

Depends on papi-ai/papi-core#30 for the imageSize docblock, which is a patch and picked up automatically by the existing ^0.15 constraint.

generateImage() has been broken for a while. It builds an Imagen-shaped request
(instances/parameters posted to :predict) and its default model,
imagen-4.0-fast-generate-001, has already dropped off Google's published list.
The whole Imagen line shuts down on 17 August 2026 and the :predict endpoint
goes with it.

The Gemini image models that replace it are reached through generateContent,
asking for an image modality back. That is the same shape editImage() has
always sent and parseImageResponse() has always parsed, so both methods now
share one private seam where the image wire format lives.

Two behaviour changes worth knowing about:

- Neither method invents an aspect ratio or an image size any more. editImage()
  used to force 1:1 and 2K on every call, which reshaped whatever the caller
  passed in. Omitting them lets the model keep the source proportions. This is
  the same mistake the Agent's unconditional temperature of 0.7 was.
- numberOfImages above 1 now throws. The Gemini image models have no equivalent
  of Imagen's sampleCount, so the alternative was handing back one image and
  pretending four had been asked for.

The IMAGEN_* constants stay, all deprecated. Nothing defaults to them now, so
model-watch no longer reports a deprecated default.

The README documented IMAGEN_3 and IMAGEN_3_FAST, neither of which exists as a
constant; copying that example was a fatal error. Both docs now list the models
the class actually ships.
@MarcelloDuarte
MarcelloDuarte merged commit bd851ea into main Jul 31, 2026
16 of 18 checks passed
@MarcelloDuarte
MarcelloDuarte deleted the fix/image-generation-without-imagen branch July 31, 2026 16:46
@MarcelloDuarte

Copy link
Copy Markdown
Contributor Author

Releasing this as v0.15.5, not 0.16.0.

papi-ai/google is a leaf package: nothing in the ecosystem requires it, only end consumers, and they pin ^0.15. A minor bump would silently strand every one of them on the version where generateImage() is broken. That is the same stranding mechanic that makes a core minor expensive, one level down, and here it lands against the 17 August Imagen shutdown.

The behaviour changes in this PR only affect paths that are about to stop working anyway: Imagen's sampleCount and its :predict endpoint both die on 17 August. There is no working code that a patch quietly takes away.

The three changes to call out in the release notes:

  • generateImage() now goes through generateContent and defaults to gemini-3.1-flash-image.
  • editImage() no longer forces 1:1 and 2K on every call.
  • numberOfImages above 1 throws instead of returning a single image.

Blocked until papi-ai/papi-core#30 is merged, tagged v0.15.2 and refreshed on Packagist: Static Analysis here resolves papi-ai/papi-core: ^0.15 from Packagist, where the contract still says imageSize?: int.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant