1515 */
1616package org .springframework .ai .qianfan ;
1717
18+ import io .micrometer .observation .ObservationRegistry ;
1819import org .slf4j .Logger ;
1920import org .slf4j .LoggerFactory ;
2021import org .springframework .ai .image .Image ;
2324import org .springframework .ai .image .ImageOptions ;
2425import org .springframework .ai .image .ImagePrompt ;
2526import org .springframework .ai .image .ImageResponse ;
27+ import org .springframework .ai .image .observation .DefaultImageModelObservationConvention ;
28+ import org .springframework .ai .image .observation .ImageModelObservationContext ;
29+ import org .springframework .ai .image .observation .ImageModelObservationConvention ;
30+ import org .springframework .ai .image .observation .ImageModelObservationDocumentation ;
2631import org .springframework .ai .model .ModelOptionsUtils ;
32+ import org .springframework .ai .qianfan .api .QianFanConstants ;
2733import org .springframework .ai .qianfan .api .QianFanImageApi ;
2834import org .springframework .ai .retry .RetryUtils ;
2935import org .springframework .http .ResponseEntity ;
36+ import org .springframework .lang .Nullable ;
3037import org .springframework .retry .support .RetryTemplate ;
3138import org .springframework .util .Assert ;
3239
@@ -43,6 +50,8 @@ public class QianFanImageModel implements ImageModel {
4350
4451 private final static Logger logger = LoggerFactory .getLogger (QianFanImageModel .class );
4552
53+ private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention ();
54+
4655 /**
4756 * The default options used for the image completion requests.
4857 */
@@ -58,6 +67,16 @@ public class QianFanImageModel implements ImageModel {
5867 */
5968 private final QianFanImageApi qianFanImageApi ;
6069
70+ /**
71+ * Observation registry used for instrumentation.
72+ */
73+ private final ObservationRegistry observationRegistry ;
74+
75+ /**
76+ * Conventions to use for generating observations.
77+ */
78+ private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION ;
79+
6180 /**
6281 * Creates an instance of the QianFanImageModel.
6382 * @param qianFanImageApi The QianFanImageApi instance to be used for interacting with
@@ -69,48 +88,85 @@ public QianFanImageModel(QianFanImageApi qianFanImageApi) {
6988 }
7089
7190 /**
72- * Initializes a new instance of the QianFanImageModel.
91+ * Creates an instance of the QianFanImageModel.
92+ * @param qianFanImageApi The QianFanImageApi instance to be used for interacting with
93+ * the QianFan Image API.
94+ * @param options The QianFanImageOptions to configure the image model.
95+ * @throws IllegalArgumentException if qianFanImageApi is null
96+ */
97+ public QianFanImageModel (QianFanImageApi qianFanImageApi , QianFanImageOptions options ) {
98+ this (qianFanImageApi , options , RetryUtils .DEFAULT_RETRY_TEMPLATE );
99+ }
100+
101+ /**
102+ * Creates an instance of the QianFanImageModel.
73103 * @param qianFanImageApi The QianFanImageApi instance to be used for interacting with
74104 * the QianFan Image API.
75105 * @param options The QianFanImageOptions to configure the image model.
76106 * @param retryTemplate The retry template.
107+ * @throws IllegalArgumentException if qianFanImageApi is null
77108 */
78109 public QianFanImageModel (QianFanImageApi qianFanImageApi , QianFanImageOptions options ,
79110 RetryTemplate retryTemplate ) {
111+ this (qianFanImageApi , options , retryTemplate , ObservationRegistry .NOOP );
112+ }
113+
114+ /**
115+ * Initializes a new instance of the QianFanImageModel.
116+ * @param qianFanImageApi The QianFanImageApi instance to be used for interacting with
117+ * the QianFan Image API.
118+ * @param options The QianFanImageOptions to configure the image model.
119+ * @param retryTemplate The retry template.
120+ * @param observationRegistry The ObservationRegistry used for instrumentation.
121+ */
122+ public QianFanImageModel (QianFanImageApi qianFanImageApi , QianFanImageOptions options , RetryTemplate retryTemplate ,
123+ ObservationRegistry observationRegistry ) {
80124 Assert .notNull (qianFanImageApi , "QianFanImageApi must not be null" );
81125 Assert .notNull (options , "options must not be null" );
82126 Assert .notNull (retryTemplate , "retryTemplate must not be null" );
127+ Assert .notNull (observationRegistry , "observationRegistry must not be null" );
83128 this .qianFanImageApi = qianFanImageApi ;
84129 this .defaultOptions = options ;
85130 this .retryTemplate = retryTemplate ;
131+ this .observationRegistry = observationRegistry ;
86132 }
87133
88134 @ Override
89135 public ImageResponse call (ImagePrompt imagePrompt ) {
90- return this . retryTemplate . execute ( ctx -> {
136+ QianFanImageOptions requestImageOptions = mergeOptions ( imagePrompt . getOptions (), this . defaultOptions );
91137
92- String instructions = imagePrompt . getInstructions (). get ( 0 ). getText ( );
138+ QianFanImageApi . QianFanImageRequest imageRequest = createRequest ( imagePrompt , requestImageOptions );
93139
94- QianFanImageApi .QianFanImageRequest imageRequest = new QianFanImageApi .QianFanImageRequest (instructions ,
95- QianFanImageApi .DEFAULT_IMAGE_MODEL );
140+ var observationContext = ImageModelObservationContext .builder ()
141+ .imagePrompt (imagePrompt )
142+ .provider (QianFanConstants .PROVIDER_NAME )
143+ .requestOptions (requestImageOptions )
144+ .build ();
96145
97- if ( this . defaultOptions != null ) {
98- imageRequest = ModelOptionsUtils . merge (this .defaultOptions , imageRequest ,
99- QianFanImageApi . QianFanImageRequest . class );
100- }
146+ return ImageModelObservationDocumentation . IMAGE_MODEL_OPERATION
147+ . observation (this .observationConvention , DEFAULT_OBSERVATION_CONVENTION , () -> observationContext ,
148+ this . observationRegistry )
149+ . observe (() -> {
101150
102- if (imagePrompt .getOptions () != null ) {
103- imageRequest = ModelOptionsUtils .merge (toQianFanImageOptions (imagePrompt .getOptions ()), imageRequest ,
104- QianFanImageApi .QianFanImageRequest .class );
105- }
151+ ResponseEntity <QianFanImageApi .QianFanImageResponse > imageResponseEntity = this .retryTemplate
152+ .execute (ctx -> this .qianFanImageApi .createImage (imageRequest ));
106153
107- // Make the request
108- ResponseEntity <QianFanImageApi .QianFanImageResponse > imageResponseEntity = this .qianFanImageApi
109- .createImage (imageRequest );
154+ ImageResponse imageResponse = convertResponse (imageResponseEntity , imageRequest );
110155
111- // Convert to org.springframework.ai.model derived ImageResponse data type
112- return convertResponse (imageResponseEntity , imageRequest );
113- });
156+ observationContext .setResponse (imageResponse );
157+
158+ return imageResponse ;
159+ });
160+ }
161+
162+ private QianFanImageApi .QianFanImageRequest createRequest (ImagePrompt imagePrompt ,
163+ QianFanImageOptions requestImageOptions ) {
164+ String instructions = imagePrompt .getInstructions ().get (0 ).getText ();
165+
166+ QianFanImageApi .QianFanImageRequest imageRequest = new QianFanImageApi .QianFanImageRequest (instructions ,
167+ QianFanImageApi .DEFAULT_IMAGE_MODEL );
168+
169+ return ModelOptionsUtils .merge (requestImageOptions , imageRequest , QianFanImageApi .QianFanImageRequest .class );
114170 }
115171
116172 private ImageResponse convertResponse (ResponseEntity <QianFanImageApi .QianFanImageResponse > imageResponseEntity ,
@@ -132,33 +188,32 @@ private ImageResponse convertResponse(ResponseEntity<QianFanImageApi.QianFanImag
132188 /**
133189 * Convert the {@link ImageOptions} into {@link QianFanImageOptions}.
134190 * @param runtimeImageOptions the image options to use.
191+ * @param defaultOptions the default options.
135192 * @return the converted {@link QianFanImageOptions}.
136193 */
137- private QianFanImageOptions toQianFanImageOptions (ImageOptions runtimeImageOptions ) {
138- QianFanImageOptions .Builder qianFanImageOptionsBuilder = QianFanImageOptions .builder ();
139- if (runtimeImageOptions != null ) {
140- if (runtimeImageOptions .getN () != null ) {
141- qianFanImageOptionsBuilder .withN (runtimeImageOptions .getN ());
142- }
143- if (runtimeImageOptions .getModel () != null ) {
144- qianFanImageOptionsBuilder .withModel (runtimeImageOptions .getModel ());
145- }
146- if (runtimeImageOptions .getWidth () != null ) {
147- qianFanImageOptionsBuilder .withWidth (runtimeImageOptions .getWidth ());
148- }
149- if (runtimeImageOptions .getHeight () != null ) {
150- qianFanImageOptionsBuilder .withHeight (runtimeImageOptions .getHeight ());
151- }
152- if (runtimeImageOptions instanceof QianFanImageOptions runtimeQianFanImageOptions ) {
153- if (runtimeQianFanImageOptions .getStyle () != null ) {
154- qianFanImageOptionsBuilder .withStyle (runtimeQianFanImageOptions .getStyle ());
155- }
156- if (runtimeQianFanImageOptions .getUser () != null ) {
157- qianFanImageOptionsBuilder .withUser (runtimeQianFanImageOptions .getUser ());
158- }
159- }
194+ private QianFanImageOptions mergeOptions (@ Nullable ImageOptions runtimeImageOptions ,
195+ QianFanImageOptions defaultOptions ) {
196+ var runtimeOptionsForProvider = ModelOptionsUtils .copyToTarget (runtimeImageOptions , ImageOptions .class ,
197+ QianFanImageOptions .class );
198+
199+ if (runtimeOptionsForProvider == null ) {
200+ return defaultOptions ;
160201 }
161- return qianFanImageOptionsBuilder .build ();
202+
203+ return QianFanImageOptions .builder ()
204+ .withModel (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getModel (), defaultOptions .getModel ()))
205+ .withN (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getN (), defaultOptions .getN ()))
206+ .withModel (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getModel (), defaultOptions .getModel ()))
207+ .withWidth (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getWidth (), defaultOptions .getWidth ()))
208+ .withHeight (
209+ ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getHeight (), defaultOptions .getHeight ()))
210+ .withStyle (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getStyle (), defaultOptions .getStyle ()))
211+ .withUser (ModelOptionsUtils .mergeOption (runtimeOptionsForProvider .getUser (), defaultOptions .getUser ()))
212+ .build ();
213+ }
214+
215+ public void setObservationConvention (ImageModelObservationConvention observationConvention ) {
216+ this .observationConvention = observationConvention ;
162217 }
163218
164219}
0 commit comments