CodeGym /コース /ChatGPT Apps /指示から tools とメタデータの設計へ: ディスカバリとルーティング

指示から tools とメタデータの設計へ: ディスカバリとルーティング

ChatGPT Apps
レベル 5 , レッスン 4
使用可能

1. なぜ良い tools とメタデータがないと指示だけでは不十分なのか

不都合な事実を一つ押さえておきましょう。モデルはあなたのコードを見ていません。Next.js のどんなコントローラや、TypeScript のどんな関数、レコメンドサービスにどんな美しいヒューリスティクスを詰め込んだかを知りません。

モデルがあなたの App を見るのは次のいくつかのインターフェースを通してです。

  1. System‑prompt(ロールの契約)。
  2. ツールの説明: 名前、descriptioninputSchemaoutputSchema、アノテーションなど。
  3. アプリ自体のメタデータ: 名前、アイコン、短文/長文の説明、カテゴリ、conversation starters など。

リクエストの処理時にモデルは、対話コンテキストとこれらのメタデータを見て次を判断します。

  • そもそも何か App を提案すべきか;
  • 提案するなら — どの App か;
  • その App が選ばれたなら — 現在のリクエストに最も適したツールはどれか。

モジュール5の前回は、system‑prompt と UX の指示という、言葉でモデルに「語れる」部分を扱いました。今回はテキスト以外にモデルが見るもの、すなわち tools とメタデータに進みます。

したがってモジュール5の課題は実は二重です。まず system‑prompt で「この App は何をすべきか、どう振る舞うべきか」を定義し、その後、tools とメタデータの設計でモデルが実際に使える形にパッケージします — discovery とルーティングのためにも。

自分向けの要約としてはこうです。system‑prompt は憲法、tools とメタデータは法律と周辺の官僚制 — 申請フォームやデータベーススキーマ等 — に相当します。憲法だけでは前に進めません。

2. 分解: 「一つのタスク — 一つの tool」を賢く

最も悩ましいところから始めましょう。ツールはいくつ作るべきか、どう切り分けるべきか。

直感的な原則: 一つのツールに一つの分かりやすいタスク。これはモデルの選択を大いに楽にします。巨大で万能な do_everything ではなく、良い名前の複数のこざっぱりしたアクションに分けます。

GiftGenius では、例えば次のような基本ツールが考えられます。

  • profile_to_segments — 受取人の自由記述(年齢、興味、関係性、文脈)を "tech", "fitness", "gamer" のような正規化されたセグメントに変換する。
  • recommend_gifts — セグメント、予算、ロケール、機会に基づいてギフトの id リストを選定する。
  • get_gift — 選ばれたギフトの完全なカード(説明、メディア、SKU/バリエーション)を id で取得する。
  • (オプション)similar_gifts — 選ばれたギフトをもとに、さらに 3–5 件の類似案を提案する。

理屈の上では gift_tool 一つにして mode: "profile_to_segments" | "recommend" | "details" | "similar" のようなパラメータも可能ですが、そうするとあなたにもモデルにも負担が増えます。説明は長大になり、inputSchema は肥大化し、ツール選択時の明確なアンカーが減ります。

アンチパターン: God Tool

次のような構成を想像してください。

server.registerTool(
  "gift_tool",
  {
    description: "ギフトに関するさまざまな操作。",
    inputSchema: { /* 50 のフィールドとフラグ */ },
  },
  async ({ input }) => { /* mode による巨大な switch */ }
);

モデルの頭の中では「ギフトに関する抽象的なツールがある、詳細はあとでなんとかする」と映ります。これは選択の精度を悪化させ、ディスカバリを妨げ、保守も難しくします。

ただし、もう一方の極端 — 何でもかんでも超小粒の 50 ツールにする — も良くありません。ツールが増えるほどコンテキストに積まれ、モデルの注意を圧迫し、ルーティングの誤りリスクが増えます。ドキュメントでも明言されていますが、小さすぎるツールが多すぎるのは品質低下につながります。特に説明が重なると顕著です。

実務で使いやすいルール:

  • ユーザーがシナリオの「一歩」として認識するもの(例: プロファイルからの初回ギフト選定)は独立ツールの良い候補。
  • 常にそのステップの内部でのみ実行され、独立の意味を持たないもの(例: スコアリングの計算やカード閲覧のロギング)はツール実装内部に留める。

この原則でシナリオを 2–4 個のツールに切り分けたとしましょう。次の重要な問いは、モデルが推測なしに使えるようにこれらのツールの入力をどう記述するかです。そこから始めます。

3. ユースケースを Input Schema に写像する

具体的なユースケースを一つ取り、ツールに本当に必要なデータは何かを正直に見極めます。

シナリオ例: 「締切間際の贈り主: 25 歳の友人向けに 5–7 個のアイデアを。サッカーとボードゲームが好き、予算は最大 50 ドル」。

jobs‑to‑be‑done から明らかなのは、GiftGenius のレコメンド中核の仕事は選択肢を小さなリストに絞り、「変なものを選んでしまうかも」という不安を減らすことです。チャットでのやり取りレベルではアシスタントに必要なのは次です。

  • 受取人の基本情報(年齢、性別、贈り主との関係);
  • 興味・趣味;
  • 予算と通貨;
  • 機会(誕生日、記念日、新年など);
  • オプション — 配送フィルタ用の国/都市。

GiftGenius のアーキテクチャではこれは二段階に分かれます。

  1. profile_to_segments(input) は「生の」データ(年齢、興味、自由記述)を受け取り、後続で扱いやすい正規化セグメントへ変換します。
  2. recommend_gifts(segments, budget, locale, occasion) はセグメントと予算に基づき、カタログから具体的なギフト id を選定します。

ChatGPT ↔ MCP の契約という観点では、特に第二段階 — recommend_gifts のスキーマをちゃんと記載することが重要です。というのも、最も多くの選定シナリオでこのツールが使われるためです。

同時に、最初からユーザーに全てを要求する必要はありません。モデルは follow‑up で一部を補えます(「おおよその予算は?」)。つまりプロフィールの一部はオプションでも構いません。ただし recommend_gifts を実行する段階では、正規化されたパラメータ集合になっているべきです。

例: recommend_gifts のための TypeScript + JSON Schema

MCP サーバーを TypeScript で書くと、次のようにできます。

// apps/mcp/server.ts
import { McpServer } from "@openai/mcp-server";

const server = new McpServer();

server.registerTool(
  "recommend_gifts",
  {
    title: "ギフトのレコメンデーション",
    description:
      "受取人のセグメント、予算、ロケール、機会に基づいてギフトを選定する必要があるときにこのツールを使用します。",
    inputSchema: {
      type: "object",
      properties: {
        segments: {
          type: "array",
          description:
            "受取人のセグメントの配列。例: ['tech', 'football_fan']。通常は profile_to_segments から取得します。",
          items: { type: "string" },
          minItems: 1
        },
        budget: {
          type: "object",
          description:
            "ユーザーの通貨でのギフト予算レンジ(最小/最大)。",
          properties: {
            min: {
              type: "number",
              minimum: 0,
              description: "ユーザーが最低限支出してもよい金額。"
            },
            max: {
              type: "number",
              minimum: 0,
              description: "ユーザーが最大で支出してもよい金額。"
            },
            currency: {
              type: "string",
              minLength: 3,
              maxLength: 3,
              description: "通貨の3文字コード(例: USD, EUR, RUB)。"
            }
          },
          required: ["min", "max", "currency"]
        },
        locale: {
          type: "string",
          description:
            "BCP‑47 形式のユーザーのロケール(例: 'ru-RU' または 'en-US')。"
        },
        occasion: {
          type: "string",
          description:
            "ギフトの機会。例: 'birthday', 'new_year', 'anniversary'。"
        }
      },
      required: ["segments", "budget", "locale", "occasion"]
    }
  },
  async ({ input }) => {
    // ここでは賢いことはせず、プレースホルダを返す
    return {
      content: [
        {
          type: "text",
          text: `セグメント ${input.segments?.join(
            ", "
          )} に基づき、予算 ${input.budget?.min}–${input.budget?.max} ${input.budget?.currency} の範囲でギフトを選定しています...`
        }
      ],
      structuredContent: {}
    };
  }
);

いくつかのポイントに注意してください。

第一に、enum 的な制約と分かりやすい説明を積極的に使っています。形式上は単なる文字列でも、description が期待する値をモデルに示すため、引数を正しく埋める確率が上がります。ぼんやりした "機会": "誕生日みたいなもの" ではなく、きちんとした occasion: "birthday" にします。

第二に、フィールド説明は「チームの人向け」ではなく、モデルへの指示そのものとして書きます。これは何のフィールドか、典型的な値は何か、例があるか。Apps SDK の作者は各パラメータに人間が理解できる descriptions と例を入れることを推奨しています。

入力スキーマに入れるべきでないもの

よく紛れ込む不要なフィールドの例:

  • 内部 ID(tenantIdinternalSegment)— これはサーバー側で付与できます;
  • モデルが知りようのないもの(例: deploymentRegion)— あなたの責務です;
  • チャット履歴の重複(例: userPrompt)— モデルは元のメッセージを見えています。コピペさせないでください。

Input Schema はモデルが 判断して入力するものだけにすべきで、何でも放り込む袋ではありません。

4. Output Schema: データだけでなく意味も

Apps SDK ではツールの結果は role: tool のメッセージとして対話に戻ります。そこから先はモデルが判断します。どう回答を構成するか、どんな follow‑up を聞くか、ウィジェットを開くべきか等々。ですから出力スキーマの設計は入力と同じくらい重要です。

アプローチは二つあります。

「生データ」バージョンは次のようになります。

{
  "items": [
    { "id": "GIFT_1" },
    { "id": "GIFT_2" }
  ]
}

モデルは id のリストだけを見ます。なぜこの候補が選ばれたのか、候補は何件いて、その中でどれが良いのかが分かりません。モデルが何か補うかもしれませんが、変な挙動の確率が上がります。

意味的に豊かなバージョン:

{
  "items": [
    {
      "id": "GIFT_1",
      "score": 0.92,
      "reason": "『football_fan』セグメントとのマッチ度が高く、予算内に収まるため。"
    },
    {
      "id": "GIFT_2",
      "score": 0.81,
      "reason": "ボードゲーム好きに適しており、予算の上限にやや近い。"
    }
  ],
  "meta": {
    "totalCandidates": 27,
    "returned": 5,
    "segmentsUsed": ["football_fan", "board_games"],
    "budget": { "min": 20, "max": 50, "currency": "USD" },
    "advice": "score が高く、説明が明確な候補から始めるのがおすすめです。"
  }
}

これならモデルは「なぜこのギフトなのか」を正直に説明でき、follow‑up も組み立てられます。「27 件見つかりました。上位 5 件を表示します。理由はこうです」といった具合です。

例: recommend_gifts の Output Schema を記述する

ツールの説明に結果のスキーマを加えます(技術的に省略可能でも、契約の一部として記述するのが望ましい)。

const recommendGiftsOutputSchema = {
  type: "object",
  properties: {
    items: {
      type: "array",
      items: {
        type: "object",
        properties: {
          id: { type: "string", description: "カタログ上のギフト ID。" },
          score: {
            type: "number",
            description: "プロフィールとの適合度(0..1)。"
          },
          reason: {
            type: "string",
            description:
              "そのギフトが適している短い理由(backend で生成してもよい)。"
          }
        },
        required: ["id", "score"]
      },
      description: "適合度スコア付きの推奨ギフト一覧。"
    },
    meta: {
      type: "object",
      properties: {
        totalCandidates: {
          type: "integer",
          description: "カタログで見つかった候補の総数。"
        },
        returned: {
          type: "integer",
          description: "この呼び出しで返した件数。"
        },
        advice: {
          type: "string",
          description:
            "全体的な助言。たとえばどのタイプから始めるべきか等。"
        }
      }
    }
  },
  required: ["items"]
};

そして実装内でこのスキーマを使います。

server.registerTool(
  "recommend_gifts",
  {
    title: "ギフトのレコメンデーション",
    description:
      "セグメントと予算に基づいて 3–7 件のギフトを選ぶ必要があるときに使用します。ギフトの id と適合度を返します。詳細カードは get_gift で取得してください。",
    inputSchema: /* 上記と同様 */,
    // outputSchema は形式上省略されることもありますが、ドキュメントとしては有用です:
    // outputSchema: recommendGiftsOutputSchema
  },
  async ({ input }) => {
    const recommendations = await recommendFromCatalog(input); // ビジネスロジック

    return {
      content: [
        {
          type: "text",
          text: `適合するアイデアを ${recommendations.items.length} 件見つけました。上位を表示します。`
        }
      ],
      structuredContent: {
        items: recommendations.items,
        meta: {
          totalCandidates: recommendations.meta.totalCandidates,
          returned: recommendations.items.length,
          advice: recommendations.meta.advice
        }
      }
    };
  }
);

ここでは二つのことをしています。ユーザー向けの最小限テキストを渡す一方で、モデルが対話や follow‑up を組み立てられる意味的な JSON を同時に渡しています。

一方で get_gift は id を基に完全なカード(名前、メディア、SKU など)を取得し、GiftGenius のウィジェットがギフトカードとしてレンダリングします。

5. ネーミングと説明はディスカバリの土台

ここからが肝です。ツールの名前と説明が、モデルに呼び出されるかどうかをどう左右するか。

メタデータに関するドキュメントとベストプラクティスは次を推奨します。

  • アクション志向の名前を使う: profile_to_segmentsrecommend_giftsget_giftsimilar_giftstool1searchdo_stuff のような曖昧名は避ける;
  • 説明は「Use this when… / このツールは〜のときに使う」から始める。トリガーとなるシナリオと制限(「〜には使わない」)も書く。

これはあなたの golden prompt set と直結します。説明文の表現は実際のユーザーのリクエストと重なっているべきです。説明に「受取人の興味と予算に基づいてギフトを選ぶときに使用」とあり、golden prompt に「ゲーマーの友人に 50 ドル以内でギフトを選んで」とあれば、モデルはリクエストとツールをより容易に結びつけます。

良いツール説明の例

GiftGenius の補助ツール similar_gifts を見てみましょう。これは特定のギフトをもとに類似案を広げるためのツールです。

server.registerTool(
  "similar_gifts",
  {
    title: "類似のギフト",
    description:
      "ユーザーが特定のギフトを選択し、さらにいくつかの類似案を見たいときにこのツールを使用します。最初のゼロからの選定には使用しないでください — その場合は recommend_gifts を使います。",
    inputSchema: {
      type: "object",
      properties: {
        giftId: {
          type: "string",
          description:
            "前回の選定に含まれていたギフトの識別子。これに似た候補を探します。"
        },
        limit: {
          type: "integer",
          description:
            "返す類似ギフトの件数(デフォルトは 3–5)。",
          minimum: 1,
          default: 5
        }
      },
      required: ["giftId"]
    }
  },
  async () => {
    /* ... */
  }
);

重要な点:

  • いつ使うべきか、いつ使うべきでないかを明示しています。
  • 説明に「類似の候補」「特定のギフトを選んだ」という、ユーザーの実リクエストで頻出する語が入っています。
  • recommend_gifts の領域と重なりを避けています。これによりツール選択時の競合が減ります。

悪い説明の例

description: "ギフトの処理。"

この説明からモデルが理解できることはほぼありません。これは GPT が「当てずっぽう」で何かを呼び出そうとしたときにしか機能しないでしょう。

6. アノテーションと hints: アクションの重さをモデルに伝える

ツールは名前やスキーマだけではありません。行為の危険度/重要度や、ユーザー確認の要否を ChatGPT に伝えるアノテーションもあります。Apps SDK の仕様には readOnlyHintdestructiveHintopenWorldHint 等の hints があります。

  • readOnlyHint: true は、そのツールがデータを読み取るだけで状態を変更しないことを示します。アシスタントは余計な確認を省いて比較的自由に呼び出せます。
  • destructiveHint: true は、削除や取り消し不能な変更がありうることを示し、明示的な「本当に実行しますか?」が必要です。
  • openWorldHint: true は、外部世界に作用する行為(SNS 投稿、アカウント外の記録の作成等)であることを示します。これも事前に知らせるべきです。

最小レベル — 確認なし

パブリックな read-only ツールがあるなら、readOnlyHint: true を付けるとよいでしょう。例:

"annotations": {
  "readOnlyHint": true,
  "destructiveHint": false,
  "openWorldHint": false
}

このようなツールは、GPT 側で余計なダイアログ確認なしに呼び出せます。

一度の確認

サーバー側の状態を変更するツールには readOnlyHint: false を付けるのが理に適っています。

"annotations": {
  "readOnlyHint": false,
  "destructiveHint": false,
  "openWorldHint": false
}

モデルはこのようなツールを見ると、通常 1 回のユーザー確認(ChatGPT UI のモーダル)が必要だと判断します。

危険な行為

サーバー上で何かを削除するようなツールは、destructiveHint: true を付けましょう。

"annotations": {
  "readOnlyHint": false,
  "destructiveHint": true,
  "openWorldHint": false
}

モデルはこのツールを非常に慎重に扱い、次の二段階で確認します。

  • まずテキストでユーザーに確認を求め、
  • その後、プラットフォームが標準のダイアログを表示します。

GiftGenius ではこのモジュールの範囲ではコマース系のツールは実装しませんが、将来の create_gift_order がどのようになるかをイメージしておきましょう。

server.registerTool(
  "create_gift_order",
  {
    title: "ギフト注文の作成",
    description:
      "ユーザーが選択したギフトを購入することに明確に同意した後にのみ使用します。システムに注文を作成し、ステータスを返します。",
    inputSchema: {
      type: "object",
      properties: {
        giftId: {
          type: "string",
          description: "ユーザーが選んだギフトの ID。"
        },
        deliveryEmail: {
          type: "string",
          description: "デジタルギフトの送付先 Email。"
        }
      },
      required: ["giftId", "deliveryEmail"]
    },
    annotations: {
      destructiveHint: true,
      openWorldHint: true
    }
  },
  async () => {
    /* ... */
  }
);

アノテーションはサーバー側の権限チェックの代わりではありません。ChatGPT が適切な UX(確認を求める、警告を表示する、黙って実行しない)を組み立てる助けになります。

7. App のメタデータと二層のディスカバリ

ツールは物語の半分です。もう半分は、ユーザーがそもそもどうやってあなたの App を見つけ、起動するかです。

ChatGPT のエコシステムには二つの主要なディスカバリ層があります。

第一は in‑conversation discovery。ユーザーがチャットに何か書いたとき(App を明示せずとも)、モデルは次を見ます。

  • メッセージのテキストと対話履歴;
  • 利用可能なアプリとそのツールの説明;
  • ブランド言及、トピック、キーフレーズ。

これに基づいて、どの App を提案するか、そしてどのシナリオで提案するかを決めます。ここではツールと App の説明が特に重要です。説明に「ギフトの選定」「ギフトのアイデア」「ギフト予算」などの「トリガー語」が含まれていると、あなたの App が選ばれる確率が上がります。

第二はグローバルなディスカバリ: カタログとランチャー。ここでは人間の判断になります。名前、アイコン、短い説明、タグを見て App を選びます。ここでは、アプリが何をするのか、誰向けか、主な価値は何かを正直かつ明確に説明することが重要です。

これを小さな表にまとめられます。

レイヤー モデル/ユーザーが見るもの メタデータで重要な点
In‑conversation 対話テキスト、ツールと App の説明 トリガー表現、アクション志向の命名、制限事項
カタログ/ランチャー 名称、アイコン、short/long description、タグ 明確なポジショニング、分かりやすい価値訴求

GiftGenius の例:

  • 名前: GiftGenius — 60 秒でギフトを選定
  • 短い説明: 受取人のプロフィールを集め、5–7 個のギフト案を提示。ChatGPT 内で即購入も可能。
  • in‑conversation 用の説明: ユーザーがギフト選びの助けを求めている、何を贈るか分からない、予算や受取人の興味、機会を挙げているときにこのアプリを使います。

これらの表現は、system‑promptrecommend_gifts の説明で書いた内容と強く同期させてください。モデルにとって一貫した全体像が見えることが重要です。

8. ChatGPT の「頭の中」でのルーティング

ここまでをまとめ、典型的なリクエストの流れを見てみましょう — MCP の詳細は次のモジュールで扱います。

ユーザーが次のように書いたとします。

「サッカーとボードゲームが大好きな兄に、最大 50 ドルで贈り物のアイデアを考えるのを手伝って。」

大まかに単純化したアルゴリズム:

  1. モデルはメッセージと履歴を分析します。「ギフト」「兄」「サッカー」「ボードゲーム」「予算 50」などの語を捉えます。
  2. これを利用可能な App とそのツールの説明と照合します。GiftGenius の説明には「興味と予算に基づくギフト選定」が明示されているため、関連性は高いと判断されます。
  3. このセッションで App がまだ有効でなければ、モデルはアナウンスを作ります。「GiftGenius を開いて、条件に合うギフト選びをお手伝いできます。開きますか?」— これはあらかじめ UX 指示に書いておきます。
  4. ユーザーが同意すると、モデルは App 内で recommend_gifts を選びます。現在の意図に最も適う説明になっているためです。名前、descriptioninputSchema の構造が入力信号として効きます。
  5. モデルはリクエストに基づいてツール引数を埋めます。まず必要に応じて profile_to_segments を使い、「兄、サッカーとボードゲームが好き」から ["football_fan", "board_games"] を得て、次に recommend_giftssegmentsbudget: {min: 0, max: 50, currency: "USD"}localeoccasion: "birthday" で呼び出します。
  6. MCP サーバーがツールを実行し、itemsmeta を持つ structured output を返します。
  7. モデルはあなたが outputSchema で記述した JSON を読み、回答を構築します。何を見つけたか、なぜそのギフトなのかを説明し、「カテゴリで絞りますか?」「このギフトに似たものを表示しますか?」「このギフトを購入しますか?」といった follow‑up を提案します。

このプロセスの簡単なフローチャート:

flowchart TD
  A[User: ギフトに関するリクエスト] --> B[ChatGPT がコンテキストを分析]
  B --> C[App と tools のメタデータと照合]
  C -->|関連あり| D[GiftGenius を提案]
  D -->|ユーザーが同意| E["recommend_gifts を呼び出し(+ profile_to_segments)"]
  E --> F[GiftGenius の MCP サーバー]
  F --> G[items/meta を含む JSON 結果]
  G --> H[モデルが回答とフォローアップを生成]

ツールとユースケースの記述が良いほど、偶然性は減り、ルーティングは安定します。

インサイト: Tool Call SEO

Apps のエコシステムでは、まもなくカタログでの人の注意だけでなく、モデル自身の注意を巡る競争も起こります。同一のユーザーリクエストに対して ChatGPT は複数のアプリを呼べますが、その選択はプレゼンの見栄えではなく、モデルの頭の中の「検索結果」で決まります。この見えない層はますます SEO に似てきます。ただしページではなく、対象は tools と MCP サーバーです。

モデルは候補をランク付けします。まず App レベル、次に個々のツールレベルです。名前、descriptions、スキーマ、アノテーションを見て、リクエストの表現と照合します。recommend_gifts の説明に「受取人の興味と予算に基づくギフト選定」とあり、リクエストに「ゲーマーの友人に 50 ドルで」とあれば、抽象的な search(説明が「ギフトの処理」)よりも「上位に出る」確率が高くなります。

ここから生まれる実践的な考えが Tool Call SEO です。名前、descriptions、enum 値、メタデータをキーワードやスニペットとして扱いましょう。開発者向けの契約を書くのではなく、あなたの golden prompt set から来る実トラフィックに最適化するのです。あまりに一般的な表現、複数ツールの重複領域、ニッチが曖昧な God ツールは、モデルの頭の中での「CTR」を下げます。

9. 小さな実践課題

次のことを頭の中で(または自分のリポジトリで)やってみてください。

まず GiftGenius の主要シナリオの一つを選びます — 例: 「職場の同僚向けに、限られた予算でギフトを選ぶ」。

次を定義してください。

  1. このシナリオに必要な単独ツールは何か。純粋に recommend_gifts でよいのか、B2B 用の特化ツールが必要なのか、あるいは recommend_gifts の後に similar_gifts でバリエーションを出すだけで足りるのか。
  2. recommend_gifts の入力スキーマで本当に必要なフィールドはどれか。モデルに当て推量させるのではなく、ユーザーに follow‑up で聞けるフィールドはどれか。
  3. モデルが選定理由を正直に説明し、次のステップ(B2B モードへの切替、デジタルギフト限定、価格帯での絞り込みなど)を提案できるように、outputSchema はどうあるべきか。

それから前回講義の golden prompt set を見直し、次を確認します。

  • 各基準プロンプトに対して明確なツールがあるか(recommend_giftsget_giftsimilar_gifts など)。
  • 一つのリクエストに対して二つのツールが同じくらい「当てはまる」状態になっていないか(overlapping tools)。
  • モデルの混乱を減らすため、説明を強化すべきか、またはどれかのツール名を改名すべきか。

これは、プロンプト/スキーマ/ロジックの重大な変更のたびに繰り返すプロセスです。ディスカバリ品質のミニ eval と考えてください。

以上をチェックリストにまとめると、今の段階で必要なのは次の通りです。

  • シナリオを 2–4 個の意味あるツールに正直に分割する;
  • inputSchema/outputSchema を例や enum とともに丁寧に記述する;
  • 名前、descriptions、アノテーションを整える;
  • system‑prompt と App のメタデータと同期する。

次のモジュールでは、これらが MCP を介してどう動くか、ディスカバリ/ルーティングの奇妙な挙動をどう診断するかを見ていきます。

10. tools とメタデータ設計での典型的なミス

ミス #1: 「system‑prompt に全部書いた。ツールは何とかなる」
App の役割、責務の境界、UX の挙動を完璧に書いても、ツール名が tool1searchdo_stuff で、スキーマに説明がなければ、モデルは美しいテキストを実際の呼び出しに結びつけられません。ChatGPT にとってツールは主要インターフェースです。適切なメタデータがなければ、どんな system‑prompt でも救えません。

ミス #2: 何でも詰め込む God ツール
「最適化」のつもりで mode 付きの一関数にしたくなる気持ちは分かりますが、それは巨大な JSON スキーマ、説明の混乱、ルーティング品質の低下を招きます。モデルはどのモードを使うか迷い、あなたはサーバーで巨大な switch を保守する羽目に。特定ステップに対応する明確な複数ツールの方がはるかに良いです。

ミス #3: 「念のため」だらけの入力スキーマ
開発者はしばしば、いつか必要になるかもしれない全パラメータを inputSchema に通し、ついでに内部フィールドも混入させます。結果、モデルは知りようのない値(たとえば tenantId)を推測しようとし、奇妙な値に驚くことになります。Input Schema は、対話からモデルが導けるもの、または質問で確認できるものだけにしましょう。内部の詳細はサーバー側で付加してください。

ミス #4: メタ情報のない「無口な」出力データ
ツールから素の配列を返すのは誘惑的です。しかしそれでは、なぜその結果になったのかという理解をモデルから奪います。scorereasonsearchCriteriatotalCandidates のようなフィールドがなければ、正直な説明や follow‑up が難しくなります。検索条件や助言を含む小さな meta ラッパーを加えるだけで、回答品質が劇的に改善することが多いです。

ミス #5: 「ギフトの処理」「コース検索」「データ処理」といった曖昧な説明
この手の説明は、トリガーも制限もモデルに与えません。いつツールを呼ぶべきか、その適用範囲が分からないのです。良い説明は「このツールは〜のときに使う」から始まり、具体的なシナリオと「〜には使わない」の禁止事項を含みます。理想は、これらの表現が golden prompt set の代表クエリと重なることです。

ミス #6: アノテーションを無視し、read‑only と変更系を混在
データを読むだけのツール(readOnlyHint)と、何らかの行為を伴うツール(destructiveHintopenWorldHint)を区別しないと、モデルは正しい確認 UX を構築できません。結果として、すべての操作で「本当に実行しますか?」が出たり、逆にユーザーの同意なしに静かに購入や変更が行われたりします。アノテーションは、操作の重要度を伝える安価で効果的な手段です。

ミス #7: カタログ用メタデータと in‑conversation 用メタデータが別世界
カタログの短い説明はマーケが書き(「人生を変える革命的 AI アシスタント」)、ツールの descriptions と system‑prompt は開発者が書く(「予算に基づくギフト選定」)ということが起こりがちです。結果として、カタログでは App が何なのか分からず、チャットでは「これはどんなサービス?」という質問と App の実力が結びつきません。メタデータは一つの仕様として書きましょう。二つの独立したマーケ文章にしないことです。

1
アンケート/クイズ
ChatGPT App の挙動、レベル 5、レッスン 4
使用不可
ChatGPT App の挙動
モデルへの指示と ChatGPT App の挙動
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION