聊天客戶端 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)
此建構函式允許您-
指定用於管理和查詢文件的 VectorStore 例項。
-
設定在上下文中未提供預設對話 ID 時使用的預設對話 ID。
-
根據 token 大小定義聊天曆史檢索的視窗大小。
-
提供用於聊天 advisor 系統的系統文字建議。
-
設定此 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
是一個記錄 ChatClient
的 request
和 response
資料的 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.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()
);
這使您可以根據您的特定需求定製記錄的資訊。
在生產環境中記錄敏感資訊時要小心。 |
聊天記憶 (已棄用)
有關當前特性和功能的更多資訊,請參閱新的 聊天記憶 文件。 |
介面 ChatMemory
表示聊天對話歷史的儲存。它提供了向對話新增訊息、從對話檢索訊息以及清除對話歷史的方法。
目前有四種實現:InMemoryChatMemory
、CassandraChatMemory
、Neo4jChatMemory
和 JdbcChatMemory
,它們分別提供了記憶體中、在 Cassandra 中帶 time-to-live
持久化以及在 Neo4j 和 Jdbc 中不帶 time-to-live
持久化的聊天對話歷史儲存。
CassandraChatMemory
建立帶 time-to-live
的 CassandraChatMemory
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
Neo4jChatMemory
Neo4j 聊天記憶支援以下配置引數
屬性 | 描述 | 預設值 |
---|---|---|
|
儲存訊息的節點標籤 |
|
|
儲存對話會話的節點標籤 |
|
|
儲存工具呼叫的節點標籤,例如在 Assistant Messages 中 |
|
|
儲存訊息元資料的節點標籤 |
|
|
儲存工具響應的節點標籤 |
|
|
儲存與訊息相關的媒體的節點標籤 |
|
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
表。目前,僅支援 postgresql
和 mariadb
。
要停用模式初始化,請將屬性 spring.ai.chat.memory.jdbc.initialize-schema
設定為 false
。
在某些情況下,您使用 Liquibase 或 Flyway 等資料庫遷移工具來管理資料庫模式。在這種情況下,您可以停用模式初始化,只需參考 這些 sql 檔案 並將它們新增到您的遷移指令碼中。