はじめに / 対象と前提
Claude API(Messages API)を使ったアプリで、送信前に入力トークン数を見積もりたい人向け。長文を突っ込んで context window を超過して落ちるのを事前に防ぎたい、あるいはリクエスト単位のコストを概算したい、というケースを想定している。
前提環境(動作確認済み):
- Python 3.13
- anthropic SDK 1.x
- モデルは
claude-opus-5(count_tokens は現行モデル全対応)
TL;DR
-
POST /v1/messages/count_tokensに本番と同じリクエストボディを投げるとinput_tokensが返る。無料・beta ヘッダー不要 -
systemやtoolsを省いてmessagesだけ数えると過少見積りになる(ここが一番のハマりどころ) - 返り値は推定値で実測と数トークンズレる。さらにレート制限が Messages 本体と別枠なので、毎リクエスト呼ぶ実装は count_tokens 側の 429 を先に踏む
手順 / 動かし方
SDK から呼ぶ
from anthropic import Anthropic
client = Anthropic() # ANTHROPIC_API_KEY を環境変数から読む
resp = client.messages.count_tokens(
model="claude-opus-5",
system="あなたは社内ドキュメントの要約アシスタントです。",
messages=[{"role": "user", "content": long_text}],
)
print(resp.input_tokens) # => 3121 のような整数
curl で叩く
curl https://api.anthropic.com/v1/messages/count_tokens \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-opus-5","messages":[{"role":"user","content":"こんにちは"}]}'
# => {"input_tokens":11}
ポイントは3つ。
-
model/messages/system/toolsなど、Messages API に投げるものをそのまま渡せる。max_tokensは不要(入力を数えるだけなので) - レスポンスは
{"input_tokens": N}の1フィールドだけ - トークンを消費しない=無料。見積りのために課金されることはない
実用形: 送信前の上限チェック
MAX_INPUT = 200_000 # 使うモデルの context window に合わせる
def build_request(messages: list) -> dict:
# 本番リクエストと見積りで「同じボディ」を使うのが肝
return dict(model="claude-opus-5", system=SYSTEM_PROMPT,
tools=TOOLS, messages=messages)
req = build_request(messages)
n = client.messages.count_tokens(**req).input_tokens
if n >= MAX_INPUT:
raise ValueError(f"入力が大きすぎる: {n} tokens")
resp = client.messages.create(**req, max_tokens=16000)
動作確認は、実際に送ったレスポンスの resp.usage.input_tokens と見積り値を突き合わせればよい。自分の環境では 3,000 トークン級の入力で誤差は数トークンだった。
ハマりどころ
1. messages だけ数えると過少見積りになる(system・tools が抜ける)
症状: 見積り 3,000 に対して実測 4,200、のように大きくズレる。
原因: count_tokens は「渡したものだけ」を数える。本番リクエストでは system プロンプトと tools の定義(+ tool use 有効時のシステム側オーバーヘッド)も input に含まれる。ツールを10個も定義していると、定義だけで数千トークン食っていることは普通にある。
回避策: 上のコードのようにリクエストボディを組み立てる関数を共通化して、count_tokens と messages.create に同じものを渡す。見積りロジックだけ別実装にすると必ずズレる。
2. 返り値は「推定値」— 実測と微妙にズレる。tiktoken 代用は論外
count_tokens の返り値は公式ドキュメント上も推定値の扱いで、実測の usage.input_tokens と数トークン程度ズレることがある。ギリギリの上限判定やコスト計算に使うなら 1〜数%のマージンを取っておく。
トークナイザはモデル固有なので、model に渡す ID は推論に使う ID と必ず揃えること。モデル世代でトークナイザが変わることがあり、同じ文章でもカウントが数%〜数十%変わる。
あと、ローカルで済ませたくて tiktoken(OpenAI のトークナイザ)で代用するのは NG。Claude に対しては典型的な英文で 15〜20% 過少、コードや日本語混じりだとさらにズレる。自分は最初これで見積もっていて、上限チェックをすり抜けた入力が本番で validation エラーになった。
なお prompt caching を使っていても count_tokens は全量のトークン数を返す。キャッシュヒットで安くなる分は見積りに反映されないので、コスト概算に使う場合はキャッシュ分を別途差し引く。
3. レート制限が Messages 本体と別枠 — 毎回呼ぶと count_tokens 側の 429 を先に踏む
count_tokens は無料だが、リクエスト数ベースのレート制限が Messages API とは別に設定されている。「毎リクエスト見積り→送信」を素直に実装すると、Messages 側の枠は余裕なのに count_tokens 側だけ 429(rate_limit_error)を返し始める。
import anthropic
try:
n = client.messages.count_tokens(**req).input_tokens
except anthropic.RateLimitError as e:
retry_after = int(e.response.headers.get("retry-after", "60"))
# retry_after 秒待って再試行するか、見積りをスキップして概算で進める
回避策: 毎回呼ばない。会話が続くケースなら「前回の usage.input_tokens + 今回追加分の概算」で足し込んでおき、閾値に近づいたときだけ count_tokens で厳密に測り直す、という2段構えにすると呼び出し回数を一桁減らせる。
背景・補足
Claude のトークナイザは公開されていないため、正確に数えるにはサーバーに聞くしかない、というのがこの専用エンドポイントの存在理由。逆に言うと「オフラインで完結する正確なカウント手段は存在しない」ので、ローカル推定(文字数 ÷ 定数など)を使うなら誤差前提の設計にしておく必要がある。
まとめ
- count_tokens は無料・beta ヘッダー不要。本番と同じボディを渡して
input_tokensを得る - system・tools 込みで数える。ボディ組み立て関数を共通化するのが確実
- 返り値は推定値。マージンを取る。tiktoken 代用は 15〜20% 過少で危険
- レート制限は別枠。毎回呼ばず、閾値付近だけ厳密に測る2段構えにする
環境: Python 3.13 / anthropic SDK 1.x(2026年9月時点)。