聊天客戶端 API

ChatClient 提供流暢的 API,用於與 AI 模型溝通。它同時支援同步和串流程式設計模型。

流暢的 API 提供用於建立 Prompt 組成部分的方法,該 Prompt 作為輸入傳遞至 AI 模型。Prompt 包含指導 AI 模型輸出和行為的指示文字。從 API 的角度來看,提示詞由訊息集合組成。

AI 模型處理兩種主要訊息類型:使用者訊息,即來自使用者的直接輸入;以及系統訊息,即由系統產生以引導對話。

這些訊息通常包含佔位符,這些佔位符在運行時根據使用者輸入進行替換,以自訂 AI 模型對使用者輸入的回應。

還可以指定提示詞選項,例如要使用的 AI 模型名稱,以及控制生成輸出隨機性或創造性的溫度設定。

建立 ChatClient

ChatClient 是使用 ChatClient.Builder 物件建立的。您可以取得任何 ChatModel Spring Boot 自動配置的自動配置 ChatClient.Builder 實例,或以程式設計方式建立一個。

使用自動配置的 ChatClient.Builder

在最簡單的使用案例中,Spring AI 提供 Spring Boot 自動配置,為您建立原型 ChatClient.Builder Bean,以便您注入到您的類別中。以下是檢索對簡單使用者請求的 String 回應的簡單範例。

@RestController
class MyController {

    private final ChatClient chatClient;

    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/ai")
    String generation(String userInput) {
        return this.chatClient.prompt()
            .user(userInput)
            .call()
            .content();
    }
}

在這個簡單的範例中,使用者輸入設定使用者訊息的內容。call() 方法將請求傳送至 AI 模型,而 content() 方法將 AI 模型的回應作為 String 傳回。

以程式設計方式建立 ChatClient

您可以透過設定屬性 spring.ai.chat.client.enabled=false 來停用 ChatClient.Builder 自動配置。如果同時使用多個聊天模型,這會很有用。然後,為您需要的每個 ChatModel 以程式設計方式建立 ChatClient.Builder 實例

ChatModel myChatModel = ... // usually autowired

ChatClient.Builder builder = ChatClient.builder(this.myChatModel);

// or create a ChatClient with the default builder settings:

ChatClient chatClient = ChatClient.create(this.myChatModel);

ChatClient 流暢 API

ChatClient 流暢 API 允許您使用多載的 prompt 方法以三種不同的方式建立提示詞來啟動流暢 API

  • prompt():此方法不帶引數,可讓您開始使用流暢 API,讓您建立使用者、系統和提示詞的其他部分。

  • prompt(Prompt prompt):此方法接受 Prompt 引數,讓您傳入使用 Prompt 的非流暢 API 建立的 Prompt 實例。

  • prompt(String content):這是一個便利方法,類似於先前的多載。它採用使用者的文字內容。

ChatClient 回應

ChatClient API 提供幾種方式,可使用流暢 API 格式化來自 AI 模型的回應。

傳回 ChatResponse

來自 AI 模型的回應是由 ChatResponse 類型定義的豐富結構。它包含關於回應如何產生的元數據,並且還可以包含多個回應,稱為 Generation,每個回應都有自己的元數據。元數據包括用於建立回應的 token 數量(每個 token 約為 3/4 個單詞)。此資訊很重要,因為託管的 AI 模型根據每個請求使用的 token 數量收費。

以下範例透過在 call() 方法之後調用 chatResponse(),展示如何傳回包含元數據的 ChatResponse 物件。

ChatResponse chatResponse = chatClient.prompt()
    .user("Tell me a joke")
    .call()
    .chatResponse();

傳回實體

您通常想要傳回從傳回的 String 映射的實體類別。entity() 方法提供此功能。

例如,給定 Java 記錄

record ActorFilms(String actor, List<String> movies) {}

您可以輕鬆地使用 entity() 方法將 AI 模型的輸出映射到此記錄,如下所示

ActorFilms actorFilms = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorFilms.class);

還有一個多載的 entity 方法,其簽名為 entity(ParameterizedTypeReference<T> type),可讓您指定諸如泛型 List 之類的類型

List<ActorFilms> actorFilms = chatClient.prompt()
    .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});

串流回應

stream() 方法可讓您取得非同步回應,如下所示

Flux<String> output = chatClient.prompt()
    .user("Tell me a joke")
    .stream()
    .content();

您也可以使用方法 Flux<ChatResponse> chatResponse() 串流 ChatResponse

未來,我們將提供一個便利方法,讓您使用反應式 stream() 方法傳回 Java 實體。同時,您應該使用 結構化輸出轉換器 來明確轉換聚合回應,如下所示。這也示範了流暢 API 中參數的使用,這將在文件後續章節中更詳細地討論。

var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});

Flux<String> flux = this.chatClient.prompt()
    .user(u -> u.text("""
                        Generate the filmography for a random actor.
                        {format}
                      """)
            .param("format", this.converter.getFormat()))
    .stream()
    .content();

String content = this.flux.collectList().block().stream().collect(Collectors.joining());

List<ActorFilms> actorFilms = this.converter.convert(this.content);

call() 傳回值

ChatClient 上指定 call() 方法後,回應類型有幾種不同的選項。

  • String content():傳回回應的 String 內容

  • ChatResponse chatResponse():傳回 ChatResponse 物件,其中包含多個生成結果以及關於回應的元數據,例如用於建立回應的 token 數量。

  • entity() 以傳回 Java 類型

    • entity(ParameterizedTypeReference<T> type):用於傳回實體類型的 Collection

    • entity(Class<T> type):用於傳回特定的實體類型。

    • entity(StructuredOutputConverter<T> structuredOutputConverter):用於指定 StructuredOutputConverter 的實例,以將 String 轉換為實體類型。

您也可以調用 stream() 方法而不是 call()

stream() 傳回值

ChatClient 上指定 stream() 方法後,回應類型有幾個選項

  • Flux<String> content():傳回 AI 模型產生的字串的 Flux

  • Flux<ChatResponse> chatResponse():傳回 ChatResponse 物件的 Flux,其中包含關於回應的其他元數據。

使用預設值

@Configuration 類別中使用預設系統文字建立 ChatClient 可簡化執行階段程式碼。透過設定預設值,您只需在呼叫 ChatClient 時指定使用者文字,從而無需在執行階段程式碼路徑中為每個請求設定系統文字。

預設系統文字

在以下範例中,我們將配置系統文字,使其始終以海盜的聲音回覆。為了避免在執行階段程式碼中重複系統文字,我們將在 @Configuration 類別中建立 ChatClient 實例。

@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
                .build();
    }

}

和一個 @RestController 來調用它

@RestController
class AIController {

	private final ChatClient chatClient;

	AIController(ChatClient chatClient) {
		this.chatClient = chatClient;
	}

	@GetMapping("/ai/simple")
	public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
		return Map.of("completion", this.chatClient.prompt().user(message).call().content());
	}
}

透過 curl 呼叫應用程式端點時,結果為

❯ curl localhost:8080/ai/simple
{"completion":"Why did the pirate go to the comedy club? To hear some arrr-rated jokes! Arrr, matey!"}

帶參數的預設系統文字

在以下範例中,我們將在系統文字中使用佔位符,以在執行階段而不是設計時指定完成的聲音。

@Configuration
class Config {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
                .build();
    }

}
@RestController
class AIController {
	private final ChatClient chatClient;

	AIController(ChatClient chatClient) {
		this.chatClient = chatClient;
	}

	@GetMapping("/ai")
	Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
		return Map.of("completion",
				this.chatClient.prompt()
						.system(sp -> sp.param("voice", voice))
						.user(message)
						.call()
						.content());
	}

}

透過 httpie 呼叫應用程式端點時,結果為

http localhost:8080/ai voice=='Robert DeNiro'
{
    "completion": "You talkin' to me? Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself? Because it was two tired! Classic, right?"
}

其他預設值

ChatClient.Builder 層級,您可以指定預設提示詞配置。

  • defaultOptions(ChatOptions chatOptions):傳入 ChatOptions 類別中定義的可攜式選項或模型特定的選項,例如 OpenAiChatOptions 中的選項。如需模型特定 ChatOptions 實作的更多資訊,請參閱 JavaDocs。

  • defaultFunction(String name, String description, java.util.function.Function<I, O> function)name 用於在使用者文字中引用函數。description 解釋函數的用途,並協助 AI 模型選擇正確的函數以獲得準確的回應。function 引數是 Java 函數實例,模型會在必要時執行它。

  • defaultFunctions(String…​ functionNames):應用程式上下文中定義的 `java.util.Function`s 的 Bean 名稱。

  • defaultUser(String text)defaultUser(Resource text)defaultUser(Consumer<UserSpec> userSpecConsumer):這些方法可讓您定義使用者文字。Consumer<UserSpec> 可讓您使用 Lambda 來指定使用者文字和任何預設參數。

  • defaultAdvisors(Advisor…​ advisor):顧問允許修改用於建立 Prompt 的資料。QuestionAnswerAdvisor 實作透過將提示詞附加與使用者文字相關的上下文資訊,來啟用 Retrieval Augmented Generation 的模式。

  • defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法可讓您定義 Consumer,以使用 AdvisorSpec 配置多個顧問。顧問可以修改用於建立最終 Prompt 的資料。Consumer<AdvisorSpec> 可讓您指定 Lambda 來新增顧問,例如 QuestionAnswerAdvisor,它透過將提示詞附加基於使用者文字的相關上下文資訊來支援 Retrieval Augmented Generation

您可以使用沒有 default 前綴的相應方法在執行階段覆寫這些預設值。

  • options(ChatOptions chatOptions)

  • function(String name, String description, java.util.function.Function<I, O> function)

  • functions(String…​ functionNames)

  • user(String text)user(Resource text)user(Consumer<UserSpec> userSpecConsumer)

  • advisors(Advisor…​ advisor)

  • advisors(Consumer<AdvisorSpec> advisorSpecConsumer)

顧問

顧問 API 提供彈性且強大的方式,可在您的 Spring 應用程式中攔截、修改和增強 AI 驅動的互動。

使用使用者文字呼叫 AI 模型時,常見的模式是將上下文資料附加或擴增到提示詞。

此上下文資料可以是不同類型。常見類型包括

  • 您自己的資料:這是 AI 模型未經訓練的資料。即使模型已看到類似資料,附加的上下文資料在產生回應時也優先。

  • 對話歷史記錄:聊天模型的 API 是無狀態的。如果您告訴 AI 模型您的名字,它不會在後續互動中記住它。對話歷史記錄必須與每個請求一起傳送,以確保在產生回應時考慮先前的互動。

ChatClient 中的顧問配置

ChatClient 流暢 API 提供 AdvisorSpec 介面來配置顧問。此介面提供用於新增參數、一次設定多個參數以及將一個或多個顧問新增至鏈的方法。

interface AdvisorSpec {
    AdvisorSpec param(String k, Object v);
    AdvisorSpec params(Map<String, Object> p);
    AdvisorSpec advisors(Advisor... advisors);
    AdvisorSpec advisors(List<Advisor> advisors);
}
顧問新增至鏈的順序至關重要,因為它決定了它們的執行順序。每個顧問都會以某種方式修改提示詞或上下文,而一個顧問所做的變更會傳遞到鏈中的下一個顧問。
ChatClient.builder(chatModel)
    .build()
    .prompt()
    .advisors(
        new MessageChatMemoryAdvisor(chatMemory),
        new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())
    )
    .user(userText)
    .call()
    .content();

在此配置中,MessageChatMemoryAdvisor 將首先執行,將對話歷史記錄新增至提示詞。然後,QuestionAnswerAdvisor 將根據使用者的問題和新增的對話歷史記錄執行其搜尋,從而可能提供更相關的結果。

檢索增強生成

向量資料庫儲存 AI 模型不知道的資料。當使用者問題傳送至 AI 模型時,QuestionAnswerAdvisor 會查詢向量資料庫,以尋找與使用者問題相關的文件。

來自向量資料庫的回應會附加到使用者文字,以提供 AI 模型產生回應的上下文。

假設您已將資料載入到 VectorStore 中,您可以透過向 ChatClient 提供 QuestionAnswerAdvisor 的實例來執行檢索增強生成 (RAG)。

ChatResponse response = ChatClient.builder(chatModel)
        .build().prompt()
        .advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
        .user(userText)
        .call()
        .chatResponse();

在此範例中,SearchRequest.defaults() 將對向量資料庫中的所有文件執行相似性搜尋。為了限制搜尋的文件類型,SearchRequest 採用類似 SQL 的篩選運算式,該運算式可在所有 VectorStore 中攜帶。

動態篩選運算式

使用 FILTER_EXPRESSION 顧問上下文參數在執行階段更新 SearchRequest 篩選運算式

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
    .build();

// Update filter expression at runtime
String content = this.chatClient.prompt()
    .user("Please answer my question XYZ")
    .advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
    .call()
    .content();

FILTER_EXPRESSION 參數可讓您根據提供的運算式動態篩選搜尋結果。

聊天記憶體

介面 ChatMemory 代表聊天對話歷史記錄的儲存空間。它提供將訊息新增至對話、從對話中檢索訊息以及清除對話歷史記錄的方法。

目前有兩個實作,InMemoryChatMemoryCassandraChatMemory,它們分別提供聊天對話歷史記錄的儲存,記憶體中儲存和使用 time-to-live 持續儲存。

若要建立具有 time-to-liveCassandraChatMemory

CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());

以下顧問實作使用 ChatMemory 介面來建議具有對話歷史記錄的提示詞,它們在記憶體如何新增至提示詞的細節上有所不同

  • MessageChatMemoryAdvisor:記憶體被檢索並作為訊息集合新增至提示詞

  • PromptChatMemoryAdvisor:記憶體被檢索並新增到提示詞的系統文字中。

  • VectorStoreChatMemoryAdvisor:建構子 VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order) 此建構子可讓您

    1. 指定用於管理和查詢文件的 VectorStore 實例。

    2. 設定預設對話 ID,以便在上下文中未提供任何 ID 時使用。

    3. 以 token 大小定義聊天歷史記錄檢索的視窗大小。

    4. 提供用於聊天顧問系統的系統文字建議。

    5. 設定此顧問在鏈中的優先順序。

VectorStoreChatMemoryAdvisor.builder() 方法可讓您指定預設對話 ID、聊天歷史記錄視窗大小以及要檢索的聊天歷史記錄順序。

以下顯示使用多個顧問的範例 @Service 實作。

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

@Service
public class CustomerSupportAssistant {

    private final ChatClient chatClient;

    public CustomerSupportAssistant(ChatClient.Builder builder, VectorStore vectorStore, ChatMemory chatMemory) {

        this.chatClient = builder
            .defaultSystem("""
                    You are a customer chat support agent of an airline named "Funnair". Respond in a friendly,
                    helpful, and joyful manner.

                    Before providing information about a booking or cancelling a booking, you MUST always
                    get the following information from the user: booking number, customer first name and last name.

                    Before changing a booking you MUST ensure it is permitted by the terms.

                    If there is a charge for the change, you MUST ask the user to consent before proceeding.
                    """)
            .defaultAdvisors(
                    new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
                    new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()), // RAG
                    new SimpleLoggerAdvisor())
            .defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
            .build();
    }

    public Flux<String> chat(String chatId, String userMessageContent) {

        return this.chatClient.prompt()
                .user(userMessageContent)
                .advisors(a -> a
                        .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
                .stream().content();
    }

}

記錄

SimpleLoggerAdvisor 是一個顧問,它記錄 ChatClientrequestresponse 資料。這對於偵錯和監控您的 AI 互動非常有用。

Spring AI 支援 LLM 和向量儲存互動的可觀察性。如需更多資訊,請參閱 可觀察性 指南。

若要啟用記錄,請在建立 ChatClient 時將 SimpleLoggerAdvisor 新增至顧問鏈。建議將其新增到鏈的末端

ChatResponse response = ChatClient.create(chatModel).prompt()
        .advisors(new SimpleLoggerAdvisor())
        .user("Tell me a joke?")
        .call()
        .chatResponse();

若要查看記錄,請將顧問套件的記錄層級設定為 DEBUG

logging.level.org.springframework.ai.chat.client.advisor=DEBUG

將此新增至您的 application.propertiesapplication.yaml 檔案。

您可以使用以下建構子自訂要從 AdvisedRequestChatResponse 記錄哪些資料

SimpleLoggerAdvisor(
    Function<AdvisedRequest, String> requestToString,
    Function<ChatResponse, String> responseToString
)

範例用法

SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
    request -> "Custom request: " + request.userText,
    response -> "Custom response: " + response.getResult()
);

這可讓您根據您的特定需求調整記錄的資訊。

請謹慎記錄生產環境中的敏感資訊。