Skip to content

Commit f18aac0

Browse files
sobychackoilayaperumalg
authored andcommitted
GH-2518: Remove requestOptions from observation context objects
Fixes: #2518 Issue: #2518 This commit removes the deprecated requestOptions field from ChatModelObservationContext and EmbeddingModelObservationContext classes. Instead of passing options separately, the code now retrieves them directly from the request objects (prompt.getOptions() or embeddingRequest.getOptions()). Key changes: - Removed requestOptions parameter from observation context builders - Updated all model implementations to stop passing options separately - Fixed EmbeddingRequest handling in several model implementations - Added buildEmbeddingRequest method in models to properly merge options This change simplifies the API and removes duplication, as options are already available in the request objects themselves. Signed-off-by: Soby Chacko <[email protected]>
1 parent 4eeeb83 commit f18aac0

File tree

36 files changed

+248
-281
lines changed

36 files changed

+248
-281
lines changed

models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
177177
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
178178
.prompt(prompt)
179179
.provider(AnthropicApi.PROVIDER_NAME)
180-
.requestOptions(prompt.getOptions())
181180
.build();
182181

183182
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
@@ -240,7 +239,6 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
240239
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
241240
.prompt(prompt)
242241
.provider(AnthropicApi.PROVIDER_NAME)
243-
.requestOptions(prompt.getOptions())
244242
.build();
245243

246244
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
245245
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
246246
.prompt(prompt)
247247
.provider(AiProvider.AZURE_OPENAI.value())
248-
.requestOptions(prompt.getOptions() != null ? prompt.getOptions() : this.defaultOptions)
249248
.build();
250249

251250
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
@@ -300,7 +299,6 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
300299
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
301300
.prompt(prompt)
302301
.provider(AiProvider.AZURE_OPENAI.value())
303-
.requestOptions(prompt.getOptions() != null ? prompt.getOptions() : this.defaultOptions)
304302
.build();
305303

306304
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingModel.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -51,6 +51,7 @@
5151
* @author Mark Pollack
5252
* @author Christian Tzolov
5353
* @author Thomas Vitale
54+
* @author Soby Chacko
5455
* @since 1.0.0
5556
*/
5657
public class AzureOpenAiEmbeddingModel extends AbstractEmbeddingModel {
@@ -124,12 +125,15 @@ public EmbeddingResponse call(EmbeddingRequest embeddingRequest) {
124125
.from(this.defaultOptions)
125126
.merge(embeddingRequest.getOptions())
126127
.build();
127-
EmbeddingsOptions azureOptions = options.toAzureOptions(embeddingRequest.getInstructions());
128+
129+
EmbeddingRequest embeddingRequestWithMergedOptions = new EmbeddingRequest(embeddingRequest.getInstructions(),
130+
options);
131+
132+
EmbeddingsOptions azureOptions = options.toAzureOptions(embeddingRequestWithMergedOptions.getInstructions());
128133

129134
var observationContext = EmbeddingModelObservationContext.builder()
130-
.embeddingRequest(embeddingRequest)
135+
.embeddingRequest(embeddingRequestWithMergedOptions)
131136
.provider(AiProvider.AZURE_OPENAI.value())
132-
.requestOptions(options)
133137
.build();
134138

135139
return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION

models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon
220220
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
221221
.prompt(prompt)
222222
.provider(AiProvider.BEDROCK_CONVERSE.value())
223-
.requestOptions(prompt.getOptions())
224223
.build();
225224

226225
ChatResponse chatResponse = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
@@ -647,7 +646,6 @@ private Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse perviousCh
647646
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
648647
.prompt(prompt)
649648
.provider(AiProvider.BEDROCK_CONVERSE.value())
650-
.requestOptions(prompt.getOptions())
651649
.build();
652650

653651
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(

models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ public ChatResponse call(Prompt prompt) {
241241
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
242242
.prompt(requestPrompt)
243243
.provider(MiniMaxApiConstants.PROVIDER_NAME)
244-
.requestOptions(requestPrompt.getOptions())
245244
.build();
246245

247246
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
@@ -334,7 +333,6 @@ public Flux<ChatResponse> stream(Prompt prompt) {
334333
final ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
335334
.prompt(requestPrompt)
336335
.provider(MiniMaxApiConstants.PROVIDER_NAME)
337-
.requestOptions(requestPrompt.getOptions())
338336
.build();
339337

340338
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(

models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingModel.java

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -43,13 +43,15 @@
4343
import org.springframework.lang.Nullable;
4444
import org.springframework.retry.support.RetryTemplate;
4545
import org.springframework.util.Assert;
46+
import org.springframework.util.StringUtils;
4647

4748
/**
4849
* MiniMax Embedding Model implementation.
4950
*
5051
* @author Geng Rong
5152
* @author Thomas Vitale
52-
* @since 1.0.0 M1
53+
* @author Soby Chacko
54+
* @since 1.0.0
5355
*/
5456
public class MiniMaxEmbeddingModel extends AbstractEmbeddingModel {
5557

@@ -149,14 +151,15 @@ public float[] embed(Document document) {
149151

150152
@Override
151153
public EmbeddingResponse call(EmbeddingRequest request) {
152-
MiniMaxEmbeddingOptions requestOptions = mergeOptions(request.getOptions(), this.defaultOptions);
154+
155+
EmbeddingRequest embeddingRequest = buildEmbeddingRequest(request);
156+
153157
MiniMaxApi.EmbeddingRequest apiRequest = new MiniMaxApi.EmbeddingRequest(request.getInstructions(),
154-
requestOptions.getModel());
158+
embeddingRequest.getOptions().getModel());
155159

156160
var observationContext = EmbeddingModelObservationContext.builder()
157161
.embeddingRequest(request)
158162
.provider(MiniMaxApiConstants.PROVIDER_NAME)
159-
.requestOptions(requestOptions)
160163
.build();
161164

162165
return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION
@@ -188,26 +191,24 @@ private DefaultUsage getDefaultUsage(MiniMaxApi.EmbeddingList apiEmbeddingList)
188191
return new DefaultUsage(0, 0, apiEmbeddingList.totalTokens());
189192
}
190193

191-
/**
192-
* Merge runtime and default {@link EmbeddingOptions} to compute the final options to
193-
* use in the request.
194-
*/
195-
private MiniMaxEmbeddingOptions mergeOptions(@Nullable EmbeddingOptions runtimeOptions,
196-
MiniMaxEmbeddingOptions defaultOptions) {
197-
var runtimeOptionsForProvider = ModelOptionsUtils.copyToTarget(runtimeOptions, EmbeddingOptions.class,
194+
EmbeddingRequest buildEmbeddingRequest(EmbeddingRequest embeddingRequest) {
195+
// Process runtime options
196+
MiniMaxEmbeddingOptions runtimeOptions = null;
197+
if (embeddingRequest.getOptions() != null) {
198+
runtimeOptions = ModelOptionsUtils.copyToTarget(embeddingRequest.getOptions(), EmbeddingOptions.class,
199+
MiniMaxEmbeddingOptions.class);
200+
}
201+
202+
// Define request options by merging runtime options and default options
203+
MiniMaxEmbeddingOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,
198204
MiniMaxEmbeddingOptions.class);
199205

200-
var optionBuilder = MiniMaxEmbeddingOptions.builder();
201-
if (runtimeOptionsForProvider != null && runtimeOptionsForProvider.getModel() != null) {
202-
optionBuilder.model(runtimeOptionsForProvider.getModel());
203-
}
204-
else if (defaultOptions.getModel() != null) {
205-
optionBuilder.model(defaultOptions.getModel());
206+
// Validate request options
207+
if (!StringUtils.hasText(requestOptions.getModel())) {
208+
throw new IllegalArgumentException("model cannot be null or empty");
206209
}
207-
else {
208-
optionBuilder.model(MiniMaxApi.DEFAULT_EMBEDDING_MODEL);
209-
}
210-
return optionBuilder.build();
210+
211+
return new EmbeddingRequest(embeddingRequest.getInstructions(), requestOptions);
211212
}
212213

213214
public void setObservationConvention(EmbeddingModelObservationConvention observationConvention) {

models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/api/MiniMaxRetryTests.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -29,6 +29,7 @@
2929
import org.springframework.ai.chat.prompt.ChatOptions;
3030
import org.springframework.ai.chat.prompt.Prompt;
3131
import org.springframework.ai.document.MetadataMode;
32+
import org.springframework.ai.embedding.EmbeddingOptions;
3233
import org.springframework.ai.minimax.MiniMaxChatModel;
3334
import org.springframework.ai.minimax.MiniMaxChatOptions;
3435
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
@@ -57,6 +58,7 @@
5758

5859
/**
5960
* @author Geng Rong
61+
* @author Soby Chacko
6062
*/
6163
@SuppressWarnings("unchecked")
6264
@ExtendWith(MockitoExtension.class)
@@ -150,8 +152,9 @@ public void miniMaxEmbeddingTransientError() {
150152
.willThrow(new TransientAiException("Transient Error 2"))
151153
.willReturn(ResponseEntity.of(Optional.of(expectedEmbeddings)));
152154

155+
EmbeddingOptions options = MiniMaxEmbeddingOptions.builder().model("model").build();
153156
var result = this.embeddingModel
154-
.call(new org.springframework.ai.embedding.EmbeddingRequest(List.of("text1", "text2"), null));
157+
.call(new org.springframework.ai.embedding.EmbeddingRequest(List.of("text1", "text2"), options));
155158

156159
assertThat(result).isNotNull();
157160
assertThat(result.getResult().getOutput()).isEqualTo(new float[] { 9.9f, 8.8f });
@@ -163,8 +166,9 @@ public void miniMaxEmbeddingTransientError() {
163166
public void miniMaxEmbeddingNonTransientError() {
164167
given(this.miniMaxApi.embeddings(isA(EmbeddingRequest.class)))
165168
.willThrow(new RuntimeException("Non Transient Error"));
169+
EmbeddingOptions options = MiniMaxEmbeddingOptions.builder().model("model").build();
166170
assertThrows(RuntimeException.class, () -> this.embeddingModel
167-
.call(new org.springframework.ai.embedding.EmbeddingRequest(List.of("text1", "text2"), null)));
171+
.call(new org.springframework.ai.embedding.EmbeddingRequest(List.of("text1", "text2"), options)));
168172
}
169173

170174
private class TestRetryListener implements RetryListener {

models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
187187
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
188188
.prompt(prompt)
189189
.provider(MistralAiApi.PROVIDER_NAME)
190-
.requestOptions(prompt.getOptions())
191190
.build();
192191

193192
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
@@ -260,7 +259,6 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
260259
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
261260
.prompt(prompt)
262261
.provider(MistralAiApi.PROVIDER_NAME)
263-
.requestOptions(prompt.getOptions())
264262
.build();
265263

266264
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(

models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,8 @@ public EmbeddingResponse call(EmbeddingRequest request) {
116116
var apiRequest = createRequest(embeddingRequest);
117117

118118
var observationContext = EmbeddingModelObservationContext.builder()
119-
.embeddingRequest(request)
119+
.embeddingRequest(embeddingRequest)
120120
.provider(MistralAiApi.PROVIDER_NAME)
121-
.requestOptions(embeddingRequest.getOptions())
122121
.build();
123122

124123
return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION

models/spring-ai-oci-genai/src/main/java/org/springframework/ai/oci/OCIEmbeddingModel.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -43,6 +43,7 @@
4343
import org.springframework.ai.model.ModelOptionsUtils;
4444
import org.springframework.ai.observation.conventions.AiProvider;
4545
import org.springframework.util.Assert;
46+
import org.springframework.util.StringUtils;
4647

4748
/**
4849
* {@link org.springframework.ai.embedding.EmbeddingModel} implementation that uses the
@@ -83,13 +84,15 @@ public OCIEmbeddingModel(GenerativeAiInference genAi, OCIEmbeddingOptions option
8384
@Override
8485
public EmbeddingResponse call(EmbeddingRequest request) {
8586
Assert.notEmpty(request.getInstructions(), "At least one text is required!");
86-
OCIEmbeddingOptions runtimeOptions = mergeOptions(request.getOptions(), this.options);
87-
List<EmbedTextRequest> embedTextRequests = createRequests(request.getInstructions(), runtimeOptions);
87+
88+
EmbeddingRequest embeddingRequest = buildEmbeddingRequest(request);
89+
90+
List<EmbedTextRequest> embedTextRequests = createRequests(embeddingRequest.getInstructions(),
91+
(OCIEmbeddingOptions) embeddingRequest.getOptions());
8892

8993
EmbeddingModelObservationContext context = EmbeddingModelObservationContext.builder()
90-
.embeddingRequest(request)
94+
.embeddingRequest(embeddingRequest)
9195
.provider(AiProvider.OCI_GENAI.value())
92-
.requestOptions(runtimeOptions)
9396
.build();
9497

9598
return EmbeddingModelObservationDocumentation.EMBEDDING_MODEL_OPERATION
@@ -158,6 +161,26 @@ private OCIEmbeddingOptions mergeOptions(EmbeddingOptions embeddingOptions, OCIE
158161
return defaultOptions;
159162
}
160163

164+
EmbeddingRequest buildEmbeddingRequest(EmbeddingRequest embeddingRequest) {
165+
// Process runtime options
166+
OCIEmbeddingOptions runtimeOptions = null;
167+
if (embeddingRequest.getOptions() != null) {
168+
runtimeOptions = ModelOptionsUtils.copyToTarget(embeddingRequest.getOptions(), EmbeddingOptions.class,
169+
OCIEmbeddingOptions.class);
170+
}
171+
172+
// Define request options by merging runtime options and default options
173+
OCIEmbeddingOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.options,
174+
OCIEmbeddingOptions.class);
175+
176+
// Validate request options
177+
if (!StringUtils.hasText(requestOptions.getModel())) {
178+
throw new IllegalArgumentException("model cannot be null or empty");
179+
}
180+
181+
return new EmbeddingRequest(embeddingRequest.getInstructions(), requestOptions);
182+
}
183+
161184
private float[] toFloats(List<Float> embedding) {
162185
float[] floats = new float[embedding.size()];
163186
for (int i = 0; i < embedding.size(); i++) {

0 commit comments

Comments
 (0)