第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)してギュッと縮めつつ、元の会話は ログとしてちゃんと残す——その いいとこ取り(ハイブリッド) のやり方を学びます。背骨①(🧠記憶)の山場です。

→ 第9章 溢れさせない — 要約(summary)+ログのハイブリッドへ

← 目次・はじめにへもどる