はじめに
先日、Spring AIが1.0となりGAしたというニュースを目にしました。
早速、Amazon Bedrockを対象に実装してみることにしました。
前提
今回は以下の環境で試しました。
はじめかた
リファレンスのGetting Startedからはじめてみようかなと思いましたが、まだドキュメントに古い記載が残っている感じがします。
spring initializrから雛形を作った方が手っ取り早いかなと思います。
DependenciesはAmazon Bedrock Converse
とSpring Web
を選びました。
また、settings.gradle
にtoolchainを動かすために必要なplugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' }
の記述も抜けていました。ビルド環境にJavaコンパイル環境がない場合はちょっとしたはまりどころかと思います(はまりました)。
あとは実装に移ります。
チャットアプリを作る
チャットアプリを作るのはまずはChatClient
を作る必要があるようです。
そのあとはモデルごと(使用する生成AIの対象ごと)の実装となります。Bedrockの場合は以下のドキュメントを見ると良いと思います。ただ、ところどころサンプルアプリがそのままだとコンパイルエラーが出たりしますのでちょっと注意です。
設定ファイル
設定ファイルのapplication.properties
は以下のように設定します。
spring.application.name=demo spring.ai.bedrock.aws.region=us-east-1 spring.ai.bedrock.converse.chat.options.model=amazon.nova-lite-v1:0
リージョンやモデルIDはそれぞれ利用するものを設定してください。アクセスキーやシークレットキーなどは設定せず、EC2に割り当てたIAMロールにて権限を付与することで実現します。
また、spring.ai.bedrock.converse.chat.options.max-tokens
のデフォルト値が500となっています。少し長い回答が欲しい場合、これだと途中で回答が切れてしまうことが多いので少し数を増やした方が良いかと思います。
IAMロール
IAMロールは以下のポリシーを追加することで対応しました。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream" ], "Resource": "arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0" } ] }
今回の実装では、最低限bedrock:InvokeModel
とbedrock:InvokeModelWithResponseStream
への権限が必要です。Resourceは使用するモデルに限定するためにつけました。
Controller
Controllerは以下のような実装としました。
package com.github.miyohide.demo; import java.util.Map; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class MyController { private final ChatClient chatClient; public MyController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/ai/gen") public Map<String, String> chat( @RequestParam(value = "message", defaultValue = "Tell me a joke") String message ) { String response = this.chatClient.prompt(message).call().content(); return Map.of("gen", response); } @GetMapping(value = "/ai/genstream", produces = MediaType.APPLICATION_JSON_VALUE) public Flux<ChatResponse> chatStream( @RequestParam(value = "message", defaultValue = "Tell me a joke") String message ) { return this.chatClient.prompt(message).stream().chatResponse(); } }
ここまで実装したら./gradlew bootRun
として実行し、/ai/gen?message=プロンプト
にアクセスすると簡単なチャットアプリが動きます。以下は東京の天気を教えて
と質問した例。
/ai/genstream
は以下のようなJSONが返ってきます。
これを読みやすくするには少しだけJavaScriptなどで頑張る必要がありそうです。
ここまでで簡単なチャットアプリは実装できました。
なお、アプリのログには以下のものが出力されていました。
The topK option is not supported by BedrockProxyChatModel. Ignoring.
注意点
現時点ではcross-region inferenceには未対応のようです。
実際、東京リージョンでアプリを起動しモデルIDとしてapac.amazon.nova-lite-v1:0
を指定したのですが、software.amazon.awssdk.services.bedrockruntime.model.ValidationException
が吐かれうまく動きませんでした。
考察
Springに慣れた人であれば、生成AIアプリを作るためにSpring AIを活用するのは良い選択肢の一つかと思います。まだGAしたばかりでドキュメントが追いついていないところもあるのですが、徐々に整備されていくかと思います。
今日書いた部分ぐらいはわざわざフレームワークを使う必要はないこともあるのですが、徐々にフレームワークのありがたさを感じるシーンは出てくるかと思います。そのうちの一つがChat Memoryかと思います。
上記のドキュメントにも書かれていますが、コンテキストを保持しないので「より詳しく」と追加の質問としてプロンプトを投げても前回の回答を踏まえない回答しか返ってことないです。
これらについても検証してみたいと思います。