您可以使用`OpenAiChatClient`注册自定义的Java函数,并且让OpenAI模型智能地选择输出包含调用一个或多个已注册函数参数的JSON对象。这允许您将LLM(大型语言模型)的能力与外部工具和APIs连接。OpenAI模型经过训练,能够检测何时应当调用函数,并以符合函数签名的JSON格式响应。
OpenAI API不会直接调用函数;相反,模型会生成JSON,您可以使用该JSON在代码中调用函数,并将结果返回给模型以完成对话。
Spring AI 提供了灵活且用户友好的方法来注册和调用自定义函数。通常,自定义函数需要提供一个函数 name
(名称)、description
(描述)以及函数调用 signature
(作为 JSON 架构)来让模型知道函数期望哪些参数。description
(描述)有助于模型了解何时调用该函数。
作为开发者,你需要实现一个函数,该函数接收来自AI模型的函数调用参数,并将结果返回给模型。你的函数可以依次调用其他第三方服务来提供结果。
Spring AI 使得这件事变得非常简单,只需要定义一个返回 java.util.Function
的 @Bean
定义,并在调用 ChatClient
时提供 bean 名称作为选项即可。
在底层,Spring通过适配器代码包装你的POJO(函数),使其能够与AI模型进行交互,这样你就不需要编写繁琐的样板代码。底层基础设施的基础是[FunctionCallback.java](FunctionCallbackWrapper.java(https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java)。
它是如何工作的
假设我们希望AI模型回答它所没有的信息,例如给定位置的当前温度。
我们可以向AI模型提供有关我们自己功能的元数据,它可以在处理您的提示时使用这些信息来检索该信息。
例如,如果在处理一个提示时,AI模型确定它需要关于一个给定位置的温度的额外信息,它将启动一个服务器端生成的请求/响应交互。AI模型调用一个客户端函数。AI模型提供方法调用的详细信息作为JSON,客户端负责执行该函数并返回响应。
模型-客户端交互在Spring AI 函数调用流程图中被说明。
Spring AI 极大地简化了你需要编写以支持函数调用的代码。它为你代理了函数调用交流。你可以简单地提供你的函数定义作为一个 @Bean
,然后在你的提示选项中提供函数的 bean 名称。你也可以在你的提示中引用多个函数 bean 名称。
快速开始
让我们创建一个通过调用我们自己的函数来回答问题的聊天机器人。为了支持聊天机器人的响应,我们将注册我们自己的函数,该函数接收一个位置并返回那个位置的当前天气。
当模型需要回答类似于“波士顿的天气怎么样?”这样的问题时,AI模型将调用客户端,并将地点值作为参数传递给函数。这种类RPC的数据以JSON格式传递。
我们的函数可以调用一些基于SaaS的天气服务API,并将天气响应返回给模型以完成对话。在这个例子中,我们将使用一个名为`MockWeatherService`的简单实现,它为各个地点硬编码了温度。
以下 MockWeatherService.java
代表了天气服务API:
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);
}
}
注册函数为Bean
通过链接:../openai-chat.html#_auto_configuration[OpenAiChatClient自动配置],您可以通过多种方式注册自定义函数作为Spring上下文中的beans。
我们首先描述最对POJO(Plain Old Java Object)友好的选项。
纯Java函数
在这种方法中,您在应用程序上下文中定义`@Beans`,就像定义任何其他Spring管理的对象一样。
在内部,Spring AI ChatClient
将创建一个 FunctionCallbackWrapper
包装器的实例,该包装器添加了通过AI模型调用它的逻辑。@Bean
的名称被作为 ChatOption
传递。
@Configuration
static class Config {
@Bean
@Description("Get the weather in location") // function description
public Function<MockWeatherService.Request, MockWeatherService.Response> weatherFunction1() {
return new MockWeatherService();
}
...
}
@Description` 注解是可选的,它提供了一个函数描述(2),帮助模型理解何时调用该函数。设置这一重要属性有助于AI模型确定调用哪个客户端函数。
提供函数描述的另一种方法是在`MockWeatherService.Request`上使用`@JacksonDescription`注解来提供函数的描述:
@Configuration
static class Config {
@Bean
public Function<Request, Response> currentWeather3() { // (1) bean name as function name.
return new MockWeatherService();
}
...
}
@JsonClassDescription("Get the weather in location") // (2) function description
public record Request(String location, Unit unit) {}
将请求对象进行注解,以便该函数生成的JSON模式尽可能描述性强,有助于AI模型选择正确的函数进行调用,这是一个最佳实践。
链接:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWithPlainFunctionBeanIT.java[FunctionCallbackWithPlainFunctionBeanIT.java] 展示了这种方法。
函数回调包装器
另一种注册函数的方式是创建像这样的`FunctionCallbackWrapper`包装器:
@Configuration
static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return new FunctionCallbackWrapper<>("CurrentWeather", // (1) 函数名
"Get the weather in location", // (2) 函数描述
(response) -> "" + response.temp() + response.unit(), // (3) 响应转换器
new MockWeatherService()); // 函数代码
}
...
}
它封装了第三方的`MockWeatherService`功能,并将其注册为`OpenAiChatClient`的`CurrentWeather`功能。它还提供了一个描述(2)和一个可选的响应转换器(3),用于将响应转换为模型所期望的文本。
Note
|
默认情况下,响应转换器会对Response对象进行JSON序列化。 |
Note
|
FunctionCallbackWrapper`在内部基于`MockWeatherService.Request`类解析函数调用签名。 |
在聊天选项中指定函数
要让模型知道并调用你的`CurrentWeather`函数,你需要在你的提示请求中启用它:
OpenAiChatClient chatClient = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
ChatResponse response = chatClient.call(new Prompt(List.of(userMessage),
OpenAiChatOptions.builder().withFunction("CurrentWeather").build())); // (1) Enable the function
logger.info("Response: {}", response);
上述用户问题将触发对`CurrentWeather`函数的3次调用(每个城市一次),最终的响应将是这样的:
这是所请求城市的当前天气: - 旧金山,加利福尼亚州:30.0°C - 东京,日本:10.0°C - 巴黎,法国:15.0°C
链接:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] 测试演示了这种方法。
使用提示选项注册/调用函数
除了自动配置之外,你还可以动态地为你的Prompt请求注册回调函数:
OpenAiChatClient chatClient = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.build();
ChatResponse response = chatClient.call(new Prompt(List.of(userMessage), promptOptions));
Note
|
在提示中注册的功能在此请求期间默认启用。 |
这种方法允许根据用户输入动态选择要调用的不同函数。
'''The FunctionCallbackInPromptIT.java integration test 提供了一个完整的示例,展示了如何向 OpenAiChatClient
注册一个函数,并在 prompt 请求中使用它。'''
附录:
Spring AI 函数调用流程
下图说明了OpenAiChatClient函数调用的流程:
OpenAI API 函数调用流程
以下图表展示了OpenAI API功能调用的流程 Function Calling:
链接:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/api/tool/OpenAiApiToolFunctionCallIT.java [OpenAiApiToolFunctionCallIT.java] 提供了一个关于如何使用 OpenAI API 函数调用的完整示例。它基于 https://platform.openai.com/docs/guides/function-calling/parallel-function-calling [OpenAI 函数调用教程]。