Address review comments

This commit is contained in:
Christian Tzolov
2024-02-15 22:38:50 +01:00
parent d580fe2757
commit bd479e1388
8 changed files with 40 additions and 45 deletions

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -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")

View File

@@ -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");
}

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -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);