生成AIモデルの評価と監視は、品質の確保、パフォーマンスの維持、本番環境での問題特定に不可欠です。Amazon Bedrockは組み込みの評価機能を提供し、AWSモニタリングサービスと統合されています。
評価の概要
flowchart TB
subgraph Eval["モデル評価"]
A["自動評価"]
B["人間評価"]
end
subgraph Metrics["評価メトリクス"]
C["精度"]
D["堅牢性"]
E["有害性"]
end
subgraph Monitor["モニタリング"]
F["CloudWatchメトリクス"]
G["呼び出しログ"]
H["カスタムダッシュボード"]
end
A --> C
A --> D
A --> E
B --> C
F --> H
G --> H
style A fill:#3b82f6,color:#fff
style B fill:#8b5cf6,color:#fff
モデル評価ジョブ
評価ジョブの作成
import boto3
import json
bedrock = boto3.client('bedrock')
response = bedrock.create_evaluation_job(
jobName='claude-evaluation-001',
jobDescription='カスタマーサポート用にClaude Sonnetを評価',
roleArn='arn:aws:iam::123456789012:role/BedrockEvaluationRole',
evaluationConfig={
'automated': {
'datasetMetricConfigs': [
{
'taskType': 'QuestionAndAnswer',
'dataset': {
'name': 'qa-dataset',
'datasetLocation': {
's3Uri': 's3://my-bucket/evaluation/qa-dataset.jsonl'
}
},
'metricNames': ['Accuracy', 'Robustness']
}
]
}
},
inferenceConfig={
'models': [
{
'bedrockModel': {
'modelIdentifier': 'anthropic.claude-3-sonnet-20240229-v1:0',
'inferenceParams': json.dumps({
'temperature': 0.7,
'max_tokens': 1024
})
}
}
]
},
outputDataConfig={
's3Uri': 's3://my-bucket/evaluation/output/'
}
)
job_arn = response['jobArn']
データセット形式(JSONL)
{"prompt": "返品ポリシーは何ですか?", "referenceResponse": "当社の返品ポリシーでは、購入後30日以内の返品が可能です。"}
{"prompt": "注文を追跡するにはどうすればいいですか?", "referenceResponse": "確認メールに記載されている追跡リンクで注文を追跡できます。"}
{"prompt": "どのような支払い方法がありますか?", "referenceResponse": "クレジットカード、PayPal、銀行振込を受け付けています。"}
評価メトリクス
| メトリクス |
説明 |
タスクタイプ |
| Accuracy |
回答の正確性 |
QA、要約 |
| Robustness |
バリエーション間の一貫性 |
すべて |
| Toxicity |
有害コンテンツの検出 |
すべて |
| Relevance |
トピックへの適合性 |
QA、分類 |
| Fluency |
言語品質 |
すべて |
人間評価
人間評価ジョブの作成
response = bedrock.create_evaluation_job(
jobName='human-evaluation-001',
jobDescription='レスポンス品質の人間評価',
roleArn='arn:aws:iam::123456789012:role/BedrockEvaluationRole',
evaluationConfig={
'human': {
'humanWorkflowConfig': {
'flowDefinitionArn': 'arn:aws:sagemaker:us-east-1:123456789012:flow-definition/bedrock-eval',
'instructions': 'AIのレスポンス品質を1-5で評価してください'
},
'customMetrics': [
{
'name': 'Helpfulness',
'description': 'レスポンスはどれくらい役立つか?',
'ratingMethod': 'ThumbsUpDown'
},
{
'name': 'Accuracy',
'description': '情報は正確か?',
'ratingMethod': 'IndividualLikertScale'
}
],
'datasetMetricConfigs': [
{
'taskType': 'QuestionAndAnswer',
'dataset': {
'name': 'human-eval-dataset',
'datasetLocation': {
's3Uri': 's3://my-bucket/evaluation/human-dataset.jsonl'
}
}
}
]
}
},
inferenceConfig={
'models': [
{
'bedrockModel': {
'modelIdentifier': 'anthropic.claude-3-sonnet-20240229-v1:0'
}
}
]
},
outputDataConfig={
's3Uri': 's3://my-bucket/evaluation/human-output/'
}
)
モデル比較
複数モデルの比較
response = bedrock.create_evaluation_job(
jobName='model-comparison-001',
jobDescription='Claude SonnetとHaikuの比較',
roleArn='arn:aws:iam::123456789012:role/BedrockEvaluationRole',
evaluationConfig={
'automated': {
'datasetMetricConfigs': [
{
'taskType': 'QuestionAndAnswer',
'dataset': {
'name': 'comparison-dataset',
'datasetLocation': {
's3Uri': 's3://my-bucket/evaluation/comparison.jsonl'
}
},
'metricNames': ['Accuracy', 'Robustness', 'Toxicity']
}
]
}
},
inferenceConfig={
'models': [
{
'bedrockModel': {
'modelIdentifier': 'anthropic.claude-3-sonnet-20240229-v1:0',
'inferenceParams': json.dumps({'temperature': 0.5})
}
},
{
'bedrockModel': {
'modelIdentifier': 'anthropic.claude-3-haiku-20240307-v1:0',
'inferenceParams': json.dumps({'temperature': 0.5})
}
}
]
},
outputDataConfig={
's3Uri': 's3://my-bucket/evaluation/comparison-output/'
}
)
CloudWatchモニタリング
主要メトリクス
| メトリクス |
説明 |
| InvocationCount |
モデル呼び出し数 |
| InvocationLatency |
レスポンス時間 |
| InvocationClientErrors |
4xxエラー |
| InvocationServerErrors |
5xxエラー |
| InputTokenCount |
リクエスト内のトークン数 |
| OutputTokenCount |
レスポンス内のトークン数 |
CloudWatchダッシュボードの作成
cloudwatch = boto3.client('cloudwatch')
dashboard_body = {
'widgets': [
{
'type': 'metric',
'x': 0, 'y': 0,
'width': 12, 'height': 6,
'properties': {
'metrics': [
['AWS/Bedrock', 'InvocationCount', 'ModelId', 'anthropic.claude-3-sonnet-20240229-v1:0'],
['.', 'InvocationLatency', '.', '.', {'stat': 'Average'}]
],
'title': 'モデル呼び出し',
'region': 'us-east-1',
'period': 300
}
},
{
'type': 'metric',
'x': 12, 'y': 0,
'width': 12, 'height': 6,
'properties': {
'metrics': [
['AWS/Bedrock', 'InvocationClientErrors', 'ModelId', 'anthropic.claude-3-sonnet-20240229-v1:0'],
['.', 'InvocationServerErrors', '.', '.']
],
'title': 'エラー率',
'region': 'us-east-1',
'period': 300
}
},
{
'type': 'metric',
'x': 0, 'y': 6,
'width': 24, 'height': 6,
'properties': {
'metrics': [
['AWS/Bedrock', 'InputTokenCount', 'ModelId', 'anthropic.claude-3-sonnet-20240229-v1:0', {'stat': 'Sum'}],
['.', 'OutputTokenCount', '.', '.', {'stat': 'Sum'}]
],
'title': 'トークン使用量',
'region': 'us-east-1',
'period': 3600
}
}
]
}
cloudwatch.put_dashboard(
DashboardName='BedrockMonitoring',
DashboardBody=json.dumps(dashboard_body)
)
CloudWatchアラーム
cloudwatch.put_metric_alarm(
AlarmName='BedrockHighLatency',
AlarmDescription='Bedrockモデルのレイテンシーが高い',
MetricName='InvocationLatency',
Namespace='AWS/Bedrock',
Dimensions=[
{'Name': 'ModelId', 'Value': 'anthropic.claude-3-sonnet-20240229-v1:0'}
],
Statistic='Average',
Period=300,
EvaluationPeriods=2,
Threshold=5000,
ComparisonOperator='GreaterThanThreshold',
AlarmActions=['arn:aws:sns:us-east-1:123456789012:alerts']
)
cloudwatch.put_metric_alarm(
AlarmName='BedrockHighErrorRate',
AlarmDescription='Bedrockのエラー率が高い',
MetricName='InvocationServerErrors',
Namespace='AWS/Bedrock',
Dimensions=[
{'Name': 'ModelId', 'Value': 'anthropic.claude-3-sonnet-20240229-v1:0'}
],
Statistic='Sum',
Period=300,
EvaluationPeriods=1,
Threshold=10,
ComparisonOperator='GreaterThanThreshold',
AlarmActions=['arn:aws:sns:us-east-1:123456789012:alerts']
)
カスタム評価
カスタムメトリクスの実装
import boto3
from datetime import datetime
class BedrockEvaluator:
def __init__(self, model_id: str):
self.runtime = boto3.client('bedrock-runtime')
self.cloudwatch = boto3.client('cloudwatch')
self.model_id = model_id
def evaluate_response(self, prompt: str, response: str,
expected: str = None) -> dict:
metrics = {}
metrics['response_length'] = len(response)
if expected:
expected_words = set(expected.lower().split())
response_words = set(response.lower().split())
overlap = len(expected_words & response_words)
metrics['relevance_score'] = overlap / len(expected_words) if expected_words else 0
return metrics
def publish_custom_metrics(self, metrics: dict):
metric_data = []
timestamp = datetime.utcnow()
for name, value in metrics.items():
metric_data.append({
'MetricName': name,
'Dimensions': [
{'Name': 'ModelId', 'Value': self.model_id},
{'Name': 'Application', 'Value': 'CustomerSupport'}
],
'Timestamp': timestamp,
'Value': value,
'Unit': 'None'
})
self.cloudwatch.put_metric_data(
Namespace='Custom/Bedrock',
MetricData=metric_data
)
def run_evaluation_batch(self, test_cases: list) -> dict:
results = []
for case in test_cases:
response = self.runtime.converse(
modelId=self.model_id,
messages=[{'role': 'user', 'content': [{'text': case['prompt']}]}]
)
answer = response['output']['message']['content'][0]['text']
metrics = self.evaluate_response(
case['prompt'],
answer,
case.get('expected')
)
results.append({
'prompt': case['prompt'],
'response': answer,
'metrics': metrics
})
self.publish_custom_metrics(metrics)
avg_relevance = sum(r['metrics'].get('relevance_score', 0) for r in results) / len(results)
return {
'total_cases': len(results),
'average_relevance': avg_relevance,
'results': results
}
evaluator = BedrockEvaluator('anthropic.claude-3-sonnet-20240229-v1:0')
test_cases = [
{'prompt': '返品ポリシーは何ですか?', 'expected': '返品は30日以内に受け付けています'},
{'prompt': 'サポートへの連絡方法は?', 'expected': 'support@example.comにメールまたは1-800-XXXに電話'}
]
results = evaluator.run_evaluation_batch(test_cases)
呼び出しログ分析
CloudWatch Insightsでログをクエリ
logs = boto3.client('logs')
query = """
fields @timestamp, @message
| filter modelId = 'anthropic.claude-3-sonnet-20240229-v1:0'
| filter latencyMs > 3000
| sort @timestamp desc
| limit 100
"""
response = logs.start_query(
logGroupName='/aws/bedrock/model-invocations',
startTime=int((datetime.now() - timedelta(hours=24)).timestamp()),
endTime=int(datetime.now().timestamp()),
queryString=query
)
query_id = response['queryId']
import time
while True:
result = logs.get_query_results(queryId=query_id)
if result['status'] == 'Complete':
break
time.sleep(1)
for record in result['results']:
print(record)
ベストプラクティス
| プラクティス |
推奨事項 |
| 定期的な評価 |
週次で評価ジョブを実行 |
| A/Bテスト |
モデルバージョンを比較 |
| アラート閾値 |
ベースラインメトリクスに基づいて設定 |
| コストモニタリング |
トークン使用量を追跡 |
| ログ保持 |
分析用にログを保持 |
重要なポイント
- 組み込み評価 - 自動評価と人間評価ジョブを使用
- CloudWatch統合 - 呼び出し、レイテンシー、エラーを監視
- カスタムメトリクス - アプリケーション固有のメトリクスを公開
- プロアクティブなアラート - 異常に対するアラームを設定
- 継続的改善 - 評価結果に基づいてイテレーション
参考文献