Remove deprecated FunctionCallback references
- Remove the deprecated FunctionCallback related classes, references - Replace them with the corresponding ToolCallback usage Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
This commit is contained in:
committed by
Mark Pollack
parent
22c778f8cc
commit
f0702c343b
@@ -44,7 +44,7 @@ public class BedrockConverseProxyChatProperties {
|
||||
}
|
||||
|
||||
public void setOptions(ToolCallingChatOptions options) {
|
||||
Assert.notNull(options, "FunctionCallingOptions must not be null");
|
||||
Assert.notNull(options, "ToolCallingChatOptions must not be null");
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.ai.chat.model.Generation;
|
||||
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;
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.ai.chat.model.Generation;
|
||||
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;
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.ai.chat.model.Generation;
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Description
|
||||
|
||||
|
||||
class FunctionCallbackKotlinIT : BaseOllamaIT() {
|
||||
class ToolCallbackKotlinIT : BaseOllamaIT() {
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -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(
|
||||
@@ -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)
|
||||
@@ -113,16 +113,4 @@ public class VertexAiGeminiChatAutoConfiguration {
|
||||
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;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
@@ -109,7 +109,7 @@ class FunctionCallWithFunctionBeanIT {
|
||||
""");
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
FunctionCallingOptions.builder().function("weatherFunction").build()));
|
||||
ToolCallingChatOptions.builder().toolNames("weatherFunction").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
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;
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
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;
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
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;
|
||||
|
||||
@@ -75,24 +75,26 @@ class ToolCallingAutoConfigurationTests {
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,10 +223,10 @@ public class VertexAiGeminiPaymentTransactionMethodIT {
|
||||
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)
|
||||
|
||||
@@ -20,9 +20,7 @@ import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.springframework.ai.content.Media;
|
||||
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;
|
||||
|
||||
@@ -248,7 +248,7 @@ public class ChatClientTest {
|
||||
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());
|
||||
@@ -270,7 +270,7 @@ public class ChatClientTest {
|
||||
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
|
||||
@@ -300,7 +300,7 @@ public class ChatClientTest {
|
||||
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());
|
||||
@@ -322,7 +322,7 @@ public class ChatClientTest {
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ public class ChatClientTest {
|
||||
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
|
||||
@@ -416,7 +416,7 @@ public class ChatClientTest {
|
||||
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
|
||||
|
||||
@@ -1491,8 +1491,6 @@ class DefaultChatClientTests {
|
||||
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();
|
||||
@@ -1544,7 +1542,8 @@ class DefaultChatClientTests {
|
||||
.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
|
||||
@@ -1556,7 +1555,8 @@ class DefaultChatClientTests {
|
||||
.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
|
||||
@@ -1610,7 +1610,8 @@ class DefaultChatClientTests {
|
||||
.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
|
||||
@@ -1632,7 +1633,7 @@ class DefaultChatClientTests {
|
||||
}
|
||||
|
||||
@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))
|
||||
@@ -1641,7 +1642,7 @@ class DefaultChatClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFunctionCallbacksThenReturn() {
|
||||
void whenFunctionToolCallbacksThenReturn() {
|
||||
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
|
||||
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
|
||||
FunctionToolCallback functionToolCallback = mock(FunctionToolCallback.class);
|
||||
|
||||
@@ -93,7 +93,7 @@ class AdvisedRequestTests {
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
@@ -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]
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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]
|
||||
----
|
||||
|
||||
@@ -19,7 +19,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
|
||||
|
||||
@@ -70,7 +70,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` instance that adds the logic for it being invoked via the AI model.
|
||||
Internally, Spring AI `ChatModel` will create an instance of a `ToolCallback` instance that adds the logic for it being invoked via the AI model.
|
||||
The name of the `@Bean` is passed as a `ChatOption`.
|
||||
|
||||
|
||||
@@ -113,9 +113,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/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach.
|
||||
|
||||
==== FunctionCallback Wrapper
|
||||
==== ToolCallback Wrapper
|
||||
|
||||
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]
|
||||
----
|
||||
|
||||
@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
|
||||
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.
|
||||
|
||||
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
|
||||
|
||||
@@ -71,7 +71,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` instance that adds the logic for it being invoked via the AI model.
|
||||
Internally, Spring AI `ChatModel` will create an instance of a `ToolCallback` instance that adds the logic for it being invoked via the AI model.
|
||||
The name of the `@Bean` is passed as a `ChatOption`.
|
||||
|
||||
|
||||
@@ -115,9 +115,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-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java[FunctionCallbackWithPlainFunctionBeanIT.java] demonstrates this approach.
|
||||
|
||||
|
||||
==== FunctionCallback Wrapper
|
||||
==== ToolCallback Wrapper
|
||||
|
||||
Another way register a function is to create `FunctionCallback` instance like this:
|
||||
Another way register a function is to create `ToolCallback` instance like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -125,9 +125,9 @@ Another way register a function is to create `FunctionCallback` instance like th
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public FunctionCallback weatherFunctionInfo() {
|
||||
public FunctionToolCallback weatherFunctionInfo() {
|
||||
|
||||
return FunctionCallback.builder()
|
||||
return FunctionToolCallback.builder()
|
||||
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) function signature
|
||||
@@ -142,7 +142,7 @@ It also provides a description (2) and the function signature (3) to let the mod
|
||||
|
||||
NOTE: By default, the response converter does a JSON serialization of the Response object.
|
||||
|
||||
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
NOTE: The `FunctionToolCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
|
||||
=== Specifying functions in Chat Options
|
||||
|
||||
@@ -185,8 +185,7 @@ MiniMaxChatModel chatModel = ...
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
|
||||
var promptOptions = MiniMaxChatOptions.builder()
|
||||
.functionCallbacks(List.of(FunctionCallback.builder()
|
||||
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.toolCallbacks(List.of(FunctionToolCallback.builder("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) function signature
|
||||
.build())) // function code
|
||||
|
||||
@@ -18,7 +18,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
|
||||
|
||||
@@ -70,7 +70,7 @@ We start by describing the most POJO-friendly options.
|
||||
|
||||
In this approach, you define a `@Bean` 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`.
|
||||
|
||||
[source,java]
|
||||
@@ -115,7 +115,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/auto-configurati
|
||||
TIP: The Mistral AI link:https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java[PaymentStatusBeanOpenAiIT] implements the same function using the OpenAI API.
|
||||
Mistral AI is almost identical to OpenAI in this regard.
|
||||
|
||||
==== FunctionCallback Wrapper
|
||||
==== ToolCallback Wrapper
|
||||
|
||||
Another way to register a function is to create a `FunctionToolCallback` like this:
|
||||
|
||||
@@ -141,7 +141,7 @@ It also provides a description (2) and the function signature (3) to let the mod
|
||||
|
||||
NOTE: By default, the response converter performs a JSON serialization of the Response object.
|
||||
|
||||
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
NOTE: The `FunctionToolCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
|
||||
=== Specifying functions in Chat Options
|
||||
|
||||
|
||||
@@ -23,7 +23,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
|
||||
|
||||
@@ -78,7 +78,7 @@ We start by describing the most POJO-friendly options.
|
||||
|
||||
In this approach, you define a `@Bean` 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`.
|
||||
|
||||
[source,java]
|
||||
@@ -117,9 +117,9 @@ public record Request(String location, Unit unit) {}
|
||||
|
||||
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
|
||||
|
||||
==== FunctionCallback
|
||||
==== ToolCallback
|
||||
|
||||
Another way to register a function is to create a `FunctionCallback` like this:
|
||||
Another way to register a function is to create a `ToolCallback` like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@@ -16,7 +16,7 @@ As a developer, you need to implement a function that takes the function call ar
|
||||
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.
|
||||
|
||||
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
|
||||
|
||||
@@ -71,7 +71,7 @@ We start by describing the most POJO-friendly options.
|
||||
|
||||
In this approach, you define a `@Bean` 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`.
|
||||
|
||||
[source,java]
|
||||
@@ -112,9 +112,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-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java[FunctionCallbackWithPlainFunctionBeanIT.java] demonstrates this approach.
|
||||
|
||||
==== FunctionCallback Wrapper
|
||||
==== FunctionToolCallback Wrapper
|
||||
|
||||
Another way to register a function is to create a `FunctionCallback` like this:
|
||||
Another way to register a function is to create a `toolCallback` like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
|
||||
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.
|
||||
|
||||
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
|
||||
|
||||
@@ -71,7 +71,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` instance that adds the logic for it being invoked via the AI model.
|
||||
Internally, Spring AI `ChatModel` will create an instance of a `ToolCallback` instance that adds the logic for it being invoked via the AI model.
|
||||
The name of the `@Bean` is passed as a `ChatOption`.
|
||||
|
||||
|
||||
@@ -115,9 +115,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-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java[FunctionCallbackWithPlainFunctionBeanIT.java] demonstrates this approach.
|
||||
|
||||
|
||||
==== FunctionCallback Wrapper
|
||||
==== FunctionToolCallback Wrapper
|
||||
|
||||
Another way register a function is to create `FunctionCallback` instance like this:
|
||||
Another way register a function is to create `ToolCallback` instance like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -125,10 +125,9 @@ Another way register a function is to create `FunctionCallback` instance like th
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public FunctionCallback weatherFunctionInfo() {
|
||||
public FunctionToolCallback weatherFunctionInfo() {
|
||||
|
||||
return FunctionCallback.builder()
|
||||
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
return FunctionToolCallback.builder("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) function signature
|
||||
.build();
|
||||
@@ -142,7 +141,7 @@ It also provides a description (2) and the input type (3) used to generate the J
|
||||
|
||||
NOTE: By default, the response converter does a JSON serialization of the Response object.
|
||||
|
||||
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
NOTE: The `FunctionToolCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
|
||||
=== Specifying functions in Chat Options
|
||||
|
||||
@@ -185,8 +184,7 @@ ZhiPuAiChatModel chatModel = ...
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
|
||||
var promptOptions = ZhiPuAiChatOptions.builder()
|
||||
.functionCallbacks(List.of(FunctionCallback.builder()
|
||||
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.toolCallbacks(List.of(FunctionToolCallback.builder("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) function signature
|
||||
.build())) // function code
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
= FunctionCallback
|
||||
|
||||
WARNING: This page describes the previous version of the Function Calling API, which has been deprecated and marked for remove in the next release. The current version is available at xref:api/tools.adoc[Tool Calling]. See the xref:api/tools-migration.adoc[Migration Guide] for more information.
|
||||
|
||||
The `FunctionCallback` interface in Spring AI provides a standardized way to implement Large Language Model (LLM) function calling capabilities. It allows developers to register custom functions that can be called by AI models when specific conditions or intents are detected in the prompts.
|
||||
|
||||
The `FunctionCallback` interface defines several key methods:
|
||||
|
||||
* `getName()`: Returns the unique function name within the AI model context
|
||||
* `getDescription()`: Provides a description that helps the model decide when to invoke the function
|
||||
* `getInputTypeSchema()`: Defines the JSON schema for the function's input parameters
|
||||
* `call(String functionArguments)`: Handles the actual function execution
|
||||
* `call(String functionArguments, ToolContext toolContext)`: Extended version that supports additional xref:api/functions.adoc#Tool-Context[tool context]
|
||||
|
||||
== Builder Pattern
|
||||
|
||||
Spring AI provides a fluent builder API for creating `FunctionCallback` implementations.
|
||||
This is particularly useful for defining function callbacks that you can register, pragmatically, on the fly, with your `ChatClient` or `ChatModel` model calls.
|
||||
|
||||
Use the `FunctionCallback.builder()` method to create a new builder instance and chain the configuration methods to set the function name, description, input type, and other properties.
|
||||
The `FunctionCallback.Builder` is a hierarchical with the following structure:
|
||||
|
||||
- FunctionCallback.Builder - The root builder interface used for configuring the xref:_common_configurations[shared properties].
|
||||
- FunctionInvokingSpec - The xref:Function-Invoking[function invoking] builder interface.
|
||||
- MethodInvokingSpec - The xref:Method-Invoking[method invoking] builder interface.
|
||||
|
||||
[[Function-Invoking]]
|
||||
== Function-Invoking Approach
|
||||
|
||||
Converts any `java.util.function.Function`, `BiFunction`, `Supplier` or `Consumer` into a `FunctionCallback` that can be called by the AI model.
|
||||
|
||||
NOTE: You can use lambda expressions or method references to define the function logic but you must provide the input type of the function using the `inputType(TYPE)`.
|
||||
|
||||
=== Function<I, O>
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback callback = FunctionCallback.builder()
|
||||
.function("processOrder", (Order order) -> processOrderLogic(order))
|
||||
.description("Process a new order")
|
||||
.inputType(Order.class)
|
||||
.build();
|
||||
----
|
||||
|
||||
=== BiFunction<I, ToolContext, O>
|
||||
|
||||
Using Function with input type <I> and additional xref:api/functions.adoc#Tool-Context[ToolContext] parameter:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback callback = FunctionCallback.builder()
|
||||
.function("processOrder", (Order order, ToolContext context) ->
|
||||
processOrderWithContext(order, context))
|
||||
.description("Process a new order with context")
|
||||
.inputType(Order.class)
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Supplier<O>
|
||||
|
||||
Use `java.util.Supplier<O>` or `java.util.function.Function<Void, O>` to define functions that don't take any input:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback.builder()
|
||||
.function("turnsLight", () -> state.put("Light", "ON"))
|
||||
.description("Turns light on in the living room")
|
||||
.inputType(Void.class)
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Consumer<I>
|
||||
|
||||
Use `java.util.Consumer<I>` or `java.util.function.Function<I, Void>` to define functions that don't produce output:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
record LightInfo(String roomName, boolean isOn) {}
|
||||
|
||||
FunctionCallback.builder()
|
||||
.function("turnsLight", (LightInfo lightInfo) -> {
|
||||
logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName());
|
||||
})
|
||||
.description("Turns light on/off in a selected room")
|
||||
.inputType(LightInfo.class)
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Generics Input Type
|
||||
|
||||
Use the `ParameterizedTypeReference` to define functions with generic input types:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
record TrainSearchRequest<T>(T data) {}
|
||||
|
||||
record TrainSearchSchedule(String from, String to, String date) {}
|
||||
|
||||
record TrainSearchScheduleResponse(String from, String to, String date, String trainNumber) {}
|
||||
|
||||
FunctionCallback.builder()
|
||||
.function("trainSchedule", (TrainSearchRequest<TrainSearchSchedule> request) -> {
|
||||
logger.info("Schedule: " + request.data().from() + " to " + request.data().to());
|
||||
return new TrainSearchScheduleResponse(request.data().from(), request. data().to(), "", "123");
|
||||
})
|
||||
.description("Schedule a train reservation")
|
||||
.inputType(new ParameterizedTypeReference<TrainSearchRequest<TrainSearchSchedule>>() {})
|
||||
.build();
|
||||
----
|
||||
|
||||
[[Method-Invoking]]
|
||||
== Method Invoking Approach
|
||||
|
||||
Enables method invocation through reflection while automatically handling JSON schema generation and parameter conversion. It’s particularly useful for integrating Java methods as callable functions within AI model interactions.
|
||||
|
||||
The method invoking implements the `FunctionCallback` interface and provides:
|
||||
|
||||
- Automatic JSON schema generation for method parameters
|
||||
- Support for both static and instance methods
|
||||
- Any number of parameters (including none) and return values (including void)
|
||||
- Any parameter/return types (primitives, objects, collections)
|
||||
- Special handling for xref:api/functions.adoc#Tool-Context[ToolContext] parameters
|
||||
|
||||
=== Static Method Invocation
|
||||
|
||||
You can refer to a static method in a class by providing the method name, parameter types, and the target class.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class WeatherService {
|
||||
public static String getWeather(String city, TemperatureUnit unit) {
|
||||
return "Temperature in " + city + ": 20" + unit;
|
||||
}
|
||||
}
|
||||
|
||||
FunctionCallback callback = FunctionCallback.builder()
|
||||
.method("getWeather", String.class, TemperatureUnit.class)
|
||||
.description("Get weather information for a city")
|
||||
.targetClass(WeatherService.class)
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Object instance Method Invocation
|
||||
|
||||
You can refer to an instance method in a class by providing the method name, parameter types, and the target object instance.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class DeviceController {
|
||||
public void setDeviceState(String deviceId, boolean state, ToolContext context) {
|
||||
Map<String, Object> contextData = context.getContext();
|
||||
// Implementation using context data
|
||||
}
|
||||
}
|
||||
|
||||
DeviceController controller = new DeviceController();
|
||||
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
.user("Turn on the living room lights")
|
||||
.functions(FunctionCallback.builder()
|
||||
.method("setDeviceState", String.class,boolean.class,ToolContext.class)
|
||||
.description("Control device state")
|
||||
.targetObject(controller)
|
||||
.build())
|
||||
.toolContext(Map.of("location", "home"))
|
||||
.call()
|
||||
.content();
|
||||
----
|
||||
|
||||
TIP: Optionally, using the `.name()`, you can set a custom function name different from the method name.
|
||||
|
||||
== Common Configurations
|
||||
|
||||
There are several common configurations that you can use to customize the function callbacks.
|
||||
|
||||
=== Schema Type
|
||||
|
||||
The framework supports different schema types for generating the schemas for the input parameters:
|
||||
|
||||
* JSON Schema (default)
|
||||
* OpenAPI Schema (for Vertex AI compatibility)
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback.builder()
|
||||
.schemaType(SchemaType.OPEN_API_SCHEMA)
|
||||
// ... other configuration
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Custom Response Handling
|
||||
|
||||
You can provide a custom response converter to format the function response before sending it back to the AI model.
|
||||
Most AI Models expect an text response, so it is your responsibility to convert the function response to a text format.
|
||||
By default, the response is converted to a String.
|
||||
|
||||
TIP: Many models cope well with JSON responses, so you can return a JSON string.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback.builder()
|
||||
.responseConverter(response ->
|
||||
customResponseFormatter.format(response))
|
||||
// ... other configuration
|
||||
.build();
|
||||
----
|
||||
|
||||
=== Custom Object Mapping
|
||||
|
||||
Spring AI uses ObjectMapper for JSON serialization and deserialization.
|
||||
You can provide a custom ObjectMapper to handle custom object mapping:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
FunctionCallback.builder()
|
||||
.objectMapper(customObjectMapper)
|
||||
// ... other configuration
|
||||
.build();
|
||||
----
|
||||
|
||||
== Best Practices
|
||||
|
||||
=== Descriptive Names and Descriptions
|
||||
|
||||
* Provide unique function names
|
||||
* Write comprehensive descriptions to help the model understand when to invoke the function
|
||||
|
||||
=== Input Type & Schema
|
||||
|
||||
* For the function invoking approach, define input types explicitly and use `ParameterizedTypeReference` for generic types.
|
||||
* Consider using custom schema when auto-generated ones don't meet requirements.
|
||||
|
||||
=== Error Handling
|
||||
|
||||
* Implement proper error handling in function implementations and return the error message in the response
|
||||
* You can use the ToolContext to provide additional error context when needed
|
||||
|
||||
=== Tool Context Usage
|
||||
|
||||
* Use xref:api/functions.adoc#Tool-Context[ToolContext] when additional state or context is required that is provided from the User and not part of the function input generated by the AI model.
|
||||
* Use `BiFunction<I, ToolContext, O>` to access the ToolContext in the function invocation approach and add `ToolContext` parameter in the method invoking approach.
|
||||
|
||||
=== Notes on Schema Generation
|
||||
|
||||
* The framework automatically generates JSON schemas from Java types
|
||||
* For function invoking, the schema is generated based on the input type for the function that needs to be set using `inputType(TYPE)`. Use `ParameterizedTypeReference` for generic types.
|
||||
* Generated schemas respect Jackson annotations on model classes
|
||||
* You can bypass the automatic generation by providing custom schemas using `inputTypeSchema()`
|
||||
|
||||
=== Common Pitfalls to Avoid
|
||||
|
||||
==== Lack of Description
|
||||
* Always provide explicit descriptions instead of relying on auto-generated ones
|
||||
* Clear descriptions improve model's function selection accuracy
|
||||
|
||||
==== Schema Mismatches
|
||||
* Ensure input types match the Function's input parameter types.
|
||||
* Use `ParameterizedTypeReference` for generic types.
|
||||
@@ -1,470 +0,0 @@
|
||||
[[Function]]
|
||||
= Function Calling API
|
||||
|
||||
WARNING: This page describes the previous version of the Function Calling API, which has been deprecated and marked for remove in the next release. The current version is available at xref:api/tools.adoc[Tool Calling]. See the xref:api/tools-migration.adoc[Migration Guide] for more information.
|
||||
|
||||
The integration of function support in AI models, permits the model to request the execution of client-side functions, thereby accessing necessary information or performing tasks dynamically as required.
|
||||
|
||||
Spring AI currently supports tool/function calling for the following AI Models:
|
||||
|
||||
* Anthropic Claude: Refer to the xref:api/chat/functions/anthropic-chat-functions.adoc[Anthropic Claude tool/function calling].
|
||||
* Azure OpenAI: Refer to the xref:api/chat/functions/azure-open-ai-chat-functions.adoc[Azure OpenAI tool/function calling].
|
||||
* Amazon Bedrock Converse: Refer to the xref:api/chat/bedrock-converse.adoc#_tool_calling[Amazon Bedrock Converse tool/function calling].
|
||||
* Google VertexAI Gemini: Refer to the xref:api/chat/vertexai-gemini-chat.adoc#_tool_calling[Vertex AI Gemini tool/function calling].
|
||||
* Groq: Refer to the xref:api/chat/groq-chat.adoc#_function_calling[Groq tool/function calling].
|
||||
* Mistral AI: Refer to the xref:api/chat/functions/mistralai-chat-functions.adoc[Mistral AI tool/function calling].
|
||||
// * MiniMax : Refer to the xref:api/chat/functions/minimax-chat-functions.adoc[MiniMax function invocation docs].
|
||||
* Ollama: Refer to the xref:api/chat/functions/ollama-chat-functions.adoc[Ollama tool/function calling]
|
||||
* OpenAI: Refer to the xref:api/chat/functions/openai-chat-functions.adoc[OpenAI tool/function calling].
|
||||
// * ZhiPu AI : Refer to the xref:api/chat/functions/zhipuai-chat-functions.adoc[ZhiPu AI function invocation docs].
|
||||
|
||||
image::function-calling-basic-flow.jpg[Function calling, width=700, align="center"]
|
||||
|
||||
You can register custom Java functions with the `ChatClient` and have the AI model intelligently choose to output a JSON object containing arguments to call one or many of the registered functions.
|
||||
This allows you to connect the LLM capabilities with external tools and APIs.
|
||||
The AI models are trained to detect when a function should be called and to respond with JSON that adheres to the function signature.
|
||||
|
||||
The API does not call the function directly; instead, the model generates JSON that you can use to call the function in your code and return the result back to the model to complete the conversation.
|
||||
|
||||
Spring AI provides flexible and user-friendly ways to register and call custom functions.
|
||||
In general, the custom functions need to provide a function `name`, `description`, and the function call `signature` (as JSON schema) to let the model know what arguments the function expects. The `description` helps the model to understand when to call the function.
|
||||
|
||||
As a developer, you need to implement a function that takes the function call arguments sent from the AI model, and responds with the result back to the model. 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 `ChatClient` or registering the function dynamically in your prompt request.
|
||||
|
||||
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-core/src/main/java/org/springframework/ai/tool/function/FunctionToolCallback.java[FunctionToolCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
|
||||
|
||||
== How it works
|
||||
|
||||
Suppose we want the AI model to respond with information that it does not have, for example, the current temperature at a given location.
|
||||
|
||||
We can provide the AI model with metadata about our own functions that it can use to retrieve that information as it processes your prompt.
|
||||
|
||||
For example, if during the processing of a prompt, the AI Model determines that it needs additional information about the temperature in a given location, it will start a server-side generated request/response interaction.
|
||||
Instead of returning the final response message, the AI Model returns at special Tool Call request, providing the function name and arguments (as JSON).
|
||||
It is the responsibility of the client to process this message and execute the named function and return the response
|
||||
as Tool Response message back to the AI Model.
|
||||
|
||||
Spring AI greatly simplifies the code you need to write to support function invocation.
|
||||
It brokers the function invocation conversation for you.
|
||||
You can simply provide your function definition as a `@Bean` and then provide the bean name of the function in your prompt options or pass the function directly as a parameter in your prompt request options.
|
||||
|
||||
You can also reference multiple function bean names in your prompt.
|
||||
|
||||
=== Example Use Case
|
||||
|
||||
Lets define a simple use case that we can use as an example to explain how function invocation works.
|
||||
Let's create a chatbot that answer questions by calling our own function.
|
||||
To support the response of the chatbot, we will register our own function that takes a location and returns the current weather in that location.
|
||||
|
||||
When the model needs to answer a question such as `"What’s the weather like in Boston?"` the AI model will invoke the client providing the location value as an argument to be passed to the function. This RPC-like data is passed as JSON.
|
||||
|
||||
Our function calls some SaaS-based weather service API and returns the weather response back to the model to complete the conversation. In this example, we will use a simple implementation named `MockWeatherService` that hard-codes the temperature for various locations.
|
||||
|
||||
The following `MockWeatherService` class represents the weather service API:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
public class MockWeatherService implements Function<Request, Response> {
|
||||
|
||||
public enum Unit { C, F }
|
||||
public record Request(String location, Unit unit) {}
|
||||
public record Response(double temp, Unit unit) {}
|
||||
|
||||
public Response apply(Request request) {
|
||||
return new Response(30.0, Unit.C);
|
||||
}
|
||||
}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
class MockWeatherService : Function1<Request, Response> {
|
||||
override fun invoke(request: Request) = Response(30.0, Unit.C)
|
||||
}
|
||||
|
||||
enum class Unit { C, F }
|
||||
data class Request(val location: String, val unit: Unit) {}
|
||||
data class Response(val temp: Double, val unit: Unit) {}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
== Server-Side Registration
|
||||
|
||||
=== Functions as Beans
|
||||
|
||||
Spring AI provides multiple ways to register custom functions as beans in the Spring context.
|
||||
|
||||
We start by describing the most POJO-friendly options.
|
||||
|
||||
==== Plain Functions
|
||||
|
||||
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
|
||||
|
||||
Internally, Spring AI `ChatModel` will create an instance of a `FunctionToolCallback` that adds the logic for it being invoked via the AI model.
|
||||
The name of the `@Bean` is used function name.
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
@Description("Get the weather in location") // function description
|
||||
public Function<MockWeatherService.Request, MockWeatherService.Response> currentWeather() {
|
||||
return new MockWeatherService();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
@Configuration
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
@Description("Get the weather in location") // function description
|
||||
fun currentWeather(): (Request) -> Response = MockWeatherService()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
The `@Description` annotation is optional and provides a function description that helps the model understand when to call the function. It is an important property to set to help the AI model determine what client side function to invoke.
|
||||
|
||||
Another option for providing the description of the function is to use the `@JsonClassDescription` annotation on the `MockWeatherService.Request`:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Function<Request, Response> currentWeather() { // bean name as function name
|
||||
return new MockWeatherService();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClassDescription("Get the weather in location") // function description
|
||||
public record Request(String location, Unit unit) {}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
@Configuration
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
fun currentWeather(): (Request) -> Response { // bean name as function name
|
||||
return MockWeatherService()
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClassDescription("Get the weather in location") // function description
|
||||
data class Request(val location: String, val unit: Unit)
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
|
||||
|
||||
==== FunctionToolCallback
|
||||
|
||||
Another way to register a function is to create a `FunctionToolCallback` like this:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public FunctionToolCallback weatherFunctionInfo() {
|
||||
|
||||
return FunctionToolCallback.builder("CurrentWeather", new MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.ai.model.function.withInputType
|
||||
|
||||
@Configuration
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
fun weatherFunctionInfo(): FunctionToolCallback {
|
||||
|
||||
return FunctionToolCallback.builder("CurrentWeather", MockWeatherService()) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
// (3) Required due to Kotlin SAM conversion being an opaque lambda
|
||||
.inputType<MockWeatherService.Request>()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `ChatClient`.
|
||||
It also provides a description (2) and an optional response converter to convert the response into a text as expected by the model.
|
||||
|
||||
NOTE: By default, the response converter performs a JSON serialization of the Response object.
|
||||
|
||||
NOTE: The `FunctionToolCallback.Builder` internally resolves the function call signature based on the `MockWeatherService.Request` class.
|
||||
|
||||
=== Enable functions by bean name
|
||||
|
||||
To let the model know and call your `CurrentWeather` function you need to enable it in your prompt requests:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ChatClient chatClient = ...
|
||||
|
||||
ChatResponse response = this.chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.tools("CurrentWeather") // Enable the function
|
||||
.call().
|
||||
chatResponse();
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
----
|
||||
|
||||
The above user question will trigger 3 calls to the `CurrentWeather` function (one for each city) and the final response will be something like this:
|
||||
|
||||
----
|
||||
Here is the current weather for the requested cities:
|
||||
- San Francisco, CA: 30.0°C
|
||||
- Tokyo, Japan: 10.0°C
|
||||
- Paris, France: 15.0°C
|
||||
----
|
||||
|
||||
The link:https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java[FunctionCallbackWithPlainFunctionBeanIT.java] test demo this approach.
|
||||
|
||||
== Client-Side Registration
|
||||
|
||||
In addition to the auto-configuration, you can register callback functions, dynamically.
|
||||
You can use either the function invoking or method invoking approaches to register functions with your `ChatClient` or `ChatModel` requests.
|
||||
|
||||
The client-side registration enables you to register functions by default.
|
||||
|
||||
=== Function Invoking
|
||||
|
||||
[source,java]
|
||||
----
|
||||
ChatClient chatClient = ...
|
||||
|
||||
ChatResponse response = this.chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.tools(FunctionToolCallback.builder("currentWeather", (Request request) -> new Response(30.0, Unit.C)) // (1) function name and instance
|
||||
.description("Get the weather in location") // (2) function description
|
||||
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
|
||||
.build())
|
||||
.call()
|
||||
.chatResponse();
|
||||
----
|
||||
|
||||
NOTE: The on the fly functions are enabled by default for the duration of this request.
|
||||
|
||||
This approach allows to choose dynamically different functions to be called based on the user input.
|
||||
|
||||
The https://github.com/spring-projects/spring-ai/blob/main/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `ChatClient` and use it in a prompt request.
|
||||
|
||||
=== Method Invoking
|
||||
|
||||
The `MethodInvokingFunctionCallback` enables method invocation through reflection while automatically handling JSON schema generation and parameter conversion.
|
||||
It's particularly useful for integrating Java methods as callable functions within AI model interactions.
|
||||
|
||||
The `MethodInvokingFunctionCallback` implements the `FunctionCallback` interface and provides:
|
||||
|
||||
- Automatic JSON schema generation for method parameters
|
||||
- Support for both static and instance methods
|
||||
- Any number of parameters (including none) and return values (including void)
|
||||
- Any parameter/return types (primitives, objects, collections)
|
||||
- Special handling for `ToolContext` parameters
|
||||
|
||||
You need the `MethodToolCallback.Builder` to create `MethodInvokingFunctionCallback` like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// Create using builder pattern
|
||||
FunctionCallback methodInvokingCallback = FunctionCallback.builder()
|
||||
.method("MethodName", Class<?>...argumentTypes) // The method to invoke and its argument types
|
||||
.description("Function calling description") // Hints the AI to know when to call this method
|
||||
.targetObject(targetObject) // Required instance methods for static methods use targetClass
|
||||
.build();
|
||||
----
|
||||
|
||||
Here are a few usage examples:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Static Method Invocation::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
public class WeatherService {
|
||||
public static String getWeather(String city, TemperatureUnit unit) {
|
||||
return "Temperature in " + city + ": 20" + unit;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
var toolMethod = ReflectionUtils.findMethod(WeatherService.class, "getWeather", String.class, TemperatureUnit.class);
|
||||
MethodToolCallback callback = MethodToolCallback.builder()
|
||||
.toolDefinition(ToolDefinition.builder(toolMethod)
|
||||
.description("Get the weather in location")
|
||||
.build())
|
||||
.toolMethod(toolMethod)
|
||||
.toolObject(targetObject)
|
||||
.build();
|
||||
----
|
||||
Instance Method with ToolContext::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
public class DeviceController {
|
||||
public void setDeviceState(String deviceId, boolean state, ToolContext context) {
|
||||
Map<String, Object> contextData = context.getContext();
|
||||
// Implementation using context data
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
DeviceController controller = new DeviceController();
|
||||
var toolMethod = ReflectionUtils.findMethod(
|
||||
DeviceController.class, "setDeviceState", String.class, Boolean.class, ToolContext.class);
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
.user("Turn on the living room lights")
|
||||
.tools(MethodToolCallback.builder()
|
||||
.toolDefinition(ToolDefinition.builder(toolMethod)
|
||||
.description("Control device state")
|
||||
.build())
|
||||
.toolMethod(toolMethod)
|
||||
.toolObject(controller)
|
||||
.build())
|
||||
.toolContext(Map.of("location", "home"))
|
||||
.call()
|
||||
.content();
|
||||
----
|
||||
======
|
||||
|
||||
The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java[OpenAiChatClientMethodInvokingFunctionCallbackIT]
|
||||
integration test provides additional examples of how to use the FunctionCallback.Builder to create method invocation FunctionCallbacks.
|
||||
|
||||
[[Tool-Context]]
|
||||
== Tool Context
|
||||
|
||||
Spring AI now supports passing additional contextual information to function callbacks through a tool context.
|
||||
This feature allows you to provide extra, user provided, data that can be used within the function execution along with the function arguments passed by the AI model.
|
||||
|
||||
image::function-calling-tool-context.jpg[Function calling with Tool Context, width=700, align="center"]
|
||||
|
||||
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContext.java[ToolContext] class provides a way to pass additional context information.
|
||||
|
||||
=== Using Tool Context
|
||||
|
||||
In case of function-invoking, the context information that is passed in as the second argument of a `java.util.BiFunction`.
|
||||
|
||||
For method-invoking, the context information is passed as a method argument of type `ToolContext`.
|
||||
|
||||
==== Function Invoking
|
||||
|
||||
You can set the tool context when building your chat options and use a BiFunction for your callback:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
BiFunction<MockWeatherService.Request, ToolContext, MockWeatherService.Response> weatherFunction =
|
||||
(request, toolContext) -> {
|
||||
String sessionId = (String) toolContext.getContext().get("sessionId");
|
||||
String userId = (String) toolContext.getContext().get("userId");
|
||||
|
||||
// Use sessionId and userId in your function logic
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new MockWeatherService.Response(temperature, 15, 20, 2, 53, 45, MockWeatherService.Unit.C);
|
||||
};
|
||||
|
||||
|
||||
ChatResponse response = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
|
||||
.tools(FunctionToolCallback.builder("getCurrentWeather", this.weatherFunction)
|
||||
.description("Get the weather in location")
|
||||
.inputType(MockWeatherService.Request.class)
|
||||
.build())
|
||||
.toolContext(Map.of("sessionId", "1234", "userId", "5678"))
|
||||
.call()
|
||||
.chatResponse();
|
||||
----
|
||||
|
||||
In this example, the `weatherFunction` is defined as a BiFunction that takes both the request and the tool context as parameters. This allows you to access the context directly within the function logic.
|
||||
|
||||
This approach allows you to pass session-specific or user-specific information to your functions, enabling more contextual and personalized responses.
|
||||
|
||||
==== Method Invoking
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class DeviceController {
|
||||
public void setDeviceState(String deviceId, boolean state, ToolContext context) {
|
||||
Map<String, Object> contextData = context.getContext();
|
||||
// Implementation using context data
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
DeviceController controller = new DeviceController();
|
||||
var toolMethod = ReflectionUtils.findMethod(
|
||||
DeviceController.class, "setDeviceState", String.class, Boolean.class, ToolContext.class);
|
||||
String response = ChatClient.create(chatModel).prompt()
|
||||
.user("Turn on the living room lights")
|
||||
.tools(MethodToolCallback.builder()
|
||||
.toolDefinition(ToolDefinition.builder(toolMethod)
|
||||
.description("Control device state")
|
||||
.build())
|
||||
.toolMethod(toolMethod)
|
||||
.toolObject(controller)
|
||||
.build())
|
||||
.toolContext(Map.of("location", "home"))
|
||||
.call()
|
||||
.content();
|
||||
----
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.ai.aot;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ai.chat.messages.AbstractMessage;
|
||||
@@ -26,15 +25,13 @@ import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.ToolResponseMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@@ -42,18 +39,16 @@ public class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar {
|
||||
public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
|
||||
var chatTypes = Set.of(AbstractMessage.class, AssistantMessage.class, ToolResponseMessage.class, Message.class,
|
||||
MessageType.class, UserMessage.class, SystemMessage.class, DefaultFunctionCallbackResolver.class,
|
||||
FunctionCallback.class);
|
||||
MessageType.class, UserMessage.class, SystemMessage.class);
|
||||
for (var c : chatTypes) {
|
||||
hints.reflection().registerType(c);
|
||||
}
|
||||
|
||||
Method getDescription = ReflectionUtils.findMethod(FunctionCallback.class, "getDescription");
|
||||
hints.reflection().registerMethod(getDescription, ExecutableMode.INVOKE);
|
||||
Method getInputTypeSchema = ReflectionUtils.findMethod(FunctionCallback.class, "getInputTypeSchema");
|
||||
hints.reflection().registerMethod(getInputTypeSchema, ExecutableMode.INVOKE);
|
||||
Method getName = ReflectionUtils.findMethod(FunctionCallback.class, "getName");
|
||||
hints.reflection().registerMethod(getName, ExecutableMode.INVOKE);
|
||||
// Register tool-related types for reflection
|
||||
var toolTypes = Set.of(ToolCallback.class, ToolDefinition.class);
|
||||
for (var c : toolTypes) {
|
||||
hints.reflection().registerType(c);
|
||||
}
|
||||
|
||||
for (var r : Set.of("embedding/embedding-model-dimensions.properties")) {
|
||||
hints.resources().registerResource(new ClassPathResource(r));
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.chat.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.ToolResponseMessage;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
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.tool.ToolCallingManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for tool call support. Provides functionality for handling function
|
||||
* callbacks and executing functions.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Grogdunn
|
||||
* @author Thomas Vitale
|
||||
* @author Jihoon Kim
|
||||
* @since 1.0.0
|
||||
* @deprecated Use {@link ToolCallingManager} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractToolCallSupport {
|
||||
|
||||
protected static final boolean IS_RUNTIME_CALL = true;
|
||||
|
||||
/**
|
||||
* The function callback register is used to resolve the function callbacks by name.
|
||||
*/
|
||||
protected final Map<String, FunctionCallback> functionCallbackRegister = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* The function callback resolver is used to resolve the function callbacks by name
|
||||
* from the Spring context. It is optional and usually used with Spring
|
||||
* auto-configuration.
|
||||
*/
|
||||
protected final FunctionCallbackResolver functionCallbackResolver;
|
||||
|
||||
@Deprecated
|
||||
protected AbstractToolCallSupport(FunctionCallbackResolver functionCallbackResolver) {
|
||||
this(functionCallbackResolver, FunctionCallingOptions.builder().build(), List.of());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected AbstractToolCallSupport(FunctionCallbackResolver functionCallbackResolver,
|
||||
FunctionCallingOptions functionCallingOptions, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
|
||||
this.functionCallbackResolver = functionCallbackResolver;
|
||||
|
||||
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 functionOptions,
|
||||
List<FunctionCallback> toolFunctionCallbacks) {
|
||||
List<FunctionCallback> toolFunctionCallbacksCopy = new ArrayList<>();
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
toolFunctionCallbacksCopy.addAll(toolFunctionCallbacks);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(functionOptions.getFunctionCallbacks())) {
|
||||
toolFunctionCallbacksCopy.addAll(functionOptions.getFunctionCallbacks());
|
||||
// Make sure that that function callbacks are registered directly to the
|
||||
// functionCallbackRegister and not passed in the default options.
|
||||
functionOptions.setFunctionCallbacks(List.of());
|
||||
}
|
||||
return toolFunctionCallbacksCopy;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Map<String, FunctionCallback> getFunctionCallbackRegister() {
|
||||
return this.functionCallbackRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the runtime function callback configurations. Register the function
|
||||
* callbacks
|
||||
* @param runtimeFunctionOptions FunctionCallingOptions to handle.
|
||||
* @return Set of function names to call.
|
||||
*/
|
||||
@Deprecated
|
||||
protected Set<String> runtimeFunctionCallbackConfigurations(FunctionCallingOptions runtimeFunctionOptions) {
|
||||
|
||||
Set<String> enabledFunctionsToCall = new HashSet<>();
|
||||
|
||||
if (runtimeFunctionOptions != null) {
|
||||
// Add the explicitly enabled functions.
|
||||
if (!CollectionUtils.isEmpty(runtimeFunctionOptions.getFunctions())) {
|
||||
enabledFunctionsToCall.addAll(runtimeFunctionOptions.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 enabledFunctionsToCall;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected List<Message> handleToolCalls(Prompt prompt, ChatResponse response) {
|
||||
Optional<Generation> toolCallGeneration = response.getResults()
|
||||
.stream()
|
||||
.filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()))
|
||||
.findFirst();
|
||||
if (toolCallGeneration.isEmpty()) {
|
||||
throw new IllegalStateException("No tool call generation found in the response!");
|
||||
}
|
||||
AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();
|
||||
|
||||
Map<String, Object> toolContextMap = Map.of();
|
||||
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallOptions
|
||||
&& !CollectionUtils.isEmpty(functionCallOptions.getToolContext())) {
|
||||
|
||||
toolContextMap = new HashMap<>(functionCallOptions.getToolContext());
|
||||
|
||||
List<Message> toolCallHistory = new ArrayList<>(prompt.copy().getInstructions());
|
||||
toolCallHistory.add(new AssistantMessage(assistantMessage.getText(), assistantMessage.getMetadata(),
|
||||
assistantMessage.getToolCalls()));
|
||||
|
||||
toolContextMap.put(ToolContext.TOOL_CALL_HISTORY, toolCallHistory);
|
||||
}
|
||||
|
||||
ToolResponseMessage toolMessageResponse = this.executeFunctions(assistantMessage,
|
||||
new ToolContext(toolContextMap));
|
||||
|
||||
List<Message> toolConversationHistory = this.buildToolCallConversation(prompt.getInstructions(),
|
||||
assistantMessage, toolMessageResponse);
|
||||
|
||||
return toolConversationHistory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected List<Message> buildToolCallConversation(List<Message> previousMessages, AssistantMessage assistantMessage,
|
||||
ToolResponseMessage toolResponseMessage) {
|
||||
List<Message> messages = new ArrayList<>(previousMessages);
|
||||
messages.add(assistantMessage);
|
||||
messages.add(toolResponseMessage);
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the function callbacks by name. Retrieve them from the registry or try to
|
||||
* resolve them from the Application Context.
|
||||
* @param functionNames Name of function callbacks to retrieve.
|
||||
* @return list of resolved FunctionCallbacks.
|
||||
*/
|
||||
@Deprecated
|
||||
protected List<FunctionCallback> resolveFunctionCallbacks(Set<String> functionNames) {
|
||||
|
||||
List<FunctionCallback> retrievedFunctionCallbacks = new ArrayList<>();
|
||||
|
||||
for (String functionName : functionNames) {
|
||||
if (!this.functionCallbackRegister.containsKey(functionName)) {
|
||||
|
||||
if (this.functionCallbackResolver != null) {
|
||||
FunctionCallback functionCallback = this.functionCallbackResolver.resolve(functionName);
|
||||
if (functionCallback != null) {
|
||||
this.functionCallbackRegister.put(functionName, functionCallback);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"No function callback [" + functionName + "] found in tht FunctionCallbackRegister");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No function callback found for name: " + functionName);
|
||||
}
|
||||
}
|
||||
FunctionCallback functionCallback = this.functionCallbackRegister.get(functionName);
|
||||
|
||||
retrievedFunctionCallbacks.add(functionCallback);
|
||||
}
|
||||
|
||||
return retrievedFunctionCallbacks;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected ToolResponseMessage executeFunctions(AssistantMessage assistantMessage, ToolContext toolContext) {
|
||||
|
||||
List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();
|
||||
|
||||
for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
|
||||
|
||||
var functionName = toolCall.name();
|
||||
String functionArguments = toolCall.arguments();
|
||||
|
||||
if (!this.functionCallbackRegister.containsKey(functionName)) {
|
||||
throw new IllegalStateException("No function callback found for function name: " + functionName);
|
||||
}
|
||||
|
||||
String functionResponse = this.functionCallbackRegister.get(functionName)
|
||||
.call(functionArguments, toolContext);
|
||||
|
||||
toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), functionName, functionResponse));
|
||||
}
|
||||
|
||||
return new ToolResponseMessage(toolResponses, Map.of());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected boolean isToolCall(ChatResponse chatResponse, Set<String> toolCallFinishReasons) {
|
||||
Assert.isTrue(!CollectionUtils.isEmpty(toolCallFinishReasons), "Tool call finish reasons cannot be empty!");
|
||||
|
||||
if (chatResponse == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var generations = chatResponse.getResults();
|
||||
if (CollectionUtils.isEmpty(generations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return generations.stream().anyMatch(g -> isToolCall(g, toolCallFinishReasons));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the generation is a tool call. The tool call finish reasons are used to
|
||||
* determine if the generation is a tool call.
|
||||
* @param generation the generation to check.
|
||||
* @param toolCallFinishReasons the tool call finish reasons to check.
|
||||
* @return true if the generation is a tool call, false otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean isToolCall(Generation generation, Set<String> toolCallFinishReasons) {
|
||||
var finishReason = (generation.getMetadata().getFinishReason() != null)
|
||||
? generation.getMetadata().getFinishReason() : "";
|
||||
return generation.getOutput().hasToolCalls() && toolCallFinishReasons.stream()
|
||||
.map(s -> s.toLowerCase())
|
||||
.toList()
|
||||
.contains(finishReason.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the proxyToolCalls is enabled for the given prompt or the default tool
|
||||
* call options. The prompt options take precedence over the default options. When the
|
||||
* proxyToolCalls is enabled the ChatModel implementation will not handle the function
|
||||
* calling internally. The tool call and tool response messages are exposed outside
|
||||
* the ChatModel implementation.
|
||||
* @param prompt the prompt to check.
|
||||
* @param defaultOptions the default tool call options to check.
|
||||
* @return true if the proxyToolCalls is enabled, false otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean isProxyToolCalls(Prompt prompt, FunctionCallingOptions defaultOptions) {
|
||||
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallOptions
|
||||
&& functionCallOptions.getProxyToolCalls() != null) {
|
||||
return functionCallOptions.getProxyToolCalls();
|
||||
}
|
||||
else if (defaultOptions.getProxyToolCalls() != null) {
|
||||
return defaultOptions.getProxyToolCalls();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import org.springframework.ai.chat.messages.Message;
|
||||
*
|
||||
* <p>
|
||||
* The context is typically populated from the {@code toolContext} field of
|
||||
* {@code FunctionCallingOptions} and is used in the function execution process.
|
||||
* {@code ToolCallingChatOptions} and is used in the function execution process.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.lang.reflect.Type;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract implementation of the {@link FunctionCallback} for interacting with the
|
||||
* Model's function calling protocol and a {@link Function} wrapping the interaction with
|
||||
* the 3rd party service/function.
|
||||
*
|
||||
* Implement the {@code O apply(I request) } method to implement the interaction with the
|
||||
* 3rd party service/function.
|
||||
*
|
||||
* The {@link #responseConverter} function is responsible to convert the 3rd party
|
||||
* function's output type into a string expected by the LLM model.
|
||||
*
|
||||
* @param <I> the 3rd party service input type.
|
||||
* @param <O> the 3rd party service output type.
|
||||
* @author Christian Tzolov
|
||||
* @deprecated in favor of {@link FunctionToolCallback}.
|
||||
*/
|
||||
@Deprecated
|
||||
abstract class AbstractFunctionCallback<I, O> implements BiFunction<I, ToolContext, O>, FunctionCallback {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final Type inputType;
|
||||
|
||||
private final String inputTypeSchema;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final Function<O, String> responseConverter;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link AbstractFunctionCallback} with the given name, description,
|
||||
* input type and default object mapper.
|
||||
* @param name Function name. Should be unique within the ChatModel's function
|
||||
* registry.
|
||||
* @param description Function description. Used as a "system prompt" by the model to
|
||||
* decide if the function should be called.
|
||||
* @param inputTypeSchema Used to compute, the argument's Schema (such as JSON Schema
|
||||
* or OpenAPI Schema)required by the Model's function calling protocol.
|
||||
* @param inputType Used to compute, the argument's JSON schema required by the
|
||||
* Model's function calling protocol.
|
||||
* @param responseConverter Used to convert the function's output type to a string.
|
||||
* @param objectMapper Used to convert the function's input and output types to and
|
||||
* from JSON.
|
||||
*/
|
||||
protected AbstractFunctionCallback(String name, String description, String inputTypeSchema, Type inputType,
|
||||
Function<O, String> responseConverter, ObjectMapper objectMapper) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(description, "Description must not be null");
|
||||
Assert.notNull(inputType, "InputType must not be null");
|
||||
Assert.notNull(inputTypeSchema, "InputTypeSchema must not be null");
|
||||
Assert.notNull(responseConverter, "ResponseConverter must not be null");
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.inputType = inputType;
|
||||
this.inputTypeSchema = inputTypeSchema;
|
||||
this.responseConverter = responseConverter;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputTypeSchema() {
|
||||
return this.inputTypeSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String functionInput, ToolContext toolContext) {
|
||||
I request = fromJson(functionInput, this.inputType);
|
||||
O response = this.apply(request, toolContext);
|
||||
return this.responseConverter.apply(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String functionArguments) {
|
||||
// Convert the tool calls JSON arguments into a Java function request object.
|
||||
I request = fromJson(functionArguments, this.inputType);
|
||||
// extend conversation with function response.
|
||||
return this.andThen(this.responseConverter).apply(request, null);
|
||||
}
|
||||
|
||||
private <T> T fromJson(String json, Type targetType) {
|
||||
try {
|
||||
return this.objectMapper.readValue(json, this.objectMapper.constructType(targetType));
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.name, this.description, this.inputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractFunctionCallback other = (AbstractFunctionCallback) obj;
|
||||
|
||||
return Objects.equals(this.name, other.name) && Objects.equals(this.description, other.description)
|
||||
&& Objects.equals(this.inputType, other.inputType);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024-2025 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.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallback.CommonCallbackInvokingSpec;
|
||||
import org.springframework.ai.model.function.FunctionCallback.SchemaType;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.ai.tool.method.MethodToolCallback;
|
||||
import org.springframework.ai.util.JacksonUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @deprecated Use specific builder for the type of tool you need, e.g.
|
||||
* {@link FunctionToolCallback.Builder} and {@link MethodToolCallback.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultCommonCallbackInvokingSpec<B extends CommonCallbackInvokingSpec<B>>
|
||||
implements CommonCallbackInvokingSpec<B> {
|
||||
|
||||
/**
|
||||
* The description of the function callback. Used to hint the LLM model about the
|
||||
* tool's purpose and when to use it.
|
||||
*/
|
||||
protected String description;
|
||||
|
||||
/**
|
||||
* The schema type to use for the input type schema generation. The default is JSON
|
||||
* Schema. Note: Vertex AI requires the input type schema to be in Open API schema
|
||||
*/
|
||||
protected SchemaType schemaType = SchemaType.JSON_SCHEMA;
|
||||
|
||||
/**
|
||||
* The function to convert the response object to a string. The default is to convert
|
||||
* the response to a JSON string.
|
||||
*/
|
||||
protected Function<Object, String> responseConverter = response -> (response instanceof String) ? "" + response
|
||||
: this.toJsonString(response);
|
||||
|
||||
/**
|
||||
* (Optional) Instead of generating the input type schema from the input type or
|
||||
* method argument types, you can provide the schema directly. This will override the
|
||||
* generated schema.
|
||||
*/
|
||||
protected String inputTypeSchema;
|
||||
|
||||
protected ObjectMapper objectMapper = JsonMapper.builder()
|
||||
.addModules(JacksonUtils.instantiateAvailableModules())
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
||||
.build();
|
||||
|
||||
private String toJsonString(Object object) {
|
||||
try {
|
||||
return this.objectMapper.writeValueAsString(object);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public B description(String description) {
|
||||
Assert.hasText(description, "Description must not be empty");
|
||||
this.description = description;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B schemaType(SchemaType schemaType) {
|
||||
Assert.notNull(schemaType, "SchemaType must not be null");
|
||||
this.schemaType = schemaType;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B responseConverter(Function<Object, String> responseConverter) {
|
||||
Assert.notNull(responseConverter, "ResponseConverter must not be null");
|
||||
this.responseConverter = responseConverter;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B inputTypeSchema(String inputTypeSchema) {
|
||||
Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
|
||||
this.inputTypeSchema = inputTypeSchema;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B objectMapper(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public SchemaType getSchemaType() {
|
||||
return this.schemaType;
|
||||
}
|
||||
|
||||
public Function<Object, String> getResponseConverter() {
|
||||
return this.responseConverter;
|
||||
}
|
||||
|
||||
public String getInputTypeSchema() {
|
||||
return this.inputTypeSchema;
|
||||
}
|
||||
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback.FunctionInvokingSpec;
|
||||
import org.springframework.ai.model.function.FunctionCallback.MethodInvokingSpec;
|
||||
import org.springframework.ai.model.function.FunctionCallback.SchemaType;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.ai.tool.method.MethodToolCallback;
|
||||
import org.springframework.ai.util.ParsingUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link FunctionCallback.Builder}.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
* @deprecated Use specific builder for the type of tool you need, e.g.
|
||||
* {@link FunctionToolCallback.Builder} and {@link MethodToolCallback.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultFunctionCallbackBuilder implements FunctionCallback.Builder {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultFunctionCallbackBuilder.class);
|
||||
|
||||
@Override
|
||||
public <I, O> FunctionInvokingSpec<I, O> function(String name, Function<I, O> function) {
|
||||
return new DefaultFunctionInvokingSpec<>(name, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I, O> FunctionInvokingSpec<I, O> function(String name, BiFunction<I, ToolContext, O> biFunction) {
|
||||
return new DefaultFunctionInvokingSpec<>(name, biFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> FunctionInvokingSpec<Void, O> function(String name, Supplier<O> supplier) {
|
||||
Function<Void, O> function = input -> supplier.get();
|
||||
return new DefaultFunctionInvokingSpec<>(name, function).inputType(Void.class);
|
||||
}
|
||||
|
||||
public <I> FunctionInvokingSpec<I, Void> function(String name, Consumer<I> consumer) {
|
||||
Function<I, Void> function = (I input) -> {
|
||||
consumer.accept(input);
|
||||
return null;
|
||||
};
|
||||
return new DefaultFunctionInvokingSpec<>(name, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInvokingSpec method(String methodName, Class<?>... argumentTypes) {
|
||||
return new DefaultMethodInvokingSpec(methodName, argumentTypes);
|
||||
}
|
||||
|
||||
private String generateDescription(String fromName) {
|
||||
|
||||
String generatedDescription = ParsingUtils.reConcatenateCamelCase(fromName, " ");
|
||||
|
||||
logger.info("Description is not set! A best effort attempt to generate a description:'{}' from the:'{}'",
|
||||
generatedDescription, fromName);
|
||||
logger.info("It is recommended to set the Description explicitly! Use the 'description()' method!");
|
||||
|
||||
return generatedDescription;
|
||||
}
|
||||
|
||||
final class DefaultFunctionInvokingSpec<I, O> extends DefaultCommonCallbackInvokingSpec<FunctionInvokingSpec<I, O>>
|
||||
implements FunctionInvokingSpec<I, O> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private Type inputType;
|
||||
|
||||
private final BiFunction<I, ToolContext, O> biFunction;
|
||||
|
||||
private final Function<I, O> function;
|
||||
|
||||
private DefaultFunctionInvokingSpec(String name, BiFunction<I, ToolContext, O> biFunction) {
|
||||
Assert.hasText(name, "Name must not be empty");
|
||||
Assert.notNull(biFunction, "BiFunction must not be null");
|
||||
this.name = name;
|
||||
this.biFunction = biFunction;
|
||||
this.function = null;
|
||||
}
|
||||
|
||||
private DefaultFunctionInvokingSpec(String name, Function<I, O> function) {
|
||||
Assert.hasText(name, "Name must not be empty");
|
||||
Assert.notNull(function, "Function must not be null");
|
||||
this.name = name;
|
||||
this.biFunction = null;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionInvokingSpec<I, O> inputType(Class<?> inputType) {
|
||||
Assert.notNull(inputType, "InputType must not be null");
|
||||
this.inputType = inputType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionInvokingSpec<I, O> inputType(ParameterizedTypeReference<?> inputType) {
|
||||
Assert.notNull(inputType, "InputType must not be null");
|
||||
this.inputType = inputType.getType();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallback build() {
|
||||
|
||||
Assert.notNull(this.getObjectMapper(), "ObjectMapper must not be null");
|
||||
Assert.hasText(this.name, "Name must not be empty");
|
||||
Assert.notNull(this.getResponseConverter(), "ResponseConverter must not be null");
|
||||
Assert.notNull(this.inputType, "InputType must not be null");
|
||||
|
||||
if (this.getInputTypeSchema() == null) {
|
||||
boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA;
|
||||
this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
|
||||
}
|
||||
|
||||
BiFunction<I, ToolContext, O> finalBiFunction = (this.biFunction != null) ? this.biFunction
|
||||
: (request, context) -> this.function.apply(request);
|
||||
|
||||
return new FunctionInvokingFunctionCallback<>(this.name, this.getDescriptionExt(),
|
||||
this.getInputTypeSchema(), this.inputType, (Function<O, String>) this.getResponseConverter(),
|
||||
this.getObjectMapper(), finalBiFunction);
|
||||
}
|
||||
|
||||
private String getDescriptionExt() {
|
||||
if (StringUtils.hasText(this.getDescription())) {
|
||||
return this.getDescription();
|
||||
}
|
||||
return generateDescription(this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class DefaultMethodInvokingSpec extends DefaultCommonCallbackInvokingSpec<MethodInvokingSpec>
|
||||
implements MethodInvokingSpec {
|
||||
|
||||
private String name;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
private Class<?> targetClass;
|
||||
|
||||
private Object targetObject;
|
||||
|
||||
private final Class<?>[] argumentTypes;
|
||||
|
||||
private DefaultMethodInvokingSpec(String methodName, Class<?>... argumentTypes) {
|
||||
Assert.hasText(methodName, "Method name must not be null");
|
||||
Assert.notNull(argumentTypes, "Argument types must not be null");
|
||||
this.methodName = methodName;
|
||||
this.argumentTypes = argumentTypes;
|
||||
}
|
||||
|
||||
public MethodInvokingSpec name(String name) {
|
||||
Assert.hasText(name, "Name must not be empty");
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MethodInvokingSpec targetClass(Class<?> targetClass) {
|
||||
Assert.notNull(targetClass, "Target class must not be null");
|
||||
this.targetClass = targetClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInvokingSpec targetObject(Object methodObject) {
|
||||
Assert.notNull(methodObject, "Method object must not be null");
|
||||
this.targetObject = methodObject;
|
||||
this.targetClass = methodObject.getClass();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallback build() {
|
||||
Assert.isTrue(this.targetClass != null || this.targetObject != null,
|
||||
"Target class or object must not be null");
|
||||
var method = ReflectionUtils.findMethod(this.targetClass, this.methodName, this.argumentTypes);
|
||||
Assert.notNull(method, "Method: '" + this.methodName + "' with arguments:"
|
||||
+ Arrays.toString(this.argumentTypes) + " not found!");
|
||||
return new MethodInvokingFunctionCallback(this.targetObject, method, this.getDescriptionExt(),
|
||||
this.getObjectMapper(), this.name, this.getResponseConverter());
|
||||
}
|
||||
|
||||
private String getDescriptionExt() {
|
||||
if (StringUtils.hasText(this.getDescription())) {
|
||||
return this.getDescription();
|
||||
}
|
||||
|
||||
return generateDescription(StringUtils.hasText(this.name) ? this.name : this.methodName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-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.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
||||
import kotlin.jvm.functions.Function0;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import kotlin.jvm.functions.Function2;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.model.function.FunctionCallback.SchemaType;
|
||||
import org.springframework.ai.tool.resolution.SpringBeanToolCallbackResolver;
|
||||
import org.springframework.ai.tool.resolution.TypeResolverHelper;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A Spring {@link ApplicationContextAware} implementation that provides a way to retrieve
|
||||
* a {@link Function} from the Spring context and wrap it into a {@link FunctionCallback}.
|
||||
* <p>
|
||||
* The name of the function is determined by the bean name.
|
||||
* <p>
|
||||
* The description of the function is determined by the following rules:
|
||||
* <ul>
|
||||
* <li>Provided as a default description</li>
|
||||
* <li>Provided as a {@code @Description} annotation on the bean</li>
|
||||
* <li>Provided as a {@code @JsonClassDescription} annotation on the input class</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Christopher Smith
|
||||
* @author Sebastien Deleuze
|
||||
* @deprecated Use {@link SpringBeanToolCallbackResolver} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultFunctionCallbackResolver implements ApplicationContextAware, FunctionCallbackResolver {
|
||||
|
||||
private GenericApplicationContext applicationContext;
|
||||
|
||||
private SchemaType schemaType = SchemaType.JSON_SCHEMA;
|
||||
|
||||
public void setSchemaType(SchemaType schemaType) {
|
||||
this.schemaType = schemaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = (GenericApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallback resolve(@NonNull String beanName) {
|
||||
ResolvableType functionType = TypeResolverHelper.resolveBeanType(this.applicationContext, beanName);
|
||||
ResolvableType functionInputType = (ResolvableType.forType(Supplier.class).isAssignableFrom(functionType))
|
||||
? ResolvableType.forType(Void.class) : TypeResolverHelper.getFunctionArgumentType(functionType, 0);
|
||||
|
||||
String functionDescription = resolveFunctionDescription(beanName, functionInputType.toClass());
|
||||
Object bean = this.applicationContext.getBean(beanName);
|
||||
|
||||
return buildFunctionCallback(beanName, functionType, functionInputType, functionDescription, bean);
|
||||
}
|
||||
|
||||
private String resolveFunctionDescription(String beanName, Class<?> functionInputClass) {
|
||||
String functionDescription = "";
|
||||
|
||||
if (!StringUtils.hasText(functionDescription)) {
|
||||
Description descriptionAnnotation = this.applicationContext.findAnnotationOnBean(beanName,
|
||||
Description.class);
|
||||
if (descriptionAnnotation != null) {
|
||||
functionDescription = descriptionAnnotation.value();
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(functionDescription)) {
|
||||
JsonClassDescription jsonClassDescriptionAnnotation = functionInputClass
|
||||
.getAnnotation(JsonClassDescription.class);
|
||||
if (jsonClassDescriptionAnnotation != null) {
|
||||
functionDescription = jsonClassDescriptionAnnotation.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(functionDescription)) {
|
||||
throw new IllegalStateException("Could not determine function description. "
|
||||
+ "Please provide a description either as a default parameter, via @Description annotation on the bean "
|
||||
+ "or @JsonClassDescription annotation on the input class.");
|
||||
}
|
||||
}
|
||||
|
||||
return functionDescription;
|
||||
}
|
||||
|
||||
private FunctionCallback buildFunctionCallback(String beanName, ResolvableType functionType,
|
||||
ResolvableType functionInputType, String functionDescription, Object bean) {
|
||||
|
||||
if (KotlinDetector.isKotlinPresent()) {
|
||||
if (KotlinDelegate.isKotlinFunction(functionType.toClass())) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, KotlinDelegate.wrapKotlinFunction(bean))
|
||||
.schemaType(this.schemaType)
|
||||
.description(functionDescription)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
if (KotlinDelegate.isKotlinBiFunction(functionType.toClass())) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, KotlinDelegate.wrapKotlinBiFunction(bean))
|
||||
.description(functionDescription)
|
||||
.schemaType(this.schemaType)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
if (KotlinDelegate.isKotlinSupplier(functionType.toClass())) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, KotlinDelegate.wrapKotlinSupplier(bean))
|
||||
.description(functionDescription)
|
||||
.schemaType(this.schemaType)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
if (bean instanceof Function<?, ?> function) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, function)
|
||||
.schemaType(this.schemaType)
|
||||
.description(functionDescription)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
if (bean instanceof BiFunction<?, ?, ?>) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, (BiFunction<?, ToolContext, ?>) bean)
|
||||
.description(functionDescription)
|
||||
.schemaType(this.schemaType)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
if (bean instanceof Supplier<?> supplier) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, supplier)
|
||||
.description(functionDescription)
|
||||
.schemaType(this.schemaType)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
if (bean instanceof Consumer<?> consumer) {
|
||||
return FunctionCallback.builder()
|
||||
.function(beanName, consumer)
|
||||
.description(functionDescription)
|
||||
.schemaType(this.schemaType)
|
||||
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
|
||||
.build();
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unsupported function type");
|
||||
}
|
||||
|
||||
private static final class KotlinDelegate {
|
||||
|
||||
public static boolean isKotlinSupplier(Class<?> clazz) {
|
||||
return Function0.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Supplier<?> wrapKotlinSupplier(Object function) {
|
||||
return () -> ((Function0<Object>) function).invoke();
|
||||
}
|
||||
|
||||
public static boolean isKotlinFunction(Class<?> clazz) {
|
||||
return Function1.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Function<?, ?> wrapKotlinFunction(Object function) {
|
||||
return t -> ((Function1<Object, Object>) function).invoke(t);
|
||||
}
|
||||
|
||||
public static boolean isKotlinBiFunction(Class<?> clazz) {
|
||||
return Function2.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BiFunction<?, ToolContext, ?> wrapKotlinBiFunction(Object function) {
|
||||
return (t, u) -> ((Function2<Object, ToolContext, Object>) function).invoke(t, u);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024-2025 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.model.tool.DefaultToolCallingChatOptions;
|
||||
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
|
||||
* @deprecated in favor of {@link DefaultToolCallingChatOptions}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultFunctionCallingOptions 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<>();
|
||||
|
||||
private String model;
|
||||
|
||||
private Double frequencyPenalty;
|
||||
|
||||
private Integer maxTokens;
|
||||
|
||||
private Double presencePenalty;
|
||||
|
||||
private List<String> stopSequences;
|
||||
|
||||
private Double temperature;
|
||||
|
||||
private Integer topK;
|
||||
|
||||
private Double topP;
|
||||
|
||||
@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 String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getFrequencyPenalty() {
|
||||
return this.frequencyPenalty;
|
||||
}
|
||||
|
||||
public void setFrequencyPenalty(Double frequencyPenalty) {
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getMaxTokens() {
|
||||
return this.maxTokens;
|
||||
}
|
||||
|
||||
public void setMaxTokens(Integer maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getPresencePenalty() {
|
||||
return this.presencePenalty;
|
||||
}
|
||||
|
||||
public void setPresencePenalty(Double presencePenalty) {
|
||||
this.presencePenalty = presencePenalty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getStopSequences() {
|
||||
return this.stopSequences != null ? Collections.unmodifiableList(this.stopSequences) : null;
|
||||
}
|
||||
|
||||
public void setStopSequences(List<String> stopSequences) {
|
||||
this.stopSequences = stopSequences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTemperature() {
|
||||
return this.temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getTopK() {
|
||||
return this.topK;
|
||||
}
|
||||
|
||||
public void setTopK(Integer topK) {
|
||||
this.topK = topK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTopP() {
|
||||
return this.topP;
|
||||
}
|
||||
|
||||
public void setTopP(Double topP) {
|
||||
this.topP = topP;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ChatOptions> T copy() {
|
||||
DefaultFunctionCallingOptions copy = new DefaultFunctionCallingOptions();
|
||||
copy.setModel(this.getModel());
|
||||
copy.setFrequencyPenalty(this.getFrequencyPenalty());
|
||||
copy.setMaxTokens(this.getMaxTokens());
|
||||
copy.setPresencePenalty(this.getPresencePenalty());
|
||||
copy.setStopSequences(this.getStopSequences() != null ? new ArrayList<>(this.getStopSequences()) : null);
|
||||
copy.setTemperature(this.getTemperature());
|
||||
copy.setTopK(this.getTopK());
|
||||
copy.setTopP(this.getTopP());
|
||||
copy.setFunctions(new HashSet<>(this.functions));
|
||||
copy.setFunctionCallbacks(new ArrayList<>(this.functionCallbacks));
|
||||
copy.setProxyToolCalls(this.proxyToolCalls);
|
||||
copy.setToolContext(new HashMap<>(this.getToolContext()));
|
||||
return (T) copy;
|
||||
}
|
||||
|
||||
public FunctionCallingOptions merge(ChatOptions options) {
|
||||
Builder builder = FunctionCallingOptions.builder();
|
||||
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());
|
||||
|
||||
// Try to get function-specific properties if options is a FunctionCallingOptions
|
||||
if (options instanceof FunctionCallingOptions functionOptions) {
|
||||
builder.proxyToolCalls(functionOptions.getProxyToolCalls() != null ? functionOptions.getProxyToolCalls()
|
||||
: this.proxyToolCalls);
|
||||
|
||||
Set<String> functions = new HashSet<>();
|
||||
if (!CollectionUtils.isEmpty(this.functions)) {
|
||||
functions.addAll(this.functions);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(functionOptions.getFunctions())) {
|
||||
functions.addAll(functionOptions.getFunctions());
|
||||
}
|
||||
builder.functions(functions);
|
||||
|
||||
List<FunctionCallback> functionCallbacks = new ArrayList<>();
|
||||
if (!CollectionUtils.isEmpty(this.functionCallbacks)) {
|
||||
functionCallbacks.addAll(this.functionCallbacks);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(functionOptions.getFunctionCallbacks())) {
|
||||
functionCallbacks.addAll(functionOptions.getFunctionCallbacks());
|
||||
}
|
||||
builder.functionCallbacks(functionCallbacks);
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
if (!CollectionUtils.isEmpty(this.context)) {
|
||||
context.putAll(this.context);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(functionOptions.getToolContext())) {
|
||||
context.putAll(functionOptions.getToolContext());
|
||||
}
|
||||
builder.toolContext(context);
|
||||
}
|
||||
else {
|
||||
// If not a FunctionCallingOptions, preserve current function-specific
|
||||
// properties
|
||||
builder.proxyToolCalls(this.proxyToolCalls);
|
||||
builder.functions(new HashSet<>(this.functions));
|
||||
builder.functionCallbacks(new ArrayList<>(this.functionCallbacks));
|
||||
builder.toolContext(new HashMap<>(this.context));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024-2025 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.model.tool.DefaultToolCallingChatOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link FunctionCallingOptions.Builder}.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @deprecated in favor of {@link DefaultToolCallingChatOptions.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultFunctionCallingOptionsBuilder implements FunctionCallingOptions.Builder {
|
||||
|
||||
private final DefaultFunctionCallingOptions options;
|
||||
|
||||
public DefaultFunctionCallingOptionsBuilder() {
|
||||
this.options = new DefaultFunctionCallingOptions();
|
||||
}
|
||||
|
||||
// Function calling specific methods
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder functionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.setFunctionCallbacks(functionCallbacks);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder functionCallbacks(FunctionCallback... functionCallbacks) {
|
||||
Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null");
|
||||
this.options.setFunctionCallbacks(List.of(functionCallbacks));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder functions(Set<String> functions) {
|
||||
this.options.setFunctions(functions);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder function(String function) {
|
||||
Assert.notNull(function, "Function must not be null");
|
||||
var set = new HashSet<>(this.options.getFunctions());
|
||||
set.add(function);
|
||||
this.options.setFunctions(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder proxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.setProxyToolCalls(proxyToolCalls);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder toolContext(Map<String, Object> context) {
|
||||
Assert.notNull(context, "Tool context must not be null");
|
||||
Map<String, Object> newContext = new HashMap<>(this.options.getToolContext());
|
||||
newContext.putAll(context);
|
||||
this.options.setToolContext(newContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder 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.options.getToolContext());
|
||||
newContext.put(key, value);
|
||||
this.options.setToolContext(newContext);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ChatOptions.Builder methods with covariant return type
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder model(String model) {
|
||||
this.options.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder frequencyPenalty(Double frequencyPenalty) {
|
||||
this.options.setFrequencyPenalty(frequencyPenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder maxTokens(Integer maxTokens) {
|
||||
this.options.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder presencePenalty(Double presencePenalty) {
|
||||
this.options.setPresencePenalty(presencePenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder stopSequences(List<String> stop) {
|
||||
this.options.setStopSequences(stop);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder temperature(Double temperature) {
|
||||
this.options.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder topK(Integer topK) {
|
||||
this.options.setTopK(topK);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions.Builder topP(Double topP) {
|
||||
this.options.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionCallingOptions build() {
|
||||
return this.options.copy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.ai.tool.method.MethodToolCallback;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
|
||||
/**
|
||||
* Represents a model function call handler. Implementations are registered with the
|
||||
* Models and called on prompts that trigger the function call.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @deprecated in favor of {@link ToolCallback}.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface FunctionCallback {
|
||||
|
||||
/**
|
||||
* @return Returns the Function name. Unique within the model.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return Returns the function description. This description is used by the model do
|
||||
* decide if the function should be called or not.
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* @return Returns the JSON schema of the function input type.
|
||||
*/
|
||||
String getInputTypeSchema();
|
||||
|
||||
/**
|
||||
* Called when a model detects and triggers a function call. The model is responsible
|
||||
* to pass the function arguments in the pre-configured JSON schema format.
|
||||
* @param functionInput JSON string with the function arguments to be passed to the
|
||||
* function. The arguments are defined as JSON schema usually registered with the
|
||||
* model.
|
||||
* @return String containing the function call response.
|
||||
*/
|
||||
String call(String functionInput);
|
||||
|
||||
/**
|
||||
* Called when a model detects and triggers a function call. The model is responsible
|
||||
* to pass the function arguments in the pre-configured JSON schema format.
|
||||
* Additionally, the model can pass a context map to the function if available. The
|
||||
* context is used to pass additional user provided state in addition to the arguments
|
||||
* provided by the AI model.
|
||||
* @param functionInput JSON string with the function arguments to be passed to the
|
||||
* function. The arguments are defined as JSON schema usually registered with the
|
||||
* model. Arguments are provided by the AI model.
|
||||
* @param toolContext Map with the function context. The context is used to pass
|
||||
* additional user provided state in addition to the arguments provided by the AI
|
||||
* model.
|
||||
* @return String containing the function call response.
|
||||
*/
|
||||
default String call(String functionInput, ToolContext toolContext) {
|
||||
if (toolContext != null && !toolContext.getContext().isEmpty()) {
|
||||
throw new UnsupportedOperationException("Function context is not supported!");
|
||||
}
|
||||
return call(functionInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} instance used to build a default
|
||||
* {@link FunctionCallback} instance.
|
||||
* @return Returns a new {@link Builder} instance.
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new DefaultFunctionCallbackBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the type of the schema used to describe the input parameters of the
|
||||
* function.
|
||||
*/
|
||||
enum SchemaType {
|
||||
|
||||
/**
|
||||
* JSON schema
|
||||
*/
|
||||
JSON_SCHEMA,
|
||||
/**
|
||||
* Open API schema
|
||||
*/
|
||||
OPEN_API_SCHEMA
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link FunctionCallback} instance. This is a hierarchical
|
||||
* builder with the following structure:
|
||||
* <ul>
|
||||
* <li>{@link Builder} - The root builder interface.
|
||||
* <li>{@link FunctionInvokingSpec} - The function invoking builder interface.
|
||||
* <li>{@link MethodInvokingSpec} - The method invoking builder interface.
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Use specific builder for the type of tool you need, e.g.
|
||||
* {@link FunctionToolCallback.Builder} and {@link MethodToolCallback.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
interface Builder {
|
||||
|
||||
/**
|
||||
* Builds a {@link Function} invoking {@link FunctionCallback} instance.
|
||||
*/
|
||||
<I, O> FunctionInvokingSpec<I, O> function(String name, Function<I, O> function);
|
||||
|
||||
/**
|
||||
* Builds a {@link BiFunction} invoking {@link FunctionCallback} instance.
|
||||
*/
|
||||
<I, O> FunctionInvokingSpec<I, O> function(String name, BiFunction<I, ToolContext, O> biFunction);
|
||||
|
||||
/**
|
||||
* Builds a {@link Supplier} invoking {@link FunctionCallback} instance.
|
||||
*/
|
||||
<O> FunctionInvokingSpec<Void, O> function(String name, Supplier<O> supplier);
|
||||
|
||||
/**
|
||||
* Builds a {@link Consumer} invoking {@link FunctionCallback} instance.
|
||||
*/
|
||||
<I> FunctionInvokingSpec<I, Void> function(String name, Consumer<I> consumer);
|
||||
|
||||
/**
|
||||
* Builds a Method invoking {@link FunctionCallback} instance.
|
||||
*/
|
||||
MethodInvokingSpec method(String methodName, Class<?>... argumentTypes);
|
||||
|
||||
}
|
||||
|
||||
interface CommonCallbackInvokingSpec<B extends CommonCallbackInvokingSpec<B>> {
|
||||
|
||||
/**
|
||||
* Function description. This description is used by the model to decide if the
|
||||
* function should be called or not.
|
||||
*/
|
||||
B description(String description);
|
||||
|
||||
/**
|
||||
* Specifies what {@link SchemaType} is used by the AI model to validate the
|
||||
* function input arguments. Most models use JSON Schema, except Vertex AI that
|
||||
* uses OpenAPI types.
|
||||
*/
|
||||
B schemaType(SchemaType schemaType);
|
||||
|
||||
/**
|
||||
* Function response converter. The default implementation converts the output
|
||||
* into String before sending it to the Model. Provide a custom function
|
||||
* responseConverter implementation to override this.
|
||||
*/
|
||||
B responseConverter(Function<Object, String> responseConverter);
|
||||
|
||||
/**
|
||||
* You can provide the Input Type Schema directly. In this case it won't be
|
||||
* generated from the inputType.
|
||||
*/
|
||||
B inputTypeSchema(String inputTypeSchema);
|
||||
|
||||
/**
|
||||
* Custom object mapper for JSON operations.
|
||||
*/
|
||||
B objectMapper(ObjectMapper objectMapper);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Function} invoking builder interface.
|
||||
*
|
||||
* @param <I> Function input type.
|
||||
* @param <O> Function output type.
|
||||
*/
|
||||
interface FunctionInvokingSpec<I, O> extends CommonCallbackInvokingSpec<FunctionInvokingSpec<I, O>> {
|
||||
|
||||
/**
|
||||
* Function input type. The input type is used to validate the function input
|
||||
* arguments.
|
||||
* @see #inputType(ParameterizedTypeReference)
|
||||
*/
|
||||
FunctionInvokingSpec<I, O> inputType(Class<?> inputType);
|
||||
|
||||
/**
|
||||
* Function input type retaining generic types. The input type is used to validate
|
||||
* the function input arguments.
|
||||
*/
|
||||
FunctionInvokingSpec<I, O> inputType(ParameterizedTypeReference<?> inputType);
|
||||
|
||||
/**
|
||||
* Builds the {@link FunctionCallback} instance.
|
||||
*/
|
||||
FunctionCallback build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method invoking builder interface.
|
||||
*/
|
||||
interface MethodInvokingSpec extends CommonCallbackInvokingSpec<MethodInvokingSpec> {
|
||||
|
||||
/**
|
||||
* Optional function name. If not provided the method name is used as the
|
||||
* function.
|
||||
* @param name Function name. Unique within the model.
|
||||
*/
|
||||
MethodInvokingSpec name(String name);
|
||||
|
||||
/**
|
||||
* For non-static objects the target object is used to invoke the method.
|
||||
* @param methodObject target object where the method is defined.
|
||||
*/
|
||||
MethodInvokingSpec targetObject(Object methodObject);
|
||||
|
||||
/**
|
||||
* Target class where the method is defined. Used for static methods. For
|
||||
* non-static methods the target object is used.
|
||||
* @param targetClass method target class.
|
||||
*/
|
||||
MethodInvokingSpec targetClass(Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* Builds the {@link FunctionCallback} instance.
|
||||
*/
|
||||
FunctionCallback build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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 org.springframework.ai.tool.resolution.ToolCallbackResolver;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Strategy interface for resolving {@link FunctionCallback} instances.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
* @deprecated Use {@link ToolCallbackResolver} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface FunctionCallbackResolver {
|
||||
|
||||
/**
|
||||
* Resolve the {@link FunctionCallback} instance by its name.
|
||||
* @param name the name of the function to resolve
|
||||
* @return the {@link FunctionCallback} instance
|
||||
*/
|
||||
FunctionCallback resolve(@NonNull String name);
|
||||
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
|
||||
/**
|
||||
* FunctionCallingOptions is a set of options that can be used to configure the function
|
||||
* calling behavior of the ChatModel.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @deprecated in favor of {@link ToolCallingChatOptions}.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface FunctionCallingOptions extends ChatOptions {
|
||||
|
||||
/**
|
||||
* @return Returns {@link DefaultFunctionCallingOptionsBuilder} to create a new
|
||||
* instance of {@link FunctionCallingOptions}.
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new DefaultFunctionCallingOptionsBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function Callbacks to be registered with the ChatModel. For Prompt Options the
|
||||
* toolCallbacks are automatically enabled for the duration of the prompt execution.
|
||||
* For Default Options the FunctionCallbacks are registered but disabled by default.
|
||||
* You have to use "functions" property to list the function names from the ChatModel
|
||||
* registry to be used in the chat completion requests.
|
||||
* @return Return the Function Callbacks to be registered with the ChatModel.
|
||||
*/
|
||||
List<FunctionCallback> getFunctionCallbacks();
|
||||
|
||||
/**
|
||||
* Set the Function Callbacks to be registered with the ChatModel.
|
||||
* @param functionCallbacks the Function Callbacks to be registered with the
|
||||
* ChatModel.
|
||||
*/
|
||||
void setFunctionCallbacks(List<FunctionCallback> functionCallbacks);
|
||||
|
||||
/**
|
||||
* @return {@link Set} of function names from the ChatModel registry to be used in the
|
||||
* next chat completion requests.
|
||||
*/
|
||||
Set<String> getFunctions();
|
||||
|
||||
/**
|
||||
* Set the list of function names from the ChatModel registry to be used in the next
|
||||
* chat completion requests.
|
||||
* @param functions the list of function names from the ChatModel registry to be used
|
||||
* in the next chat completion requests.
|
||||
*/
|
||||
void setFunctions(Set<String> functions);
|
||||
|
||||
default Boolean getProxyToolCalls() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default void setProxyToolCalls(Boolean proxyToolCalls) {
|
||||
if (proxyToolCalls != null) {
|
||||
throw new UnsupportedOperationException("Setting Proxy Tool Calls are not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> getToolContext();
|
||||
|
||||
void setToolContext(Map<String, Object> tooContext);
|
||||
|
||||
/**
|
||||
* Builder for creating {@link FunctionCallingOptions} instance.
|
||||
*/
|
||||
interface Builder extends ChatOptions.Builder {
|
||||
|
||||
/**
|
||||
* The list of Function Callbacks to be registered with the Chat model.
|
||||
* @param functionCallbacks the list of Function Callbacks.
|
||||
* @return the FunctionCallOptions Builder.
|
||||
*/
|
||||
Builder functionCallbacks(List<FunctionCallback> functionCallbacks);
|
||||
|
||||
/**
|
||||
* The Function Callbacks to be registered with the Chat model.
|
||||
* @param functionCallbacks the function callbacks.
|
||||
* @return the FunctionCallOptions Builder.
|
||||
*/
|
||||
Builder 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.
|
||||
*/
|
||||
Builder 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.
|
||||
*/
|
||||
Builder 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.
|
||||
*/
|
||||
Builder 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.
|
||||
*/
|
||||
Builder 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.
|
||||
*/
|
||||
Builder toolContext(String key, Object value);
|
||||
|
||||
/**
|
||||
* Builds the {@link FunctionCallingOptions}.
|
||||
* @return the FunctionCalling options.
|
||||
*/
|
||||
@Override
|
||||
FunctionCallingOptions build();
|
||||
|
||||
// Override all ChatOptions.Builder methods to return
|
||||
// FunctionCallingOptions.Builder
|
||||
@Override
|
||||
Builder model(String model);
|
||||
|
||||
@Override
|
||||
Builder frequencyPenalty(Double frequencyPenalty);
|
||||
|
||||
@Override
|
||||
Builder maxTokens(Integer maxTokens);
|
||||
|
||||
@Override
|
||||
Builder presencePenalty(Double presencePenalty);
|
||||
|
||||
@Override
|
||||
Builder stopSequences(List<String> stopSequences);
|
||||
|
||||
@Override
|
||||
Builder temperature(Double temperature);
|
||||
|
||||
@Override
|
||||
Builder topK(Integer topK);
|
||||
|
||||
@Override
|
||||
Builder topP(Double topP);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.lang.reflect.Type;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Note that the underlying function is responsible for converting the output into format
|
||||
* that can be consumed by the Model. The default implementation converts the output into
|
||||
* String before sending it to the Model. Provide a custom function responseConverter
|
||||
* implementation to override this.
|
||||
*
|
||||
* @param <I> the input type
|
||||
* @param <O> the output type
|
||||
* @author Christian Tzolov
|
||||
* @deprecated in favor of {@link FunctionToolCallback}.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class FunctionInvokingFunctionCallback<I, O> extends AbstractFunctionCallback<I, O> {
|
||||
|
||||
private final BiFunction<I, ToolContext, O> biFunction;
|
||||
|
||||
FunctionInvokingFunctionCallback(String name, String description, String inputTypeSchema, Type inputType,
|
||||
Function<O, String> responseConverter, ObjectMapper objectMapper, BiFunction<I, ToolContext, O> function) {
|
||||
super(name, description, inputTypeSchema, inputType, responseConverter, objectMapper);
|
||||
Assert.notNull(function, "Function must not be null");
|
||||
this.biFunction = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public O apply(I input, ToolContext context) {
|
||||
return this.biFunction.apply(input, context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
|
||||
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.tool.method.MethodToolCallback;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A {@link FunctionCallback} that invokes methods on objects via reflection, supporting:
|
||||
* <ul>
|
||||
* <li>Static and non-static methods</li>
|
||||
* <li>Any number of parameters (including none)</li>
|
||||
* <li>Any parameter/return types (primitives, objects, collections)</li>
|
||||
* <li>Special handling for {@link ToolContext} parameters</li>
|
||||
* </ul>
|
||||
* Automatically infers the input parameters JSON schema from method's argument types.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
* @deprecated in favor of {@link MethodToolCallback}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MethodInvokingFunctionCallback implements FunctionCallback {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MethodInvokingFunctionCallback.class);
|
||||
|
||||
/**
|
||||
* Object instance that contains the method to be invoked. If the method is static
|
||||
* this object can be null.
|
||||
*/
|
||||
private final Object functionObject;
|
||||
|
||||
/**
|
||||
* The method to be invoked.
|
||||
*/
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* Description to help the LLM model to understand worth the method does and when to
|
||||
* use it.
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Internal ObjectMapper used to serialize/deserialize the method input and output.
|
||||
*/
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
/**
|
||||
* The JSON schema generated from the method input parameters.
|
||||
*/
|
||||
private final String inputSchema;
|
||||
|
||||
/**
|
||||
* Flag indicating if the method accepts a {@link ToolContext} as input parameter.
|
||||
*/
|
||||
private boolean isToolContextMethod = false;
|
||||
|
||||
/**
|
||||
* Optional function name. If not provided the method name is used as the function.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final Function<Object, String> responseConverter;
|
||||
|
||||
MethodInvokingFunctionCallback(Object functionObject, Method method, String description, ObjectMapper mapper,
|
||||
String name, Function<Object, String> responseConverter) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
Assert.notNull(mapper, "ObjectMapper must not be null");
|
||||
Assert.hasText(description, "Description must not be empty");
|
||||
Assert.notNull(responseConverter, "Response converter must not be null");
|
||||
|
||||
this.method = method;
|
||||
this.description = description;
|
||||
this.mapper = mapper;
|
||||
this.functionObject = functionObject;
|
||||
this.name = name;
|
||||
this.responseConverter = responseConverter;
|
||||
|
||||
Assert.isTrue(this.functionObject != null || Modifier.isStatic(this.method.getModifiers()),
|
||||
"Function object must be provided for non-static methods!");
|
||||
|
||||
// Generate the JSON schema from the method input parameters
|
||||
Map<String, Class<?>> methodParameters = Stream.of(method.getParameters())
|
||||
.collect(Collectors.toMap(param -> param.getName(), param -> param.getType()));
|
||||
|
||||
this.inputSchema = this.generateJsonSchema(methodParameters);
|
||||
|
||||
logger.debug("Generated JSON Schema: {}", this.inputSchema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return org.springframework.util.StringUtils.hasText(this.name) ? this.name : this.method.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputTypeSchema() {
|
||||
return this.inputSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String functionInput) {
|
||||
return this.call(functionInput, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String functionInput, ToolContext toolContext) {
|
||||
|
||||
try {
|
||||
|
||||
// If the toolContext is not empty but the method does not accept ToolContext
|
||||
// as
|
||||
// input parameter then throw an exception.
|
||||
if (toolContext != null && !CollectionUtils.isEmpty(toolContext.getContext())
|
||||
&& !this.isToolContextMethod) {
|
||||
throw new IllegalArgumentException("Configured method does not accept ToolContext as input parameter!");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = this.mapper.readValue(functionInput, Map.class);
|
||||
|
||||
// ReflectionUtils.findMethod
|
||||
Object[] methodArgs = Stream.of(this.method.getParameters()).map(parameter -> {
|
||||
Class<?> type = parameter.getType();
|
||||
if (ClassUtils.isAssignable(type, ToolContext.class)) {
|
||||
return toolContext;
|
||||
}
|
||||
Object rawValue = map.get(parameter.getName());
|
||||
return this.toJavaType(rawValue, type);
|
||||
}).toArray();
|
||||
|
||||
Object response = ReflectionUtils.invokeMethod(this.method, this.functionObject, methodArgs);
|
||||
|
||||
var returnType = this.method.getReturnType();
|
||||
if (returnType == Void.TYPE) {
|
||||
return "Done";
|
||||
}
|
||||
else if (returnType == Class.class || returnType.isRecord() || returnType == List.class
|
||||
|| returnType == Map.class) {
|
||||
return ModelOptionsUtils.toJsonString(response);
|
||||
}
|
||||
|
||||
return this.responseConverter.apply(response);
|
||||
}
|
||||
catch (Exception e) {
|
||||
ReflectionUtils.handleReflectionException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a JSON schema from the given named classes.
|
||||
* @param namedClasses The named classes to generate the schema from.
|
||||
* @return The generated JSON schema.
|
||||
*/
|
||||
protected String generateJsonSchema(Map<String, Class<?>> namedClasses) {
|
||||
try {
|
||||
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(this.mapper);
|
||||
|
||||
ObjectNode rootNode = this.mapper.createObjectNode();
|
||||
rootNode.put("$schema", "https://json-schema.org/draft/2020-12/schema");
|
||||
rootNode.put("type", "object");
|
||||
ObjectNode propertiesNode = rootNode.putObject("properties");
|
||||
|
||||
for (Map.Entry<String, Class<?>> entry : namedClasses.entrySet()) {
|
||||
String className = entry.getKey();
|
||||
Class<?> clazz = entry.getValue();
|
||||
|
||||
if (ClassUtils.isAssignable(clazz, ToolContext.class)) {
|
||||
// Skip the ToolContext class from the schema generation.
|
||||
this.isToolContextMethod = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonSchema schema = schemaGen.generateSchema(clazz);
|
||||
JsonNode schemaNode = this.mapper.valueToTree(schema);
|
||||
propertiesNode.set(className, schemaNode);
|
||||
}
|
||||
|
||||
return this.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value to the specified Java type.
|
||||
* @param value The value to convert.
|
||||
* @param javaType The Java type to convert to.
|
||||
* @return Returns the converted value.
|
||||
*/
|
||||
protected Object toJavaType(Object value, Class<?> javaType) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
javaType = ClassUtils.resolvePrimitiveIfNecessary(javaType);
|
||||
|
||||
if (javaType == String.class) {
|
||||
return value.toString();
|
||||
}
|
||||
else if (javaType == Integer.class) {
|
||||
return Integer.parseInt(value.toString());
|
||||
}
|
||||
else if (javaType == Long.class) {
|
||||
return Long.parseLong(value.toString());
|
||||
}
|
||||
else if (javaType == Double.class) {
|
||||
return Double.parseDouble(value.toString());
|
||||
}
|
||||
else if (javaType == Float.class) {
|
||||
return Float.parseFloat(value.toString());
|
||||
}
|
||||
else if (javaType == Boolean.class) {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
else if (javaType.isEnum()) {
|
||||
return Enum.valueOf((Class<Enum>) javaType, value.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
String json = this.mapper.writeValueAsString(value);
|
||||
return this.mapper.readValue(json, javaType);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import org.springframework.ai.chat.model.ChatResponse;
|
||||
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.FunctionCallback;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
|
||||
@@ -93,7 +92,9 @@ public class DefaultToolCallingManager implements ToolCallingManager {
|
||||
// Skip the tool if it is already present in the request toolCallbacks.
|
||||
// That might happen if a tool is defined in the options
|
||||
// both as a ToolCallback and as a tool name.
|
||||
if (chatOptions.getToolCallbacks().stream().anyMatch(tool -> tool.getName().equals(toolName))) {
|
||||
if (chatOptions.getToolCallbacks()
|
||||
.stream()
|
||||
.anyMatch(tool -> tool.getToolDefinition().name().equals(toolName))) {
|
||||
continue;
|
||||
}
|
||||
ToolCallback toolCallback = this.toolCallbackResolver.resolve(toolName);
|
||||
@@ -163,9 +164,7 @@ public class DefaultToolCallingManager implements ToolCallingManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the tool call and return the response message. To ensure backward
|
||||
* compatibility, both {@link ToolCallback} and {@link FunctionCallback} are
|
||||
* supported.
|
||||
* Execute the tool call and return the response message.
|
||||
*/
|
||||
private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,
|
||||
ToolContext toolContext) {
|
||||
@@ -186,7 +185,7 @@ public class DefaultToolCallingManager implements ToolCallingManager {
|
||||
String toolInputArguments = toolCall.arguments();
|
||||
|
||||
ToolCallback toolCallback = toolCallbacks.stream()
|
||||
.filter(tool -> toolName.equals(tool.getName()))
|
||||
.filter(tool -> toolName.equals(tool.getToolDefinition().name()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> this.toolCallbackResolver.resolve(toolName));
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.function.Function;
|
||||
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -69,10 +68,6 @@ public interface ToolExecutionEligibilityChecker extends Function<ChatResponse,
|
||||
internalToolExecutionEnabled = Boolean.TRUE
|
||||
.equals(toolCallingChatOptions.getInternalToolExecutionEnabled());
|
||||
}
|
||||
else if (chatOptions instanceof FunctionCallingOptions functionCallingOptions
|
||||
&& functionCallingOptions.getProxyToolCalls() != null) {
|
||||
internalToolExecutionEnabled = Boolean.TRUE.equals(!functionCallingOptions.getProxyToolCalls());
|
||||
}
|
||||
else {
|
||||
internalToolExecutionEnabled = true;
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* <p>
|
||||
* Example usage: <pre>{@code
|
||||
* FunctionCallback callback1 = new MyFunctionCallback();
|
||||
* FunctionCallback callback2 = new AnotherFunctionCallback();
|
||||
* ToolCallback callback1 = new MyFunctionCallback();
|
||||
* ToolCallback callback2 = new AnotherFunctionCallback();
|
||||
*
|
||||
* // Create provider with varargs constructor
|
||||
* ToolCallbackProvider provider1 = new StaticToolCallbackProvider(callback1, callback2);
|
||||
*
|
||||
* // Or create provider with List constructor
|
||||
* List<FunctionCallback> callbacks = Arrays.asList(callback1, callback2);
|
||||
* List<ToolCallback> callbacks = Arrays.asList(callback1, callback2);
|
||||
* ToolCallbackProvider provider2 = new StaticToolCallbackProvider(callbacks);
|
||||
* }</pre>
|
||||
*
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.ai.tool;
|
||||
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.ai.tool.metadata.ToolMetadata;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -28,7 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface ToolCallback extends FunctionCallback {
|
||||
public interface ToolCallback {
|
||||
|
||||
/**
|
||||
* Definition used by the AI model to determine when and how to call the tool.
|
||||
@@ -59,22 +58,4 @@ public interface ToolCallback extends FunctionCallback {
|
||||
return call(toolInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // Call getToolDefinition().name() instead
|
||||
default String getName() {
|
||||
return getToolDefinition().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // Call getToolDefinition().description() instead
|
||||
default String getDescription() {
|
||||
return getToolDefinition().description();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // Call getToolDefinition().inputTypeSchema() instead
|
||||
default String getInputTypeSchema() {
|
||||
return getToolDefinition().inputSchema();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public final class MethodToolCallback implements ToolCallback {
|
||||
});
|
||||
}
|
||||
|
||||
// Based on the implementation in MethodInvokingFunctionCallback.
|
||||
// Based on the implementation in MethodToolCallback.
|
||||
private Object[] buildMethodArguments(Map<String, Object> toolInputArguments, @Nullable ToolContext toolContext) {
|
||||
return Stream.of(this.toolMethod.getParameters()).map(parameter -> {
|
||||
if (parameter.getType().isAssignableFrom(ToolContext.class)) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.ai.tool.resolution;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -29,7 +28,7 @@ import org.springframework.lang.Nullable;
|
||||
public interface ToolCallbackResolver {
|
||||
|
||||
/**
|
||||
* Resolve the {@link FunctionCallback} for the given tool name.
|
||||
* Resolve the {@link ToolCallback} for the given tool name.
|
||||
*/
|
||||
@Nullable
|
||||
ToolCallback resolve(String toolName);
|
||||
|
||||
@@ -112,7 +112,7 @@ public final class JsonParser {
|
||||
|
||||
/**
|
||||
* Convert a Java Object to a typed Object. Based on the implementation in
|
||||
* MethodInvokingFunctionCallback.
|
||||
* MethodToolCallback.
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static Object toTypedObject(Object value, Class<?> type) {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-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 org.springframework.core.ParameterizedTypeReference
|
||||
|
||||
/**
|
||||
* Extension for [FunctionCallback.FunctionInvokerBuilder.inputType] providing a `inputType<Foo>()`
|
||||
* variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
inline fun <reified I, reified O> FunctionCallback.FunctionInvokingSpec<I, O>.inputType(): FunctionCallback.FunctionInvokingSpec<I, O> =
|
||||
inputType(I::class.java)
|
||||
|
||||
@@ -18,7 +18,8 @@ package org.springframework.ai.aot;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -32,11 +33,13 @@ class SpringAiCoreRuntimeHintsTest {
|
||||
var runtimeHints = new RuntimeHints();
|
||||
var springAiCore = new SpringAiCoreRuntimeHints();
|
||||
springAiCore.registerHints(runtimeHints, null);
|
||||
|
||||
// Verify resource hints
|
||||
assertThat(runtimeHints).matches(resource().forResource("embedding/embedding-model-dimensions.properties"));
|
||||
|
||||
assertThat(runtimeHints).matches(reflection().onMethod(FunctionCallback.class, "getDescription"));
|
||||
assertThat(runtimeHints).matches(reflection().onMethod(FunctionCallback.class, "getInputTypeSchema"));
|
||||
assertThat(runtimeHints).matches(reflection().onMethod(FunctionCallback.class, "getName"));
|
||||
// Verify ToolCallback and ToolDefinition type registration
|
||||
assertThat(runtimeHints).matches(reflection().onType(ToolCallback.class));
|
||||
assertThat(runtimeHints).matches(reflection().onType(ToolDefinition.class));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -96,26 +95,9 @@ class DefaultToolExecutionEligibilityPredicateTests {
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFunctionCallingOptionsAndToolExecutionEnabled() {
|
||||
// Create a FunctionCallingOptions with proxy tool calls disabled (which means
|
||||
// internal tool execution is enabled)
|
||||
FunctionCallingOptions options = FunctionCallingOptions.builder().proxyToolCalls(false).build();
|
||||
|
||||
// Create a ChatResponse with tool calls
|
||||
AssistantMessage.ToolCall toolCall = new AssistantMessage.ToolCall("id1", "function", "testTool", "{}");
|
||||
AssistantMessage assistantMessage = new AssistantMessage("test", Map.of(), List.of(toolCall));
|
||||
ChatResponse chatResponse = new ChatResponse(List.of(new Generation(assistantMessage)));
|
||||
|
||||
// Test the predicate
|
||||
boolean result = this.predicate.test(options, chatResponse);
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenRegularChatOptionsAndHasToolCalls() {
|
||||
// Create regular ChatOptions (not ToolCallingChatOptions or
|
||||
// FunctionCallingOptions)
|
||||
// Create regular ChatOptions (not ToolCallingChatOptions)
|
||||
ChatOptions options = ChatOptions.builder().build();
|
||||
|
||||
// Create a ChatResponse with tool calls
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
|
||||
@@ -56,13 +55,6 @@ class ToolCallingChatOptionsTests {
|
||||
assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFunctionCallingOptionsAndExecutionEnabledTrue() {
|
||||
FunctionCallingOptions options = FunctionCallingOptions.builder().build();
|
||||
options.setProxyToolCalls(false);
|
||||
assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMergeRuntimeAndDefaultToolNames() {
|
||||
Set<String> runtimeToolNames = Set.of("toolA");
|
||||
@@ -102,7 +94,7 @@ class ToolCallingChatOptionsTests {
|
||||
List<ToolCallback> mergedToolCallbacks = ToolCallingChatOptions.mergeToolCallbacks(runtimeToolCallbacks,
|
||||
defaultToolCallbacks);
|
||||
assertThat(mergedToolCallbacks).hasSize(1);
|
||||
assertThat(mergedToolCallbacks.get(0).getName()).isEqualTo("toolA");
|
||||
assertThat(mergedToolCallbacks.get(0).getToolDefinition().name()).isEqualTo("toolA");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,7 +104,7 @@ class ToolCallingChatOptionsTests {
|
||||
List<ToolCallback> mergedToolCallbacks = ToolCallingChatOptions.mergeToolCallbacks(runtimeToolCallbacks,
|
||||
defaultToolCallbacks);
|
||||
assertThat(mergedToolCallbacks).hasSize(1);
|
||||
assertThat(mergedToolCallbacks.get(0).getName()).isEqualTo("toolA");
|
||||
assertThat(mergedToolCallbacks.get(0).getToolDefinition().name()).isEqualTo("toolA");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,7 +114,7 @@ class ToolCallingChatOptionsTests {
|
||||
List<ToolCallback> mergedToolCallbacks = ToolCallingChatOptions.mergeToolCallbacks(runtimeToolCallbacks,
|
||||
defaultToolCallbacks);
|
||||
assertThat(mergedToolCallbacks).hasSize(1);
|
||||
assertThat(mergedToolCallbacks.get(0).getName()).isEqualTo("toolB");
|
||||
assertThat(mergedToolCallbacks.get(0).getToolDefinition().name()).isEqualTo("toolB");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.tool.resolution
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.ai.model.function.FunctionCallback
|
||||
import org.springframework.ai.model.function.inputType
|
||||
|
||||
class FunctionCallbackExtensionsTests {
|
||||
|
||||
private val spec = mockk<FunctionCallback.FunctionInvokingSpec<WeatherRequest, WeatherResponse>>()
|
||||
|
||||
@Test
|
||||
fun inputType() {
|
||||
every { spec.inputType(any<Class<*>>()) } returns spec
|
||||
spec.inputType<WeatherRequest, WeatherResponse>()
|
||||
verify { spec.inputType(WeatherRequest::class.java) }
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,6 @@
|
||||
<suppress files="BaseOllamaIT.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files="BaseOCIGenAITest.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files="OpenAiChatModelResponseFormatIT.java" checks="RegexpSinglelineJava"/>
|
||||
<suppress files="MethodInvokingFunctionCallbackTests.java" checks="RegexpSinglelineJava"/>
|
||||
<suppress files="DefaultFunctionCallbackBuilderTests.java" checks="RegexpSinglelineJava"/>
|
||||
<suppress files="ClientIT.java" checks="RegexpSinglelineJava"/>
|
||||
<suppress files="MistralAiApi.java" checks="RegexpSinglelineJava"/>
|
||||
<suppress files="MariaDBSchemaValidator.java" checks="RegexpSinglelineJava"/>
|
||||
|
||||
Reference in New Issue
Block a user