CAMEL-23967: camel-openai - Add image generation and edit operations - #25489
CAMEL-23967: camel-openai - Add image generation and edit operations#25489k-krawczyk wants to merge 1 commit into
Conversation
Adds the image-generation and image-edit operations backed by the images API of the openai-java SDK. The multipart parts of an image edit declare their content type. The API validates the upload on that content type, not on the file name, and the SDK leaves it as text/plain unless it is set, so an edit would otherwise be rejected. It is resolved from the usual MIME type detection, then from the extension of a File or Path body, and falls back to image/png for anything the API does not accept. imageResponseFormat has no default and is only sent when set explicitly. The GPT image models always return base64 and reject the parameter, and the OpenAI images endpoint now rejects it for every model, the DALL-E models that used to accept it no longer being offered. The option is kept for OpenAI-compatible providers that still implement the older images API. The body shape follows the response instead, decoding base64 into byte[] and leaving URLs as String, with a single image as the body and several images as a List. The image-edit body accepts File, Path, InputStream, byte[] or a List of those, since the GPT image models take up to 16 reference images, plus an optional mask header. camel-test-infra-openai-mock gains image generation and edit expectations. Image edit requests are multipart and are not parsed by the mock, as is already the case for transcription, so those expectations are matched in declaration order and the raw body is exposed to assertions. Co-authored-by: Claude <noreply@anthropic.com>
davsclaus
left a comment
There was a problem hiding this comment.
Nice work on this PR — the documentation, test coverage, and alignment with existing component patterns are all solid. Two minor suggestions below.
Note: this review covers project conventions and contribution expectations. It does not replace specialized review tools (CodeRabbit, SonarCloud, etc.).
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
| private static final byte[] SOURCE_IMAGE = "FAKE-PNG-SOURCE".getBytes(StandardCharsets.UTF_8); | ||
| private static final byte[] SECOND_SOURCE_IMAGE = "FAKE-PNG-SOURCE-TWO".getBytes(StandardCharsets.UTF_8); | ||
| private static final byte[] MASK_IMAGE = "FAKE-PNG-MASK".getBytes(StandardCharsets.UTF_8); | ||
| private static final byte[] EDITED_IMAGE = "FAKE-PNG-EDITED".getBytes(StandardCharsets.UTF_8); |
There was a problem hiding this comment.
Per the project's JUnit 5 convention, new test classes should be package-private (drop public). The newer tests in this component already follow this pattern.
| private static final byte[] EDITED_IMAGE = "FAKE-PNG-EDITED".getBytes(StandardCharsets.UTF_8); | |
| class OpenAIImageEditMockTest extends CamelTestSupport { |
|
|
||
| @RegisterExtension | ||
| public OpenAIMock openAIMock = new OpenAIMock().builder() | ||
| .whenImageGeneration("A red bicycle") |
There was a problem hiding this comment.
Same here — drop public to follow the JUnit 5 convention.
| .whenImageGeneration("A red bicycle") | |
| class OpenAIImageGenerationMockTest extends CamelTestSupport { |
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * Shared helpers for the {@code image-generation} and {@code image-edit} operations. |
There was a problem hiding this comment.
Nit: resolveParameter() at the bottom of this class is byte-for-byte identical to OpenAIProducer.resolveParameter(). Consider extracting it to a shared location to avoid the duplication. Not blocking — just a follow-up suggestion.
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 17 tested, 26 compile-only — current: 14 all testedMaveniverse Scalpel detected 43 affected modules (current approach: 14).
|
Description
Adds the
image-generationandimage-editoperations tocamel-openai, closing CAMEL-23967.Two deviations from the issue
imageResponseFormathas no default and is only sent when set. The KDoc ofImageGenerateParamsin the pinned SDK says the parameter is not supported by the GPT image models, which always return base64. Checking against the live API on 13 August 2026 turned out to be stricter still:POST /v1/images/generationsanswers400 Unknown parameter: 'response_format'for every model, and/v1/modelsno longer lists any DALL-E model. The option is kept because OpenAI-compatible providers still implement the older images API, whereurlis often the default — but a default value here would break every route against OpenAI, so there is none.The body shape follows the response, not the option. Base64 payloads are decoded into
byte[], URLs stay asString. A single image becomes the body directly and several images become aList, so the common case does not force routes to unwrap a one-element list.What it adds
imageModel,imagePrompt,imageSize,imageQuality,imageResponseFormat,imageCount,imageBackground,imageOutputFormat,imageOutputCompression,imageStyle,imageModeration,imageInputFidelity— each overridable per exchange by a header.image-edittakes the image from the body asFile,Path,InputStream,byte[], or aListof those, since the GPT image models accept up to 16 reference images. An optional mask goes throughCamelOpenAIImageMask.Content-Typeis set from the output format reported by the response, falling back to the requested one, so the result chains intofile:or object storage unchanged.text/plainunless it is set, so an edit is rejected without this. It is resolved from the usual MIME type detection, then from the extension of aFile/Pathbody, and falls back toimage/pngfor anything the API does not accept.storeFullResponse=truekeeps the full SDK response inCamelOpenAIImageResponse.Left out, as the issue suggests:
createVariationand the streaming variants.Testing
camel-test-infra-openai-mockgainedwhenImageGeneration(),whenImageEdit(),replyWithImage(byte[]),replyWithImageUrl(String),withRevisedPrompt(),withImageOutputFormat(),withImageSize(),withImageUsage()andassertImageRequest(). Image edit requests are multipart and the mock does not parse multipart — the same simplification the transcription handler already makes — so those expectations are matched in declaration order and the raw body is exposed to assertions, which is enough to verify that several images and a mask reach the wire.24 unit tests, no model and no GPU involved.
Beyond the mock, both operations were also exercised against the real OpenAI API once, which is what surfaced the multipart content type and the state of
response_format. That smoke test is not part of the contribution — there is no CI-suitable local image backend, as the issue notes — but the multipart content type is now asserted in the mock tests.Reported by Claude Code on behalf of Karol Krawczyk