Fix a FunctionCallback inside a container that pollutes the global model's ChatOptions
The autoconfiguration adds the FunctionCallbacks directly to the model's ChatOptions, which results in the FunctionCallback being included in the request each time it is called. The modification registers the container's FunctionCallback directly to the model's functionCallbackRegister using the parent AbstractToolCallSupport constsructor. Replace the handleFunctionCallbackConfigurations by simplified runtimeFunctionCallbackConfigurations. Co-authored-by Christian Tzolov <ctzolov@vmware.com>
This commit is contained in:
committed by
Christian Tzolov
parent
ce5961348b
commit
7c800f35b8
@@ -46,6 +46,7 @@ import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -134,7 +135,24 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
|
||||
public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions,
|
||||
RetryTemplate retryTemplate, FunctionCallbackContext functionCallbackContext) {
|
||||
|
||||
super(functionCallbackContext);
|
||||
this(anthropicApi, defaultOptions, retryTemplate, functionCallbackContext, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link AnthropicChatModel} instance.
|
||||
* @param anthropicApi the lower-level API for the Anthropic service.
|
||||
* @param defaultOptions the default options used for the chat completion requests.
|
||||
* @param retryTemplate the retry template used to retry the Anthropic API calls.
|
||||
* @param functionCallbackContext the function callback context used to store the
|
||||
* state of the function calls.
|
||||
* @param toolFunctionCallbacks the tool function callbacks used to handle the tool
|
||||
* calls.
|
||||
*/
|
||||
public AnthropicChatModel(AnthropicApi anthropicApi, AnthropicChatOptions defaultOptions,
|
||||
RetryTemplate retryTemplate, FunctionCallbackContext functionCallbackContext,
|
||||
List<FunctionCallback> toolFunctionCallbacks) {
|
||||
|
||||
super(functionCallbackContext, defaultOptions, toolFunctionCallbacks);
|
||||
|
||||
Assert.notNull(anthropicApi, "AnthropicApi must not be null");
|
||||
Assert.notNull(defaultOptions, "DefaultOptions must not be null");
|
||||
@@ -318,21 +336,17 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
|
||||
AnthropicChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
|
||||
ChatOptions.class, AnthropicChatOptions.class);
|
||||
|
||||
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
|
||||
IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(promptEnabledFunctions);
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
|
||||
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions,
|
||||
!IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(defaultEnabledFunctions);
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
|
||||
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
|
||||
|
||||
List<AnthropicApi.Tool> tools = getFunctionTools(functionsForThisRequest);
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -121,7 +122,12 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
public AzureOpenAiChatModel(OpenAIClient microsoftOpenAiClient, AzureOpenAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext) {
|
||||
super(functionCallbackContext);
|
||||
this(microsoftOpenAiClient, options, functionCallbackContext, List.of());
|
||||
}
|
||||
|
||||
public AzureOpenAiChatModel(OpenAIClient microsoftOpenAiClient, AzureOpenAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
super(functionCallbackContext, options, toolFunctionCallbacks);
|
||||
Assert.notNull(microsoftOpenAiClient, "com.azure.ai.openai.OpenAIClient must not be null");
|
||||
Assert.notNull(options, "AzureOpenAiChatOptions must not be null");
|
||||
this.openAIClient = microsoftOpenAiClient;
|
||||
@@ -267,23 +273,17 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
ChatCompletionsOptions options = new ChatCompletionsOptions(azureMessages);
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
options = this.merge(options, this.defaultOptions);
|
||||
|
||||
options = this.merge(options, this.defaultOptions);
|
||||
|
||||
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions,
|
||||
!IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(defaultEnabledFunctions);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions()))
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
AzureOpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
|
||||
ChatOptions.class, AzureOpenAiChatOptions.class);
|
||||
options = this.merge(updatedRuntimeOptions, options);
|
||||
|
||||
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
|
||||
IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(promptEnabledFunctions);
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
}
|
||||
|
||||
// Add the enabled functions definitions to the request's tools parameter.
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionMessage.T
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.mistralai.metadata.MistralAiUsage;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -95,7 +96,13 @@ public class MistralAiChatModel extends AbstractToolCallSupport implements ChatM
|
||||
|
||||
public MistralAiChatModel(MistralAiApi mistralAiApi, MistralAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext);
|
||||
this(mistralAiApi, options, functionCallbackContext, List.of(), retryTemplate);
|
||||
}
|
||||
|
||||
public MistralAiChatModel(MistralAiApi mistralAiApi, MistralAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks,
|
||||
RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext, options, toolFunctionCallbacks);
|
||||
Assert.notNull(mistralAiApi, "MistralAiApi must not be null");
|
||||
Assert.notNull(options, "Options must not be null");
|
||||
Assert.notNull(retryTemplate, "RetryTemplate must not be null");
|
||||
@@ -292,22 +299,17 @@ public class MistralAiChatModel extends AbstractToolCallSupport implements ChatM
|
||||
|
||||
var request = new MistralAiApi.ChatCompletionRequest(chatCompletionMessages, stream);
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions,
|
||||
!IS_RUNTIME_CALL);
|
||||
|
||||
functionsForThisRequest.addAll(defaultEnabledFunctions);
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, MistralAiApi.ChatCompletionRequest.class);
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, MistralAiApi.ChatCompletionRequest.class);
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
var updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
MistralAiChatOptions.class);
|
||||
|
||||
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
|
||||
IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(promptEnabledFunctions);
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
|
||||
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, MistralAiApi.ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.api.OllamaApi.ChatRequest;
|
||||
@@ -83,31 +84,18 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
public OllamaChatModel(OllamaApi chatApi, OllamaOptions defaultOptions,
|
||||
FunctionCallbackContext functionCallbackContext) {
|
||||
super(functionCallbackContext);
|
||||
this(chatApi, defaultOptions, functionCallbackContext, List.of());
|
||||
}
|
||||
|
||||
public OllamaChatModel(OllamaApi chatApi, OllamaOptions defaultOptions,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
super(functionCallbackContext, defaultOptions, toolFunctionCallbacks);
|
||||
Assert.notNull(chatApi, "OllamaApi must not be null");
|
||||
Assert.notNull(defaultOptions, "DefaultOptions must not be null");
|
||||
this.chatApi = chatApi;
|
||||
this.defaultOptions = defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link OllamaOptions#setModel} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public OllamaChatModel withModel(String model) {
|
||||
this.defaultOptions.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link OllamaOptions} constructor instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public OllamaChatModel withDefaultOptions(OllamaOptions options) {
|
||||
this.defaultOptions = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
|
||||
@@ -246,10 +234,12 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
if (prompt.getOptions() != null) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
OllamaOptions.class);
|
||||
functionsForThisRequest.addAll(this.handleFunctionCallbackConfigurations(runtimeOptions, IS_RUNTIME_CALL));
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(runtimeOptions));
|
||||
}
|
||||
|
||||
functionsForThisRequest.addAll(this.handleFunctionCallbackConfigurations(this.defaultOptions, IS_RUNTIME_CALL));
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
OllamaOptions mergedOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions, OllamaOptions.class);
|
||||
|
||||
// Override the model.
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion;
|
||||
@@ -131,10 +132,29 @@ public class OpenAiChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
*/
|
||||
public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext);
|
||||
this(openAiApi, options, functionCallbackContext, List.of(), retryTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the OpenAiChatModel.
|
||||
* @param openAiApi The OpenAiApi instance to be used for interacting with the OpenAI
|
||||
* Chat API.
|
||||
* @param options The OpenAiChatOptions to configure the chat model.
|
||||
* @param functionCallbackContext The function callback context.
|
||||
* @param toolFunctionCallbacks The tool function callbacks.
|
||||
* @param retryTemplate The retry template.
|
||||
*/
|
||||
public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks,
|
||||
RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext, options, toolFunctionCallbacks);
|
||||
|
||||
Assert.notNull(openAiApi, "OpenAiApi must not be null");
|
||||
Assert.notNull(options, "Options must not be null");
|
||||
Assert.notNull(retryTemplate, "RetryTemplate must not be null");
|
||||
Assert.isTrue(CollectionUtils.isEmpty(options.getFunctionCallbacks()),
|
||||
"The default function callbacks must be set via the toolFunctionCallbacks constructor parameter");
|
||||
|
||||
this.openAiApi = openAiApi;
|
||||
this.defaultOptions = options;
|
||||
this.retryTemplate = retryTemplate;
|
||||
@@ -302,8 +322,6 @@ public class OpenAiChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
*/
|
||||
ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
|
||||
|
||||
Set<String> functionsForThisRequest = new HashSet<>();
|
||||
|
||||
List<ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions().stream().map(message -> {
|
||||
if (message.getMessageType() == MessageType.USER || message.getMessageType() == MessageType.SYSTEM) {
|
||||
Object content = message.getContent();
|
||||
@@ -358,33 +376,29 @@ public class OpenAiChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
ChatCompletionRequest request = new ChatCompletionRequest(chatCompletionMessages, stream);
|
||||
|
||||
Set<String> enabledToolsToUse = new HashSet<>();
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
|
||||
ChatOptions.class, OpenAiChatOptions.class);
|
||||
|
||||
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
|
||||
IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(promptEnabledFunctions);
|
||||
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
|
||||
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
|
||||
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions,
|
||||
!IS_RUNTIME_CALL);
|
||||
|
||||
functionsForThisRequest.addAll(defaultEnabledFunctions);
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
enabledToolsToUse.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
|
||||
// Add the enabled functions definitions to the request's tools parameter.
|
||||
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
|
||||
if (!CollectionUtils.isEmpty(enabledToolsToUse)) {
|
||||
|
||||
request = ModelOptionsUtils.merge(
|
||||
OpenAiChatOptions.builder().withTools(this.getFunctionTools(functionsForThisRequest)).build(),
|
||||
request, ChatCompletionRequest.class);
|
||||
OpenAiChatOptions.builder().withTools(this.getFunctionTools(enabledToolsToUse)).build(), request,
|
||||
ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
// Remove `streamOptions` from the request if it is not a streaming request
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -52,7 +53,8 @@ public class OpenAiTestConfiguration {
|
||||
|
||||
@Bean
|
||||
public OpenAiChatModel openAiChatModel(OpenAiApi api) {
|
||||
OpenAiChatModel openAiChatModel = new OpenAiChatModel(api);
|
||||
OpenAiChatModel openAiChatModel = new OpenAiChatModel(api,
|
||||
OpenAiChatOptions.builder().withModel(ChatModel.GPT_4_O_MINI).build());
|
||||
return openAiChatModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ public class OpenAiPaymentTransactionIT {
|
||||
public OpenAiChatModel openAiClient(OpenAiApi openAiApi, FunctionCallbackContext functionCallbackContext) {
|
||||
return new OpenAiChatModel(openAiApi,
|
||||
OpenAiChatOptions.builder()
|
||||
.withModel(ChatModel.GPT_4_TURBO.getName())
|
||||
.withModel(ChatModel.GPT_4_O_MINI.getName())
|
||||
.withTemperature(0.1f)
|
||||
.build(),
|
||||
functionCallbackContext, RetryUtils.DEFAULT_RETRY_TEMPLATE);
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ChatModelDescription;
|
||||
import org.springframework.ai.model.Media;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.vertexai.gemini.metadata.VertexAiUsage;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
@@ -145,8 +146,13 @@ public class VertexAiGeminiChatModel extends AbstractToolCallSupport implements
|
||||
|
||||
public VertexAiGeminiChatModel(VertexAI vertexAI, VertexAiGeminiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext) {
|
||||
this(vertexAI, options, functionCallbackContext, List.of());
|
||||
}
|
||||
|
||||
super(functionCallbackContext);
|
||||
public VertexAiGeminiChatModel(VertexAI vertexAI, VertexAiGeminiChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
|
||||
super(functionCallbackContext, options, toolFunctionCallbacks);
|
||||
|
||||
Assert.notNull(vertexAI, "VertexAI must not be null");
|
||||
Assert.notNull(options, "VertexAiGeminiChatOptions must not be null");
|
||||
@@ -281,29 +287,22 @@ public class VertexAiGeminiChatModel extends AbstractToolCallSupport implements
|
||||
var generativeModelBuilder = new GenerativeModel.Builder().setModelName(this.defaultOptions.getModel())
|
||||
.setVertexAi(this.vertexAI);
|
||||
|
||||
VertexAiGeminiChatOptions updatedRuntimeOptions = null;
|
||||
VertexAiGeminiChatOptions updatedRuntimeOptions = VertexAiGeminiChatOptions.builder().build();
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
VertexAiGeminiChatOptions.class);
|
||||
|
||||
functionsForThisRequest
|
||||
.addAll(handleFunctionCallbackConfigurations(updatedRuntimeOptions, IS_RUNTIME_CALL));
|
||||
functionsForThisRequest.addAll(runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
}
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
|
||||
functionsForThisRequest.addAll(handleFunctionCallbackConfigurations(this.defaultOptions, !IS_RUNTIME_CALL));
|
||||
|
||||
if (updatedRuntimeOptions == null) {
|
||||
updatedRuntimeOptions = VertexAiGeminiChatOptions.builder().build();
|
||||
}
|
||||
|
||||
updatedRuntimeOptions = ModelOptionsUtils.merge(updatedRuntimeOptions, this.defaultOptions,
|
||||
VertexAiGeminiChatOptions.class);
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
updatedRuntimeOptions = ModelOptionsUtils.merge(updatedRuntimeOptions, this.defaultOptions,
|
||||
VertexAiGeminiChatOptions.class);
|
||||
|
||||
if (updatedRuntimeOptions != null) {
|
||||
|
||||
if (StringUtils.hasText(updatedRuntimeOptions.getModel())
|
||||
|
||||
@@ -59,43 +59,72 @@ public abstract class AbstractToolCallSupport {
|
||||
protected final FunctionCallbackContext functionCallbackContext;
|
||||
|
||||
protected AbstractToolCallSupport(FunctionCallbackContext functionCallbackContext) {
|
||||
this(functionCallbackContext, FunctionCallingOptions.builder().build(), List.of());
|
||||
}
|
||||
|
||||
protected AbstractToolCallSupport(FunctionCallbackContext functionCallbackContext,
|
||||
FunctionCallingOptions functionCallingOptions, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
|
||||
this.functionCallbackContext = functionCallbackContext;
|
||||
|
||||
List<FunctionCallback> defaultFunctionCallbacks = merge(functionCallingOptions, toolFunctionCallbacks);
|
||||
|
||||
if (!CollectionUtils.isEmpty(defaultFunctionCallbacks)) {
|
||||
this.functionCallbackRegister.putAll(defaultFunctionCallbacks.stream()
|
||||
.collect(ConcurrentHashMap::new, (m, v) -> m.put(v.getName(), v), ConcurrentHashMap::putAll));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<FunctionCallback> merge(FunctionCallingOptions funcitonOptions,
|
||||
List<FunctionCallback> toolFunctionCallbacks) {
|
||||
List<FunctionCallback> toolFunctionCallbacksCopy = new ArrayList<>();
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
toolFunctionCallbacksCopy.addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(funcitonOptions.getFunctionCallbacks())) {
|
||||
toolFunctionCallbacksCopy.addAll(funcitonOptions.getFunctionCallbacks());
|
||||
// Make sure that that function callbacks are are registered directly to the
|
||||
// functionCallbackRegister and not passed in the default options.
|
||||
funcitonOptions.setFunctionCallbacks(List.of());
|
||||
}
|
||||
return toolFunctionCallbacksCopy;
|
||||
}
|
||||
|
||||
public Map<String, FunctionCallback> getFunctionCallbackRegister() {
|
||||
return this.functionCallbackRegister;
|
||||
}
|
||||
|
||||
protected Set<String> handleFunctionCallbackConfigurations(FunctionCallingOptions options, boolean isRuntimeCall) {
|
||||
/**
|
||||
* Handle the runtime function callback configurations. Register the function
|
||||
* callbacks
|
||||
* @param runtimeFunctionOptions FunctionCallingOptions to handle.
|
||||
* @return Set of function names to call.
|
||||
*/
|
||||
protected Set<String> runtimeFunctionCallbackConfigurations(FunctionCallingOptions runtimeFunctionOptions) {
|
||||
|
||||
Set<String> functionToCall = new HashSet<>();
|
||||
Set<String> enabledFunctionsToCall = new HashSet<>();
|
||||
|
||||
if (options != null) {
|
||||
if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) {
|
||||
options.getFunctionCallbacks().stream().forEach(functionCallback -> {
|
||||
|
||||
// Register the tool callback.
|
||||
if (isRuntimeCall) {
|
||||
this.functionCallbackRegister.put(functionCallback.getName(), functionCallback);
|
||||
}
|
||||
else {
|
||||
this.functionCallbackRegister.putIfAbsent(functionCallback.getName(), functionCallback);
|
||||
}
|
||||
|
||||
// Automatically enable the function, usually from prompt callback.
|
||||
if (isRuntimeCall) {
|
||||
functionToCall.add(functionCallback.getName());
|
||||
}
|
||||
});
|
||||
if (runtimeFunctionOptions != null) {
|
||||
// Add the explicitly enabled functions.
|
||||
if (!CollectionUtils.isEmpty(runtimeFunctionOptions.getFunctions())) {
|
||||
enabledFunctionsToCall.addAll(runtimeFunctionOptions.getFunctions());
|
||||
}
|
||||
|
||||
// Add the explicitly enabled functions.
|
||||
if (!CollectionUtils.isEmpty(options.getFunctions())) {
|
||||
functionToCall.addAll(options.getFunctions());
|
||||
// Add the function callbacks to the register and automatically enable them.
|
||||
if (!CollectionUtils.isEmpty(runtimeFunctionOptions.getFunctionCallbacks())) {
|
||||
runtimeFunctionOptions.getFunctionCallbacks().stream().forEach(functionCallback -> {
|
||||
|
||||
// Register the tool callback.
|
||||
this.functionCallbackRegister.put(functionCallback.getName(), functionCallback);
|
||||
|
||||
// Automatically enable the function, usually from prompt callback.
|
||||
enabledFunctionsToCall.add(functionCallback.getName());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return functionToCall;
|
||||
return enabledFunctionsToCall;
|
||||
}
|
||||
|
||||
protected List<Message> handleToolCalls(Prompt prompt, ChatResponse response) {
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
@@ -66,12 +65,8 @@ public class AnthropicAutoConfiguration {
|
||||
RetryTemplate retryTemplate, FunctionCallbackContext functionCallbackContext,
|
||||
List<FunctionCallback> toolFunctionCallbacks) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new AnthropicChatModel(anthropicApi, chatProperties.getOptions(), retryTemplate,
|
||||
functionCallbackContext);
|
||||
return new AnthropicChatModel(anthropicApi, chatProperties.getOptions(), retryTemplate, functionCallbackContext,
|
||||
toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -15,12 +15,8 @@
|
||||
*/
|
||||
package org.springframework.ai.autoconfigure.azure.openai;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.core.util.ClientOptions;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
@@ -36,10 +32,14 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.core.util.ClientOptions;
|
||||
|
||||
/**
|
||||
* @author Piotr Olaszewski
|
||||
@@ -103,11 +103,8 @@ public class AzureOpenAiAutoConfiguration {
|
||||
AzureOpenAiChatProperties chatProperties, List<FunctionCallback> toolFunctionCallbacks,
|
||||
FunctionCallbackContext functionCallbackContext) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new AzureOpenAiChatModel(openAIClient, chatProperties.getOptions(), functionCallbackContext);
|
||||
return new AzureOpenAiChatModel(openAIClient, chatProperties.getOptions(), functionCallbackContext,
|
||||
toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -37,6 +37,9 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
@@ -59,11 +62,16 @@ public class MiniMaxAutoConfiguration {
|
||||
var miniMaxApi = miniMaxApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
MiniMaxChatModel chatModel = new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(),
|
||||
functionCallbackContext, retryTemplate);
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
Map<String, FunctionCallback> toolFunctionCallbackMap = toolFunctionCallbacks.stream()
|
||||
.collect(Collectors.toMap(FunctionCallback::getName, Function.identity(), (a, b) -> b));
|
||||
chatModel.getFunctionCallbackRegister().putAll(toolFunctionCallbackMap);
|
||||
}
|
||||
|
||||
return new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(), functionCallbackContext, retryTemplate);
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
@@ -81,12 +80,8 @@ public class MistralAiAutoConfiguration {
|
||||
var mistralAiApi = mistralAiApi(chatProperties.getApiKey(), commonProperties.getApiKey(),
|
||||
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new MistralAiChatModel(mistralAiApi, chatProperties.getOptions(), functionCallbackContext,
|
||||
retryTemplate);
|
||||
toolFunctionCallbacks, retryTemplate);
|
||||
}
|
||||
|
||||
private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String baseUrl, String commonBaseUrl,
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.boot.autoconfigure.web.reactive.function.client.WebCl
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
@@ -68,11 +67,7 @@ public class OllamaAutoConfiguration {
|
||||
public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
|
||||
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
properties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new OllamaChatModel(ollamaApi, properties.getOptions(), functionCallbackContext);
|
||||
return new OllamaChatModel(ollamaApi, properties.getOptions(), functionCallbackContext, toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
@@ -75,11 +74,8 @@ public class OpenAiAutoConfiguration {
|
||||
var openAiApi = openAiApi(chatProperties, commonProperties, restClientBuilder, webClientBuilder,
|
||||
responseErrorHandler, "chat");
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new OpenAiChatModel(openAiApi, chatProperties.getOptions(), functionCallbackContext, retryTemplate);
|
||||
return new OpenAiChatModel(openAiApi, chatProperties.getOptions(), functionCallbackContext,
|
||||
toolFunctionCallbacks, retryTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -18,10 +18,6 @@ package org.springframework.ai.autoconfigure.vertexai.gemini;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.cloud.vertexai.VertexAI;
|
||||
|
||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper.Builder.SchemaType;
|
||||
@@ -36,6 +32,9 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.cloud.vertexai.VertexAI;
|
||||
|
||||
/**
|
||||
* Auto-configuration for Vertex AI Gemini Chat.
|
||||
*
|
||||
@@ -84,11 +83,8 @@ public class VertexAiGeminiAutoConfiguration {
|
||||
|
||||
FunctionCallbackContext functionCallbackContext = springAiFunctionManager(context);
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
return new VertexAiGeminiChatModel(vertexAi, chatProperties.getOptions(), functionCallbackContext);
|
||||
return new VertexAiGeminiChatModel(vertexAi, chatProperties.getOptions(), functionCallbackContext,
|
||||
toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,9 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
@@ -61,11 +64,16 @@ public class ZhiPuAiAutoConfiguration {
|
||||
var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(),
|
||||
functionCallbackContext, retryTemplate);
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
|
||||
Map<String, FunctionCallback> toolFunctionCallbackMap = toolFunctionCallbacks.stream()
|
||||
.collect(Collectors.toMap(FunctionCallback::getName, Function.identity(), (a, b) -> b));
|
||||
chatModel.getFunctionCallbackRegister().putAll(toolFunctionCallbackMap);
|
||||
}
|
||||
|
||||
return new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), functionCallbackContext, retryTemplate);
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -67,9 +67,7 @@ public class FunctionCallWithFunctionWrapperIT {
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
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", "15.0");
|
||||
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("30", "10", "15");
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class OllamaChatAutoConfigurationIT {
|
||||
createImage(ollamaContainer, OLLAMA_WITH_MODEL);
|
||||
}
|
||||
|
||||
static String baseUrl;
|
||||
static String baseUrl = "http://localhost:11434";
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() throws IOException, InterruptedException {
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -41,11 +42,12 @@ public class FunctionCallbackInPrompt2IT {
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.run(context -> {
|
||||
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
ChatClient chatClient = ChatClient.builder(chatModel).build();
|
||||
ChatClient chatClient = ChatClient.builder(chatModel).build();
|
||||
|
||||
// @formatter:off
|
||||
chatClient.prompt()
|
||||
@@ -58,19 +60,18 @@ public class FunctionCallbackInPrompt2IT {
|
||||
.call().content();
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
});
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionCallTest2() {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.run(context -> {
|
||||
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
// @formatter:off
|
||||
String content = ChatClient.builder(chatModel).build().prompt()
|
||||
@@ -84,18 +85,19 @@ public class FunctionCallbackInPrompt2IT {
|
||||
})
|
||||
.call().content();
|
||||
// @formatter:on
|
||||
logger.info("Response: {}", content);
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).contains("18");
|
||||
});
|
||||
assertThat(content).contains("18");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamingFunctionCallTest() {
|
||||
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.run(context -> {
|
||||
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
// @formatter:off
|
||||
String content = ChatClient.builder(chatModel).build().prompt()
|
||||
@@ -105,12 +107,10 @@ public class FunctionCallbackInPrompt2IT {
|
||||
.collectList().block().stream().collect(Collectors.joining());
|
||||
// @formatter:on
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
});
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -50,7 +51,7 @@ public class FunctionCallbackInPromptIT {
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o",
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(),
|
||||
"spring.ai.openai.chat.options.temperature=0.1")
|
||||
.run(context -> {
|
||||
|
||||
@@ -79,7 +80,7 @@ public class FunctionCallbackInPromptIT {
|
||||
void streamingFunctionCallTest() {
|
||||
|
||||
contextRunner
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o",
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(),
|
||||
"spring.ai.openai.chat.options.temperature=0.5")
|
||||
.run(context -> {
|
||||
|
||||
@@ -108,9 +109,7 @@ public class FunctionCallbackInPromptIT {
|
||||
.collect(Collectors.joining());
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -54,36 +55,37 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.run(context -> {
|
||||
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
// Test weatherFunction
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'");
|
||||
// Test weatherFunction
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'");
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
OpenAiChatOptions.builder().withFunction("weatherFunction").build()));
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
OpenAiChatOptions.builder().withFunction("weatherFunction").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
|
||||
|
||||
// Test weatherFunctionTwo
|
||||
response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
OpenAiChatOptions.builder().withFunction("weatherFunctionTwo").build()));
|
||||
// Test weatherFunctionTwo
|
||||
response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
OpenAiChatOptions.builder().withFunction("weatherFunctionTwo").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionCallWithPortableFunctionCallingOptions() {
|
||||
contextRunner
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o",
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(),
|
||||
"spring.ai.openai.chat.options.temperature=0.1")
|
||||
.run(context -> {
|
||||
|
||||
@@ -104,7 +106,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
@Test
|
||||
void streamFunctionCallTest() {
|
||||
contextRunner
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=gpt-4o",
|
||||
.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(),
|
||||
"spring.ai.openai.chat.options.temperature=0.1")
|
||||
.run(context -> {
|
||||
|
||||
@@ -127,9 +129,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
.collect(Collectors.joining());
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
|
||||
// Test weatherFunctionTwo
|
||||
response = chatModel.stream(new Prompt(List.of(userMessage),
|
||||
@@ -146,9 +146,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).isNotEmpty().withFailMessage("Content returned from OpenAI model is empty");
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -39,7 +40,8 @@ public class FunctionCallbackWrapper2IT {
|
||||
private final Logger logger = LoggerFactory.getLogger(FunctionCallbackWrapperIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@@ -62,9 +64,7 @@ public class FunctionCallbackWrapper2IT {
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
assertThat(content).containsAnyOf("10", "10");
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,9 +84,7 @@ public class FunctionCallbackWrapper2IT {
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
|
||||
assertThat(content).containsAnyOf("30.0", "30");
|
||||
assertThat(content).containsAnyOf("10.0", "10");
|
||||
assertThat(content).containsAnyOf("15.0", "15");
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackWrapper;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -47,7 +48,8 @@ public class FunctionCallbackWrapperIT {
|
||||
private final Logger logger = LoggerFactory.getLogger(FunctionCallbackWrapperIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
Reference in New Issue
Block a user