このバージョンはまだ開発中であり、まだ安定しているとは考えられていません。最新のスナップショットバージョンについては、Spring AI 1.0.3 を使用してください。

チャットクライアント API

ChatClient は、AI モデルと通信するためのスムーズな API を提供します。同期プログラミングモデルとストリーミングプログラミングモデルの両方をサポートします。

ChatClient における命令型プログラミングモデルとリアクティブプログラミングモデルの併用については、このドキュメントの下部にある実装上の注意を参照してください。

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 を自動構成します。ただし、アプリケーションで複数のチャットモデルを扱う必要がある場合があります。その場合の対処方法を以下に示します。

いずれの場合も、プロパティ spring.ai.chat.client.enabled=false を設定して ChatClient.Builder 自動構成を無効にする必要があります。

これにより、複数の ChatClient インスタンスを手動で作成できるようになります。

単一モデル型で複数の ChatClients

このセクションでは、すべて同じ基礎モデル型を使用するが構成が異なる複数の ChatClient インスタンスを作成する必要がある一般的なユースケースについて説明します。

// Create ChatClient instances programmatically
ChatModel myChatModel = ... // already autoconfigured by Spring Boot
ChatClient chatClient = ChatClient.create(myChatModel);

// Or use the builder for more control
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
    .defaultSystemPrompt("You are a helpful assistant.")
    .build();

さまざまなモデル型の ChatClients

複数の AI モデルを操作する場合は、モデルごとに個別の ChatClient Bean を定義できます。

import org.springframework.ai.chat.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatClientConfig {

    @Bean
    public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
        return ChatClient.create(chatModel);
    }

    @Bean
    public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {
        return ChatClient.create(chatModel);
    }
}

次に、@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 エンドポイント

OpenAiApi クラスと OpenAiChatModel クラスは、既存のインスタンスの異なるプロパティを持つバリエーションを作成できる mutate() メソッドを提供します。これは、複数の OpenAI 互換 API を扱う必要がある場合に特に便利です。

@Service
public class MultiModelService {

    private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);

    @Autowired
    private OpenAiChatModel baseChatModel;

    @Autowired
    private OpenAiApi baseOpenAiApi;

    public void multiClientFlow() {
        try {
            // Derive a new OpenAiApi for Groq (Llama3)
            OpenAiApi groqApi = baseOpenAiApi.mutate()
                .baseUrl("https://api.groq.com/openai")
                .apiKey(System.getenv("GROQ_API_KEY"))
                .build();

            // Derive a new OpenAiApi for OpenAI GPT-4
            OpenAiApi gpt4Api = baseOpenAiApi.mutate()
                .baseUrl("https://api.openai.com")
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .build();

            // Derive a new OpenAiChatModel for Groq
            OpenAiChatModel groqModel = baseChatModel.mutate()
                .openAiApi(groqApi)
                .defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
                .build();

            // Derive a new OpenAiChatModel for GPT-4
            OpenAiChatModel gpt4Model = baseChatModel.mutate()
                .openAiApi(gpt4Api)
                .defaultOptions(OpenAiChatOptions.builder().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>>() {});

ストリーミングレスポンス

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 のインスタンスを指定するために使用されます。

  • responseEntity() は、ChatResponse と Java 型の両方を返します。これは、AI モデルの完全なレスポンス(メタデータと世代を含む)と構造化された出力エンティティの両方に 1 回の呼び出しでアクセスする必要がある場合に便利です。

    • responseEntity(Class<T> type): 完全な ChatResponse オブジェクトと特定のエンティティ型の両方を含む ResponseEntity を返すために使用されます。

    • responseEntity(ParameterizedTypeReference<T> type): 完全な ChatResponse オブジェクトとエンティティ型の Collection の両方を含む ResponseEntity を返すために使用されます。

    • responseEntity(StructuredOutputConverter<T> structuredOutputConverter): 完全な ChatResponse オブジェクトと、指定された StructuredOutputConverter を使用して変換されたエンティティの両方を含む ResponseEntity を返すために使用されます。

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();

デフォルトのメタデータのサポート

You can also configure default metadata at the ChatClient builder level:

@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();
    }
}

Metadata Validation

The ChatClient validates metadata to ensure data integrity:

  • Metadata keys cannot be null or empty

  • Metadata values cannot be null

  • When passing a Map, neither keys nor values can contain null elements

// 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();

Accessing Metadata

The metadata is included in the generated UserMessage and SystemMessage objects and can be accessed through the message’s getMetadata() method. This is particularly useful when processing messages in advisors or when examining the conversation history.

デフォルトの使用

@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` の Bean 名。

  • defaultUser(String text)defaultUser(Resource text)defaultUser(Consumer<UserSpec> userSpecConsumer): これらのメソッドを使用すると、ユーザーテキストを定義できます。Consumer<UserSpec> では、ラムダを使用してユーザーテキストと既定のパラメーターを指定できます。

  • defaultAdvisors(Advisor…​ 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(Advisor…​ advisor)

  • advisors(Consumer<AdvisorSpec> advisorSpecConsumer)

アドバイザー

アドバイザー 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(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build()
    )
    .user(userText)
    .call()
    .content();

この構成では、最初に MessageChatMemoryAdvisor が実行され、プロンプトに会話履歴が追加されます。次に、QuestionAnswerAdvisor がユーザーの質問と追加された会話履歴に基づいて検索を実行し、より関連性の高い結果が提供される可能性が高くなります。

検索拡張生成

検索拡張生成ガイドを参照してください。

ログ

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
);

これにより、ログに記録された情報を特定のニーズに合わせてカスタマイズできます。

運用環境で機密情報を記録する場合は注意してください。

チャットメモリ

ChatMemory インターフェースは、チャット会話メモリのストレージを表します。会話にメッセージを追加したり、会話からメッセージを取得したり、会話履歴を消去したりするためのメソッドを提供します。

現在、組み込み実装は MessageWindowChatMemory が 1 つあります。

MessageWindowChatMemory は、指定された最大サイズ(デフォルト: 20 件)までのメッセージウィンドウを維持するチャットメモリ実装です。メッセージ数がこの制限を超えると、古いメッセージは削除されますが、システムメッセージは保持されます。新しいシステムメッセージが追加されると、それ以前のシステムメッセージはすべてメモリから削除されます。これにより、メモリ使用量を抑えながら、会話で常に最新のコンテキストを利用できるようになります。

MessageWindowChatMemory は、チャット会話メモリ用のストレージ実装を提供する ChatMemoryRepository 抽象化を基盤としています。InMemoryChatMemoryRepositoryJdbcChatMemoryRepositoryCassandraChatMemoryRepositoryNeo4jChatMemoryRepository を含む複数の実装が利用可能です。

詳細と使用例については、チャットメモリのドキュメントを参照してください。

実装上の注意

ChatClient における命令型プログラミングモデルとリアクティブプログラミングモデルの併用は、この API のユニークな特徴です。多くの場合、アプリケーションはリアクティブか命令型のいずれか一方のみで構成されますが、両方が使用されることはありません。

  • モデル実装の HTTP クライアント相互作用をカスタマイズする場合は、RestClientWebClient の両方を構成する必要があります。

Spring Boot 3.4 のバグにより、"spring.http.client.factory=jdk" プロパティを設定する必要があります。設定されていない場合はデフォルトで "reactor" に設定され、ImageModel などの特定の AI ワークフローが機能しなくなります。

  • ストリーミングは Reactive スタック経由でのみサポートされます。そのため、命令型アプリケーションには Reactive スタックを含める必要があります(例: spring-boot-starter-webflux)。

  • 非ストリーミングはサーブレットスタック経由でのみサポートされます。このため、リアクティブアプリケーションはサーブレットスタック(例: spring-boot-starter-web)を組み込む必要があり、一部の呼び出しがブロッキングされることを想定しています。

  • ツール呼び出しは必須であるため、ワークフローがブロックされる可能性があります。また、Micrometer 観測が不完全または中断される結果にもなります(例: ChatClient スパンとツール呼び出しスパンが接続されておらず、最初のスパンが不完全なままになる)。

  • 組み込みアドバイザーは、標準呼び出しに対してはブロッキング操作を、ストリーミング呼び出しに対してはノンブロッキング操作を実行します。アドバイザーのストリーミング呼び出しに使用される Reactor スケジューラは、各アドバイザークラスのビルダーから設定できます。