A
AIエージェントの仕組み
ch6-s3 · State & Termination

状態管理と停止条件

約 14 分

この回のゴール

1. 暴走の実例

例 1: 無限ループ

Claude が同じツールを繰り返し呼ぶ:

反復1: get_stock_price("AAPL") → $230
反復2: get_stock_price("AAPL") → $230  ← 同じ
反復3: get_stock_price("AAPL") → $230
...

原因: - ツール結果を 使いこなせていない(理解ミス) - ツールの 戻り値が曖昧 で決定打にならない - system プロンプトが 「検索し続けろ」と示唆 している

例 2: 予算超過

エージェントは 毎反復でトークンを消費 します:

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):
    ...

③ トークン予算の上限

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

④ 時間の上限

import time
t0 = time.time()
for it in range(max_iter):
    if time.time() - t0 > TIMEOUT_SECONDS:
        break
    ...

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

まとめ

この回の限界(次への動機)

安全に止まるエージェントは作れた。次は「RAG × 計算 × 時刻」など 複数種類のツールを連携 させた本格タスクを解かせる番。 👉 次回「マルチステップ実践」で、第 5 章の RAG を組み込んだ完成形エージェントを作ります。

参考文献

A
本家 Claude Academy で手を動かす
この回に対応する Anthropic 公式のレッスンです。ここは「なぜそう動くか」、向こうは「どう書くか」。サインインなしで読めます。
📝 理解度クイズ (3 問) 💡 ログインすると進捗が保存されます

💬 このサブステップの Q&A

まだ質問はありません。最初の質問を投稿してみましょう。

質問の投稿にはログインが必要です。