予算を圧迫する無限ループ複雑なクエリによってエージェントが再帰的なスパイラルに陥るまでは、すべてがスムーズに進みます。しかし、突然ログがトレースバックで埋め尽くされ、強制停止してしまいます。エージェントはタスクを完了できず、お財布を守るために設計された安全上の上限に達してしまったのです。
langgraph.errors.GraphRecursionError: Recursion limit of 25 reached without hitting a stop condition.
このエラーは「金銭的なサーキットブレーカー」だと考えてください。デフォルトでは、LangGraphは25個のノード遷移を超えるグラフを終了させます。これにより、混乱したLLMが2つのツール間を無限にピンポンし、50ドルのAPIクレジットを使い果たすのを防ぎます。
なぜグラフが制限に達するのか?設定を変更する前に、根本原因を特定しましょう。おそらく、以下の2つの状況のいずれかに直面しています。
- シナリオA:高負荷なタスク。 エージェントは正しく動作していますが、タスクが膨大です。10個の異なるソースを閲覧してレポートを合成するリサーチエージェントなどは、正当に40〜50ステップ必要になる場合があります。- シナリオB:ロジックの罠。 グラフが壊れています。ノードAがノードBを呼び出し、それが再びノードAをトリガーします。これは、LLMがツールの出力を幻覚(ハルシネーション)したり、条件付きエッジのロジックが終了シグナルを認識できなかったりする場合に発生します。## クイックフィックス:制限を増やすエージェントに本当に余裕が必要な場合は、呼び出し時に制限を増やすことができます。デフォルトは25ですが、複雑なRAGパイプラインでは50や100への引き上げが必要になることがよくあります。
呼び出しごとの設定invokeまたはstreamを呼び出す際にconfigオブジェクトを渡します。これが、多様なワークロードを処理する最も安全な方法です。
# この特定の呼び出しに対して recursion_limit を 50 に増やす
config = {"recursion_limit": 50}
inputs = {"messages": [("user", "直近100件のサポートチケットを要約してトレンドを分析して")]}
try:
response = app.invoke(inputs, config=config)
print(response)
except Exception as e:
print(f"エラーをキャッチしました: {e}")
恒久的な修正:循環ロジックの監査エージェントが行き詰まっている場合、症状を隠しても解決にはなりません。グラフが100ステップに達しているなら、制限を200に増やしても問題を解決せずにコストを2倍にするだけです。
1. 条件付きエッジの監査ループは通常、should_continueルーターに潜んでいます。LLMがツール呼び出しを生成したのに、コードがtoolsノードに遷移できない場合、エージェントは自分の思考の中に閉じ込められてしまいます。
def should_continue(state: AgentState):
messages = state['messages']
last_message = messages[-1]
# LLMが「ループ」している場合、この条件に達しない可能性があります
if last_message.tool_calls:
return "tools"
if "最終回答" in last_message.content:
return "end"
return "agent" # 警告: これにより無限ループが発生する可能性があります
2. ステートで反復回数を追跡するLangGraphの内部カウンターだけに頼らないでください。AgentStateにiterations(整数)を追加しましょう。これにより、10回の試行後もエージェントが苦戦している場合に、それまでの進捗状況を要約して出力する「グレースフル・イグジット(優雅な終了)」ノードを構築できます。
3. LangSmithで実行軌跡を可視化する実行パスを可視化することは、ループを見つけるための最速の方法です。LANGCHAIN_TRACING_V2=trueを有効にしてトレースを確認してください。メッセージの内容が全く同じまま続く「Agent-Action-Agent」パターンを探しましょう。これは通常、ツールの出力がLLMの要件を満たしておらず、同じ失敗アクションをリトライしていることを意味します。
検証:修正の確認以前に失敗した特定のテストケースを実行し、ステップ数を監視します。制限を増やした場合は、エージェントがゴールに到達できるはずです。ロジックを修正した場合は、ノード遷移数は少なく抑えられるはずです。
- ノードのフローを監視: 実行中に
app.stream()を使用して、すべてのノード名を出力します。- 履歴の深さをチェック:len(state['messages'])を追跡し、指数関数的に増加していないことを確認します。本番環境では、常にLangGraphの呼び出しをtry/exceptブロックでラップしてください。特にGraphRecursionErrorをキャッチすることで、汎用的な500エラーの代わりに「行き詰まりましたが、これまでに判明したことは以下の通りです」といった役立つメッセージを返すことができます。

