AIコーディング 2026.06.07

ChatGPT APIで長文を送信する方法|トークン上限超過エラーの対処と分割戦略【2026年版】

タグ:ChatGPT API / トークン上限 / エラー対処 / テキスト分割 / API実装

ChatGPT APIのトークン上限とは

ChatGPT APIを使うときに最もよくぶつかる問題が「トークン上限を超えた」というエラーです。トークンとは、APIが理解できる最小の文字単位のことで、日本語の場合、おおよそ3〜4文字で1トークンとして数えられます。

モデルごとにトークン上限が決まっており、GPT-4では8,000トークン、GPT-4 Turboでは128,000トークン、GPT-4o では200,000トークンが上限です。この上限は入力と出力を合わせた合計なので、非常に長いテキストを送信すると簡単に超えてしまいます。

実務では、ユーザーからの質問に長い文書を添付してもらったり、データベースから大量のテキストを取得して処理したりする場面が頻繁にあります。こうしたケースでAPIが「上限超過」エラーを返すと、処理が止まってしまい、ユーザー体験が著しく低下します。

なぜトークン上限超過が起きるのか

トークン上限超過の原因は大きく3つあります。

1. 入力テキストが大きすぎる

送信するテキスト(質問や文書)が上限を超えているケースです。例えば、数十ページのPDF文書を丸ごと送ったり、数千行のログファイルを一度に処理しようとしたりすると起きます。

2. 会話履歴が溜まっている

APIを繰り返し呼び出す会話形式で使う場合、過去のやり取りがすべてコンテキストに含まれます。長い会話をしていると、古いメッセージも新しいメッセージもすべてトークン数にカウントされるため、知らないうちに上限に近づいていることがあります。

3. 生成する回答が予想以上に長い

リクエストに対して、モデルが非常に長い回答を生成しようとする場合も上限に達します。特に、「詳しく説明してください」といった開放的な質問をすると、モデルが長い答えを返そうとして上限を超えることがあります。

トークン数を事前に計算する方法

トークン上限に達する前に、送信前のテキストのトークン数を計算することが重要です。OpenAIは公式に tiktoken というPythonライブラリを提供しており、これを使って正確にトークン数を数えられます。

import tiktoken

# 使いたいモデルのエンコーディングを取得
encoding = tiktoken.encoding_for_model("gpt-4")

# トークン数を計算
text = "ここにあなたのテキストを入れてください。"
tokens = encoding.encode(text)
token_count = len(tokens)

print(f"トークン数: {token_count}")

このコードを実行すれば、実際のトークン数がわかります。事前に計算しておくことで、上限に達する前に対応できます。

もし tiktoken をまだインストールしていなければ、以下のコマンドで導入してください。

pip install tiktoken

長文テキストを送信する3つの戦略

トークン上限を超えないようにするには、テキストを分割して複数のリクエストに分ける方法が一般的です。

戦略1: テキストを固定サイズで分割

最もシンプルな方法は、テキストを一定のトークン数ごとに分割することです。例えば、3,000トークンごとに区切り、複数回に分けてAPIに送信します。

import tiktoken

def split_text_by_tokens(text, max_tokens=3000):
    """テキストを指定トークン数で分割"""
    encoding = tiktoken.encoding_for_model("gpt-4")
    tokens = encoding.encode(text)
    
    chunks = []
    current_chunk = []
    current_count = 0
    
    for token in tokens:
        current_chunk.append(token)
        current_count += 1
        
        if current_count >= max_tokens:
            # チャンクをテキストにデコード
            chunk_text = encoding.decode(current_chunk)
            chunks.append(chunk_text)
            current_chunk = []
            current_count = 0
    
    # 残りのトークンをチャンクに追加
    if current_chunk:
        chunk_text = encoding.decode(current_chunk)
        chunks.append(chunk_text)
    
    return chunks

# 使用例
long_text = "非常に長いテキスト..."
chunks = split_text_by_tokens(long_text, max_tokens=3000)

for i, chunk in enumerate(chunks):
    print(f"\n--- チャンク {i+1} ---")
    print(chunk)

この方法なら、どのサイズのモデルでも対応できます。ただし、テキストが意味のある単位(段落や文)で分割されないため、チャンクの途中で文が切れることがあります。

戦略2: 段落や改行で区切る

テキストの意味を損なわないように、段落や改行単位で分割する方法です。

import tiktoken

def split_text_by_paragraphs(text, max_tokens=3000):
    """段落ごとにテキストを分割"""
    encoding = tiktoken.encoding_for_model("gpt-4")
    
    # 段落で分割(2つ以上の改行を区切り文字とする)
    paragraphs = text.split("\n\n")
    
    chunks = []
    current_chunk = ""
    
    for paragraph in paragraphs:
        test_text = current_chunk + paragraph + "\n\n"
        token_count = len(encoding.encode(test_text))
        
        if token_count <= max_tokens:
            current_chunk = test_text
        else:
            # 現在のチャンクを保存
            if current_chunk:
                chunks.append(current_chunk.strip())
            # 新しいチャンクを開始
            current_chunk = paragraph + "\n\n"
    
    # 最後のチャンクを追加
    if current_chunk:
        chunks.append(current_chunk.strip())
    
    return chunks

# 使用例
long_text = """
第1章 序論
これは序論です。...

第2章 方法
方法についてです。...

第3章 結果
結果は以下の通りです。...
"""

chunks = split_text_by_paragraphs(long_text, max_tokens=3000)
print(f"分割されたチャンク数: {len(chunks)}")

この方法は、元のテキストの構造を保ちながら分割できるため、より自然な処理が可能です。

戦略3: API呼び出しごとに段階的に処理

複数のチャンクを別々に処理するのではなく、段階的にAPIを呼び出し、前の結果を次のリクエストに含める方法です。例えば、長い文書を処理するときに「まず第1部分を要約して」「その要約と第2部分を合わせて分析して」というように進めます。

from openai import OpenAI

def process_long_document_step_by_step(chunks, task="要約"):
    """チャンクを段階的に処理"""
    client = OpenAI()
    
    accumulated_result = ""
    
    for i, chunk in enumerate(chunks):
        # プロンプトを組み立て
        if i == 0:
            # 最初のチャンク
            prompt = f"以下のテキストを{task}してください:\n\n{chunk}"
        else:
            # 2回目以降は前の結果を含める
            prompt = f"これまでの{task}:\n{accumulated_result}\n\n新しいテキスト:\n{chunk}\n\nこれまでの{task}と新しいテキストを合わせて、更新された{task}を提供してください。"
        
        # APIを呼び出し
        response = client.chat.completions.create(
            model="gpt-4",
            messages=[
                {"role": "user", "content": prompt}
            ]
        )
        
        accumulated_result = response.choices[0].message.content
        print(f"チャンク {i+1} 処理完了")
    
    return accumulated_result

# 使用例
long_text = "非常に長いテキスト..."
chunks = split_text_by_paragraphs(long_text, max_tokens=2000)
final_summary = process_long_document_step_by_step(chunks, task="要約")
print("\n最終要約:")
print(final_summary)

この方法は、各ステップでAPIに送信するテキスト量が少なくなるため、上限超過のリスクが低いです。ただし、APIを複数回呼び出すため、料金とレスポンス時間が増加します。

回答が途中で切れた場合の対処法

APIが返す回答が上限に近づくと、文が途中で切れることがあります。その場合、finish_reason というフィールドで理由を確認できます。

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": "長い質問..."}
    ]
)

print(f"終了理由: {response.choices[0].finish_reason}")
print(f"回答: {response.choices[0].message.content}")

finish_reason"length" だった場合、回答がトークン上限で切られたことを意味します。この場合、続きを取得するには、これまでのやり取りに「続きを書いてください」という新しいメッセージを追加します。

from openai import OpenAI

client = OpenAI()

# 最初のリクエスト
messages = [
    {"role": "user", "content": "次の物語を続けてください: 昔々、ある森に..."}
]

response = client.chat.completions.create(
    model="gpt-4",
    messages=messages
)

first_response = response.choices[0].message.content
finish_reason = response.choices[0].finish_reason

# アシスタントの回答をメッセージに追加
messages.append({"role": "assistant", "content": first_response})

# 回答が切れていたら、続きをリクエスト
if finish_reason == "length":
    messages.append({"role": "user", "content": "続きを書いてください。"})
    
    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages
    )
    
    second_response = response.choices[0].message.content
    
    # 2つの回答を合わせる
    full_response = first_response + second_response
    print(full_response)
else:
    print(first_response)

ポイントは、新しいリクエストでも過去のメッセージをすべて含めることです。そうすることで、モデルが文脈を理解した上で、自然な続きを書くことができます。

ただし、このやり方でも新しいリクエストでトークン数が増えるため、非常に長い会話の場合は、古いメッセージを削除するなどの工夫が必要になる場合があります。

つまずきやすいポイントと解決策

チャンク分割後に重複や欠落が起きる

テキストを分割するときに、チャンク境界で文字が重複したり、逆に落ちたりすることがあります。特に、トークンレベルで分割した場合、日本語が文字化けすることもあります。

解決策: 段落や文で分割する戦略2を使い、「テキストを確認してから送信する」というチェックステップを入れましょう。

# チャンク分割後に合計トークン数を確認
total_tokens = sum(len(encoding.encode(chunk)) for chunk in chunks)
original_tokens = len(encoding.encode(original_text))
print(f"元のテキスト: {original_tokens} トークン")
print(f"分割後の合計: {total_tokens} トークン")

複数のAPIコール実行時にレート制限に達する

短時間に大量のAPIコールをすると、OpenAIのレート制限(Rate limit exceeded)に引っかかります。

解決策: APIコール間に遅延を入れる、またはバッチ処理APIを使う。

import time
from openai import OpenAI

client = OpenAI()

for chunk in chunks:
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": chunk}]
    )
    
    print(response.choices[0].message.content)
    
    # 次のリクエストまで1秒待機
    time.sleep(1)

トークン数の計算が実際と異なる

tiktoken でカウントしたトークン数と、APIが実際にカウントしたトークン数がズレることがあります。特に、システムプロンプトやメッセージ構造(rolecontent など)もトークン数に含まれるため、純粋なテキストだけで計算すると少なく見積もってしまいます。

解決策: テキストだけでなく、メッセージ全体のトークン数を計算する関数を使う。

import tiktoken

def count_message_tokens(messages, model="gpt-4"):
    """メッセージ全体のトークン数を計算"""
    encoding = tiktoken.encoding_for_model(model)
    
    token_count = 0
    
    # メッセージごとにトークンを計算
    for message in messages:
        # ロールとコンテンツを含める
        token_count += len(encoding.encode(message["role"]))
        token_count += len(encoding.encode(message["content"]))
        # メッセージのオーバーヘッド(約4トークン)
        token_count += 4
    
    # プロンプト全体のオーバーヘッド(約2トークン)
    token_count += 2
    
    return token_count

# 使用例
messages = [
    {"role": "system", "content": "あなたは日本語アシスタントです。"},
    {"role": "user", "content": "長いテキスト..."}
]

total = count_message_tokens(messages)
print(f"メッセージ全体のトークン数: {total}")

実装時の注意点

ChatGPT APIを本番環境で使う場合、以下の点に注意してください。

API キーの管理: APIキーは絶対にコード内に書かず、環境変数から読み込むようにします。

import os
from openai import OpenAI

api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)

エラーハンドリング: トークン上限超過だけでなく、ネットワークエラーや認証エラーに対応する必要があります。

from openai import OpenAI, APIError, RateLimitError

client = OpenAI()

try:
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "..."}]
    )
except RateLimitError:
    print("レート制限に達しました。少し待ってからやり直してください。")
except APIError as e:
    print(f"APIエラーが発生しました: {e}")

コスト管理: 長文処理は多くのトークンを使うため、API料金が増加します。定期的に使用状況を確認しましょう。OpenAIの管理画面では、日次のAPI使用量を確認できます。

応用: 大規模言語処理の最適化

上記の方法を組み合わせることで、より効率的な処理ができます。例えば、PDF文書を処理する場合は、以下の流れが考えられます。

  1. PDFを段落単位で抽出
  2. 各段落のトークン数を計算
  3. トークン数が3,000以下であれば一度に要約
  4. それ以上なら複数回に分ける
  5. 各段落の要約を集約して、最終的な全体要約を生成

このようにすることで、トークン上限超過を避けながら、高品質な処理が実現できます。

実務では、ユーザーが送信するテキストのサイズを事前に予測できないため、「上限超過時の自動リトライ」ロジックを実装しておくと、エラーが起きにくくなります。


あわせて読みたい

参考ソース