ブログ

長いエージェントのターンをコンテキストウィンドウ内に収める

モデルが一度に読めるのは、限られた量だけです。これがコンテキストウィンドウです。Mythexのエージェントは長い作業をします。多数のファイルを読み、コマンドを実行し、有界な作業をサブエージェントに渡します。長い作業が壊れる経路を3つ見つけました。サブエージェントが大きくなりすぎて、モデルがすべてのリクエストを拒否するようになったこと。サブエージェントが、余裕がないと思い込んで早々に止まったこと。そして、ロシア語、中国語、日本語のトークン数の見積もりが大きく外れていたことです。以下が、その変更点です。

最初に用語の説明

  • トークンは、モデルが読むかたまりです。上限もコストも、文字数や単語数ではなく、トークンで数えられます。
  • コンテキストウィンドウは、1回のリクエストでモデルが読める最大量です。コストと速度を妥当な範囲に保つために、私たちはリクエストごとに、それより低い独自の予算も設定しています。
  • ツールの結果は、エージェントがツールから受け取るものです。読み込んだファイルの内容や、実行したコマンドの出力です。長い作業では、送られる内容の大部分を占めます。
  • サブエージェントは、独立した作業のためにメインのエージェントが開始する補助役です。たとえば「これらの章をノートと照合して」といった作業です。サブエージェントは1つの指示を受け取り、あとは自分で多数のツール呼び出しを重ねて作業します。

問題1:サブエージェントが大きくなりすぎて、モデルに拒否された

エージェントは毎ステップ、これまでの会話をモデルに送ります。それが大きくなりすぎると、私たちのコードはそれを切り詰めていました。ただし、切り詰めはユーザーメッセージの先頭でだけ行われていました。そのため、ツール呼び出しとその結果が分断されることはありませんでした。最初のメッセージを残し、会話の一部が切り詰められたという注記を加え、収まる範囲で新しい方のターンを丸ごと残していました。

サブエージェントのユーザーメッセージは、指示の1つだけです。そのため、切る場所がありませんでした。毎ステップ、履歴全体を送り続け、そのたびに大きくなっていきました。ついには、モデルが受け付けるサイズ(約100万トークン)を超えてしまいました。8つのサブエージェントを使ったテストでは、8つすべてが拒否されました。

修正:まず古いツールの結果を消す

リクエストは、3つのステップで予算に収めるようになりました。前のステップで足りなかった場合にだけ、次のステップを試します。

  1. 古いツールの結果を消します。 結果の内容は、短い注記に置き換えられます。注記には、会話をモデルのコンテキスト内に収めるために結果を消したこと、必要ならエージェントがツールを再実行できることが書かれています。1時間前に読み込んだファイルは、たいてい最も大きく、最も役に立たないものです。ただし、そのファイルは再び読めるので残っています。新しい結果は、そのまま残します。
  2. ユーザーのターンを丸ごと残します。 以前と同じです。最初のメッセージ、注記、収まる範囲で新しい方の完全なターンを残します。
  3. ツールのやりとりを丸ごと残します。 サブエージェントが持つのはこの種類の会話だけです。1つの長いターンの中で、最初のメッセージ、注記、新しい方から「ツールを呼び出し、その結果を受け取る」という完全なラウンドを残します。

これは送られる内容の形を決めるだけです。エージェントが保存する履歴には、すべてが残ります。同じテストで、拒否されたものは0個、8つすべてが完了しました。

キャッシュが効くように、まとめて消す

モデルのプロバイダーは、直前のリクエストと同一であれば、リクエストの先頭部分の処理を再利用できます。プレフィックスキャッシュと呼ばれるものです。もし毎回の呼び出しで結果を1つずつ消していたら、リクエストの先頭が毎回変わってしまい、キャッシュは決して効きません。

そこで、古い結果は1つずつではなく、4つずつ消すようにしました。そうすると、消された部分は数回の呼び出しにわたって同じままになります。注記よりそれほど大きくない結果は、そのままにしておきます。消しても得られるものはほとんどなく、それでもリクエストの先頭が変わってしまうからです。完全な状態で残す量は、個数ではなくサイズで決めます。4分の1の予算に収まる新しい結果を残し、必ず少なくとも1つは残します。以前は、固定で6つ残していました。それは短いコマンドの出力には適していましたが、長いファイルを読むと、古い結果が予算の大半を占めてしまっていました。

拒否されたときは、そのことを伝える

モデルが無効として拒否したリクエストは、もう再試行しません。同じリクエストを送り直しても、同じ答えが返ってくるだけだからです。会話がモデルには長すぎる場合、利用者には次のように表示されます。「この会話はモデルが一度に読むには長くなりすぎました。次のステップは新しいチャットで始めてください。ここまでに作ったものはすべて残ります。」これは利用者が対処すべきことであり、障害ではないので、私たちにアラートは出しません。

問題2:エージェントが「余裕を確保する」ために止まった

40本の長い章を読むように頼まれたサブエージェントが、2本読んだところで、余裕を確保したいと言って止まりました。400,000トークンの予算のうち、約100,000トークンしか使っていませんでした。

古い結果が、自分のために消されるようになることを、エージェントは知る方法がありませんでした。そこで、それを伝えるようにしました。エージェントの指示には、会話がモデルの受け付けられる量を超えたとき、古いツールの結果は消され、新しい結果はそのまま残ると書かれています。そのため、余裕がないからといって長いタスクを止めてはいけません。作業を続け、消された内容が必要なら、そのファイルをもう一度読んでください。

モデルに見えない修正は、モデルの視点から見れば修正ではありません。その仕組みは、エージェントがそれを信頼して初めて役立ちます。

問題3:すべてが英語であるかのようにトークンを数えていた

トークンの見積もりは、トークナイザーを使わずに、文字数を4で割って行っていました。英語にはそれで妥当な目安ですが、ほかの文字体系には当てはまりません。ロシア語は1トークンあたり約2.2文字、中国語や日本語は約1文字です。

そのため、ロシア語の章を読むサブエージェントは、約640,000トークンのリクエストを送っていました。それなのに、私たちの見積もりは400,000の予算内に収まっていると判断していました。見積もりは、リクエストがモデルの絶対的な上限を超えていても、同じように気づかなかったかもしれません。

見積もりは今、文字体系ごとに数えるようになりました。ASCIIの通常の文字は4文字で1トークン、中国語・日本語・韓国語は1文字で1トークン、そしてキリル文字、ギリシャ文字、アラビア文字、アクセント付きの文字、絵文字などそれ以外は2文字で1トークンとしています。見積もりは多めになるように設計されています。あるロシア語の章では、今は実測の約44,600トークンに対して43,423トークンと見積もられます。以前は25,023トークンでした。メッセージは一度書かれたら変わらないので、その見積もりは毎ステップ計算し直すのではなく、一度だけ計算して覚えておくようにしています。

この件から学んだこと

  • サイズの実態がある場所を切り詰めてください。 エージェントの作業では、それはユーザーメッセージではなく、ツールの結果です。
  • エージェントに、その安全装置について伝えてください。 伝えなければ、もう適用されていない上限を前提に計画してしまいます。
  • 「文字数÷4」は英語の前提です。 多言語のユーザー向けにトークンを数えるものは、文字体系ごとに数えるか、本物のトークナイザーを使う必要があります。
  • キャッシュに注意してください。 毎回の呼び出しで先頭が変わるリクエストは、コストが高くなり、遅くなります。

ご自身のアプリにAI機能を組み込む場合は、LLMとは何かとLLM APIの使い方のガイドで、コンテキストウィンドウとトークンを最初から解説しています。Mythexでサブエージェントがどう動くかについては、サブエージェントのドキュメントをご覧ください。

Mythex · Pricing · AIでなんでも作ろう · ウェブサイト・アプリのテンプレート · Mythexを比較 · AIアプリ開発用語集 · AIアプリ開発の無料ツール · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)