チャットクライアント API
ChatClient
は、AI モデルと通信するための流れるような API を提供します。同期プログラミングモデルとリアクティブプログラミングモデルの両方をサポートします。
Fluent API には、AI モデルに入力として渡されるプロンプトの構成要素を構築するためのメソッドがあります。Prompt
には、AI モデルの出力と動作をガイドする指示テキストが含まれています。API の観点から見ると、プロンプトはメッセージのコレクションで構成されます。
AI モデルは、ユーザーからの直接入力であるユーザーメッセージと、会話をガイドするためにシステムによって生成されるシステムメッセージという 2 種類の主なメッセージを処理します。
これらのメッセージには、多くの場合、ユーザー入力に基づいて実行時に置換され、ユーザー入力に対する AI モデルのレスポンスをカスタマイズするプレースホルダーが含まれます。
使用する AI モデルの名前や、生成される出力のランダム性や創造性を制御する温度設定など、指定できるプロンプトオプションもあります。
ChatClient の作成
ChatClient
は、ChatClient.Builder
オブジェクトを使用して作成されます。任意の ChatModel Spring Boot 自動構成に対して自動構成された ChatClient.Builder
インスタンスを取得するか、プログラムで作成することができます。
自動構成された ChatClient.Builder を使用する
最も単純な使用例では、Spring AI は Spring Boot の自動構成を提供し、クラスに挿入するためのプロトタイプ ChatClient.Builder
Bean を作成します。以下は、単純なユーザーリクエストに対する文字列レスポンスを取得する簡単な例です。
@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 モデルのレスポンスを文字列として返します。
プログラムで ChatClient を作成する
プロパティ spring.ai.chat.client.enabled=false
を設定することで、ChatClient.Builder
の自動構成を無効にすることができます。これは、複数のチャットモデルを一緒に使用する場合に便利です。次に、プログラムですべての ChatModel
に対して ChatClient.Builder
インスタンスを作成します。
ChatModel myChatModel = ... // usually autowired
ChatClient.Builder builder = ChatClient.builder(myChatModel);
// or create a ChatClient with the default builder settings:
ChatClient chatClient = ChatClient.create(myChatModel);
ChatClient のレスポンス
ChatClient API は、AI モデルからのレスポンスをフォーマットするいくつかの方法を提供します。
ChatResponse の返却
AI モデルからのレスポンスは、型 ChatResponse で定義されるリッチ構造です。レスポンスがどのように生成されたかに関するメタデータが含まれており、それぞれ独自のメタデータを持つ複数のレスポンス ( 世代と呼ばれる) を含めることもできます。メタデータには、レスポンスの作成に使用されたトークンの数 (各トークンは 1 ワードの約 3/4 です) が含まれます。ホスト型 AI モデルは、リクエストごとに使用されるトークンの数に基づいて課金されるため、この情報は重要です。
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(ParameterizedTypeReference<T> type)
を持つオーバーロードされた entity
メソッドもあり、これを使用すると、ジェネリクスリストなどの型を指定できます。
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
をストリーミングすることもできます。
1.0.0 M2 では、リアクティブ stream()
メソッドを使用して Java エンティティを返すことができる便利なメソッドを提供します。それまでの間、以下に示すように、構造化出力コンバーターを使用して集約されたレスポンスを明示的に変換する必要があります。これは、ドキュメントの後のセクションで詳しく説明する Fluent 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", converter.getFormat()))
.stream()
.content();
String content = flux.collectList().block().stream().collect(Collectors.joining());
List<ActorFilms> actorFilms = converter.convert(content);
call() の戻り値
ChatClient
で call
メソッドを指定した後、レスポンス型にはいくつかの異なるオプションがあります。
String content()
: レスポンスの文字列コンテンツを返しますChatResponse chatResponse()
: 複数の世代と、レスポンスの作成に使用されたトークンの数など、レスポンスに関するメタデータを含むChatResponse
オブジェクトを返します。entity
は Java 型を返すentity(ParameterizedTypeReference<T> type): エンティティ型のコレクションを返すために使用されます。
entity(Class<T> type): 特定のエンティティ型を返すために使用されます。
entity(StructuredOutputConverter<T> structuredOutputConverter):
String
をエンティティ型に変換するためにStructuredOutputConverter
のインスタンスを指定するために使用されます。
call
の代わりに stream
メソッドを呼び出すこともできます
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", chatClient.prompt().user(message).call().content());
}
}
curl 経由で呼び出すと
❯ curl localhost:8080/ai/simple
{"generation":"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",
chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
}
回答は
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` の Bean 名。defaultUser(String text)
、defaultUser(Resource text)
、defaultUser(Consumer<UserSpec> userSpecConsumer)
: これらのメソッドを使用すると、ユーザーテキストを定義できます。Consumer<UserSpec>
では、ラムダを使用してユーザーテキストと既定のパラメーターを指定できます。defaultAdvisors(RequestResponseAdvisor… advisor)
: アドバイザーを使用すると、Prompt
の作成に使用されるデータを変更することができます。QuestionAnswerAdvisor
実装では、プロンプトにユーザーテキストに関連するコンテキスト情報を追加することで、Retrieval Augmented Generation
のパターンが有効になります。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)
: このメソッドを使用すると、AdvisorSpec
を使用して複数のアドバイザーを構成するためのConsumer
を定義できます。アドバイザーは、最終的なPrompt
を作成するために使用されるデータを変更できます。Consumer<AdvisorSpec>
を使用すると、ユーザーテキストに基づいて関連するコンテキスト情報をプロンプトに追加することでRetrieval Augmented Generation
をサポートするQuestionAnswerAdvisor
などのアドバイザーを追加するラムダを指定できます。
実行時に、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(RequestResponseAdvisor… advisor)
advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
アドバイザー
ユーザーテキストを使用して AI モデルを呼び出す場合の一般的なパターンは、プロンプトにコンテキストデータを追加または拡張することです。
このコンテキストデータにはさまざまな種類があります。一般的な種類は次のとおりです。
あなた自身のデータ : これは、AI モデルがトレーニングされていないデータです。モデルが同様のデータを見たことがある場合でも、レスポンスの生成では追加されたコンテキストデータが優先されます。
会話履歴 : チャットモデルの API はステートレスです。AI モデルに名前を伝えても、その後のやり取りではその名前は記憶されません。レスポンスを生成する際に以前のやり取りが考慮されるようにするには、各リクエストで会話履歴を送信する必要があります。
検索拡張生成
ベクトルデータベースには、AI モデルが認識していないデータが格納されます。ユーザーの質問が AI モデルに送信されると、QuestionAnswerAdvisor
はベクトルデータベースに対してユーザーの質問に関連するドキュメントを照会します。
ベクトルデータベースからのレスポンスはユーザーテキストに追加され、AI モデルがレスポンスを生成するためのコンテキストを提供します。
すでに VectorStore
にデータをロードしていると仮定すると、QuestionAnswerAdvisor
のインスタンスを ChatClient
に提供することで、検索拡張生成 (RAG) を実行できます。
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.user(userText)
.call()
.chatResponse();
この例では、SearchRequest.defaults()
は ベクトルデータベース内のすべてのドキュメントに対して類似性検索を実行します。検索するドキュメントの種類を制限するために、SearchRequest
はすべての VectorStores
間で移植可能な SQL のようなフィルター式を使用します。
動的フィルター式
FILTER_EXPRESSION
アドバイザーコンテキストパラメーターを使用して、実行時に SearchRequest
フィルター式を更新します。
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()))
.build();
// Update filter expression at runtime
String content = chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();
FILTER_EXPRESSION
パラメーターを使用すると、指定された式に基づいて検索結果を動的にフィルタリングできます。
チャットメモリ
インターフェース ChatMemory
は、チャット会話履歴のストレージを表します。会話にメッセージを追加したり、会話からメッセージを取得したり、会話履歴をクリアしたりするメソッドを提供します。
チャットの会話履歴をメモリ内に保存し、それぞれ time-to-live
で永続化する、InMemoryChatMemory
と CassandraChatMemory
の 2 つの実装があります。
time-to-live
で CassandraChatMemory
を作成するには
CassandraChatMemory.create(CassandraChatMemoryConfig.builder().withTimeToLive(Duration.ofDays(1)).build());
以下のアドバイザー実装は、ChatMemory
インターフェースを使用して会話履歴を含むプロンプトをアドバイスしますが、メモリがプロンプトに追加される方法の詳細は異なります。
MessageChatMemoryAdvisor
: メモリが取得され、メッセージのコレクションとしてプロンプトに追加されますPromptChatMemoryAdvisor
: メモリが取得され、プロンプトのシステムテキストに追加されます。VectorStoreChatMemoryAdvisor
: コンストラクターVectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize)
を使用すると、チャット履歴を取得する VectorStore、一意の会話 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 PromptChatMemoryAdvisor(chatMemory),
// new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()),
new LoggingAdvisor()) // RAG
.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 のやり取りのデバッグや監視に役立ちます。
ログ記録を有効にするには、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
)
使用例:
javaCopySimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.userText,
response -> "Custom response: " + response.getResult()
);
これにより、ログに記録された情報を特定のニーズに合わせてカスタマイズできます。
運用環境で機密情報を記録する場合は注意してください。 |