Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ToolCallingChatOptions getOptions() {
}

public void setOptions(ToolCallingChatOptions options) {
Assert.notNull(options, "FunctionCallingOptions must not be null");
Assert.notNull(options, "ToolCallingChatOptions must not be null");
this.options = options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,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.FunctionCallback;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.boot.autoconfigure.AutoConfigurations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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.tool.ToolCallingChatOptions;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,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.FunctionCallback;
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.tool.function.FunctionToolCallback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class FunctionCallbackResolverKotlinIT : BaseOllamaIT() {
.withUserConfiguration(Config::class.java)

@Test
fun functionCallTest() {
fun toolCallTest() {
this.contextRunner.run {context ->

val chatModel = context.getBean(OllamaChatModel::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Description


class FunctionCallbackKotlinIT : BaseOllamaIT() {
class ToolCallbackKotlinIT : BaseOllamaIT() {

companion object {

Expand All @@ -46,7 +46,7 @@ class FunctionCallbackKotlinIT : BaseOllamaIT() {
}
}

private val logger = LoggerFactory.getLogger(FunctionCallbackKotlinIT::class.java)
private val logger = LoggerFactory.getLogger(ToolCallbackKotlinIT::class.java)

private val contextRunner = ApplicationContextRunner()
.withPropertyValues(
Expand All @@ -59,7 +59,7 @@ class FunctionCallbackKotlinIT : BaseOllamaIT() {
.withUserConfiguration(Config::class.java)

@Test
fun functionCallTest() {
fun toolCallTest() {
this.contextRunner.run {context ->

val chatModel = context.getBean(OllamaChatModel::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,4 @@ public VertexAiGeminiChatModel vertexAiGeminiChat(VertexAI vertexAi, VertexAiGem
return chatModel;
}

/**
* Because of the OPEN_API_SCHEMA type, the FunctionCallbackResolver instance must
* different from the other JSON schema types.
*/
// private FunctionCallbackResolver springAiFunctionManager(ApplicationContext
// context) {
// DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
// manager.setSchemaType(SchemaType.OPEN_API_SCHEMA);
// manager.setApplicationContext(context);
// return manager;
// }

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
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.tool.ToolCallingChatOptions;
import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions;
Expand Down Expand Up @@ -109,14 +109,14 @@ void functionCallWithPortableFunctionCallingOptions() {
""");

ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
FunctionCallingOptions.builder().function("weatherFunction").build()));
ToolCallingChatOptions.builder().toolNames("weatherFunction").build()));

logger.info("Response: {}", response);

assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");

response = chatModel.call(new Prompt(List.of(userMessage),
VertexAiGeminiChatOptions.builder().function("weatherFunction3").build()));
VertexAiGeminiChatOptions.builder().toolName("weatherFunction3").build()));

logger.info("Response: {}", response);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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.FunctionCallback;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.tool.function.FunctionToolCallback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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.tool.ToolCallingChatOptions;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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.FunctionCallback;
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.tool.ToolCallback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,26 @@ void resolveMultipleFunctionAndToolCallbacks() {
assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class);

assertThat(toolCallbackResolver.resolve("getForecast")).isNotNull();
assertThat(toolCallbackResolver.resolve("getForecast").getName()).isEqualTo("getForecast");
assertThat(toolCallbackResolver.resolve("getForecast").getToolDefinition().name())
.isEqualTo("getForecast");

assertThat(toolCallbackResolver.resolve("getAlert")).isNotNull();
assertThat(toolCallbackResolver.resolve("getAlert").getName()).isEqualTo("getAlert");
assertThat(toolCallbackResolver.resolve("getAlert").getToolDefinition().name()).isEqualTo("getAlert");

assertThat(toolCallbackResolver.resolve("weatherFunction1")).isNotNull();
assertThat(toolCallbackResolver.resolve("weatherFunction1").getName()).isEqualTo("weatherFunction1");
assertThat(toolCallbackResolver.resolve("weatherFunction1").getToolDefinition().name())
.isEqualTo("weatherFunction1");

assertThat(toolCallbackResolver.resolve("getCurrentWeather3")).isNotNull();
assertThat(toolCallbackResolver.resolve("getCurrentWeather3").getName())
assertThat(toolCallbackResolver.resolve("getCurrentWeather3").getToolDefinition().name())
.isEqualTo("getCurrentWeather3");

assertThat(toolCallbackResolver.resolve("getCurrentWeather4")).isNotNull();
assertThat(toolCallbackResolver.resolve("getCurrentWeather4").getName())
assertThat(toolCallbackResolver.resolve("getCurrentWeather4").getToolDefinition().name())
.isEqualTo("getCurrentWeather4");

assertThat(toolCallbackResolver.resolve("getCurrentWeather5")).isNotNull();
assertThat(toolCallbackResolver.resolve("getCurrentWeather5").getName())
assertThat(toolCallbackResolver.resolve("getCurrentWeather5").getToolDefinition().name())
.isEqualTo("getCurrentWeather5");
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ ToolCallingManager toolCallingManager(GenericApplicationContext applicationConte
List<ToolCallbackProvider> tcps, List<ToolCallback> toolCallbacks,
ObjectProvider<ObservationRegistry> observationRegistry) {

List<ToolCallback> allFunctionCallbacks = new ArrayList(toolCallbacks);
tcps.stream().map(pr -> List.of(pr.getToolCallbacks())).forEach(allFunctionCallbacks::addAll);
List<ToolCallback> allToolCallbacks = new ArrayList(toolCallbacks);
tcps.stream().map(pr -> List.of(pr.getToolCallbacks())).forEach(allToolCallbacks::addAll);

var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionCallbacks);
var staticToolCallbackResolver = new StaticToolCallbackResolver(allToolCallbacks);

var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder()
.applicationContext(applicationContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@

import org.junit.jupiter.api.Test;

import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.zhipuai.api.MockWeatherService;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.ZhiPuAiTestConfiguration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ void mutateDefaults() {
var fco = (ToolCallingChatOptions) prompt.getOptions();

assertThat(fco.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2");
assertThat(fco.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(fco.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");

// Streaming
content = join(chatClient.prompt().stream().content());
Expand All @@ -270,7 +270,7 @@ void mutateDefaults() {
fco = (ToolCallingChatOptions) prompt.getOptions();

assertThat(fco.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2");
assertThat(fco.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(fco.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");

// mutate builder
// @formatter:off
Expand Down Expand Up @@ -300,7 +300,7 @@ void mutateDefaults() {
fco = (ToolCallingChatOptions) prompt.getOptions();

assertThat(fco.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2", "fun4");
assertThat(fco.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(fco.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");

// Streaming
content = join(chatClient.prompt().stream().content());
Expand All @@ -322,7 +322,7 @@ void mutateDefaults() {
fco = (ToolCallingChatOptions) prompt.getOptions();

assertThat(fco.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2", "fun4");
assertThat(fco.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(fco.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");

}

Expand Down Expand Up @@ -385,7 +385,7 @@ void mutatePrompt() {
var tco = (ToolCallingChatOptions) prompt.getOptions();

assertThat(tco.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2", "fun5");
assertThat(tco.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(tco.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");

// Streaming
// @formatter:off
Expand Down Expand Up @@ -416,7 +416,7 @@ void mutatePrompt() {
var tcoptions = (ToolCallingChatOptions) prompt.getOptions();

assertThat(tcoptions.getToolNames()).containsExactlyInAnyOrder("fun1", "fun2", "fun5");
assertThat(tcoptions.getToolCallbacks().iterator().next().getName()).isEqualTo("fun3");
assertThat(tcoptions.getToolCallbacks().iterator().next().getToolDefinition().name()).isEqualTo("fun3");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1490,8 +1490,6 @@ void whenToolCallbacksThenReturn() {
assertThat(defaultSpec.getToolCallbacks()).contains(toolCallback);
}

// FunctionCallback.builder().description("description").function(null,input->"hello").inputType(String.class).build()

@Test
void whenFunctionNameIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
Expand Down Expand Up @@ -1543,7 +1541,8 @@ void whenFunctionThenReturn() {
.description("description")
.build());
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolCallbacks()).anyMatch(callback -> callback.getName().equals("name"));
assertThat(defaultSpec.getToolCallbacks())
.anyMatch(callback -> callback.getToolDefinition().name().equals("name"));
}

@Test
Expand All @@ -1555,7 +1554,8 @@ void whenFunctionAndInputTypeThenReturn() {
.description("description")
.build());
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolCallbacks()).anyMatch(callback -> callback.getName().equals("name"));
assertThat(defaultSpec.getToolCallbacks())
.anyMatch(callback -> callback.getToolDefinition().name().equals("name"));
}

@Test
Expand Down Expand Up @@ -1609,7 +1609,8 @@ void whenBiFunctionThenReturn() {
.inputType(String.class)
.build());
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolCallbacks()).anyMatch(callback -> callback.getName().equals("name"));
assertThat(defaultSpec.getToolCallbacks())
.anyMatch(callback -> callback.getToolDefinition().name().equals("name"));
}

@Test
Expand All @@ -1631,7 +1632,7 @@ void whenFunctionBeanNamesThenReturn() {
}

@Test
void whenFunctionCallbacksElementIsNullThenThrow() {
void whenFunctionToolCallbacksElementIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(mock(FunctionToolCallback.class), null))
Expand All @@ -1640,7 +1641,7 @@ void whenFunctionCallbacksElementIsNullThenThrow() {
}

@Test
void whenFunctionCallbacksThenReturn() {
void whenFunctionToolCallbacksThenReturn() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
FunctionToolCallback functionToolCallback = mock(FunctionToolCallback.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void whenFunctionNamesIsNullThenThrows() {
}

@Test
void whenFunctionCallbacksIsNullThenThrows() {
void whenToolCallbacksIsNullThenThrows() {
assertThatThrownBy(() -> new AdvisedRequest(mock(ChatModel.class), "user", null, null, List.of(), List.of(),
null, List.of(), Map.of(), Map.of(), List.of(), Map.of(), Map.of(), Map.of()))
.isInstanceOf(IllegalArgumentException.class)
Expand Down
2 changes: 0 additions & 2 deletions spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
**** xref:api/chat/functions/openai-chat-functions.adoc[OpenAI Function Calling (Deprecated)]
*** xref:api/chat/qianfan-chat.adoc[QianFan]
*** xref:api/chat/zhipuai-chat.adoc[ZhiPu AI]
// **** xref:api/chat/functions/zhipuai-chat-functions.adoc[Function Calling]
*** xref:api/chat/watsonx-ai-chat.adoc[watsonx.AI]
** xref:api/embeddings.adoc[Embedding Models]
*** xref:api/bedrock.adoc[Amazon Bedrock]
Expand Down Expand Up @@ -107,7 +106,6 @@
** xref:api/prompt.adoc[]
** xref:api/chat/prompt-engineering-patterns.adoc[]
* xref:api/testing.adoc[AI Model Evaluation]
* xref:api/functions.adoc[Function Calling (Deprecated)]


* Service Connections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.

Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java[ToolCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.

== How it works

Expand Down Expand Up @@ -72,7 +72,7 @@ We start with describing the most POJO friendly options.

In this approach you define `@Beans` in your application context as you would any other Spring managed object.

Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `ToolCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.


Expand Down Expand Up @@ -117,9 +117,9 @@ It is a best practice to annotate the request object with information such that
The link:https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach.


==== FunctionCallback
==== ToolCallback

Another way to register a function is to create a `FunctionCallback` instance like this:
Another way to register a function is to create a `ToolCallback` instance like this:

[source,java]
----
Expand Down
Loading