Refactor FunctionCallingOptions Builder

- Deprecate existing FunctionCallingOptionsBuilder
  - Create FunctionCallingOptions.Builder which extends ChatOptions.Builder
  - Create DefaultFunctionCallingOptions which extends DefaultChatOptions and implements FunctionCallingOptions to serve the default FunctionCalling options
  - Create DefaultFunctionCallingOptionsBuilder to build the default functioncalling options
  - Update the usage of functioncalling options builder to use the newly added builder including the tests

Improve extensibility of DefaultChatOptionsBuilder

 - Enable DefaultChatOptionsBuilder to accommodate any other sub types
   - Introduce generics to support sub types that extend DefaultChatOptionsBuilder
   - Update builder methods to return the sub type

 - Make FunctionCallingOptions' builder()'s return type to accommodate sub types which can extend FunctionCallingOptions.Builder
This commit is contained in:
Ilayaperumal Gopinathan
2024-12-12 22:39:06 +00:00
committed by Mark Pollack
parent 627fb79e5e
commit b25d6e8d2d
33 changed files with 446 additions and 129 deletions

View File

@@ -50,7 +50,7 @@ import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
@@ -259,9 +259,7 @@ class AnthropicChatModelIT {
List.of(new Media(new MimeType("application", "pdf"), pdfData)));
var response = this.chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
.build()));
FunctionCallingOptions.builder().model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName()).build()));
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Spring AI", "portable API");
}

View File

@@ -96,11 +96,10 @@ import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.DefaultFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackResolver;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.observation.conventions.AiProvider;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -322,12 +321,12 @@ public class BedrockProxyChatModel extends AbstractToolCallSupport implements Ch
if (prompt.getOptions() != null) {
if (prompt.getOptions() instanceof FunctionCallingOptions) {
var functionCallingOptions = (FunctionCallingOptions) prompt.getOptions();
updatedRuntimeOptions = ((PortableFunctionCallingOptions) updatedRuntimeOptions)
updatedRuntimeOptions = ((DefaultFunctionCallingOptions) updatedRuntimeOptions)
.merge(functionCallingOptions);
}
else if (prompt.getOptions() instanceof ChatOptions) {
var chatOptions = (ChatOptions) prompt.getOptions();
updatedRuntimeOptions = ((PortableFunctionCallingOptions) updatedRuntimeOptions).merge(chatOptions);
updatedRuntimeOptions = ((DefaultFunctionCallingOptions) updatedRuntimeOptions).merge(chatOptions);
}
}
@@ -697,7 +696,7 @@ public class BedrockProxyChatModel extends AbstractToolCallSupport implements Ch
private Duration timeout = Duration.ofMinutes(10);
private FunctionCallingOptions defaultOptions = new FunctionCallingOptionsBuilder().build();
private FunctionCallingOptions defaultOptions = new DefaultFunctionCallingOptions();
private FunctionCallbackResolver functionCallbackResolver;

View File

@@ -372,7 +372,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.options(FunctionCallingOptions.builder().withModel(modelName).build())
.options(FunctionCallingOptions.builder().model(modelName).build())
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))
.call()
@@ -394,7 +394,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
// TODO consider adding model(...) method to ChatClient as a shortcut to
.options(FunctionCallingOptions.builder().withModel(modelName).build())
.options(FunctionCallingOptions.builder().model(modelName).build())
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url))
.call()
.content();

View File

@@ -42,7 +42,7 @@ public class BedrockConverseTestConfiguration {
.withRegion(Region.US_EAST_1)
.withTimeout(Duration.ofSeconds(120))
// .withRegion(Region.US_EAST_1)
.withDefaultOptions(FunctionCallingOptions.builder().withModel(modelId).build())
.withDefaultOptions(FunctionCallingOptions.builder().model(modelId).build())
.build();
}

View File

@@ -41,7 +41,6 @@ import software.amazon.awssdk.services.bedrockruntime.model.ToolUseBlock;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.isA;
@@ -145,7 +144,7 @@ public class BedrockConverseUsageAggregationTests {
.build();
var result = this.chatModel.call(new Prompt("What is the weather in Paris?",
PortableFunctionCallingOptions.builder().withFunctionCallbacks(functionCallback).build()));
FunctionCallingOptions.builder().functionCallbacks(functionCallback).build()));
assertThat(result).isNotNull();
assertThat(result.getResult().getOutput().getText())

View File

@@ -90,7 +90,7 @@ class BedrockProxyChatModelIT {
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource);
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage),
FunctionCallingOptions.builder().withModel(modelName).build());
FunctionCallingOptions.builder().model(modelName).build());
ChatResponse response = this.chatModel.call(prompt);
assertThat(response.getResults()).hasSize(1);
assertThat(response.getMetadata().getUsage().getGenerationTokens()).isGreaterThan(0);
@@ -126,7 +126,7 @@ class BedrockProxyChatModelIT {
@Test
void streamingWithTokenUsage() {
var promptOptions = FunctionCallingOptions.builder().withTemperature(0.0).build();
var promptOptions = FunctionCallingOptions.builder().temperature(0.0).build();
var prompt = new Prompt("List two colors of the Polish flag. Be brief.", promptOptions);
var streamingTokenUsage = this.chatModel.stream(prompt).blockLast().getMetadata().getUsage();
@@ -252,7 +252,7 @@ class BedrockProxyChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
@@ -279,8 +279,8 @@ class BedrockProxyChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = FunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.model("anthropic.claude-3-5-sonnet-20240620-v1:0")
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
@@ -306,7 +306,7 @@ class BedrockProxyChatModelIT {
String model = "anthropic.claude-3-5-sonnet-20240620-v1:0";
// @formatter:off
ChatResponse response = ChatClient.create(this.chatModel).prompt()
.options(FunctionCallingOptions.builder().withModel(model).build())
.options(FunctionCallingOptions.builder().model(model).build())
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
.call()
.chatResponse();
@@ -321,7 +321,7 @@ class BedrockProxyChatModelIT {
String model = "anthropic.claude-3-5-sonnet-20240620-v1:0";
// @formatter:off
ChatResponse response = ChatClient.create(this.chatModel).prompt()
.options(FunctionCallingOptions.builder().withModel(model).build())
.options(FunctionCallingOptions.builder().model(model).build())
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
.stream()
.chatResponse()

View File

@@ -35,7 +35,6 @@ import org.springframework.ai.chat.observation.ChatModelObservationDocumentation
import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.observation.conventions.AiOperationType;
import org.springframework.ai.observation.conventions.AiProvider;
import org.springframework.beans.factory.annotation.Autowired;
@@ -68,13 +67,13 @@ public class BedrockProxyChatModelObservationIT {
@Test
void observationForChatOperation() {
var options = PortableFunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withMaxTokens(2048)
.withStopSequences(List.of("this-is-the-end"))
.withTemperature(0.7)
var options = FunctionCallingOptions.builder()
.model("anthropic.claude-3-5-sonnet-20240620-v1:0")
.maxTokens(2048)
.stopSequences(List.of("this-is-the-end"))
.temperature(0.7)
// .withTopK(1)
.withTopP(1.0)
.topP(1.0)
.build();
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
@@ -90,12 +89,12 @@ public class BedrockProxyChatModelObservationIT {
@Test
void observationForStreamingChatOperation() {
var options = PortableFunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withMaxTokens(2048)
.withStopSequences(List.of("this-is-the-end"))
.withTemperature(0.7)
.withTopP(1.0)
var options = FunctionCallingOptions.builder()
.model("anthropic.claude-3-5-sonnet-20240620-v1:0")
.maxTokens(2048)
.stopSequences(List.of("this-is-the-end"))
.temperature(0.7)
.topP(1.0)
.build();
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
@@ -174,7 +173,7 @@ public class BedrockProxyChatModelObservationIT {
.withCredentialsProvider(EnvironmentVariableCredentialsProvider.create())
.withRegion(Region.US_EAST_1)
.withObservationRegistry(observationRegistry)
.withDefaultOptions(FunctionCallingOptions.builder().withModel(modelId).build())
.withDefaultOptions(FunctionCallingOptions.builder().model(modelId).build())
.build();
}

View File

@@ -184,7 +184,7 @@ public class BedrockNovaChatClientIT {
.withCredentialsProvider(EnvironmentVariableCredentialsProvider.create())
.withRegion(Region.US_EAST_1)
.withTimeout(Duration.ofSeconds(120))
.withDefaultOptions(FunctionCallingOptions.builder().withModel(modelId).build())
.withDefaultOptions(FunctionCallingOptions.builder().model(modelId).build())
.build();
}

View File

@@ -27,7 +27,7 @@ import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
import org.springframework.ai.bedrock.converse.MockWeatherService;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
/**
* Used for reverse engineering the protocol
@@ -50,9 +50,9 @@ public final class BedrockConverseChatModelMain2 {
// "What's the weather like in San Francisco, Tokyo, and Paris? Return the
// temperature in Celsius.",
"What's the weather like in Paris? Return the temperature in Celsius.",
PortableFunctionCallingOptions.builder()
.withModel(modelId)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
FunctionCallingOptions.builder()
.model(modelId)
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)

View File

@@ -25,7 +25,7 @@ import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
import org.springframework.ai.bedrock.converse.MockWeatherService;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
/**
* Used for reverse engineering the protocol
@@ -48,9 +48,9 @@ public final class BedrockConverseChatModelMain3 {
// "What's the weather like in San Francisco, Tokyo, and Paris? Return the
// temperature in Celsius.",
"What's the weather like in Paris? Return the temperature in Celsius.",
PortableFunctionCallingOptions.builder()
.withModel(modelId)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
FunctionCallingOptions.builder()
.model(modelId)
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)

View File

@@ -94,70 +94,70 @@ public interface ChatOptions extends ModelOptions {
* {@link ChatOptions}.
* @return Returns a new {@link ChatOptions.Builder}.
*/
static ChatOptions.Builder builder() {
static ChatOptions.Builder<? extends DefaultChatOptionsBuilder> builder() {
return new DefaultChatOptionsBuilder();
}
/**
* Builder for creating {@link ChatOptions} instance.
*/
interface Builder {
interface Builder<B extends Builder<B>> {
/**
* Builds with the model to use for the chat.
* @param model
* @return the builder
*/
Builder model(String model);
B model(String model);
/**
* Builds with the frequency penalty to use for the chat.
* @param frequencyPenalty
* @return the builder.
*/
Builder frequencyPenalty(Double frequencyPenalty);
B frequencyPenalty(Double frequencyPenalty);
/**
* Builds with the maximum number of tokens to use for the chat.
* @param maxTokens
* @return the builder.
*/
Builder maxTokens(Integer maxTokens);
B maxTokens(Integer maxTokens);
/**
* Builds with the presence penalty to use for the chat.
* @param presencePenalty
* @return the builder.
*/
Builder presencePenalty(Double presencePenalty);
B presencePenalty(Double presencePenalty);
/**
* Builds with the stop sequences to use for the chat.
* @param stopSequences
* @return the builder.
*/
Builder stopSequences(List<String> stopSequences);
B stopSequences(List<String> stopSequences);
/**
* Builds with the temperature to use for the chat.
* @param temperature
* @return the builder.
*/
Builder temperature(Double temperature);
B temperature(Double temperature);
/**
* Builds with the top K to use for the chat.
* @param topK
* @return the builder.
*/
Builder topK(Integer topK);
B topK(Integer topK);
/**
* Builds with the top P to use for the chat.
* @param topP
* @return the builder.
*/
Builder topP(Double topP);
B topP(Double topP);
/**
* Build the {@link ChatOptions}.

View File

@@ -21,48 +21,52 @@ import java.util.List;
/**
* Implementation of {@link ChatOptions.Builder} to create {@link DefaultChatOptions}.
*/
public class DefaultChatOptionsBuilder implements ChatOptions.Builder {
public class DefaultChatOptionsBuilder<T extends DefaultChatOptionsBuilder<T>> implements ChatOptions.Builder<T> {
private final DefaultChatOptions options = new DefaultChatOptions();
public ChatOptions.Builder model(String model) {
protected T self() {
return (T) this;
}
public T model(String model) {
this.options.setModel(model);
return this;
return self();
}
public ChatOptions.Builder frequencyPenalty(Double frequencyPenalty) {
public T frequencyPenalty(Double frequencyPenalty) {
this.options.setFrequencyPenalty(frequencyPenalty);
return this;
return self();
}
public ChatOptions.Builder maxTokens(Integer maxTokens) {
public T maxTokens(Integer maxTokens) {
this.options.setMaxTokens(maxTokens);
return this;
return self();
}
public ChatOptions.Builder presencePenalty(Double presencePenalty) {
public T presencePenalty(Double presencePenalty) {
this.options.setPresencePenalty(presencePenalty);
return this;
return self();
}
public ChatOptions.Builder stopSequences(List<String> stop) {
public T stopSequences(List<String> stop) {
this.options.setStopSequences(stop);
return this;
return self();
}
public ChatOptions.Builder temperature(Double temperature) {
public T temperature(Double temperature) {
this.options.setTemperature(temperature);
return this;
return self();
}
public ChatOptions.Builder topK(Integer topK) {
public T topK(Integer topK) {
this.options.setTopK(topK);
return this;
return self();
}
public ChatOptions.Builder topP(Double topP) {
public T topP(Double topP) {
this.options.setTopP(topP);
return this;
return self();
}
public ChatOptions build() {

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.DefaultChatOptions;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Default implementation of {@link FunctionCallingOptions}.
*
* @author Christian Tzolov
* @author Thomas Vitale
* @author Ilayaperumal Gopinathan
*/
public class DefaultFunctionCallingOptions extends DefaultChatOptions implements FunctionCallingOptions {
private List<FunctionCallback> functionCallbacks = new ArrayList<>();
private Set<String> functions = new HashSet<>();
private Boolean proxyToolCalls = false;
private Map<String, Object> context = new HashMap<>();
@Override
public List<FunctionCallback> getFunctionCallbacks() {
return Collections.unmodifiableList(this.functionCallbacks);
}
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null");
this.functionCallbacks = new ArrayList<>(functionCallbacks);
}
@Override
public Set<String> getFunctions() {
return Collections.unmodifiableSet(this.functions);
}
public void setFunctions(Set<String> functions) {
Assert.notNull(functions, "Functions must not be null");
this.functions = new HashSet<>(functions);
}
@Override
public Boolean getProxyToolCalls() {
return this.proxyToolCalls;
}
public void setProxyToolCalls(Boolean proxyToolCalls) {
this.proxyToolCalls = proxyToolCalls;
}
public Map<String, Object> getToolContext() {
return Collections.unmodifiableMap(this.context);
}
public void setToolContext(Map<String, Object> context) {
Assert.notNull(context, "Context must not be null");
this.context = new HashMap<>(context);
}
@Override
public FunctionCallingOptions copy() {
return FunctionCallingOptions.builder()
.model(this.getModel())
.frequencyPenalty(this.getFrequencyPenalty())
.maxTokens(this.getMaxTokens())
.presencePenalty(this.getPresencePenalty())
.stopSequences(this.getStopSequences() != null ? new ArrayList<>(this.getStopSequences()) : null)
.temperature(this.getTemperature())
.topK(this.getTopK())
.topP(this.getTopP())
.functions(new HashSet<>(this.functions))
.functionCallbacks(new ArrayList<>(this.functionCallbacks))
.proxyToolCalls(this.proxyToolCalls)
.toolContext(new HashMap<>(this.getToolContext()))
.build();
}
public FunctionCallingOptions merge(FunctionCallingOptions options) {
var builder = FunctionCallingOptions.builder()
.model(StringUtils.hasText(options.getModel()) ? options.getModel() : this.getModel())
.frequencyPenalty(
options.getFrequencyPenalty() != null ? options.getFrequencyPenalty() : this.getFrequencyPenalty())
.maxTokens(options.getMaxTokens() != null ? options.getMaxTokens() : this.getMaxTokens())
.presencePenalty(
options.getPresencePenalty() != null ? options.getPresencePenalty() : this.getPresencePenalty())
.stopSequences(options.getStopSequences() != null ? options.getStopSequences() : this.getStopSequences())
.temperature(options.getTemperature() != null ? options.getTemperature() : this.getTemperature())
.topK(options.getTopK() != null ? options.getTopK() : this.getTopK())
.topP(options.getTopP() != null ? options.getTopP() : this.getTopP());
builder.proxyToolCalls(options.getProxyToolCalls() != null ? options.getProxyToolCalls() : this.proxyToolCalls);
Set<String> functions = new HashSet<>();
if (!CollectionUtils.isEmpty(this.functions)) {
functions.addAll(this.functions);
}
if (!CollectionUtils.isEmpty(options.getFunctions())) {
functions.addAll(options.getFunctions());
}
builder.functions(functions);
List<FunctionCallback> functionCallbacks = new ArrayList<>();
if (!CollectionUtils.isEmpty(this.functionCallbacks)) {
functionCallbacks.addAll(this.functionCallbacks);
}
if (!CollectionUtils.isEmpty(options.getFunctionCallbacks())) {
functionCallbacks.addAll(options.getFunctionCallbacks());
}
builder.functionCallbacks(functionCallbacks);
Map<String, Object> context = new HashMap<>();
if (!CollectionUtils.isEmpty(this.context)) {
context.putAll(this.context);
}
if (!CollectionUtils.isEmpty(options.getToolContext())) {
context.putAll(options.getToolContext());
}
builder.toolContext(context);
return builder.build();
}
public FunctionCallingOptions merge(ChatOptions options) {
var builder = FunctionCallingOptions.builder()
.model(StringUtils.hasText(options.getModel()) ? options.getModel() : this.getModel())
.frequencyPenalty(
options.getFrequencyPenalty() != null ? options.getFrequencyPenalty() : this.getFrequencyPenalty())
.maxTokens(options.getMaxTokens() != null ? options.getMaxTokens() : this.getMaxTokens())
.presencePenalty(
options.getPresencePenalty() != null ? options.getPresencePenalty() : this.getPresencePenalty())
.stopSequences(options.getStopSequences() != null ? options.getStopSequences() : this.getStopSequences())
.temperature(options.getTemperature() != null ? options.getTemperature() : this.getTemperature())
.topK(options.getTopK() != null ? options.getTopK() : this.getTopK())
.topP(options.getTopP() != null ? options.getTopP() : this.getTopP());
return builder.build();
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.model.function;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.ai.chat.prompt.DefaultChatOptionsBuilder;
import org.springframework.util.Assert;
/**
* Default implementation of {@link FunctionCallingOptions.Builder}.
*
* @author Christian Tzolov
* @author Thomas Vitale
* @author Ilayaperumal Gopinathan
*/
public class DefaultFunctionCallingOptionsBuilder
extends DefaultChatOptionsBuilder<DefaultFunctionCallingOptionsBuilder>
implements FunctionCallingOptions.Builder<DefaultFunctionCallingOptionsBuilder> {
private final DefaultFunctionCallingOptions functionCallingOptions = new DefaultFunctionCallingOptions();
public DefaultFunctionCallingOptionsBuilder functionCallbacks(List<FunctionCallback> functionCallbacks) {
this.functionCallingOptions.setFunctionCallbacks(functionCallbacks);
return this;
}
public DefaultFunctionCallingOptionsBuilder functionCallbacks(FunctionCallback... functionCallbacks) {
Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null");
this.functionCallingOptions.setFunctionCallbacks(List.of(functionCallbacks));
return this;
}
public DefaultFunctionCallingOptionsBuilder functions(Set<String> functions) {
this.functionCallingOptions.setFunctions(functions);
return this;
}
public DefaultFunctionCallingOptionsBuilder function(String function) {
Assert.notNull(function, "Function must not be null");
var set = new HashSet<>(this.functionCallingOptions.getFunctions());
set.add(function);
this.functionCallingOptions.setFunctions(set);
return this;
}
public DefaultFunctionCallingOptionsBuilder proxyToolCalls(Boolean proxyToolCalls) {
this.functionCallingOptions.setProxyToolCalls(proxyToolCalls);
return this;
}
public DefaultFunctionCallingOptionsBuilder toolContext(Map<String, Object> context) {
Assert.notNull(context, "Tool context must not be null");
Map<String, Object> newContext = new HashMap<>(this.functionCallingOptions.getToolContext());
newContext.putAll(context);
this.functionCallingOptions.setToolContext(newContext);
return this;
}
public DefaultFunctionCallingOptionsBuilder toolContext(String key, Object value) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
Map<String, Object> newContext = new HashMap<>(this.functionCallingOptions.getToolContext());
newContext.put(key, value);
this.functionCallingOptions.setToolContext(newContext);
return this;
}
public FunctionCallingOptions build() {
return this.functionCallingOptions;
}
}

View File

@@ -34,7 +34,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.util.CollectionUtils;
/**
@@ -45,7 +44,7 @@ import org.springframework.util.CollectionUtils;
public class FunctionCallingHelper extends AbstractToolCallSupport {
public FunctionCallingHelper() {
this(null, PortableFunctionCallingOptions.builder().build(), List.of());
this(null, FunctionCallingOptions.builder().build(), List.of());
}
public FunctionCallingHelper(FunctionCallbackResolver functionCallbackResolver,

View File

@@ -27,15 +27,16 @@ import org.springframework.ai.chat.prompt.ChatOptions;
* calling behavior of the ChatModel.
*
* @author Christian Tzolov
* @author Ilayaperumal Gopinathan
*/
public interface FunctionCallingOptions extends ChatOptions {
/**
* @return Returns FunctionCallingOptionsBuilder to create a new instance of
* FunctionCallingOptions.
* @return Returns {@link DefaultFunctionCallingOptionsBuilder} to create a new
* instance of {@link FunctionCallingOptions}.
*/
static FunctionCallingOptionsBuilder builder() {
return new FunctionCallingOptionsBuilder();
static FunctionCallingOptions.Builder<? extends FunctionCallingOptions.Builder> builder() {
return new DefaultFunctionCallingOptionsBuilder();
}
/**
@@ -83,4 +84,67 @@ public interface FunctionCallingOptions extends ChatOptions {
void setToolContext(Map<String, Object> tooContext);
/**
* Builder for creating {@link FunctionCallingOptions} instance.
*/
interface Builder<T extends Builder<T>> extends ChatOptions.Builder<T> {
/**
* The list of Function Callbacks to be registered with the Chat model.
* @param functionCallbacks the list of Function Callbacks.
* @return the FunctionCallOptions Builder.
*/
T functionCallbacks(List<FunctionCallback> functionCallbacks);
/**
* The Function Callbacks to be registered with the Chat model.
* @param functionCallbacks the function callbacks.
* @return the FunctionCallOptions Builder.
*/
T functionCallbacks(FunctionCallback... functionCallbacks);
/**
* {@link Set} of function names to be registered with the Chat model.
* @param functions the {@link Set} of function names
* @return the FunctionCallOptions Builder.
*/
T functions(Set<String> functions);
/**
* The function name to be registered with the chat model.
* @param function the name of the function.
* @return the FunctionCallOptions Builder.
*/
T function(String function);
/**
* Boolean flag to indicate if the proxy ToolCalls is enabled.
* @param proxyToolCalls boolean value to enable proxy ToolCalls.
* @return the FunctionCallOptions Builder.
*/
T proxyToolCalls(Boolean proxyToolCalls);
/**
* Add a {@link Map} of context values into tool context.
* @param context the map representing the tool context.
* @return the FunctionCallOptions Builder.
*/
T toolContext(Map<String, Object> context);
/**
* Add a specific key/value pair to the tool context.
* @param key the key to use.
* @param value the corresponding value.
* @return the FunctionCallOptions Builder.
*/
T toolContext(String key, Object value);
/**
* Builds the {@link FunctionCallingOptions}.
* @return the FunctionCalling options.
*/
FunctionCallingOptions build();
}
}

View File

@@ -34,10 +34,12 @@ import org.springframework.util.StringUtils;
* permits options portability between different AI providers that support
* function-calling.
*
* @deprecated Use {@link FunctionCallingOptions.Builder} instead.
* @author Christian Tzolov
* @author Thomas Vitale
* @since 0.8.1
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public class FunctionCallingOptionsBuilder {
private final PortableFunctionCallingOptions options;
@@ -136,6 +138,10 @@ public class FunctionCallingOptionsBuilder {
return this.options;
}
/**
* @deprecated use {@link DefaultFunctionCallingOptions} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public static class PortableFunctionCallingOptions implements FunctionCallingOptions {
private List<FunctionCallback> functionCallbacks = new ArrayList<>();

View File

@@ -86,11 +86,11 @@ public class ChatBuilderTests {
functionCallbacks.add(cb);
FunctionCallingOptions options = FunctionCallingOptions.builder()
.withFunctionCallbacks(functionCallbacks)
.withFunctions(functions)
.withTopK(topK)
.withTopP(topP)
.withTemperature(temperature)
.functionCallbacks(functionCallbacks)
.functions(functions)
.topK(topK)
.topP(topP)
.temperature(temperature)
.build();
// Callback Functions

View File

@@ -40,10 +40,9 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.DefaultFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.MimeTypeUtils;
@@ -199,7 +198,7 @@ public class ChatClientTest {
@Test
void mutateDefaults() {
PortableFunctionCallingOptions options = new FunctionCallingOptionsBuilder().build();
FunctionCallingOptions options = new DefaultFunctionCallingOptions();
given(this.chatModel.getDefaultOptions()).willReturn(options);
given(this.chatModel.call(this.promptCaptor.capture()))
@@ -331,7 +330,7 @@ public class ChatClientTest {
@Test
void mutatePrompt() {
PortableFunctionCallingOptions options = new FunctionCallingOptionsBuilder().build();
FunctionCallingOptions options = new DefaultFunctionCallingOptions();
given(this.chatModel.getDefaultOptions()).willReturn(options);
given(this.chatModel.call(this.promptCaptor.capture()))

View File

@@ -16,7 +16,7 @@
package org.springframework.ai.autoconfigure.bedrock.converse;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.util.Assert;
@@ -38,10 +38,10 @@ public class BedrockConverseProxyChatProperties {
private boolean enabled = true;
@NestedConfigurationProperty
private PortableFunctionCallingOptions options = PortableFunctionCallingOptions.builder()
.withTemperature(0.7)
.withMaxTokens(300)
.withTopK(10)
private FunctionCallingOptions options = FunctionCallingOptions.builder()
.temperature(0.7)
.maxTokens(300)
.topK(10)
.build();
public boolean isEnabled() {
@@ -52,12 +52,12 @@ public class BedrockConverseProxyChatProperties {
this.enabled = enabled;
}
public PortableFunctionCallingOptions getOptions() {
public FunctionCallingOptions getOptions() {
return this.options;
}
public void setOptions(PortableFunctionCallingOptions options) {
Assert.notNull(options, "PortableFunctionCallingOptions must not be null");
public void setOptions(FunctionCallingOptions options) {
Assert.notNull(options, "FunctionCallingOptions must not be null");
this.options = options;
}

View File

@@ -33,7 +33,7 @@ import org.springframework.ai.autoconfigure.anthropic.tool.MockWeatherService.Re
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -96,7 +96,7 @@ class FunctionCallWithFunctionBeanIT {
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
FunctionCallingOptions.builder().function("weatherFunction").build()));
logger.info("Response: {}", response);

View File

@@ -31,7 +31,7 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -96,7 +96,7 @@ class FunctionCallWithFunctionBeanIT {
"What's the weather like in San Francisco, Paris and in Tokyo? Use Multi-turn function calling.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
FunctionCallingOptions.builder().function("weatherFunction").build()));
logger.info("Response: {}", response);

View File

@@ -33,7 +33,6 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -65,14 +64,14 @@ class FunctionCallWithFunctionBeanIT {
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
FunctionCallingOptions.builder().function("weatherFunction").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
response = chatModel.call(new Prompt(List.of(userMessage),
FunctionCallingOptions.builder().withFunction("weatherFunction3").build()));
FunctionCallingOptions.builder().function("weatherFunction3").build()));
logger.info("Response: {}", response);
@@ -94,7 +93,7 @@ class FunctionCallWithFunctionBeanIT {
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
Flux<ChatResponse> responses = chatModel.stream(new Prompt(List.of(userMessage),
FunctionCallingOptions.builder().withFunction("weatherFunction").build()));
FunctionCallingOptions.builder().function("weatherFunction").build()));
String content = responses.collectList()
.block()

View File

@@ -57,7 +57,7 @@ public class FunctionCallWithPromptFunctionIT {
"What's the weather like in San Francisco, in Paris and in Tokyo? Return the temperature in Celsius.");
var promptOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.functionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.inputType(MockWeatherService.Request.class)

View File

@@ -35,7 +35,6 @@ import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.minimax.MiniMaxChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -98,8 +97,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
UserMessage userMessage = new UserMessage(
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherFunction")
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.function("weatherFunction")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));

View File

@@ -39,7 +39,6 @@ import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -98,8 +97,8 @@ public class WeatherServicePromptIT {
UserMessage userMessage = new UserMessage("What's the weather like in Paris? Use Celsius.");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.functionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MyWeatherService())
.description("Get the current weather in requested location")
.inputType(MyWeatherService.Request.class)

View File

@@ -34,7 +34,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.moonshot.MoonshotChatModel;
import org.springframework.ai.moonshot.MoonshotChatOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -98,8 +97,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
UserMessage userMessage = new UserMessage(
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherFunction")
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.function("weatherFunction")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));

View File

@@ -20,7 +20,6 @@ import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +34,6 @@ import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -120,9 +118,7 @@ public class OllamaFunctionCallbackIT extends BaseOllamaIT {
UserMessage userMessage = new UserMessage(
"What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations.");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("WeatherInfo")
.build();
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder().function("WeatherInfo").build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));

View File

@@ -41,7 +41,6 @@ import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
@@ -156,8 +155,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
UserMessage userMessage = new UserMessage(
"Please schedule a train from San Francisco to Los Angeles on 2023-12-25");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("trainReservation")
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.function("trainReservation")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));
@@ -267,8 +266,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
// Test weatherFunction
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherFunction")
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.function("weatherFunction")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));

View File

@@ -28,7 +28,7 @@ import org.springframework.ai.autoconfigure.vertexai.gemini.VertexAiGeminiAutoCo
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -109,7 +109,7 @@ class FunctionCallWithFunctionBeanIT {
""");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
FunctionCallingOptions.builder().function("weatherFunction").build()));
logger.info("Response: {}", response);

View File

@@ -34,7 +34,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -98,8 +97,8 @@ class FunctionCallbackWithPlainFunctionBeanIT {
UserMessage userMessage = new UserMessage(
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherFunction")
FunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.function("weatherFunction")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));

View File

@@ -89,7 +89,7 @@ class FunctionCallbackResolverKotlinIT : BaseOllamaIT() {
"What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations.")
val functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherInfo")
.function("weatherInfo")
.build()
val response = chatModel.call(Prompt(listOf(userMessage), functionOptions));

View File

@@ -89,7 +89,7 @@ class FunctionCallbackKotlinIT : BaseOllamaIT() {
"What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations.")
val functionOptions = FunctionCallingOptions.builder()
.withFunction("WeatherInfo")
.function("WeatherInfo")
.build()
val response = chatModel.call(Prompt(listOf(userMessage), functionOptions));