JavaRush /Курсы /ChatGPT Apps /Multi‑App сценарии и композиция Apps

Multi‑App сценарии и композиция Apps

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

1. Что такое Multi‑App‑сценарии и почему они вам понадобятся

До сих пор мы смотрели на GiftGenius как на единственное внешнее приложение в конкретном чате: пользователь выбирает ваш App в списке, ChatGPT поднимает ваши tools, и вы «главный герой» истории. В реальном Store всё иначе: пользователь может подключить сразу несколько Apps, и ChatGPT будет решать, какое именно приложение вызвать в ответ на конкретный запрос.

Например, в одном чате могут быть:

  • корпоративный календарный App, который знает дни рождения коллег;
  • GiftGenius, который подбирает идеи подарков;
  • commerce‑App компании, который умеет оформлять заказы и оплату.

Пользователь пишет: «Напомни мне про дни рождения коллег и сразу предложи, что подарить и как купить». Модель может последовательно вызвать три разных App: один — за календарём, второй — за идеями подарков, третий — за чекаутом.

Важно понимать: у пользователя нет кнопки «вызови, пожалуйста, App №2 и вот его HTTP‑endpoint». Он общается естественным языком, а ChatGPT выступает роутером — читает descriptions и метаданные всех доступных приложений и решает, кого когда позвать.

Отсюда следуют три ключевые мысли:

  • Идёт конкуренция за контекст. Ваш App должен быть выбран среди десятков других на основе описаний, названий и поведения.
  • Метаданные становятся вашим «SEO для LLM» — именно они определяют, увидит ли модель GiftGenius в нужный момент или проигнорирует.
  • Нужно думать об интероперабельности: ваши ответы должны быть полезны не только человеку в чате, но и другим App, которые читают тот же контекст.

По сути, мы изолированный ChatGPT App превращаем в компонент более крупной системы.

2. Как модель выбирает App: ментальная модель роутинга

Роутинг в Multi‑App‑сценариях устроен примерно так (сильно упрощая, но полезно для разработки):

  1. У ChatGPT есть список доступных App и их tools с метаданными (имя, описание, JSON Schema параметров, аннотации и _meta).
  2. Пользователь пишет сообщение.
  3. Модель строит внутреннее представление намерения (intent) и по сути делает семантический поиск по descriptions tools и приложений, чтобы понять, какие инструменты уместны.
  4. Если критерии сходятся — вызывает tool или предлагает открыть App.

Здесь есть важный нюанс: descriptions должны быть достаточно различающимися (дискриминативными). Формулировка «Поиск товаров» мало чем отличается от «Поиск подарков» или «Поиск книг», а вот «Поиск идей подарков по базе партнёров GiftGenius» сильно сужает домен и повышает шансы, что именно ваш инструмент будет выбран для подарочных запросов.

Вторая деталь — избегайте коллизий имён. Инструмент с названием get_data в мире десятков App ни о чём не говорит, а вот giftgenius_get_gift_catalog гораздо яснее. И особенно — в комбинации с чётким description.

И, наконец, модель опирается на контекст: если в чате уже упоминались «подарки», «дни рождения» и даже название GiftGenius, это подсвечивает ваш App в глазах роутера.

3. Метаданные и descriptions как LLM‑SEO

Чтобы не относиться к метаданным как к «обязательной формальности в JSON», полезно думать о них как о продуктовой копирайтинговой работе. Официальные рекомендации прямо говорят: treat metadata like product copy и проектируйте «one job per tool».

Условно можно выделить несколько уровней описаний:

Уровень Для кого Что описывает
Manifest description Человек + модель Задачу всего App: зачем его включать в чат
Tool description Модель (routing) Когда использовать конкретный tool и для каких задач
Parameter descriptions Модель (slot fill) Как заполнять аргументы, какие значения допустимы
_meta["openai/widgetDescription"]
Модель (UI) Что именно появляется в виджете и нужно ли это дублировать текстовым ответом модели

widgetDescription особенно важен в мире виджетов: модель не «видит» ваш React‑код, она лишь знает, какие props вы ей передадите и зачем. Хорошо заполненное поле позволяет ей не придумывать «за вас», а, наоборот, адаптировать текстовые ответы с учётом уже показанного UI.

Документация по Apps SDK подчёркивает: ChatGPT решает, когда и как вызывать ваш коннектор (App), опираясь на метаданные. Аккуратные descriptions и docs по параметрам повышают recall — долю ситуаций, в которых модель вообще вспоминает про ваш App, — и уменьшают ложные срабатывания.

Мини‑пример: старый vs новый description для GiftGenius

Допустим, у нас раньше было что‑то вроде:

export const appDescription = `
GiftGenius — помощник для поиска и покупки подарков.
`;

С точки зрения человека всё неплохо, но для роутинга в Multi‑App‑мире лучше сделать акцент на том, когда использовать App и чего он не делает:

export const appDescription = `
GiftGenius — ассистент по идеям подарков.
Используй это приложение, когда пользователь просит придумать подарок 
для конкретного человека или повода и уложиться в бюджет.
Не используй его для общего онлайн-шопинга или планирования личных финансов.
`;

Теперь модели проще отличить GiftGenius от общего e‑commerce App или финконсультанта.

4. _meta["openai/widgetDescription"]: объясняем модели наш UI

В таблице выше мы отдельно упомянули _meta["openai/widgetDescription"]. Теперь сфокусируемся на этом уровне: он помогает модели «вообразить» ваш виджет и понять, какие части ответа уже покрыты UI, а что стоит проговорить текстом.

Допустим, наш основной инструмент suggest_gifts возвращает список подарков, а виджет рендерит их как горизонтальную карусель карточек. В описании инструмента мы уже объяснили, когда его использовать, а в widgetDescription объясняем, как выглядит результат.

Пример фрагмента дескриптора инструмента (descriptor, упрощённый, по мотивам рекомендаций):

const suggestGiftsTool = {
  name: "suggest_gifts",
  description: "Use this to generate gift ideas within user's budget.",
  inputSchema: { /* ... */ },
  _meta: {
    "openai/widgetDescription":
      "Показывает горизонтальный список карточек подарков с ценой и кнопкой 'Купить'. Не повторяй названия подарков в тексте ответа."
  }
};

Здесь мы сразу добиваемся нескольких целей:

  • Модель знает, что UI уже покажет названия и цены — значит, в текстовом ответе можно сосредоточиться на объяснениях и советах, а не дубляже списка.
  • Другие Apps (через модель) понимают, что toolOutput — это не просто абзац текста, а структурированный список, который можно «подхватить» в своём контексте.

И да, давайте честно: писать такие описания скучнее, чем кодить, но именно они потом сэкономят вам часы дебага странного поведения модели.

5. Аннотации инструментов: readOnlyHint, destructiveHint, openWorldHint

В Multi‑App‑мире важно не только «когда вызывать», но и насколько безопасно вызывать конкретный инструмент. Для этого Apps SDK вводит набор аннотаций в дескрипторах инструментов.

Идея такая: аннотации — это мягкие подсказки модели о характере операции. Они не заменяют серверную авторизацию, но сильно влияют на то, как ChatGPT будет себя вести в цепочках.

Короткое резюме (концептуально):

Аннотация Смысл Типичное поведение модели
readOnlyHint
Ничего не меняет в данных Можно вызывать без лишних подтверждений, часто
destructiveHint / isConsequential
Меняет состояние (покупки, удаление) Перед вызовом спросить подтверждение у пользователя
openWorldHint
Лезет во «внешний мир» (поиск, веб) Модель осторожнее с объёмом и качеством результата

Аннотации (readOnlyHint, destructiveHint, openWorldHint) — это часть стандартного описания инструмента и могут использоваться не только ChatGPT. Поле _meta["openai/isConsequential"] — более узкий, специфичный для ChatGPT сигнал, который дополнительно помогает модели различать «безопасные» и «последовательные» вызовы.

Посмотрим на два инструмента GiftGenius:

  • suggest_gifts — чтение каталога, безопасно.
  • create_checkout_session — создание чекаута, явное side‑effect‑действие.

Пример описания инструмента suggest_gifts

const suggestGiftsTool = {
  name: "suggest_gifts",
  description:
    "Use this when the user asks for gift ideas for a person or occasion.",
  inputSchema: { /* ... */ },
  annotations: {
    readOnlyHint: true
  },
  _meta: {
    "openai/widgetDescription": "Карусель подарков с ценой и ссылкой.",
    "openai/isConsequential": false
  }
};

Такой инструмент модель может вызывать несколько раз подряд, в том числе «превентивно», чтобы подготовить варианты заранее, не спрашивая пользователя про каждое действие.

Пример описания инструмента create_checkout_session

const createCheckoutTool = {
  name: "create_checkout_session",
  description:
    "Finalize purchase of selected gifts via Instant Checkout.",
  inputSchema: { /* ... */ },
  annotations: {
    destructiveHint: true
  },
  _meta: {
    "openai/isConsequential": true
  }
};

Здесь мы явно сигналим: это write‑операция, у неё последствия (деньги списались, заказ создан), и модель должна запросить подтверждение пользователя перед вызовом, особенно в длинных цепочках с несколькими App.

Важно не переоценивать магию: даже с destructiveHint вы обязаны на сервере перепроверять входные данные, токены и права, как мы обсуждали в модулях про безопасность и авторизацию. Но с точки зрения Multi‑App‑оркестрации аннотации помогают модели не «стрелять» такими инструментами без нужды.

6. Изолированный App vs экосистема: уточняем границы GiftGenius

Когда GiftGenius был единственным App в чате, можно было позволить себе довольно широкий scope: подбор подарков, советы по упаковке, напоминания о праздниках, даже небольшие мотивационные тексты для поздравлений. Модель всё равно вызовет только ваши инструменты.

В Multi‑App‑сценарии такой «я делаю всё» подход начинает вредить:

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

Лучший подход — чётко очертить зону ответственности:

  • GiftGenius: только идеи подарков + помощь с покупкой через ACP/Checkout;
  • CalendarApp: события и напоминания;
  • Finance‑App: бюджет пользователя в целом, личный финансовый план.

В описаниях App и инструментов полезно явно прописывать не только «Use this when…», но и «Do not use when…». Официальный discovery‑playbook именно это и рекомендует.

Мини‑пример описания инструмента:

description: `
Use this tool when the user explicitly asks for gift suggestions.
Do not use for generic product discovery or price comparison.
`

Такие ограничения не только помогают роутингу, но и делают поведение вашего App более предсказуемым для продукта и QA.

7. Паттерны композиции Apps: pipeline, handoff, shared context

В Multi‑App‑сценариях на практике всплывают три связанные идеи:

  • pipeline — несколько App идут один за другим (календарь → подарки → commerce), каждый выполняет свой шаг;
  • handoff — вывод одного App становится входом для следующего;
  • shared context — вся эта передача происходит через общий текстовый контекст чата, без прямых HTTP‑вызовов между приложениями.

Как мы уже намекали, Multi‑App‑сценарий — это не магия «App A вызывает App B по HTTP». В текущей реализации ChatGPT Apps изоляция довольно жёсткая: приложения не вызывают друг друга напрямую, коммуникация идёт через общий текстовый контекст.

Базовый паттерн можно сформулировать так:

  1. App A возвращает в чат текст или JSON (часто внутри structuredContent/виджета).
  2. Модель читает этот вывод.
  3. В следующем ходе она может вызвать App B, подставив детали из ответа A в аргументы его tools.

Это называют text/context handoff: «Вывод App A → модель → вход App B».

Пример: CalendarApp + GiftGenius + CommerceApp

Разберём конкретный сценарий.

Пользователь: «У шефа завтра день рождения, подбери подарок и сразу оформи покупку».

По шагам:

  1. Модель понимает, что нужно сначала понять дату и человека. Она вызывает инструмент календарного App, условно corporate_calendar.list_upcoming_birthdays, и получает структуру:

    [
      { "name": "Алексей Быков", "date": "2025-11-22", "relation": "manager" }
    ]
    
  2. Дальше модель решает, что пора позвать GiftGenius. Она вызывает ваш suggest_gifts с аргументами, полученными из календаря:

    {
      "recipientName": "Алексей",
      "occasion": "birthday",
      "budget": 150,
      "relationship": "manager"
    }
    

    Виджет GiftGenius показывает карусель подарков, а текстовый ответ объясняет, почему эти идеи уместны.

  3. Пользователь выбирает один‑два варианта (кнопкой в виджете → widgetState), и модель вызывает уже инструмент commerce‑App, например corp_checkout.create_gift_order, с ID выбранных SKU и адресом доставки.

С точки зрения ChatGPT это три разных приложения, но для пользователя — единый разговор. Ключ к тому, чтобы это работало:

  • чёткие descriptions инструментов каждого App;
  • аккуратные имена (corporate_calendar.list_upcoming_birthdays, а не просто list_events);
  • согласованный формат структурированных данных (чтобы идея подарка была описана так, что commerce‑App может её понять).

Визуальная схема

Можно изобразить этот pipeline так:

sequenceDiagram
    participant U as Пользователь
    participant C as ChatGPT (Router)
    participant Cal as CalendarApp
    participant G as GiftGenius
    participant Com as CommerceApp

    U->>C: У шефа завтра ДР, подбери и оформи подарок
    C->>Cal: tools.call(list_upcoming_birthdays)
    Cal-->>C: [{ name, date, relation }]
    C->>G: tools.call(suggest_gifts, { recipient, occasion, budget })
    G-->>C: gift suggestions (+ widget)
    C-->>U: Пояснения + виджет GiftGenius
    U->>C: Нравится вариант #2, купи его
    C->>Com: tools.call(create_gift_order, { skuId, address })
    Com-->>C: Order confirmation
    C-->>U: Готово, заказ оформлен

Ваша задача как разработчика GiftGenius — сделать так, чтобы в этом хоре ваш голос звучал чётко и по делу, а не мешался с другими.

8. Интероперабельность: делаем ответы пригодными для других Apps

В Multi‑App‑мире мало просто «красиво ответить пользователю». Желательно, чтобы ваш toolOutput можно было машинно обработать другим приложением: commerce‑App, аналитическим агентом, workflow‑оркестратором и т.д.

Это означает пару практических вещей:

  • использовать структурированный JSON в ответах инструментов, а не сериализованный «человеческий» текст;
  • стараться придерживаться стабильных, понятных полей.

Например, можно типизировать результат suggest_gifts так:

export type GiftSuggestion = {
  id: string;
  title: string;
  description: string;
  price: number;
  currency: string;
  forPerson: string;
  occasion: string;
  purchaseUrl: string;
};

И в ответе инструмента возвращать массив таких объектов:

{
  "gifts": [
    {
      "id": "sku_123",
      "title": "Настольный планетарий",
      "description": "Мини-проектор звездного неба...",
      "price": 89.99,
      "currency": "USD",
      "forPerson": "Алексей",
      "occasion": "birthday",
      "purchaseUrl": "https://shop.example.com/sku_123"
    }
  ]
}

Виджет GiftGenius возьмёт это как props и красиво отрендерит карточки, а commerce‑App, увидев этот JSON в контексте, сможет подхватить id и purchaseUrl для дальнейшего чекаута.

Практика Multi‑App‑сценариев показывает: хороший App возвращает данные так, чтобы их мог «съесть» другой App, а не только глаза человека.

9. Практический рефакторинг GiftGenius под Multi‑App

Давайте сведём всё к нескольким конкретным изменениям в нашем учебном приложении.

Уточняем manifest‑description

Представим, что у нас есть openai-app.json (или эквивалент в Next.js‑шаблоне) с описанием:

{
  "name": "GiftGenius",
  "description": "Gift assistant for finding and buying presents."
}

Сделаем его более явным для роутинга:

{
  "name": "GiftGenius",
  "description": "Assistant for gift ideas and purchase flows. Use this app when the user asks what to gift a specific person or for a specific occasion within a budget. Do not use for generic online shopping or personal finance planning."
}

Теперь из этого текста уже видно, что это не общий шопинг, не финконсультант и не календарь.

Переписываем descriptions инструментов

Инструмент поиска подарков:

const suggestGiftsTool = {
  name: "giftgenius_suggest_gifts",
  description: `
Use this when the user asks for gift ideas for a specific person or group,
optionally with a budget or occasion.
Do not use for non-gift product recommendations or travel booking.
`
};

Инструмент, который подтягивает подробности SKU из вашего каталога (read‑only):

const getGiftDetailsTool = {
  name: "giftgenius_get_gift_details",
  description: `
Use this to fetch more details for a gift suggested earlier by GiftGenius,
for example when the user asks “tell me more about option #2”.
`,
  annotations: { readOnlyHint: true }
};

Инструмент покупки — с destructiveHint, как уже показывали.

Обновляем _meta["openai/widgetDescription"]

Допустим, в нашем виджете уже есть карточки с CTA «Купить». Подскажем это модели:

const giftWidgetMeta = {
  _meta: {
    "openai/widgetDescription": `
Показывает список карточек подарков с описанием, ценой и кнопкой 'Купить'.
Модель не должна повторять полный список в тексте, а лишь прокомментировать выбор и помочь принять решение.
`
  }
};

Теперь модель будет реже расписывать простыню из десяти подарков в чате, если они и так видны во виджете, а сосредоточится на объяснениях и логике — что и полезно и для UX, и для стоимости токенов.

10. Multi‑App‑мышление для вашего будущего продукта

Важно переключить голову из режима «как мне победить всех конкурентов и быть единственным App пользователя» в режим «как сделать свой App идеальным модулем в большой экосистеме».

Такой подход даёт несколько практических выгод:

  • вам проще объяснять пользователю и ревьюверам Store, зачем ваш App и когда он уместен;
  • модели проще принимать решение о роутинге: меньшая путаница, меньше «не тех» вызовов;
  • вы сможете сознательно проектировать композиции: сегодня — с календарём и commerce, завтра — с корпоративным HR‑ботом или внутренней CRM.

Официальные гайды по discovery акцентируют: проектируйте «one job per tool», а метаданные рассматривайте как живой артефакт, который нужно тестировать и обновлять, а не статичный текст из первого коммита.

Здесь очень поможет то, что вы уже сделали в предыдущих темах Модуля 20: golden‑кейсы, LLM‑evals, CI‑прогон. Вы можете расширить набор кейсов сценариями «в чате есть и GiftGenius, и CalendarApp», и отслеживать, как изменения в описаниях влияют на выбор App и качество ответов.

11. Типичные ошибки при работе с Multi‑App и композицией

Ошибка №1: описание App «я умею всё на свете».
Если вы пишете в manifest‑description что‑то вроде «умный помощник для любых задач», вы конкурируете не только с другими App, но и с базовым ChatGPT. Роутеру становится сложно понять, когда он обязан звать именно вас, а когда достаточно встроенных возможностей. В Multi‑App‑мире выигрывают приложения с чётким, узким назначением: «подбор подарков», «управление календарём», «анализ логов».

Ошибка №2: размытые descriptions инструментов и коллизии имён.
Инструменты с именами get_data, process_request и пояснением «обрабатывает данные пользователя» прекрасно подходят, чтобы запутать модель. В мире нескольких App легко оказаться в ситуации, когда ваш tool вызывается там, где речь о совершенно другом домене. Правильный путь — связывать домен и действие (giftgenius_get_gift_catalog, calendar.list_birthdays) и явно описывать «Use this when… / Do not use when…».

Ошибка №3: игнорирование _meta["openai/widgetDescription"].
Разработчики часто заполняют только description, а про _meta вспоминают в лучшем случае ради локали. В результате модель не понимает, что именно показывает виджет, и начинает либо дублировать UI текстом, либо, наоборот, обещать пользователю «таблицу с ценами», которой в вашем виджете нет. Пара строк в widgetDescription экономит много таких недоразумений.

Ошибка №4: отсутствие аннотаций readOnlyHint/destructiveHint.
Если все ваши инструменты выглядят одинаково «нейтральными», модель не различает, какие из них безопасно вызывать часто, а какие требуют подтверждения пользователя. В многошаговых сценариях с несколькими App это особенно критично: можно случайно сделать несколько write‑операций подряд без явного участия человека. Не забывайте помечать read‑only‑tools и явно выделять consequential/destructive действия.

Ошибка №5: ответы, рассчитанные только на человека, а не на другие Apps.
Возвращать из инструмента просто «перечень подарков» одной строкой текста соблазнительно, но тогда любому другому App сложнее использовать этот результат. Структурированный JSON с ясными полями (id, price, currency, purchaseUrl, occasion) даёт вам бонус как в UI, так и в композиции: модель может подставить эти данные в аргументы других tools без парсинга естественного языка.

Ошибка №6: попытка внутри одного App реализовать всё, что потенциально может понадобиться пользователю.
Иногда хочется: «ну раз я уже сделал GiftGenius, пусть он ещё и календарь ведёт, и письма коллегам рассылает, и бюджет планирует». В изолированном мире это ещё терпимо, но в Multi‑App‑контексте вы превращаетесь в комбайн, который конфликтует с другими узкими, хорошо заточенными App. Правильнее договориться с собой: мой App делает X и делает это идеально; остальное — чужая ответственность. Такой дизайн сильно упрощает и UX, и развитие экосистемы.

Ошибка №7: отсутствие тестирования поведения в окружении других App.
Разработчики часто тестируют своё приложение в Dev Mode в «чистом» чате, где других App нет. А в Store пользователь легко может иметь десяток подключенных приложений, часть из которых концептуально пересекаются с вашим. Не поленитесь создать тестовый сценарий, где в чате есть соседние App (календарь, общий шопинг, финансы), и прогнать golden‑кейсы: правильно ли модель выбирает GiftGenius в подарочных запросах и не путает ли его с другими участниками?

1
Задача
ChatGPT Apps, 20 уровень, 4 лекция
Недоступна
WidgetDescription как “инструкция для роутера и UI”
WidgetDescription как “инструкция для роутера и UI”
1
Задача
ChatGPT Apps, 20 уровень, 4 лекция
Недоступна
Namespacing инструментов + дискриминативные descriptions (анти‑коллизии)
Namespacing инструментов + дискриминативные descriptions (анти‑коллизии)
1
Опрос
LLM-Apps: новое поколение, 20 уровень, 4 лекция
Недоступен
LLM-Apps: новое поколение
LLM-Apps: новое поколение
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ