第8章 トークンあふれ — コンテキストには上限がある
📖 この章のゴール:トークンとコンテキストウィンドウ(一度に渡せる上限) を理解し、会話が伸びると“あふれる”理由が分かるようになる。 ← 目次・はじめにへもどる
📱 ChatGPTではこう見える
長い会話をずっと続けていると、こんな経験はありませんか?
- さっき話したはずのことを、急に忘れたみたいな返事をする
- 突然 エラーが出て返事が来なくなる
- 返事が だんだん短くなる、途中でぷつっと 切れる
「AIが疲れたのかな?」と思うかもしれません。でも本当の理由は、一度に渡せる量に上限があるから。この章では、その正体をのぞいていきます。
🤔 なぜ会話が伸びると“あふれる”のか(🟢 基礎)
第4章で確かめたとおり、LLM(大規模言語モデル=文章を作るAI)は毎回まっさらに忘れます(ステートレス)。会話が続いて見えるのは、こちらが毎回 「これまでの会話ぜんぶ(全 messages)」を渡し直している からでした。
ということは——会話が伸びるほど、毎回送る量が増えていくのです。
1往復目 :[system, user, assistant] ← 少ない
10往復目 :[system, user, assistant, ... ×20] ← どんどん増える
50往復目 :[system, user, assistant, ... ×100] ← かなり多い
ところが、LLMには 一度に扱える量の上限があります。これを コンテキストウィンドウ(context window=“文脈の窓”)と呼びます。しかもこの窓には、送る分(入力)と返事の分(出力)の両方が入ります。
🧰 たとえ話:宅配の箱 コンテキストウィンドウは 決まった大きさの箱。会話の言葉は、その箱に詰める 荷物です。会話が伸びると荷物が増え、やがて 箱に入りきらなくなる。これが「トークンあふれ」です。
あふれると、APIは エラーを返すか、入りきらなかった古い部分を だまって切り捨てる(だから“前のほうを忘れた”ように見える)ことがあります。
トークンって何?(🟢 基礎)
では、その「荷物の量」はどう数えるのでしょう。使う単位が トークン(token)です。
トークンは、ざっくり言うと 文章を区切った小さな断片。単語まるごとのときもあれば、単語の一部のときもあります。
- 英語:1単語が だいたい 1〜数トークン(
cat=1、unbelievableのような長い語は複数に割れる) - 日本語:1文字で複数トークン になりがち。つまり、同じ“見た目の長さ”でも 日本語のほうがトークンを多く食べます
📌 大事なのは「文字数 = トークン数 ではない」こと。日本語まじりの会話では、思ったより早く箱が一杯になります。
モデルごとに箱の大きさは違う(🔧 応用)
箱(コンテキストウィンドウ)の大きさは モデルによって違います。新しいモデルほど大きい箱を持つ傾向がありますが、「大きい=あふれない」ではありません。長い会話を続ければ、どんな箱でもいつかは一杯になります。具体的な上限値や数え方・料金は 付録D にまとめます。
🛠 どう向き合う — 送る前にざっくり見積もる(🟢 基礎)
向き合い方はシンプルです。送る前に「だいたい何トークンか」を見積もり、上限に近づいたら手を打つ。
まずは、いちばん簡単な 文字数からのざっくり概算を見てみましょう。
// 文字数からトークン数を“ざっくり”見積もる(あくまで目安)
const CHARS_PER_TOKEN = 2; // 日本語まじりは多めに食うので小さめに見積もる
const estimateTokens = (text: string): number =>
Math.ceil(text.length / CHARS_PER_TOKEN);
1行ずつ読むと:
CHARS_PER_TOKEN = 2:「2文字ぶんで だいたい1トークン」という、ゆるい目安を名前付きの定数に。日本語は食いが多いので、わざと 多めに見積もる(小さめの数) ようにしています。text.length / CHARS_PER_TOKEN:文章の 文字数を、その目安で割る。Math.ceil(...):割り算の余りは 切り上げ(安全側に多めに数える)。
⚠️ これは 正確な値ではなく“目安” です。「まだ余裕/そろそろ危ない」を 早めに気づく ための、ものさし程度に使います。きっちり数えたいときは、tiktoken(ティックトークン) のような 専用のライブラリ を使います(モデルに合わせて正確に数えてくれる道具)。導入の手順は 付録D へ。
そして、上限が近いと感じたら——古い会話を 要約してまとめる、保存は別に ログとして残す、といった工夫で“あふれ”を防ぎます。これがまさに 次の第9章(要約+ログ) のテーマです。
⚠️ ハマりどころ
- 「文字数=トークン数」だと思い込む:とくに日本語は1文字で複数トークンになりがち。見た目より早くあふれます。
- system プロンプトや過去履歴を“ノーカウント”にしてしまう:箱に入るのは user の最新発言だけではありません。system(AIへの前提・キャラ)も、これまでの全 messages も、ぜんぶトークンを食べます。
- 出力にも上限があるのを忘れる:返事の長さも max_tokens(マックストークンズ=出力の上限)で頭打ちになります。入力で箱を使いすぎると、返事に使える残りが減って、答えが途中で切れることも。
- モデルごとの上限差を見落とす:あるモデルで動いたからと別のモデルに変えると、箱の大きさが違ってあふれる/あふれない が変わります(付録D)。
- 概算を“正確な数”だと信じる:上の概算はあくまで早期警告用。課金や厳密な制御には専用ライブラリを。
🤖 AIに頼むなら(Vibe codingのコツ)
🗣 プロンプト例: 「TypeScript で、
messages({ role, content }の配列)を受け取り、会話全体のトークン数をざっくり見積もる関数を書いて。日本語まじりを想定して 多めに見積もること。さらに、上限(引数で渡す)に近づいたら true を返して警告できるようにして。係数や上限は マジックナンバーにせず名前付きの定数にして、各行に短いコメントを付けて」
出てきたコードの確認ポイント:
- 全 messages(system も過去履歴も) を合計しているか(最新の user だけ数えていないか)
- 係数や上限が 名前付きの定数になっているか(マジックナンバーでないか)
- 日本語を想定して 多めに見積もる側に倒しているか
- 「正確な値ではなく 目安」だと、コメントや関数名で分かるか
- 上限に近いとき(例:8割超え)に 警告/真偽値を返せるか
📝 ことばメモ
- トークン:文章を区切った小さな断片。LLMが「量」を数える単位。日本語は1文字で複数トークンになりがち
- コンテキストウィンドウ:一度に渡せる量の上限(“文脈の窓”)。入力+出力の両方がここに収まる
- 入力トークン/出力トークン:送る分(system+全 messages)が入力、返事の分が出力。どちらも窓を消費する
- 上限(max_tokens):返事(出力)の長さの上限。入力を使いすぎると返事に使える残りが減る
➡️ 次の章へ
この章で、「会話が伸びる → 送る量が増える → いつか箱(コンテキストウィンドウ)からあふれる」という流れが見えました。原因が分かれば、あとは あふれさせない工夫です。
次の第9章では、古い会話を 要約(summary)してギュッと縮めつつ、元の会話は ログとしてちゃんと残す——その いいとこ取り(ハイブリッド) のやり方を学びます。背骨①(🧠記憶)の山場です。