AWS SDK for KotlinでAmazon BedrockのConverse APIを動かす

# はじめに

> [Kotlin Advent Calendar 2024](https://qiita.com/advent-calendar/2024/kotlin) 7日目の記事です(誰もいなかったので「えいや!!」で投稿しちゃいました)。

最近、Kotlinを勉強し始めました(モバイルアプリが作りたくて...)。
その練習として、Amazon BedrockのConverse APIをAWS SDK for Kotlin経由で叩いてみたというメモです。

https://aws.amazon.com/jp/bedrock

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-sdk-kotlin

2024年12月現在だと、AWS SDKのGithub 公式リポジトリには、ListFoundationModelsのサンプルしかなく、デベロッパーが一番知りたいであろうAmazon Bedrock Runtime(Converse API)のサンプルコードがありません。

https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/kotlin/services/bedrock/src/main/kotlin/com/example/bedrock/ListFoundationModels.kt

そこで、他のAWS SDKのサンプルコードも参考にしながら、Converse APIを使ったシンプルなプログラムを実装してみようと思います。

## 実行環境

- Kotlin: 2.0.21
- Gradle: 8.11.1

## 必要なライブラリはこれだ!!

Amazon BedrockのRuntimeとロギング用(これがないと実行時に怒られる)のライブラリを追加しています。

サンプルコードに記載されているようなバージョンだと、Converse APIや後述のPrompt Managementを用いるためのメソッドが実装されていないことが多々あるため、現時点で最新だったバージョンを指定しています。

```kotlin
dependencies {
    implementation("aws.sdk.kotlin:bedrockruntime:1.3.89")
    implementation("ch.qos.logback:logback-classic:1.5.12")
}
```

## Converce APIを動かす

リージョンはモデルアクセスが有効化されていればどこでもいいのですが、レート制限の緩い`us-west-2`で試します。

普段はもっぱらPythonばかり書いているのですが、`ConversationRole.User`や`ContentBlock.Text`のようなPythonだと忘れがちな部分を、特にドキュメントを見ずとも書けるのは嬉しいですね。

```kotlin
import aws.sdk.kotlin.services.bedrockruntime.*
import aws.sdk.kotlin.services.bedrockruntime.model.*

suspend fun invokeModel(question: String): String {
    BedrockRuntimeClient {
        region = "us-west-2"
    }.use { client ->
        val response = client.converse {
            modelId = "anthropic.claude-3-5-haiku-20241022-v1:0"
            messages = listOf(Message {
                role = ConversationRole.User
                content = listOf(ContentBlock.Text(question))
            })
            inferenceConfig = InferenceConfiguration {
                maxTokens = 4096
                temperature = 0.0f
            }
        }
        return response.output?.asMessage()?.content?.get(0)?.asText()
            ?: throw Exception("Invoke model request was not processed successfully")
    }
}
```

モデルの応答を得る関数が作成できたので、さっそく実行してみます。
Gradleのアイコンが何故ゾウさんモチーフなのか気になったので、それを聞いてみようと思います。

```kotlin
suspend fun main() {
    val question = "Gradleが象をモチーフにしているのは何故ですか?"
    val answer = invokeModel(question)

    println("=== AIからの回答 ===")
    println(answer)
}
```

正しいかどうかはさておき、基盤モデルからの応答を得ることができました。

```
=== AIからの回答 ===
Gradleのロゴが象（より正確にはゾウ）をモチーフにしている理由は、以下のようないくつかの背景があります：

1. 象徴的な意味
- 象は知性と記憶力が高い動物として知られています
- ビルドツールにおける「重い作業」を象徴しています

2. 創造者の意図
- Gradleの創設者Hans Dockterが象を選んだ理由は明確に公表されていません

3. デザイン的な特徴
- 象は大きく、力強いイメージを持っています
- Gradleの複雑なビルドプロセスを象徴しているとも解釈できます

4. 印象的なロゴ
- 覚えやすく、独自性のあるデザインになっています

正確な選択理由は公式に明言されていませんが、これらの要素が背景にあると考えられています。
```

:::note
余談: Gradleのアイコンになっているゾウさんには、**Gradlephant**っていう名前がついてます。可愛いですね。

> Gradle、Develocity、Gradlephant ロゴ、およびこのページに表示されている商標 (「Gradle マーク」) は、Gradle, Inc. および/またはその子会社の登録商標です。
> [Gradle - Branding Guidelines](https://gradle.com/brand) より
:::

## Prompt Management経由でも試してみる

会話を複数回やり取りする場合やTool Useなどを使用する場合は、Kotlinのコードに全て書き切るのは大変です。
一部Prompt Managementに切り出して、Converse APIをもう少しシンプルに扱う方法も試してみます。


まずは、Amazon Bedrockのコンソール上から、Prompt Managementでプロンプトを作成します。プロンプトやパラメータなどは先ほど実行したものと揃えておきます。

![スクリーンショット 2024-12-07 15.26.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571091/c749dadd-2b70-a757-fc81-382a1ff9e83c.png)

`modelId`にPrompt Managementで作成したプロンプトのARN、`promptVariables`にキーと値のペアを含めます(今回の例でいくと`{{question}}`の部分)。

```kotlin
import aws.sdk.kotlin.services.bedrockruntime.BedrockRuntimeClient
import aws.sdk.kotlin.services.bedrockruntime.converse
import aws.sdk.kotlin.services.bedrockruntime.model.PromptVariableValues

suspend fun invokeModel(question: String): String {
    BedrockRuntimeClient {
        region = "us-west-2"
    }.use { client ->
        val response = client.converse {
            modelId = "arn:aws:bedrock:us-west-2:154652010864:prompt/C9SUOA86M1"
            promptVariables = mapOf(
                "question" to PromptVariableValues.Text(question)
            )
        }
        return response.output?.asMessage()?.content?.get(0)?.asText()
            ?: throw Exception("Invoke model request was not processed successfully")
    }
}
```

ほんの少しだけコードがスッキリしました。こちらも同様に試してみます(適当に「なぜ地球は丸いのか?」を聞いてみました)。

```
=== AIからの回答 ===
地球が丸い（正確には、ほぼ球形）理由は、主に重力と自転によるものです。以下にその詳細を説明します：

1. 重力の影響
- 地球を構成する物質は、互いに引き合う重力によって中心に向かって引っ張られています。
- この引力により、物質は可能な限り最も安定した形状である球形に近づきます。

2. 自転の影響
- 地球は自転しているため、完全な球形ではなく、赤道部分がやや膨らんだ楕円球（回転楕円体）の形状をしています。
- 自転により、物質が中心から外側に押し出される遠心力が働き、わずかに扁平な形状になっています。

3. 形成過程
- 地球の形成初期、物質は高温で柔らかく、重力によって球形に近い形状になりました。
- 冷却と固化の過程で、この形状が維持されました。

これらの要因により、地球はほぼ球形の形状を持っています。
```

こちらも同様に、基盤モデルからの応答を得ることができました。

## まとめ

ほぼAWS SDK for Kotlinの使い方で終わりました(あまりKotlinの勉強にはならず...)。

Bedrock Runtimeのサンプルコードくらいなら初心者が出しても怒られないかなと思ったので、[aws-doc-sdk-examples](https://github.com/awsdocs/aws-doc-sdk-examples)にプルリクを上げることも視野に入れて考えようと思います。