2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GenU閉域モードを深掘りする~バックエンド・ユースケース編~

2
Posted at

概要

前回はGenU閉域モードのフロントエンドと認証部分を見ていきました。

今回はバックエンド側の処理を深掘りします。まずはRAGなどの追加機能を有効化していないデフォルト状態で使えるユースケースを深掘りしていきます。

g01.png

ホームのユースケースには下記が表示されています。

  • チャット
  • 音声チャット
  • 文章生成
  • 要約
  • 議事録作成
  • 執筆
  • 翻訳
  • Webコンテンツ抽出
  • 画像生成
  • 動画生成
  • 映像分析
  • ダイアグラム作成

ユースケース連携には下記があります。

  • ブログ記事作成
  • 議事録作成

ここではまず、チャットを例に見ていきます。

チャット

フロントエンドで認証後のバックエンド側の処理は下記の図のような感じになります。

g02.png

チャット画面から「Hello. Where is the capital of Japan?」と入力します。

g03.png

この際、Cognitoで取得した一時クレデンシャルを使用して、「APIPredictStream」Lambda関数が実行されます。
※実際のリソース名は下記のように、StackName-APIPredictStream<ランダム文字列>となります。
今後登場するリソースに関しても、省略形で記載します。

g04.png

この関数から、選択したモデルに対してBedrockへリクエストを送信し、ストリーム形式で回答を受信します。ここで左側の履歴の箇所を見ると、チャットの履歴が表示されています。
この処理では、API Gateway→Lambda「APIApiHandler」→DynamoDB「DatabaseTable」という流れで書き込みが行われます。

g05.png

DatabaseTableには下記の通り、チャットの履歴が保存されています。

g06.png

この書き込みに続けて、同じLambda「APIApiHandler」内でDynamoDB「DatabaseStatsTable」への書き込みも行われます。
このテーブルはトークン使用量の集計に使用されます。

なお、この履歴保存はBedrockのストリーミング応答がすべて完了した後に行われます。チャンクを受信するたびに都度書き込まれているわけではありません。

g07.png

上記の通り、推論処理はLambdaを実行し、履歴の保存はAPI Gateway経由でDynamoDBへの書き込みで行われます。

チャット以外のユースケース

チャット以外のユースケースが、バックエンド処理としてチャットと同じ仕組みを使っているのか、表で整理します。

ユースケース Lambda呼び出し方式 チャットと同じ処理か
チャット APIPredictStream直接Invoke 基準
文章生成 APIPredictStream直接Invoke ○ 同じ
要約 APIPredictStream直接Invoke ○ 同じ
翻訳 APIPredictStream直接Invoke ○ 同じ
執筆 APIPredictStream直接Invoke ○ 同じ
Webコンテンツ抽出 APIPredictStream直接Invoke ○ 同じ(事前にURL→テキスト抽出APIが1つ挟まる)
映像分析 APIPredictStream直接Invoke ○ 同じ(カメラ映像から切り出した画像フレームをS3にアップロードしてから解析する点が追加)
議事録作成 APIPredictStream直接Invoke ○ 同じ(リアルタイム音声認識と組み合わせる)
ダイアグラム作成 APIPredictStream直接Invoke ○ 同じ(事前に図の種類を判定するAPIが1つ挟まる)
音声チャット AppSync Events API(イベントチャネル)経由 × 異なる
画像生成 API Gateway経由 POST /image/generate × 異なる(同期API)
動画生成 API Gateway経由 POST /video/generate × 異なる(非同期ジョブ、DynamoDBにジョブ情報を保存し、ポーリングで状況を取得)

チャットと同じ処理のユースケース

つまり、チャット・文章生成・要約・翻訳・執筆・Webコンテンツ抽出・映像分析・議事録作成・ダイアグラム作成の9つは、すべてAPIPredictStreamを直接Invokeしてストリーミング応答を受け取るという、チャットと全く同じバックエンド処理パターンを共有しています。

各処理によって異なる点は下記の3点です。(フロントエンド側の処理)
以下、チャットと要約を例に、実際のコードで確認していきます。

どのシステムプロンプトを積むか

下記はChatの場合と要約の場合のシステムプロンプトです。処理内容によって、これらが選択されて使用される形になります。

const systemContexts: { [key: string]: string } = {
  '/chat': `You are an AI assistant helping users in chat.
When explaining processes, relationships, or structures, you can use Mermaid diagrams in code blocks (e.g., \`\`\`mermaid).
Automatically detect the language of the user's request and think and answer in the same language.`,

  '/summarize': `You are an AI assistant that summarizes text. 
I will give you summarization instructions in the first chat, and then you should improve the summary results in subsequent chats.
Automatically detect the language of the user's request and think and answer in the same language.`,
  // ...他の画面パス分も定義されている
};

入力した文字列をどう加工してから送るか

ユーザーが入力した内容も、画面ごとに「そのまま送る」か「加工してから送る」かが異なります。チャットは、入力した文章をそのままBedrockに送ります。

chatPrompt(params: ChatParams): string {
  return params.content;
},

要約は、入力した文章を<article>というタグで囲み、「この記事を要約してください」という指示文を付け加えた形に変換してから送ります。

summarizePrompt(params: SummarizeParams): string {
  return `Summarize the article enclosed in the <article></article> xml tag.

<article>
${params.sentence}
</article>`;
},

どの見た目のコンポーネントを使うか

画面の「入力欄」や「送信ボタン」の作りも、画面ごとに違います。
チャット画面は、InputChatContentという専用パーツを1つ呼び出すだけで、入力欄・送信ボタン・ファイル添付・リセットボタンなどが全部セットになっています。会話を何度も続ける前提の画面なので、送信後は入力欄が空になり、続けて次の質問を入力できます。

<InputChatContent
  content={content}
  onChangeContent={setContent}
  onSend={() => {
    if (!loading) {
      onSend();
    } else {
      onStop();
    }
  }}
  fileUpload={fileUpload}
/>

一方、要約画面はTextarea(入力欄)とButton(実行ボタン)を、画面側のコードで個別に配置しています。1回入力して1回結果を受け取るだけのシンプルなフォームです。

<Textarea
  placeholder={t('summarize.enter_text')}
  value={sentence}
  onChange={setSentence}
/>

<Button disabled={disabledExec} onClick={onClickExec}>
  {t('common.execute')}
</Button>

このように画面の見た目や操作感は大きく異なりますが、ボタンを押した後に動くバックエンドの処理はどちらも同じです。「どんな入力パーツを画面に置くか」だけがReact側で画面ごとに変えられているというわけです。

音声チャット

続いて、チャットと処理が異なる3つのユースケースを見ていきます。まずは音声チャットです。

閉域モードでは、この音声チャットのユースケースは利用できません。

理由は、音声チャットが他のユースケースと異なりAppSync Events APIを使った双方向通信で実装されている点にあります。閉域モードではVPCエンドポイント経由で各AWSサービスにプライベート接続しますが、AppSyncにはインターフェース型のVPCエンドポイントが提供されていないため、閉域環境のブラウザからAppSyncのリアルタイムエンドポイントへ到達する経路を作れません。そのため公式ドキュメントの制約事項にも「Voice Chat のユースケースは現状利用できません」と明記されています。

画像生成

画像生成はチャットと違ってAPI Gateway経由の同期APIとして実装されています。

g08.png

ブラウザから処理を実行するとPOST /image/generateをAPI Gatewayに送信します。ここからAPI Gatewayに紐づいたLambda(APIApiHandler)が実行され、Lambda内からInvokeModelCommandでBedrockを呼び出します。

g09.png

チャットは文字が徐々に表示されるストリーミングが必要でしたが、画像生成は「生成完了 → 1枚の画像を返す」という性質のため、ストリーミングを使う必要がありません。そのため通常のAPI Gateway + Lambdaという素直な同期API構成になっています。
また、生成した画像はDynamoDBには保存されず、ブラウザの画面上にBase64データとして保持されるだけです。そのため画面をリロードすると生成結果は消えます。

動画生成

ブラウザからPOST /video/generateをAPI Gatewayに送信し、APIApiHandler Lambdaが実行されます。ここまでは画像生成と同様ですが、動画生成は、Bedrockの非同期ジョブを使う構成です。
生成に数分かかるため、リクエストを投げて終わりではなく、ジョブの状態を管理する仕組みが入っています。
Bedrockは生成した動画をS3の一時バケットに保存します。一時バケットはLambdaの環境変数「VIDEO_BUCKET_REGION_MAP」に定義されています。

ジョブ状態はチャット履歴と同じDynamoDB(DatabaseTable)を使用しています。
ブラウザからGET /video/generateでジョブ一覧を取得し、その際にLambdaがBedrockのジョブ状態を確認しています。定期実行のスケジューラなどはなく、ブラウザが一覧を取得しに来たタイミングでまとめて確認する仕組みです。

g10.png

ジョブが完了すると、Lambda「APIApiHandler」が別のLambda「APICopyVideoJob」を非同期で呼び出し、動画をS3の一時バケットからアプリが参照するバケットにコピーします。コピーが完了するとブラウザ側で動画が再生できるようになります。Lambdaの環境変数「BUCKET_NAME」で指定されたS3バケットに動画が保存されています。

DynamoDB上のステータスは「InProgress(生成中)」→「Finalizing(コピー中)」→「Completed(完了)」の3段階で遷移します。コピー中を表すFinalizingがあるため、「生成は終わったがまだ再生できない」状態をブラウザ側で区別できるようになっています。

g11.png

その他サービスの利用

ここまでBedrockを中心に見てきましたが、GenUではAmazon TranscribeとAmazon Pollyも使われています。どちらもBedrockの前後で音声とテキストを変換するために使用されています。

Amazon Transcribe(音声 → テキスト)

Amazon Transcribeは、音声をテキストに文字起こしするサービスです。下記の箇所で使用されています。

  • ユースケースの「議事録作成」
  • ユースケースの「翻訳」(マイクボタンで音声入力する場合)
  • ツールの「音声認識」
  • ユースケース連携の「議事録作成」(1ステップ目でツールの音声認識を使用)

使い方は2種類あります。マイクやブラウザタブの音声をその場で文字にするリアルタイム文字起こしと、録音済みファイルをアップロードして後からまとめて文字起こしする方法です。

前者はブラウザからTranscribeへWebSocketで直接接続します。後者はPOST /transcribe/startをAPI Gatewayに送信し、Lambda「APIApiHandler」から非同期の文字起こしジョブを起動します。音声ファイルはS3「TranscribeAudioBucket」に、結果はS3「TranscribeTranscriptBucket」に保存され、動画生成と同じくポーリングで結果を取得します。

Amazon Polly(テキスト → 音声)

Amazon Pollyは、テキストを音声に変換して読み上げるサービスです。ユースケースの「翻訳」で使用されています。翻訳結果の横にあるスピーカーのボタンを押すと、翻訳後の文章をPollyが読み上げます。こちらはブラウザからPollyへ直接リクエストを送信しており、Lambdaを経由しません。

いずれもCognitoで取得した一時クレデンシャルで認証されます。閉域モードでは、これらのサービス向けにインターフェース型VPCエンドポイント(Transcribe、Transcribe Streaming、Polly)が作成されており、そのエンドポイント経由でプライベートに接続されます。

まとめ

改めて全体を振り返ると、GenUはBedrockを使いやすくするためのアプリという構成になっています。多くのユースケースでLambda(APIPredictStream)を呼び出しており、違いはシステムプロンプト・ユーザープロンプトの組み立て方・画面のパーツだけです。残る3つのユースケースが違う作りになっているのも、Bedrock側のAPIの性質が違うためです(画像生成は同期API、動画生成は非同期ジョブ、音声チャットは双方向ストリーム)。そしてTranscribeとPollyは、そのBedrockに渡す入力・受け取る出力を音声に変換する周辺サービスという位置づけです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?