付録C ツール定義(JSON Schema)の書き方ミニ辞典
📖 この付録のゴール:道具(tool)を渡すときの
input_schema(=引数の設計図) を、こわがらずに書けるようになる。「どんな形の引数を、どう説明すればLLMが正しく道具を使ってくれるか」を、本編の道具(read_file/write_file/run_command)を題材に、1行ずつ読み解きます。 ← 目次・はじめにへもどる
本編の第3章で、道具はこんな形で渡しました。
const tools: Anthropic.Tool[] = [{
name: "get_current_time",
description: "今の日時を返す。引数は不要。",
input_schema: {
type: "object",
properties: {},
required: [],
},
}];
この input_schema の部分が、今回の主役です。「引数(ひきすう)=道具に渡す材料」の形を、機械が読める言葉で書いたもので、その言葉が JSON Schema(ジェイソン・スキーマ) です。
🍳 たとえ:道具は「電話の向こうの天才シェフ(LLM)」に渡すレシピカードです。
name=道具の名前、description=使い方の説明、input_schema=「材料はこの形でちょうだいね」という注文票。注文票がちゃんとしているほど、シェフは間違った材料を送ってこなくなります。
① いちばん基本:3点セット 🟢
JSON Schema は見た目はゴツいですが、最初に覚えるのはたった3つだけです。
input_schema: {
type: "object",
properties: {
path: { type: "string", description: "読みたいファイルのパス" },
},
required: ["path"],
}
1行ずつ読むと:
type: "object"… 引数全体は 「名前つきの箱の集まり」 ですよ、という宣言。道具の引数はほぼ必ずこれで始まります(材料をpathのような名札つきで渡すから)。properties: { ... }… その箱の中にどんな名札の材料が入るかを並べる場所。ここにpathを1つ定義しました。path: { type: "string", ... }…pathという材料は 文字列(string) ですよ、という指定。description: "..."… その材料が何なのかの説明(後で詳しく。これがいちばん大事)。required: ["path"]… 絶対に欠かせない材料の名札リスト。pathが無いとこの道具は動かない、という意味。
💡 おぼえ方:
typeで「箱」と言って、propertiesで「中身の名札と型」を並べて、requiredで「絶対いる名札」を挙げる。この3点セットがJSON Schemaの土台です。
引数がまったく要らない道具(さっきの get_current_time など)は、properties を空の {}、required を空の [] にします。「箱はあるけど、中に入れる材料はゼロ」という意味です。
② 材料の「型」いろいろ 🟢
材料(プロパティ)には 型(かた) =「どんな種類のデータか」を指定します。よく使うのは次の6つです。
| 型の書き方 | 意味 | 例 |
|---|---|---|
"string" |
文字列 | "README.md"、"こんにちは" |
"number" |
数(小数もOK) | 3.14、42 |
"integer" |
整数(小数はダメ) | 1、100 |
"boolean" |
はい/いいえ(真偽) | true / false |
"array" |
並び(リスト) | ["a", "b", "c"] |
"object" |
名前つきの箱(入れ子) | { city: "東京" } |
書き方はどれも { type: "型の名前", description: "説明" } の形でそろっています。
properties: {
path: { type: "string", description: "ファイルのパス" },
max_lines: { type: "integer", description: "読み込む最大行数" },
overwrite: { type: "boolean", description: "既存ファイルを上書きしてよいか" },
}
1行ずつ読むと:
path… 文字列。ファイルの場所を文字でもらう。max_lines… 整数(integer)。行数は「2.5行」みたいな小数だと困るのでnumberではなくintegerを選びます。「ここは整数しか来ないでほしい」をスキーマで伝えられるのがポイント。overwrite… 真偽(boolean)。true(上書きする)かfalse(しない)の二択。
🔧
numberとintegerの違い:numberは小数も整数も許す広い型、integerは整数だけ。「個数」「行数」「ページ番号」のように小数があり得ない材料はintegerにしておくと、シェフが1.5のような変な値を送りにくくなります。
③ いちばん大事:description はLLMへの指示そのもの 🟢
ここが付録Cでいちばん覚えてほしいところです。
🗣
nameとdescriptionは、ただのメモではありません。LLMがそれを読んで「いつ・どの道具を・どんな引数で使うか」を決める“指示文”そのものです。
LLMはあなたのコードの中身を見ていません。見えているのは name と description の文章だけ。だから——
- 道具名(
name)が曖昧 → LLMが「どの道具を使えばいいか」を間違える - 引数の
descriptionが雑 → LLMが変な値を入れてくる、または必要な道具を呼ばない
逆に言えば、説明をていねいに書くほど、道具は正しく使われます。プロンプトを書くのと同じ気持ちで、説明文を書いてください。
よい例 / わるい例
同じ「ファイルを読む道具」でも、説明しだいで賢さが変わります。
// 😖 わるい例:曖昧で、何を渡せばいいか分からない
{
name: "read",
description: "読む",
input_schema: {
type: "object",
properties: { p: { type: "string", description: "値" } },
required: ["p"],
},
}
// 😀 よい例:いつ使うか・何を渡すかが具体的
{
name: "read_file",
description:
"テキストファイルの中身を読んで返す。中身を確認・編集したいときに使う。" +
"引数 path には、作業フォルダからの相対パス(例: src/index.ts)を渡す。",
input_schema: {
type: "object",
properties: {
path: {
type: "string",
description: "読みたいファイルのパス。例: README.md, src/index.ts",
},
},
required: ["path"],
},
}
1行ずつ読むと(よい例):
name: "read_file"… 動詞+目的語で具体的。readだけだと「何を読む?」が伝わらないのでread_fileにします。descriptionの前半 … 「いつ使うか」(中身を確認・編集したいとき)を書く。LLMは“使いどころ”が分かると道具を取り違えません。descriptionの後半 … 「何を渡すか」(作業フォルダからの相対パス、例つき)を書く。例があると形を間違えにくい。pathのdescription… プロパティ側にも具体例を添える。description: "値"のような中身ゼロの説明は、無いのと同じです。
🍳 たとえ:シェフへの注文票に「材料:適量」とだけ書いたら、何がどれだけ来るか分かりません。「塩:小さじ1(粒の細かいもの)」のように具体的に書くほど、思いどおりの料理(道具の使われ方)になります。
💡 こつ:説明はLLM(読み手)目線で書く。「この道具はいつ使うべきで/いつ使うべきでないか」「引数はどんな形式・単位か」「禁止事項(例:作業フォルダの外は読まない)」まで書けると、誤用がぐっと減ります。
④ 選択肢を絞る enum・並びの array・入れ子 🔧
基本の3点セットに慣れたら、引数をもっと正確に縛れる道具を覚えましょう。
enum:選べる値を決まったものだけにする
「上書きモードは overwrite か append のどちらかしか許さない」——そんなときは enum(イーナム=選択肢)です。
properties: {
mode: {
type: "string",
enum: ["overwrite", "append"],
description: "書き込み方法。overwrite=丸ごと置換 / append=末尾に追記",
},
}
1行ずつ読むと:
type: "string"… この材料は文字列。enum: ["overwrite", "append"]… この2つ以外の文字列はダメ、という制限。LLMが"replace"のような勝手な値を作っても弾けます。description… それぞれの選択肢が何を意味するかを説明。enumだけだと言葉の意味までは伝わらないので、ここで補足します。
💡
enumはタイプミスや表記ゆれを防ぐ最強の道具。「決まった値しか来ないはず」の引数には積極的に使いましょう。
array:並び(リスト)には items で中身の型を書く
複数のファイルをまとめて渡したいときは array(配列=並び)。並びの中の1個1個がどんな型かを items で指定します。
properties: {
paths: {
type: "array",
items: { type: "string" },
description: "読みたいファイルのパスの一覧。例: ["a.ts", "b.ts"]",
},
}
1行ずつ読むと:
type: "array"… この材料は並び(リスト)ですよ、という宣言。items: { type: "string" }… その並びの中身は、ぜんぶ文字列ですよ、という指定。itemsを書き忘れると「中身が何の並びか」が伝わりません。description… 例つきで「ファイルのパスの一覧」と説明。
入れ子(ネスト):箱の中に箱を入れる
object の中にさらに object を入れて、まとまった材料を渡すこともできます。
properties: {
target: {
type: "object",
properties: {
path: { type: "string", description: "書き込み先のパス" },
content: { type: "string", description: "書き込む中身" },
},
required: ["path", "content"],
description: "書き込み対象(場所と中身のセット)",
},
}
1行ずつ読むと:
target: { type: "object", ... }…targetという材料がさらに箱になっている(入れ子)。- 中の
properties… その箱の中身(pathとcontent)を、いつもの3点セットで定義。 - 中の
required: ["path", "content"]… 箱の中でも「絶対いる材料」を指定できる。外側と同じルールが、入れ子の中でも効きます。
⚠️ 入れ子は深くしすぎないこと。1〜2段までが読みやすさの目安です。深くするほどLLMも人間も形を間違えやすくなります。本編の道具は基本フラット(入れ子なし)で十分です。
⑤ 🔧 strict tool use:形を「厳密」にする
ここまでのスキーマは「こういう形で来てね」というお願いでした。さらに一歩進めて、「この形以外は受け付けない」と厳しくしたいときは、strict tool use(ストリクト=厳密モード) を使います。
ねらいは2つ:①余計な材料を混ぜさせない/②必須の材料を必ず付けさせる。組み合わせは次の3点セットです。
{
name: "write_file",
description: "テキストファイルに中身を書き込む(上書き)。",
input_schema: {
type: "object",
properties: {
path: { type: "string", description: "書き込み先のパス" },
content: { type: "string", description: "書き込む中身(全文)" },
},
required: ["path", "content"],
additionalProperties: false,
},
strict: true,
}
1行ずつ読むと:
required: ["path", "content"]… すべての材料を必須にする。strict では「全部required」が基本ルールです(省略可の材料を作らない)。additionalProperties: false…propertiesに書いていない材料は禁止。LLMが勝手にmodeなどを足してきても弾かれます。strict: true… 上の2つとセットで、「スキーマぴったりの形しか通さない」を有効にするスイッチ。
🛡 背骨②(安全)とのつながり:strict は「LLMが余計な引数を紛れ込ませる」事故を入口で止めてくれます。とくに
write_fileやrun_commandのような危険な道具ほど、形を厳密に固めておくと安心です。ただしstrict: trueを使うと必須にできない(省略OKの)引数が作りにくくなるので、「全部必須でいい道具」から試すのがおすすめ。
💡 strict は「あれば便利な安全弁」。本編はまず strict 無しで動かして仕組みを理解し、慣れたら危険な道具から strict を足す、という順番でOKです。
⑥ まとめ:本編の道具を“スキーマ目線”で読む 🟢
最後に、本編で作る3つの道具のスキーマを並べて、型・必須・説明の決め方を振り返ります。
const tools: Anthropic.Tool[] = [
{
name: "read_file",
description: "テキストファイルの中身を読んで返す。中身を確認したいときに使う。",
input_schema: {
type: "object",
properties: {
path: { type: "string", description: "読みたいファイルのパス(作業フォルダからの相対パス)" },
},
required: ["path"],
},
},
{
name: "write_file",
description: "テキストファイルに中身を書き込む(上書き)。新規作成・修正に使う。",
input_schema: {
type: "object",
properties: {
path: { type: "string", description: "書き込み先のパス" },
content: { type: "string", description: "書き込む中身(ファイル全文)" },
},
required: ["path", "content"],
},
},
{
name: "run_command",
description: "シェルコマンドを実行して、その出力を返す。テスト実行やビルドに使う。",
input_schema: {
type: "object",
properties: {
command: { type: "string", description: "実行するコマンド。例: npm test, ls -la" },
},
required: ["command"],
},
},
];
1行ずつ読むと:
read_file… 材料はpath(文字列)1つ、必須。読むだけなので安全な道具。write_file… 材料はpathとcontent(どちらも文字列)、両方とも必須。中身を全文もらうのでcontentの説明に「ファイル全文」と明記します(危険:上書き)。run_command… 材料はcommand(文字列)1つ、必須。説明に使いどころ(テスト・ビルド)と例を入れて、何でもかんでも実行させない方向に誘導します(いちばん危険)。
🍳 たとえで総まとめ:JSON Schema は「シェフへの注文票」。
typeで材料の種類を、requiredで絶対いる材料を決め、descriptionでその材料が何かを“言葉で”伝える。そしてenum/array/strict は、注文票をより正確にする“追記”です。注文票がていねいなほど、道具は思いどおりに、安全に使われます。
📌 もっと知りたい人へ:JSON Schema には他にも
minimum/maximum(数の範囲)、minLength/maxLength(文字数)、pattern(正規表現で形を縛る)などがあります。ただし本編のCLIエージェントはここまでの基本でじゅうぶん。まずは「3点セット+ていねいなdescription」を体にしみこませましょう。