ChatGPT APIでトークン数を送信前に正確にカウントする方法|tiktoken使用法と料金計算【2026年版】
この方法で何が解決するか
ChatGPT APIを使う際、テキスト入力に対してAPIが返す応答の長さは「トークン」という単位で計測されます。トークン数に応じて使用料金が発生するため、送信前に正確なトークン数を把握することはコスト管理と通信の効率化に不可欠です。
特に以下の場面で重要です:
- 長文テキストの送信前: 1回の APIリクエストがコンテキスト制限を超えていないか確認したい
- 料金の事前計算: 送信するテキストが「いくら分」のコストになるか知りたい
- リクエスト設計: 複数のテキストをバッチ処理する際に、1リクエストに収めるテキスト量を決めたい
- 不完全な応答への対応: API応答が途中で切れた場合、残りのテキストを取得するかどうか判断する前に「あと何トークン必要か」を知りたい
- RAGアプリケーションでのトークン超過エラー回避: 検索結果とチャット履歴を組み合わせる際に、APIリクエスト前に超過を検知したい
本記事では、OpenAIが提供する「tiktoken」ライブラリを使い、Pythonコード例で実装方法を紹介します。
前提環境・必要なもの
動作確認済み環境
- Python 3.8以上
- tiktoken ライブラリ(最新版)
- OpenAI Python SDK(バージョン1.0.0以上)
重要な注意:OpenAI APIの仕様変更
OpenAIは1.0.0以降のライブラリで、従来のChatCompletionインターフェースを廃止しました。以前のコード(openai.ChatCompletion.create())は動作しなくなっています。本記事ではOpenAI 1.0.0以上の新しいインターフェースを使用した実装を説明します。
インストール方法
pip install tiktoken openai
バージョン確認:
pip list | grep -E "openai|tiktoken"
OpenAIのバージョンが1.0.0以上であることを確認してください。
トークンの基本知識
トークンとは何か
OpenAIのAPIドキュメント(https://platform.openai.com/docs/guides/tokenization)によると、トークンは言語モデルが処理する基本単位です。**1トークンはおおよそ4文字分**に相当します(日本語は例外で後述)。
例えば「Hello, world!」は3トークンです:
- “Hello”(1トークン)
- ”,“(1トークン)
- ” world!”(1トークン)
日本語のトークン数は多くなる傾向
日本語テキストは英語と比べてトークン数が大幅に増える傾向にあります。理由は、日本語の文字体系(ひらがな・カタカナ・漢字)がアルファベットよりも複雑で、より多くのトークンで表現されるからです。
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
# 英語と日本語の比較
ja_text = "こんにちは、ChatGPT APIについて学んでいます。"
en_text = "Hello, I'm learning about ChatGPT API."
ja_tokens = len(encoding.encode(ja_text))
en_tokens = len(encoding.encode(en_text))
print(f"日本語: {len(ja_text)}字 → {ja_tokens}トークン")
print(f"英語: {len(en_text)}字 → {en_tokens}トークン")
出力例:
日本語: 24字 → 39トークン
英語: 40字 → 9トークン
対応策: 日本語テキストを送信する場合は、文字数ではなく、tiktokenで計測したトークン数を基準に考えてください。
実装方法:Pythonでトークン数をカウントする
ステップ1:tiktokenのインストールと初期化
import tiktoken
# モデル名を指定してエンコーディングを初期化
encoding = tiktoken.encoding_for_model("gpt-4o")
よく使うモデルのエンコーディング名:
gpt-4o(GPT-4 Omni)gpt-4-turbo(GPT-4 Turbo)gpt-3.5-turbo(GPT-3.5 Turbo)
ステップ2:テキストをトークンに変換してカウント
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
# テキストをトークン列に変換
text = "ChatGPT APIを使うときのトークン数は重要です。"
tokens = encoding.encode(text)
# トークン数をカウント
token_count = len(tokens)
print(f"トークン数: {token_count}")
# トークン列を確認
print(f"トークン: {tokens}")
実行結果(例):
トークン数: 20
トークン: [39644, 38996, ...] # 実際のトークン ID
ステップ3:メッセージ形式でのトークンカウント
ChatGPT APIの実際の呼び出しでは、メッセージをmessagesリストの形式で送信します。この形式でのトークン数を正確にカウントする必要があります。
import tiktoken
def count_messages_tokens(messages, model="gpt-3.5-turbo"):
"""
メッセージリストのトークン数をカウント
"""
encoding = tiktoken.encoding_for_model(model)
token_count = 0
for message in messages:
# メッセージごとにオーバーヘッドがある
token_count += 3 # メッセージのメタデータ用
for key, value in message.items():
token_count += len(encoding.encode(value))
# 返応開始のオーバーヘッド
token_count += 3
return token_count
# 実装例
messages = [
{"role": "system", "content": "あなたは優秀なプログラミング講師です。"},
{"role": "user", "content": "PythonでAPIを呼び出すベストプラクティスを教えてください。"}
]
token_count = count_messages_tokens(messages)
print(f"メッセージのトークン数: {token_count}")
重要なポイント:
- メッセージ形式では、各メッセージごとにメタデータ用のトークンが消費されます
roleとcontentの各キー・バリューもトークン化されます- 上記の実装では近似値を計算していますが、正確な値はOpenAIの内部仕様に依存します
ステップ4:複数のテキストをまとめてカウント
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
messages = [
{"role": "user", "content": "こんにちは"},
{"role": "assistant", "content": "こんにちは。何かお手伝いできることはありますか?"}
]
# メッセージ全体のトークン数を計算
total_tokens = 0
for message in messages:
tokens = encoding.encode(message["content"])
total_tokens += len(tokens)
print(f"{message['role']}: {len(tokens)}トークン")
print(f"合計: {total_tokens}トークン")
ステップ5:モデル別の料金を計算する
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
text = "ここに長いテキストを入れる..." * 100 # 実際のテキスト
token_count = len(encoding.encode(text))
# 2026年時点での料金表(参考値)
pricing = {
"gpt-3.5-turbo": {"input": 0.50 / 1000000, "output": 1.50 / 1000000},
"gpt-4": {"input": 3.0 / 1000000, "output": 6.0 / 1000000},
"gpt-4o": {"input": 3.0 / 1000000, "output": 12.0 / 1000000},
"gpt-4-turbo": {"input": 10.0 / 1000000, "output": 30.0 / 1000000}
}
model = "gpt-4o"
input_cost = token_count * pricing[model]["input"]
print(f"トークン数: {token_count}")
print(f"推定入力コスト: ${input_cost:.6f}")
注意: 上記の料金はあくまで例です。実際の料金はOpenAI Pricingで最新情報を確認してください。
ステップ6:コンテキスト制限をチェック
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
# モデルのコンテキスト制限(トークン)
context_limits = {
"gpt-4o": 128000,
"gpt-4-turbo": 128000,
"gpt-4": 8192,
"gpt-3.5-turbo": 4096
}
text = "テキストここから..." * 500
token_count = len(encoding.encode(text))
model = "gpt-4o"
limit = context_limits[model]
if token_count > limit:
print(f"警告: {token_count}トークンはコンテキスト制限{limit}を超えています")
excess = token_count - limit
print(f"超過トークン数: {excess}")
else:
remaining = limit - token_count
print(f"トークン数: {token_count} / {limit}")
print(f"残り容量: {remaining}トークン")
RAGアプリケーションでのトークン超過エラー回避
APIリクエスト前のトークン数確認
RAG(情報検索強化生成)やチャットボットでは、質問に対して関連する情報源や過去の会話履歴をモデルに渡す必要があります。これらのコンテクストを組み合わせるとトークン数が急速に増加するため、APIリクエスト前に超過を検知することが重要です。
import tiktoken
from typing import List
def count_tokens(messages: List[dict], model: str = "gpt-4") -> int:
"""メッセージリストのトークン数をカウント"""
encoding = tiktoken.encoding_for_model(model)
total_tokens = 0
for message in messages:
total_tokens += 3 # メッセージメタデータ
for value in message.values():
total_tokens += len(encoding.encode(str(value)))
total_tokens += 3 # 応答開始オーバーヘッド
return total_tokens
# 使用例:検索結果とチャット履歴を組み合わせる
context_messages = [
{"role": "system", "content": "あなたは優秀なアシスタントです。"},
]
# 検索結果をコンテクストに追加
search_results = [
"関連ドキュメント1:機械学習の基礎について説明します...",
"関連ドキュメント2:ディープラーニングの応用例...",
]
for result in search_results:
context_messages.append(
{"role": "system", "content": f"検索結果: {result}"}
)
# チャット履歴を追加
chat_history = [
{"role": "user", "content": "機械学習について教えてください"},
{"role": "assistant", "content": "機械学習は..."},
]
context_messages.extend(chat_history)
# ユーザーの現在の質問を追加
user_question = {"role": "user", "content": "もっと詳しく教えてください"}
context_messages.append(user_question)
# トークン数を確認
token_count = count_tokens(context_messages)
context_limit = 4096 # gpt-3.5-turboの場合
if token_count > context_limit:
print(f"警告: {token_count}トークン(上限: {context_limit})")
print("チャット履歴を削減または検索結果を絞ってください")
else:
print(f"OK: {token_count}トークン(余裕: {context_limit - token_count})")
長文テキストの効率的な分割
コンテキスト制限に引っかかるテキストを複数に分割する例:
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
def split_text_by_tokens(text, max_tokens=100000):
"""テキストをトークン数で分割する関数"""
tokens = encoding.encode(text)
chunks = []
current_chunk = []
current_token_count = 0
for token in tokens:
current_chunk.append(token)
current_token_count += 1
if current_token_count >= max_tokens:
chunks.append(encoding.decode(current_chunk))
current_chunk = []
current_token_count = 0
if current_chunk:
chunks.append(encoding.decode(current_chunk))
return chunks
# 使用例
long_text = "ここに長いテキストを入れる..." * 1000
chunks = split_text_by_tokens(long_text, max_tokens=100000)
for i, chunk in enumerate(chunks):
print(f"チャンク {i+1}: {len(encoding.encode(chunk))}トークン")
チャット履歴の動的管理
古いメッセージを動的に削除する戦略:
import tiktoken
from typing import List, Dict
def manage_conversation_memory(
messages: List[Dict],
max_tokens: int = 3000,
model: str = "gpt-4"
) -> List[Dict]:
"""
メッセージリストをトークン上限内に調整する関数
最新のメッセージを優先的に保持し、古いメッセージから削除
"""
encoding = tiktoken.encoding_for_model(model)
def count_message_tokens(msg: Dict) -> int:
return len(encoding.encode(msg.get("content", "")))
total_tokens = sum(count_message_tokens(msg) for msg in messages)
# トークン数が上限以下なら変更なし
if total_tokens <= max_tokens:
return messages
# システムメッセージは必ず保持
system_messages = [msg for msg in messages if msg.get("role") == "system"]
other_messages = [msg for msg in messages if msg.get("role") != "system"]
# 最新メッセージから逆順で必要な分だけ保持
kept_messages = system_messages.copy()
remaining_tokens = max_tokens - sum(count_message_tokens(msg) for msg in system_messages)
for message in reversed(other_messages):
msg_tokens = count_message_tokens(message)
if remaining_tokens - msg_tokens >= 0:
kept_messages.insert(len(system_messages), message)
remaining_tokens -= msg_tokens
else:
break
return kept_messages
# 使用例
chat_history = [
{"role": "system", "content": "あなたは優秀なアシスタントです。"},
{"role": "user", "content": "機械学習とは何ですか?"},
{"role": "assistant", "content": "機械学習は、データからパターンを学習する技術です。"},
{"role": "user", "content": "ディープラーニングについても教えてください。"},
{"role": "assistant", "content": "ディープラーニングは、多層のニューラルネットワークを使用します。"},
]
managed_history = manage_conversation_memory(chat_history, max_tokens=2000)
print(f"保持されたメッセージ数: {len(managed_history)}")
つまずきやすいポイントと解決策
ポイント1:OpenAI APIのバージョンエラー
エラーメッセージ:
AttributeError: module 'openai' has no attribute 'ChatCompletion'
原因:OpenAI 1.0.0以降、openai.ChatCompletion.create()のインターフェースが廃止されました。
解決方法:新しいインターフェースを使用してください。
# ❌ 古い書き方(動作しません)
# response = openai.ChatCompletion.create(...)
# ✅ 新しい書き方
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
response = client.chat.completions.create(...)
ポイント2:モデル名が間違っている
エラーメッセージ:
ValueError: Unknown model name specified
原因:存在しないモデル名や、廃止されたモデル名を使用している可能性があります。gpt-4-0314やgpt-4-0613といった日付付きのモデル名は、OpenAIが段階的に廃止しています。
# ❌ 日付付きモデル名は廃止傾向
# model="gpt-4-0613"
# ✅ 固定名を使用
model = "gpt-4"
使用するモデルが本当に存在するかOpenAI Modelsで確認し、正確な名前を使用します。
ポイント3:日本語テキストのトークン数が思ったより多い
日本語は英数字よりトークン数が増える傾向にあります。上述の比較例のとおり、同程度の文字数でも英語の数倍のトークン数になることがあります。
対応策: 日本語テキストの場合は、実装時に常にtiktokenでカウントしましょう。
ポイント4:トークンカウントの不正確さ
現象:計算したトークン数と実際のAPIレスポンスのトークン数が異なる。
原因:メッセージ形式でのトークン計算には、OpenAIが公開していない内部的なオーバーヘッドが存在します。
解決方法:より正確な計算が必要な場合は、APIレスポンスのusageフィールドから実際のトークン数を取得してください。
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages
)
# 実際のトークン数はレスポンスに含まれる
actual_input_tokens = response.usage.prompt_tokens
actual_output_tokens = response.usage.completion_tokens
print(f"入力トークン(実際): {actual_input_tokens}")
print(f"出力トークン(実際): {actual_output_tokens}")
ポイント5:APIレスポンスのトークン数も含める必要がある
チャットAPIでは、送信テキストだけでなくAPIから返される応答もトークン数にカウントされて料金が発生します。
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
# ユーザー入力
user_input = "日本の首都を教えてください"
input_tokens = len(encoding.encode(user_input))
# APIレスポンス(例)
response = "日本の首都は東京です。"
output_tokens = len(encoding.encode(response))
total_cost_tokens = input_tokens + output_tokens
print(f"入力: {input_tokens}トークン")
print(f"出力: {output_tokens}トークン")
print(f"合計: {total_cost_tokens}トークン分の料金が発生します")
ポイント6:不完全な応答(途中で切れたレスポンス)への対応
APIの応答がコンテキスト制限に達して途中で切れた場合、続きを取得する方法があります:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 最初のリクエスト
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "長いテキストを処理する必要があります..."}
],
max_tokens=2000
)
# 応答を確認
if response.choices[0].finish_reason == "length":
print("応答が途中で切れました。続きを取得します。")
# 続きを取得するための追加リクエスト
continuation_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "前回の続きをお願いします"}
]
)
else:
print("応答が完全です。")
ポイント7:トークン超過エラーの自動処理
実際のAPIリクエスト時には、トークン超過エラーをキャッチして自動調整するロジックを実装します。
from openai import OpenAI, APIError
import tiktoken
client = OpenAI()
encoding = tiktoken.encoding_for_model("gpt-4")
def call_chat_api_with_retry(messages: list, model: str = "gpt-4"):
"""トークン超過エラーを自動処理"""
try:
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.7
)
return response.choices[0].message.content
except APIError as e:
error_message = str(e)
# トークン超過エラーの検出
if "maximum context length" in error_message:
print("⚠️ トークン超過エラー: メモリを削減して再試行")
# システムメッセージと最新メッセージのみ保持
system_messages = [msg for msg in messages if msg.get("role") == "system"]
other_messages = [msg for msg in messages if msg.get("role") != "system"]
reduced_messages = system_messages + other_messages[-3:]
response = client.chat.completions.create(
model=model,
messages=reduced_messages,
temperature=0.7
)
return response.choices[0].message.content
else:
raise
# 使用例
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "...長いテキスト..."}
]
try:
result = call_chat_api_with_retry(messages)
print(result)
except Exception as e:
print(f"エラーが解決できません: {e}")
応用:実用的な実装パターン
パターン1:API実行前の安全チェック
トークン数をカウントした上で、実際にAPIを呼び出す前にチェックを入れる実装です。
from openai import OpenAI
import tiktoken
def safe_api_call_with_token_check(messages, model="gpt-3.5-turbo", max_tokens=4096):
"""
トークン数をチェックしてからAPI呼び出し
"""
client = OpenAI(api_key="YOUR_API_KEY") # 環境変数から読む推奨
encoding = tiktoken.encoding_for_model(model)
# 入力トークン数をカウント
input_tokens = 0
for message in messages:
input_tokens += 3 # メッセージオーバーヘッド
for value in message.values():
input_tokens += len(encoding.encode(value))
input_tokens += 3 # 応答開始オーバーヘッド
# モデルのコンテキストウィンドウ確認
context_windows = {
"gpt-4": 8192,
"gpt-4-turbo": 128000,
"gpt-4o": 128000,
"gpt-3.5-turbo": 4096,
}
context_limit = context_windows.get(model, 4096)
# チェック
if input_tokens >= context_limit:
print(f"エラー: 入力トークン数({input_tokens})がコンテキスト制限({context_limit})を超えています")
return None
remaining_tokens = context_limit - input_tokens - max_tokens
if remaining_tokens < 0:
print(f"警告: 出力トークン数の余裕が不足しています")
return None
print(f"入力トークン数: {input_tokens}")
print(f"残り利用可能トークン: {remaining_tokens}")
# API呼び出し
try:
response = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens
)
return response
except Exception as e:
print(f"APIエラー: {e}")
return None
# 使用例
messages = [
{"role": "system", "content": "あなたは技術顧問です。"},
{"role": "user", "content": "APIレート制限について説明してください。"}
]
response = safe_api_call_with_token_check(messages, model="gpt-3.5-turbo")
if response:
print(f"回答: {response.choices[0].message.content}")
パターン2:バッチ処理での料金事前計算
複数のテキストをAPIに送信する前に、合計コストを計算:
import tiktoken
encoding = tiktoken.encoding_for_model("gpt-4o")
texts = [
"テキスト1...",
"テキスト2...",
"テキスト3..."
]
# 料金表(1トークンあたり、USD)
pricing = {
"input": 3.0 / 1000000,
"output": 12.0 / 1000000
}
total_input_tokens = 0
for text in texts:
input_tokens = len(encoding.encode(text))
total_input_tokens += input_tokens
print(f"テキスト: {input_tokens}トークン")
# 出力は平均を推定(例:入力の2倍程度)
estimated_output_tokens = total_input_tokens * 2
total_cost = (total_input_tokens * pricing["input"] +
estimated_output_tokens * pricing["output"])
print(f"\n入力合計: {total_input_tokens}トークン")
print(f"出力推定: {estimated_output_tokens}トークン")
print(f"推定コスト: ${total_cost:.4f}")
パターン3:リアルタイム課金表示とコスト推定
APIを実際に呼び出しながら、リアルタイムで課金額を表示:
from openai import OpenAI
import tiktoken
client = OpenAI(api_key="your-api-key")
encoding = tiktoken.encoding_for_model("gpt-4o")
# 料金表(2026年版)
pricing = {
"gpt-4o": {"input": 3.0 / 1000000, "output": 12.0 / 1000000},
"gpt-4o mini": {"input": 0.00000015, "output": 0.0000006}
}
def call_api_with_cost_tracking(messages, model="gpt-4o", max_tokens=500):
"""APIを呼び出して、入力・出力のトークン数と料金を表示する"""
#
---
## あわせて読みたい
- [Claude Codeのトークン削減【94%コスト削減の5ステップ】実装ガイド付き](/code/token-reduction-with-claude-code-94-cost-savings-guide/)
- [Claude Codeのトークン消費を98%削減する方法【MCP活用+コンテキスト最適化】](/code/claude-code-token-consumption-reduction/)
- [AIから正確な回答をもらう7つのプロンプト術【Claude・ChatGPT実例付き】](/code/ai-7-2/)