JavaにおけるAI統合:Spring AIで5分でチャットモデルを構築する

序章

Pythonがプログラミング言語の主流となっている現代においても、Javaは人工知能分野において重要な位置を占めています。これは、堅牢なSpringフレームワークの存在によるものです。近年急速に進歩しているAI技術により、革新的なアイデアが次々と生まれています。音声アシスタントから複雑な自然言語処理システムまで、AIは現代社会と仕事において欠かせない存在となっています。この流れの中で、Spring AIプロジェクトは新たな機会を掴みました。PythonのLangChainやLlamaIndexなどのプロジェクトから影響を受けているものの、Spring AIは単なる移植ではなく、生成AIアプリケーションの開発を広げるためのものです。

Spring AIの核となる理念は、高水準な抽象化コンポーネントを提供し、AIアプリケーション開発の基盤となることです。これらの抽象化されたコンポーネントは複数の実装を持ち、開発者は少ないコード変更で機能モジュールを容易に交換・最適化できます。

具体的には、Spring AIはOpenAI、Microsoft、Amazon、Google、Hugging Faceなど主要なモデルプロバイダーに対応しています。対応するモデルは、チャットボットからテキスト生成、画像処理、音声認識まで幅広くカバーされています。また、モデル間の移植性を重視したAPI設計により、同期およびストリーミングインターフェースをサポートし、特定のモデル機能に対する柔軟なオプションを提供します。

さらに、AIモデル出力をPOJOにマッピングしたり、Apache Cassandra、Azure Vector Search、MongoDB Atlasなど主流のベクトルデータベースプロバイダーとの連携を可能にしています。AIの機能だけでなく、データ処理におけるETLフレームワークや便利な関数呼び出しも含まれ、AIアプリケーションの開発をより効率的かつ信頼性の高いものにします。

クイックハンズオン

今回のハンズオンは初回として、Spring AIプロジェクトの魅力を素早く体験できるよう設計されています。コードはGitHubリポジトリに公開されており、Swagger UIによるAPI呼び出しインターフェースも追加されているため、簡単に試すことができます。興味があれば、リポジトリをご覧ください。今後も継続的にメンテナンスしていきます。

リポジトリ:https://github.com/StudiousXiaoYu/spring-ai-courses

プロジェクト作成

プロジェクト作成の際、公式サイトからSpring AIの依存関係を生成し、プロジェクトを作成できます。

チャットモデル

大規模モデルにおいて、チャットモデルは重要な役割を果たします。Spring AIではどのようにそれをラップしているのでしょうか?今回のサンプルでは、Spring AIのChatClientの活用方法に焦点を当てます。

ログレベル設定

リクエスト詳細のログを確認したい場合は、ログレベルをDEBUGに設定してください。

モデル設定

モデルを使用する際には、まずプロジェクトに必要な依存関係を追加し、設定ファイルに該当情報を記述する必要があります。

モデルの注入

モデルは自動的に注入され、直接使用可能です。今回のデモンストレーションでは、3つのカスタムモデルの注入方法を紹介します。

private final ChatClient myChatClientWithSystem;

private final ChatClient myChatClientWithParam;

/**
 * 自動注入またはメソッド内でカスタマイズ可能。システムメッセージなし
 */
private final ChatClient chatClient;

public MyController(ChatClient.Builder chatClientBuilder, MyChatClientWithSystem myChatClient, MyChatClientWithParam myChatClientWithParam) {
    this.chatClient = chatClientBuilder.build();
    this.myChatClientWithSystem = myChatClient.client();
    this.myChatClientWithParam = myChatClientWithParam.client();
}

上記の3つのケースについて説明します:

  1. chatClient:デフォルトの自動注入ChatClient、条件不要。
  2. myChatClientWithParam:システムメッセージとパラメータを含むChatClient。
  3. myChatClientWithSystem:システムメッセージのみを含むChatClient。

最初のケースは特に処理不要です。設定クラスで以下の2つのChatClientをシンプルに設定してください。

@Configuration
class Config {

    @Bean
    MyChatClientWithSystem myChatClientWithSystem(ChatClient.Builder builder) {
        MyChatClientWithSystem build = MyChatClientWithSystem.builder()
                .client(builder.defaultSystem("あなたは努力する小雨というJavaサーバーサイドエンジニアで、AI技術の奥深さを研究しています。技術の共有と交流が好きで、オープンソースコミュニティに情熱を持っています。掘金の優秀著者、騰訊雲コンテンツコントリビューター、アリババクラウドエキスパートブロガー、华为クラウドクラウドエキスパートなど複数の役割を持っています。")
                .build()).build();
        return build;
    }

    @Bean
    MyChatClientWithParam myChatClientWithParam(ChatClient.Builder builder) {
        MyChatClientWithParam build = MyChatClientWithParam.builder()
                .client(builder.defaultSystem("あなたは{user}です。")
                        .build()).build();
        return build;
    }
}

簡単なテキスト応答

まずは簡単な質問・回答の例から始めましょう。

@GetMapping("/ai")
String generationByText(String userInput) {
    return this.chatClient.prompt()
        .user(userInput)
        .call()
        .content();
}

この簡潔なコードで、さまざまな抽象化とインタラクションが実現されています。より明確に示すために、以下のように拡張します。

応答エンティティオブジェクトのカプセル化

Javaはオブジェクト指向言語であり、AI技術を導入する際には業務要件に合わせてオブジェクトを活用することが重要です。AIの応答をSpringフレームワークが自動的にオブジェクトにマッピングする方法を見てみましょう。

以下のようなレコードクラスを定義します。これはActorFilmsという名前のレコードクラスで、フィールドをカプセル化します。レコードクラスはtoString()equals()hashCode()getterを自動的に実装します。

public record ActorFilms(String actor, List<String> movies) {
}

次に、以下のようにしてエンティティを取得します。

@GetMapping("/ai-Entity")
ActorFilms generationByEntity() {
    ActorFilms actorFilms = chatClient.prompt()
            .user("Generate the filmography for a random actor.")
            .call()
            .entity(ActorFilms.class);
    return actorFilms;
}

上記のように、entity()メソッドにActorFilms.classを指定するだけで、Springが自動的にオブジェクトにマッピングしてくれます。応答の際に、ユーザー入力に加えてJSON形式の出力を要求する追加情報も送られます。

例えば、出力が{"actor": "Emily Blunt", "movies": ["Edge of Tomorrow", "A Quiet Place", "The Devil Wears Prada", "Sicario", "Mary Poppins Returns"]}の場合、Springはそれを自動的にActorFilmsオブジェクトに変換できます。

応答リストエンティティのカプセル化

オブジェクトではなくリストを返す必要がある場合、Spring AIのカプセル化機能を利用できます。以下のようにします。

@GetMapping("/ai-EntityList")
List<ActorFilms> generationByEntityList() {
    List<ActorFilms> actorFilms = chatClient.prompt()
            .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
            .call()
            .entity(new ParameterizedTypeReference<List<ActorFilms>>() {
            });
    return actorFilms;
}

ParameterizedTypeReferenceオブジェクトを直接使用します。戻り値の形式に関するヒントも送信されるため、Springが自動的にリストにマッピングできます。

ストリーミング応答

これまでの例では、大規模モデルが一度に応答を出力していました。しかし、フロントエンドではタイピング効果を再現できないため、ストリーミング応答で実演します。

@GetMapping("/ai-streamWithParam")
Flux<String> generationByStreamWithParam() {
    var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorFilms>>() {
    });

    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);
    log.info("actorFilms: {}", actorFilms);
    return flux;
}

ユーザー入力にパラメータを渡すために、ストリーミング応答をブロックしています。不要であれば削除可能です。また、リストオブジェクトをカプセル化するためにブロック操作を行っています。この方法は、上記と同じく、応答形式を明示することで実現されています。

システム情報付きクライアント

次に、クライアント設定の例を示します。会話において、system、user、assistantという3つの識別子があります。これまでシステム識別子については触れていませんでしたが、先ほどシステム形式のクライアントを定義しました。今回はそれを直接使用します。

@GetMapping("/ai-withSystemClient")
Map<String, String> generationByTextWithSystemClient(String message) {
    return Map.of("completion", myChatClientWithSystem.prompt().user(message).call().content());
}

非常にシンプルなコードですが、ChatClientを使用するだけです。ユーザー入力後に、キーが"completion"で値が応答内容のMapを返します。結果を見てみましょう。

実際には、システムメッセージも含まれています。

パラメータ付きクライアント

パラメータ付きの例を示します。ユーザー入力後、キーと値を持つMapを返します。実際の業務ではパラメータは避けられず、このような例がAIの適用性を示します。

@GetMapping("/ai-withParamClient")
Map<String, String> generationByTextWithParamClient(String message, String user) {
    return Map.of("completion", myChatClientWithParam.prompt().system(sp ->sp.param("user",user)).user(message).call().content());
}

非常にシンプルなコードです。効果を見てみましょう。

応答に疑問を感じる場合は、バックエンドの通信ログを確認して、パラメータの詳細を確認できます。

実際、パラメータが正しく設定されています。

チャット履歴

最後の主なユースケースとして、各ユーザーごとにチャット履歴を保持する必要があります。状態を持たない会話は不自然です。そのため、チャット履歴機能が必要です。Spring AIはまだこの機能のカプセル化を完全に決定していませんが、簡単なオブジェクトクラスを提供しています。以下のように使用します。

@GetMapping("/ai-chatMemory")
String generationByChatMemory(HttpServletRequest request, String userInput) {
    String sessionId = request.getSession().getId();
    chatMemory.add(sessionId, new UserMessage(userInput));
    String content = this.chatClient.prompt()
            .advisors(new MessageChatMemoryAdvisor(chatMemory))
            .user(userInput)
            .call()
            .content();
    chatMemory.add(sessionId, new AssistantMessage(content));
    return content;
}

この場合、チャット履歴オブジェクトを手動で作成・管理する必要があります。チャット前後に必要な情報を追加し、そのまま使用します。この方法での効果を見てみましょう。

実際に履歴も含まれた応答が返されます。

まとめ

この記事を通じて、Spring AIプロジェクトの利点と特徴、そして実際の応用例について学びました。Spring AIは高度な抽象化されたAIアプリケーション開発フレームワークであり、モデルサポート、柔軟な機能交換・最適化を提供します。AIモデル出力をPOJOにマッピングしたり、主流のベクトルデータベースと統合することで、AIアプリケーション開発の効率性と信頼性を大幅に向上させます。

Pythonと比較して、Javaは企業向けアプリケーションや大規模システムにおいて優れた利点を持っています。静的型付けと厳格なコンパイル時チェックにより、コードはより堅牢で保守しやすくなります。特に、高い信頼性と長期サポートが必要なプロジェクトに適しています。また、成熟したJavaエコシステムにより、豊富なライブラリとツールが利用可能で、開発サイクルを短縮し、プロジェクトリスクを低減できます。

この記事が、Spring AIプロジェクトの理解と応用の助けになれば幸いです。また、このプロジェクトをぜひご注目・ご利用いただき、最新情報を継続的にご確認ください。AI技術の進歩と応用を一緒に見守っていきましょう。

私は努力する小雨というJavaサーバーサイドエンジニアで、AI技術の奥深さを研究しています。技術の共有と交流が好きで、オープンソースコミュニティに情熱を持っています。掘金の優秀著者、騰訊雲コンテンツコントリビューター、アリババクラウドエキスパートブロガー、华为クラウドクラウドエキスパートなど複数の役割を持っています。

🚀 現在、私の研究テーマはAIエージェントの応用です。その可能性と限界に強い関心を持っており、常に探求しています。もし同じような関心をお持ちでしたら、ぜひ交流してください。未知の領域を一緒に探求しましょう!

💡 私は技術の旅路での経験と考察を積極的に共有します。あなたの学習と成長に少しでもお役に立てれば幸いです。

🌟 努力する小雨をぜひフォローしてください!🌟

タグ: Spring AI Java 人工知能 チャットモデル ストリーミング応答

5月17日 02:36 投稿