Skip to content

GH-3873 Add an api-version option for Azure OpenAI #3874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,7 @@
import com.azure.ai.openai.models.AudioTranscriptionFormat;
import com.azure.ai.openai.models.AudioTranscriptionOptions;
import com.azure.ai.openai.models.AudioTranscriptionTimestampGranularity;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.http.rest.Response;

import org.springframework.ai.audio.transcription.AudioTranscription;
Expand Down Expand Up @@ -83,9 +84,18 @@ public AudioTranscriptionResponse call(AudioTranscriptionPrompt audioTranscripti
AudioTranscriptionOptions audioTranscriptionOptions = toAudioTranscriptionOptions(audioTranscriptionPrompt);

AudioTranscriptionFormat responseFormat = audioTranscriptionOptions.getResponseFormat();
RequestOptions requestOptions = new RequestOptions();

if (audioTranscriptionPrompt
.getOptions() instanceof AzureOpenAiAudioTranscriptionOptions azureOpenAiAudioTranscriptionOptions
&& null != azureOpenAiAudioTranscriptionOptions.getApiVersion()) {
requestOptions.addQueryParam("api-version", azureOpenAiAudioTranscriptionOptions.getApiVersion());
}
if (JSON_FORMATS.contains(responseFormat)) {
var audioTranscription = this.openAIClient.getAudioTranscription(deploymentOrModelName, FILENAME_MARKER,
audioTranscriptionOptions);
var audioTranscription = this.openAIClient
.getAudioTranscriptionWithResponse(deploymentOrModelName, FILENAME_MARKER, audioTranscriptionOptions,
requestOptions)
.getValue();

List<Word> words = null;
if (audioTranscription.getWords() != null) {
Expand Down Expand Up @@ -120,7 +130,7 @@ public AudioTranscriptionResponse call(AudioTranscriptionPrompt audioTranscripti
}
else {
Response<String> audioTranscription = this.openAIClient.getAudioTranscriptionTextWithResponse(
deploymentOrModelName, FILENAME_MARKER, audioTranscriptionOptions, null);
deploymentOrModelName, FILENAME_MARKER, audioTranscriptionOptions, requestOptions);
String text = audioTranscription.getValue();
AudioTranscription transcript = new AudioTranscription(text);
return new AudioTranscriptionResponse(transcript, AzureOpenAiAudioTranscriptionResponseMetadata.from(text));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@

import com.azure.ai.openai.models.AudioTranscriptionFormat;
import com.azure.ai.openai.models.AudioTranscriptionTimestampGranularity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down Expand Up @@ -67,6 +68,12 @@ public class AzureOpenAiAudioTranscriptionOptions implements AudioTranscriptionO

private @JsonProperty("timestamp_granularities") List<GranularityType> granularityType;

/**
* The explicit Azure AI Foundry Models API version to use for this request. latest if
* not otherwise specified.
*/
@JsonIgnore private String apiVersion;

public static Builder builder() {
return new Builder();
}
Expand Down Expand Up @@ -128,6 +135,14 @@ public void setGranularityType(List<GranularityType> granularityType) {
this.granularityType = granularityType;
}

public String getApiVersion() {
return apiVersion;
}

public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}

@Override
public int hashCode() {
final int prime = 31;
Expand All @@ -136,6 +151,7 @@ public int hashCode() {
result = prime * result + ((this.prompt == null) ? 0 : this.prompt.hashCode());
result = prime * result + ((this.language == null) ? 0 : this.language.hashCode());
result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.hashCode());
result = prime * result + ((this.apiVersion == null) ? 0 : this.apiVersion.hashCode());
return result;
}

Expand Down Expand Up @@ -175,6 +191,16 @@ else if (!this.prompt.equals(other.prompt)) {
else if (!this.language.equals(other.language)) {
return false;
}

if (this.apiVersion == null) {
if (other.apiVersion != null) {
return false;
}
}
else if (!this.apiVersion.equals(other.apiVersion)) {
return false;
}

if (this.responseFormat == null) {
return other.responseFormat == null;
}
Expand Down Expand Up @@ -302,6 +328,11 @@ public Builder granularityType(List<GranularityType> granularityType) {
return this;
}

public Builder apiVersion(String apiVersion) {
this.options.apiVersion = apiVersion;
return this;
}

public AzureOpenAiAudioTranscriptionOptions build() {
Assert.hasText(this.options.model, "model must not be empty");
Assert.notNull(this.options.responseFormat, "response_format must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import com.azure.ai.openai.models.ContentFilterResultsForPrompt;
import com.azure.ai.openai.models.FunctionCall;
import com.azure.ai.openai.models.ReasoningEffortValue;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.util.BinaryData;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
Expand Down Expand Up @@ -265,7 +266,16 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
ChatCompletionsOptions options = toAzureChatCompletionsOptions(prompt);
ChatCompletionsOptionsAccessHelper.setStream(options, false);

ChatCompletions chatCompletions = this.openAIClient.getChatCompletions(options.getModel(), options);
RequestOptions requestOptions = new RequestOptions();
if (prompt.getOptions() instanceof AzureOpenAiChatOptions azureOpenAiChatOptions
&& null != azureOpenAiChatOptions.getApiVersion()) {
requestOptions.addQueryParam("api-version", azureOpenAiChatOptions.getApiVersion());
}

ChatCompletions chatCompletions = this.openAIClient
.getChatCompletionsWithResponse(options.getModel(), BinaryData.fromObject(options), requestOptions)
.getValue()
.toObject(ChatCompletions.class);
ChatResponse chatResponse = toChatResponse(chatCompletions, previousChatResponse);
observationContext.setResponse(chatResponse);
return chatResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -216,6 +216,13 @@ public class AzureOpenAiChatOptions implements ToolCallingChatOptions {
@JsonProperty("reasoning_effort")
private String reasoningEffort;

/**
* The explicit Azure AI Foundry Models API version to use for this request. latest if
* not otherwise specified.
*/
@JsonIgnore
private String apiVersion;

@Override
@JsonIgnore
public List<ToolCallback> getToolCallbacks() {
Expand Down Expand Up @@ -288,6 +295,7 @@ public static AzureOpenAiChatOptions fromOptions(AzureOpenAiChatOptions fromOpti
.toolCallbacks(
fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null)
.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
.apiVersion(fromOptions.getApiVersion())
.build();
}

Expand Down Expand Up @@ -482,6 +490,14 @@ public void setStreamOptions(ChatCompletionStreamOptions streamOptions) {
this.streamOptions = streamOptions;
}

public String getApiVersion() {
return apiVersion;
}

public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}

@Override
@SuppressWarnings("unchecked")
public AzureOpenAiChatOptions copy() {
Expand Down Expand Up @@ -512,7 +528,8 @@ public boolean equals(Object o) {
&& Objects.equals(this.toolContext, that.toolContext) && Objects.equals(this.maxTokens, that.maxTokens)
&& Objects.equals(this.frequencyPenalty, that.frequencyPenalty)
&& Objects.equals(this.presencePenalty, that.presencePenalty)
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topP, that.topP);
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topP, that.topP)
&& Objects.equals(this.apiVersion, that.apiVersion);
}

@Override
Expand All @@ -521,7 +538,7 @@ public int hashCode() {
this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, this.seed, this.logprobs,
this.topLogProbs, this.enhancements, this.streamOptions, this.reasoningEffort, this.enableStreamUsage,
this.toolContext, this.maxTokens, this.frequencyPenalty, this.presencePenalty, this.temperature,
this.topP);
this.topP, this.apiVersion);
}

public static class Builder {
Expand Down Expand Up @@ -664,6 +681,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut
return this;
}

public Builder apiVersion(String apiVersion) {
this.options.setApiVersion(apiVersion);
return this;
}

public AzureOpenAiChatOptions build() {
return this.options;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.azure.ai.openai.models.Embeddings;
import com.azure.ai.openai.models.EmbeddingsOptions;
import com.azure.ai.openai.models.EmbeddingsUsage;
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.util.BinaryData;
import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -140,7 +142,18 @@ public EmbeddingResponse call(EmbeddingRequest embeddingRequest) {
.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
this.observationRegistry)
.observe(() -> {
Embeddings embeddings = this.azureOpenAiClient.getEmbeddings(azureOptions.getModel(), azureOptions);

RequestOptions requestOptions = new RequestOptions();

if (null != options.getApiVersion()) {
requestOptions.addQueryParam("api-version", options.getApiVersion());
}

Embeddings embeddings = this.azureOpenAiClient
.getEmbeddingsWithResponse(azureOptions.getModel(), BinaryData.fromObject(azureOptions),
requestOptions)
.getValue()
.toObject(Embeddings.class);

logger.debug("Embeddings retrieved");
var embeddingResponse = generateEmbeddingResponse(embeddings);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,12 @@ public class AzureOpenAiEmbeddingOptions implements EmbeddingOptions {
*/
private Integer dimensions;

/**
* The explicit Azure AI Foundry Models API version to use for this request. latest if
* not otherwise specified.
*/
private String apiVersion;

public static Builder builder() {
return new Builder();
}
Expand Down Expand Up @@ -105,6 +111,14 @@ public void setDimensions(Integer dimensions) {
this.dimensions = dimensions;
}

public String getApiVersion() {
return apiVersion;
}

public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}

public com.azure.ai.openai.models.EmbeddingsOptions toAzureOptions(List<String> instructions) {

var azureOptions = new com.azure.ai.openai.models.EmbeddingsOptions(instructions);
Expand Down Expand Up @@ -144,6 +158,9 @@ public Builder merge(EmbeddingOptions from) {
if (castFrom.getDimensions() != null) {
this.options.setDimensions(castFrom.getDimensions());
}
if (castFrom.getApiVersion() != null) {
this.options.setApiVersion(castFrom.getApiVersion());
}
}
return this;
}
Expand Down Expand Up @@ -177,6 +194,11 @@ public Builder dimensions(Integer dimensions) {
return this;
}

public Builder apiVersion(String apiVersion) {
this.options.apiVersion = apiVersion;
return this;
}

public AzureOpenAiEmbeddingOptions build() {
return this.options;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -70,6 +70,7 @@ public void createRequestWithChatOptions() {
.topLogprobs(5)
.enhancements(mockAzureChatEnhancementConfiguration)
.responseFormat(AzureOpenAiResponseFormat.builder().type(Type.TEXT).build())
.apiVersion("preview")
.build();

var client = AzureOpenAiChatModel.builder()
Expand All @@ -79,6 +80,7 @@ public void createRequestWithChatOptions() {

var requestOptions = client.toAzureChatCompletionsOptions(new Prompt("Test message content"));

assertThat(client.getDefaultOptions().getApiVersion()).isEqualTo("preview");
assertThat(requestOptions.getMessages()).hasSize(1);

assertThat(requestOptions.getModel()).isEqualTo("DEFAULT_MODEL");
Expand Down