11/*
2- * Copyright 2023-2024 the original author or authors.
2+ * Copyright 2023-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
3939import org .springframework .ai .openai .metadata .OpenAiImageGenerationMetadata ;
4040import org .springframework .ai .retry .RetryUtils ;
4141import org .springframework .http .ResponseEntity ;
42- import org .springframework .lang .Nullable ;
4342import org .springframework .retry .support .RetryTemplate ;
4443import org .springframework .util .Assert ;
4544
@@ -127,13 +126,16 @@ public OpenAiImageModel(OpenAiImageApi openAiImageApi, OpenAiImageOptions option
127126
128127 @ Override
129128 public ImageResponse call (ImagePrompt imagePrompt ) {
130- OpenAiImageOptions requestImageOptions = mergeOptions (imagePrompt .getOptions (), this .defaultOptions );
131- OpenAiImageApi .OpenAiImageRequest imageRequest = createRequest (imagePrompt , requestImageOptions );
129+ // Before moving any further, build the final request ImagePrompt,
130+ // merging runtime and default options.
131+ ImagePrompt requestImagePrompt = buildRequestImagePrompt (imagePrompt );
132+
133+ OpenAiImageApi .OpenAiImageRequest imageRequest = createRequest (requestImagePrompt );
132134
133135 var observationContext = ImageModelObservationContext .builder ()
134136 .imagePrompt (imagePrompt )
135137 .provider (OpenAiApiConstants .PROVIDER_NAME )
136- .requestOptions (requestImageOptions )
138+ .requestOptions (requestImagePrompt . getOptions () )
137139 .build ();
138140
139141 return ImageModelObservationDocumentation .IMAGE_MODEL_OPERATION
@@ -151,14 +153,14 @@ public ImageResponse call(ImagePrompt imagePrompt) {
151153 });
152154 }
153155
154- private OpenAiImageApi .OpenAiImageRequest createRequest (ImagePrompt imagePrompt ,
155- OpenAiImageOptions requestImageOptions ) {
156+ private OpenAiImageApi .OpenAiImageRequest createRequest (ImagePrompt imagePrompt ) {
156157 String instructions = imagePrompt .getInstructions ().get (0 ).getText ();
158+ OpenAiImageOptions imageOptions = (OpenAiImageOptions ) imagePrompt .getOptions ();
157159
158160 OpenAiImageApi .OpenAiImageRequest imageRequest = new OpenAiImageApi .OpenAiImageRequest (instructions ,
159161 OpenAiImageApi .DEFAULT_IMAGE_MODEL );
160162
161- return ModelOptionsUtils .merge (requestImageOptions , imageRequest , OpenAiImageApi .OpenAiImageRequest .class );
163+ return ModelOptionsUtils .merge (imageOptions , imageRequest , OpenAiImageApi .OpenAiImageRequest .class );
162164 }
163165
164166 private ImageResponse convertResponse (ResponseEntity <OpenAiImageApi .OpenAiImageResponse > imageResponseEntity ,
@@ -179,31 +181,29 @@ private ImageResponse convertResponse(ResponseEntity<OpenAiImageApi.OpenAiImageR
179181 return new ImageResponse (imageGenerationList , openAiImageResponseMetadata );
180182 }
181183
182- /**
183- * Merge runtime and default {@link ImageOptions} to compute the final options to use
184- * in the request.
185- */
186- private OpenAiImageOptions mergeOptions (@ Nullable ImageOptions runtimeOptions , OpenAiImageOptions defaultOptions ) {
187- var runtimeOptionsForProvider = ModelOptionsUtils .copyToTarget (runtimeOptions , ImageOptions .class ,
188- OpenAiImageOptions .class );
189-
190- if (runtimeOptionsForProvider == null ) {
191- return defaultOptions ;
184+ private ImagePrompt buildRequestImagePrompt (ImagePrompt imagePrompt ) {
185+ // Process runtime options
186+ OpenAiImageOptions runtimeOptions = null ;
187+ if (imagePrompt .getOptions () != null ) {
188+ runtimeOptions = ModelOptionsUtils .copyToTarget (imagePrompt .getOptions (), ImageOptions .class ,
189+ OpenAiImageOptions .class );
192190 }
193191
194- return OpenAiImageOptions .builder ()
192+ OpenAiImageOptions requestOptions = runtimeOptions == null ? this . defaultOptions : OpenAiImageOptions .builder ()
195193 // Handle portable image options
196- .model (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getModel (), defaultOptions .getModel ()))
197- .N (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getN (), defaultOptions .getN ()))
198- .responseFormat (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getResponseFormat (),
194+ .model (ModelOptionsUtils .mergeOption (runtimeOptions .getModel (), defaultOptions .getModel ()))
195+ .N (ModelOptionsUtils .mergeOption (runtimeOptions .getN (), defaultOptions .getN ()))
196+ .responseFormat (ModelOptionsUtils .mergeOption (runtimeOptions .getResponseFormat (),
199197 defaultOptions .getResponseFormat ()))
200- .width (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getWidth (), defaultOptions .getWidth ()))
201- .height (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getHeight (), defaultOptions .getHeight ()))
202- .style (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getStyle (), defaultOptions .getStyle ()))
198+ .width (ModelOptionsUtils .mergeOption (runtimeOptions .getWidth (), defaultOptions .getWidth ()))
199+ .height (ModelOptionsUtils .mergeOption (runtimeOptions .getHeight (), defaultOptions .getHeight ()))
200+ .style (ModelOptionsUtils .mergeOption (runtimeOptions .getStyle (), defaultOptions .getStyle ()))
203201 // Handle OpenAI specific image options
204- .quality (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getQuality (), defaultOptions .getQuality ()))
205- .user (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getUser (), defaultOptions .getUser ()))
202+ .quality (ModelOptionsUtils .mergeOption (runtimeOptions .getQuality (), defaultOptions .getQuality ()))
203+ .user (ModelOptionsUtils .mergeOption (runtimeOptions .getUser (), defaultOptions .getUser ()))
206204 .build ();
205+
206+ return new ImagePrompt (imagePrompt .getInstructions (), requestOptions );
207207 }
208208
209209 /**
0 commit comments