Hono + Cloudflare Workers + AWS SDK for JavaScript v3の構成でAmazon Bedrockが利用できるか試してみた

2024.05.10

どうも!オペレーション部の西村祐二です。

APIを作るときにHono + Cloudflare Workersの構成は開発体験もよく、この構成で検証をおこなっております。

AWSの環境もよく利用しており、AWSのサービスと連携するためにはSDKの利用がほぼ必須になってます。

最近、AIのサービスも利用する機会もあるので、Hono + Cloudflare Workers + AWS SDK for JavaScript v3の構成でAmazon Bedrockが利用できるか試してみました。

その手順などを備忘録のためにまとめておきたいと思います。

環境

  • Hono: 4.3.4
  • AWS SDK for JavaScript v3: @aws-sdk/client-bedrock-runtime 3.572.0
  • wrangler: 3.55.0

事前準備

  • Cloudflareの利用環境の準備

  • Cloudflare環境からAWSを利用するためにAWSのアクセスキーとシークレットキーを取得

  • Amazon Bedrockから利用したい基盤モデルを利用できる状態にしておく

    • 今回、Amazon Titan Text G1 基盤モデル:amazon.titan-text-express-v1を利用します。

下記ブログが参考になります。

AWS入門ブログリレー2024〜Amazon Bedrock編〜 | DevelopersIO

プロジェクト作成

まず、Cloudflare Workersの環境を作っていきます。

CLIをインストールします。

npm install -g wranger

Cloudflareのリソースにアクセスするために認証します。

wrangler login

Cloudflare Workers のプロジェクトを作成します。「test-hono-bedrock」という名前にしてますが適宜変更してください。

wrangler init -y test-hono-bedrock
cd test-hono-bedrock

honoをインストールします。

npm install hono

最小限のコードを実装し、動作確認します。

src/index.ts

import { Hono } from "hono";

const app = new Hono();
app.get("/", (c) => c.text("Hello 🔥"));

export default app;

ローカル環境で開発サーバーを起動し動作確認します。

npm start

下記、画面が表示されていればOKです。

Basic認証

Basic認証を実装しておきます。

数行追加するだけで認証機能をつけれるのは検証時とても便利です。username、passwordは適宜変更してください。

src/index.ts

import { Hono } from 'hono';
import { basicAuth } from 'hono/basic-auth';

const app = new Hono();

app.use(
	'*',
	basicAuth({
		username: 'test',
		password: 'test',
	})
);
app.get('/', (c) => c.text('Hello 🔥'));

export default app;

再度、ブラウザにアクセスしてBasic認証が正常に動作しているか確認しておきましょう。

デプロイ

Cloudflare Workersの環境に下記コマンドでデプロイします。

npm run deply

デプロイが完了したら、Cloudflareのコンソール画面にデプロイしたプロジェクトが追加されていると思います。

Deployments -> View Versionなどからアクセスし、ローカルで確認したものと同じ挙動がCloudflare Workers上でも確認できるかと思います。

Amazon Bedrock関連の実装

ここまで確認できたら、次はAWSサービスとの連携部分の実装をしていきます。

まずは骨組みを作ります。

今回、チャット画面などUI部分をつくらずに動作確認したいため、GETのエンドポイントでpathパラメータにモデルID、クエリパラメータにpromptを指定、その情報を使ってAmazon Bedrock Runtime APIを実行する想定としています。

GET /api/bedrock/:modelId?p=hogehoge

あくまで、動作確認のための実装で本番などには利用しないでください。

まずは、パラメータを受取、レスポンスを返すベースを作ります。

src/bedrock/api.ts

import { Hono } from 'hono';

type Bindings = {
	AWS_ACCESS_KEY_ID: string;
	AWS_SECRET_ACCESS_KEY: string;
	AWS_REGION: string;
};

const bedrock = new Hono<{ Bindings: Bindings }>();
bedrock.get('/:modelId', async (c) => {
	const modelId = c.req.param('modelId');
	const prompt = c.req.query('p');
	const res = 'ai response';
	return c.json({ modelId, prompt, res });
});

export { bedrock };

index.tsを修正しておきます。

src/index.ts

import { Hono } from 'hono';
import { basicAuth } from 'hono/basic-auth';
import { bedrock } from './bedrock/api';

const app = new Hono();

app.use(
	'*',
	basicAuth({
		username: 'test',
		password: 'test',
	})
);
app.route('/api/bedrock', bedrock);

export default app;

ローカルサーバーを起動して/api/bedrock/model?p=hogeのパスにアクセスすると、想定のレスポンスが返ってくることを確認します。

AWS SDK for JavaScript v3導入

Amazon Bedrock Runtime API経由でモデルを実行するため対応したライブラリをインストールします。

npm install @aws-sdk/client-bedrock-runtime

環境変数の設定

ローカル環境で環境変数を読み込むために.dev.varsを作成します。 事前準備で取得しておいたアクセスキー、シークレットキーを設定しておきます。

AWS_REGION=your_region
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key

.dev.varsのファイル名が作成できたら、ローカルサーバーを起動しなおすとログに環境変数を読みこまれた旨のログが出力されます。

...
- Vars:
  - AWS_REGION: "(hidden)"
  - AWS_ACCESS_KEY_ID: "(hidden)"
  - AWS_SECRET_ACCESS_KEY: "(hidden)"
..

クライアント取得、モデル実行部分の実装

AWSより提供されているドキュメントを参考に実装していきます。

SDK for JavaScript (v3) を使用した Amazon Bedrock ランタイムの例 - AWS SDK for JavaScript

src/bedrock/api.ts

import { Hono } from 'hono';
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';

type Bindings = {
	AWS_ACCESS_KEY_ID: string;
	AWS_SECRET_ACCESS_KEY: string;
	AWS_REGION: string;
};

const bedrock = new Hono<{ Bindings: Bindings }>();
bedrock.get('/:modelId', async (c) => {
	const modelId = c.req.param('modelId');
	const prompt = c.req.query('p');

	const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION } = c.env;

	const client = new BedrockRuntimeClient({
		region: AWS_REGION,
		credentials: {
			accessKeyId: AWS_ACCESS_KEY_ID,
			secretAccessKey: AWS_SECRET_ACCESS_KEY,
		},
	});
	const res = 'ai response';
	return c.json({ modelId, prompt, res });
});

export { bedrock };

モデル実行部分の実装

src/bedrock/api.ts

import { Hono } from 'hono';
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';

type Bindings = {
	AWS_ACCESS_KEY_ID: string;
	AWS_SECRET_ACCESS_KEY: string;
	AWS_REGION: string;
};

const bedrock = new Hono<{ Bindings: Bindings }>();
bedrock.get('/:modelId', async (c) => {
	const modelId = c.req.param('modelId');
	const prompt = c.req.query('p');

	const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION } = c.env;

	const client = new BedrockRuntimeClient({
		region: AWS_REGION,
		credentials: {
			accessKeyId: AWS_ACCESS_KEY_ID,
			secretAccessKey: AWS_SECRET_ACCESS_KEY,
		},
	});

	const textGenerationConfig = {
		maxTokenCount: 4096,
		stopSequences: [],
		temperature: 0,
		topP: 1,
	};

	const payload = {
		inputText: prompt,
		textGenerationConfig,
	};

	const command = new InvokeModelCommand({
		body: JSON.stringify(payload),
		contentType: 'application/json',
		accept: 'application/json',
		modelId,
	});

	const response = await client.send(command);
	const decodedResponseBody = new TextDecoder().decode(response.body);
	const responseBody = JSON.parse(decodedResponseBody);
	const res = [];
	for (const result of responseBody.results) {
		res.push(result.outputText);
		console.log(result.outputText);
	}
	return c.json({ modelId, prompt, res: res.join('\n') });
});

export { bedrock };

これで実装は完了です。動作確認していきます。

今回、モデルはAmazon Titan Text G1 基盤モデルを利用するため、対象のモデルIDamazon.titan-text-express-v1をpathパラメータに指定、プロンプトにhelloと指定してます。

パスで表すと下記になります。

/api/bedrock/amazon.titan-text-express-v1?p=hello

パスにアクセスするとSDKからAmazon Bedrock Runtime API経由でモデルが実行され回答が得られます。

プロンプトを変更したりして動作確認をしてください。

Cloudflareにデプロイ

環境変数をCloudflare上に登録しておきます。CLIから下記コマンドで登録できます。

wrangler secret put AWS_REGION
wrangler secret put AWS_ACCESS_KEY_ID
wrangler secret put AWS_SECRET_ACCESS_KEY

デプロイを実行します。

npm run deploy

デプロイが完了したら、Cloudflare WorkersのURLにアクセスし、モデルやプロンプトを指定し、正常にモデルから回答が返ってきているか確認します。

例:

https://test-hono-bedrock.<your_url>.workers.dev/api/bedrock/amazon.titan-text-express-v1?p=こんにちは

正常に回答が確認できればOKです。

さいごに

Hono + Cloudflare Workers + AWS SDK for JavaScript v3の構成でAmazon Bedrockが利用できるか試してみました。

AWS SDKなどで特にエラーは発生せず、すんなりと実装できました。エッジ環境ということもあって何かしらハマる想定でした。

誰かの参考なれば幸いです。