Claude APIのOpenTelemetryスパン属性リファレンス — 二重計上の見分け方
Claude APIとSDKが出すOpenTelemetryスパンの名前・種別・属性の読み方と、トークン属性を足して二重計上にならない合計の取り方を示します。
Claude APIのスパンでトークン数を集計するとき、いちばん多い失敗は同じトークンを2回数えることです。1回のMessages APIリクエストには、アプリ側のSDKスパンとAPI側のスパンが同名で並び、さらにAPI側では親子の両方に使用量の属性が付きます。全部を足すと、実際より大きい数字になります。
この記事は、スパンの名前と種別、共通の属性、スパンごとの属性を読み解いたうえで、どのスパンのどの属性を足せば1回ずつ数えられるかを表にします。OpenTelemetry出力はベータ版で、スパン名と属性は変わる可能性があります。ここに載せた名前は、仕様ページが参照する規約のコミット(2026年9月24日付)に沿った内容です。
SDKスパンとAPIスパンは何が違うのか
スパンの出どころは2つあります。アプリケーションに組み込んだSDKが出すスパン(SDKスパン)と、Claude APIが出すスパン(APIスパン)です。見分けるときは、まずservice.nameを見ます。
| 項目 | SDKスパン | APIスパン |
|---|---|---|
service.name | SDKスパンアプリケーション自身のservice.name | APIスパンclaude-platform |
| スコープ名 | SDKスパンcom.anthropic.sdk.<language>(CLIはcom.anthropic.sdk.cli) | APIスパンcom.anthropic.claude-platform |
| リクエストのスパン種別 | SDKスパンCLIENT | APIスパンSERVER |
| それ以外のスパン種別 | SDKスパンINTERNAL | APIスパンINTERNAL |
クラウドプラットフォームのクライアントでは、スコープ名の末尾に.<platform>が付きます。
スパン名の付き方にも規則があります。
- リクエストのスパン:
anthropic.の後ろにAPIメソッドのパスを、スラッシュをドットに変え、betaを除いて続けます。client.beta.sessions.events.send()ならanthropic.sessions.events.sendです - ツールのスパン: 固定の名前、半角スペース、ツール名の順です。たとえば
anthropic.tool_use bashになります
ステータスは3値です。OKは完了、ERRORは失敗、未設定(unset)はアプリケーションが途中で打ち切った場合です。ストリームを途中で閉じると、SDKスパンは未設定になります。未設定を「失敗」として集計すると、ユーザーが中断しただけのリクエストがエラー率に混ざります。
どのスパンにも付く共通属性
リクエストのスパンには、次の属性が付きます。ただし先頭3つ以外は、リクエストでないスパンには付かない場合があります。
| 属性 | 中身 |
|---|---|
gen_ai.provider.name | 中身anthropic(Bedrockクライアントはaws.bedrock、Vertex系はgcp.vertex_ai) |
anthropic.organization.id | 中身組織ID |
anthropic.workspace.id | 中身処理が動くワークスペースのID |
anthropic.request.id | 中身レスポンスヘッダーrequest-idの値 |
http.request.method / http.route | 中身HTTPメソッドと、パスパラメータを{session_id}のように置き換えた経路 |
http.response.status_code | 中身最終レスポンスのステータス。ストリームではヘッダーと一緒に送られた値 |
server.address / server.port | 中身送信先のホストとポート |
url.scheme / url.path / url.query / url.full | 中身URLの各部分 |
anthropic.request.idは、サポートへ問い合わせるときの手がかりになります。Claude Platform on AWSでは、APIスパンにserver.address・server.port・url.fullが付きません。
Managed Agentsのセッションでは、さらにセッション属性が付きます。anthropic.session.idがセッションID、gen_ai.conversation.idがセッション内のスレッドIDです。1つのセッションのスパンが複数のトレースに分かれることがあるため、会話の全体像を組み立てるときはトレースIDではなくこの2つのIDでまとめます。
messages.createスパンの属性を読む
Messages APIのリクエストを表すanthropic.messages.createが、いちばん属性の多いスパンです。SDKからもAPIからも出ます。APIはPOST /v1/messagesのうち認証を通ったリクエストにだけこのスパンを出し、OpenAI互換の/v1/chat/completionsには出しません。フォールバックモデルが応答したときも、APIが出すスパンは1つです。
読み方の要点は、次の3群です。
誰が何を答えたか
gen_ai.request.model: リクエストのmodelgen_ai.response.model: 実際に応答したモデル。エイリアスで呼んだときやフォールバックが働いたときは、リクエスト側と値が変わりますanthropic.message.stop_reason:end_turnなどの停止理由anthropic.response.service_tier: 実際に使われたサービスティア
どう頼んだか
gen_ai.request.max_tokens、temperature、top_p、top_kは、リクエストで指定したときだけ付きます。gen_ai.request.reasoning.levelはoutput_configのeffort、anthropic.thinking.typeとanthropic.thinking.budget_tokensはthinkingの指定です。gen_ai.request.streamはstream: trueのときだけtrueで付き、falseは付きません。
失敗と速さ
error.typeにはレート制限エラーなどのエラー種別が入ります。APIがエラー応答なしにリクエストを終えたときはapi_errorです。gen_ai.response.time_to_first_chunkはストリーミングのSDKスパンだけに付く秒数で、リクエスト送信から最初のcontent_block_deltaまでを指します。
gen_ai.usage.reasoning.output_tokensは思考トークンで、出力トークンの内数です。出力側に足すと二重になります。
サーバーツールの使用回数(web_search_requestsなど)は、このスパンの属性にはありません。回数を知りたいときは、ツールごとのスパンを数えます。
本文を含む属性は「全コンテンツ」モードでだけ付く
プロンプトとレスポンスを持つ属性には、仕様ページでContentの印が付いています。
| 属性 | 中身 |
|---|---|
gen_ai.system_instructions | 中身システムプロンプト |
gen_ai.input.messages | 中身リクエストのmessages |
gen_ai.output.messages | 中身レスポンスのcontent(1件のassistantメッセージ) |
gen_ai.tool.call.arguments | 中身ツール呼び出しのinput |
anthropic.tool_result.content | 中身モデルへ返すツール結果 |
どれもJSON文字列です。コンテンツモードの既定は「メタデータのみ」で、このときスパンには識別子・時間・ステータス・トークン数と、エージェントやツールの名前・説明のように利用者が選んだ文字列だけが入ります。上の属性が見えないときは、まずモードを疑います。
大きすぎる内容は短縮され、anthropic.content.truncatedがtrueになります。上限は1スパンあたり既定0.75MBで、リクエストのスパンではシステムプロンプトと古いメッセージから順に落ちます。画像やドキュメントは種類名だけが残ります。HIPAA・FERPA・K-12のデータ取り扱い制限がある組織では、APIスパンの出力先はモードにかかわらずメタデータだけになります。SDKスパンはこの制限の対象外です。
スパンごとの親子関係と使用量の有無
トークン数の合計は、どのスパンが使用量を持つかを知らないと取れません。使用量の属性を持つのは次の4つだけです。
使用量を持つスパン
anthropic.messages.create
Messages APIのリクエスト。SDKとAPIの両方が出します。
anthropic.model_request
APIが出す、モデル1回分の実行。Messages APIでは
messages.createの子です。Managed Agentsではターンの子です。anthropic.compaction
Messages APIのコンパクション1回分。
messages.createの子です。anthropic.session.outcome_evaluation
Managed Agentsの成果評価1回分。入力と出力のトークンだけを持ちます。
次のスパンは使用量を持ちません。足し上げの対象から外します。
anthropic.messages.tool_runner: ツール実行ループ全体のスパン。使用量は子のmessages.createを読みますanthropic.session.turn: Managed Agentsの1ターン。子のmodel_requestが使用量を持ちますanthropic.tool_use <tool>、anthropic.mcp_tool_use <tool>、anthropic.custom_tool_use <tool>: ツールのスパン。名前の前置きが、サーバーツールやエージェントのツールセット(tool_use)、MCPツール(mcp_tool_use)、アプリ側のツールランナーが動かすツール(custom_tool_use)の別を表します。custom_tool_useはSDKだけが出します
anthropic.messages.count_tokensは、SDKだけが出すスパンです。APIはPOST /v1/messages/count_tokensにスパンを出しません。このスパンのanthropic.message.count_tokens.input_tokensはトークン数の見積もりであって使用量ではないので、合計に入れません。
トークン属性の意味と、足してはいけない組み合わせ
使用量の属性は、次の6つです。
| 属性 | 意味 |
|---|---|
gen_ai.usage.input_tokens | 意味入力トークン。キャッシュの読み取りと書き込みを含む |
gen_ai.usage.output_tokens | 意味出力トークン |
gen_ai.usage.cache_write.input_tokens | 意味キャッシュへ書いたトークン |
gen_ai.usage.cache_read.input_tokens | 意味キャッシュから読んだトークン |
anthropic.usage.cache_creation.ephemeral_5m_input_tokens | 意味5分キャッシュへの書き込み |
anthropic.usage.cache_creation.ephemeral_1h_input_tokens | 意味1時間キャッシュへの書き込み |
gen_ai.usage.input_tokensにはキャッシュの読み書きがすでに入っています。キャッシュ系の属性を上乗せすると二重になります。この定義はClaude Codeのinput_tokensとは逆で、Claude Code側はキャッシュ分を含みません。Claude Codeのテレメトリを同じダッシュボードに載せるときの換算はOpenTelemetryの入力トークンをGenAI規約に合わせるClaude Codeの換算にあります。
cache_writeとcache_readは、outcome_evaluationのスパンには付きません。5分・1時間の書き込みは、Messages APIのリクエストのスパンだけに付き、ストリーミング応答のSDKスパンでは最初の出力トークンより前に書かれた分だけが載ります。
二重計上になる場面の早見表
1回のリクエストの使用量は、複数のスパンに重ねて載ります。足すスパンは、出どころごとに1種類に絞ります。
| 集計の対象 | 足すスパン | 避けること |
|---|---|---|
| SDKスパン | 足すスパンanthropic.messages.create | 避けることコンパクションがあるときは、そのスパンのanthropic.usage.compaction.input_tokensとoutput_tokensも足す |
| APIスパン(Messages API) | 足すスパンmessages.createかmodel_requestのどちらか一方 | 避けること両方を足さない。コンパクションがあればanthropic.compactionスパンも足す |
| APIスパン(Managed Agents) | 足すスパンmodel_requestとoutcome_evaluation | 避けることsession.turnは使用量を持たない |
| SDKとAPIの両方がある | 足すスパンAPIスパンの側を足す | 避けること同名のmessages.createを両方足さない |
SDKスパンのgen_ai.usage.*はコンパクションのトークンを含みません。だからanthropic.usage.compaction.*を別に足します。一方、APIスパンのmessages.createにあるanthropic.usage.compaction.*は、anthropic.compactionスパンと同じトークンです。こちらを足すと二重になります。同じ属性名でも、足すか足さないかが出どころで逆になる点が、いちばん間違えやすいところです。
同名のanthropic.messages.createが2つあるときは、種別で見分けます。CLIENTがSDKスパン、その子のSERVERがAPIスパンです。
次のようなコードにしておくと、取り違えを防げます。スパンを辞書に展開済みという前提の、考え方を示す例です。
def api_usage(spans):
"""APIスパン(SERVER種別)の messages.create だけを足す"""
total = {"input": 0, "output": 0}
seen = set()
for s in spans:
key = (s["trace_id"], s["span_id"])
if key in seen: # 再送で届いた重複スパンを除く
continue
seen.add(key)
if s["name"] == "anthropic.messages.create" and s["kind"] == "SERVER":
a = s["attributes"]
total["input"] += a.get("gen_ai.usage.input_tokens", 0)
total["output"] += a.get("gen_ai.usage.output_tokens", 0)
return total重複の除去も必要です。スパンは配信の再試行で複数回届くことがあり、観測基盤が重複を除かないなら、トレースIDとスパンIDの組で1回ずつにします。
この集計は課金の正本ではない
スパンの配信はベストエフォートで、欠けることがあります。リクエストのtraceparentヘッダーでサンプリングフラグを落とせば、呼び出し側がそのリクエストのスパンを出さないこともできます。APIキーごとの使用量を確かめたい場合、たとえばキーの漏えいを疑うときは、スパンのない呼び出しも数えるUsage APIを使います。
Managed Agentsのスパンを読む
セッションでは、リクエストのスパンのほかにターンのスパンが付きます。anthropic.session.turnはgen_ai.operation.nameがinvoke_agentで、ターンの親は起こした理由で変わります。
- 最初のターン:
anthropic.sessions.create(initial_eventsが空でないとき) - イベント起因のターン:
anthropic.sessions.events.send - 予算の引き上げで再開したターン:
anthropic.sessions.update - 別スレッドで始めたターン: 呼び出し元のターンのスパン
- 別スレッドのエージェントが返信して始まるターン: 親なし
ターンは、スレッドがrunning状態に入ったときに始まり、出たときに終わります。anthropic.session.turn.stop_reasonには、ターンが終わった理由が入ります。たとえばアプリ側のカスタムツールを待つとrequires_actionで終わり、ツールの結果を送ると新しいターンが始まります。このため、1つの依頼が2つのターンに分かれて見えることがあります。
ツールのスパンは、APIがツールを動かした場合にだけAPI側から出ます。アプリが動かすカスタムツール、セルフホストのサンドボックスで動くツール、マルチエージェントのツールとアドバイザーツール、autoポリシーで拒否されたり、アプリが確認を断った使用にはAPIのスパンが付きません。ツールのスパンが見つからないときは、障害を疑う前にこの一覧に当てはまらないかを見ます。
anthropic.session.outcome_evaluationは、成果評価の1反復ごとに付くスパンです。anthropic.session.outcome_evaluation.resultはsatisfied、needs_revision、max_iterations_reached、failed、interruptedのどれかで、interruptedのときだけステータスが未設定、ほかはOKになります。
属性を見て原因を絞る使い方
属性から読み取れることを、症状ごとに並べます。
| 症状 | 見る属性 |
|---|---|
| 想定と違うモデルが答えた | 見る属性gen_ai.request.modelとgen_ai.response.modelの差 |
| 応答が途中で切れる | 見る属性anthropic.message.stop_reasonとgen_ai.request.max_tokens |
| 遅い | 見る属性gen_ai.response.time_to_first_chunk(ストリーミングのSDKスパン)とanthropic.response.service_tier |
| 再試行が多い | 見る属性SDKスパンのhttp.request.resend_count(再送がなければ付かない) |
| 特定の呼び出しだけ失敗する | 見る属性error.typeとanthropic.request.id |
SDKスパンのリクエスト1つは、再試行を含む全体の時間を表します。ページ付きの一覧では、次ページのリクエストごとに別のスパンになります。
Message Batchesは、SDKのメソッドごとにanthropic.messages.batches.<method>が出ます。anthropic.message.batch.idと、createのときだけanthropic.message.batch.request_countが付きます。バッチの運用例はClaude Batch APIで論文を一次スクリーニングするにあります。
ここまで読んだ属性は、組織の監査ログとは別の仕組みです。管理操作の記録との違いはClaude Compliance APIとはで比べています。
よくある質問
Claude Codeのスパンと同じ属性名ですか
同じではありません。Claude Codeはclaude_code.llm_requestのような独自のスパンとメトリクスを出し、この記事のanthropic.*スパンとは別系統です。入力トークンの範囲も違います。ダッシュボードで混ぜる場合は、上の換算を先に済ませます。
Agent SDKでも同じスパンが出ますか
仕様ページはClaude Code、Agent SDK、Coworkを別の手順に案内しています。Agent SDKの可観測化はAgent SDKをOpenTelemetryで可観測化する実装ガイドにあります。