2026年9月4日 金曜日
AI時短ラボ
検証· 約14

GoogleのADKには、エージェントの暴走を止める「500回」という既定の天井がある──ADK_MAX_LLM_CALLSで変更できる仕組み

Google Agent Development Kit(ADK)はv2.8.0で、LLM呼び出し回数の上限を環境変数から設定できる`ADK_MAX_LLM_CALLS`を追加した。ソースコードを確認すると、既定値は500回で、無効な値を指定すると警告ログを出して既定値にフォールバックする仕組みになっている。0以下を指定した場合は制限自体が実質的に無効化される、という挙動も確認できた。

GoogleのADKには、エージェントの暴走を止める「500回」という既定の天井がある──ADK_MAX_LLM_CALLSで変更できる仕組み
執筆・編集:
目次

AIエージェントに作業を任せている最中、意図せず何百回もLLM呼び出しを繰り返してしまい、気づいたら想定外の課金が発生していた——という事態を防ぐための仕組みは、フレームワーク側にどう組み込まれているのか。Google Agent Development Kit(ADK)はv2.8.0(2026年8月26日公開)で、この上限を環境変数から直接コントロールできるADK_MAX_LLM_CALLSを追加した。

3行まとめ

  1. ADKのLLM呼び出し上限は既定500回で、ADK_MAX_LLM_CALLS環境変数で変更できる。不正な値を入れても警告ログを出して既定値にフォールバックする安全設計
  2. 上限到達時はLlmCallsLimitExceededErrorという専用の例外クラスがraiseされる。カウンタは「先に加算してから判定」するため、実際に失敗するのは501回目の呼び出しであることをソースコードで確認した
  3. runners.pyを確認したが、この例外を専用にcatchする処理は見当たらなかった。つまり上限超過は「握りつぶされて処理が続く」のではなく、通常のPython例外として呼び出し元まで伝播する設計になっている

コミットメッセージは1行、しかし実装には具体的な挙動がある

GitHubのコミット履歴を確認すると、この変更のメッセージは次の1行だけだ。

"feat: add ADK_MAX_LLM_CALLS environment variable to configure max LLM calls limit"

ADK_MAX_LLM_CALLS環境変数を追加し、LLM呼び出し回数の上限を設定できるようにした)

だが、変更が加わったソースファイルsrc/google/adk/agents/run_config.pyを実際に開くと、コミットメッセージ以上の設計が読み取れる。

既定値は500回

ソースコードには、既定の上限値が明示的に定義されている。

_DEFAULT_MAX_LLM_CALLS = 500

つまり、ADK_MAX_LLM_CALLSを何も設定しない場合、ADKで動くエージェントは1回の実行につき最大500回までしかLLM呼び出しができない、という制限が既定でかかっている。

環境変数の値が不正な場合は警告を出して既定値にフォールバック

_default_max_llm_callsという関数の実装を見ると、環境変数の値を検証するロジックが入っている。

def _default_max_llm_calls() -> int:
  if env_val := os.getenv('ADK_MAX_LLM_CALLS'):
    ...
  return _DEFAULT_MAX_LLM_CALLS

コード中には、環境変数の値が数値に変換できない場合のログ出力として次のような文字列も確認できる。

'Invalid value for ADK_MAX_LLM_CALLS env var: %s. Using default %s.'

ADK_MAX_LLM_CALLS環境変数の値が不正:%s。既定値%sを使用する)

つまり、環境変数に文字列や不正な形式の値を入れてしまっても、エージェントの実行自体はクラッシュせず、警告ログを出した上で既定の500回にフォールバックする、という安全側の実装になっている。

max_llm_callsフィールド自体のドキュメントと、0以下の値の挙動

RunConfigクラスのフィールド定義には、この値の役割についての説明も付与されている。

  max_llm_calls: int = Field(
      default_factory=_default_max_llm_calls,
      description=(
          ...
          ' overridden by ADK_MAX_LLM_CALLS environment variable.'
      ),
  )

さらに、バリデーターの実装には次のようなチェックが含まれている。

  @field_validator('max_llm_calls', mode='after')
  @classmethod
  def validate_max_llm_calls(cls, value: int) -> int:
    if value == sys.maxsize:
      raise ValueError(f'max_llm_calls should be less than {sys.maxsize}.')
    elif value <= 0:
      logger.warning(
          'max_llm_calls is less than or equal to 0. This will result in'
          ' no enforcement on total number of llm calls that will be made for a'
          ' run. This may not be ideal, as this could result in a never'
          ' ending communication between the model and the agent in certain'
          ' cases.',
      )

    return value

(値がsys.maxsizeと等しい場合はエラーで拒否する。値が0以下の場合は、「これは1回の実行で行われるLLM呼び出しの総数に対する制限を一切なくすことになる。これは望ましくない可能性がある。特定のケースでは、モデルとエージェントの間で終わりのないやり取りに陥る恐れがあるためだ」という警告を出す)

つまり、0や負の値をmax_llm_callsに設定した場合、エラーにはならず警告ログが出るだけで、実際には呼び出し回数の上限チェックそのものが無効化される(実質無制限になる)。この警告文自体が、暴走ループのリスクを開発者自身の言葉で明記している。上限を極端に大きい値(sys.maxsize)に設定することはできないが、0や負の値で意図的に無制限にする余地は残されている、という設計だ。

run_config.pyinvocation_context.pyを突き合わせると、設定値ごとの実際の挙動は次のように整理できる。

設定値 挙動
未設定(既定) 500回。501回目の呼び出しでLlmCallsLimitExceededErrorが発生
正の整数(例:100) その回数+1回目の呼び出しで例外が発生
sys.maxsize バリデーターがエラーを送出し、値自体を拒否する
0または負の数 上限チェックが無効化され、警告ログのみで実質無制限になる
数値に変換できない文字列 警告ログを出して既定値500にフォールバック

上限到達時、実際にどんな例外が飛ぶのか

run_config.pyだけでなく、実際にLLM呼び出し回数をカウントして上限を強制している側のコード(src/google/adk/agents/invocation_context.py)も今回curlで取得して確認した。この中に_InvocationCostManagerという内部クラスがあり、次のようなメソッドが定義されている。

class LlmCallsLimitExceededError(Exception):
  """Error thrown when the number of LLM calls exceed the limit."""

class _InvocationCostManager(BaseModel):
  _number_of_llm_calls: int = 0

  def increment_and_enforce_llm_calls_limit(
      self, run_config: RunConfig | None
  ) -> None:
    # We first increment the counter and then check the conditions.
    self._number_of_llm_calls += 1
    if (
        run_config
        and run_config.max_llm_calls > 0
        and self._number_of_llm_calls > run_config.max_llm_calls
    ):
      raise LlmCallsLimitExceededError(
          "Max number of llm calls limit of"
          f" `{run_config.max_llm_calls}` exceeded"
      )

コードのコメント「We first increment the counter and then check the conditions(まずカウンタを加算してから条件をチェックする)」が示す通り、判定の順序は「加算→比較」だ。既定値の500で言えば、500回目の呼び出しは_number_of_llm_callsが500になった時点で500 > 500は偽なので通過し、501回目の呼び出しで501 > 500が真になり、そこで初めて専用の例外クラスLlmCallsLimitExceededErrorが投げられる。つまり実際に許可される呼び出し回数は、設定値ちょうどの回数であり、そこからさらに1回試みたところで打ち切られる、という挙動だ。

この例外がどこで捕捉されるかを確認するため、src/google/adk/runners.pycurlで取得し、except節を一通り検索した。asyncio.CancelledErrorStaleSessionErrorasyncio.TimeoutErrorなどを個別に捕まえている箇所は見つかったが、LlmCallsLimitExceededErrorを専用にcatchしている箇所は見当たらなかった。つまりこの例外は、ADKのランナー内部で握りつぶされて「静かに処理を打ち切る」のではなく、通常のPython例外として、エージェントを呼び出しているアプリケーション側まで伝播する設計になっていると考えられる。ただしrunners.py以外のファイル(呼び出し階層の途中にある別のモジュール)で捕捉されている可能性までは、今回の検索範囲では排除できない。

なぜこの上限が必要なのか

エージェントがツール呼び出しの結果を誤って解釈し続けたり、終了条件を満たせないまま同じ処理を繰り返したりする「暴走ループ」は、AIエージェント実装における典型的な失敗モードの1つだ。Gooseの/goalコマンド(本サイトの別記事で取り上げた、目標検証の自己ナッジを3回で打ち切る設計)も、根本的には同じ種類の問題——「エージェントがいつまでも終わらない」という状況——への対処だった。ADKのADK_MAX_LLM_CALLSは、目標検証のような賢い仕組みではなく、単純な回数の天井によってこの問題に歯止めをかける、より原始的だが確実な安全策だ。

API呼び出しが膨らんだ経験から見た500回という数字

筆者は、AIエージェントを使ったバッチ処理で、想定より多くのAPI呼び出しが発生してしまった経験が実際にある。そうした場面を振り返ると、こうした「最終的な回数の天井」がフレームワーク側に既定で組み込まれていること自体が、地味だが実務上は重要な安全網だと感じる。500回という数字は、複雑なマルチステップタスクには十分な余裕がありつつも、無限ループに陥った場合の被害を一定範囲に抑える、バランスを取った既定値だと考えられる。

runners.py以外のcatch処理までは追い切れていない

LlmCallsLimitExceededErrorrunners.py内でcatchされていないことは確認したが、ADKのコードベース全体(flows/配下の各モジュールなど)をgrepで網羅的に検索したわけではなく、他のどこかで捕捉されて別の形でユーザーに提示されている可能性は排除できない。また、この機能がADKの過去バージョンから存在した固定の上限値(環境変数化される前の挙動)とどう違うのかについても、今回の情報源からは確認できなかった。実際にPythonスクリプトでADKエージェントを動かして500回を超えさせ、例外の発生・伝播を手元で再現するところまでは、このセッションでは行っていない。

関連記事

シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事