Claudeで応対品質を採点する — スコアカードとBatchで通話記録を評価
通話やチャットの文字起こしをルーブリックでClaudeに採点させ、Message Batches APIで一括処理する手順です。観点の設計、採点コード、人手との校正、費用の見積もりを扱います。
サポート窓口の応対品質は、通話やチャットの記録を人が聞き返して採点するのが定番でした。件数が増えるほど抜き取り率は下がり、採点者ごとのばらつきも残ります。ここにClaudeを使うと、スコアカード(ルーブリック)を渡して全件を同じ基準で採点させる運用が組めます。
要点は3つです。観点を「機械で判定できるもの」と「Claudeに判断させるもの」に分けること。採点は応答を急がないのでMessage Batches APIで回すこと。本番に流す前に、人の採点と突き合わせて信頼できるかを測ることです。順に組んでいきます。
採点の全体像 — 設計・採点・校正の3段
LLMを使った評価は、成功基準を決め、評価を作り、採点方法を選ぶ流れで組みます。応対品質の採点に当てはめると次の形になります。
応対品質採点の組み立て順
- 1
スコアカードを作る
観点ごとに「何ができていれば何点か」を文章で定義します。
- 2
1件ずつ採点して挙動を見る
少数の文字起こしでプロンプトを調整し、根拠つきで採点されるかを見ます。
- 3
Batchで全件を流す
custom_idに通話IDを入れ、非同期で一括処理します。 - 4
人の採点と突き合わせる
抜き取りで一致度を測り、ずれる観点だけルーブリックを直します。
返信文を作る側の使い方はClaudeで問い合わせ返信を作るに、顧客対応のチャットボットを実装する側はtool useで作るカスタマーサポートボットにあります。ここで扱うのは、すでに終わった応対を事後に評価する側です。
スコアカードの設計 — 曖昧な「良い応対」を測れる形にする
評価ガイドは、よい成功基準の条件を「具体的」「測定可能」「達成可能」「目的に関連している」の4つで示しています。「安全な出力」は悪い例で、「1万回の試行のうち有害判定が0.1%未満」のように数字で書いたものが良い例です。応対品質も同じで、「丁寧な対応」ではなく「冒頭で名乗り、相手の用件を復唱している」と書きます。
機械で判定できる観点は、Claudeに任せない
採点方法について評価ガイドは、速く確実で大量に回せる順に選ぶよう説明しています。コードによる採点(完全一致・文字列一致)は最も速く確実ですが、ニュアンスの判断はできません。人による採点は最も柔軟で質が高い一方、遅くて高価なので「可能なら避ける」とされています。LLMによる採点は、速く柔軟で複雑な判断に向きますが、先に信頼性を試してから拡大する、という位置づけです。
この序列をスコアカードに当てはめた例が次の表です。観点の中身は一例で、自社の応対マニュアルに合わせて置き換えます。
| 観点 | 判定方法 | 採点基準の例 |
|---|---|---|
| 名乗りと挨拶 | 判定方法コード | 採点基準の例冒頭3発話に会社名と担当者名を含む |
| 本人確認の実施 | 判定方法コード | 採点基準の例確認フレーズのいずれかが記録に出現 |
| 用件の把握 | 判定方法Claude | 採点基準の例顧客の用件を自分の言葉で復唱したか |
| 共感と言葉づかい | 判定方法Claude | 採点基準の例怒りや不安への受け止めがあり、敬語が崩れていないか |
| 解決の正確さ | 判定方法Claude | 採点基準の例案内した手順や数字が社内ナレッジと食い違わないか |
| 次の行動の明示 | 判定方法Claude | 採点基準の例誰がいつ何をするかを最後に伝えたか |
「本人確認の実施」のように、特定の語句の有無で決まる観点は文字列一致で足ります。LLMに任せると、同じ記録でも採点が揺れる余地を自分で作ってしまいます。
ルーブリックは数値で、根拠つきで
LLMで採点するときの注意は3点です。
- 詳細で明確なルーブリックを用意する。たとえば「最初の文に必ず会社名を入れる。入っていなければ自動的に不正解」のように書く
- 出力は「correct / incorrect」や1〜5の数値に絞る。純粋に定性的な評価は、素早く大量に見直しにくい
- 採点の前に推論させる。thinkingを有効にした採点モデルで、複雑な判断ほど精度が上がる
1つのユースケースでも、観点によっては複数のルーブリックが要ります。応対品質なら、観点ごとにルーブリックを分け、0・1・2点のように段階の定義を文章で固定するのが扱いやすい形です。
1件を採点するコード
まず1件で動かし、出力の形とばらつきを見ます。観点ごとに引用つきの根拠を書かせてから、最後に採点結果だけを <result> タグで返させる構成です。結果をタグで囲ませておくと、正規表現で取り出せます。
import json
import re
import anthropic
client = anthropic.Anthropic()
GRADER_MODEL = "claude-opus-5-5"
RUBRIC = """
各観点を0〜2点で採点する。
comprehension(用件の把握)
0: 用件に触れない / 1: 部分的に触れる / 2: 自分の言葉で復唱する
empathy(共感と言葉づかい)
0: 受け止めがなく敬語も崩れる / 1: どちらかが欠ける / 2: 両方満たす
accuracy(解決の正確さ)
0: 案内が誤っている / 1: 曖昧 / 2: 正確で具体的
next_step(次の行動の明示)
0: 示さない / 1: 担当か期限のどちらかのみ / 2: 誰がいつ何をするか示す
"""
def build_prompt(transcript: str) -> str:
return f"""次のルーブリックでサポート応対の記録を採点してください。
<rubric>{RUBRIC}</rubric>
<transcript>{transcript}</transcript>
観点ごとに、根拠となる発言を引用して <reasoning> に書いてください。
その後、<result> タグの中に次の形のJSONだけを出力してください。
{{"comprehension": 0, "empathy": 0, "accuracy": 0, "next_step": 0}}"""
def grade(transcript: str) -> dict:
msg = client.messages.create(
model=GRADER_MODEL,
max_tokens=2048,
messages=[{"role": "user", "content": build_prompt(transcript)}],
)
text = next(b.text for b in msg.content if b.type == "text")
body = re.search(r"<result>(.*?)</result>", text, re.S).group(1)
return json.loads(body)ここでの max_tokens や観点名は一例です。書き換えるときは、<reasoning> が長くなっても <result> まで出力が届くように、余裕を持たせます。<result> が見つからなかった場合に例外で落ちる箇所は、本番では再採点キューに回す分岐に置き換えます。
試すときの記録は、良い応対だけでなく端的に悪い応対や、怒っている顧客、用件が二転三転する通話も混ぜます。評価データは実際の分布に合わせ、エッジケースも含めます。曖昧で人間でも判断が割れるケースを意図的に入れておくと、後で校正するときに役立ちます。
Message Batches APIで全件を流す
採点は結果をすぐに返す必要がありません。Message Batches APIは、メッセージのリクエストを非同期でまとめて処理する仕組みで、料金は通常のAPIの50%です。Anthropicはユースケースの例に「大規模な評価」を挙げています。
提出から回収まで
提出するときは、各リクエストに custom_id を付けます。1〜64文字の英数字・ハイフン・アンダースコアが使えるので、通話IDや記録のファイル名をそのまま入れられます。
from anthropic.types.message_create_params import (
MessageCreateParamsNonStreaming,
)
from anthropic.types.messages.batch_create_params import Request
def submit(calls: dict[str, str]) -> str:
"""calls: {通話ID: 文字起こし}"""
batch = client.messages.batches.create(
requests=[
Request(
custom_id=call_id,
params=MessageCreateParamsNonStreaming(
model=GRADER_MODEL,
max_tokens=2048,
messages=[
{"role": "user", "content": build_prompt(text)}
],
),
)
for call_id, text in calls.items()
]
)
return batch.id回収は、processing_status が ended になるまで状態を確認してから、結果をストリームで読みます。結果ファイルは大きくなることがあるので、まとめてダウンロードせず、ストリームで1件ずつ処理します。
import time
def collect(batch_id: str) -> dict[str, dict]:
status = client.messages.batches.retrieve(batch_id).processing_status
while status != "ended":
time.sleep(60)
status = client.messages.batches.retrieve(batch_id).processing_status
scores, retry = {}, []
for item in client.messages.batches.results(batch_id):
if item.result.type == "succeeded":
msg = item.result.message
text = next(b.text for b in msg.content if b.type == "text")
body = re.search(r"<result>(.*?)</result>", text, re.S)
if body:
scores[item.custom_id] = json.loads(body.group(1))
continue
retry.append(item.custom_id)
print("再採点が必要:", retry)
return scores結果の取り違えを防ぐ4つの注意
- 結果の並び順は入力と一致しない: 結果は入力と同じ順では返りません。突き合わせは必ず
custom_idで行います - 結果は4種類ある:
succeeded・errored・canceled・expiredです。最後の3つは請求されません。erroredのうちinvalid_request_errorはリクエストを直してから再送し、サーバー側のエラーはそのまま再送できます - 提出時の検証は非同期: パラメーターの誤りは、バッチ全体の処理が終わった時点で初めて返ります。1件だけ通常のAPIで試してから流すと、全件が失敗に終わる事態を避けられます
- 上限と期限がある: 1バッチは10万リクエストか256MBのどちらか先に届いた方までです。24時間以内に処理が終わらないとバッチは期限切れになり、結果のダウンロードは作成から29日間に限られます。29日を過ぎてもバッチ自体は閲覧できるので、結果はすぐ自社のデータベースへ保存します
succeeded でも、中身が採点になっているとは限りません。拒否の応答が成功として返ってくる落とし穴はBatch APIの拒否は成功扱いになるで扱っています。上のコードで <result> が無い場合を再採点に回しているのは、そのためです。
費用の見積もり
料金は入力と出力のトークン数で決まります。次の前提で、採点1,000件分をバッチ料金で計算した例です。
- 1件あたりの入力: 3,000トークン(ルーブリックと文字起こしの合計)
- 1件あたりの出力: 500トークン(根拠と採点結果)
| モデル | バッチ入力 / 出力(百万トークンあたり) | 1,000件の概算 |
|---|---|---|
| Claude Opus 5.5 | バッチ入力 / 出力(百万トークンあたり)$2 / $10 | 1,000件の概算$6 + $5 = $11 |
| Claude Sonnet 5.5 | バッチ入力 / 出力(百万トークンあたり)$1 / $5 | 1,000件の概算$3 + $2.5 = $5.5 |
| Claude Haiku 5.5(100,000トークン以下のプロンプト) | バッチ入力 / 出力(百万トークンあたり)$0.05 / $0.25 | 1,000件の概算$0.15 + $0.125 = $0.275 |
入力は3,000トークン×1,000件で300万トークン、出力は500トークン×1,000件で50万トークンです。これに表の単価を掛けています。通常のAPIの価格はこの2倍です。
どのモデルを使うかは、校正の結果で決めます。軽いモデルで人の採点との一致度が足りるなら、費用は桁で下がります。足りない観点だけを上位モデルに回す分け方も取れます。
全リクエストの先頭にルーブリックが共通で付くため、プロンプトキャッシュも効きます。バッチは5分より長くかかることがあり、1時間のキャッシュ期間を選ぶのが向いています。ただしバッチ内のリクエストは並行して任意の順に処理されるので、キャッシュのヒットはベストエフォートです。ヒット率の上げ方はバッチ処理でプロンプトキャッシュのヒット率を上げる方法にまとめています。
人の採点と突き合わせる校正
LLM採点は、信頼性を試してから規模を広げるものです。校正の手順は次のように組めます。
- 全件の中から抜き取りで数十件を選ぶ(低評価・高評価・中間から偏りなく)
- 熟練のQA担当者が、同じルーブリックで人手採点する
- 観点ごとに、人とClaudeの採点が一致した割合を出す
- 一致しない記録を読み、ルーブリックの書き方のどこが曖昧だったかを探す
一致度はコードで求められます。ここでの件数や基準値は運用で決める数字で、公式に定められた値ではありません。
def agreement(human: dict, model: dict, aspect: str) -> float:
ids = human.keys() & model.keys()
same = sum(human[i][aspect] == model[i][aspect] for i in ids)
return same / len(ids)ずれ方にも傾向があります。たとえば「共感」は人の担当者間でも判断が割れやすい観点です。この観点で人同士の一致度も低いなら、問題はClaudeではなくルーブリックの定義にあります。
評価ガイドは、自動採点の問題数を増やす方が、少数の高品質な人手採点より良いと述べています。人の工数は、全件の採点ではなく校正と例外の確認に使うという配分が、この考え方に沿います。実際の運用では、低評価と判定された応対だけを人が見直す形が現実的です。
運用で押さえておきたい点
個人情報の扱い: 文字起こしには氏名・住所・カード情報の断片が入りがちです。送る前にマスキングするか、そもそも採点に不要な情報を落とします。Claudeの利用での個人情報の注意点は問い合わせ返信の記事にもあります。
採点結果の使い道: スコアはオペレーター個人への評価に直結しやすい数値です。ルーブリックの定義や校正の結果は、採点される側にも見える形で共有しておくと、数字への納得感が変わります。
ルーブリックの更新: 応対マニュアルが変わったら、ルーブリックも変えます。変えたときは同じ校正用データで再採点して、前後の一致度を比べます。評価の設計全般やAgentの評価はAIエージェントのEval設計が参考になります。
問い合わせの振り分けの精度を評価するなら、同じ「評価関数を作って回す」考え方がサポートチケットの自動振り分けでも使えます。
よくある質問
通話の音声から直接採点できますか
この手順は文字起こし(テキスト)を入力にします。通話音声は、先に音声認識で文字起こしにしてから渡します。
同じ記録を2回採点すると点数が変わりますか
同じ入力でも出力が揺れる可能性があります。気になる観点は、同じ記録を複数回採点して点数が一致するかを、校正のなかで測ります。