文章目录
- [Function Calling](#Function Calling)
-
- 工作原理
- 快速上手
-
- [将函数注册为 Bean](#将函数注册为 Bean)
-
- [纯 Java 函数实现(Plain Java Functions)](#纯 Java 函数实现(Plain Java Functions))
- [FunctionCallback Wrapper](#FunctionCallback Wrapper)
- [Specifying functions in Chat Options](#Specifying functions in Chat Options)
- [Register/Call Functions with Prompt Options](#Register/Call Functions with Prompt Options)
- 附录:
-
- [Spring AI 函数调用流程](#Spring AI 函数调用流程)
- [OpenAI API 函数调用流程](#OpenAI API 函数调用流程)
Function Calling
您可以向 OpenAiChatClient 注册自定义 Java 函数,并让 OpenAI 模型输出包含参数的 JSON 对象,智能地选择调用一个或多个已注册函数。这允许您将LLM功能与外部工具和 API 连接起来。OpenAI 模型经过训练,可以检测何时应调用函数,并使用符合函数协议的 JSON 进行响应。
OpenAI API 不直接调用函数 ;相反,模型会生成符合函数协议的 JSON,您可以使用该 JSON 在代码中调用函数并将结果返回给模型以完成对话。
Spring AI 提供了灵活且用户友好的注册和调用自定义函数的方式。通常,自定义函数需要提供函数name、description和函数调用signature(作为 JSON 架构),以便让模型知道函数需要哪些参数。description有助于模型了解何时调用函数。
作为开发人员,您需要实现一个函数,该函数接收从 AI 模型返回的函数调用参数,并将函数的结果再返回给模型。您的函数也可以反过来调用其他第三方服务来提供结果。
Spring AI 使这变得简单,只需定义一个返回java.util.Function的@Bean定义,在调用ChatClient的时候,设置这个 Bean 的名字到ChatClient的参数中。
当下,Spring 使用适配器包装您的 POJO(函数),该代理支持与 AI 模型进行交互,从而避免编写繁琐的代码。底层基础结构的基础是 FunctionCallback.java 接口和配套的 FunctionCallbackWrapper.java实用程序类,以简化 Java 回调函数的实现和注册。
工作原理
假设我们希望 AI 模型处理它没有的信息进行响应,例如让大模型提供某个位置的实时温度。
我们可以为 AI 模型提供有关我们自己函数的元数据,它可以结合您的提示词和这些元数据来判断是否调用用户定义的函数检索该信息。
例如,如果在处理提示词期间,AI 模型确定它需要给定地域的实时温度,它将启动一个请求/响应交互的服务器端。AI 模型调用客户端。AI 模型以 JSON 格式提供方法调用详细信息给客户端,客户端负责执行该函数并将函数返回的数据返回给 AI模型。
模型与客户端的交互在下面的 Spring AI 函数调用流程图 中进行了说明。
Spring AI 大大简化了支持函数调用所需的代码。它为您代理函数调用对话。您只需将函数定义为@Bean,然后在提示词参数(options)中提供函数的 Bean 名称即可。您还可以在提示词中引用多个函数 Bean 名称。
快速上手
让我们创建一个聊天机器人,通过调用我们自己的函数来回答问题。为了支持聊天机器人的响应,我们将注册自己的函数,该函数获取一个位置并返回该位置的当前天气。
当模型对提示词的返回信息中有需要回答问题如"What's the weather like in Boston?",AI模型将调用客户端,提供位置值作为要传递给函数的参数。此类似 RPC 的数据以 JSON 形式传递。
我们的函数可以基于一些 SaaS 的天气服务 API 实现,将天气响应返回给模型以完成对话。在此示例中,我们将使用一个名为" MockWeatherService " 的类进行硬编码来简单实现,该实现对不同位置的温度进行硬编码。
天气服务 API MockWeatherService.java 如下所示:
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);
}
}
将函数注册为 Bean
OpenAI 章节中 的 自动装配 机制,可以让您通过多种方式在 Spring 上下文中将自定义函数注册为 bean。
我们需要对POJO的参数做友好的描述。
纯 Java 函数实现(Plain Java Functions)
在这种方法中,您可以在应用程序上下文中定义@Beans,就像定义任何其他 Spring 托管对象一样。
在内部,Spring AI ChatClient 将创建一个 FunctionCallbackWrapper 包装类实例,该实例添加通过 AI 模型调用它的逻辑。@Bean的名称将转换成一个ChatOption 。
java
@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 模型确定要调用的客户端函数。
提供函数描述的另一个注解是 @JacksonDescription , 这个注解可以作用在MockWeatherService.Request 上面:
java
@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 模型可以正确选择要调用的函数。
FunctionCallbackWithPlainFunctionBeanIT.java 演示了这种方法。
FunctionCallback Wrapper
注册函数的另一种方法是创建 FunctionCallbackWrapper 包装器,如下所示:
java
@Configuration
static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return new FunctionCallbackWrapper<>("CurrentWeather", // (1) function name
"Get the weather in location", // (2) function description
(response) -> "" + response.temp() + response.unit(), // (3) Response Converter
new MockWeatherService()); // function code
}
...
}
它包装第 3 方函数MockWeatherService并将其注册为 OpenAiChatClient 的 CurrentWeather 函数 。它还提供了函数描述 (2)参数 和可选的响应转换器 (3)参数(用于将响应转换为模型预期的文本)。
默认情况下,响应转换器是对 Response 对象执行 JSON 序列化操作。
FunctionCallbackWrapper 基于 MockWeatherService.Request 类解析函数调用签名。
Specifying functions in Chat Options
要让模型知道并调用您的 CurrentWeather 函数,您需要在提示词中启用它:
java
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);
上面的用户问题将触发 3 次CurrentWeather函数调用(每个城市一个),最终响应如下:
text
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
FunctionCallbackWrapperIT.java 测试演示了这种方法。
Register/Call Functions with Prompt Options
除了自动配置之外,您还可以在提示词中动态注册回调函数:
java
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));
默认情况下,会启用提示词内注册的函数。
这种方法允许用户动态输入想要调用的函数。
FunctionCallbackInPromptIT.java 集成测试提供了一个完整的示例,说明如何向 OpenAiChatClient 注册函数并在提示词中使用它。
附录:
Spring AI 函数调用流程
下图说明了 OpenAiChatClient 函数调用的流程:
OpenAI API 函数调用流程
下图说明了 OpenAI API Function Calling 的流程:
OpenAiApiToolFunctionCallIT.java 提供了使用 OpenAI API 函数调用的完整示例。它基于 OpenAI Function Calling 教程。