AWS SDK for Java で Amazon Bedrock を呼び出す
[point_maker type=“heading_icon” base_color=“apple_green” title_icon=“info-circle-solid” title_color_background=“true” title_color_border=“false” content_type=“text” content_color_background=“true” content_color_border=“false” block_editor=“true”]
こちらは、 “ゆるWeb勉強会@札幌 Advent Calendar 2023” の 19日目の記事です。
また、“Java Advent Calendar 2023 シリーズ 1” の 19日目の記事です。
[/point_maker]
(投稿日が遅くなってしまいました。。)
普段 AWS SDK を触る場合は JavaScript (TypeScript) がほとんどなのですが、 Java Do での発表をきっかけに Java 版の AWS SDK を触ってみたので、簡単な使い方の流れと感想です。
https://javado.connpass.com/event/300918/
今回は、AWS SDKを使い Amazon Bedrock を呼び出すことにします。
プロジェクト作成
ベースは、公式のチュートリアルに添います。こちらは「S3」での流れなので、 Bedrock と Java のバージョンなどを調整しながら進めます。
https://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/get-started.html
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 なんだよなぁ」というシチュエーションでも立ち向かうことができそうです。