見出し画像

Amazon Bedrockをアプリと繋げてみる ~ ローカル開発用バックエンドの作成 ~

前回は「Amazon Bedrockを使ってみた」ということで、AWSコンソール上で利用してみるところまでをやってみました。

本記事ではローカル環境用のバックエンドを作成するところまでやっていきます。ローカル環境と言いつつも、Cognito、Lambda、Bedrockについては、AWS側のリソースを作成する必要があるため、完全なローカル環境は難しいですね。とは言え、AWS上でもローカル環境用リソースを用意しておくことで、本番環境などに影響を与えることなく、開発を行えるので、メリットは大きいです。

※Lambdaレスポンスストリーミングをローカル環境で実現する方法があれば、もう少しAWSリソースを減らせるのですが、色々やった結果、無理そうなので諦めました。

AWSリソースの作成

AWSリソースの作成については、AWSコンソールから作成でも良いのですが、今回はAWS Cloud Development Kit (CDK) を利用してみます。

AWS Cloud Development Kit (CDK)について

AWS CDKは、IaCをプログラミング言語で書けるということで、プログラミングをする方にとっては、利用しやすいものとなっています。CloudFormationだと実現が出来ない柔軟性やコードの整理のしやすさもあります。また、CDKはCloudFormationのテンプレートを作成しているため、CloudFormationの知識も活かせるのも良い点だと感じています。最近は勉強がてら色々と作ってみています。

初めての利用であれば以下のページの通り実施していくことで、利用ができるようになります。

また、CDKではsamコマンドと連携して開発を効率化することも出来るため、そちらを利用して開発を行うことができます。通常のAPI Gateway + Lambdaという構成であれば、sam local start-apiなどで、ローカルである程度の開発を行うことが可能になっています。

なお、cdk watch コマンドもあるため、デプロイを伴うような場合はこのコマンドを利用して開発効率を高めるという手もあるかなと思います。私は今回はsam local start-api、start-lambdaで上手く確認ができなかったため、cdk watchでLambdaのデプロイをファイル保存と同時に行い、サンプルの作成を行っていました。

CDKでのIaCの作成

今回はローカル環境用にLambda、Cognitoを作成していきます。

  • Lambda

    • リクエストを受け、Bedrockを呼び出し、その結果を呼び出し側にストリームで返却します

  • Cognito

    • Lambdaを実行する上で権限が必要になるため、CognitoのIDプールと紐づけて、IAMロールで権限を付与します。

構成図としては以下の形です。今回はAWS側のリソースの作成になります。

構成図

適当なディレクトリを作成して、その中でCDKの初期化を行います。言語としてはTypeScriptを利用します。

mkdir -p sample-bedrock/cdk
cd sample-bedrock/cdk
cdk init app --language typescript

色々とファイルの作成やライブラリのインストールなどが実行されて、以下の通り終了すれば初期化が完了した状態です。

図1

次に追加で必要となるライブラリを入れていきます。

@aws-sdk/client-bedrock-runtime

LambdaでBedrockを利用するためのライブラリです。CDKとして利用するわけでは無く、Lambdaのリソース作成の際に指定するのですが、npmに無いライブラリを指定するとエラーになるため、入れておきます。

@aws-cdk/aws-cognito-identitypool-alpha

CognitoのIDプール用のライブラリで、まだAlphaモジュールということで、別で提供されています。CDKのバージョンと合わせる必要があるため、package.jsonを確認し、CDKのバージョンに合わせたものをインストールしてください。

私の現在のCDKバージョンは「"aws-cdk-lib": "2.102.0"」ということなので、「@aws-cdk/aws-cognito-identitypool-alpha@2.102.0-alpha.0」を入れます。

バージョンについては以下で確認ができます。

以下を実行してライブラリをインストールします。

npm i @aws-sdk/client-bedrock-runtime @aws-cdk/aws-cognito-identitypool-alpha@2.102.0-alpha.0
図2

あとは、Cognito、Lambda、Bedrockを利用するためのLambdaのコードを作成していきます。

CDKでのポイント

以下はCDKでのポイントとなるソースです。

// Bedrockの呼び出しを行うLambda関数を作成する
const functionName = `${localNamePrefix}-predict-stream`
const predictStreamFunction = new NodejsFunction(this, functionName, {
  functionName,
  runtime: Runtime.NODEJS_18_X,
  entry: './backend/predictStream.ts', // Lambdaでの実際の処理を行うソースファイル
  timeout: Duration.minutes(15),
  bundling: {
    nodeModules: ['@aws-sdk/client-bedrock-runtime'], // Bedrockのライブラリを指定
  },
  environment: {
    MODEL_REGION: region,
    MODEL_ID: 'anthropic.claude-instant-v1',
  },
})

// IDプールの認証済みロールからの呼びだしを許可する
predictStreamFunction.grantInvoke(idPool.authenticatedRole)

// Bedrock を利用できるように権限を付与する
const bedrockPolicy = new PolicyStatement({
  effect: Effect.ALLOW,
  resources: ['*'],
  actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
})
predictStreamFunction.role?.addToPrincipalPolicy(bedrockPolicy)

bundlingフィールドに@aws-sdk/client-bedrock-runtimeを指定することで、一緒にパッケージングして、デプロイしてくれます。
entryフィールドには、Lambdaでの実際の処理を行うソースファイルを指定します。

次にgrantInvokeメソッドで、CognitoのIDプールの認証済みロールからの呼び出しを許可しています。また、ポリシーにBedrockのInvokeModelInvokeModelWithResponseStreamのアクションを追加することで、Bedrockのモデル実行系のアクションを許可しています。

Lambdaのソースファイル

import type { Writable } from 'node:stream'
import type { Context, Handler } from 'aws-lambda'
import {
  BedrockRuntimeClient,
  InvokeModelWithResponseStreamCommand,
} from '@aws-sdk/client-bedrock-runtime'

// 以下からTypeScriptのため、形を定義している
declare let awslambda: {
  streamifyResponse: (
    streamHandler: (
      event: PredictRequest,
      responseStream: Writable,
      context: Context,
    ) => Promise<void>,
  ) => Handler
}

interface PredictRequest {
  messages: string
}

// 以下から実際のLambdaでの処理
export const handler = awslambda.streamifyResponse(async (event, responseStream, context) => {
  if (event.messages == null) {
    console.error('messages is required')
    responseStream.end()
    return
  }

  const client = new BedrockRuntimeClient({
    region: process.env.MODEL_REGION,
  })

  const params = {
    max_tokens_to_sample: 3000,
    prompt: event.messages,
  }

  const command = new InvokeModelWithResponseStreamCommand({
    modelId: process.env.MODEL_ID,
    body: JSON.stringify(params),
    contentType: 'application/json',
  })
  const res = await client.send(command)

  if (res?.body != null) {
    for await (const streamChunk of res.body) {
      if (streamChunk.chunk?.bytes == null) {
        break
      }
      const body = JSON.parse(new TextDecoder('utf-8').decode(streamChunk.chunk?.bytes))
      if (body?.completion != null) {
        responseStream.write(body.completion)
      }
      if (body?.stop_reason != null) {
        break
      }
    }
  }

  responseStream.end()
})

今回はTypeScriptを利用しているため、インポートの後の declare let awslambda ~ interface PredictRequest あたりで型などを定義しています。

実際の処理としては、BedrockRuntimeClient、InvokeModelWithResponseStreamCommand を利用して簡単に実行することが出来るようになっています。

InvokeModelWithResponseStreamCommandに渡すパラメーターとしては、Bedrockのモデルの情報として載っていますので、それらを確認して設定します。

今回はClaude 1.2を利用するため、以下の画面の情報を参考に設定します。
Bedrock > Getting started > Providers > Anthropic > Claude Instant 1.2

図2

あとは受け取ったレスポンスをストリーミングの書き方に倣い、呼び出し側に返していくだけです。

Lambda レスポンスストリーミングの書き方など

Lambda レスポンスストリーミングは以下のドキュメントなどを見ることでおおよそは把握ができると思います。あとはサンプルなどがインターネット上にあるので、それで書き方などを調べてみてください。

CDKでのデプロイ

準備ができたら、CDKでのデプロイをして、AWS環境にローカル環境用のリソースを作成していきます。cdk deployコマンドがあるため、このコマンドでデプロイを行います。実際には、環境ごとにcdk deployコマンドのオプションが異なったりするため、package.jsonに専用のデプロイコマンドを用意して、それで実行する形が望ましいと思います。

今回は直接cdk deployコマンドを実行してデプロイします。

cdk deploy

途中で以下のようにデプロイをするか確認がされるため 、誤りが無いか確認して、y で許可します。CI/CDに組み込むときには、コマンドのオプションの --require-approval neverで確認が出ないようにすればよいでしょう。

図2
図3

実際にAWSコンソールから、Cognito、Lambdaを確認すると作成されていることが分かります。また、CDKはCloudFormationを使ってデプロイを行なっているため、CloudFormationの画面から作成されたリソースなどの情報を確認することもできます。

図4

動作確認について

以上でバックエンド環境の作成が完了しました。本当はLambdaの動作確認も書きたかったのですが、フロントエンドのアプリ無しに、Lambdaレスポンスストリーミングの呼び出しが上手くいかず断念しました。

AWSコンソールのLambdaのテスト実行をすると、ストリーミングに対応していないのか、永遠にレスポンスが返ってこなかったです。何か設定などがあれば確認はできそうですが、調べた限り見つからなかったです。

関数URLを作成すればcurlなどで確認が出来るようですが、今回は使うことは無いので、動作確認のためにURLを作るのも違うなと感じたのでやめました。

そのため、実際には次回の記事の主題となるフロントエンドを作成して、ブラウザから呼び出しながら確認と修正、cdk watchでのデプロイを行いながら確認を実施していました。

まとめ

今回はローカル環境用のバックエンドの作成ということで、CDKを利用してデプロイまでしました。チームで開発となると共有リソースと個人リソースを考慮して作り込む必要がありますが、今回はサンプルという事でその辺は考慮はせずに作りました。

次回はローカル環境用のフロントエンドを作成し、実際に画面を動作させて、Bedrockからの回答を画面に反映させてみたいと思います。

今回のCDKのソースの完全版は以下で確認ができます。

関連記事


この記事が気に入ったらサポートをしてみませんか?