最近、AIエージェントを触っていて、こんな経験ありませんか。
モデルをもっと賢いものに変えた。プロンプトも何度も書き直した。なのに、エージェントは相変わらず不安定。変なツールを呼んだり、引数を間違えたり、ツールの返事を読み違えて変な方向に進んでいったり。
正直に言うと、僕も最初は「モデルが悪いんかな」「プロンプトが下手なんかな」と思っていました。でも、いろんな失敗を観察していくうちに、だんだん見え方が変わってきたんです。
犯人は、モデルの賢さでもプロンプトでもなくて、AIに渡している「道具棚」の設計が雑だっただけ、というケースがすごく多い。
今日は、その「道具棚」——つまり Function Calling(ツール)の設計 について、AI開発にまだ不慣れな方でも分かるように、でも仕事で明日から使える粒度まで、じっくり書いていきます。コード例もプロンプト例も厚めに入れました。
そもそも「Function Calling」「ツール」って何の話?
まずここから。専門用語っぽいので、ひとことで噛み砕きます。
Function Calling(関数呼び出し)というのは、ざっくり言うと「AIが、外の世界に手を伸ばすための唯一の口」のことです。
LLM(ChatGPTやClaudeみたいな大規模言語モデル)って、そのままだと文章を返すことしかできません。データベースを見ることも、ファイルを消すことも、メールを送ることもできない。脳みそはあるけど、手がない状態。
そこで人間側が、「これ使っていいよ」とあらかじめ道具(=関数)を何個か渡しておきます。たとえば get_order(注文を調べる) とか send_email(メールを送る) とか。AIは「今この道具を、この引数で使いたい」とリクエストを出し、実際の処理はこっちのプログラムが実行して、結果をAIに返す。この一連の仕組みが Function Calling、渡す道具のことを ツール(Tool) と呼びます。
ここで大事なのは、こういう感覚です。
AIにとってツールは、料理人にとっての包丁やまな板みたいなもの。
どんなに腕のいい料理人でも、切れない包丁とぐらぐらのまな板を渡されたら、いい仕事はできない。逆に、道具がちゃんとしていれば、そこそこの料理人でも安定したアウトプットを出せる。AIエージェントもまったく同じなんですよね。
AIが道具を使いこなせないとき、だいたいこの3つで失敗している
2026年に入って、ツール利用(Tool Use)の失敗パターンはかなり研究で分類が進みました。難しい話は抜きにして、現場でよく起きる失敗を3つに絞ると、こうです。
- 間違った道具を選ぶ — 注文を「確認するだけ」でいいのに、いきなり「返金する」ツールを呼んでしまう
- 引数を間違える — 必要な項目を埋めなかったり、ありえない値(マイナスの金額とか)を渡したりする
- 道具の返事を読み違える — エラーが返ってきているのに成功したと勘違いして、次の処理に進んでしまう
しかも厄介なのが、ツール呼び出しって 引数がひとつズレただけで、下流の結果が大きく狂う という性質があること。文章なら多少ブレても読めますが、amount: 1000 が amount: 100000 になったら、それは事故です。
ここでつい、「じゃあプロンプトでもっと細かく指示すればいいんだ」と考えがちなんですが……ちょっと待ってください。そこが今日いちばん伝えたいポイントなんです。
直し方の「主語」を、プロンプトから道具に変える
多くの人は、AIが失敗すると プロンプトを直そうとします。 「もっと丁寧に指示しよう」「気をつけてって書こう」と。
でも、考えてみてほしいんです。包丁が切れなくて料理が失敗したとき、料理人に「次はもっと気をつけて切ってね」と口で言い続けるのと、包丁そのものを研ぐのと、どっちが効くか。
AIエージェントの安定化って、実は 「プロンプトエンジニアリング」より「道具側の設計」で決まる部分がめちゃくちゃ大きい んですよね。これは最近よく言われる Context Engineering(文脈の設計) の、いちばん実装に近い部分だと僕は捉えています。AIに渡す道具の形・説明・制約そのものが、AIの判断材料(=文脈)になっているからです。
ここから先は、その「道具の設計」を具体的に5つの原則に分けて、コードつきで見ていきます。全部、今日から1個ずつ試せるものです。
ツール設計の5原則
原則1:1つの道具に、1つの仕事だけ持たせる
いちばんやりがちなのが、便利だからと「なんでもできる万能ツール」を作ってしまうこと。
{
"name": "manage_order",
"description": "注文を管理する(取得・更新・キャンセル・返金)",
"input_schema": {
"type": "object",
"properties": {
"action": { "type": "string" },
"data": { "type": "string" }
}
}
}
一見スマートですが、これはAIにとって地獄です。action に何を入れればいいのか、data の形式は何なのか、AIは毎回推測することになる。しかも「取得(読むだけ)」と「返金(お金が動く)」が同じ道具に同居しているので、読むだけのつもりが誤って返金を呼ぶ 事故が起きやすい。
2026年のベストプラクティスは、はっきりしています。1つの万能ツールに read・write・delete を詰め込まず、責務ごとに分割する。 こうすると、AIの判断面(選択肢の広さ)が狭くなって迷いにくくなるし、ログも追いやすくなるし、「この危ない操作だけ承認制にする」みたいな制御もかけやすくなります。
道具棚の整理整頓は、こんなイメージです。
-
get_order… 注文を取得する(読むだけ・安全) -
update_shipping_address… 配送先だけ変更する(書くけど低リスク) -
refund_payment… 返金する(お金が動く・高リスク)
特に 「読む道具」と「書く道具」を分ける のは効果が大きいです。読むだけの道具は気軽に何度呼んでもいいけど、書く道具は慎重に。この線引きが道具のレベルで効いてきます。
原則2:スキーマで「言わせない・間違えさせない」
スキーマというのは、その道具が受け取る引数の「型」や「ルール」を定義したものです。ここをゆるく作ると、AIは自由すぎて間違えます。逆に ガチガチに縛ると、そもそも間違った値を作れなくなる。
さっきの manage_order を、refund_payment 単体として、スキーマをしっかり書き直すとこうなります。
{
"name": "refund_payment",
"description": "確定済みの注文に返金する。明確な返金指示があるときだけ使う。内容の確認だけなら get_order を使い、このツールは使わない。",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^ord_[a-zA-Z0-9]+$",
"description": "返金対象の注文ID。get_order で取得した値をそのまま渡す"
},
"amount": {
"type": "integer",
"minimum": 1,
"description": "返金額(円)。注文の支払額以下であること"
},
"reason": {
"type": "string",
"enum": ["customer_request", "defective", "duplicate_charge"],
"description": "返金理由。3種類から必ず1つ選ぶ"
}
},
"required": ["order_id", "amount", "reason"],
"additionalProperties": false
}
}
ポイントを噛み砕きます。
-
pattern… IDの形を正規表現で固定。それっぽい嘘のIDを作れなくする -
minimum… 金額は1以上の整数。マイナスや0、小数を弾く -
enum… 理由は3択だけ。自由記述させず、選ばせる -
required… 必須項目を明示。埋め忘れを防ぐ -
additionalProperties: false… 余計な項目を勝手に足させない
ちなみに、この input_schema の形は、たとえばAnthropicのMessages APIで tools として渡すスキーマとそのまま同じ構造です。スキーマは「AIに守らせる契約書」 だと思ってください。契約が曖昧だと、AIは曖昧に振る舞う。契約がカチッとしていれば、AIもカチッと動きます。
原則3:descriptionは「人間向けの説明」じゃなく「AIへの取扱説明書」
ここ、意外と見落とされがちなんですが、めちゃくちゃ大事です。
ツールの description(説明文)を、人間用のドキュメントみたいに「注文を返金する」とだけ書いて終わりにしていませんか。AIはこの説明文を読んで「いつこの道具を使うか」を判断します。つまり description は、AIにとっての取扱説明書そのもの なんです。
良い取扱説明書には、最低この4つを入れます。
- その道具が何をするか(副作用、つまり「何が変わるか」も書く)
- 使うべき状況
- 使ってはいけない状況と、その代わりに使う道具
- 各引数の意味と制約(できれば例つき)
特に3番、「使わない時」を書くのが効きます。さっきのスキーマでも、わざわざこう書きました。
内容の確認だけなら get_order を使い、このツールは使わない。
これ1行があるだけで、「ちょっと注文を見たいだけ」のときに返金ツールを誤発火する事故が、ぐっと減ります。人間の新人さんに道具を渡すときと一緒ですよね。「これは○○のときに使う。△△のときは絶対使わずに、隣のこっちを使ってね」って添えるだけで、事故が減る。AIにも同じ親切をしてあげる、というだけの話です。
原則4:エラーは「AIが次の一手を打てる」形で返す
道具がエラーを返すこと自体は、悪いことじゃありません。問題は その返し方 です。
よくあるダメな返し方は、プログラムの内部エラー(スタックトレース)をそのままAIに投げること。人間でも読みたくないあれを渡されても、AIは「で、次どうすればいいの?」が分からず、同じ失敗を繰り返したり、無限ループに入ったりします。
目指すのは、「AIがそのエラーを読んで、自分で次の正しい一手を打てる」エラー です。具体的には、機械可読な error コードと、次にどうすべきかの hint をセットで返す。そして何より、副作用を起こす前に、自分で入力を検証する。 AIの引数を鵜呑みにしないことです。
Pythonで書くとこんな感じになります。
from typing import Any
def refund_payment(args: dict[str, Any]) -> dict[str, Any]:
# 1) まず自分で入力を検証する(AIの引数を信用しない)
order_id = args.get("order_id")
amount = args.get("amount")
if not isinstance(order_id, str) or not order_id.startswith("ord_"):
return {
"ok": False,
"error": "INVALID_ORDER_ID",
"hint": "order_id は 'ord_' で始まる文字列です。先に get_order で注文を確認してください。",
}
if not isinstance(amount, int) or amount <= 0:
return {
"ok": False,
"error": "INVALID_AMOUNT",
"hint": "amount は1以上の整数(円)で渡してください。",
}
order = db_find_order(order_id)
if order is None:
return {
"ok": False,
"error": "ORDER_NOT_FOUND",
"hint": f"{order_id} は存在しません。list_orders で有効なIDを確認してください。",
}
if amount > order["paid_amount"]:
return {
"ok": False,
"error": "AMOUNT_TOO_LARGE",
"hint": f"返金できるのは {order['paid_amount']} 円までです。",
}
# 2) 実行はいちばん最後。ここまで来て初めて副作用を起こす
refund = payment_gateway.refund(order_id, amount)
return {"ok": True, "refund_id": refund.id, "refunded": amount}
このコードのキモは2つです。
ひとつは、実行(返金)を関数のいちばん最後に置いていること。 それより前は全部チェックです。お金を動かす前に、おかしな入力を全部はじいている。もうひとつは、エラーの hint が 「次にどの道具を使えばいいか」まで教えていること。 たとえば「先に get_order で確認してください」と書いてあれば、AIはちゃんとその通りに動き直してくれます。エラーが、ダメ出しじゃなくて道案内になっているわけです。
原則5:危ない操作には「最小権限・確認ゲート・空打ち・冪等」を仕込む
最後は、いちばん大事な安全の話です。
2026年にいちばん多い設定ミスは何かというと、エージェントが、起動した人と同じ強い権限で動いてしまっている ことだそうです。ファイル書き込み、ネット送信、コマンド実行、データベースの管理者権限まで、本当はその仕事に要らないのに全部持っている。これは事故が起きたときの被害が大きすぎます。
なので、道具を設計するときは、この4つを意識します。
- 最小権限(least privilege) … その道具に必要な権限だけ与える。削除ツールに送金権限はいらない
- 確認ゲート … 削除・送金・公開みたいな取り返しのつかない操作は、実行前に人間のOKを挟む
- 空打ち(dry-run) … 「実際にやらずに、何が起きるかだけ見せる」モードを用意する
- 冪等(べきとう) … 同じ操作を2回呼んでも、結果が二重にならない作りにする(同じ注文を二重返金しない、など)
「冪等」は聞き慣れない言葉かもしれません。「何回押しても1回分の結果にしかならない」性質 のことです。エレベーターのボタンを連打しても1回しか呼ばれないのと同じ。AIはたまに同じツールを連続で呼ぶので、これがないと二重実行の事故になります。
確認ゲートを入れた例を、TypeScript(zodというライブラリで入力検証)で書いてみます。
import { z } from "zod";
// 入力スキーマ=AIに守らせる契約
const DeleteFileInput = z.object({
path: z.string().regex(/^\/workspace\//, "操作できるのは /workspace/ 配下だけです"),
reason: z.string().min(1, "削除理由は必須です"),
});
type Ctx = { confirm: (msg: string) => Promise<boolean> };
export async function deleteFile(rawArgs: unknown, ctx: Ctx) {
// 1) パースに失敗したら、実行せずヒントを返す
const parsed = DeleteFileInput.safeParse(rawArgs);
if (!parsed.success) {
return { ok: false, error: "INVALID_INPUT", hint: parsed.error.issues[0].message };
}
const { path, reason } = parsed.data;
// 2) 破壊的操作は人間の確認ゲートを通す(最小権限の発想)
const approved = await ctx.confirm(`${path} を削除します。理由: ${reason}。実行しますか?`);
if (!approved) {
return { ok: false, error: "REJECTED_BY_HUMAN", hint: "ユーザーが削除を承認しませんでした。別の方法を検討してください。" };
}
await fs.rm(path);
return { ok: true, deleted: path };
}
path を /workspace/ 配下だけに正規表現で縛っているのがポイントです。これで、AIが間違って /etc/ みたいな大事な場所を消そうとしても、道具のレベルで物理的に弾けます。プロンプトで「危ないファイルは消さないでね」とお願いするより、そもそも消せないように道具を作る ほうが、何十倍も確実なんですよね。
そのまま使える、ツール設計レビュー用プロンプト3本
ここまでの原則を、自分のプロジェクトに当てはめるためのプロンプトを3つ置いておきます。AIに自分の道具棚をレビューさせる、という使い方です。
プロンプト1:道具の棚卸し
あなたはAIエージェントのツール設計レビュアーです。
以下のツール一覧を、次の観点で棚卸ししてください。
- 1つのツールが複数の責務(readとwrite、複数の操作)を抱えていないか
- read系とwrite系が分離されているか
- 破壊的操作(削除・送金・公開・上書き)はどれか
出力は表形式で「ツール名 / 主な責務 / 分割案 / 危険度(低中高) / 必要な権限」。
# ツール一覧
(ここに今のツール定義を貼る)
プロンプト2:説明文(description)の書き直し
次のツールのdescriptionを、AIが「いつ使い・いつ使わないか」を迷わず判断できる
取扱説明書に書き直してください。必ず含めるもの:
1. このツールが何をするか(副作用=何が変わるかも明記)
2. 使うべき状況
3. 使ってはいけない状況と、代わりに使うべきツール名
4. 各引数の意味と制約(できれば例つき)
人間向けの曖昧な説明ではなく、モデルが迷わない具体的な文にしてください。
# 対象ツール
(nameと現状のdescription、input_schemaを貼る)
プロンプト3:エラー設計のレビュー
次のツール実装のエラーハンドリングをレビューしてください。
判定基準は「AIがこのエラーを受け取って、次の正しい一手を自分で打てるか」です。
- スタックトレースや内部例外をそのまま返していないか
- error(機械可読なコード)とhint(次にどうすべきか)が揃っているか
- 副作用を起こす前に、無効な入力を検証して弾いているか
改善版のコードと、改善した理由を箇条書きで出してください。
# 対象コード
(ここに実装を貼る)
この3本を順番に回すだけで、自分の道具棚がだいぶ整います。AIに自分の道具を点検させる、というのは、なかなか気持ちのいい使い方です。
人間とAI、それぞれどこを持つのか
この記事の核心は、「AIが賢くなれば全部解決する」じゃないところにあります。道具を設計するのは、最後まで人間の仕事 なんです。役割を整理しておきます。
| 工程 | AIに任せること | 人間が設計・判断すること |
|---|---|---|
| ツールの実行 | 引数を組み立てて呼び出す | どの道具を棚に並べるか |
| 入力 | スキーマに沿った値を生成する | スキーマ(型・必須・enum・範囲)を定義する |
| ツール選択 | 状況に応じて選ぶ | descriptionに使う/使わない条件を書く |
| エラー回復 | hintを読んで次の手を打つ | errorとhintの形式を設計する |
| 権限 | 与えられた範囲だけで動く | 最小権限とread/write分離を決める |
| 破壊的操作 | 必要性を提案する | 確認ゲートの有無を決め、承認/却下する |
| 全体の品質 | 動いてログを残す | ログを読み、道具棚を改善し続ける |
こうやって並べると、はっきりします。AIに任せるのは「道具を使うこと」。人間が持つのは「道具を設計し、危ないところに鍵をかけ、棚を整え続けること」。AI時代に強いエンジニアって、賢いプロンプトを書く人じゃなくて、AIが安全に使える道具棚を設計できる人 なのかもしれません。
明日からの4ステップ
大きく構えなくて大丈夫です。小さく1個ずつでいい。
- 今あるツール(or これから作る予定のツール)を全部書き出して、「読む系・書く系・壊す系」で色分けする
- いちばん危ない道具を1つだけ選んで、スキーマをガチガチに(必須・enum・範囲)する。descriptionに「使わない時」を1行足す
- その道具のエラーを、
errorとhintの形に書き換える。実行の前に入力を検証する数行を足す - 取り返しのつかない操作に、確認ゲートを1個だけ付けてみる
これだけで、エージェントの安定感はかなり変わってきます。モデルを乗り換えなくても、今日の自分の手で直せる。そこがいいところです。
おわりに — 道具棚を整えるのは、明日の自分とAIへのプレゼント
最後に、ちょっとだけ僕の感じていることを。
道具棚をちゃんと設計するのって、正直、地味な作業です。スキーマを縛って、説明文を直して、エラーを書き換えて、確認ゲートを足して……。派手さはない。でも、これをやっておくと、未来がだいぶ楽になるんですよね。
僕は何かを設計するとき、いつも「明日の自分が、今日の自分にあざっすって言ってくれるかな」と問いかけるようにしています。雑な道具棚を放置したら、明日の自分(とAI)が事故処理に追われる。逆に、今日ちょっと手間をかけて道具を整えておけば、明日の自分は安心してAIに手を動かしてもらえる。
道具の設計って、未来の自分とAIへのプレゼントを、今日のうちに用意しておく作業 なんだと思います。しかも、一度ちゃんと作った道具は、使い捨てのプロンプトと違って、ずっと再利用できる資産になる。書いたぶんだけ積み上がっていく。
AIがどんどん賢くなっていくこれからの時代、僕らの仕事は「AIの代わりに手を動かすこと」から、「AIが安全に手を動かせる場所を設計すること」へ、静かに移っていくのかなと感じています。
その第一歩として、まずは自分の道具棚をひとつ、整えてみる。今日のその小さなひと手間に、きっと明日の自分が「あざっす」って言ってくれるはずです。
ここまで読んでくださって、ありがとうございました。