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 を触ってみたので、簡単な使い方の流れと感想です。

Java 21 リリース記念イベント@札幌 (2023/11/18 14:00〜)# ㊗️ Java "21" どうでしょう ㊗️ 2023年9月に、 Javaの新しいLTSバージョンである Java 21 がリリース されました! そこで、Javaチャンピオンである櫻庭祐一さんを東京からお招きし、登壇いただく記念イベント を開催します! また、現地での登壇(20分程度) も募集します!登壇の話題は、Javaの話題はもちろん、挑戦・工夫・ライブラリやフレームワーク・設計・ノウハウ・マネジメント・成功談・失敗談・注目しているもの・おすすめしたいもの...などなど、なんでもOK です! ぜひみなさん話題を持ち寄っていただき、ワイワイ話しましょう! # Timet...
Java 21 リリース記念イベント@札幌 (2023/11/18 14:00〜) javado.connpass.com
Java 21 リリース記念イベント@札幌 (2023/11/18 14:00〜)

今回は、AWS SDKを使い Amazon Bedrock を呼び出すことにします。

プロジェクト作成

ベースは、公式のチュートリアルに添います。こちらは「S3」での流れなので、 Bedrock と Java のバージョンなどを調整しながら進めます。

AWS SDK for Java 2.x の使用を開始する - AWS SDK for Java 2.x最新バージョンの AWS SDK for Java 2.x を使用して最初の Java アプリケーションを構築する
AWS SDK for Java 2.x の使用を開始する - AWS SDK for Java 2.x docs.aws.amazon.com
AWS SDK for Java 2.x の使用を開始する - AWS SDK for Java 2.x

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 なんだよなぁ」というシチュエーションでも立ち向かうことができそうです。

tacck
  • tacck
  • 北の大地の普通のソフトウェアエンジニア。
    インフラ・バックエンド・フロントエンドと、色々やります。

    初心者・若手向けのメンターも希望あればお受けします。

    勉強会運営中
    * ゆるWeb勉強会@札幌
    * スマートスピーカーで遊ぼう会@札幌

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください