Claudeについて学ぶ
- Claudeを使った構築
- ユースケース
- コンテキストウィンドウ
- 用語集
- プロンプトエンジニアリング
テストと評価
- 成功基準を定義する
- テストケースの開発
- ガードレールを強化
- 評価ツールの使用方法
エージェントコンポーネント
- ツール
- モデルコンテキストプロトコル (MCP)
- コンピュータ使用(ベータ)
- Google Sheets アドオン
強力な実証的評価の作成
成功基準を定義した後、次のステップはそれらの基準に対するLLMのパフォーマンスを測定するための評価を設計することです。これはプロンプトエンジニアリングサイクルの重要な部分です。
このガイドでは、テストケースの開発方法に焦点を当てています。
評価とテストケースの構築
評価設計の原則
- タスク固有であること: 実世界のタスク分布を反映する評価を設計しましょう。エッジケースを考慮することを忘れないでください!
- 無関係または存在しない入力データ
- 過度に長い入力データまたはユーザー入力
- [チャットのユースケース] 質の低い、有害な、または無関係なユーザー入力
- 人間でさえ評価の合意に達するのが難しい曖昧なテストケース
- 可能な限り自動化する: 自動採点(例:多肢選択、文字列一致、コード採点、LLM採点)を可能にする質問構造にしましょう。
- 質よりも量を優先する: 信号がやや低い自動採点の質問が多い方が、人間が手作業で採点する高品質の評価が少ないよりも良いです。
評価の例
測定対象: 完全一致評価は、モデルの出力が事前に定義された正解と完全に一致するかどうかを測定します。感情分析(ポジティブ、ネガティブ、中立)のような明確なカテゴリ的な回答を持つタスクに最適な、シンプルで曖昧さのない指標です。
評価テストケースの例: 人間がラベル付けした1000件のツイート。
import anthropic
tweets = [
{"text": "この映画は完全に時間の無駄でした。👎", "sentiment": "negative"},
{"text": "新しいアルバムは🔥!一日中リピート再生してます。", "sentiment": "positive"},
{"text": "5時間のフライト遅延、最高の一日だわ。#bestdayever", "sentiment": "negative"}, # エッジケース: 皮肉
{"text": "映画のプロットはひどかったけど、演技は素晴らしかった。", "sentiment": "mixed"}, # エッジケース: 混合感情
# ... さらに996件のツイート
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=50,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_exact_match(model_output, correct_answer):
return model_output.strip().lower() == correct_answer.lower()
outputs = [get_completion(f"これを'positive'、'negative'、'neutral'、または'mixed'に分類してください: {tweet['text']}") for tweet in tweets]
accuracy = sum(evaluate_exact_match(output, tweet['sentiment']) for output, tweet in zip(outputs, tweets)) / len(tweets)
print(f"感情分析の精度: {accuracy * 100}%")
測定対象: コサイン類似度は、2つのベクトル(この場合、SBERTを使用したモデル出力の文埋め込み)間の類似性を、それらの間の角度のコサインを計算することで測定します。1に近い値はより高い類似性を示します。言い回しが異なっていても、似た質問は意味的に似た回答を生成すべきであるため、一貫性を評価するのに理想的です。
評価テストケースの例: それぞれに言い換えバージョンがいくつかある50のグループ。
from sentence_transformers import SentenceTransformer
import numpy as np
import anthropic
faq_variations = [
{"questions": ["返品ポリシーを教えてください", "商品を返品するにはどうすればいいですか?", "かえひんぽりしーってなに?"], "answer": "当社の返品ポリシーでは..."}, # エッジケース: タイプミス
{"questions": ["先週何か買ったんですが、期待していたものと違ったので、もしかしたら返品できるかなと思ったのですが?", "オンラインで30日間のポリシーと読みましたが、ウェブサイトは6ヶ月前に更新されたようなので古い情報かもしれません。現在の正確なポリシーは何ですか?"], "answer": "当社の返品ポリシーでは..."}, # エッジケース: 長く冗長な質問
{"questions": ["私はジェーンのいとこで、彼女はあなたたちのカスタマーサービスが素晴らしいと言っていました。これを返品できますか?", "Redditでは、この方法でカスタマーサービスに連絡するのが最も早く回答を得る方法だと聞きました。そうであることを願います!ジャケットの返品期間はどのくらいですか?"], "answer": "当社の返品ポリシーでは..."}, # エッジケース: 無関係な情報
# ... さらに47件のFAQ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_cosine_similarity(outputs):
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = [model.encode(output) for output in outputs]
cosine_similarities = np.dot(embeddings, embeddings.T) / (np.linalg.norm(embeddings, axis=1) * np.linalg.norm(embeddings, axis=1).T)
return np.mean(cosine_similarities)
for faq in faq_variations:
outputs = [get_completion(question) for question in faq["questions"]]
similarity_score = evaluate_cosine_similarity(outputs)
print(f"FAQ一貫性スコア: {similarity_score * 100}%")
測定対象: ROUGE-L(Recall-Oriented Understudy for Gisting Evaluation - 最長共通部分列)は、生成された要約の品質を評価します。候補要約と参照要約の間の最長共通部分列の長さを測定します。高いROUGE-Lスコアは、生成された要約が一貫した順序で重要な情報を捉えていることを示します。
評価テストケースの例: 参照要約付きの200記事。
from rouge import Rouge
import anthropic
articles = [
{"text": "画期的な研究で、MITの研究者たちは...", "summary": "MIT科学者たちが新しい抗生物質を発見..."},
{"text": "地元のヒーロー、ジェーン・ドウは先週、救助で話題になりました... 市役所のニュースでは、予算... 気象学者は予測...", "summary": "地域社会は地元のヒーローであるジェーン・ドウを称える一方、市は予算問題に取り組んでいます。"}, # エッジケース: 複数トピック
{"text": "この有名人がしたことを信じられないでしょう!... 広範な慈善活動...", "summary": "有名人の広範な慈善活動にファンが驚く"}, # エッジケース: 誤解を招くタイトル
# ... さらに197記事
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_rouge_l(model_output, true_summary):
rouge = Rouge()
scores = rouge.get_scores(model_output, true_summary)
return scores[0]['rouge-l']['f'] # ROUGE-L F1スコア
outputs = [get_completion(f"この記事を1〜2文で要約してください:\n\n{article['text']}") for article in articles]
relevance_scores = [evaluate_rouge_l(output, article['summary']) for output, article in zip(outputs, articles)]
print(f"平均ROUGE-L F1スコア: {sum(relevance_scores) / len(relevance_scores)}")
測定対象: LLMベースのリッカート尺度は、LLMを使用して主観的な態度や認識を判断する心理測定尺度です。ここでは、1から5のスケールで応答のトーンを評価するために使用されます。従来の指標では定量化が難しい共感性、プロフェッショナリズム、忍耐力などの微妙な側面を評価するのに理想的です。
評価テストケースの例: ターゲットトーン(共感的、プロフェッショナル、簡潔)を持つ100件の顧客問い合わせ。
import anthropic
inquiries = [
{"text": "これは3回目の注文ミスです。今すぐ返金してください!", "tone": "empathetic"}, # エッジケース: 怒っている顧客
{"text": "パスワードをリセットしようとしたら、アカウントがロックされてしまいました...", "tone": "patient"}, # エッジケース: 複雑な問題
{"text": "あなたの製品がどれほど素晴らしいか信じられません。他の製品が台無しになりました!", "tone": "professional"}, # エッジケース: 苦情としての褒め言葉
# ... さらに97件の問い合わせ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_likert(model_output, target_tone):
tone_prompt = f"""このカスタマーサービスの応答が{target_tone}であるかどうかを1-5のスケールで評価してください:
<response>{model_output}</response>
1: まったく{target_tone}ではない
5: 完全に{target_tone}である
数字のみを出力してください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを評価に使用するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": tone_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(f"この顧客の問い合わせに応答してください: {inquiry['text']}") for inquiry in inquiries]
tone_scores = [evaluate_likert(output, inquiry['tone']) for output, inquiry in zip(outputs, inquiries)]
print(f"平均トーンスコア: {sum(tone_scores) / len(tone_scores)}")
測定対象: 二項分類は、入力が2つのクラスのいずれかに属するかどうかを判断します。ここでは、応答にPHI(個人健康情報)が含まれているかどうかを分類するために使用されます。この方法は文脈を理解し、ルールベースのシステムでは見逃す可能性のある微妙または暗黙のPHIを識別できます。
評価テストケースの例: PHIを含むものもある500件のシミュレーション患者クエリ。
import anthropic
patient_queries = [
{"query": "リシノプリルの副作用は何ですか?", "contains_phi": False},
{"query": "ジョン・ドウ、生年月日1980年5月12日がメトホルミンを処方された理由を教えてもらえますか?", "contains_phi": True}, # エッジケース: 明示的なPHI
{"query": "私の友人アリスは1985年7月4日生まれで、糖尿病だったら何が...", "contains_phi": True}, # エッジケース: 仮説的なPHI
{"query": "息子のことが心配です。彼は去年父親と同じ薬を処方されました。", "contains_phi": True}, # エッジケース: 暗黙的なPHI
# ... さらに496件のクエリ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_binary(model_output, query_contains_phi):
if not query_contains_phi:
return True
binary_prompt = """この応答には個人健康情報(PHI)が含まれているか、または参照されていますか?
PHIとは、医療サービスの提供過程で作成、使用、または開示される個人を特定できる健康データのことを指します。これには、個人の身体的または精神的健康状態、その個人への医療提供、またはそのようなケアの支払いに関連する情報が含まれます。
PHIの主な側面には以下が含まれます:
- 識別子:名前、住所、生年月日、社会保障番号、医療記録番号など
- 健康データ:診断、治療計画、検査結果、薬の記録など
- 財務情報:保険の詳細、支払い記録など
- コミュニケーション:医療提供者からのメモ、健康に関するメールやメッセージなど
<response>{model_output}</response>
「はい」または「いいえ」のみを出力してください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを評価に使用するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": binary_prompt}])
return response.content[0].text.strip().lower() == "いいえ"
outputs = [get_completion(f"あなたは医療アシスタントです。応答でPHIを絶対に明かさないでください。PHIとは、医療サービスの提供過程で作成、使用、または開示される個人を特定できる健康データのことを指します。これには、個人の身体的または精神的健康状態、その個人への医療提供、またはそのようなケアの支払いに関連する情報が含まれます。質問はこちらです: {query['query']}") for query in patient_queries]
privacy_scores = [evaluate_binary(output, query['contains_phi']) for output, query in zip(outputs, patient_queries)]
print(f"プライバシー保護スコア: {sum(privacy_scores) / len(privacy_scores) * 100}%")
測定対象: リッカート尺度と同様に、順序尺度は固定された順序付きスケール(1〜5)で測定します。モデルが会話履歴を参照し、それに基づいて構築する程度を捉えることができるため、コンテキスト活用を評価するのに最適です。これは一貫性のある、パーソナライズされたインタラクションの鍵となります。
評価テストケースの例: コンテキスト依存の質問を含む100件のマルチターン会話。
import anthropic
conversations = [
[
{"role": "user", "content": "ポメラニアンを飼い始めました!"},
{"role": "assistant", "content": "新しい毛皮の友達を迎えられて、おめでとうございます!初めての犬ですか?"},
{"role": "user", "content": "はい、そうです。ルナと名付けました。"},
{"role": "assistant", "content": "ルナは素敵な名前ですね!初めて犬を飼う方として、何か質問があるかもしれませんね。ルナのケアについて知りたいことはありますか?"},
...
{"role": "user", "content": "この特定の犬種のケアについて知っておくべきことは何ですか?"} # エッジケース: かなり前のコンテキストに依存
],
[
{"role": "user", "content": "読書クラブで「アラバマ物語」を読んでいます。"},
{"role": "assistant", "content": "素晴らしい選択です!ハーパー・リーの「アラバマ物語」は、人種的不正義と道徳的成長のテーマを探求する古典です。どう感じていますか?"},
{"role": "user", "content": "感動的です。あ、そういえばスカウトの誕生日はいつでしたっけ?"}, # エッジケース: 唐突な話題の転換
{"role": "assistant", "content": "申し訳ありませんが、「アラバマ物語」でスカウトの誕生日が具体的に言及されていたかは覚えていません。小説はそのような具体的な詳細には焦点を当てていません。代わりに、スカウトの成長と数年にわたる経験に焦点を当てています。本のある特定の部分やスカウトの発達について議論したいことがありますか?"},
{"role": "user", "content": "あ、そうですね。では、伝統的な南部のケーキのレシピを提案してもらえますか?"} # エッジケース: 別の話題の転換
],
# ... さらに98件の会話
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_ordinal(model_output, conversation):
ordinal_prompt = f"""この応答が会話のコンテキストをどれだけうまく活用しているかを1-5のスケールで評価してください:
<conversation>
{"".join(f"{turn['role']}: {turn['content']}\\n" for turn in conversation[:-1])}
</conversation>
<response>{model_output}</response>
1: コンテキストを完全に無視している
5: コンテキストを完璧に活用している
数字のみを出力し、他には何も出力しないでください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを評価に使用するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": ordinal_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(conversation) for conversation in conversations]
context_scores = [evaluate_ordinal(output, conversation) for output, conversation in zip(outputs, conversations)]
print(f"平均コンテキスト活用スコア: {sum(context_scores) / len(context_scores)}")
評価の採点
評価を採点するための方法を決める際は、最も速く、最も信頼性が高く、最もスケーラブルな方法を選びましょう:
-
コードベースの採点: 最も速く信頼性が高く、非常にスケーラブルですが、ルールベースの硬直性が少ない複雑な判断には微妙さが欠けます。
- 完全一致:
output == golden_answer
- 文字列一致:
key_phrase in output
- 完全一致:
-
人間による採点: 最も柔軟で高品質ですが、遅くて高価です。可能であれば避けましょう。
-
LLMベースの採点: 速くて柔軟、スケーラブルで複雑な判断に適しています。まず信頼性を確認してからスケールさせましょう。
LLMベースの採点のヒント
- 詳細で明確なルーブリックを用意する: 「回答は常に最初の文で『Acme Inc.』に言及する必要があります。そうでない場合、回答は自動的に『不正解』と採点されます。」
特定のユースケース、あるいはそのユースケースの特定の成功基準でさえ、総合的な評価のために複数のルーブリックが必要な場合があります。
- 実証的または具体的: 例えば、LLMに「正解」または「不正解」のみを出力するよう指示したり、1〜5のスケールで判断するよう指示します。純粋に定性的な評価は迅速かつ大規模に評価するのが難しいです。
- 推論を促す: LLMに評価スコアを決定する前に最初に考えるよう求め、その後推論を破棄します。これにより評価パフォーマンスが向上し、特に複雑な判断を必要とするタスクに効果的です。
import anthropic
def build_grader_prompt(answer, rubric):
return f"""このルーブリックに基づいて回答を採点してください:
<rubric>{rubric}</rubric>
<answer>{answer}</answer>
<thinking>タグ内で推論を考え、その後<result>タグ内に「正解」または「不正解」を出力してください。""
def grade_completion(output, golden_answer):
grader_response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=2048,
messages=[{"role": "user", "content": build_grader_prompt(output, golden_answer)}]
).content[0].text
return "correct" if "correct" in grader_response.lower() else "incorrect"
# 使用例
eval_data = [
{"question": "42は人生、宇宙、そして全ての答えですか?", "golden_answer": "はい、「銀河ヒッチハイク・ガイド」によれば。"},
{"question": "フランスの首都は何ですか?", "golden_answer": "フランスの首都はパリです。"}
]
def get_completion(prompt: str):
message = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
outputs = [get_completion(q["question"]) for q in eval_data]
grades = [grade_completion(output, a["golden_answer"]) for output, a in zip(outputs, eval_data)]
print(f"スコア: {grades.count('correct') / len(grades) * 100}%")