AIエージェントのちょっと怖い話 〜エージェンティック無限ループ?〜

記事タイトルとURLをコピーする

はじめに

こんにちは、久保です。

AIエージェントは、与えられた目標に対して自律的に計画、実行を行う仕組みと考えられます。

2025年はAIエージェントの年と言われており、AWSにおいてもAIエージェントを本番環境で安全かつ効率的に運用可能とする Amazon Bedrock AgentCore が提供開始されるなど、注目が集まっています。

本記事では、AIエージェントの試行中に私が遭遇したちょっとだけ怖い体験を元に、AIエージェントのプラクティスについて考えます。

エージェントループ(Agent Loop)とは?

エージェントループ(Agent Loop)、もしくはエージェンティックループ(Agentic Loop)とは、AIエージェントが目標達成のために繰り返し行う一連のプロセスを指します。

AWSが開発し、OSSとして公開しているAIエージェントのフレームワークである Strands Agents では、エージェントループは以下のステップで構成されると説明されています。

出典: Agent Loop - Strands Agents

  1. ユーザー入力とコンテキスト情報を受け取る
  2. 言語モデル(LLM)を使用して入力を処理する
  3. 情報収集やアクション実行にツールを使用するかどうかを決定する
  4. ツールを実行し、結果を受け取る
  5. 新しい情報に基づいて推論を続ける
  6. 最終的なレスポンスを生成するか、ループを再度繰り返します

ユーザの入力に応じてLLMが手持ちの手段(ツール)を元に目標達成のための計画を自然言語で立案し、必要に応じてツールを呼び出し、その結果を踏まえてさらに計画を練り直す、という一連の流れがエージェントループです。

この自律性故に、AIエージェントのループの精度はその「脳」にあたるLLM(大規模言語モデル)の性能に大きく依存することがお分かりいただけるかと思います。

エージェンティック無限ループとの遭遇

Generative AI Usecases(GenU)と構成について

Generative AI Usecases(GenU)というAWS Japanの方が開発しOSSとして公開されているサンプルアプリケーションがあり、Bedrock AgentCoreを利用したAIエージェントを簡単に試すことができます。

https://aws-samples.github.io/generative-ai-use-cases/ja/index.html

Generative AI Usecases(GenU)

GenUのAgentCore Runtimeの実装はStrands Agentsで行われており、かつあらかじめサンプルとしていくつかのMCP(Model Context Protocol)サーバや、Bedrock AgentCore のビルトインツールが組み込まれています。

今回私は、下図のように、Code Interpreter (コード実行ツール)というビルトインツールを利用するケースを試していました。

例えばシンプルに、「1から100の合計を計算して」といった、プログラムコードによる計算が必要なタスクをAIエージェントに依頼すると、Code Interpreterツールが呼び出され、コードが生成されて実行され、その結果が返されます。

また、AIエージェントで利用するLLMはGenUのチャット画面から選択することができます。

このとき、コスト効率に優れるBedrockのAmazon Nova Proを選択していました。


GenUの現時点での実装では、プロンプトキャッシュ(プロンプト、ツールの双方)に対応したモデルのみ利用可能であるため、Novaを利用しようとするとエラーになります。
Novaを利用するにはAIエージェントのコードを一部変更し、プロンプトキャッシュの指定を外す必要があります。(あえて試していただく必要はございません、ぜひプロンプトキャッシュはご利用ください。)

無限ループの発生

下図のように、Code Interpreterの実行を無限に試行しつづける状態に陥りました。

図中赤枠部分の、繰り返されていた部分は以下のとおりです。

<thinking> It seems that I am unable to execute the calculation directly without creating a session. I will try to create a new session and execute the calculation again. </thinking>
<thinking> It seems that I am unable to create a new session and execute the calculation. I will try a different approach by directly executing the calculation without creating a session. </thinking>

日本語にすると以下となります。

  • 「セッションを作成せずに計算を直接実行することはできないようです。新しいセッションを作成して、もう一度計算を実行してみます。」
  • 「新しいセッションを作成して計算を実行することもできないようです。別のやり方として、セッションを作成せずに計算を直接実行してみます。」

このように、AIエージェントが同じ考えを繰り返し続け、無限ループに陥ってしまいました。

強制停止

Amazon Bedrock AgentCore Runtimeは、最大8時間もの間実行を継続することが可能です。
これは大きなメリットがある一方で、今回のケースでは非常に困ったことになります。

本記事を執筆している時点では StopRuntimeSession というAPIがあるため、実行中のセッションを停止することができます。

当時の時点(GA前)ではこのAPIが存在しなかったため(見落としかもしれませんが)、AgentCore Runtime用のIAMロールからCodeInterpreterツールの実行権限を一時的に剥奪することで、強制的にエラー終了させました。

なお、AgentCore Runtimeは $0.0895 per vCPU-hour, $0.00945 per GB-hourなので、1vCPU+2GBで8時間実行しっぱなしにしても$0.8672と非常に安価です。
どちらかというと無限に消費しつづけるBedrockのLLM利用料金の方が大きな金額になるリスクが高いかと思います。

無限ループとなってしまった要因

AIエージェントの「脳」として利用したAmazon Nova Proが、今回の事例においてはですが、AIエージェントのループを処理する性能(精度)に不足があったと考えられます。

エージェントの「計画作成」を行い「適切にツールを呼び出す」部分はLLMの性能に大きく依存するため、特に「脳」の部分には高性能なモデルを利用することが重要であることが分かります。

対策

同じくAmazon Bedrock で利用可能なAnthropicのClaude系では前述のような無限ループが発生することは今のところ観測していません。

しかし、AIエージェントが無制限にループを継続してしまうリスクはゼロではないため、対策を検討いたしました。

(1) 無限ループ対策の実装

Strands Agentsでは、 Swarmというマルチエージェントを定義する機能では最大繰り返し回数を制限する max_iterations パラメータが存在するのですが、単体のエージェント利用の場合はエージェントループの回数を制限するような仕組みはありません。

代わりに、Callback Handlersという仕組みを利用して、エージェントループの回数をカウントし、一定回数を超えた場合に強制終了させる方法が考えられます。

以下が実装例です。(簡単化のため部分的に省略しています)

from strands import Agent
class IterationLimitExceededError(Exception):
    """反復制限を超えた場合に発生する例外"""
    pass
  
class IterationLimitHandler:
    def __init__(self, max_iterations=10):
        self.iteration_count = 0
        self.max_iterations = max_iterations
    
    def __call__(self, **ev):
        if ev.get("init_event_loop"):
            self.iteration_count = 0
        if ev.get("start_event_loop"):
            self.iteration_count += 1
            if self.iteration_count > self.max_iterations:
                raise IterationLimitExceededError(
                    f"イベントループが最大反復回数({self.max_iterations})に達しました。"
                )
  
agent = Agent(
    model=bedrock_model,
    tools=tools,
    callback_handler=IterationLimitHandler(max_iterations=10),
)
  
agent("ユーザーからの入力")

callback_handlerで指定された関数は、エージェントループの各ステップ(イベント)で呼び出されるため、ここでループ回数をカウントし、上限を超えた場合に例外を発生させることで、無限ループを防止できます。
上記の例では、10回を超えると強制終了するようになります。

何回を上限にしておくべきかはユースケースに依存しますが、チャットのようなユースケースであれば、途中で一旦強制終了されたとしても再度ユーザが入力を行うことで再開できるため、比較的低い回数に設定しておくことも可能かと思います。

参考(グレースフルな停止について)

エージェントループ(event_loop)にはrequest_stateというループを制御するための要求状態を示す仕組みがあります。

Callback Handlersでこのrequest_state"stop_event_loop"を設定することで、ループの早期終了を指示することも可能です。
こちらはグレースフルな停止といえるかと思います。
前述の実装ではエージェントが外部ツールの例外によってうまく動作しない場合を想定していたため、強制終了の例外を発生させる方法としています。
ユースケースによってはこちらのより安全に停止させる方法もご検討ください。

実装例:

class IterationLimitHandler:
    def __init__(self, max_iterations=10):
        self.iteration_count = 0
        self.max_iterations = max_iterations
    
    def __call__(self, **ev):
        if ev.get("init_event_loop"):
            self.iteration_count = 0
        if ev.get("start_event_loop"):
            self.iteration_count += 1
            if self.iteration_count > self.max_iterations:
                request_state = ev.get("request_state", {})
                request_state["stop_event_loop"] = True  // graceful stop

(2) 最大実行時間の短縮

Amazon Bedrock AgentCore Runtimeには、 maxLifetime という設定項目があり、最大実行時間を指定できます。

デフォルトでは8時間となっていますが、60秒から8時間までの間で指定可能です。

こちらをあらかじめ短くしておくことも、対策として有効です。

対策の横展開について

コールバックを利用した(1) 無限ループ対策は、GenUのGitHubリポジトリにプルリクエストとして登録させていただきマージ済みとなっています。

github.com

デフォルトでは DEFAULT_MAX_ITERATIONS = 20 としており20回が上限となっています。
コードを変更いただくか、MAX_ITERATIONS という環境変数を設定することで上書きが可能です。

おわりに

AWSのベストプラクティス集である Well-Architected Framework の Generative AI Lens においても、エージェントの長時間実行への対策が推奨されています。
これは「コスト最適化」に分類されるプラクティスです。

docs.aws.amazon.com

AIエージェントを安全かつコスト最適に運用いただくため、意図せぬ長時間実行の対策についてもぜひご検討ください。

参考資料

久保 賢二(執筆記事の一覧)

猫とAWSが好きです。