AI を取り入れたシステムを実装するとき、特に制限がないなら Python ベースで考えることが多いと思います。ただ、私の普段使いは Kotlin/Spring なので、今後場合によってはそちらで実装したいことがあるかもしれません。ということで、その場合の採用候補として、Spring AI を触ってみることにします。
前提
本記事では以下のバージョンのライブラリを使用しています。
- Spring AI 0.8.1
- Spring Boot 3.2.4
準備
Spring AI プロジェクトは、さまざまな LLM・生成 AI へのアクセスを抽象化するライブラリ群を提供しています。今回はそのうちの Chat Completion 関連のものをドキュメントを適宜なぞりつつ触ってみることにします。すでに Spring Initializr で Spring AI プロジェクトのライブラリが選択できるようになっているので、今回は Spring Initializr から雛形生成を行いましょう。Gradle / Kotlin / Spring Boot 3.2.4 を選択し、依存ライブラリとして OpenAI と動作確認の API 提供のために Spring Web を追加しておきます。
生成されたプロジェクトの build.gradle を見ると org.springframework.ai:spring-ai-bom
の BOM が設定されていたり org.springframework.ai:spring-ai-openai-spring-boot-starter
という OpenAI 用の spring-boot-stareter が依存関係に含まれているのが確認できます。
ext { set('springAiVersion', "0.8.1") } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' implementation 'org.jetbrains.kotlin:kotlin-reflect' implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter' testImplementation 'org.springframework.boot:spring-boot-starter-test' } dependencyManagement { imports { mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}" } }
Chat Completion を試してみる
さて、OpenAI の依存関係を追加したプロジェクトが生成できたので、Chat Completion を試してみましょう。ドキュメントにある通り、インターフェースとして ChatClient があり、starter 内の AutoConfiguration によって ChatClient を実装した OpenAiChatClient が Bean として登録されるので、抽象に依存するように ChatClient を DI したクラスを作るとします。
@Service class ChatService(private val client: ChatClient) { fun chatCompletion(message: String): String { return client.call(message) } }
単一のチャットメッセージを渡して回答を返してもらうだけなら、最低限はこれで実現できます。OpenAI API を呼び出すためには認証情報を渡す必要があるので、そちらは環境変数で渡したり、application.properties ないし application.yaml にて指定したりしましょう。また、使用するモデル名なども指定できるので、今回は以下のように指定しておきます。指定できる内容の詳細はドキュメントを参照してください。
spring.ai.openai.api-key=${APIキー} spring.ai.openai.chat.options.model=gpt-4
Chat Completion を試すために、リクエストを受け付ける RestController も最低限動くものを作っておきます。
@RestController class ChatController(private val service: ChatService) { @PostMapping("") fun chatCompletion(@RequestBody request: ChatCompletionRequest): ChatCompletionResponse { return ChatCompletionResponse(service.chatCompletion(request.message)) } data class ChatCompletionRequest(val message: String) data class ChatCompletionResponse(val result: String) }
ではアプリを起動して試してみましょう。起動後に curl で API を呼び出すと、以下のように質問に対する回答が返ってきます。
$ curl -H "Content-Type: application/json" \ -d '{"message": "Kotlinについて教えてください"}' localhost:8080 {"result":"Kotlinは、静的型付けされたプログラミング言語で、Javaと完全な互換性があります。 JetBrainsが開発し、2011年に最初に公開されました。元々はJavaをより使いやすく、生産性を高めるために開発されました。\n\n Kotlinは、Javaよりもシンタックスが簡潔で、Null安全などの機能を備えています。 また、関数型プログラミングの特徴も持ち合わせています。\n\n 2017年にGoogleがAndroidの公式開発言語としてKotlinを採用しました。 これにより、Androidアプリ開発においてKotlinの使用が急速に広がりました。\n\n Kotlinはコンパイルが可能で、JavaバイトコードやJavaScriptに変換することもできます。 そのため、サーバーサイド開発やフロントエンド開発にも利用できます。\n\n また、Kotlinは現在、Kotlin/Nativeというプロジェクトを通じて、ネイティブバイナリへのコンパイルも可能となっており、 さまざまなプラットフォームで利用することができます。"}
もう少し複雑なことをしてみる
単純な受け答えをするだけなら上記でひとまず動きますが、例えばシステムメッセージを使って AI に役割設定をさせたり、これまでのコンテキストを含めた上で AI に回答させたりしたい場合はどうなるのでしょうか。 ChatClient の call メソッドには Prompt を引数に取るものがあり、Prompt は Message のリストを設定できるので、これを利用してみることにします。上記の ChatService を修正して、システムメッセージを付与して AI に指示を追加してみましょう。
@Service class ChatService(private val client: ChatClient) { fun chatCompletion(message: String): String { val response = client.call( Prompt(listOf(SystemMessage("質問に回答する際、語尾に「のだ」をつけて回答してください。"), UserMessage(message))) ) // Prompt が引数の場合、戻り値は ChatResponse になる // ChatResponse には生成結果のリスト(List<Generation>)が内包され、Generation は AssistantMessage を持つので、 // ひとまず AssistantMessage を連結している return response.results.joinToString("\n") { it.output.content } } }
変化を分かりやすくするために、回答する際に特定の語尾をつけて返すように指示してみました。システムメッセージの指示が効いていれば、以下のように指定した語尾をつけて返してくれるようになります。
$ curl -H "Content-Type: application/json" \ -d '{"message": "Kotlinについて教えてください"}' localhost:8080 {"result":"Kotlinは、2011年にJetBrainsによって開発されたプログラミング言語のことなのだ。 Javaに代わるAndroidの公式言語としてGoogleによって推奨されているのだ。 Javaと100%互換性があり、既存のJavaフレームワークとライブラリをそのまま利用することが可能なのだ。 また、null安全やラムダ式といった現代的な機能を持ち、コンパイルも高速で、開発効率が高いと評価されているのだ。"}
使用する AI モデルを切り替えてみる
例えば OpenAI の API から Azure OpenAI Service に切り替えたいとか、あるいは検証がてら AWS Bedrock から Claude を使ってみたいとか、使用する AI モデルを切り替えたいニーズが出ることは考えられます。上記の実装のように、特定モデルのクライアントに依存していない場合は、(切り替え先の AI モデルが対応している限りは)容易に切り替えが可能です。
では、例として上記の OpenAI API を利用している例から Azure OpenAI Service に切り替えてみましょう。まずは、build.gradle に以下の依存関係を追加します。
implementation 'org.springframework.ai:spring-ai-azure-openai-spring-boot-starter'
spring-ai-openai-spring-boot-starter を依存関係に含めたままだと、OpenAiChatClient も Bean 登録されて不都合なので、設定で無効化しつつ、Azure OpenAI Service 用の設定を付け加えます。
spring.ai.openai.chat.enabled=false
spring.ai.azure.openai.api-key=${Azure OpenAI Service の API キー}
spring.ai.azure.openai.endpoint=${Azure OpenAI Service の API エンドポイント}
spring.ai.azure.openai.chat.options.deployment-name=${Azure OpenAI Service でデプロイしたモデル名}
これで切り替えは完了です。簡単ですね。
感想
ということで、Spring AI で提供されているもののうち、Chat Completion の部分を試してみました。いかにも Spring な感じの手触りなので、慣れている身としては使いやすさはあります。進化の早い分野なので、どのくらいのスピードで LLM, 生成 AI の変化に追従できるか気になるところではありますが、GitHub の Milestone を見ると近いうちに 1.0.0 のバージョンもリリースされそうですし、今後の開発に期待ですね。Spring AI では他にも Embedding や Vector Database などに関連するライブラリもあるので、またの機会に触ってみたいと思います。