spring AI (三) 函数调用

有了前 2 节的基础，接下来要上点强度了，Function Calling - 函数调用。

那么什么是函数调用呢？官方的解释是：

You can register custom Java functions with the OpenAiChatClient and have the OpenAI 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 OpenAI models are trained to detect when a function should be called and to respond with JSON that adheres to the function signature.
您可以使用 OpenAiChatClient 注册自定义 Java 函数，让 OpenAI 模型智能地选择输出包含参数的 JSON 对象，以调用一个或多个已注册函数。这样，您就可以将 LLM 功能与外部工具和 API 相连接。OpenAI 模型经过训练，能够检测函数何时应被调用，并用符合函数签名的 JSON 做出响应。

众所周知，OpenAI 的 ChatGPT 是不能联网的，但是有了 Function Calling 我们就可以提供给模型最新的数据。

想象一下，给 ChatGPT 加上 ‘天气’ 插件，告诉 GPT 北京上海最新的天气，让 GPT 总结告诉你应该穿什么衣服。或者加上一个 ‘热搜榜’ 插件，让 AI 总结最新的热点。

创建函数

函数的创建需要实现 Function 接口

Copy
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.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) {
System.err.println("MockWeatherService.apply");
return new Response(30.0, Unit.C);
}
}

这里返回的是固定值，30C° 实际可以调用接口返回真实的数据。

函数注册

有了函数之后需要进行注册，注册有 2 种方式。

创建模型时注册

你可以在创建模型时把函数注册进去，例如

Copy
var openAiApi = new OpenAiApi("https://xxx", "sk-xxx");

OpenAiChatClient chatClient = new OpenAiChatClient(openAiApi, OpenAiChatOptions.builder()
.withModel("gpt-3.5-turbo-1106")
.withTemperature(0.4F)
.withFunctionCallbacks(List.of(
FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("weather").withDescription("Get the weather in location").build()))
.build());

其中 withFunctionCallbacks() 表示注册函数， FunctionCallbackWrapper.builder 用于构建函数，withName("weather").withDescription("Get the weather in location") 表示函数的名称和介绍，尽量使用英文，介绍应符合函数的操作以便于让 AI 判断何时使用某个函数

动态注册

还有一种动态注册是更推荐的，可以根据提问选择对应的函数

Copy
var promptOptions = OpenAiChatOptions.builder().withFunctionCallbacks(List.of(
FunctionCallbackWrapper.builder(new MockWeatherService()).withName("weather").withDescription("Get the weather in location").build(),
FunctionCallbackWrapper.builder(new WbHotService()).withName("wbHot").withDescription("Get the hot list of Weibo").build(),
FunctionCallbackWrapper.builder(new TodayNews()).withName("todayNews").withDescription("60s watch world news").build())).build();

ChatResponse response = chatClient.call(new Prompt(message, promptOptions));

return response.getResult().getOutput().getContent();

这里面注册了 3 个函数

weather 就是上面创建的天气函数

wbHot 是调用接口获取的微博热搜榜

todayNews 也是调用接口获取的 60s 世界新闻

根据不同的提示，AI 会选择调用最符合的函数

以下是微博热榜的函数示例

Copy
public class WbHotService implements Function<WbHotService.Request, WbHotService.Response> {

public record Request(String wb) {}
public record Response(WbHot wbHot) {}

/**
* Applies this function to the given argument.
*
* @param request the function argument
* @return the function result
*/
@Override
public Response apply(Request request) {

System.err.println("微博热榜哦，表哥我进来了哦(*/ω＼*)(*/ω＼*)");

// 使用hutool请求接口
String result = HttpUtil.get("https://api.vvhan.com/api/hotlist/wbHot");
WbHot bean = JSONUtil.toBean(result, WbHot.class);
return new Response(bean);
}

}

事实上，也不需要将接口返回的 json 转为对象，直接返回给 AI 它会自动处理的。

不过目前 Function Calling 也有问题，即函数返回的结果总会被 AI 处理。

例如，我有一个图片函数，我对 AI 说 “来点色图”，然后调用了这个函数，函数很简单啊，返回了一个图片的 URL，这就完全没有必要让 AI 处理啊。万一 AI 看完来了句 “淦，老兄你的 XP 真的很怪” 呢🤣
不过之前也有人提过 ISSUES

推荐

关于 Function Calling 的具体应用落地，可以看下面这篇文章
Spring AI 应用 - 智能记者
完整的代码在这个gist

Spring AI Chat 简单示例
韩小韩 API 接口站 - 免费 API 数据接口调用服务平台
韩小韩 API 接口站 -