Address review comments
This commit is contained in:
@@ -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<String> enabledFunctionsForRequest = new HashSet<>();
|
||||
Set<String> functionsForThisRequest = new HashSet<>();
|
||||
|
||||
List<ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions()
|
||||
.stream()
|
||||
@@ -223,9 +217,9 @@ public class OpenAiChatClient implements ChatClient, StreamingChatClient {
|
||||
OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions,
|
||||
ChatOptions.class, OpenAiChatOptions.class);
|
||||
|
||||
Set<String> promptEnabledFunctions = handleFunctionCallbackConfigurations(updatedRuntimeOptions, true,
|
||||
true);
|
||||
enabledFunctionsForRequest.addAll(promptEnabledFunctions);
|
||||
Set<String> 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<String> defaultEnabledFunctions = handleFunctionCallbackConfigurations(this.defaultOptions, false,
|
||||
false);
|
||||
Set<String> 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<String> handleFunctionCallbackConfigurations(OpenAiChatOptions options,
|
||||
boolean autoEnableCallbackFunctions, boolean overrideCallbackFunctionsRegister) {
|
||||
private Set<String> handleFunctionCallbackConfigurations(OpenAiChatOptions options, boolean isRuntimeCall) {
|
||||
|
||||
Set<String> enabledFunctions = new HashSet<>();
|
||||
Set<String> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<String> enabledFunctions = new HashSet<>();
|
||||
private Set<String> functions = new HashSet<>();
|
||||
// @formatter:on
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -239,15 +239,15 @@ public class OpenAiChatOptions implements ChatOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withEnabledFunctions(Set<String> functionNames) {
|
||||
public Builder withFunctions(Set<String> 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<String> getEnabledFunctions() {
|
||||
return enabledFunctions;
|
||||
public Set<String> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
public void setEnabledFunctions(Set<String> functionNames) {
|
||||
this.enabledFunctions = functionNames;
|
||||
public void setFunctions(Set<String> functionNames) {
|
||||
this.functions = functionNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user