0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CodexにCIの状態を何度も確認させるのをやめたら、入力トークンが約9割減った

0
Posted at

はじめに

Codex CLIにIssueの実装からPR作成、CI・Codexレビューへの対応まで任せていたところ、GitHubの状態確認を何度も繰り返していることに気づきました。

例えば、次のようなメッセージです。

  • CIの成功状態に変化はありません。CIの結果が分かるまで待機します。
  • Codexレビューはまだ完了していません。

1回のPRで平均5〜6回、多いときには10回以上も表示されていました。

これだけ何度も確認しているなら、そのたびにLLMが呼び出され、大量のトークンを消費しているはずです。

そこで待機方法を見直したところ、CI監視中のLLM呼び出し回数と入力トークン数を約9割削減できました。

想定読者

  • Codexでの実装タスクにおけるトークン消費が気になっている方
  • CI・レビューが完了するまでCodexに任せている方

改善①:GitHub CLIにCIの監視を任せる

最初にセッションログを調べたところ、GitHubの状態を確認するたびにツールの結果がLLMへ返され、次の確認処理を判断していることが分かりました。

そこで考えたのが、CIの監視をGitHub CLIに任せ、完了したときだけCodexに結果を返す方法です。

従来:
Codex → GitHub確認 → Codex → GitHub確認 → ...

改善後:
Codex → 監視コマンドを実行 → CI完了 → Codex

調べてみると、GitHub CLIにはgh pr checks --watchというコマンドがありました。

gh pr checks <PR番号> --watch --interval 30

これは30秒ごとにGitHubへ問い合わせ、PRのCIチェックが完了するまで監視を続けるコマンドです。
つまり、Codexがこのコマンドを1回実行して終了まで待てば、CIの状態を確認するたびにLLMが判断する必要はなくなります。

ただし、ここで1つ問題があります。
Codexがコマンドを実行する際、一定時間が経過しても処理が終了しなければ、ツールが次のような結果を返すことがあります。

Process running with session ID 12345

これはCIが完了したという意味ではなく、コマンドがまだ実行中であることを示しています。

このsession_idは、実行中のコマンドを識別するためのIDです。CodexはこのIDを使って、同じコマンドの終了を引き続き待つことができます。

そのために使うのがwrite_stdinです。
write_stdinは、実行中のコマンドとやり取りするためのツールで、session_idを指定すると、コマンドを再実行せずに、その終了や出力を待つことができます。

例えば、次のように呼び出します。

{
  "session_id": 12345,
  "chars": "",
  "yield_time_ms": 300000
}
  • session_id:待機するコマンドを指定する。
  • chars: "":何も入力せずに待つ。
  • yield_time_ms: 300000:最大300秒(5分)待機する。

5分は最大待機時間です。コマンドがそれより早く終了すれば、その時点で結果を受け取れます。

これなら、gh pr checks --watchにCIを監視させ、その終了をwrite_stdinで長時間待つことで、無駄なLLM呼び出しを減らせると考えました。

そこで、独自に作成していたGitHub操作用Skillに、この待機方法を記述しました。

失敗:ツールの待機構造への理解不足

実際に試してみると、LLMの呼び出し回数は全く減りませんでした。

セッションログを確認すると、そもそもwrite_stdinに300秒が指定されておらず、最大60秒程度の待機になっていました。

さらに調べると、write_stdin以外にも待機時間に関わるツールがあることが分かりました。

ツール 役割
functions.exec JavaScriptを実行し、その中からwrite_stdinなどのツールを呼び出す
write_stdin 実行中のコマンドの終了や出力を待つ
functions.wait 途中で返却されたfunctions.execの続きを待つ

ここで重要なのは、write_stdinと、それを呼び出すfunctions.execに、それぞれ待機時間があることです。

例えば、functions.execからwrite_stdinを呼び出し、60秒間待とうとします。

しかし、外側のfunctions.execが30秒で途中返却すると、内部のwrite_stdinが待機を続けていても、LLMに制御が戻ります。

Codexはfunctions.waitを呼び、残りの処理を待とうとします。ところが、functions.waitの待機時間も短いと、再びLLMに制御が戻ってしまいます。

実際のセッションログでも、次の動作を確認できました。

ツール 当時の動作
write_stdin 60秒の待機を指定
functions.exec 約30秒で途中返却
functions.wait 1秒・10秒程度の待機を繰り返す

Codex v0.163.0のソースコードを確認すると、デフォルトの待機時間はfunctions.execが30秒、functions.waitが10秒でした。

つまり、最初の改善には次の2つの問題がありました。

  1. Skillに5分待機と書いても、実際のwrite_stdinにはその値が指定されていなかった。
  2. 仮にwrite_stdinを5分にしても、外側のfunctions.execやfunctions.waitが短時間で返却してしまう。

無駄なLLM呼び出しを減らすには、3つのツールの待機時間を見直す必要があったわけです。

改善②:config.tomlで待機時間を5分に変更する

Codexのソースコードと設定ファイルを調べ、3つのツールの待機時間を最大5分に変更することにしました。

functions.execのデフォルト待機時間は、~/.codex/config.tomlで変更できます。

一方、write_stdinとfunctions.waitには、同様の設定項目が見つかりませんでした。

そこで利用したのが、LLMに共通の指示を追加できるdeveloper_instructionsです。

最終的に、config.tomlへ次の設定を追加しました。

developer_instructions = """
長時間実行中の処理を待機する場合:
- functions.wait は yield_time_ms=300000 を優先する。
- write_stdin の空入力も yield_time_ms=300000 を優先する。
- 途中経過の確認だけを目的とした短時間の待機を繰り返さない。
"""

[features.code_mode]
enabled = true
default_exec_yield_time_ms = 300000

この設定で、functions.execのデフォルト待機時間を300秒に変更できます。write_stdinとfunctions.waitには、最大300秒の待機を優先するよう指示しています。

※ developer_instructionsはLLMへの指示なので、必ず指定どおりに動作する保証はありません。

検証結果:LLM呼び出しが16回から2回に減少

設定を変更した後、GitHub Actionsを手動実行し、Codexに完了まで監視させました。

検証にはgh run watchを使い、設定変更前後でそれぞれ約4分間のCIの監視を比較しました。

指標 改善前 改善後 削減率
LLM呼び出し 16回 2回 87.5%
入力トークン 587,257 65,982 88.8%

改善前は、functions.execとfunctions.waitが短時間で制御を返すたびに、LLMが呼び出されていました。

改善後はwrite_stdinがCIが動作していた約4分間連続で待機し、その間にLLMが呼び出されることはありませんでした。

この実験ではfunctions.waitが呼ばれなかったため、別途functions.execの待機時間を短くして検証し、functions.waitでも約5分の連続待機を確認できました。

考察:5分待機による影響

今回、3つのツールの待機時間を最大5分に変更しましたが、デフォルトをそこまで長くして問題ないのでしょうか。

CI監視では、コマンドが正常に終了すれば5分を待たずに結果が返ってきます。そのため、待機時間を延ばすことによる影響は小さいと考えました。

一方、処理が停止した場合や、途中で入力が必要になった場合には、問題に気づくのが遅れる可能性があります。

それでも、私の場合は数分程度でCIが完了するため、待機時間を延ばすメリットの方が大きいと判断しました。

ただし、1時間かかるようなCIでは、5分待機でも何度もLLMが呼ばれる可能性があります。そうした場合は、Webhookなどで完了を通知する仕組みの方が適しているかもしれません。

おわりに

今回特に驚いたのは、単にCIの完了を待っているだけで、約60万もの入力トークンが発生していたことです。

その大部分はキャッシュ入力でしたが、待機時間を見直すことで大幅に削減できました。

ひとまずCIの監視はかなり改善できたので、よかったです。

一方、Codexレビューの完了をイベント駆動で検知し、ローカルのCodexを自動再開する仕組みは、まだ実装できていません。こちらもいつか改善したいと思っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?