👀

Amazon Bedrockの「Too Many Requests」エラー回避策

2024/11/13に公開

はじめに

Amazon BedrockのFoundation modelsとAmazon Kendraを用いてRAGを構築をしました事前に用意した質問リストに対してAnthropicのClaudeモデルを使用し、回答させました。しかし以下のエラーメッセージが発生し、処理が停止してしまいました。

ThrottlingException: An error occurred (ThrottlingException) when calling the
InvokeModel operation (reached max retries: 4): Too many requests, please wait
before trying again. You have sent too many requests.  Wait before trying again."

また事前に用意した質問数は100を超えており、boto3を使用してリクエストを送信しています。本記事では、この「Too Many Requests」エラーの原因とその解決策について詳しく解説します。

原因

クォータの上限に達している

AWS Bedrockには、リージョンごとおよびアカウントレベルで設定された1分あたりのリクエスト数とトークン数の上限があります。これらのクォータを超過すると、「Too Many Requests」エラーが発生します。

クォータの確認方法は以下の通りです:

  1. AWSマネジメントコンソールにログインします。
  2. Service Quotas に移動します。
  3. クォータの管理 から Amazon Bedrock を選択します。
  4. クォータ名で「Claude」を検索して、現在のクォータ値を確認します。

クォータ確認方法

解決策

解決策1:リージョンを変更する

AWSの異なるリージョンでは、利用可能なクォータが異なります。リージョンを変更することで、より高いクォータを利用できる場合があります。以下の表は、バージニア北部 リージョンと東京リージョンにおけるAnthropic Claudeモデルの1分あたりのトークン数の上限を比較したものです。

モデル リージョン 適用されたアカウントレベルのクォータ値 AWS のデフォルトのクォータ値
On-demand InvokeModel tokens per minute for Anthropic Claude 3.5 Sonnet バージニア北部 (us-east-1) 40,000 400,000
東京 (ap-northeast-1) 20,000 200,000
On-demand InvokeModel tokens per minute for Anthropic Claude 3 Sonnet バージニア北部 (us-east-1) 100,000 1,000,000
東京 (ap-northeast-1) 20,000 200,000
On-demand InvokeModel tokens per minute for Anthropic Claude 3 Haiku バージニア北部 (us-east-1) 200,000 2,000,000
東京 (ap-northeast-1) 20,000 200,000

リージョンを変更することで、利用可能なクォータを増やし、エラーの発生を抑制することが可能です。

解決策2:リクエスト頻度を制限する

リクエストの頻度を適切に制御することで、クォータの上限を超えずにリクエストを送信することができます。また、Boto3のリトライ機能を活用することで、エラー発生時に自動的に再試行を行い、リクエストを送信することができます。 この二つの方法を合わせ、「Too Many Requests」エラーを回避しました。

Boto3のリトライ機能の概要

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html より
Boto3では、AWSサービスへの呼び出しがクライアント側の予期せぬ問題や、サービス側のレート制限により失敗することがあります。これらの失敗は特別な処理を必要とせず、通常は短い待機期間の後に再試行することで解決できます。Boto3は、これらのエラーや例外が発生した際にクライアント呼び出しを再試行するための多くの機能を提供しています。

Boto3のリトライ機能を活用する

Boto3には、自動的にリクエストをリトライする機能が備わっています。この機能を活用することで、リクエストが失敗した際に自動的に再試行し、エラーを回避することが可能です。以下はその実装例です。

import pandas as pd
import boto3
import os
import json
from dotenv import load_dotenv
from tqdm import tqdm  
import time  
from botocore.config import Config 
from botocore.exceptions import ClientError

load_dotenv()

# リトライ設定の作成
retries_config = Config(
    retries={
        'max_attempts': 10,  # 最大10回のリトライ
        'mode': 'adaptive'   # リトライモード
    }
)

# Kendraクライアントの作成
kendra = boto3.client(
    'kendra',
    aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
    aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
    region_name=os.getenv('AWS_REGION'),
    config=retries_config  # リトライ設定を適用
)

# Bedrockクライアントの作成
bedrock = boto3.client(
    'bedrock-runtime',
    aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
    aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
    region_name=os.getenv('AWS_REGION'),
    config=retries_config  # リトライ設定を適用
)

# Kendraで検索する関数
def search_kendra(query):
    while True:
        try:
            response = kendra.query(
                IndexId='8',
                QueryText=query,
                PageSize=3,
                AttributeFilter={
                    "EqualsTo": {
                        "Key": "_language_code",
                        "Value": {"StringValue": "ja"},
                    },
                }
            )
            passages = []
            for result in response['ResultItems']:
                if 'DocumentExcerpt' in result and 'Text' in result['DocumentExcerpt']:
                    passages.append(result['DocumentExcerpt']['Text'])
            return '\n'.join(passages)
        except ClientError as e:
            if e.response['Error']['Code'] == 'ThrottlingException':
                print(f"ThrottlingException: {e}, 1分後に再試行します...")
                time.sleep(60)  # 1分待機
                continue  # 再試行
            else:
                print(f"ClientErrorが発生しました: {e}")
                return None
        except Exception as e:
            print(f"エラーが発生しました: {e}")
            return None

# 回答を生成する関数
def generate_answer(question, context):
    while True:
        try:
            # 会話のメッセージを作成
            messages = [
                {
                    "role": "user",
                    "content": f"あなたは有能なアシスタントです。以下の文脈に基づいて質問に答えてください。\n\n文脈:\n{context}\n\n質問:\n{question}"
                }
            ]
            
            # bodyの作成
            body = {
                "messages": messages,
                "max_tokens": 8192,
                "temperature": 0.1,
                "anthropic_version": "bedrock-2023-05-31"
            }

            response = bedrock.invoke_model(
                modelId='anthropic.claude-3-5-sonnet-20240620-v1:0',  # 利用可能なモデルID
                contentType='application/json',
                accept='application/json',
                body=json.dumps(body)
            )
            
            # レスポンスの解析
            response_body = json.loads(response['body'].read())
            return response_body['content'][0]['text']
        except ClientError as e:
            if e.response['Error']['Code'] == 'ThrottlingException':
                print(f"ThrottlingException: {e}, 1分後に再試行します...")
                time.sleep(60)  # 1分待機
                continue  # 再試行
            else:
                print(f"ClientErrorが発生しました: {e}")
                return None
        except Exception as e:
            print(f"エラーが発生しました: {e}")
            return None

# 質問をdfの質問列から取得し、全ての行で処理を実行
for idx, row in tqdm(df.iterrows(), total=len(df)):
    question = row['質問']
    # Kendraから文脈を取得
    context = None
    while context is None:
        context = search_kendra(question)
        if context is None:
            print(f"質問 {idx} の文脈取得に失敗しました。1分後に再試行します...")
            time.sleep(60)  # 1分待機

    # 回答の生成
    answer = None
    while answer is None:
        answer = generate_answer(question, context)
        if answer is None:
            print(f"質問 {idx} の回答生成に失敗しました。1分後に再試行します...")
            time.sleep(60)  # 1分待機

    # 出力文に格納
    df.at[idx, '出力文'] = answer

このコードでは、boto3のリトライConfigを設定し、ThrottlingExceptionが発生した際に自動的に再試行するようにしています。ThrottlingExceptionがは発生したら以下のエラーがターミナルに書かれます。

ThrottlingException: An error occurred (ThrottlingException) when calling the InvokeModel operation: Too many requests, please wait before trying again. You have sent too many requests.  Wait before trying again., 1分後に再試行します...

これにより、「Too Many Requests」エラーを回避することができました。

Discussion