diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatClient.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatClient.java index 15e1ef20e..942b2ead6 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatClient.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatClient.java @@ -73,6 +73,8 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { private final Logger logger = LoggerFactory.getLogger(getClass()); + private final static boolean IS_RUNTIME_CALL = true; + /** * The default options used for the chat completion requests. */ @@ -127,14 +129,6 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { this.openAiApi = openAiApi; this.defaultOptions = options; this.functionCallbackContext = functionCallbackContext; - - // Register the default function callbacks. - if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctionCallbacks())) { - this.defaultOptions.getFunctionCallbacks() - .stream() - .forEach(functionCallback -> this.functionCallbackRegister.put(functionCallback.getName(), - functionCallback)); - } } /** @@ -208,7 +202,7 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { */ ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { - Set enabledFunctionsForRequest = new HashSet<>(); + Set functionsForThisRequest = new HashSet<>(); List chatCompletionMessages = prompt.getInstructions() .stream() @@ -223,9 +217,9 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, ChatOptions.class, OpenAiChatOptions.class); - Set promptEnabledFunctions = handleFunctionCallbackConfigurations(updatedRuntimeOptions, true, - true); - enabledFunctionsForRequest.addAll(promptEnabledFunctions); + Set promptEnabledFunctions = handleFunctionCallbackConfigurations(updatedRuntimeOptions, + IS_RUNTIME_CALL); + functionsForThisRequest.addAll(promptEnabledFunctions); request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class); } @@ -237,40 +231,39 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { if (this.defaultOptions != null) { - Set defaultEnabledFunctions = handleFunctionCallbackConfigurations(this.defaultOptions, false, - false); + Set defaultEnabledFunctions = handleFunctionCallbackConfigurations(this.defaultOptions, + !IS_RUNTIME_CALL); - enabledFunctionsForRequest.addAll(defaultEnabledFunctions); + functionsForThisRequest.addAll(defaultEnabledFunctions); request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class); } // Add the enabled functions definitions to the request's tools parameter. - if (!CollectionUtils.isEmpty(enabledFunctionsForRequest)) { + if (!CollectionUtils.isEmpty(functionsForThisRequest)) { if (stream) { throw new IllegalArgumentException("Currently tool functions are not supported in streaming mode"); } request = ModelOptionsUtils.merge( - OpenAiChatOptions.builder().withTools(this.getFunctionTools(enabledFunctionsForRequest)).build(), + OpenAiChatOptions.builder().withTools(this.getFunctionTools(functionsForThisRequest)).build(), request, ChatCompletionRequest.class); } return request; } - private Set handleFunctionCallbackConfigurations(OpenAiChatOptions options, - boolean autoEnableCallbackFunctions, boolean overrideCallbackFunctionsRegister) { + private Set handleFunctionCallbackConfigurations(OpenAiChatOptions options, boolean isRuntimeCall) { - Set enabledFunctions = new HashSet<>(); + Set functionToCall = new HashSet<>(); if (options != null) { if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) { options.getFunctionCallbacks().stream().forEach(functionCallback -> { // Register the tool callback. - if (overrideCallbackFunctionsRegister) { + if (isRuntimeCall) { this.functionCallbackRegister.put(functionCallback.getName(), functionCallback); } else { @@ -278,19 +271,19 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient { } // Automatically enable the function, usually from prompt callback. - if (autoEnableCallbackFunctions) { - enabledFunctions.add(functionCallback.getName()); + if (isRuntimeCall) { + functionToCall.add(functionCallback.getName()); } }); } // Add the explicitly enabled functions. - if (!CollectionUtils.isEmpty(options.getEnabledFunctions())) { - enabledFunctions.addAll(options.getEnabledFunctions()); + if (!CollectionUtils.isEmpty(options.getFunctions())) { + functionToCall.addAll(options.getFunctions()); } } - return enabledFunctions; + return functionToCall; } /** diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index 5168b785c..82ceaee28 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -141,11 +141,11 @@ public class OpenAiChatOptions implements ChatOptions { * The {@link #functionCallbacks} from the PromptOptions are automatically enabled for the duration of the prompt execution. * * Note that function enabled with the default options are enabled for all chat completion requests. This could impact the token count and the billing. - * If the enabledFunctions is set in a prompt options, then the enabled functions are only active for the duration of this prompt execution. + * If the functions is set in a prompt options, then the enabled functions are only active for the duration of this prompt execution. */ @NestedConfigurationProperty @JsonIgnore - private Set enabledFunctions = new HashSet<>(); + private Set functions = new HashSet<>(); // @formatter:on public static Builder builder() { @@ -239,15 +239,15 @@ public class OpenAiChatOptions implements ChatOptions { return this; } - public Builder withEnabledFunctions(Set functionNames) { + public Builder withFunctions(Set functionNames) { Assert.notNull(functionNames, "Function names must not be null"); - this.options.enabledFunctions = functionNames; + this.options.functions = functionNames; return this; } - public Builder withEnabledFunction(String functionName) { + public Builder withFunction(String functionName) { Assert.hasText(functionName, "Function name must not be empty"); - this.options.enabledFunctions.add(functionName); + this.options.functions.add(functionName); return this; } @@ -381,12 +381,12 @@ public class OpenAiChatOptions implements ChatOptions { this.functionCallbacks = functionCallbacks; } - public Set getEnabledFunctions() { - return enabledFunctions; + public Set getFunctions() { + return functions; } - public void setEnabledFunctions(Set functionNames) { - this.enabledFunctions = functionNames; + public void setFunctions(Set functionNames) { + this.functions = functionNames; } @Override diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java index fad3f7d1b..f4dab325e 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java @@ -114,7 +114,7 @@ public class ChatCompletionRequestTests { // Explicitly enable the function request = client.createRequest(new Prompt("Test message content", - OpenAiChatOptions.builder().withEnabledFunction(TOOL_FUNCTION_NAME).build()), false); + OpenAiChatOptions.builder().withFunction(TOOL_FUNCTION_NAME).build()), false); assertThat(request.tools()).hasSize(1); assertThat(request.tools().get(0).function().name()).as("Explicitly enabled function") diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java index a664e4562..9ad14a5cd 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java @@ -182,7 +182,9 @@ class OpenAiChatClientIT extends AbstractIT { logger.info("Response: {}", response); - assertThat(response.getResult().getOutput().getContent()).contains("30.0", "10.0", "15.0"); + assertThat(response.getResult().getOutput().getContent()).containsAnyOf("30.0", "30"); + assertThat(response.getResult().getOutput().getContent()).containsAnyOf("10.0", "10"); + assertThat(response.getResult().getOutput().getContent()).containsAnyOf("15.0", "15"); } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java index 894c0df83..e74b57150 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/EmbeddingIT.java @@ -38,11 +38,11 @@ class EmbeddingIT { EmbeddingResponse embeddingResponse = embeddingClient.embedForResponse(List.of("Hello World")); assertThat(embeddingResponse.getResults()).hasSize(1); assertThat(embeddingResponse.getResults().get(0)).isNotNull(); - assertThat(embeddingResponse.getMetadata()).containsEntry("model", "text-embedding-ada-002"); + assertThat(embeddingResponse.getMetadata()).containsEntry("model", "text-embedding-3-large"); assertThat(embeddingResponse.getMetadata()).containsEntry("total-tokens", 2); assertThat(embeddingResponse.getMetadata()).containsEntry("prompt-tokens", 2); - assertThat(embeddingClient.dimensions()).isEqualTo(1536); + assertThat(embeddingClient.dimensions()).isEqualTo(3072); } } diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/clients/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/clients/openai-chat.adoc index 7f04768e8..aa797d0f3 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/clients/openai-chat.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/clients/openai-chat.adoc @@ -73,7 +73,7 @@ The prefix `spring.ai.openai.chat` is the property prefix that lets you configur | spring.ai.openai.chat.options.tools | A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. | - | spring.ai.openai.chat.options.toolChoice | Controls which (if any) function is called by the model. none means the model will not call a function and instead generates a message. auto means the model can pick between generating a message or calling a function. Specifying a particular function via {"type: "function", "function": {"name": "my_function"}} forces the model to call that function. none is the default when no functions are present. auto is the default if functions are present. | - | spring.ai.openai.chat.options.user | A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. | - -| spring.ai.openai.chat.options.enabledFunctions | List of functions, identified by their names, to enable for function calling in a single prompt requests. Functions with those names must exist in the functionCallbacks registry. | - +| spring.ai.openai.chat.options.functions | List of functions, identified by their names, to enable for function calling in a single prompt requests. Functions with those names must exist in the functionCallbacks registry. | - |==== NOTE: You can override the common `spring.ai.openai.base-url` and `spring.ai.openai.api-key` for the `ChatClient` and `EmbeddingClient` implementations. diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWithPlainFunctionBeanIT.java index 3e4308216..f0fd6e158 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -58,14 +58,14 @@ class FunctionCallbackWithPlainFunctionBeanIT { UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?"); ChatResponse response = chatClient.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().withEnabledFunction("weatherFunction").build())); + OpenAiChatOptions.builder().withFunction("weatherFunction").build())); logger.info("Response: {}", response); assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); response = chatClient.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().withEnabledFunction("weatherFunction3").build())); + OpenAiChatOptions.builder().withFunction("weatherFunction3").build())); logger.info("Response: {}", response); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java index cfb77440d..9e7855fac 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java @@ -57,8 +57,8 @@ public class FunctionCallbackWrapperIT { UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?"); - ChatResponse response = chatClient.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().withEnabledFunction("WeatherInfo").build())); + ChatResponse response = chatClient.call( + new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withFunction("WeatherInfo").build())); logger.info("Response: {}", response);