Make chat and embedding paths configurable
* Updated docs * Minor code cleanup
This commit is contained in:
@@ -15,17 +15,9 @@
|
||||
*/
|
||||
package org.springframework.ai.openai.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
|
||||
@@ -39,6 +31,13 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
// @formatter:off
|
||||
/**
|
||||
@@ -56,6 +55,10 @@ public class OpenAiApi {
|
||||
public static final String DEFAULT_EMBEDDING_MODEL = EmbeddingModel.TEXT_EMBEDDING_ADA_002.getValue();
|
||||
private static final Predicate<String> SSE_DONE_PREDICATE = "[DONE]"::equals;
|
||||
|
||||
private final String completionsPath;
|
||||
|
||||
private final String embeddingsPath;
|
||||
|
||||
private final RestClient restClient;
|
||||
|
||||
private final WebClient webClient;
|
||||
@@ -99,7 +102,24 @@ public class OpenAiApi {
|
||||
* @param responseErrorHandler Response error handler.
|
||||
*/
|
||||
public OpenAiApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
this(baseUrl, openAiToken, "/v1/chat/completions", "/v1/embeddings",
|
||||
restClientBuilder, webClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new chat completion api.
|
||||
*
|
||||
* @param baseUrl api base URL.
|
||||
* @param openAiToken OpenAI apiKey.
|
||||
* @param restClientBuilder RestClient builder.
|
||||
* @param responseErrorHandler Response error handler.
|
||||
*/
|
||||
public OpenAiApi(String baseUrl, String openAiToken, String completionsPath, String embeddingsPath,
|
||||
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
Assert.hasText(completionsPath, "Completions Path must not be null");
|
||||
Assert.hasText(embeddingsPath, "Embeddings Path must not be null");
|
||||
this.completionsPath = completionsPath;
|
||||
this.embeddingsPath = embeddingsPath;
|
||||
this.restClient = restClientBuilder
|
||||
.baseUrl(baseUrl)
|
||||
.defaultHeaders(ApiUtils.getJsonContentHeaders(openAiToken))
|
||||
@@ -812,7 +832,7 @@ public class OpenAiApi {
|
||||
Assert.isTrue(!chatRequest.stream(), "Request must set the steam property to false.");
|
||||
|
||||
return this.restClient.post()
|
||||
.uri("/v1/chat/completions")
|
||||
.uri(this.completionsPath)
|
||||
.body(chatRequest)
|
||||
.retrieve()
|
||||
.toEntity(ChatCompletion.class);
|
||||
@@ -834,7 +854,7 @@ public class OpenAiApi {
|
||||
AtomicBoolean isInsideTool = new AtomicBoolean(false);
|
||||
|
||||
return this.webClient.post()
|
||||
.uri("/v1/chat/completions")
|
||||
.uri(this.completionsPath)
|
||||
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
@@ -1022,7 +1042,7 @@ public class OpenAiApi {
|
||||
}
|
||||
|
||||
return this.restClient.post()
|
||||
.uri("/v1/embeddings")
|
||||
.uri(this.embeddingsPath)
|
||||
.body(embeddingRequest)
|
||||
.retrieve()
|
||||
.toEntity(new ParameterizedTypeReference<>() {
|
||||
|
||||
@@ -89,6 +89,7 @@ The prefix `spring.ai.openai.chat` is the property prefix that lets you configur
|
||||
|
||||
| spring.ai.openai.chat.enabled | Enable OpenAI chat model. | true
|
||||
| spring.ai.openai.chat.base-url | Optional overrides the spring.ai.openai.base-url to provide chat specific url | -
|
||||
| spring.ai.openai.chat.completions-path | The path to append to the base-url | `/v1/chat/completions`
|
||||
| spring.ai.openai.chat.api-key | Optional overrides the spring.ai.openai.api-key to provide chat specific api-key | -
|
||||
| spring.ai.openai.chat.options.model | This is the OpenAI Chat model to use. `gpt-4o`, `gpt-4-turbo`, `gpt-4-turbo-2024-04-09`, `gpt-4-0125-preview`, `gpt-4-turbo-preview`, `gpt-3.5-turbo`, `gpt-3.5-turbo-0125`, `gpt-3.5-turbo-1106`. See the https://platform.openai.com/docs/models[models] page for more information. | `gpt-3.5-turbo`
|
||||
| spring.ai.openai.chat.options.temperature | The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make output more random while lower values will make results more focused and deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction of these two settings is difficult to predict. | 0.8
|
||||
|
||||
@@ -89,6 +89,7 @@ The prefix `spring.ai.openai.embedding` is property prefix that configures the `
|
||||
|
||||
| spring.ai.openai.embedding.enabled | Enable OpenAI embedding model. | true
|
||||
| spring.ai.openai.embedding.base-url | Optional overrides the spring.ai.openai.base-url to provide embedding specific url | -
|
||||
| spring.ai.openai.chat.embeddings-path | The path to append to the base-url | `/v1/embeddings`
|
||||
| spring.ai.openai.embedding.api-key | Optional overrides the spring.ai.openai.api-key to provide embedding specific api-key | -
|
||||
| spring.ai.openai.embedding.metadata-mode | Document content extraction mode. | EMBED
|
||||
| spring.ai.openai.embedding.options.model | The model to use | text-embedding-ada-002 (other options: text-embedding-3-large, text-embedding-3-small)
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.ai.autoconfigure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
@@ -71,8 +72,7 @@ public class OpenAiAutoConfiguration {
|
||||
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
var openAiApi = openAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
|
||||
var openAiApi = openAiApi(chatProperties, commonProperties, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler, "chat");
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
@@ -91,17 +91,39 @@ public class OpenAiAutoConfiguration {
|
||||
WebClient.Builder webClientBuilder, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
var openAiApi = openAiApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
|
||||
var openAiApi = openAiApi(embeddingProperties, commonProperties, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler, "embedding");
|
||||
|
||||
return new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate);
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey,
|
||||
private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties,
|
||||
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
|
||||
ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
ResolvedBaseUrlAndApiKey result = getResolvedBaseUrlAndApiKey(chatProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties, modelType);
|
||||
|
||||
return new OpenAiApi(result.resolvedBaseUrl(), result.resolvedApiKey(), chatProperties.getCompletionsPath(),
|
||||
OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler);
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(OpenAiEmbeddingProperties embeddingProperties,
|
||||
OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder,
|
||||
WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
ResolvedBaseUrlAndApiKey result = getResolvedBaseUrlAndApiKey(embeddingProperties.getBaseUrl(),
|
||||
embeddingProperties.getApiKey(), commonProperties, modelType);
|
||||
|
||||
return new OpenAiApi(result.resolvedBaseUrl(), result.resolvedApiKey(),
|
||||
OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH, embeddingProperties.getEmbeddingsPath(),
|
||||
restClientBuilder, webClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
private static @NotNull ResolvedBaseUrlAndApiKey getResolvedBaseUrlAndApiKey(String baseUrl, String apiKey,
|
||||
OpenAiConnectionProperties commonProperties, String modelType) {
|
||||
var commonBaseUrl = commonProperties.getBaseUrl();
|
||||
var commonApiKey = commonProperties.getApiKey();
|
||||
|
||||
String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl;
|
||||
Assert.hasText(resolvedBaseUrl,
|
||||
@@ -112,9 +134,10 @@ public class OpenAiAutoConfiguration {
|
||||
Assert.hasText(resolvedApiKey,
|
||||
"OpenAI API key must be set. Use the connection property: spring.ai.openai.api-key or spring.ai.openai."
|
||||
+ modelType + ".api-key property.");
|
||||
return new ResolvedBaseUrlAndApiKey(resolvedBaseUrl, resolvedApiKey);
|
||||
}
|
||||
|
||||
return new OpenAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler);
|
||||
private record ResolvedBaseUrlAndApiKey(String resolvedBaseUrl, String resolvedApiKey) {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -164,10 +187,8 @@ public class OpenAiAutoConfiguration {
|
||||
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler);
|
||||
|
||||
OpenAiAudioTranscriptionModel openAiChatModel = new OpenAiAudioTranscriptionModel(openAiAudioApi,
|
||||
transcriptionProperties.getOptions(), retryTemplate);
|
||||
return new OpenAiAudioTranscriptionModel(openAiAudioApi, transcriptionProperties.getOptions(), retryTemplate);
|
||||
|
||||
return openAiChatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -193,10 +214,7 @@ public class OpenAiAutoConfiguration {
|
||||
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler);
|
||||
|
||||
OpenAiAudioSpeechModel openAiSpeechModel = new OpenAiAudioSpeechModel(openAiAudioApi,
|
||||
speechProperties.getOptions(), retryTemplate);
|
||||
|
||||
return openAiSpeechModel;
|
||||
return new OpenAiAudioSpeechModel(openAiAudioApi, speechProperties.getOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -28,11 +28,15 @@ public class OpenAiChatProperties extends OpenAiParentProperties {
|
||||
|
||||
private static final Double DEFAULT_TEMPERATURE = 0.7;
|
||||
|
||||
public static final String DEFAULT_COMPLETIONS_PATH = "/v1/chat/completions";
|
||||
|
||||
/**
|
||||
* Enable OpenAI chat model.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
private String completionsPath = DEFAULT_COMPLETIONS_PATH;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private OpenAiChatOptions options = OpenAiChatOptions.builder()
|
||||
.withModel(DEFAULT_CHAT_MODEL)
|
||||
@@ -55,4 +59,12 @@ public class OpenAiChatProperties extends OpenAiParentProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getCompletionsPath() {
|
||||
return completionsPath;
|
||||
}
|
||||
|
||||
public void setCompletionsPath(String completionsPath) {
|
||||
this.completionsPath = completionsPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public class OpenAiEmbeddingProperties extends OpenAiParentProperties {
|
||||
|
||||
public static final String DEFAULT_EMBEDDING_MODEL = "text-embedding-ada-002";
|
||||
|
||||
public static final String DEFAULT_EMBEDDINGS_PATH = "/v1/embeddings";
|
||||
|
||||
/**
|
||||
* Enable OpenAI embedding model.
|
||||
*/
|
||||
@@ -34,6 +36,8 @@ public class OpenAiEmbeddingProperties extends OpenAiParentProperties {
|
||||
|
||||
private MetadataMode metadataMode = MetadataMode.EMBED;
|
||||
|
||||
private String embeddingsPath = DEFAULT_EMBEDDINGS_PATH;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder()
|
||||
.withModel(DEFAULT_EMBEDDING_MODEL)
|
||||
@@ -63,4 +67,12 @@ public class OpenAiEmbeddingProperties extends OpenAiParentProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getEmbeddingsPath() {
|
||||
return embeddingsPath;
|
||||
}
|
||||
|
||||
public void setEmbeddingsPath(String embeddingsPath) {
|
||||
this.embeddingsPath = embeddingsPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user