Function Calling

您可以向 OpenAiChatModel 註冊自訂 Java 函數,並讓 OpenAI 模型智慧地選擇輸出一個 JSON 物件,其中包含呼叫一個或多個已註冊函數的引數。這讓您可以將 LLM 功能與外部工具和 API 連接。OpenAI 模型經過訓練,可以偵測何時應呼叫函數,並以符合函數簽章的 JSON 回應。

OpenAI API 不會直接呼叫函數;相反地,模型會產生 JSON,您可以使用它在程式碼中呼叫函數,並將結果傳回模型以完成對話。

Spring AI 提供了彈性且使用者友善的方式來註冊和呼叫自訂函數。一般來說,自訂函數需要提供函數的 namedescription 和函數呼叫 signature (作為 JSON schema),以讓模型知道函數預期的引數。description 幫助模型理解何時應呼叫函數。

作為開發人員,您需要實作一個函數,該函數接收從 AI 模型傳送的函數呼叫引數,並將結果回應回模型。您的函數可以進而調用其他第三方服務以提供結果。

Spring AI 使此過程變得非常簡單,只需定義一個 @Bean 定義,該定義會回傳一個 java.util.Function,並在調用 ChatModel 時提供 Bean 名稱作為選項即可。

在底層,Spring 使用適當的適配器程式碼封裝您的 POJO (函數),以實現與 AI 模型的互動,從而節省您編寫繁瑣的樣板程式碼。底層基礎架構的基礎是 FunctionCallback.java 介面和配套的 Builder 公用程式類別,以簡化 Java 回呼函數的實作和註冊。

運作方式

假設我們希望 AI 模型回應它沒有的資訊,例如,給定位置的目前溫度。

我們可以向 AI 模型提供關於我們自己的函數的中繼資料,以便它可以在處理您的提示時使用這些函數來檢索該資訊。

例如,如果在處理提示期間,AI 模型判斷它需要關於給定位置溫度的額外資訊,它將啟動伺服器端產生的請求/回應互動。AI 模型調用用戶端函數。AI 模型以 JSON 格式提供方法調用詳細資訊,而用戶端的責任是執行該函數並傳回回應。

模型與用戶端互動在 Spring AI 函數呼叫流程 圖表中說明。

Spring AI 大幅簡化了您需要編寫以支援函數調用的程式碼。它為您協調函數調用對話。您只需將您的函數定義提供為 @Bean,然後在您的提示選項中提供函數的 Bean 名稱。您也可以在提示中參考多個函數 Bean 名稱。

快速開始

讓我們建立一個聊天機器人,透過呼叫我們自己的函數來回答問題。為了支援聊天機器人的回應,我們將註冊我們自己的函數,該函數接收一個位置並傳回該位置的目前天氣。

當模型需要回答諸如 "What’s the weather like in Boston?" 之類的問題時,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

透過 OpenAiChatModel 自動組態,您有多種方法可以在 Spring 容器中將自訂函數註冊為 Bean。

我們先從描述最 POJO 友善的選項開始。

純 Java 函數

在此方法中,您在應用程式容器中定義一個 @Bean,就像您定義任何其他 Spring 管理的物件一樣。

在內部,Spring AI ChatModel 將建立一個 FunctionCallback 的實例,該實例會新增透過 AI 模型調用的邏輯。@Bean 的名稱作為 ChatOption 傳遞。

@Configuration
static class Config {

	@Bean
	@Description("Get the weather in location") // function description
	public Function<MockWeatherService.Request, MockWeatherService.Response> currentWeather() {
		return new MockWeatherService();
	}

}

@Description 註解是選用的,它提供函數描述,以幫助模型理解何時應呼叫函數。這是一個重要的屬性,設定它可以幫助 AI 模型判斷要調用哪個用戶端函數。

提供函數描述的另一個選項是在 MockWeatherService.Request 上使用 @JsonClassDescription 註解

@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) {}

最佳實務是在請求物件上註解資訊,以便該函數產生的 JSON schema 盡可能具有描述性,以幫助 AI 模型選擇正確的函數來調用。

FunctionCallback 包裝器

註冊函數的另一種方法是建立一個像這樣的 FunctionCallback

@Configuration
static class Config {

	@Bean
	public FunctionCallback weatherFunctionInfo() {

    return FunctionCallback.builder()
        .description("Get the weather in location") // (2) function description
        .function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
        .inputType(MockWeatherService.Request.class) // (3) function input type
        .build();
	}

}

它包裝了第三方 MockWeatherService 函數,並將其註冊為具有 OpenAiChatModelCurrentWeather 函數。它還提供了描述 (2) 和輸入類型 (3),用於產生函數呼叫的 JSON schema。

預設情況下,回應轉換器會執行回應物件的 JSON 序列化。
FunctionCallback 在內部根據 MockWeatherService.Request 類別解析函數呼叫簽章。

在 Chat 選項中指定函數

為了讓模型知道並呼叫您的 CurrentWeather 函數,您需要在您的提示請求中啟用它

OpenAiChatModel chatModel = ...

UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");

ChatResponse response = this.chatModel.call(new Prompt(this.userMessage,
		OpenAiChatOptions.builder().withFunction("CurrentWeather").build())); // Enable the function

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

上述使用者問題將觸發對 CurrentWeather 函數的 3 次呼叫 (每個城市一次),最終回應將如下所示

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

OpenAiFunctionCallbackIT.java 測試示範了此方法。

使用提示選項註冊/呼叫函數

除了自動組態之外,您還可以透過您的 Prompt 請求動態註冊回呼函數

OpenAiChatModel chatModel = ...

UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");

var promptOptions = OpenAiChatOptions.builder()
	.withFunctionCallbacks(List.of(FunctionCallback.builder()
        .description("Get the weather in location") // (2) function description
        .function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
        .inputType(MockWeatherService.Request.class) // (3) function input type
        .build())) // function code
	.build();

ChatResponse response = this.chatModel.call(new Prompt(this.userMessage, this.promptOptions));
預設情況下,提示內註冊的函數會在此請求的持續時間內啟用。

此方法允許根據使用者輸入動態選擇要呼叫的不同函數。

FunctionCallbackInPromptIT.java 整合測試提供了一個完整的範例,說明如何向 OpenAiChatModel 註冊函數並在提示請求中使用它。

工具上下文支援

Spring AI 現在支援透過工具上下文將額外的上下文資訊傳遞給函數回呼。此功能讓您可以提供可在函數執行中使用的額外資料,從而增強函數呼叫的彈性和強大功能。

上下文資訊作為 java.util.BiFunction 的第二個引數傳遞。ToolContext 作為不可變的 Map<String,Object> 包含,允許您存取鍵值對。

如何使用工具上下文

您可以在建構您的 chat 選項時設定工具上下文,並為您的回呼使用 BiFunction

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);
    };

OpenAiChatOptions options = OpenAiChatOptions.builder()
    .withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
    .withFunctionCallbacks(List.of(FunctionCallback.builder()
        .function("getCurrentWeather", this.weatherFunction)
        .description("Get the weather in location")
        .inputType(MockWeatherService.Request.class)
        .build()))
    .withToolContext(Map.of("sessionId", "123", "userId", "user456"))
    .build();

在此範例中,weatherFunction 定義為一個 BiFunction,它同時接收請求和工具上下文作為參數。這讓您可以直接在函數邏輯中存取上下文。

然後,您可以在呼叫 chat 模型時使用這些選項

UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage), options));

此方法讓您可以將會話特定或使用者特定的資訊傳遞給您的函數,從而實現更具上下文感知和個人化的回應。

附錄

Spring AI 函數呼叫流程

下圖說明了 OpenAiChatModel 函數呼叫的流程

openai chatclient function call

OpenAI API 函數呼叫流程

下圖說明了 OpenAI API Function Calling 的流程

openai function calling flow

OpenAiApiToolFunctionCallIT.java 提供了一個關於如何使用 OpenAI API 函數呼叫的完整範例。它基於 OpenAI 函數呼叫教學