チャットクライアント 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 を作成します。以下は、単純なユーザーリクエストに対する 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 として返します。
複数のチャットモデルの操作
1 つのアプリケーションで複数のチャットモデルを操作する必要があるシナリオはいくつかあります。
異なる型のタスクに異なるモデルを使用する (たとえば、複雑な推論には強力なモデル、より単純なタスクにはより高速で安価なモデルなど)
1 つのモデルサービスが利用できない場合のフォールバックメカニズムの実装
異なるモデルや構成の A/B テスト
ユーザーの好みに応じてモデルの選択肢を提供する
特殊モデルを組み合わせる (1 つはコード生成用、もう 1 つはクリエイティブコンテンツ用などです。)
デフォルトでは、Spring AI は単一の ChatClient.Builder Bean を自動構成します。ただし、アプリケーションで複数のチャットモデルを扱う必要がある場合があります。その場合の対処方法を以下に示します。
単一モデル型で複数の ChatClients
このセクションでは、同じ基盤となるモデル型を使用しながら構成が異なる複数の ChatClient インスタンスを作成する必要がある一般的なユースケースについて説明します。自動構成された ChatClient.Builder はプロトタイプスコープであるため、各インジェクションポイントごとに新しいインスタンスが作成されます。
@Configuration
class ChatClientConfig {
@Bean
ChatClient defaultChatClient(ChatClient.Builder builder) {
return builder.build();
}
@Bean
ChatClient customChatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a helpful assistant.").build();
}
}さまざまなモデル型の ChatClients
複数の AI モデルを扱う場合、ChatClient.create(chatModel) や ChatClient.builder(chatModel) を使用して個別の ChatClient Bean を定義したくなるかもしれません。しかし、そうすると自動構成された ChatClient.Builder がバイパスされ、オブザーバビリティと ChatClientBuilderCustomizer Bean が無視されてしまいます。
可観測性とカスタマイザーを維持するには、ChatClientBuilderConfigurer を注入してカスタムビルダーを作成する必要があります。ChatClientBuilderConfigurer は、登録されているすべての ChatClientBuilderCustomizer Bean を適用し、可観測性を接続します。これは、自動構成が内部的に行う処理を反映したものです。
アプリケーションコンテキストに複数の ChatModel Bean が存在する場合、Spring は自動構成された ChatClient.Builder Bean の ChatModel 依存関係を曖昧さなく解決できません。これを解決するには、いずれかの ChatClient Bean を @Primary としてマークします。また、手動で定義している場合は、いずれかの ChatModel Bean を @Primary としてマークする必要があるかもしれません。あるいは、自動構成された ChatClient.Builder Bean をオーバーライドするために、独自の ChatClient.Builder Bean を定義することもできます。
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationConvention;
import org.springframework.ai.chat.client.observation.ChatClientObservationConvention;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.model.chat.client.autoconfigure.ChatClientBuilderConfigurer;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class ChatClientConfig {
@Bean
@Primary
public ChatClient openAiChatClient(OpenAiChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
return buildChatClient(chatModel, configurer, observationRegistry,
chatClientObservationConvention, advisorObservationConvention, toolCallingAdvisorBuilder);
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
return buildChatClient(chatModel, configurer, observationRegistry,
chatClientObservationConvention, advisorObservationConvention, toolCallingAdvisorBuilder);
}
private ChatClient buildChatClient(ChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
ChatClient.Builder builder = ChatClient.builder(chatModel,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
chatClientObservationConvention.getIfUnique(),
advisorObservationConvention.getIfUnique(),
toolCallingAdvisorBuilder.getIfAvailable());
return configurer.configure(builder).build();
}
} 次に、@Qualifier アノテーションを使用してこれらの Bean をアプリケーションコンポーネントに挿入できます。
@Configuration
public class ChatClientExample {
@Bean
CommandLineRunner cli(
@Qualifier("openAiChatClient") ChatClient openAiChatClient,
@Qualifier("anthropicChatClient") ChatClient anthropicChatClient) {
return args -> {
var scanner = new Scanner(System.in);
ChatClient chat;
// Model selection
System.out.println("\nSelect your AI model:");
System.out.println("1. OpenAI");
System.out.println("2. Anthropic");
System.out.print("Enter your choice (1 or 2): ");
String choice = scanner.nextLine().trim();
if (choice.equals("1")) {
chat = openAiChatClient;
System.out.println("Using OpenAI model");
} else {
chat = anthropicChatClient;
System.out.println("Using Anthropic model");
}
// Use the selected chat client
System.out.print("\nEnter your question: ");
String input = scanner.nextLine();
String response = chat.prompt(input).call().content();
System.out.println("ASSISTANT: " + response);
scanner.close();
};
}
}複数の OpenAI 互換 API エンドポイント
OpenAiChatModel のビルダーを使用すれば、複数のインスタンスを作成して、さまざまな OpenAI 互換 API に接続できます。これは、複数のプロバイダーと連携する必要がある場合に特に便利です。
@Service
public class MultiModelService {
private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);
public void multiClientFlow() {
try {
// Create a new OpenAiChatModel for Groq (Llama3)
OpenAiChatModel groqModel = OpenAiChatModel.builder()
.options(OpenAiChatOptions.builder()
.baseUrl("https://api.groq.com/openai/v1")
.apiKey(System.getenv("GROQ_API_KEY"))
.model("llama3-70b-8192")
.temperature(0.5)
.build())
.build();
// Create a new OpenAiChatModel for GPT-4
OpenAiChatModel gpt4Model = OpenAiChatModel.builder()
.options(OpenAiChatOptions.builder()
.baseUrl("https://api.openai.com")
.apiKey(System.getenv("OPENAI_API_KEY"))
.model("gpt-4")
.temperature(0.7)
.build())
.build();
// Simple prompt for both models
String prompt = "What is the capital of France?";
String groqResponse = ChatClient.builder(groqModel).build().prompt(prompt).call().content();
String gpt4Response = ChatClient.builder(gpt4Model).build().prompt(prompt).call().content();
logger.info("Groq (Llama3) response: {}", groqResponse);
logger.info("OpenAI GPT-4 response: {}", gpt4Response);
}
catch (Exception e) {
logger.error("Error in multi-client flow", e);
}
}
}ChatClient 流れるような API
ChatClient Fluent API を使用すると、オーバーロードされた prompt メソッドを使用して Fluent API を開始し、3 つの異なる方法でプロンプトを作成できます。
prompt(): 引数のないこのメソッドを使用すると、Fluent API の使用を開始して、プロンプトのユーザー、システム、その他の部分を構築できます。prompt(Prompt prompt): このメソッドはPrompt引数を受け入れ、Prompt の非 Fluent API を使用して作成したPromptインスタンスを渡すことができます。prompt(String content): これは、前のオーバーロードに似た便利なメソッドです。ユーザーのテキストコンテンツを取得します。
ChatClient のレスポンス
ChatClient API は、Fluent 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>>() {});EntityParamSpec
3 つの entity() オーバーロードはすべて、2 つの独立した動作を可能にするオプションの Consumer<EntityParamSpec> を受け入れます。
プロバイダーネイティブ構造化出力
デフォルトでは、JSON スキーマはテキスト指示としてユーザーメッセージに追加されます。useProviderStructuredOutput() が設定されている場合、スキーマは構造化制約として AI プロバイダー API に直接渡されます(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT の設定と同等)。これには、基となるモデルが StructuredOutputChatOptions をサポートしている必要があります。
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec.useProviderStructuredOutput());| ネイティブ構造化出力は、モデルやプロバイダによってサポート状況が大きく異なるため、デフォルトでは有効になっていません。特に注意すべき制限事項としては、推論モードの Ollama モデル(JSON ではなくプレーンテキストを返す場合があります)や、OpenAI におけるトップレベル配列スキーマのサポート不足などが挙げられます。詳細および回避策については、既知の制限を参照してください。 |
アドバイザーパラメーターを使用して、すべての呼び出しに対してこの設定をグローバルに行うこともできます。
ActorFilms actorFilms = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);スキーマ検証(再試行機能付き)
validateSchema() は、モデルの JSON レスポンスをエンティティスキーマと照合して検証してから返します。検証に失敗した場合、エラーメッセージがユーザープロンプトに追加され、モデルは maxRepeatAttempts 回(デフォルト: 3 回)まで再試行されます。内部的には StructuredOutputValidationAdvisor が使用されます。
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec.validateSchema());両方のオプションを組み合わせることができます。
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec
.useProviderStructuredOutput()
.validateSchema());validateSchema() がアクティブな場合、ストリーミングはサポートされません。 |
ストリーミングレスポンス
stream() メソッドを使用すると、次に示すように非同期レスポンスを取得できます。
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();Flux<ChatResponse> chatResponse() メソッドを使用して ChatResponse をストリーミングすることもできます。
将来的には、リアクティブ 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", this.converter.getFormat()))
.stream()
.content();
String content = this.flux.collectList().block().stream().collect(Collectors.joining());
List<ActorsFilms> actorFilms = this.converter.convert(this.content);プロンプトテンプレート
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 は Terence Parr が開発したオープンソースの StringTemplate (英語) エンジンをベースにした StTemplateRenderer 実装を使用します。
Spring AI は、テンプレート処理が不要な場合のために NoOpTemplateRenderer も提供します。
ChatClient (.templateRenderer() 経由)に直接設定された TemplateRenderer は、ChatClient ビルダーチェーン(例: .user()、.system() 経由)で直接定義されたプロンプトコンテンツにのみ適用されます。これは、QuestionAnswerAdvisor など、アドバイザーが内部的に使用するテンプレートには影響しません。これらのテンプレートには独自のテンプレートカスタマイズメカニズムがあります(カスタムアドバイザーテンプレートを参照)。 |
別のテンプレートエンジンをご利用になりたい場合は、TemplateRenderer インターフェースのカスタム実装を ChatClient に直接提供できます。また、デフォルトの StTemplateRenderer をカスタム設定で使い続けることもできます。
例: デフォルトでは、テンプレート変数は {} 構文で識別されます。プロンプトに 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(): レスポンスの文字列コンテンツを返しますChatResponse chatResponse(): 複数の世代と、レスポンスの作成に使用されたトークンの数など、レスポンスに関するメタデータを含むChatResponseオブジェクトを返します。ChatClientResponse chatClientResponse():ChatResponseオブジェクトと ChatClient 実行コンテキストを含むChatClientResponseオブジェクトを返します。これにより、アドバイザーの実行中に使用される追加データ (RAG フローで取得された関連ドキュメントなど) にアクセスできるようになります。entity()は Java 型を返すentity(ParameterizedTypeReference<T> type): エンティティ型のCollectionを返すために使用されます。entity(Class<T> type): 特定のエンティティ型を返すために使用されます。entity(StructuredOutputConverter<T> structuredOutputConverter):Stringをエンティティ型に変換するためにStructuredOutputConverterのインスタンスを指定するために使用されます。entity(ParameterizedTypeReference<T> type, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。entity(Class<T> type, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。entity(StructuredOutputConverter<T> converter, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。
responseEntity()は、ChatResponseと Java 型の両方を返します。これは、AI モデルの完全なレスポンス(メタデータと世代を含む)と構造化された出力エンティティの両方に 1 回の呼び出しでアクセスする必要がある場合に便利です。responseEntity(Class<T> type): 完全なChatResponseオブジェクトと特定のエンティティ型の両方を含むResponseEntityを返すために使用されます。responseEntity(Class<T> type, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。responseEntity(ParameterizedTypeReference<T> type): 完全なChatResponseオブジェクトとエンティティ型のCollectionの両方を含むResponseEntityを返すために使用されます。responseEntity(ParameterizedTypeReference<T> type, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。responseEntity(StructuredOutputConverter<T> structuredOutputConverter): 完全なChatResponseオブジェクトと、指定されたStructuredOutputConverterを使用して変換されたエンティティの両方を含むResponseEntityを返すために使用されます。responseEntity(StructuredOutputConverter<T> converter, Consumer<EntityParamSpec> spec): 上記と同様、オプションで EntityParamSpec 構成も選択可能。
call() の代わりに stream() メソッドを呼び出すこともできます。
call() メソッドの呼び出しは、実際には AI モデルの実行をトリガーしません。Spring AI に対して、同期呼び出しとストリーミング呼び出しのどちらを使用するかを指示するだけです。実際の AI モデルの呼び出しは、content()、chatResponse()、responseEntity() などのメソッドが呼び出されたときに行われます。 |
stream() の戻り値
ChatClient で stream() メソッドを指定した後、レスポンス型にはいくつかのオプションがあります。
Flux<String> content(): AI モデルによって生成される文字列のFluxを返します。Flux<ChatResponse> chatResponse(): レスポンスに関する追加のメタデータを含むChatResponseオブジェクトのFluxを返します。Flux<ChatClientResponse> chatClientResponse():ChatResponseオブジェクトと ChatClient 実行コンテキストを含むChatClientResponseオブジェクトのFluxを返します。これにより、アドバイザーの実行中に使用される追加データ (RAG フローで取得された関連ドキュメントなど) にアクセスできるようになります。
メッセージメタデータ
ChatClient は、ユーザーメッセージとシステムメッセージの両方にメタデータを追加できます。メタデータは、AI モデルや下流処理で使用できるメッセージに関する追加のコンテキストと情報を提供します。
ユーザーメッセージへのメタデータの追加
metadata() メソッドを使用して、ユーザーメッセージにメタデータを追加できます。
// Adding individual metadata key-value pairs
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata("messageId", "msg-123")
.metadata("userId", "user-456")
.metadata("priority", "high"))
.call()
.content();
// Adding multiple metadata entries at once
Map<String, Object> userMetadata = Map.of(
"messageId", "msg-123",
"userId", "user-456",
"timestamp", System.currentTimeMillis()
);
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata(userMetadata))
.call()
.content();システムメッセージへのメタデータの追加
同様に、システムメッセージにメタデータを追加することもできます。
// Adding metadata to system messages
String response = chatClient.prompt()
.system(s -> s.text("You are a helpful assistant.")
.metadata("version", "1.0")
.metadata("model", "gpt-4"))
.user("Tell me a joke")
.call()
.content();デフォルトのメタデータのサポート
ChatClient ビルダーレベルでデフォルトのメタデータを構成することもできます。
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem(s -> s.text("You are a helpful assistant")
.metadata("assistantType", "general")
.metadata("version", "1.0"))
.defaultUser(u -> u.text("Default user context")
.metadata("sessionId", "default-session"))
.build();
}
}メタデータ検証
ChatClient はメタデータを検証してデータの整合性を確保します。
メタデータキーは null または空にできません
メタデータ値は null にできません
マップを渡す場合、キーも値も null 要素を含むことはできません。
// This will throw an IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata(null, "value")) // Invalid: null key
.call()
.content();
// This will also throw an IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata("key", null)) // Invalid: null value
.call()
.content();デフォルトの使用
@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 を参照してください。defaultTools(Object… tools): すべてのリクエストで使用できるデフォルトツールを 1 つ以上登録します。ToolCallback、ToolCallbackProvider、@Toolアノテーション付きメソッドを持つ POJO の混在を受け入れます。defaultToolContext(Map<String, Object> toolContext): ツール実行時のデフォルトコンテキストを設定します。defaultSystem(String text)、defaultSystem(Resource text)、defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer): これらの方法を使用すると、デフォルトのシステムテキストを定義できます。defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer): これらのメソッドを使用すると、ユーザーテキストを定義できます。Consumer<UserSpec>では、ラムダを使用してユーザーテキストと既定のパラメーターを指定できます。defaultTemplateRenderer(TemplateRenderer templateRenderer): プロンプトテンプレートのデフォルトをTemplateRendererに設定します。defaultAdvisors(Advisor… advisor),defaultAdvisors(List<Advisor> advisors): アドバイザーを使用すると、Promptの作成に使用されるデータを変更できます。QuestionAnswerAdvisorの実装では、プロンプトにユーザーテキストに関連するコンテキスト情報を追加することで、Retrieval Augmented Generationのパターンを実現できます。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer): このメソッドを使用すると、AdvisorSpecを使用して複数のアドバイザーを構成するためのConsumerを定義できます。アドバイザーは、最終的なPromptを作成するために使用されるデータを変更できます。Consumer<AdvisorSpec>を使用すると、ユーザーテキストに基づいて関連するコンテキスト情報をプロンプトに追加することでRetrieval Augmented GenerationをサポートするQuestionAnswerAdvisorなどのアドバイザーを追加するラムダを指定できます。
実行時に、default プレフィックスなしの対応するメソッドを使用して、これらのデフォルトをオーバーライドできます。
options(ChatOptions.Builder optionsCustomizer)tools(Object… tools)toolContext(Map<String, Object> toolContext)messages(Message… messages),messages(List<Message> messages)system(String text)、system(Resource text)、system(Consumer<PromptSystemSpec> systemSpecConsumer)user(String text)、user(Resource text)、user(Consumer<UserSpec> userSpecConsumer)templateRenderer(TemplateRenderer templateRenderer)advisors(Advisor… advisor),advisors(List<Advisor> advisors)advisors(Consumer<AdvisorSpec> advisorSpecConsumer)

ChatClient の変異
mutate() 方式を使用すると、既存の ChatClient (または ChatClientRequestSpec)の設定を複製して新しい ChatClient (または ChatClientRequestSpec)を作成できます。
ChatClientにて:Builder mutate()は、クライアントのデフォルト設定で初期化されたChatClient.Builderを返します。ChatClientRequestSpecにて:Builder mutate()は、リクエストの現在の設定で初期化されたChatClient.Builderを返します。
これは、すべてのオプションを再定義することなく、派生クライアントやリクエストを作成する場合に便利です。
アドバイザー
アドバイザー API は、Spring アプリケーションで AI 駆動型のインタラクションをインターセプト、変更、強化するための柔軟かつ強力な方法を提供します。
ユーザーテキストを使用して AI モデルを呼び出す場合の一般的なパターンは、プロンプトにコンテキストデータを追加または拡張することです。
このコンテキストデータにはさまざまな種類があります。一般的な種類は次のとおりです。
あなた自身のデータ : これは、AI モデルがトレーニングされていないデータです。モデルが同様のデータを見たことがある場合でも、レスポンスの生成では追加されたコンテキストデータが優先されます。
会話履歴 : チャットモデルの API はステートレスです。AI モデルに名前を伝えても、その後のやり取りではその名前は記憶されません。レスポンスを生成する際に以前のやり取りが考慮されるようにするには、各リクエストで会話履歴を送信する必要があります。
ChatClient のアドバイザー構成
ChatClient Fluent API は、アドバイザーを構成するための AdvisorSpec インターフェースを提供します。このインターフェースは、パラメーターを追加したり、複数のパラメーターを一度に設定したり、1 つ以上のアドバイザーを チェーンに追加したりするためのメソッドを提供します。
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}| アドバイザーが チェーンに追加される順序は、アドバイザーの実行順序を決定するため重要です。各アドバイザーはプロンプトまたはコンテキストを何らかの方法で変更し、1 つのアドバイザーによって行われた変更は チェーンの次のアドバイザーに渡されます。 |
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(a -> a
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(userText)
.call()
.content(); この構成では、最初に MessageChatMemoryAdvisor が実行され、プロンプトに会話履歴が追加されます。次に、QuestionAnswerAdvisor がユーザーの質問と追加された会話履歴に基づいて検索を実行し、より関連性の高い結果が提供される可能性が高くなります。
メモリアドバイザーを使用するすべての呼び出しにおいて、ChatMemory.CONVERSATION_ID は .param() を介して指定する必要があります。これを省略すると、実行時に IllegalArgumentException 例外が発生します。 |
検索拡張生成
検索拡張生成ガイドを参照してください。
ログ
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<ChatClientRequest, String> requestToString,
Function<ChatResponse, String> responseToString,
int order
)使用例:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.prompt().getUserMessage(),
response -> "Custom response: " + response.getResult(),
0
);これにより、ログに記録された情報を特定のニーズに合わせてカスタマイズできます。
| 運用環境で機密情報を記録する場合は注意してください。 |
ツール呼び出し
ChatClient は、自動登録が明示的に無効になっていない限り(下記参照)、常にアドバイザーチェーンに ToolCallingAdvisor を自動登録します。これにより、呼び出し時に静的ツールが設定されていない場合でも、別のアドバイザーによって実行時に動的に注入されたツールが正しく処理されることが保証されます。
String response = ChatClient.builder(chatModel)
.build()
.prompt("What day is tomorrow?")
.tools(new DateTimeTools()) // ToolCallingAdvisor is auto-registered
.call()
.content();ToolCallingAdvisor はデフォルトで Ordered.HIGHEST_PRECEDENCE + 300 に登録されます。
自動登録を無効にする
世界的に (すべての呼び出し)
自動構成された ChatClient からのすべての呼び出しに対して自動登録を無効にするように spring.ai.chat.client.tool-calling.enabled=false を設定します。
spring.ai.chat.client.tool-calling.enabled=falseこのプロパティが設定されている場合、ツールは定義として AI モデルに送信されますが、レスポンス内のツール呼び出しは自動的には実行されません。ループを自分で制御するには、ユーザー制御によるツール実行を使用してください。
呼び出しごとに
AdvisorParams.toolCallingAdvisorAutoRegister(false) を使用すると、1 回の呼び出しの自動登録を無効にできます。
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
.call()
.content(); または、独自の ToolCallingAdvisor (または ToolAdvisor を実装する他のアドバイザー) を提供することもできます。その場合、チェーンにはすでに ToolAdvisor が含まれているため、自動登録は自動的に抑制されます。
.advisors() を介してカスタムアドバイザーを渡すことができます。
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.advisors(customAdvisor) // auto-registration suppressed
.call()
.content();デフォルトの ToolCallingAdvisor をカスタマイズする
spring.ai.chat.client.tool-calling.* のプロパティは、自動構成された ToolCallingAdvisor を制御します。
| プロパティ | デフォルト | 説明 |
|---|---|---|
|
| すべての呼び出しで |
|
| アドバイザーチェーンにおける自動登録された |
Spring Boot: アドバイザーがプロパティをオーダーします
自動登録アドバイザーを調整する最も簡単な方法は、spring.ai.chat.client.tool-calling.advisor-order プロパティを使用することです。このプロパティは、アドバイザーチェーン内のどこに ToolCallingAdvisor が挿入されるかを制御します。
spring.ai.chat.client.tool-calling.advisor-order=0 この値は、ツール呼び出しループ内で実行されるアドバイザー(つまり、各イテレーションで繰り返されるアドバイザー)の次数よりも小さく、ループ外で実行されるアドバイザー(つまり、ユーザーリクエストごとに一度だけ実行されるアドバイザー)の次数よりも大きくなければなりません。デフォルト値は ToolCallingAdvisor.DEFAULT_ORDER です。
Spring Boot: ユーザー制御によるツール実行
ツール呼び出しループの各イテレーションを監視するには(たとえば、中間チャンクを UI に転送する場合など)、呼び出しごとに自動登録されるアドバイザーを無効にし、AdvisorParams.toolCallingAdvisorAutoRegister(false) を使用してループを自分で制御します。完全な例については、ユーザー制御ツールの実行 — ChatClient 付を参照してください。
Spring Boot: カスタム ToolCallingAdvisor.Builder Bean
より詳細なカスタマイズについては — たとえば、カスタム ToolCallingManager を供給する — ToolCallingAdvisor.Builder<?> Bean を宣言します。自動構成された Bean 上の Spring Boot の @ConditionalOnMissingBean は、Bean が優先されることを意味します。
@Bean
ToolCallingAdvisor.Builder<?> toolCallingAdvisorBuilder(ToolCallingManager myToolCallingManager) {
return ToolCallingAdvisor.builder()
.toolCallingManager(myToolCallingManager)
.advisorOrder(Ordered.LOWEST_PRECEDENCE);
}ChatClient.Builder Bean は、このカスタムビルダーによって自動的に接続されます。
Spring Boot なし
Spring Boot の自動構成を使用しない場合は、事前に構成済みの ToolCallingAdvisor.Builder を ChatClient.builder() に直接渡します。
ToolCallingManager customManager = ToolCallingManager.builder()
// custom resolver, exception processor, etc.
.build();
ChatClient chatClient = ChatClient
.builder(chatModel, observationRegistry, null, null,
ToolCallingAdvisor.builder().toolCallingManager(customManager))
.build();
// Every prompt that registers tools will use customManager in the auto-registered advisor
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();ToolAdvisor マーカーインターフェース
ToolAdvisor は、ChatClient に対して、アドバイザチェーンがすでにツール実行を処理していることを通知するマーカーインターフェースです。カスタムアドバイザにこのインターフェースを実装すると、2 つ目の ToolCallingAdvisor の自動登録が防止されます。
詳細については、アドバイザー制御ツール実行を参照してください。
チャットメモリ
ChatMemory インターフェースは、チャット会話メモリのストレージを表します。会話にメッセージを追加したり、会話からメッセージを取得したり、会話履歴を消去したりするためのメソッドを提供します。
現在、組み込み実装は MessageWindowChatMemory が 1 つあります。
MessageWindowChatMemory は、指定された最大サイズ(デフォルト: 20 件)までのメッセージウィンドウを維持するチャットメモリ実装です。メッセージ数がこの制限を超えると、古いメッセージは削除されますが、システムメッセージは保持されます。新しいシステムメッセージが追加されると、それ以前のシステムメッセージはすべてメモリから削除されます。これにより、メモリ使用量を抑えながら、会話で常に最新のコンテキストを利用できるようになります。
MessageWindowChatMemory は、チャット会話メモリ用のストレージ実装を提供する ChatMemoryRepository 抽象化を基盤としています。InMemoryChatMemoryRepository、JdbcChatMemoryRepository、CassandraChatMemoryRepository、Neo4jChatMemoryRepository、MongoChatMemoryRepository、RedisChatMemoryRepository を含む複数の実装が利用可能です。
ChatMemory.CONVERSATION_ID パラメーターは、すべてのメモリアドバイザで必須です。.advisors(a → a.param(ChatMemory.CONVERSATION_ID, conversationId)) を介してすべての呼び出しで指定する必要があります。省略すると、IllegalArgumentException 例外が発生します。デフォルトの会話 ID はありません。 |
詳細と使用例については、チャットメモリのドキュメントを参照してください。
実装上の注意
ChatClient における命令型プログラミングモデルとリアクティブプログラミングモデルの併用は、この API のユニークな特徴です。多くの場合、アプリケーションはリアクティブか命令型のいずれか一方のみで構成されますが、両方が使用されることはありません。
モデル実装の HTTP クライアント相互作用をカスタマイズする場合は、RestClient と WebClient の両方を構成する必要があります。
ストリーミングは Reactive スタック経由でのみサポートされます。そのため、命令型アプリケーションには Reactive スタックを含める必要があります(例: spring-boot-starter-webflux)。
非ストリーミングはサーブレットスタック経由でのみサポートされます。このため、リアクティブアプリケーションはサーブレットスタック(例: spring-boot-starter-web)を組み込む必要があり、一部の呼び出しがブロッキングされることを想定しています。
ツール呼び出しは必須であるため、ワークフローがブロックされる可能性があります。また、Micrometer 観測が不完全または中断される結果にもなります(例: ChatClient スパンとツール呼び出しスパンが接続されておらず、最初のスパンが不完全なままになる)。
組み込みアドバイザーは、標準呼び出しに対してはブロッキング操作を、ストリーミング呼び出しに対してはノンブロッキング操作を実行します。アドバイザーのストリーミング呼び出しに使用される Reactor スケジューラは、各アドバイザークラスのビルダーから設定できます。