Amazon API Gatewayのストリーム応答を使ったBedrockの滑らかな応答の実装例

はじめに

Amazon API Gatewayがストリーム応答をサポートするというアナウンスがありました。

aws.amazon.com

上記のアナウンス文にも書かれていますが、ユースケースとしてわかりやすいのがBedrockを使ったエージェントアプリケーションです。一気に応答が帰ってくるよりもぱらぱらと応答が返ってきた方が利用者体験としては優れています。

実装

実装はAPI Gatewayだけ設定すれば良いというものではなく、Lambdaの実装から考える必要があります。以下のドキュメントを見ながら実装を進めます。

docs.aws.amazon.com

最終的には、以下のような実装となりました。ランタイムはNode.js、モデルはAmazon Nova Liteです。

import { BedrockRuntimeClient, ConverseStreamCommand } from "@aws-sdk/client-bedrock-runtime";

const client = new BedrockRuntimeClient({ region: process.env.AWS_REGION || "ap-northeast-1" });

export const handler = awslambda.streamifyResponse(async (event, responseStream) => {
  try {
    console.log("Lambda function started");
    
    const body = JSON.parse(event.body || "{}");
    const prompt = body.prompt || "日本のプロ野球球団のひとつ、阪神タイガースについて教えてください。";
    const modelId = body.modelId || "amazon.nova-lite-v1:0";
    
    console.log("Request parameters:", { modelId, promptLength: prompt.length });

    // Converse API用のコマンド
    const command = new ConverseStreamCommand({
      modelId: modelId,
      messages: [
        {
          role: "user",
          content: [
            {
              text: prompt
            }
          ]
        }
      ],
      inferenceConfig: {
        maxTokens: body.maxTokens || 4096,
        temperature: body.temperature || 0.7,
        topP: body.topP || 0.9
      }
    });

    console.log("Sending request to Bedrock");
    const response = await client.send(command);
    console.log("Received response from Bedrock");

    responseStream = awslambda.HttpResponseStream.from(responseStream, {
        statusCode: 200,
        headers: {
          "Content-Type": "application/json"
        }
    });

    // Converse APIのストリーミングレスポンスを処理
    let chunkCount = 0;
    for await (const event of response.stream) {
      if (event.contentBlockDelta?.delta?.text) {
        chunkCount++;
        responseStream.write(event.contentBlockDelta.delta.text);
      }
    }

    console.log(`Streaming completed. Total chunks: ${chunkCount}`);
    responseStream.end();
  } catch (error) {
    console.error("Error:", error);
    
    // エラー時は500ステータスコードを返す
    responseStream = awslambda.HttpResponseStream.from(responseStream, {
      statusCode: 500,
      headers: {
        "Content-Type": "application/json"
      }
    });
    
    responseStream.write(JSON.stringify({ 
      error: error.message,
      type: error.name 
    }));
    responseStream.end();
  }
});

エラー時の処理についてはあまりテストしていないので、本番適用するのはもうちょっとテストが必要かなと思います。

API Gateway

API Gatewayの設定は、レスポンス転送モードをストリームに設定するだけです。

APIのデプロイを実行するのを忘れずに。

実行

動作検証はcURLを使って以下のようなコマンドを打ちました。

curl --no-buffer <API GatewayのGETメソッドのURL>

こんな結果が返ってきました(文字ベースなのでわからないかと思いますが、実際にはパラパラと応答が返ってきました)。なお、結果は色々と間違えているのはAmazon Nova Liteの影響です。

阪神タイガース(Hanshin Tigers)は、日本のプロ野球・セントラル・リーグに所属するプロ野球球団です。本拠地は兵庫県西宮市にある阪神甲子園球場です。以下に、阪神タイガースに関する詳細な情報を提供します。

### 歴史
- **創設**: 1935年、阪神電気鉄道の創業者・小林一三によって創設されました。
- **戦前**: 1936年に「阪神電気鉄道株式会社」として設立され、同年に「阪神軍」として創設されました。
- **戦後**: 1946年に現在の「阪神タイガース」に名称を変更しました。

### 球団の特徴
- **ユニフォーム**: ユニフォームのカラーは赤色と白色が特徴的です。赤いユニフォームは「赤いダイヤモンド」と称され、球団の象徴となっています。
- **マスコット**: 球団の公式マスコットは「トラッキー」です。トラッキーは阪神タイガースのマスコットキャラクターとして親しまれています。
- **応援スタイル**: 阪神タイガースのファンは「タイガースファン」と呼ばれ、熱狂的な応援で知られています。特に甲子園球場では、独特の応援スタイルが特徴的です。
(以下略)

考察

Lambdaの実装に一工夫いるとはいえ、API Gatewayのストリーム応答を使ってBedrockの滑らかな応答がコンソールの設定一つで実現できるのは非常に魅力です。

なお、いくつか注意点があります。

Lambdaのストリーミングレスポンスには追加料金がかかります。東京リージョンの場合、USD 0.008/GBかかるようです(2025年11月時点)。詳細は料金ページを参照してください。

aws.amazon.com

また、今回ランタイムとしてNode.jsを使いました。他の言語においては、カスタムラインタイムの使用やLambda Web Adapterを使用する必要があります。以下、引用です。

Lambda は Node.js マネージドランタイムでのレスポンスストリーミングをサポートしています。その他の言語の場合は、カスタムランタイム API 統合を備えたカスタムランタイムを使用してレスポンスをストリーミングするか、Lambda Web Adapter を使用することができます。

docs.aws.amazon.com

他の言語でサクッと実装するのはちょっと難しそうなので、今回はNode.jsを使いました。