[アップデート] Amazon Bedrock でカスタムプロンプトルーターが利用できるようになりました
こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。
What's New にはでてきていないのですが、Amazon Bedrock でカスタムプロンプトルーターが利用できるようになりました。
Intelligent Prompt Routing
Intelligent Prompt Routing は、ユーザーからのプロンプトの内容に応じて適切なモデルへルーティングしてくれる機能です。
Intelligent Prompt Routing によって、簡単なタスクに関しては軽量なモデル、複雑なタスクは大きめなモデルといった、モデルの使い分けが利用できます。
詳しくは以下をご覧ください。
アップデート内容
今回、アップデートとして AWS API に CreatePromptRouter, DeletePromptRouter が登場し、ListPromptRouters に type の指定ができるようになりました。
やってみた
今回、以下のコードを利用して推論プロファイルおよび基礎モデルのすべての組み合わせでプロンプトルーターの作成を試行してみました。(作れたわけではないです)
import boto3
from itertools import combinations
import uuid
import time
from botocore.exceptions import ClientError
import re
def get_provider_from_arn(arn: str) -> str:
"""ARNからプロバイダー名を抽出する"""
if "anthropic" in arn.lower():
return "anthropic"
elif "amazon" in arn.lower():
return "amazon"
return "other"
def create_valid_router_name(profile1_id: str, profile2_id: str) -> str:
"""
有効なルーター名を生成する
- 英数字、スペース、ハイフン、アンダースコアのみ使用可能
- 64文字以下
"""
# プロファイルIDから不要な部分を削除
name1 = profile1_id.split(':')[0].split('.')[-2:] # 例: ['nova', 'pro']
name2 = profile2_id.split(':')[0].split('.')[-2:] # 例: ['nova', 'micro']
# 短い識別子を生成
router_name = f"router-{name1[-2]}-{name1[-1]}-{name2[-1]}"
# 特殊文字を削除し、スペースをハイフンに置換
router_name = re.sub(r'[^a-zA-Z0-9\s_-]', '', router_name)
router_name = router_name.replace(' ', '-')
# 64文字に制限
return router_name[:64]
def create_prompt_routers(region_name="us-east-1"):
bedrock = boto3.client('bedrock', region_name=region_name)
try:
# 推論プロファイルの一覧を取得
response = bedrock.list_inference_profiles()
profiles = response['inferenceProfileSummaries']
# プロバイダーごとにプロファイルを分類
provider_profiles = {}
for profile in profiles:
provider = get_provider_from_arn(profile['inferenceProfileArn'])
if provider not in provider_profiles:
provider_profiles[provider] = []
provider_profiles[provider].append(profile)
# プロバイダーごとに組み合わせを生成してルーターを作成
for provider, provider_profiles_list in provider_profiles.items():
print(f"\n=== {provider.upper()} モデルの組み合わせ ===")
# 2つのプロファイルの組み合わせを生成
for profile1, profile2 in combinations(provider_profiles_list, 2):
router_name = create_valid_router_name(
profile1['inferenceProfileId'],
profile2['inferenceProfileId']
)
print(f"\n試行: {router_name}")
try:
response = bedrock.create_prompt_router(
clientRequestToken=str(uuid.uuid4()),
promptRouterName=router_name,
models=[
{'modelArn': profile1['inferenceProfileArn']},
{'modelArn': profile2['inferenceProfileArn']}
],
description=f"Comparing {profile1['inferenceProfileName']} and {profile2['inferenceProfileName']}",
routingCriteria={
'responseQualityDifference': 0
},
fallbackModel={
'modelArn': profile1['inferenceProfileArn']
}
)
print(f"✅ ルーター作成成功: {router_name}")
print(f"ARN: {response.get('promptRouterArn', 'N/A')}")
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
print(f"❌ エラー ({error_code}): {error_message}")
# APIレート制限を考慮して少し待機
time.sleep(1)
except ClientError as e:
print(f"エラーが発生しました: {str(e)}")
return
if __name__ == "__main__":
create_prompt_routers()
import boto3
from itertools import combinations
import uuid
import time
from botocore.exceptions import ClientError
import re
from typing import List, Dict
def get_provider_from_arn(arn: str) -> str:
"""ARNからプロバイダー名を抽出する"""
if "anthropic" in arn.lower():
return "anthropic"
elif "amazon" in arn.lower():
return "amazon"
return "other"
def create_valid_router_name(model1_arn: str, model2_arn: str) -> str:
"""
有効なルーター名を生成する
- 英数字、スペース、ハイフン、アンダースコアのみ使用可能
- 64文字以下
"""
# モデルARNから識別子を抽出
def extract_model_name(arn: str) -> str:
parts = arn.split('/')[-1].split('-')
return f"{parts[0]}-{parts[1]}" # 例: anthropic-claude
name1 = extract_model_name(model1_arn)
name2 = extract_model_name(model2_arn)
# ルーター名を生成
router_name = f"fm-router-{name1}-{name2}"
# 特殊文字を削除し、スペースをハイフンに置換
router_name = re.sub(r'[^a-zA-Z0-9\s_-]', '', router_name)
router_name = router_name.replace(' ', '-')
# 64文字に制限
return router_name[:64]
def create_valid_description(model1_arn: str, model2_arn: str) -> str:
"""
有効な説明文を生成する
- 英数字、コロン、ドット、スペース、ハイフン、アンダースコアのみ使用可能
- 200文字以下
"""
# モデル名を抽出
model1_name = model1_arn.split('/')[-1]
model2_name = model2_arn.split('/')[-1]
description = f"Router for {model1_name} and {model2_name}"
# 特殊文字を削除(許可された文字のみ残す)
description = re.sub(r'[^a-zA-Z0-9:.\s_-]', '', description)
# 200文字に制限
return description[:200]
def get_foundation_models_by_provider(bedrock) -> Dict[str, List[str]]:
"""基礎モデルを取得してプロバイダーごとに分類する"""
provider_models = {}
try:
response = bedrock.list_foundation_models()
for model in response.get('modelSummaries', []):
model_arn = model.get('modelArn')
provider = get_provider_from_arn(model_arn)
if provider not in provider_models:
provider_models[provider] = []
provider_models[provider].append(model_arn)
except ClientError as e:
print(f"基礎モデルの取得中にエラーが発生しました: {str(e)}")
return provider_models
def create_foundation_model_routers(region_name="us-east-1"):
bedrock = boto3.client('bedrock', region_name=region_name)
try:
# プロバイダーごとの基礎モデルを取得
provider_models = get_foundation_models_by_provider(bedrock)
# プロバイダーごとに組み合わせを生成してルーターを作成
for provider, model_arns in provider_models.items():
print(f"\n=== {provider.upper()} 基礎モデルの組み合わせ ===")
# 2つのモデルの組み合わせを生成
for model1_arn, model2_arn in combinations(model_arns, 2):
router_name = create_valid_router_name(model1_arn, model2_arn)
description = create_valid_description(model1_arn, model2_arn)
print(f"\n試行: {router_name}")
print(f"説明: {description}")
print(f"モデル1: {model1_arn}")
print(f"モデル2: {model2_arn}")
try:
response = bedrock.create_prompt_router(
clientRequestToken=str(uuid.uuid4()),
promptRouterName=router_name,
models=[
{'modelArn': model1_arn},
{'modelArn': model2_arn}
],
description=description,
routingCriteria={
'responseQualityDifference': 0
},
fallbackModel={
'modelArn': model1_arn
}
)
print(f"✅ ルーター作成成功: {router_name}")
print(f"ARN: {response.get('promptRouterArn', 'N/A')}")
except ClientError as e:
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
print(f"❌ エラー ({error_code}): {error_message}")
# APIレート制限を考慮して少し待機
time.sleep(1)
except ClientError as e:
print(f"エラーが発生しました: {str(e)}")
return
if __name__ == "__main__":
create_foundation_model_routers()
残念ながら、多くの場合、次のように Prompt routing is not supported for the specified model identifier.
となり、Custom Prompt Router の作成に失敗してしまいました、
(custom-prompt-router-py3.12) takakuni@Mac custom_prompt_router % python main.py
=== ANTHROPIC モデルの組み合わせ ===
試行: router-anthropic-claude-3-sonnet-20240229-v1-claude-3-opus-20240
❌ エラー (ValidationException): Prompt routing is not supported for the specified model identifier.
作成できたプロンプトルーターは、以下の組み合わせです。
結果的にはデフォルトで提供されている Prompt Router と同じ組み合わせとなりました。
- 基礎モデル
- Claude 3 Haiku と Claude 3.5 Sonnet の組み合わせ
- Llama 3.1 8B Instruct と Llama 3.1 70B Instruct の組み合わせ
- 推論プロファイル
- Claude 3 Haiku と Claude 3.5 Sonnet の組み合わせ
- Llama 3.1 8B Instruct と Llama 3.1 70B Instruct の組み合わせ
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock list-prompt-routers --type custom
{
"promptRouterSummaries": [
{
"promptRouterName": "fm-router-metallama3-1-metallama3-1",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Router for meta.llama3-1-8b-instruct-v1:0 and meta.llama3-1-70b-instruct-v1:0",
"createdAt": "2025-03-07T06:33:15.726000+00:00",
"updatedAt": "2025-03-07T06:33:15.726000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/pr703fyatpl4",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/meta.llama3-1-8b-instruct-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/meta.llama3-1-70b-instruct-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/meta.llama3-1-8b-instruct-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
},
{
"promptRouterName": "fm-router-anthropicclaude-3-anthropicclaude-3",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Router for anthropic.claude-3-haiku-20240307-v1:0 and anthropic.claude-3-5-sonnet-20240620-v1:0",
"createdAt": "2025-03-07T06:38:41.088000+00:00",
"updatedAt": "2025-03-07T06:38:41.088000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/s5p7fw1pzqfx",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
},
{
"promptRouterName": "router-anthropic-claude-3-haiku-20240307-v1-claude-3-5-sonnet-20",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Comparing US Anthropic Claude 3 Haiku and US Anthropic Claude 3.5 Sonnet",
"createdAt": "2025-03-07T06:03:18.074000+00:00",
"updatedAt": "2025-03-07T06:03:18.074000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/u7fiva3cbs6y",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
},
{
"promptRouterName": "router-meta-llama3-1-8b-instruct-v1-llama3-1-70b-instruct-v1",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Comparing US Meta Llama 3.1 8B Instruct and US Meta Llama 3.1 70B Instruct",
"createdAt": "2025-03-07T06:03:57.190000+00:00",
"updatedAt": "2025-03-07T06:03:57.190000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/wb6ekd5317z6",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-8b-instruct-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-70b-instruct-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-8b-instruct-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
}
]
}
アップデートのどこが嬉しいのか
上記の結果からどこが嬉しいポイントなのでしょうか。私が考えつく限り、以下のポイントが考えられました。
- コスト配分タグ
- Quality Difference を調整可能
- Fallback Model を調整可能
コスト配分タグ
2024年11月にカスタムな推論プロファイルを作成できるようになりました。
カスタム推論プロファイルではタグを指定できるため、プロンプトルーターによる推論も同様に、コスト配分タグを付与できるようになったといって良いかと思います。
Quality Difference を調整可能
Quality Difference はプロンプトルーター内でどのモデルを利用するか判断するために利用するしきい値です。
プロンプトルーター内で各モデルの回答品質のスコアを求め、差分が Quality Difference 未満であれば、下位モデルを利用する、より大きい場合は上位モデルを利用するといった具合に利用されます。
デフォルトプロンプトルーターの Quality Difference は 0.0
の固定値で決まっているため、カスタムプロンプトルーターで調整可能な値になります。
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock list-prompt-routers
{
"promptRouterSummaries": [
{
"promptRouterName": "Anthropic Prompt Router",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Routes requests among models in the Claude family",
"createdAt": "2024-11-20T00:00:00+00:00",
"updatedAt": "2024-11-20T00:00:00+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:default-prompt-router/anthropic.claude:1",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
},
"status": "AVAILABLE",
"type": "default"
},
{
"promptRouterName": "Meta Prompt Router",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Routes requests among models in the LLaMA family",
"createdAt": "2024-11-20T00:00:00+00:00",
"updatedAt": "2024-11-20T00:00:00+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:default-prompt-router/meta.llama:1",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-8b-instruct-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-70b-instruct-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-70b-instruct-v1:0"
},
"status": "AVAILABLE",
"type": "default"
}
]
}
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock create-prompt-router \
> --prompt-router-name claude-response-quality-difference-5 \
> --models '[{"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0" }, {"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" }]' \
> --routing-criteria '{ "responseQualityDifference": 5.0 }' \
> --fallback-model '{ "modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" }'
{
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/8zmapw7c19cu"
}
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock get-prompt-router --prompt-router-arn "arn:aws:bedrock:us-east-1:123456789012:prompt-router/8zmapw7c19cu"
{
"promptRouterName": "claude-response-quality-difference-5",
"routingCriteria": {
"responseQualityDifference": 5.0
},
"createdAt": "2025-03-07T07:27:50.370000+00:00",
"updatedAt": "2025-03-07T07:27:50.370000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/8zmapw7c19cu",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
}
なお、responseQualityDifference
は 5 の倍数でない場合、怒られました。
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock create-prompt-router \
> --prompt-router-name claude-response-quality-difference-1 \
> --models '[{"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0" }, {"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" }]' \
> --routing-criteria '{ "responseQualityDifference": 1.0 }' \
> --fallback-model '{ "modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" }'
An error occurred (ValidationException) when calling the CreatePromptRouter operation: Response quality difference must be a value multiple of 5
Fallback Model を調整可能
Fallback Model は選択したモデルがどれも望ましいパフォーマンス基準を満たさない場合に使用されるモデルです。
デフォルトプロンプトルーターでは Claude 3.5 Sonnet, Llama3.1 70B が固定値で決まっています。
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock list-prompt-routers
{
"promptRouterSummaries": [
{
"promptRouterName": "Anthropic Prompt Router",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Routes requests among models in the Claude family",
"createdAt": "2024-11-20T00:00:00+00:00",
"updatedAt": "2024-11-20T00:00:00+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:default-prompt-router/anthropic.claude:1",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
},
"status": "AVAILABLE",
"type": "default"
},
{
"promptRouterName": "Meta Prompt Router",
"routingCriteria": {
"responseQualityDifference": 0.0
},
"description": "Routes requests among models in the LLaMA family",
"createdAt": "2024-11-20T00:00:00+00:00",
"updatedAt": "2024-11-20T00:00:00+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:default-prompt-router/meta.llama:1",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-8b-instruct-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-70b-instruct-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.meta.llama3-1-70b-instruct-v1:0"
},
"status": "AVAILABLE",
"type": "default"
}
]
}
こちらも、カスタムモデルを利用することでデフォルトプロンプトルーターで選ばれていた Claude 3.5 Sonnet から Claude Haiku 3 に変更できるといった具合です。
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock create-prompt-router \
> --prompt-router-name claude-fallback-model \
> --models '[{"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0" }, {"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" }]' \
> --routing-criteria '{ "responseQualityDifference": 5.0 }' \
> --fallback-model '{ "modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0" }'
{
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/5y4jivfmen53"
}
[cloudshell-user@ip-10-136-61-122 ~]$ aws bedrock get-prompt-router --prompt-router-arn "arn:aws:bedrock:us-east-1:123456789012:prompt-router/5y4jivfmen53"
{
"promptRouterName": "claude-fallback-model",
"routingCriteria": {
"responseQualityDifference": 5.0
},
"createdAt": "2025-03-07T07:52:10.370000+00:00",
"updatedAt": "2025-03-07T07:52:10.370000+00:00",
"promptRouterArn": "arn:aws:bedrock:us-east-1:123456789012:prompt-router/5y4jivfmen53",
"models": [
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
{
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"
}
],
"fallbackModel": {
"modelArn": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-3-haiku-20240307-v1:0"
},
"status": "AVAILABLE",
"type": "custom"
}
まとめ
以上、「Amazon Bedrock でカスタムプロンプトルーターが利用できるようになりました。」でした。
What's New にでてきていない部分なので答え合わせが楽しみですね。
このブログがどなたかの参考になれば幸いです。クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!