
LLM APIコールにおけるContext Length Exceeded:修正方法、トレードオフ、モデル選択

LLM APIコールから次のようなレスポンスが返ってきました:
{
"error": {
"message": "This model's maximum context length is 128000 tokens. However, your messages resulted in 142837 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded"
}
}これは、入力(システムプロンプト + 会話履歴 + ユーザーメッセージ)がモデルのコンテキストウィンドウを超えていることを意味します。トークンが1つも生成される前に、リクエストが拒否されました。
レートリミットエラー(リクエストの頻度に関するもの)とは異なり、コンテキスト長エラーはリクエストのサイズに関するものです。解決策は速度を落とすことではなく、入力を削減するか、リクエストを再構成するか、より大きなコンテキストウィンドウを持つモデルに切り替えることです。
まとめ
- Context length exceeded = 入力がモデルのトークンウィンドウに対して大きすぎる。
- 4つの選択肢がある:トランケート、要約、チャンク分割、モデル切り替え。
- 各選択肢にはコスト、品質、レイテンシのトレードオフがある。
- 以下の判断基準表を使って、ワークロードに適したアプローチを選択する。
- 本番システムでは、ユーザーにエラーが見える前に対処する。
クイックフィックスチェックリスト
戦略を選ぶ前に、まずこれらの一般的な原因を確認してください:
| 確認項目 | 何を探すか | 即座の対処法 |
|---|---|---|
| 会話履歴が長すぎる | マルチターンチャットでメッセージ配列が際限なく増える | 古いメッセージを削除するかスライディングウィンドウを実装 |
| システムプロンプトが大きすぎる | 詳細な指示がコンテキスト予算を圧迫している | システムプロンプトを圧縮するか、固定的な指示をリファレンスに移動 |
| 重複コンテンツ | 同じコンテキストが複数回挿入されている(RAG、ツール結果) | 送信前に重複を排除 |
| ツール/関数の出力が大きい | ツールコールが巨大なJSONやテキストを返した | ツール出力をコンテキストに追加する前にトランケートまたは要約 |
| 不要なメタデータ | 必要なフィールドが少数なのにオブジェクト全体を含めている | 関連フィールドのみを抽出 |
これらのクイックフィックスのいずれも該当しない場合は、構造的なアプローチが必要です。
判断基準表:トランケート vs 要約 vs チャンク分割 vs モデル切り替え
| 戦略 | 仕組み | 品質への影響 | コストへの影響 | レイテンシへの影響 | 最適な用途 |
|---|---|---|---|---|---|
| トランケート | 古いメッセージを削除するか入力をトリミング | 重要なコンテキストが失われる可能性 | 入力トークン削減 = 低コスト | 高速化(入力が少ない) | 長い履歴のあるチャットアプリ;直近のコンテキストが最も重要な場合 |
| 要約 | より安価なモデルで過去のコンテキストを要約に圧縮 | 非可逆 — 要約で詳細が漏れる可能性 | 要約用の追加APIコール、ただしメインコールは小さくなる | 1回の追加コール | 状態が蓄積されるエージェントワークフロー;知識集約型の会話 |
| チャンク分割 + マージ | 入力をチャンクに分割し、個別に処理し、結果をマージ | チャンク間のコンテキスト欠落リスク | 複数コール = 総コスト増 | 低速化(逐次または並列チャンク) | ドキュメント処理、長文テキストの分析 |
| モデル切り替え | より大きなコンテキストウィンドウのモデルを使用 | 通常は同等かそれ以上 | 大コンテキストモデルはトークン単価が高いことが多い | 場合による | 重要な情報を失わずに入力を削減できない場合 |
戦略1:トランケート — 重要度の低いものを削除する
トランケーションは最もシンプルなアプローチです。入力の中で最も古い、または最も関連性の低い部分を削除します。
チャット用スライディングウィンドウ
def sliding_window(messages: list, max_tokens: int, system_prompt: str) -> list:
"""Keep system prompt + most recent messages within token budget."""
# Always keep system prompt
result = [{"role": "system", "content": system_prompt}]
token_count = count_tokens(system_prompt)
# Add messages from newest to oldest
for msg in reversed(messages):
msg_tokens = count_tokens(msg["content"])
if token_count + msg_tokens > max_tokens:
break
result.insert(1, msg) # Insert after system prompt
token_count += msg_tokens
return resultトランケーションが効果的な場面
- 直近のコンテキストが最も重要なマルチターンチャット
- 必要に応じて再取得が可能なRAGパイプライン
- 順序通りに処理できるバッチ処理
トランケーションが危険な場面
- 初期の指示が後の動作に影響するエージェントワークフロー
- 完全性が重要な法的・コンプライアンス関連のコンテキスト
- ステップの省略が回答を変えてしまう多段階推論
戦略2:要約 — 意味を保ちながら圧縮する
要約は、より安価で高速なモデルを使って過去のコンテキストを圧縮します:
async def summarize_context(messages: list, client) -> str:
"""Compress older messages into a summary."""
context_text = "\n".join(
f"{m['role']}: {m['content']}" for m in messages
)
response = await client.chat.completions.create(
model="gpt-4o-mini", # Cheaper model for summarization
messages=[{
"role": "user",
"content": f"Summarize this conversation context in under 500 tokens. "
f"Preserve key decisions, facts, and pending actions:\n\n{context_text}"
}],
max_tokens=500
)
return response.choices[0].message.contentコスト比較:要約 vs トランケート
| アプローチ | メインモデルへの入力トークン | 追加APIコール | 総コスト |
|---|---|---|---|
| 管理なし(エラー発生) | N/A — リクエスト拒否 | 0 | 無駄なレイテンシ + リトライコスト |
| 80Kトークンにトランケート | 80K | 0 | 80K入力での基本コスト |
| 古いコンテキストを要約 | ~10K(要約)+ 40K(直近)= 50K | 要約コール1回(約$0.01) | メインコールのコスト低減 + 少額の要約コスト |
要約は安価なコール1回を追加しますが、メインコールの入力トークンを大幅に削減することが多く、高額なモデルでは正味のコスト削減になる可能性があります。
戦略3:チャンク分割とマージ — 長文ドキュメント向け
入力が会話ではなく単一の長文ドキュメントの場合、チャンク分割が適切なアプローチであることが多いです:
async def process_long_document(
document: str,
question: str,
client,
chunk_size: int = 50000
) -> str:
"""Process a long document by chunking."""
chunks = split_into_chunks(document, chunk_size)
chunk_results = []
for i, chunk in enumerate(chunks):
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": f"Analyze chunk {i+1}/{len(chunks)} of a document.\n"
f"Question: {question}\n\n"
f"Chunk content:\n{chunk}"
}]
)
chunk_results.append(response.choices[0].message.content)
# Merge chunk results
merge_response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": f"Merge these partial analyses into a final answer.\n"
f"Question: {question}\n\n"
f"Partial results:\n" + "\n---\n".join(chunk_results)
}]
)
return merge_response.choices[0].message.contentチャンキングのトレードオフ
- メリット:任意の長さの入力を処理可能
- デメリット:チャンク間のコンテキストが失われる
- デメリット:複数のAPIコール = 総コストとレイテンシの増加
- 対策:チャンクを10-20%オーバーラップさせて境界のコンテキストを保持
戦略4:より大きなコンテキストのモデルに切り替える
入力を本当に大きいまま保つ必要がある場合は、より大きなコンテキストウィンドウを持つモデルに切り替えます:
| モデルファミリー | 典型的な最大コンテキスト | 使用場面 |
|---|---|---|
| GPT-4o | 128Kトークン | ほとんどのワークロードのデフォルト |
| GPT-4o (long context) | 128Kトークン | すでにデフォルト |
| Claude Sonnet/Opus | 200Kトークン | 128K以上が必要な場合 |
| Gemini Pro | 1M+トークン | 非常に長いドキュメント、コードベース全体 |
| Gemini Flash | 1M+トークン | コスト重視の長コンテキストタスク |
大コンテキストモデルのコストへの影響
コンテキストウィンドウが大きいほど、通常はトークン単価が高くなります。品質向上がコストに見合うかどうかを計算してください:
Option A: 80Kにトランケート + GPT-4o = $X
Option B: 150Kフル入力 + Claude Sonnet = $Y
Option C: 150Kフル入力 + Gemini Flash = $Z
比較:品質差 vs コスト差ルーティングゲートウェイによるモデル選択
モデル選択をハードコードする代わりに、入力サイズに応じて適切なモデルにルーティングするゲートウェイを利用できます:
from openai import OpenAI
client = OpenAI(
api_key="your-evolink-key",
base_url="https://api.evolink.ai/v1"
)
# Let the Smart Router choose based on your workload
response = client.chat.completions.create(
model="evolink/auto",
messages=your_messages
)本番パターン:送信前の事前チェック
APIにリクエストを拒否されるのを待たないでください。送信前に入力サイズを確認しましょう:
import tiktoken
def check_context_length(messages: list, model: str, max_tokens: int) -> dict:
"""Pre-check whether messages fit within the model's context window."""
encoder = tiktoken.encoding_for_model(model)
total_tokens = sum(
len(encoder.encode(m["content"])) for m in messages
)
if total_tokens > max_tokens:
return {
"fits": False,
"total_tokens": total_tokens,
"excess": total_tokens - max_tokens,
"suggestion": "truncate" if total_tokens < max_tokens * 1.5
else "summarize_or_switch"
}
return {"fits": True, "total_tokens": total_tokens}これにより、拒否されたリクエストによるレイテンシの無駄を避け、ユーザーにエラーが表示される前に適切な戦略を適用できます。
関連記事
- Fix OpenRouter 429 "Provider Returned Error" — 問題がレートリミットであり入力サイズではない場合
- AI API Timeout: Causes, Retry Patterns, and Fallback Design — 長い入力が拒否ではなくタイムアウトを引き起こす場合
- How to Reduce 429 Errors in Agent Workloads — リクエストサイズと並行してリクエスト量を管理する
- Best AI API Platform for Production Reliability — モデルルーティングを処理するプラットフォームの選択
FAQ
「context length exceeded」とは何を意味しますか?
入力(システムプロンプト + メッセージ + 注入されたコンテキスト)がモデルの最大コンテキストウィンドウで許容されるトークン数を超えていることを意味します。生成が開始される前にリクエストが拒否されます。
「context length exceeded」はレートリミットエラーと同じですか?
いいえ。レートリミットエラー(429)はリクエストの頻度に関するもの — 一定時間内にリクエストが多すぎる場合です。コンテキスト長エラーはリクエストのサイズに関するもの — 1つのリクエストが大きすぎる場合です。それぞれ異なる対処法が必要です。
トランケーションと要約、どちらが良いですか?
トランケーションはシンプルで低コストですが情報が失われます。要約は意味を保持しますが、追加のAPIコールが必要で圧縮のアーティファクトが生じます。直近の情報が最も重要なチャット履歴にはトランケーションを、蓄積されたコンテキストが重要なエージェントワークフローには要約を使用してください。
より大きなモデルを使えばコンテキスト長エラーを回避できますか?
はい、ただしコストが伴います。より大きなコンテキストウィンドウを持つモデル(Gemini 1M+、Claude 200K)はより大きな入力を受け付けますが、通常はトークン単価が高くなります。品質の向上が追加コストに見合うかどうかを計算してください。
リクエスト送信前にトークン数を数えるにはどうすればよいですか?
tiktokenライブラリを、その他のプロバイダーにはそれぞれのトークンカウントエンドポイントを使用してください。正確なトークン数はモデルのトークナイザーに依存するため、ターゲットモデルに適したエンコーダーを使用してください。このエラーはアプリケーションコードとゲートウェイレベルのどちらで処理すべきですか?
両方です。アプリケーションコードでは入力サイズを事前にチェックし、適切な戦略(トランケート、要約、チャンク分割、切り替え)を適用すべきです。ルーティングゲートウェイは、リクエストに対して十分なコンテキストウィンドウを持つモデルを追加で選択できます。


