Contents
1. 前提条件とツールのインストール
| 項目 | 推奨環境 |
|---|---|
| JDK | Java 17 以上(OpenJDK / Oracle JDK) |
| ビルドツール | Gradle 8 系 の Kotlin DSL (gradlew が推奨) |
| IDE | Android Studio Flamingo (2023.2) 以降 または IntelliJ IDEA 2024.1 以降 |
| プラグイン | Kotlin、AWS Toolkit for JetBrains(必要に応じて) |
※ バージョン確認コマンド例
bash
java -version # → 17.x が表示されることを確認
./gradlew --version # → 8.x 系が出力されれば OK
1‑1. JDK と Gradle Wrapper のセットアップ
|
1 2 3 4 5 6 |
# macOS (Homebrew) の例 brew install openjdk@17 # プロジェクト直下で Gradle Wrapper を生成(バージョンは最新の 8 系を指定) ./gradlew wrapper --distribution-type all --gradle-version 8.9 # ← 2024‑04 時点の例 |
build.gradle.kts の最小構成
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
plugins { kotlin("jvm") version "2.0.20" // 最新安定版を公式リポジトリで確認してください application } repositories { mavenCentral() } java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } |
ポイント
- Kotlin のプラグインバージョンは、Kotlin Releases で最新の安定版を使用してください。
-applicationプラグインを入れると./gradlew runがすぐに利用可能です。
1‑2. IDE の設定例(IntelliJ / Android Studio)
| 手順 | 内容 |
|---|---|
| プラグイン有効化 | Preferences → Plugins → 「Kotlin」「AWS Toolkit for JetBrains」へチェック |
| Gradle JVM 設定 | Preferences → Build, Execution, Deployment → Compiler → Gradle JVM に JDK 17 を選択 |
| プロジェクト SDK | Project Structure → Project → Project SDK を JDK 17 に設定 |
ポイント
公式にサポートされた IDE とプラグインを使うと、コード補完・ビルドエラーの早期検出が格段に楽になります。
2. AI SDK の選択基準と依存関係
2‑1. AWS SDK for Kotlin と MCP Kotlin SDK の比較
| 項目 | AWS SDK for Kotlin | MCP Kotlin SDK |
|---|---|---|
| 対象サービス | Amazon Bedrock、SageMaker、Polly 等の公式 AWS サービス | Claude、OpenAI、Anthropic など複数ベンダー向け統一インターフェース |
| 認証方式 | IAM ロール / 環境変数(SigV4) | API キー(.env 推奨) |
| 設定柔軟性 | 高(サービスごとの細かいオプションが利用可能) | 中(ベンダー共通設定に抽象化) |
| 学習コスト | やや高め(AWS 固有概念の理解が必要) | 低め(REST API ライクな呼び出しで完結) |
参考文献
- AWS SDK for Kotlin – Developer Guide https://docs.aws.amazon.com/ja_jp/sdk-for-kotlin/latest/developer-guide/get-started.html
- MCP Kotlin SDK – GitHub リポジトリ(最新リリースページ) https://github.com/mcp/kotlin-sdk/releases
※ 「Reddit のクイックスタートガイド」については、具体的な URL が不明確だったため削除し、代わりに公式ドキュメントへのリンクを掲載しました。
2‑2. Gradle における依存設定(最新版取得の注意点)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
dependencies { // AWS SDK for Bedrock – バージョンは Maven Central の最新安定版をご確認ください implementation("aws.sdk.kotlin:bedrock:<latest-version>") // MCP Kotlin SDK – 同上 implementation("io.mcp:kotlin-sdk:<latest-version>") // コルーチンと JSON パーサ(例として 2024 年時点の最新版) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.0") } |
ポイント
-<latest-version>の部分は、実際にビルドするタイミングで公式リポジトリや Maven Central を確認し、最新の安定版を入力してください。
- バージョン番号が古くなると互換性エラーが起きやすいため、CI パイプライン内でも自動チェック(例:./gradlew dependencyUpdates)を導入すると便利です。
3. 認証情報の安全な管理
3‑1. 環境変数による API キーの取り扱い
|
1 2 3 |
# ~/.zshrc や ~/.bash_profile に追記(例: OpenAI) export OPENAI_API_KEY=sk-xxxxxxxxxxxxxx |
Kotlin 側で取得するシンプルなコード例です。
|
1 2 3 |
val openAiKey = System.getenv("OPENAI_API_KEY") require(!openAiKey.isNullOrBlank()) { "OpenAI API key is missing" } |
ポイント
- 環境変数は OS のプロファイルに保存でき、CI/CD でもシークレットストア(GitHub Actions Secrets や GitLab CI Variables)から安全に注入できます。
3‑2. AWS IAM ロールの活用(リスクの過大評価を避ける)
重要
「認証情報漏洩リスクが実質的にゼロ」という表現は誤解を招くため、以下のように限定します。
- IAM ロール は EC2・ECS・Lambda などのインスタンスプロファイルに紐付けることで、一時的な認証情報(数分〜数時間有効)を自動取得できます。
- これにより 永続的なアクセスキー をコードや環境変数に保持する必要がなくなるため、漏洩リスクは大幅に低減 します。
- ただし、ロール自体の権限設定ミスやインスタンスプロファイルの誤付与があると、過剰なアクセスが可能になる点には注意が必要です。
|
1 2 3 4 5 |
val bedrockClient = BedrockRuntimeClient { region = "us-east-1" // デフォルトで EC2/ECS のロール情報を参照します } |
参考
- AWS SDK for Kotlin – Credentials Provider https://docs.aws.amazon.com/ja_jp/sdk-for-kotlin/latest/developer-guide/get-started.html#credentials-provider
3‑3. .env ファイルと dotenv-kotlin の併用
.gitignoreに追加
text
.env
- サンプル
.env
text
CLAUDE_API_KEY=sk-claude-xxxxxxxxxxxx
OPENAI_API_KEY=sk-openai-yyyyyyyyyyyy
- Kotlin コードでロード
kotlin
import io.github.cdimascio.dotenv.Dotenv
val dotenv = Dotenv.load()
val claudeKey = dotenv["CLAUDE_API_KEY"]
?: error("Claude API key missing")
ポイント
-.envはローカル開発時の手軽な代替手段です。運用環境では IAM ロールやクラウドシークレットマネージャー(AWS Secrets Manager、Google Secret Manager 等)への置き換えを検討してください。
4. AI クライアント実装ハンズオン
4‑1. 共通インターフェースとベンダー別クライアント
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
// ------------------------------------------------- // 共通インターフェース // ------------------------------------------------- interface AiClient { suspend fun generate(prompt: String, temperature: Double = 0.7): String } // ------------------------------------------------- // AWS Bedrock 用実装(IAM ロール使用を想定) // ------------------------------------------------- class BedrockAiClient( private val region: String = "us-east-1" ) : AiClient { private val client = BedrockRuntimeClient { this.region = region // IAM ロールが自動で取得されます } override suspend fun generate(prompt: String, temperature: Double): String = client.invokeModel { modelId = "anthropic.claude-v2" contentType = "application/json" accept = "application/json" body = """ {"prompt":"$prompt","temperature":$temperature} """.trimIndent().toByteArray() }.body?.decodeToString() ?: error("Empty response from Bedrock") } // ------------------------------------------------- // Claude (MCP SDK) 用実装 // ------------------------------------------------- class ClaudeAiClient(private val apiKey: String) : AiClient { private val http = HttpClient(CIO) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } } override suspend fun generate(prompt: String, temperature: Double): String = http.post("https://api.anthropic.com/v1/complete") { header("X-API-Key", apiKey) contentType(ContentType.Application.Json) setBody( mapOf( "prompt" to prompt, "temperature" to temperature, "model" to "claude-v2" ) ) }.bodyAsText() } // ------------------------------------------------- // OpenAI 用実装 // ------------------------------------------------- class OpenAiClient(private val apiKey: String) : AiClient { private val http = HttpClient(CIO) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } } override suspend fun generate(prompt: String, temperature: Double): String = http.post("https://api.openai.com/v1/completions") { header(HttpHeaders.Authorization, "Bearer $apiKey") contentType(ContentType.Application.Json) setBody( mapOf( "model" to "gpt-4o-mini", "prompt" to prompt, "temperature" to temperature, "max_tokens" to 512 ) ) }.bodyAsText() } |
ポイント
-AiClientを介して呼び出すことで、ベンダーを入れ替えてもアプリケーションコードの変更は最小限に抑えられます。
4‑2. プロンプトバリデーションとパラメータ調整
|
1 2 3 4 5 6 7 8 |
/** * トークン上限(ベンダー別)を超えていないか簡易チェックし、JSON エスケープも行う。 */ fun preparePrompt(raw: String, maxTokens: Int = 4096): String { require(raw.length <= maxTokens) { "Prompt exceeds $maxTokens characters (token limit)" } return raw.replace("\"", "\\\"") // JSON 用エスケープ例 } |
- 温度 (
temperature) 0.0→ 完全に決定的、再現性が高い1.0→ 高ランダム性、創造的出力だが一貫性は低下
ベストプラクティス:開発段階では
0.7前後をデフォルトにし、実運用で要件に合わせて調整してください。
4‑3. コルーチンによる並列呼び出し例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import kotlinx.coroutines.* suspend fun invokeAllClients(prompt: String): List<String> = coroutineScope { val clients: List<AiClient> = listOf( BedrockAiClient(), ClaudeAiClient(apiKey = dotenv["CLAUDE_API_KEY"]!!), OpenAiClient(apiKey = dotenv["OPENAI_API_KEY"]!!) ) clients.map { client -> async { try { client.generate(preparePrompt(prompt)) } catch (e: Exception) { "Error (${client::class.simpleName}): ${e.message}" } } }.awaitAll() } // エントリポイント fun main() = runBlocking { val prompt = "Kotlin で非同期処理を書くベストプラクティスを教えて" invokeAllClients(prompt).forEachIndexed { idx, res -> println("[$idx] $res") } } |
ポイント
-async/awaitAllにより、3 つのベンダーへ同時リクエストが可能です。
- エラーハンドリングは各async内で行い、失敗しても他の呼び出しに影響しません。
4‑4. 型安全なレスポンス解析と統一例外
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import io.ktor.client.statement.* data class CompletionResponse( val id: String, val choices: List<Choice> ) { data class Choice(val text: String) } /** 4xx/5xx 系エラーを統一例外に変換 */ class AiException(message: String, val statusCode: Int) : RuntimeException(message) /** * OpenAI クライアントの改良版(例外処理付き) */ class OpenAiClientWithErrorHandling(private val apiKey: String) : AiClient { private val http = HttpClient(CIO) { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } } override suspend fun generate(prompt: String, temperature: Double): String { val response: HttpResponse = http.post("https://api.openai.com/v1/completions") { header(HttpHeaders.Authorization, "Bearer $apiKey") contentType(ContentType.Application.Json) setBody( mapOf( "model" to "gpt-4o-mini", "prompt" to prompt, "temperature" to temperature, "max_tokens" to 512 ) ) } if (!response.status.isSuccess()) { throw AiException("OpenAI error ${response.status}", response.status.value) } val body = response.bodyAsText() val parsed: CompletionResponse = jacksonObjectMapper().readValue(body) return parsed.choices.firstOrNull()?.text?.trim() ?: "" } } |
ポイント
- JSON デシリアライズは Jackson Kotlin Module(jackson-module-kotlin)で行い、ignoreUnknownKeys=trueを設定して将来のフィールド追加に耐性を持たせます。
- HTTP ステータスが 2xx でなければAiExceptionにラップし、呼び出し側で一元的にハンドリングできます。
5. ビルド・デバッグ・公開までのフロー
5‑1. ローカルビルドと実行
|
1 2 3 4 5 |
// build.gradle.kts の追記(application プラグイン設定) application { mainClass.set("com.example.ai.MainKt") } |
ターミナルから次のコマンドでビルド・実行できます。
|
1 2 3 |
./gradlew clean build # ビルド+テスト実行 ./gradlew run --args="" # アプリ起動(必要なら引数を渡す) |
ポイント
-applicationプラグインがエントリポイントを自動解決するため、IDE に依存せず CI/CD パイプラインでも同様に実行可能です。
5‑2. 主なトラブルシューティング表
| 症状 | 想定原因 | 確認・対処法 |
|---|---|---|
NullPointerException が API キー取得箇所で発生 |
.env が読み込まれていない、または環境変数が未設定 |
echo $OPENAI_API_KEY で確認。.gitignore に .env を必ず追加し、IDE の Run Configuration でも env ファイルを指定 |
| HTTP 403 / SigV4 エラー | IAM ポリシーに必要権限が足りない | IAM コンソールで bedrock:*(または対象サービス)を許可したポリシーをロールにアタッチ |
| JSON デシリアライズ失敗 (UnrecognizedPropertyException) | API のレスポンスフォーマットがバージョンアップで変化 | 最新ドキュメントのサンプルレスポンスを取得し、CompletionResponse クラスを更新 |
| Gradle 依存解決エラー(conflict) | ライブラリ間のバージョン不整合 | ./gradlew dependencies --configuration runtimeClasspath でツリー確認。必要に応じて dependencyManagement プラグインや constraints を利用 |
5‑3. GitHub へのサンプルプロジェクト公開手順
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# (1) ローカルリポジトリ初期化 git init git add . git commit -m "Initial commit: Kotlin AI SDK ハンズオン" # (2) GitHub に新規リポジトリ作成(ブラウザで実行) # → リモート URL を取得 # (3) リモート登録 & プッシュ git remote add origin https://github.com/<ユーザー名>/kotlin-ai-sdk-sample.git git branch -M main git push -u origin main |
README.md の冒頭例(タイムレスな記述)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Kotlin AI SDK ハンズオンサンプル このリポジトリは、Kotlin で **AWS SDK for Kotlin** と **MCP Kotlin SDK** を利用し、 Bedrock・Claude・OpenAI へ非同期リクエストを送る最小構成例を示しています。 ## 前提条件 - JDK 17 以上 - Gradle 8 系(`./gradlew` がプロジェクトに同梱されています) - `.env` に API キーを設定し、`.gitignore` で除外してください ## ビルド & 実行 ```bash ./gradlew run |
`
ポイント
- 手順は IDE 非依存なので、チーム全員が同じ環境で即座に試すことができます。
6. まとめ
- 開発基盤:JDK 17 + Gradle 8 系 Kotlin DSL が必須。IDE は Android Studio Flamingo 以上か IntelliJ IDEA 2024.1 以上を推奨。
- SDK の選択:AWS の公式サービスは AWS SDK、マルチベンダー実験やベンダーロックイン回避には MCP SDK が適切。
- 認証管理:環境変数・
.env・dotenv-kotlinでローカル開発を安全にし、AWS 上では IAM ロールを利用して永続キーの保管を不要にする(リスクは低減されるがゼロではない)。 - 実装パターン:共通インターフェース
AiClientでベンダー間の差し替えを簡潔化。コルーチンで非同期並列呼び出し、Jackson による型安全デシリアライズ、統一例外でエラーハンドリング。 - ビルド・デバッグ:
./gradlew runが最も手軽。トラブルは環境変数、IAM ポリシー、JSON 形式の相違が主因。 - 公開:GitHub にサンプルを置くことでナレッジ共有とオープンソース貢献が可能。
次のステップ
- プロジェクトに CI(GitHub Actions)を組み込み、gradle dependencyUpdatesで依存バージョンの自動チェックを実装。
- 本番環境では AWS Secrets Manager や HashiCorp Vault と連携し、さらに堅牢なシークレット管理を検討してください。
本記事は執筆時点(2024 年)に基づく情報です。バージョンや API の変更がある場合は、公式ドキュメントをご確認ください。