聊天客戶端 API

ChatClient 提供了一個流暢的 API,用於與 AI 模型通訊。它支援同步和流式程式設計模型。

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

AI 模型處理兩種主要型別的訊息:使用者訊息(來自使用者的直接輸入)和系統訊息(系統生成用於引導對話)。

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

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

建立 ChatClient

ChatClient 是使用 ChatClient.Builder 物件建立的。您可以獲取任何 聊天模型 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 方法以三種不同的方式建立 Prompt,從而啟動流暢 API

  • prompt(): 這個無引數方法讓您開始使用流暢 API,允許您構建使用者、系統和 Prompt 的其他部分。

  • prompt(Prompt prompt): 這個方法接受一個 Prompt 引數,允許您傳入使用 Prompt 的非流暢 API 建立的 Prompt 例項。

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

ChatClient 響應

ChatClient API 提供了幾種使用流暢 API 格式化 AI 模型響應的方式。

返回 ChatResponse

AI 模型的響應是由型別 ChatResponse 定義的豐富結構。它包含有關響應如何生成的元資料,並且還可以包含多個響應,稱為 Generations,每個響應都有自己的元資料。元資料包括用於建立響應的 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);

Prompt 模板

ChatClient 流暢 API 允許您將使用者和系統文字作為模板提供,其中包含在執行時替換的變數。

String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            .text("Tell me the names of 5 movies whose soundtrack was composed by {composer}")
            .param("composer", "John Williams"))
    .call()
    .content();

在內部,ChatClient 使用 PromptTemplate 類來處理使用者和系統文字,並依靠給定的 TemplateRenderer 實現將變數替換為執行時提供的值。預設情況下,Spring AI 使用 StTemplateRenderer 實現,該實現基於 Terence Parr 開發的開源 StringTemplate 引擎。

Spring AI 還提供了一個 NoOpTemplateRenderer,適用於不需要模板處理的情況。

Spring AI 也提供了一個 NoOpTemplateRenderer

直接在 ChatClient 上配置的 TemplateRenderer (透過 .templateRenderer()) 僅應用於直接在 ChatClient 構建器鏈中定義的 Prompt 內容(例如,透過 .user(), .system())。它影響 Advisors 內部使用的模板,例如 QuestionAnswerAdvisor,它們有自己的模板自定義機制(請參閱 自定義 Advisor 模板)。

如果您更喜歡使用不同的模板引擎,可以直接向 ChatClient 提供 TemplateRenderer 介面的自定義實現。您也可以繼續使用預設的 StTemplateRenderer,但進行自定義配置。

例如,預設情況下,模板變數由 {} 語法標識。如果您計劃在 Prompt 中包含 JSON,您可能希望使用不同的語法來避免與 JSON 語法衝突。例如,您可以使用 <> 分隔符。

String answer = ChatClient.create(chatModel).prompt()
    .user(u -> u
            .text("Tell me the names of 5 movies whose soundtrack was composed by <composer>")
            .param("composer", "John Williams"))
    .templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build())
    .call()
    .content();

call() 返回值

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

  • String content(): 返回響應的 String 內容

  • ChatResponse chatResponse(): 返回 ChatResponse 物件,該物件包含多個生成結果以及有關響應的元資料,例如用於建立響應的 token 數量。

  • ChatClientResponse chatClientResponse(): 返回一個 ChatClientResponse 物件,該物件包含 ChatResponse 物件和 ChatClient 執行上下文,使您能夠訪問在執行 advisors 期間使用的額外資料(例如,在 RAG 流程中檢索到的相關文件)。

  • 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,該物件包含有關響應的額外元資料。

  • Flux<ChatClientResponse> chatClientResponse(): 返回 ChatClientResponse 物件的 Flux,該物件包含 ChatResponse 物件和 ChatClient 執行上下文,使您能夠訪問在執行 advisors 期間使用的額外資料(例如,在 RAG 流程中檢索到的相關文件)。

使用預設值

@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 級別,您可以指定預設的 Prompt 配置。

  • 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` bean 名稱。

  • defaultUser(String text), defaultUser(Resource text), defaultUser(Consumer<UserSpec> userSpecConsumer): 這些方法允許您定義使用者文字。Consumer<UserSpec> 允許您使用 lambda 表示式指定使用者文字和任何預設引數。

  • defaultAdvisors(Advisor…​ advisor): Advisors 允許修改用於建立 Prompt 的資料。QuestionAnswerAdvisor 實現透過將與使用者文字相關的上下文資訊附加到 Prompt 來啟用 檢索增強生成 模式。

  • defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer): 此方法允許您定義一個 Consumer,以使用 AdvisorSpec 配置多個 advisors。Advisors 可以修改用於建立最終 Prompt 的資料。Consumer<AdvisorSpec> 允許您指定 lambda 表示式來新增 advisors,例如 QuestionAnswerAdvisor,它透過根據使用者文字將相關上下文資訊附加到 Prompt 來支援 檢索增強生成

您可以在執行時使用不帶 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)

Advisors

Advisors API 提供了一種靈活而強大的方式來攔截、修改和增強 Spring 應用程式中的 AI 驅動互動。

使用使用者文字呼叫 AI 模型時的一個常見模式是使用上下文資料附加或增強 Prompt。

這種上下文資料可以是不同型別。常見型別包括

  • 您自己的資料: 這是 AI 模型未經過訓練的資料。即使模型見過類似資料,附加的上下文資料在生成響應時也具有優先權。

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

ChatClient 中的 Advisor 配置

ChatClient 流暢 API 提供了 AdvisorSpec 介面用於配置 advisors。該介面提供了新增引數、一次設定多個引數以及向鏈中新增一個或多個 advisors 的方法。

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

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

有關如何結合 advisors 使用 ChatMemory 介面管理對話歷史的更多資訊,請參閱 聊天記憶 文件。

以下 advisor 實現使用 ChatMemory 介面為 Prompt 提供對話歷史,它們在記憶體如何新增到 Prompt 的細節上有所不同

  • MessageChatMemoryAdvisor : 記憶體被檢索並作為訊息集合新增到 Prompt 中

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

  • VectorStoreChatMemoryAdvisor : 建構函式 VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order) 此建構函式允許您

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

    2. 設定在上下文中未提供預設對話 ID 時使用的預設對話 ID。

    3. 根據 token 大小定義聊天曆史檢索的視窗大小。

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

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

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

下面顯示了一個使用多個 advisors 的 @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), // 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 資料的 advisor。這對於除錯和監控您的 AI 互動非常有用。

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

要啟用日誌記錄,請在建立 ChatClient 時將 SimpleLoggerAdvisor 新增到 advisor 鏈中。建議將其新增到鏈的末尾

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

要檢視日誌,請將 advisor 包的日誌級別設定為 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()
);

這使您可以根據您的特定需求定製記錄的資訊。

在生產環境中記錄敏感資訊時要小心。

聊天記憶 (已棄用)

有關當前特性和功能的更多資訊,請參閱新的 聊天記憶 文件。

介面 ChatMemory 表示聊天對話歷史的儲存。它提供了向對話新增訊息、從對話檢索訊息以及清除對話歷史的方法。

目前有四種實現:InMemoryChatMemoryCassandraChatMemoryNeo4jChatMemoryJdbcChatMemory,它們分別提供了記憶體中、在 Cassandra 中帶 time-to-live 持久化以及在 Neo4j 和 Jdbc 中不帶 time-to-live 持久化的聊天對話歷史儲存。

CassandraChatMemory

建立帶 time-to-liveCassandraChatMemory

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

Neo4jChatMemory

Neo4j 聊天記憶支援以下配置引數

屬性 描述 預設值

spring.ai.chat.memory.neo4j.messageLabel

儲存訊息的節點標籤

Message

spring.ai.chat.memory.neo4j.sessionLabel

儲存對話會話的節點標籤

Session

spring.ai.chat.memory.neo4j.toolCallLabel

儲存工具呼叫的節點標籤,例如在 Assistant Messages 中

ToolCall

spring.ai.chat.memory.neo4j.metadataLabel

儲存訊息元資料的節點標籤

Metadata

spring.ai.chat.memory.neo4j.toolResponseLabel

儲存工具響應的節點標籤

ToolResponse

spring.ai.chat.memory.neo4j.mediaLabel

儲存與訊息相關的媒體的節點標籤

ToolResponse

JdbcChatMemory

建立 JdbcChatMemory

JdbcChatMemory.create(JdbcChatMemoryConfig.builder().jdbcTemplate(jdbcTemplate).build());

JdbcChatMemory 也可以透過將以下依賴項新增到您的專案來自動配置(前提是您有 JdbcTemplate bean)

到您的 Maven pom.xml 檔案

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-jdbc</artifactId>
</dependency>

或到您的 Gradle build.gradle 檔案

dependencies {
    implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-jdbc'
}

自動配置將預設根據 JDBC 驅動程式自動建立 ai_chat_memory 表。目前,僅支援 postgresqlmariadb

要停用模式初始化,請將屬性 spring.ai.chat.memory.jdbc.initialize-schema 設定為 false

在某些情況下,您使用 Liquibase 或 Flyway 等資料庫遷移工具來管理資料庫模式。在這種情況下,您可以停用模式初始化,只需參考 這些 sql 檔案 並將它們新增到您的遷移指令碼中。