JavaRush /Курсы /ChatGPT Apps /Из инструкций в дизайн tools и метаданных: discovery и ма...

Из инструкций в дизайн tools и метаданных: discovery и маршрутизация

ChatGPT Apps
5 уровень , 4 лекция
Открыта

1. Почему инструкции недостаточны без хороших tools и метаданных

Важно зафиксировать одну неприятную истину: модель не видит ваш код. Она не знает, какие там контроллеры в Next.js, какие функции в TypeScript и какие чудесные эвристики вы собрали в сервисе рекомендаций.

Она видит ваш App через несколько интерфейсов:

  1. System‑prompt (ролевой контракт).
  2. Описания инструментов: имя, description, inputSchema, outputSchema, аннотации и т. д.
  3. Метаданные самого приложения: название, иконка, короткое и длинное описание, категории, conversation starters и т. п.

При обработке запроса модель смотрит на контекст диалога и эти метаданные, чтобы решить:

  • нужно ли вообще предлагать какой‑то App;
  • если да — какой именно из доступных;
  • и если App выбран — какой именно инструмент этого App подходит под текущий запрос.

В прошлой части Модуля 5 мы занимались тем, что можно «рассказать» модели словами — system‑prompt и UX‑инструкции. Теперь переходим к тому, что она видит помимо текста: tools и метаданные.

Поэтому задача Модуля 5 на самом деле двойная. Вы сначала в system‑prompt формулируете «что этот App должен делать и как себя вести», а затем в дизайне tools и метаданных вы упаковываете это в форму, которую модель действительно умеет использовать — в том числе для discovery и маршрутизации.

Себе можно сформулировать так: system‑prompt — это конституция, а tools и метаданные — это уже законы и вся бюрократика вокруг: формы заявлений, схемы баз данных и т. п. Если ограничиться только конституцией, далеко не уедем.

2. Декомпозиция: «одна задача — один tool», но разумно

Начнём с самого болезненного: сколько вообще инструментов делать и как их резать.

Интуитивный принцип: один инструмент — одна понятная задача. Это сильно облегчает модели выбор: у неё не 1 монструозная функция 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 }) => { /* огромный switch по mode */ }
);

У модели в голове это выглядит как «есть какой‑то абстрактный инструмент про подарки, а дальше разберёмся». Это ухудшает точность выбора, мешает discovery и усложняет вам сопровождение.

Но и впасть в другую крайность — сделать 50 микроскопических инструментов на каждый чих — тоже плохо. Каждый дополнительный инструмент попадает в контекст, нагружает внимание модели и повышает риск ошибок маршрутизации. Документация прямо предупреждает: слишком много мелких инструментов — минус для качества, особенно когда их описания пересекаются.

Практическое правило, которым удобно пользоваться:

  • всё, что пользователь воспринимает как один «шаг» в сценарии (например, первый подбор подарков по профилю), — хороший кандидат на отдельный tool;
  • то, что всегда выполняется строго внутри этого шага и не имеет самостоятельного смысла (например, вычислить скоринг или залогировать просмотр карточек), лучше оставить внутри реализации инструмента.

Допустим, вы по этому принципу уже нарезали сценарии на 2–4 инструмента. Дальше важный вопрос — как описать входы этих tools так, чтобы модель могла ими пользоваться без догадок. С этого и начнём.

3. Проецируем use‑cases на Input Schema

Теперь берём один конкретный use‑case и честно смотрим, какие данные действительно нужны инструменту.

Возьмём сценарий: «Даритель на дедлайне: подобрать 5–7 идей для друга 25 лет, любит футбол и настольные игры, бюджет до 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, у него уже должен быть нормализованный набор параметров.

Пример: TypeScript + JSON Schema для recommend_gifts

В 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: "Трёхбуквенный код валюты (например, USD, EUR, BGN)."
            }
          },
          required: ["min", "max", "currency"]
        },
        locale: {
          type: "string",
          description:
            "Локаль пользователя в формате BCP‑47 (например, 'en-US', 'bg-BG' или 'ru-RU')."
        },
        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 и примеры для каждого параметра.

Чего не должно быть во входной схеме

Типичные паразитные поля, которые часто пытаются туда засунуть:

  • внутренние идентификаторы (tenantId, internalSegment), которые и так можно добавить на сервере;
  • вещи, которые модель никак не может знать (например, 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 лучших, вот почему именно они».

Пример: описываем Output Schema для recommend_gifts

Добавим в описание инструмента схему результата (даже если технически её можно не указывать, лучше это сделать — это часть контракта с моделью):

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
        }
      }
    };
  }
);

Мы делаем две вещи: даём модели минимальный текст для пользователя и одновременно кладём семантический JSON, по которому она может дальше строить диалог и follow‑up.

При этом get_gift уже по id подтянет полные карточки (название, медиа, SKU и т. д.), а виджет GiftGenius отрендерит их как карточки подарков.

5. Нейминг и описания инструментов как основа discovery

Теперь самое вкусное: как названия и описания tools влияют на то, вызовет их модель или нет.

Документация и best practice по метаданным советуют:

  • использовать action‑oriented имена: profile_to_segments, recommend_gifts, get_gift, similar_gifts, а не tool1, search, do_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 для этого есть разные hints, вроде readOnlyHint, destructiveHint, openWorldHint и другие.

  • readOnlyHint: true говорит, что инструмент только читает данные и не меняет состояние. Тогда ассистент может пропустить лишние подтверждения и вызывать его свободнее.
  • destructiveHint: true сигнализирует, что инструмент может что‑то удалить или безвозвратно изменить, поэтому надо показать пользователю явное «Вы уверены?».
  • openWorldHint: true указывает, что действие затрагивает внешний мир (постинг в соцсетях, создание записи вне аккаунта и т. п.), и об этом тоже важно предупредить.

Минимальный уровень — без подтверждений

Если у вас есть public readonly tools, то имеет смысл помечать их как readOnlyHint: true. Пример:

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

Такие инструменты можно вызывать без лишних диалоговых подтверждений со стороны GPT.

Одно подтверждение

Если у вас есть tools, которые что‑то меняют на сервере, логично помечать их как readOnlyHint: false:

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

Модель, увидев такой инструмент, скорее всего запросит у пользователя подтверждение один раз (обычно это модальное диалоговое окно в UI ChatGPT).

Опасное действие

Если у вас есть tool, который что‑то удаляет на сервере, то пометьте его как destructiveHint: true:

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

Модель будет очень осторожно вызывать этот tool и дважды уточнит:

  • сначала попросит подтверждение у пользователя в тексте,
  • затем платформа покажет стандартное диалоговое окно.

Для нашего GiftGenius в рамках этого модуля мы пока не пишем commerce‑инструменты, но можно наметить, как будет выглядеть будущий 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 и два уровня discovery

Инструменты — это половина истории. Вторая половина — как пользователь вообще находит и запускает ваш App.

В экосистеме ChatGPT есть два ключевых уровня discovery.

Первый — in‑conversation discovery. Когда пользователь пишет что‑то в чате (даже без явного упоминания App), модель смотрит:

  • на текст сообщения и историю диалога;
  • на описания доступных приложений и их инструментов;
  • на бренд‑упоминания, тематику и ключевые фразы.

На основе этого она решает, стоит ли предложить какой‑то App, и если да — какой и с каким сценарием. Здесь особенно важны описания инструментов и самого App. Если в них есть «триггеры» вроде «подбор подарков», «идея подарка», «бюджет подарка», шанс, что модель выберет ваш App, резко растёт.

Второй уровень — глобальный discovery: каталог и launcher. Там уже играет роль человек: он глазами выбирает App по названию, иконке, короткому описанию и тегам. Здесь важно, чтобы вы честно и понятно объяснили, что делает ваше приложение, для кого оно и в чём его основная ценность.

Можно свести это в небольшую таблицу:

Слой Что видит модель/юзер Что важно в метаданных
In‑conversation Текст диалога, описания tools и App Триггерные формулировки, action‑именование, ограничения
Каталог/launcher Название, иконка, short/long description, теги Чёткое позиционирование, понятный value‑props

Для GiftGenius можно, например, сформулировать:

  • Название: GiftGenius — подбор подарков за 60 секунд.
  • Короткое описание: Собирает профиль получателя и предлагает 5–7 идей подарков с возможностью мгновенной покупки внутри ChatGPT.
  • Описание для in‑conversation: Используй это приложение, когда пользователь просит помочь выбрать подарок, не знает, что подарить, называет бюджет, интересы получателя или повод.

Эти формулировки крайне желательно синхронизировать с тем, что вы уже писали в system‑prompt и описаниях инструмента recommend_gifts. Тогда модель видит целостную картину, а не набор противоречивых текстов.

8. Как маршрутизация работает «в голове» ChatGPT

Соберём всё вместе и посмотрим на типичный путь запроса — без углубления в протокол MCP, это будет в следующих модулях.

Пусть пользователь пишет:

«Помоги придумать подарок брату, он обожает футбол и настолки, бюджет до 50 долларов.»

Грубо упрощённый алгоритм:

  1. Модель анализирует сообщение и историю. Видит слова «подарок», «брат», «футбол», «настолки», «бюджет 50».
  2. Сравнивает это с описаниями доступных App и их инструментов. Для GiftGenius описания явно содержат «подбор подарков по интересам и бюджету», поэтому вероятность того, что App релевантен, высока.
  3. Если App ещё не активен в этой сессии, модель формирует реплику‑анонс: «Я могу открыть приложение GiftGenius, которое поможет подобрать подарок по вашим параметрам. Открыть?» — это мы заранее прописали в UX‑инструкциях.
  4. После согласия пользователя модель выбирает внутри App инструмент recommend_gifts, потому что именно его описание лучше всего соответствует текущему намерению. Тут и имя, и description, и структура inputSchema работают как входящие сигналы.
  5. Модель заполняет аргументы инструмента на основе запроса: сначала (при необходимости) вызывает profile_to_segments, чтобы из текста «брат, любит футбол и настолки» получить сегменты ["football_fan", "board_games"], затем вызывает recommend_gifts с segments, budget: {min: 0, max: 50, currency: "USD"}, locale, occasion: "birthday".
  6. MCP‑сервер исполняет инструмент, формирует structured output с items и meta и возвращает его.
  7. Модель читает JSON, который вы описали в outputSchema, и строит ответ: объясняет, что нашла, почему именно эти подарки, и предлагает follow‑up («хотите сузить по категории?», «показать похожие на этот подарок?» или «оформить покупку этого подарка?»).

Вот простая блок‑схема этого процесса:

flowchart TD
  A[User: запрос про подарок] --> B[ChatGPT анализирует контекст]
  B --> C[Сравнение с метаданными App и tools]
  C -->|релевантно| D[Анонс GiftGenius]
  D -->|пользователь согласен| E["Вызов recommend_gifts (+ profile_to_segments)"]
  E --> F[MCP-сервер GiftGenius]
  F --> G[JSON-результат с items/meta]
  G --> H[Модель формирует ответ и follow-up]

Чем лучше вы описали инструменты и use‑cases, тем меньше здесь случайности и тем стабильнее маршрутизация.

Insight: Tool Call SEO

В экосистеме Apps у вас скоро будет не только конкуренция за внимание людей в каталоге, но и конкуренция за внимание самой модели. На один и тот же пользовательский запрос ChatGPT может позвать десяток разных приложений, и выбор будет происходить не в чьей презентации дизайн лучше, а в «поисковой выдаче» внутри головы модели. Этот невидимый слой всё больше напоминает SEO, только вместо страниц у вас tools и MCP-сервера.

Модель по сути ранжирует кандидатов: сначала на уровне App, потом на уровне отдельных инструментов. Она смотрит на название, descriptions, схемы, аннотации и сопоставляет их с формулировками запроса. Если в описании recommend_gifts есть «подбор подарков по бюджету и интересам получателя», а в запросе звучит «подбери подарок другу-геймеру за 50$», у этого инструмента больше шансов «попасть в топ» выдачи, чем у абстрактного search с описанием «работа с подарками».

Отсюда рождается практическая идея Tool Call SEO: относиться к именам, descriptions, enum-значениям и метаданным как к ключевым словам и сниппетам. Вы не просто описываете контракт для разработчиков — вы оптимизируете его под реальный трафик запросов из своего golden prompt set. Слишком общие формулировки, пересекающиеся области нескольких tools, God-инструменты без чёткой ниши — всё это снижает «CTR» вашего App в голове модели.

9. Небольшое практическое упражнение

Попробуйте мысленно (или в своём репозитории) проделать следующее.

Сначала выберите один из ключевых сценариев GiftGenius — например, «Подобрать подарок коллеге по работе с ограниченным бюджетом».

Сформулируйте для него:

  1. Какой отдельный инструмент под этот сценарий нужен: это чистый recommend_gifts, или вам нужен ещё специализированный инструмент для B2B‑кейса, или, скажем, достаточно после recommend_gifts использовать similar_gifts для вариаций?
  2. Какие поля действительно необходимы во входной схеме recommend_gifts. Какие поля можно спросить у пользователя отдельно (через follow‑up), а не заставлять модель угадывать.
  3. Как должен выглядеть outputSchema, чтобы модель могла честно объяснить выбор и предложить следующие шаги (например, переключиться на B2B‑режим, показать только цифровые подарки, сузить по ценовому диапазону).

А затем посмотрите на свой golden prompt set из прошлой лекции и проверьте:

  • есть ли для каждого эталонного запроса очевидный инструмент (recommend_gifts, get_gift, similar_gifts и т. д.);
  • не получилось ли так, что два инструмента одинаково «подходят» к одному и тому же запросу (overlapping tools);
  • нужно ли усилить описания или переименовать какой‑то tool, чтобы модель меньше путалась.

Это именно тот процесс, который вы будете повторять перед каждым серьёзным изменением промпта, схем или логики — по сути, мини‑eval качества discovery.

Если свести всё выше к чек‑листу, на этом этапе вам нужно:

  • честно нарезать сценарии на 2–4 осмысленных инструмента;
  • аккуратно описать inputSchema/outputSchema с примерами и enum‑ами;
  • привести в порядок названия, descriptions и аннотации;
  • синхронизировать это с system‑prompt и метаданными App.

В следующих модулях мы уже будем смотреть, как это всё работает через MCP и как диагностировать странное поведение discovery/маршрутизации.

10. Типичные ошибки при проектировании tools и метаданных

Ошибка №1: «Мы всё описали в system‑prompt, инструменты как‑нибудь разберутся».
Если вы отлично расписали роль App, границы ответственности и UX‑поведение, но при этом оставили инструменты с названиями tool1, search, do_stuff и схемами без описаний, модель просто не сможет связать ваш красивый текст с реальными вызовами. Для ChatGPT инструменты — это основной интерфейс; без грамотных метаданных никакой system‑prompt не спасёт.

Ошибка №2: God Tool, который делает всё подряд.
Желание «оптимизировать» и сделать одну функцию с параметром mode понятно, но приводит к монструозным JSON‑схемам, путанице в описаниях и ухудшению маршрутизации. Модель начинает гадать, какой режим использовать, а вы — поддерживать огромный switch на сервере. Лучше несколько чётких инструментов под конкретные шаги сценария, чем один «сделай всё».

Ошибка №3: Входная схема, переполненная полями «на всякий случай».
Часто разработчики пытаются сразу протащить через inputSchema все параметры, которые когда‑либо могут пригодиться, плюс пару внутренних полей. В итоге модель пытается угадать то, чего она не может знать (например, tenantId), а вы потом удивляетесь странным значениям. Input Schema должен содержать только то, что модель реально может вывести из диалога или уточнить вопросом. Внутренние детали добавляйте на сервере.

Ошибка №4: «Немые» output‑данные без метаинформации.
Возвращать из инструмента просто массив объектов соблазнительно. Но так вы лишаете модель понимания, почему эти результаты появились. Без полей вроде score, reason, searchCriteria, totalCandidates ей сложнее строить честные объяснения и follow‑up. Добавление небольшой meta‑обёртки с критериями поиска и советами зачастую радикально улучшает качество ответа.

Ошибка №5: Вагонка в описаниях: «Работа с подарками», «Поиск курсов», «Обработка данных».
Такие описания плохи тем, что не дают модели ни триггеров, ни ограничений. Она не знает, когда именно нужно вызывать инструмент и в какой области он применим. Хорошее описание начинается с «Используй этот инструмент, когда…» и содержит конкретные сценарии и запреты типа «Не используй для…». Идеально, если эти формулировки пересекаются с золотыми запросами из вашего golden prompt set.

Ошибка №6: Игнорирование аннотаций и смешивание read‑only и изменяющих действий.
Если вы не помечаете инструменты, которые только читают данные (readOnlyHint), и те, которые совершают действия (destructiveHint, openWorldHint), модель не может выстроить правильный UX подтверждений. В итоге либо лишние «Вы уверены?» на каждом шаге, либо, наоборот, тихие покупки и изменения без согласия пользователя. Аннотации — дешёвый и эффективный способ подсказать модели важность операции.

Ошибка №7: Метаданные App для каталога и метаданные для in‑conversation живут в разных вселенных.
Бывает, что короткое описание в каталоге написано маркетологом («Революционный AI‑ассистент, который меняет вашу жизнь»), а descriptions tools и system‑prompt — разработчиком («подбор подарков по бюджету»). В результате в каталоге непонятно, про что вообще App, а модель в чате не может сопоставить запросы вида «что это за сервис?» с реальными возможностями App. Пишите метаданные как единую спецификацию, а не как два независимых маркетинговых текста.

1
Задача
ChatGPT Apps, 5 уровень, 4 лекция
Недоступна
“Tool Call SEO lab” — единый реестр метаданных + симуляция маршрутизации
“Tool Call SEO lab” — единый реестр метаданных + симуляция маршрутизации
1
Опрос
Поведение ChatGPT App, 5 уровень, 4 лекция
Недоступен
Поведение ChatGPT App
Инструкции для модели и поведение ChatGPT App
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ