9.3.5 よく使うツールの統合
- Agent でよく使う代表的なツールの種類を理解する
- 各種類のツールが何の問題に向いているかを理解する
- 統一されたツール登録とディスパッチの例を読めるようになる
- ツール統合でよく起きる失敗点と、実装上の注意点を理解する
なぜツールを種類ごとに見るのか?
Section titled “なぜツールを種類ごとに見るのか?”「ツール」という言葉の範囲が広すぎるから
Section titled “「ツール」という言葉の範囲が広すぎるから”検索もツール、計算機もツール、データベース検索もツール、ファイルの読み書きもツールです。
これらを全部まとめて「1つの関数」と考えると、すぐに混乱します。
より実用的なのは、まず次のように分類することです。
- 検索系
- 計算系
- データアクセス系
- ファイル / 環境操作系
- 外部サービス呼び出し系
なぜ分類が役立つのか?
Section titled “なぜ分類が役立つのか?”種類ごとに注目点が違うからです。
- 検索系は召喚率ではなく、検索結果の質を見る
- 計算系は正確さと安全性を見る
- データベース系は権限と絞り込みを見る
- ファイル系はパスの境界を見る
- 外部サービス系はタイムアウトとリトライを見る
つまり、
どれも「ツール」ではあるけれど、実装上のリスクはまったく同じではない
ということです。
よくある5種類のツール
Section titled “よくある5種類のツール”検索 / 検索取得系
Section titled “検索 / 検索取得系”向いている用途:
- ドキュメントを探す
- ナレッジベースを検索する
- Webページを探す
特徴:
- 入力は通常 クエリ
- 出力は通常、候補の一覧
向いている用途:
- 四則演算
- 統計指標の計算
- 小さなデータ変換
特徴:
- 出力は安定して正確である必要がある
- 安全性に特に注意する必要がある
データアクセス系
Section titled “データアクセス系”向いている用途:
- データベースを検索する
- 注文情報を確認する
- ユーザー状態を確認する
特徴:
- パラメータと権限が最重要
- 多くの業務ロジックがこの層で決まる
ファイル / 環境操作系
Section titled “ファイル / 環境操作系”向いている用途:
- ファイルを読む
- ファイルを書く
- ディレクトリを列挙する
- コードを実行する
特徴:
- リスクが高い
- 境界管理が非常に重要
外部サービス呼び出し系
Section titled “外部サービス呼び出し系”向いている用途:
- メールを送る
- 外部の API を呼ぶ
- チケットを作成する
特徴:
- 失敗、タイムアウト、リトライがよく発生する
統一されたツール登録表
Section titled “統一されたツール登録表”実際のシステムでは、ツールをあちこちに散らすのではなく、まとめて登録することがよくあります。
import astimport operator
OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv,}
def safe_calculate(expression): def visit(node): if isinstance(node, ast.Expression): return visit(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](visit(node.left), visit(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -visit(node.operand) raise ValueError("unsupported_expression")
return visit(ast.parse(expression, mode="eval"))
def search_docs(keyword): docs = { "返金": "コース購入後 7 日以内に返金申請ができます", "証明書": "プロジェクトを完了し、テストに合格すると証明書を取得できます" } return docs.get(keyword, "関連ドキュメントが見つかりませんでした")
def calculator(expression): return safe_calculate(expression)
def get_user_status(user_id): mock_db = { 1: {"name": "Alice", "progress": 0.15}, 2: {"name": "Bob", "progress": 0.35} } return mock_db.get(user_id, {"error": "user_not_found"})
TOOLS = { "search_docs": search_docs, "calculator": calculator, "get_user_status": get_user_status}
print(TOOLS.keys())期待される出力:
dict_keys(['search_docs', 'calculator', 'get_user_status'])なぜ統一登録が重要なのか?
Section titled “なぜ統一登録が重要なのか?”後で次のようなことが必要になるからです。
- スキーマ を統一して説明する
- 権限管理をまとめて行う
- ログを統一して取る
- ディスパッチと集計を一元化する
ツールに登録表がないと、システムはどんどん保守しづらくなります。
統一ディスパッチャ
Section titled “統一ディスパッチャ”最小ディスパッチャ例
Section titled “最小ディスパッチャ例”def dispatch(call): name = call["name"] arguments = call["arguments"]
if name not in TOOLS: return {"error": "unknown_tool"}
try: result = TOOLS[name](**arguments) return {"result": result} except Exception as e: return {"error": str(e)}
calls = [ {"name": "search_docs", "arguments": {"keyword": "返金"}}, {"name": "calculator", "arguments": {"expression": "12 * 7"}}, {"name": "get_user_status", "arguments": {"user_id": 1}}]
for call in calls: print(call, "->", dispatch(call))期待される出力:
{'name': 'search_docs', 'arguments': {'keyword': '返金'}} -> {'result': 'コース購入後 7 日以内に返金申請ができます'}{'name': 'calculator', 'arguments': {'expression': '12 * 7'}} -> {'result': 84}{'name': 'get_user_status', 'arguments': {'user_id': 1}} -> {'result': {'name': 'Alice', 'progress': 0.15}}このコードで分かること
Section titled “このコードで分かること”このコードが教えてくれるのは、次の点です。
- 異なるツールでも、同じ呼び出し口を共有できる
- プログラム側でエラー処理をまとめられる
- 後からツールを増やしても構造が崩れにくい
種類ごとに何へ注意すべきか?
Section titled “種類ごとに何へ注意すべきか?”検索系ツール
Section titled “検索系ツール”重点ポイント:
- クエリ を書き換える必要があるか
- 何件返すか
- 結果を rerank する必要があるか
計算系ツール
Section titled “計算系ツール”重点ポイント:
- 安全性
- 精度
- 式が正しいかどうか
安全な計算機の簡単な例:
import astimport operator
OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv,}
def safe_calculate(expression): def visit(node): if isinstance(node, ast.Expression): return visit(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in OPS: return OPS[type(node.op)](visit(node.left), visit(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -visit(node.operand) raise ValueError("unsupported_expression")
return visit(ast.parse(expression, mode="eval"))
def safe_calculator(expression): allowed = set("0123456789+-*/(). ") if not set(expression) <= allowed: return {"error": "invalid_expression"} return {"result": safe_calculate(expression)}
print(safe_calculator("3 * (4 + 5)"))print(safe_calculator("__import__('os').system('rm -rf /')"))期待される出力:
{'result': 27}{'error': 'invalid_expression'}データベース系ツール
Section titled “データベース系ツール”重点ポイント:
- 権限
- パラメータの完全性
- クエリの境界
たとえば、モデルに任意の SQL を自由に生成させて、そのまま実行するのは避けるべきです。
ファイル系ツール
Section titled “ファイル系ツール”重点ポイント:
- パスのホワイトリスト
- 書き込み権限
- 人の確認が必要かどうか
外部サービス系ツール
Section titled “外部サービス系ツール”重点ポイント:
- タイムアウト
- リトライ
- 冪等性
Agent らしいツール組み合わせの例
Section titled “Agent らしいツール組み合わせの例”シナリオ:ユーザーが返金できるか判断する
Section titled “シナリオ:ユーザーが返金できるか判断する”この処理には、次の 2 つのツールが必要かもしれません。
- ユーザーの学習進捗を確認する
- 返金ポリシーを確認する
def refund_eligibility_agent(user_id): status = get_user_status(user_id) if "error" in status: return {"error": "ユーザーが存在しません"}
policy = search_docs("返金") progress = status["progress"]
can_refund = progress < 0.2 return { "user": status["name"], "progress": progress, "policy": policy, "can_refund": can_refund }
print(refund_eligibility_agent(1))print(refund_eligibility_agent(2))期待される出力:
{'user': 'Alice', 'progress': 0.15, 'policy': 'コース購入後 7 日以内に返金申請ができます', 'can_refund': True}{'user': 'Bob', 'progress': 0.35, 'policy': 'コース購入後 7 日以内に返金申請ができます', 'can_refund': False}
このコードが本当に示していること
Section titled “このコードが本当に示していること”このコードが示しているのは、
ツール統合とは、各ツールを単独で置くことではなく、複数のツールを協力させて 1 つの目的を達成すること
という点です。
だからこそ、今後の Agent はツールのオーケストレーション能力にますます依存していきます。
ツール統合でよくある失敗点
Section titled “ツール統合でよくある失敗点”スキーマ が一致しない
Section titled “スキーマ が一致しない”たとえば:
- ツールは
user_idを必要とする - でもモデルは
idを送ってしまう
戻り値の形式が統一されていない
Section titled “戻り値の形式が統一されていない”あるツールは文字列、別のツールは dict、さらに別のツールは list を返すと、システムはだんだん接続しづらくなります。
エラー処理が統一されていない
Section titled “エラー処理が統一されていない”あるツールは None を返し、別のツールは例外を投げ、さらに別のツールは "failed" を返す。
これでは後続の処理がすぐに崩れます。
ログと再現手段がない
Section titled “ログと再現手段がない”本番で問題が起きたとき、どの種類のツールに問題があったのか分かりません。
実用的な提案:ツールの戻り値形式を統一する
Section titled “実用的な提案:ツールの戻り値形式を統一する”もっとも安定しやすい方法の1つは、ツールの出力構造を統一することです。たとえば、すべて次の形式にそろえます。
{ "ok": True, "data": ...}または:
{ "ok": False, "error": ...}簡単な例:
def wrapped_search(keyword): try: result = search_docs(keyword) return {"ok": True, "data": result} except Exception as e: return {"ok": False, "error": str(e)}
print(wrapped_search("返金"))期待される出力:
{'ok': True, 'data': 'コース購入後 7 日以内に返金申請ができます'}こうしておくと、後の Agent 層で統一的に判定しやすくなります。
初心者がよくつまずくポイント
Section titled “初心者がよくつまずくポイント”とりあえず全部のツールをつなぐ
Section titled “とりあえず全部のツールをつなぐ”ツールが増えるほど、システムは複雑になります。
より安定したやり方は、
- まず本当に必要な 2〜3 個だけつなぐ
ことです。
高リスクなツールと低リスクなツールを区別しない
Section titled “高リスクなツールと低リスクなツールを区別しない”ファイル削除、支払い操作、データベース書き込みは、ドキュメント検索とは危険度がまったく違います。
ツール API の約束事が統一されていない
Section titled “ツール API の約束事が統一されていない”これは、Agent システムがだんだん混乱していく大きな原因の1つです。
このページを終えたら、この証拠カードを残します。
- ツール契約
- 名前、説明、入力スキーマ、出力スキーマ
- 権限
- ツールが読み取りまたは変更を許可されている範囲
- 呼び出しトレース
- 引数、結果、エラー、再試行、またはフォールバック
- 失敗確認
- 間違ったツール、不適切な引数、危険な操作、または観測不足
- 安全対策
- 検証、確認、サンドボックス化、レート制限、またはロールバック
この節で一番大切なのは、「どんなツールがあるか」を覚えることではなく、次の点を理解することです。
よく使うツール統合のポイントは、ツールを接続することだけではなく、それらを統一されたインターフェース、統一されたエラー処理、統一された境界制御でまとめることにある。
こうして初めて、ツール層は Agent の能力を広げる存在になり、障害を増やす存在にはなりません。
- この節のツール登録表に
get_weather(city)ツールを追加してください。 - すべてのツールの戻り値を
{"ok": ..., "data": ..., "error": ...}の形式に統一してください。 - 考えてみましょう。なぜデータベース書き込みツールと検索ツールを同じ権限レベルにしてはいけないのでしょうか?
- 自分の言葉で説明してください。なぜツール登録表と統一ディスパッチャが Agent 実装でとても重要な 2 つの構造だと言えるのでしょうか?
参考実装と解説
get_weather(city)は registry に入れ、schema、risk level、timeout、正規化された戻り値形式を持たせます。{ok, data, error}に統一すると下流ロジックが単純になります。成功時はdataを読み、失敗時は自然言語を解析せずerrorで分岐できます。- データベース書き込みツールは記録を変更するため、検索ツールより強い権限、確認、rollback ルールが必要です。
- registry はツールメタデータの単一の参照元です。dispatcher は validation、permission check、retry、logging、error handling を集中管理します。