聊天客戶端 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
代表聊天對話歷史記錄的儲存空間。它提供將訊息新增至對話、從對話中檢索訊息以及清除對話歷史記錄的方法。
目前有兩個實作,InMemoryChatMemory
和 CassandraChatMemory
,它們分別提供聊天對話歷史記錄的儲存,記憶體中儲存和使用 time-to-live
持續儲存。
若要建立具有 time-to-live
的 CassandraChatMemory
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
以下顧問實作使用 ChatMemory
介面來建議具有對話歷史記錄的提示詞,它們在記憶體如何新增至提示詞的細節上有所不同
-
MessageChatMemoryAdvisor
:記憶體被檢索並作為訊息集合新增至提示詞 -
PromptChatMemoryAdvisor
:記憶體被檢索並新增到提示詞的系統文字中。 -
VectorStoreChatMemoryAdvisor
:建構子VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize, int order)
此建構子可讓您-
指定用於管理和查詢文件的 VectorStore 實例。
-
設定預設對話 ID,以便在上下文中未提供任何 ID 時使用。
-
以 token 大小定義聊天歷史記錄檢索的視窗大小。
-
提供用於聊天顧問系統的系統文字建議。
-
設定此顧問在鏈中的優先順序。
-
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
是一個顧問,它記錄 ChatClient
的 request
和 response
資料。這對於偵錯和監控您的 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.properties
或 application.yaml
檔案。
您可以使用以下建構子自訂要從 AdvisedRequest
和 ChatResponse
記錄哪些資料
SimpleLoggerAdvisor(
Function<AdvisedRequest, String> requestToString,
Function<ChatResponse, String> responseToString
)
範例用法
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText,
response -> "Custom response: " + response.getResult()
);
這可讓您根據您的特定需求調整記錄的資訊。
請謹慎記錄生產環境中的敏感資訊。 |