「エージェントが途中で止まる」「同じツールを何度も呼び出してループする」「期待していたアクションをとらない」——AI エージェントを実際に動かすと、こういうトラブルが必ず出てきます。
エージェントのデバッグが難しいのは、モデルの判断プロセスが完全にはわからないからです。でも、詰まるポイントにはある程度のパターンがあります。よくある原因と、効率よく絞り込む診断の手順を整理しました。
まず確認すること: エラーの種類を特定する
エージェントが「動かない」と一口に言っても、状況は大きく異なります。最初にどのカテゴリの問題かを特定することが大切です。
カテゴリ1: エージェントが起動しない
- ツールの設定が間違っている
- 認証エラー
- プロンプトの形式が正しくない
カテゴリ2: エージェントが途中で止まる
- ツール呼び出しが失敗している
- 入力データの形式がツールと合っていない
- タイムアウト
カテゴリ3: エージェントがループする
- タスクの完了条件が不明確
- ツールのレスポンスが次のアクションの判断に使えていない
- メモリ/コンテキストの管理の問題
カテゴリ4: エージェントが間違った行動をとる
- システムプロンプトが不明確
- ツールの説明(description)が曖昧
- Few-shot 例が逆効果になっている
この分類をするだけで、デバッグの方向性がかなり絞れます。
カテゴリ1の診断: エージェントが起動しない
Antigravity でエージェントを設定する際の典型的なミスを確認します。
# まずシンプルな疎通確認から始める
import os
from google import genai
from google.genai import types
client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
# ✅ Step 1: ツールなしで基本的な疎通を確認する
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="こんにちは。テストメッセージです。"
)
print("疎通確認:", response.text[:50])
# ✅ Step 2: ツールを1つだけ追加して確認する
def get_current_time() -> str:
"""現在の時刻を返します"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S JST")
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="現在の時刻を教えてください",
config=types.GenerateContentConfig(
tools=[get_current_time], # 1つのツールだけ
system_instruction="ユーザーのリクエストに応じてツールを使用してください。"
)
)
print("ツール呼び出し確認:", response.candidates[0].content)疎通確認からシンプルなツール追加へと段階的に試すことで、どの段階で問題が起きているかがわかります。
カテゴリ2の診断: ツール呼び出しの失敗
ツールが呼び出されているのに結果が正しくない場合は、ツールの入出力をロギングして確認します。
import json
import logging
from datetime import datetime
from typing import Any
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def debug_tool_wrapper(tool_func):
"""ツール呼び出しのデバッグログを記録するデコレータ"""
def wrapper(*args, **kwargs):
call_id = datetime.now().strftime("%H:%M:%S.%f")
logger.debug(f"[{call_id}] ツール呼び出し: {tool_func.__name__}")
logger.debug(f"[{call_id}] 引数: args={args}, kwargs={kwargs}")
try:
result = tool_func(*args, **kwargs)
logger.debug(f"[{call_id}] 成功: {str(result)[:200]}")
return result
except Exception as e:
logger.error(f"[{call_id}] エラー: {type(e).__name__}: {e}")
# エラーをそのまま投げると エージェントが止まるので、エラー情報を返す
return f"エラーが発生しました: {type(e).__name__}: {str(e)}"
wrapper.__name__ = tool_func.__name__
wrapper.__doc__ = tool_func.__doc__
return wrapper
# 実際のツールにデバッグラッパーを適用
@debug_tool_wrapper
def search_database(query: str, limit: int = 10) -> str:
"""データベースを検索して結果を返します。
Args:
query: 検索クエリ
limit: 返す結果の最大件数(デフォルト: 10)
Returns:
JSON形式の検索結果
"""
# 実際の検索処理
results = perform_actual_search(query, limit)
return json.dumps(results, ensure_ascii=False)ツールのエラーハンドリングで特に重要なのは、例外をそのまま投げないことです。エージェントはツールからのレスポンスを次の判断に使うため、エラー情報を文字列として返してあげることで、エージェントが状況を理解して次の行動を選べるようになります。
カテゴリ3の診断: ループの検出と対処
エージェントがループに入っている場合、コールバックでステップを記録して原因を特定します。
from google import genai
from google.genai import types
import os
class AgentLoopDetector:
"""エージェントのループを検出するクラス"""
def __init__(self, max_repetitions: int = 3, max_steps: int = 20):
self.tool_call_history: list[tuple[str, str]] = [] # (ツール名, 引数ハッシュ)
self.step_count = 0
self.max_repetitions = max_repetitions
self.max_steps = max_steps
def record_tool_call(self, tool_name: str, args: dict) -> bool:
"""ツール呼び出しを記録し、ループを検出した場合は True を返す"""
self.step_count += 1
# ステップ数の上限チェック
if self.step_count > self.max_steps:
print(f"⚠️ 最大ステップ数 ({self.max_steps}) に達しました")
return True
# 同じツールに同じ引数を繰り返し呼んでいるか確認
call_signature = (tool_name, str(sorted(args.items()) if args else []))
recent_calls = self.tool_call_history[-self.max_repetitions * 2:]
repetition_count = recent_calls.count(call_signature)
self.tool_call_history.append(call_signature)
if repetition_count >= self.max_repetitions - 1:
print(f"⚠️ ループ検出: {tool_name} が {repetition_count + 1} 回繰り返されています")
print(f" 直近の呼び出し履歴: {recent_calls[-6:]}")
return True
return False
# 使用例
detector = AgentLoopDetector(max_repetitions=3, max_steps=15)
# エージェントのステップ実行前にループチェック
def safe_execute_agent_step(tool_name: str, args: dict):
if detector.record_tool_call(tool_name, args):
raise RuntimeError(f"エージェントループを検出しました。処理を中断します。ステップ: {detector.step_count}")
# 実際のツール実行
return execute_tool(tool_name, args)ループが起きる根本原因の多くは、タスクの完了条件が曖昧なことです。システムプロンプトに「タスクが完了したと判断する基準」を明示しましょう。
# ❌ ループが起きやすいシステムプロンプト
bad_system = """
ユーザーの質問に答えてください。
必要に応じてツールを使用してください。
"""
# ✅ 完了条件を明示したシステムプロンプト
good_system = """
あなたはリサーチエージェントです。
## タスク
ユーザーの質問に対して、ツールを使って情報を収集し、回答を生成します。
## 完了条件
以下のいずれかの条件を満たしたら、ツールの使用を停止して最終回答を生成してください:
1. 質問に答えるために必要な情報がすべて揃った
2. 検索ツールを3回使用したが情報が見つからなかった
3. 同じ検索を2回実行した
## 重要なルール
- 同じ検索クエリを2回以上実行しない
- 情報が不足していても、得られた情報の範囲で最善の回答を提供する
- 「情報が見つかりませんでした」で終わらず、代替案や次のステップを提案する
"""カテゴリ4の診断: 間違った行動の原因を特定する
エージェントが意図しない行動をとる場合、ツールの description が問題のことがよくあります。
# ❌ 曖昧なツール説明(エージェントが誤用しやすい)
def search_knowledge_base(query: str) -> str:
"""ナレッジベースを検索する"""
# ...
# ✅ 具体的なツール説明(いつ使うべきか・何を返すかが明確)
def search_knowledge_base(query: str, search_type: str = "semantic") -> str:
"""社内のナレッジベースを検索して関連ドキュメントを返します。
このツールは、以下の場合に使用してください:
- ユーザーが製品仕様・マニュアル・FAQ に関する質問をしている
- 過去のトラブル対応事例を調べたい
このツールは、以下には使用しないでください:
- リアルタイムの外部情報が必要な場合(web_search を使ってください)
- データベースのレコードを取得する場合(query_database を使ってください)
Args:
query: 検索キーワードまたは自然言語による質問
search_type: "semantic"(意味的検索)または "keyword"(キーワード検索)
Returns:
関連ドキュメントの内容を含むJSON文字列。各ドキュメントには
title・content・relevance_score・last_updated が含まれます。
結果が見つからない場合は空のリストを返します。
"""
# ...ツールの description に「いつ使うべきか・使うべきでないか」を書くことで、エージェントが適切なツールを選べるようになります。
エージェントのデバッグは試行錯誤が必要ですが、まずカテゴリを絞り込んでから原因を探ると効率よく進められます。ログとループ検出の仕組みを最初から入れておくことで、本番で問題が起きた時の調査時間を大幅に短縮できます。
最初は safe_execute_agent_step の実装から始めてみてください。ループを検出できるようになるだけで、エージェントのデバッグがずいぶん楽になります。
個人開発12年とアーティスト活動から見るエージェント設計
線引きするときの3つの判断軸
- 失敗時の影響が金銭やユーザー体験にどれだけ波及するか
- 復旧オペレーションが明文化されているか
- 観測ログから人間が再現できる粒度に整っているか
まとめ — 検証ステップの推奨
- 観測メトリクスとアラートを設置
- 限定ロールアウトで本番検証