この回のゴール
- エージェントが 無限ループ や 予算超過 に陥る原因を理解する
- 4 つの 停止条件 を使い分けられるようになる
- 状態トラッキング で「今どこにいるか」を把握する
- 本番運用で必須の 安全設計 を身につける
1. 暴走の実例
例 1: 無限ループ
Claude が同じツールを繰り返し呼ぶ:
反復1: get_stock_price("AAPL") → $230
反復2: get_stock_price("AAPL") → $230 ← 同じ
反復3: get_stock_price("AAPL") → $230
...
原因: - ツール結果を 使いこなせていない(理解ミス) - ツールの 戻り値が曖昧 で決定打にならない - system プロンプトが 「検索し続けろ」と示唆 している
例 2: 予算超過
エージェントは 毎反復でトークンを消費 します:
- 反復 1: 入力 500 tok / 出力 100 tok
- 反復 2: 入力 700 tok / 出力 150 tok(履歴追加で input 増)
- 反復 3: 入力 1000 tok / 出力 200 tok
- ...
10 反復もすると 合計 10,000 トークン 超え。長い調査タスクでは数十万トークンに。
例 3: 答えにならない脱線
RAG エージェントで:
Q: 「有給は何日?」
エージェント:
→ search_handbook("有給")
→ search_handbook("休暇") ← まだ深掘る
→ search_handbook("年次") ← さらに
→ search_handbook("日数") ← ...
もういい加減にして回答してほしい のに掘り続ける。
2. 4 つの停止条件
① Claude 自身が end_turn を返す(理想)
if response.stop_reason == "end_turn":
break # Claude が「完了」と判断
これが 一番自然な停止。ただし常に期待通りに止まるとは限らない。
② 反復回数の上限 (max_iterations)
for it in range(max_iter):
...
- 安全弁。必ず設定すべき
- 目安: 簡単 5、普通 10〜15、複雑 30
- 大きすぎるとコスト暴発、小さすぎると途中打ち切り
③ トークン予算の上限
total_tokens = 0
for it in range(max_iter):
response = client.messages.create(...)
total_tokens += response.usage.input_tokens + response.usage.output_tokens
if total_tokens > TOKEN_BUDGET:
break
- コスト管理 の王道
- 例: 1 タスク 50,000 トークンまで → 超えたら打ち切り
④ 時間の上限
import time
t0 = time.time()
for it in range(max_iter):
if time.time() - t0 > TIMEOUT_SECONDS:
break
...
- ユーザー待機を避ける
- バッチ処理では長めで OK、対話型は 10〜30 秒 くらい
3. 「同じツール連続呼び出し検知」
追加のカスタム条件として:
tool_history = []
for it in range(max_iter):
...
# 直近 3 回が全く同じ呼び出しなら停止
tool_history.append((tool_name, json.dumps(tool_input, sort_keys=True)))
if len(tool_history) >= 3 and len(set(tool_history[-3:])) == 1:
print("同じツールの連続呼び出し検知、打ち切り")
break
4. 状態を「見える化」する
エージェントの状態を トラッキング するための基本項目:
state = {
"iteration": 0,
"tool_calls": [], # 呼び出し履歴
"total_input_tokens": 0,
"total_output_tokens": 0,
"total_cost_usd": 0.0,
"elapsed_sec": 0.0,
"status": "running", # running / done / max_iter / budget / timeout / error
}
これを毎反復で更新し、UI に表示 します(進捗バー、コスト計、etc)。
5. デッドロック的な状況とその対処
ツール実行エラー時
try:
result = tool_func(**tool_input)
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": ...,
"content": f"Error: {e}",
"is_error": True, # 👈 明示的にエラーフラグ
})
Claude は is_error: True を見て リトライ or 別アプローチ を選びます。
連続エラー時の強制終了
consecutive_errors = 0
for it in range(max_iter):
if consecutive_errors >= 3:
# 3 連続エラーなら諦める
break
...
if any(r["is_error"] for r in tool_results):
consecutive_errors += 1
else:
consecutive_errors = 0
6. 安全なエージェントループ(まとめテンプレ)
def safe_agent_loop(
question, tools, tool_impls,
max_iter=10, max_tokens_budget=50000, timeout_sec=60,
):
state = {"iteration": 0, "total_tokens": 0, "status": "running"}
t0 = time.time()
messages = [{"role": "user", "content": question}]
for it in range(max_iter):
state["iteration"] = it + 1
if time.time() - t0 > timeout_sec:
state["status"] = "timeout"; break
if state["total_tokens"] > max_tokens_budget:
state["status"] = "budget"; break
response = client.messages.create(tools=tools, messages=messages, ...)
state["total_tokens"] += (
response.usage.input_tokens + response.usage.output_tokens
)
if response.stop_reason == "end_turn":
state["status"] = "done"; break
# ツール実行(省略)
else:
state["status"] = "max_iter"
return response, state
まとめ
- 停止条件は 4 種:
end_turn/max_iter/ トークン予算 / タイムアウト -
- 同一ツール連続呼び出し検知 などのカスタム安全弁
- エラー時は
is_error: Trueで Claude に明示。連続エラーで強制終了 - 状態 (
statedict) を毎反復で更新し UI に可視化
この回の限界(次への動機)
安全に止まるエージェントは作れた。次は「RAG × 計算 × 時刻」など 複数種類のツールを連携 させた本格タスクを解かせる番。 👉 次回「マルチステップ実践」で、第 5 章の RAG を組み込んだ完成形エージェントを作ります。
参考文献
- Anthropic: Building effective agents
- AutoGPT / BabyAGI の反省から生まれた「安全なエージェント」設計ドキュメント多数