AWS SDK for Java で Amazon Bedrock を呼び出す
こちらは、 “ゆるWeb勉強会@札幌 Advent Calendar 2023” の 19日目の記事です。
また、“Java Advent Calendar 2023 シリーズ 1” の 19日目の記事です。
(投稿日が遅くなってしまいました。。)
普段 AWS SDK を触る場合は JavaScript (TypeScript) がほとんどなのですが、 Java Do での発表をきっかけに Java 版の AWS SDK を触ってみたので、簡単な使い方の流れと感想です。
今回は、AWS SDKを使い Amazon Bedrock を呼び出すことにします。
目次
プロジェクト作成
ベースは、公式のチュートリアルに添います。こちらは「S3」での流れなので、 Bedrock と Java のバージョンなどを調整しながら進めます。
Java および Maven は下記バージョンを使用しました。
$ java -version openjdk version "17.0.9" 2023-10-17 OpenJDK Runtime Environment Homebrew (build 17.0.9+0) OpenJDK 64-Bit Server VM Homebrew (build 17.0.9+0, mixed mode, sharing) $ mvn -v Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: /opt/homebrew/Cellar/maven/3.9.6/libexec Java version: 17.0.9, vendor: Homebrew, runtime: /opt/homebrew/Cellar/openjdk@17/17.0.9/libexec/openjdk.jdk/Contents/Home Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x", version: "14.2.1", arch: "aarch64", family: "mac"
プロジェクト作成自体は、チュートリアルのコマンド通りです。
その後、いくつか質問が出てくるなかに利用するサービスを聞かれるところがあるので、そこを Bedrock に対応させます。
$ mvn archetype:generate \ -DarchetypeGroupId=software.amazon.awssdk \ -DarchetypeArtifactId=archetype-app-quickstart \ -DarchetypeVersion=2.20.43 [INFO] Scanning for projects... ... Define value for property 'service': bedrockruntime Define value for property 'httpClient' (should match expression '(url-connection-client|apache-client|netty-nio-client)'): apache-client Define value for property 'nativeImage' (should match expression '(true|false)'): false Define value for property 'credentialProvider' (should match expression '(default|identity-center)'): identity-center Define value for property 'groupId': org.example Define value for property 'artifactId': getstarted Define value for property 'version' 1.0-SNAPSHOT: : <Enter> Define value for property 'package' org.example: : <Enter> ... $
作成したプロジェクトの修正
プロジェクトが作成されたら getstarted
ディレクトリの下に移動し、 pom.xml
の編集を行ないます。
... <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>17</maven.compiler.source> <!-- Change --> <maven.compiler.target>17</maven.compiler.target> <!-- Change --> <maven.shade.plugin.version>3.2.1</maven.shade.plugin.version> <maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version> <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version> <aws.java.sdk.version>2.21.21</aws.java.sdk.version> <!-- Change --> <slf4j.version>1.7.28</slf4j.version> <junit5.version>5.8.1</junit5.version> </properties> ... <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bedrockruntime</artifactId> <version>2.21.21</version> <!-- Add --> <exclusions> ... <dependency> <!-- Add Start --> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Add End -->
引き続き、 src
配下の Java コードの中のパッケージ名を Bedrock に対応させる形で修正します。
コードの中にある、下記の変数っぽいところを、明示したものにします。
change "$serviceClientPrefixClient" -> "BedrockRuntimeClient"
change "${serviceClientVariable}" -> "bedrockRuntime"
src/main/java/org/example/DependencyFactory.java
package org.example; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; /** * The module containing all dependencies required by the {@link Handler}. */ public class DependencyFactory { private DependencyFactory() {} /** * @return an instance of BedrockRuntimeClient */ public static BedrockRuntimeClient bedrockRuntimeClient() { return BedrockRuntimeClient.builder() .httpClientBuilder(ApacheHttpClient.builder()) .build(); } }
src/main/java/org/example/Handler.java
package org.example; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; public class Handler { private final BedrockRuntimeClient bedrockRuntimeClient; public Handler() { bedrockRuntimeClient = DependencyFactory.bedrockRuntimeClient(); } public void sendRequest() { // TODO: invoking the api calls using bedrockRuntimeClient. } }
パッケージ名の修正が終わったので、 Bedrock を呼び出すコードを追加します。今回は Bedrock で提供しているモデルのうち “AI21 Labs / Jurassic-2 Ultra” を使っています(リージョン: 米国東部(バージニア北部) / us-east-1)。そちらの利用設定自体は、この記事では割愛させていただきます。
src/main/java/org/example/Handler.java
package org.example; import java.nio.charset.StandardCharsets; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import software.amazon.awssdk.services.bedrockruntime.model.InvokeModelRequest; import software.amazon.awssdk.services.bedrockruntime.model.InvokeModelResponse; public class Handler { private final BedrockRuntimeClient bedrockRuntimeClient; public Handler() { bedrockRuntimeClient = BedrockRuntimeClient.builder().region(Region.US_EAST_1).build(); } public void sendRequest() { String body = """ { "prompt": "What is the Java?", "maxTokens": 200, "temperature": 0, "topP": 1, "stopSequences": [], "countPenalty": { "scale": 0 }, "presencePenalty": { "scale": 0 }, "frequencyPenalty": { "scale": 0 } } """; InvokeModelResponse response = bedrockRuntimeClient.invokeModel(InvokeModelRequest.builder() .modelId("ai21.j2-ultra-v1") .contentType("application/json") .accept("*/*") .body(SdkBytes.fromString(body, StandardCharsets.UTF_8)) .build() ); String responseBody = new String(response.body().asByteArray()); ObjectMapper mapper = new ObjectMapper(); try { JsonNode root = mapper.readTree(responseBody); String results = root.get("completions").get(0).get("data").get("text").toString(); System.out.println("results: " + results); } catch (Exception e) { System.out.println(e); } bedrockRuntimeClient.close(); } }
ざっと説明すると「Javaとは何ですか?」という質問を投げるコードになっています。
では、これを実行してみましょう。
ビルドしてローカルから実行
実行は簡単、下記のようにします。
$ mvn clean package $ mvn exec:java -Dexec.mainClass="org.example.App" [INFO] Scanning for projects... [INFO] [INFO] -----------------------< org.example:getstarted >----------------------- [INFO] Building getstarted 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- exec:3.1.0:java (default-cli) @ getstarted --- 2023-12-24 14:56:27:185 +0900 [org.example.App.main()] INFO org.example.App - Application starts 2023-12-24 14:56:27:455 +0900 [org.example.App.main()] DEBUG software.amazon.awssdk.request - Sending Request: DefaultSdkHttpFullRequest(httpMethod=GET, protocol=https, host=portal.sso.ap-northeast-1.amazonaws.com, encodedPath=/federation/credentials, headers=[amz-sdk-invocation-id, User-Agent, x-amz-sso_bearer_token], queryParameters=[role_name, account_id]) 2023-12-24 14:56:27:881 +0900 [org.example.App.main()] DEBUG software.amazon.awssdk.request - Received successful response: 200, Request ID: 242bb8f6-0090-4467-bdd0-ea2a3691178d, Extended Request ID: not available 2023-12-24 14:56:27:891 +0900 [org.example.App.main()] DEBUG software.amazon.awssdk.request - Sending Request: DefaultSdkHttpFullRequest(httpMethod=POST, protocol=https, host=bedrock-runtime.us-east-1.amazonaws.com, encodedPath=/model/ai21.j2-ultra-v1/invoke, headers=[Accept, amz-sdk-invocation-id, Content-Type, User-Agent], queryParameters=[]) 2023-12-24 14:56:30:456 +0900 [org.example.App.main()] DEBUG software.amazon.awssdk.request - Received successful response: 200, Request ID: d15471d7-6472-4da3-8b5f-d1441cf2b0d5, Extended Request ID: not available results: "\nJava is a programming language and computing platform first released by Sun Microsystems in 1995. Java is a general-purpose language, meaning it can be used to create a wide range of applications, from simple to complex. It is a high-level language, meaning it is closer to human language than machine language, making it easier to read and write. Java is also an object-oriented language, meaning it encourages a modular approach to programming. Java code can run on any device that has a Java Virtual Machine (JVM) installed, making it a popular choice for cross-platform development." 2023-12-24 14:56:30:704 +0900 [org.example.App.main()] INFO org.example.App - Application ends [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.824 s [INFO] Finished at: 2023-12-24T14:56:30+09:00 [INFO] ------------------------------------------------------------------------
それっぽい感じで応答が返ってきました。問題なく呼び出せているようですね。
感想
実際に使ってみた感想としては、使い方そのものは特に違和感なく扱うことができました。
JavaScript と比較した場合は、レスポンスの受け方の柔軟性が低いところが「試行錯誤しながらやる」時にはちょっと面倒に関します。
JavaScript であれば、「とりあえず Response を受け取って、その中身を JSON(の文字列)化して確認する。」ということをやったりするのですが、 Java の場合は「そもそも JSON で受け取るために外部パッケージが必要」だったり、「送信するデータをJSONで作成するときに型や構造をだいぶ意識して構築する」ような感じになるので、言語の型の強弱の違いはだいぶ感じます。
また、レスポンスも SdkBytes
というバイト列で受け取ったものを文字列にする感じなので、この辺りも言語の特徴を感じたところでした。
そういう違いはあれど、 Java でも AWS SDK を手軽に扱うことができたので、「AWS SDK 使いたいけど Java なんだよなぁ」というシチュエーションでも立ち向かうことができそうです。