CodeGym /Kursy /ChatGPT Apps /Od instrukcji do projektu tools i metadanych: discovery i...

Od instrukcji do projektu tools i metadanych: discovery i routowanie

ChatGPT Apps
Poziom 5 , Lekcja 4
Dostępny

1. Dlaczego same instrukcje nie wystarczą bez dobrych tools i metadanych

Warto uświadomić sobie jedną nieprzyjemną prawdę: model nie widzi waszego kodu. Nie wie, jakie macie kontrolery w Next.js, jakie funkcje w TypeScript i jakie cudowne heurystyki zebraliście w serwisie rekomendacji.

Widzi wasz App przez kilka interfejsów:

  1. System‑prompt (kontrakt roli).
  2. Opisy narzędzi: nazwa, description, inputSchema, outputSchema, adnotacje itp.
  3. Metadane samej aplikacji: nazwa, ikona, krótki i długi opis, kategorie, conversation starters itp.

Podczas obsługi zapytania model patrzy na kontekst dialogu i te metadane, aby zdecydować:

  • czy w ogóle proponować jakiś App;
  • jeśli tak — który z dostępnych;
  • a jeśli App został wybrany — które narzędzie tego App pasuje do bieżącego zapytania.

W poprzedniej części Modułu 5 zajmowaliśmy się tym, co można „opowiedzieć” modelowi słowami — system‑prompt i instrukcje UX. Teraz przechodzimy do tego, co widzi poza tekstem: tools i metadane.

Dlatego zadanie Modułu 5 jest w istocie podwójne. Najpierw w system‑prompt formułujecie „co ten App ma robić i jak się zachowywać”, a następnie w projekcie tools i metadanych pakujecie to w formę, którą model faktycznie potrafi wykorzystać — również do discovery i routowania.

Można to sobie ująć tak: system‑prompt to konstytucja, a tools i metadane — to już prawo i cała biurokracja wokół: formularze wniosków, schematy baz danych itd. Jeśli ograniczymy się tylko do konstytucji, daleko nie zajedziemy.

2. Dekompozycja: „jedno zadanie — jeden tool”, ale z głową

Zacznijmy od najbardziej bolesnego: ile w ogóle narzędzi robić i jak je pociąć.

Intuicyjna zasada: jedno narzędzie — jedno zrozumiałe zadanie. To bardzo ułatwia modelowi wybór: nie ma jednej monstrualnej funkcji do_everything, tylko kilka schludnych akcji z dobrymi nazwami.

Dla GiftGenius możemy mieć takie bazowe narzędzia:

  • profile_to_segments — zamienić swobodny opis odbiorcy (wiek, zainteresowania, relacja, kontekst) na znormalizowane segmenty typu "tech", "fitness", "gamer".
  • recommend_gifts — dobrać listę id prezentów na podstawie segmentów, budżetu, lokalizacji i okazji.
  • get_gift — pobrać pełną kartę wybranego prezentu (opis, media, SKU/warianty) po jego id.
  • (opcjonalnie) similar_gifts — na bazie wybranego prezentu zaproponować jeszcze 3–5 podobnych wariantów.

Teoretycznie można by zrobić jeden gift_tool z parametrem mode: "profile_to_segments" | "recommend" | "details" | "similar", ale wtedy utrudniacie życie i sobie, i modelowi: opis zamienia się w tasiemiec, inputSchema puchnie, a przy wyborze narzędzia model ma mniej wyraźnych kotwic.

Antywzorzec: God Tool

Wyobraźcie sobie taki schemat:

server.registerTool(
  "gift_tool",
  {
    description: "Różne operacje z prezentami.",
    inputSchema: { /* 50 pól i flag */ },
  },
  async ({ input }) => { /* ogromny switch po mode */ }
);

W głowie modelu wygląda to jak „jest jakieś abstrakcyjne narzędzie od prezentów, a dalej się zobaczy”. To pogarsza trafność wyboru, szkodzi discovery i utrudnia wam utrzymanie.

Ale wpadnięcie w drugą skrajność — zrobienie 50 mikroskopijnych narzędzi na każdy drobiazg — też jest złe. Każde dodatkowe narzędzie trafia do kontekstu, obciąża uwagę modelu i zwiększa ryzyko błędów routowania. Dokumentacja wprost ostrzega: zbyt wiele drobnych narzędzi to minus dla jakości, zwłaszcza gdy ich opisy się pokrywają.

Praktyczna zasada, z której wygodnie korzystać:

  • wszystko, co użytkownik postrzega jako jeden „krok” w scenariuszu (np. pierwszy dobór prezentów na podstawie profilu), to dobry kandydat na osobny tool;
  • to, co zawsze wykonuje się ściśle w ramach tego kroku i nie ma samodzielnego sensu (np. wyliczenie scoringu czy zalogowanie podglądu kart), lepiej zostawić wewnątrz implementacji narzędzia.

Załóżmy, że według tej zasady pocięliście już scenariusze na 2–4 narzędzia. Kolejne ważne pytanie — jak opisać wejścia tych tools tak, aby model mógł z nich korzystać bez zgadywania. Od tego zaczniemy.

3. Rzutujemy use‑cases na Input Schema

Bierzemy teraz jeden konkretny use‑case i szczerze sprawdzamy, jakie dane naprawdę są potrzebne narzędziu.

Weźmy scenariusz: „Darczyńca na deadlinie: dobrać 5–7 pomysłów dla przyjaciela, 25 lat, kocha piłkę nożną i gry planszowe, budżet do 50 $”.

Ze spojrzenia jobs‑to‑be‑done wynika, że zadaniem jądra rekomendacyjnego GiftGenius jest zawęzić wybór do małej listy i obniżyć niepokój „a może wybiorę coś słabego”. Na poziomie rozmowy w czacie asystent potrzebuje:

  • podstawowych informacji o odbiorcy (wiek, płeć, relacja z darczyńcą);
  • zainteresowań/hobby;
  • budżetu i waluty;
  • okazji (urodziny, jubileusz, Nowy Rok itd.);
  • opcjonalnie — kraju/miasta do filtrowania po dostawie.

W architekturze GiftGenius rozkłada się to na dwa kroki:

  1. profile_to_segments(input) przyjmuje „surowe” dane (wiek, zainteresowania, opis tekstowy) i przekształca je w znormalizowane segmenty, z którymi wygodniej dalej pracować.
  2. recommend_gifts(segments, budget, locale, occasion) na podstawie segmentów i budżetu dobiera konkretne id prezentów z katalogu.

Z punktu widzenia kontraktu ChatGPT ↔ MCP ważne jest opisanie właśnie drugiego kroku — schematu recommend_gifts, ponieważ to to narzędzie będzie wykorzystywane w większości scenariuszy doboru.

Nie trzeba przy tym od razu wymagać od użytkownika wszystkiego: model może dopytać część przez follow‑up („a jaki mniej więcej budżet?”). Zatem część pól w profilu może być opcjonalna; ale gdy dochodzimy do recommend_gifts, powinien już istnieć znormalizowany zestaw parametrów.

Przykład: TypeScript + JSON Schema dla recommend_gifts

W serwerze MCP w TypeScript może to wyglądać tak:

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

const server = new McpServer();

server.registerTool(
  "recommend_gifts",
  {
    title: "Rekomendacje prezentów",
    description:
      "Użyj tego narzędzia, gdy trzeba dobrać prezenty na podstawie segmentów odbiorcy, budżetu, locale i okazji.",
    inputSchema: {
      type: "object",
      properties: {
        segments: {
          type: "array",
          description:
            "Lista segmentów odbiorcy, np. ['tech', 'football_fan']. Zwykle pochodzi z profile_to_segments.",
          items: { type: "string" },
          minItems: 1
        },
        budget: {
          type: "object",
          description:
            "Zakres budżetu na prezent w walucie użytkownika (minimum/maksimum).",
          properties: {
            min: {
              type: "number",
              minimum: 0,
              description: "Minimalna kwota, którą użytkownik jest gotów wydać."
            },
            max: {
              type: "number",
              minimum: 0,
              description: "Maksymalna kwota, którą użytkownik jest gotów wydać."
            },
            currency: {
              type: "string",
              minLength: 3,
              maxLength: 3,
              description: "Trzyliterowy kod waluty (np. USD, EUR, RUB)."
            }
          },
          required: ["min", "max", "currency"]
        },
        locale: {
          type: "string",
          description:
            "Locale użytkownika w formacie BCP‑47 (np. 'ru-RU' lub 'en-US')."
        },
        occasion: {
          type: "string",
          description:
            "Okazja na prezent, np. 'birthday', 'new_year', 'anniversary'."
        }
      },
      required: ["segments", "budget", "locale", "occasion"]
    }
  },
  async ({ input }) => {
    // Na razie bez kombinowania — zwrócimy prostą odpowiedź
    return {
      content: [
        {
          type: "text",
          text: `Dobieram prezenty dla segmentów ${input.segments?.join(
            ", "
          )} w budżecie ${input.budget?.min}–${input.budget?.max} ${input.budget?.currency}...`
        }
      ],
      structuredContent: {}
    };
  }
);

Zwróćcie uwagę na kilka kwestii.

Po pierwsze, aktywnie używamy ograniczeń w stylu enum i zrozumiałych opisów. Nawet jeśli formalnie to tylko stringi, description podpowiada modelowi, jakich wartości oczekujemy, co istotnie zwiększa szansę, że poprawnie wypełni argumenty. Zamiast rozmytej frazy "powód": "coś jak urodziny" mamy uporządkowane occasion: "birthday".

Po drugie, opisy pól piszemy nie „dla ludzi z zespołu”, lecz dosłownie jako podpowiedzi dla modelu: co to za pole, jakie typowe wartości, czy jest przykład. Autorzy dokumentacji Apps SDK wprost zalecają dodawać zrozumiałe opisy i przykłady dla każdego parametru.

Czego nie powinno być w schemacie wejściowym

Typowe pasożytnicze pola, które często próbuje się tam wcisnąć:

  • wewnętrzne identyfikatory (tenantId, internalSegment), które i tak można dodać po stronie serwera;
  • rzeczy, których model nie może znać (np. deploymentRegion) — to już wasza odpowiedzialność;
  • pola‑duplikaty historii czatu (np. userPrompt): model i tak widzi wiadomość źródłową, nie każcie mu jej kopiować.

Input Schema to dokładnie to, co model ma ustalić i wypełnić, a nie ogólny worek wszystkiego.

4. Output Schema: nie tylko dane, lecz także sens

W Apps SDK rezultat narzędzia wraca do dialogu jako wiadomość o role: tool. Dalej model decyduje, co z tym zrobić: jak sformatować odpowiedź, jakie follow‑up zadać, czy otwierać widżet itd. Dlatego projekt schematu wyjściowego jest równie ważny jak wejściowego.

Są dwa podejścia.

Wariant „surowe dane” wygląda tak:

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

Model widzi po prostu listę id, bez zrozumienia, dlaczego te warianty tu trafiły, ilu było kandydatów i które są najlepsze. Może coś dorobić, ale ryzyko dziwactw jest większe.

Wariant semantycznie bogaty:

{
  "items": [
    {
      "id": "GIFT_1",
      "score": 0.92,
      "reason": "Silnie dopasowany do segmentu 'football_fan' i mieści się w budżecie."
    },
    {
      "id": "GIFT_2",
      "score": 0.81,
      "reason": "Pasuje dla miłośnika gier planszowych, nieco bliżej górnej granicy budżetu."
    }
  ],
  "meta": {
    "totalCandidates": 27,
    "returned": 5,
    "segmentsUsed": ["football_fan", "board_games"],
    "budget": { "min": 20, "max": 50, "currency": "USD" },
    "advice": "Lepiej zacząć od wariantów z najwyższym score i czytelnym uzasadnieniem."
  }
}

Teraz model może uczciwie wyjaśnić, dlaczego właśnie te prezenty, i budować follow‑up: „Znalazłem 27 wariantów, pokazuję 5 najlepszych, oto dlaczego właśnie one”.

Przykład: opisujemy Output Schema dla recommend_gifts

Dodajmy do opisu narzędzia schemat rezultatu (nawet jeśli technicznie można go nie podawać, lepiej to zrobić — to część kontraktu z modelem):

const recommendGiftsOutputSchema = {
  type: "object",
  properties: {
    items: {
      type: "array",
      items: {
        type: "object",
        properties: {
          id: { type: "string", description: "ID prezentu w katalogu." },
          score: {
            type: "number",
            description: "Ocena dopasowania do profilu (0..1)."
          },
          reason: {
            type: "string",
            description:
              "Krótkie wyjaśnienie, dlaczego prezent pasuje (może być generowane na backendzie)."
          }
        },
        required: ["id", "score"]
      },
      description: "Lista rekomendowanych prezentów z ocenami trafności."
    },
    meta: {
      type: "object",
      properties: {
        totalCandidates: {
          type: "integer",
          description: "Ilu kandydatów łącznie znaleziono w katalogu."
        },
        returned: {
          type: "integer",
          description: "Ile prezentów zwróciło to wywołanie."
        },
        advice: {
          type: "string",
          description:
            "Ogólna wskazówka: np. od jakiego typu prezentów warto zacząć."
        }
      }
    }
  },
  required: ["items"]
};

I użyjmy tego schematu wewnątrz implementacji:

server.registerTool(
  "recommend_gifts",
  {
    title: "Rekomendacje prezentów",
    description:
      "Użyj, gdy trzeba dobrać 3–7 prezentów na podstawie segmentów i budżetu. Zwraca id prezentów i oceny dopasowania; szczegółowe karty pobieraj przez get_gift.",
    inputSchema: /* jak wyżej */,
    // Nie zawsze formalnie podaje się outputSchema, ale do dokumentacji to przydatne:
    // outputSchema: recommendGiftsOutputSchema
  },
  async ({ input }) => {
    const recommendations = await recommendFromCatalog(input); // nasza logika biznesowa

    return {
      content: [
        {
          type: "text",
          text: `Znalazłem ${recommendations.items.length} pasujących pomysłów. Zaraz pokażę najlepsze.`
        }
      ],
      structuredContent: {
        items: recommendations.items,
        meta: {
          totalCandidates: recommendations.meta.totalCandidates,
          returned: recommendations.items.length,
          advice: recommendations.meta.advice
        }
      }
    };
  }
);

Robimy dwie rzeczy: dajemy modelowi minimalny tekst dla użytkownika i jednocześnie dokładamy semantyczny JSON, na podstawie którego może dalej prowadzić dialog i follow‑up.

Jednocześnie get_gift na podstawie id dociągnie pełne karty (nazwa, media, SKU itd.), a widżet GiftGenius wyrenderuje je jako karty prezentów.

5. Nazewnictwo i opisy narzędzi jako podstawa discovery

Teraz najciekawsze: jak nazwy i opisy tools wpływają na to, czy model je wywoła, czy nie.

Dokumentacja i best practice dotyczące metadanych zalecają:

  • używać nazw zorientowanych na działanie: profile_to_segments, recommend_gifts, get_gift, similar_gifts, a nie tool1, search, do_stuff;
  • zaczynać opis w stylu „Use this when… / Użyj tego narzędzia, gdy…”, opisując scenariusze wyzwalające i ograniczenia („nie używaj do…”).

To bezpośrednio wiąże się z waszym golden prompt set. Sformułowania w opisie powinny pokrywać się z realnymi zapytaniami użytkowników. Jeśli w opisie jest „Użyj, gdy użytkownik prosi o dobór prezentu według budżetu i zainteresowań odbiorcy”, a w golden prompt macie „dobierz prezent dla przyjaciela‑gracza do 50 $”, model o wiele łatwiej skojarzy zapytanie z narzędziem.

Przykład dobrego opisu narzędzia

Przeanalizujmy dodatkowe narzędzie GiftGenius — similar_gifts, które pomaga rozszerzyć wybór o podobne pomysły na podstawie konkretnego prezentu:

server.registerTool(
  "similar_gifts",
  {
    title: "Podobne prezenty",
    description:
      "Użyj tego narzędzia, gdy użytkownik wybrał konkretny prezent i chce zobaczyć jeszcze kilka podobnych wariantów. Nie używaj do pierwszego doboru od zera — do tego służy recommend_gifts.",
    inputSchema: {
      type: "object",
      properties: {
        giftId: {
          type: "string",
          description:
            "Identyfikator prezentu z poprzedniego doboru, dla którego trzeba znaleźć podobne warianty."
        },
        limit: {
          type: "integer",
          description:
            "Ile podobnych prezentów zwrócić (domyślnie 3–5).",
          minimum: 1,
          default: 5
        }
      },
      required: ["giftId"]
    }
  },
  async () => {
    /* ... */
  }
);

Ważne punkty:

  • Wyraźnie mówimy, kiedy narzędzie trzeba użyć, a kiedy nie.
  • W opisie pojawiają się słowa „podobne warianty”, „wybrał konkretny prezent” — właśnie te, które często występują w realnych zapytaniach użytkowników.
  • Unikamy nakładania się z obszarem recommend_gifts — to zmniejsza konkurencję między narzędziami przy wyborze.

Przykład złego opisu

description: "Praca z prezentami."

Model z takiego opisu niewiele rozumie. Takie narzędzie może zadziałać tylko wtedy, gdy GPT desperacko próbuje coś wywołać „na chybił trafił”.

6. Adnotacje i hints: jak zasugerować modelowi wagę działania

Narzędzie to nie tylko nazwa i schemat, lecz także adnotacje, które podpowiadają ChatGPT, jak niebezpieczna/istotna jest akcja i czy trzeba prosić użytkownika o potwierdzenie. W specyfikacji Apps SDK są do tego różne hints, jak readOnlyHint, destructiveHint, openWorldHint i inne.

  • readOnlyHint: true mówi, że narzędzie tylko czyta dane i nie zmienia stanu. Wtedy asystent może pominąć zbędne potwierdzenia i wywoływać je swobodniej.
  • destructiveHint: true sygnalizuje, że narzędzie może coś usunąć lub nieodwracalnie zmienić, więc trzeba pokazać użytkownikowi wyraźne „Czy na pewno?”.
  • openWorldHint: true wskazuje, że działanie dotyka świata zewnętrznego (postowanie w mediach społecznościowych, tworzenie wpisu poza kontem itd.), o czym także warto uprzedzić.

Minimalny poziom — bez potwierdzeń

Jeśli macie public readonly tools, warto oznaczać je jako readOnlyHint: true. Przykład:

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

Takie narzędzia można wywoływać bez zbędnych dialogowych potwierdzeń po stronie GPT.

Jedno potwierdzenie

Jeśli macie tools, które coś zmieniają na serwerze, logiczne jest oznaczenie ich jako readOnlyHint: false:

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

Model, widząc takie narzędzie, najpewniej poprosi użytkownika o potwierdzenie jeden raz (zwykle to modalne okno dialogowe w UI ChatGPT).

Niebezpieczne działanie

Jeśli macie tool, który coś usuwa na serwerze, oznaczcie go jako destructiveHint: true:

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

Model będzie bardzo ostrożnie wywoływać taki tool i dwukrotnie dopyta:

  • najpierw poprosi o potwierdzenie w tekście,
  • potem platforma pokaże standardowe okno dialogowe.

Dla naszego GiftGenius w ramach tego modułu nie piszemy jeszcze narzędzi commerce, ale można naszkicować, jak wyglądałby przyszły create_gift_order:

server.registerTool(
  "create_gift_order",
  {
    title: "Utworzenie zamówienia na prezent",
    description:
      "Używaj tylko po wyraźnej zgodzie użytkownika na zakup wybranego prezentu. Tworzy zamówienie w systemie i zwraca status.",
    inputSchema: {
      type: "object",
      properties: {
        giftId: {
          type: "string",
          description: "ID prezentu, który użytkownik wybrał."
        },
        deliveryEmail: {
          type: "string",
          description: "Email, na który trzeba wysłać prezent cyfrowy."
        }
      },
      required: ["giftId", "deliveryEmail"]
    },
    annotations: {
      destructiveHint: true,
      openWorldHint: true
    }
  },
  async () => {
    /* ... */
  }
);

Adnotacje nie zastępują waszych kontroli uprawnień po stronie serwera, one jedynie pomagają ChatGPT ułożyć UX: zapytać o potwierdzenie, pokazać ostrzeżenie i nie wykonywać takich narzędzi „po cichu”.

7. Metadane App i dwa poziomy discovery

Narzędzia to połowa historii. Druga połowa to: jak użytkownik w ogóle znajduje i uruchamia wasz App.

W ekosystemie ChatGPT są dwa kluczowe poziomy discovery.

Pierwszy — in‑conversation discovery. Gdy użytkownik pisze coś na czacie (nawet bez jawnego wspomnienia App), model patrzy:

  • na tekst wiadomości i historię dialogu;
  • na opisy dostępnych aplikacji i ich narzędzi;
  • na wzmianki o marce, tematykę i słowa kluczowe.

Na tej podstawie decyduje, czy warto zaproponować jakiś App, a jeśli tak — który i z jakim scenariuszem. Tu szczególnie ważne są opisy narzędzi i samego App. Jeśli zawierają „triggery” typu „dobór prezentów”, „pomysł na prezent”, „budżet prezentu”, szansa, że model wybierze wasz App, rośnie znacząco.

Drugi poziom — globalne discovery: katalog i launcher. Tam rolę odgrywa człowiek: wybiera App wzrokiem po nazwie, ikonie, krótkim opisie i tagach. Ważne, abyście uczciwie i jasno wyjaśnili, co robi wasza aplikacja, dla kogo jest i gdzie leży jej podstawowa wartość.

Można to streścić w krótkiej tabeli:

Warstwa Co widzi model/użytkownik Co jest ważne w metadanych
In‑conversation Tekst dialogu, opisy tools i App Formuły wyzwalające, nazwy akcyjne, ograniczenia
Katalog/launcher Nazwa, ikona, short/long description, tagi Jasne pozycjonowanie, czytelny value‑props

Dla GiftGenius można np. sformułować:

  • Nazwa: GiftGenius — dobór prezentów w 60 sekund.
  • Krótki opis: Zbiera profil odbiorcy i proponuje 5–7 pomysłów na prezenty z możliwością natychmiastowego zakupu w ChatGPT.
  • Opis dla in‑conversation: Używaj tej aplikacji, gdy użytkownik prosi o pomoc w wyborze prezentu, nie wie, co podarować, podaje budżet, zainteresowania odbiorcy lub okazję.

Te sformułowania warto zsynchronizować z tym, co już napisaliście w system‑prompt i w opisach narzędzia recommend_gifts. Wtedy model widzi spójny obraz, a nie zbiór sprzecznych tekstów.

8. Jak routowanie działa „w głowie” ChatGPT

Zbierzmy wszystko i spójrzmy na typową ścieżkę zapytania — bez wchodzenia w protokół MCP, to będzie w kolejnych modułach.

Załóżmy, że użytkownik pisze:

„Pomóż wymyślić prezent dla brata, uwielbia piłkę nożną i planszówki, budżet do 50 dolarów.”

W dużym uproszczeniu algorytm:

  1. Model analizuje wiadomość i historię. Widzi słowa „prezent”, „brat”, „piłka nożna”, „planszówki”, „budżet 50”.
  2. Porównuje to z opisami dostępnych App i ich narzędzi. Dla GiftGenius opisy jednoznacznie zawierają „dobór prezentów według zainteresowań i budżetu”, więc prawdopodobieństwo, że App jest relewantny, jest wysokie.
  3. Jeśli App nie jest jeszcze aktywny w tej sesji, model formułuje zapowiedź: „Mogę otworzyć aplikację GiftGenius, która pomoże dobrać prezent według twoich parametrów. Otworzyć?” — to z góry ujęliśmy w instrukcjach UX.
  4. Po zgodzie użytkownika model wybiera wewnątrz App narzędzie recommend_gifts, ponieważ to jego opis najlepiej odpowiada bieżącej intencji. Tutaj i nazwa, i description, i struktura inputSchema działają jako sygnały wejściowe.
  5. Model uzupełnia argumenty narzędzia na podstawie zapytania: najpierw (w razie potrzeby) wywołuje profile_to_segments, aby z tekstu „brat, lubi piłkę nożną i planszówki” uzyskać segmenty ["football_fan", "board_games"], następnie wywołuje recommend_gifts z segments, budget: {min: 0, max: 50, currency: "USD"}, locale, occasion: "birthday".
  6. Serwer MCP wykonuje narzędzie, formuje structured output z items i meta i zwraca go.
  7. Model czyta JSON, który opisaliście w outputSchema, i buduje odpowiedź: wyjaśnia, co znalazł, dlaczego właśnie te prezenty, i proponuje follow‑up („chcesz zawęzić po kategorii?”, „pokazać podobne do tego prezentu?” lub „sfinalizować zakup tego prezentu?”).

Oto prosta blok‑schemat tego procesu:

flowchart TD
  A[User: zapytanie o prezent] --> B[ChatGPT analizuje kontekst]
  B --> C[Porównanie z metadanymi App i tools]
  C -->|relewantne| D[Zapowiedź GiftGenius]
  D -->|użytkownik się zgadza| E["Wywołanie recommend_gifts (+ profile_to_segments)"]
  E --> F[Serwer MCP GiftGenius]
  F --> G[Wynik JSON z items/meta]
  G --> H[Model formułuje odpowiedź i follow‑up]

Im lepiej opisaliście narzędzia i use‑cases, tym mniej tu przypadkowości i tym stabilniejsze routowanie.

Insight: Tool Call SEO

W ekosystemie Apps wkrótce będziecie konkurować nie tylko o uwagę ludzi w katalogu, ale i o uwagę samego modelu. Na to samo zapytanie użytkownika ChatGPT może przywołać kilkanaście różnych aplikacji, a wybór zajdzie nie w czyjej prezentacji będzie ładniejszy design, lecz w „wynikach wyszukiwania” wewnątrz głowy modelu. Ta niewidzialna warstwa coraz bardziej przypomina SEO, tylko zamiast stron macie tools i serwery MCP.

Model w istocie rangruje kandydatów: najpierw na poziomie App, potem na poziomie poszczególnych narzędzi. Patrzy na nazwę, descriptions, schematy, adnotacje i zestawia je z sformułowaniami zapytania. Jeśli w opisie recommend_gifts jest „dobór prezentów według budżetu i zainteresowań odbiorcy”, a w zapytaniu brzmi „dobierz prezent przyjacielowi‑graczowi za 50 $”, to to narzędzie ma większe szanse „trafić do topu” niż abstrakcyjne search z opisem „praca z prezentami”.

Stąd rodzi się praktyczna idea Tool Call SEO: traktować nazwy, descriptions, wartości enum i metadane jak słowa kluczowe i snippety. Nie opisujecie tylko kontraktu dla developerów — optymalizujecie go pod realny ruch zapytań ze swojego golden prompt set. Zbyt ogólne sformułowania, nachodzące na siebie obszary kilku narzędzi, God‑narzędzia bez wyraźnej niszy — to wszystko obniża „CTR” waszego App w głowie modelu.

9. Małe ćwiczenie praktyczne

Spróbujcie w myślach (albo w swoim repozytorium) wykonać następujące kroki.

Najpierw wybierzcie jeden z kluczowych scenariuszy GiftGenius — np. „Dobrać prezent dla kolegi z pracy z ograniczonym budżetem”.

Sformułujcie dla niego:

  1. Jakie osobne narzędzie jest potrzebne pod ten scenariusz: czy to czyste recommend_gifts, czy potrzebujecie jeszcze wyspecjalizowanego narzędzia dla case’u B2B, czy może wystarczy po recommend_gifts użyć similar_gifts do wariacji?
  2. Jakie pola naprawdę są niezbędne w schemacie wejściowym recommend_gifts. Jakie pola można dopytać użytkownika osobno (przez follow‑up), zamiast kazać modelowi zgadywać.
  3. Jak powinien wyglądać outputSchema, aby model mógł uczciwie wyjaśnić wybór i zaproponować następne kroki (np. przełączyć się na tryb B2B, pokazać tylko prezenty cyfrowe, zawęzić po przedziale cenowym).

A potem spójrzcie na swój golden prompt set z poprzedniego wykładu i sprawdźcie:

  • czy dla każdego wzorcowego zapytania istnieje oczywiste narzędzie (recommend_gifts, get_gift, similar_gifts itd.);
  • czy nie wyszło tak, że dwa narzędzia równie „pasują” do jednego i tego samego zapytania (overlapping tools);
  • czy trzeba wzmocnić opisy albo zmienić nazwę któregoś narzędzia, aby model mylił się rzadziej.

To dokładnie ten proces, który będziecie powtarzać przed każdą poważniejszą zmianą promptu, schematów czy logiki — w istocie mini‑eval jakości discovery.

Jeśli streścić wszystko powyżej w checkliście, na tym etapie potrzebujecie:

  • uczciwie pociąć scenariusze na 2–4 sensowne narzędzia;
  • porządnie opisać inputSchema/outputSchema z przykładami i enum‑ami;
  • uporządkować nazwy, descriptions i adnotacje;
  • zsynchronizować to z system‑prompt i metadanymi App.

W kolejnych modułach zobaczymy już, jak to wszystko działa przez MCP oraz jak diagnozować dziwne zachowania discovery/routowania.

10. Typowe błędy przy projektowaniu tools i metadanych

Błąd nr 1: „Wszystko opisaliśmy w system‑prompt, narzędzia jakoś sobie poradzą”.
Jeśli świetnie rozpisaliście rolę App, granice odpowiedzialności i zachowania UX, ale zostawiliście narzędzia z nazwami tool1, search, do_stuff i schematami bez opisów, model po prostu nie zwiąże waszego ładnego tekstu z realnymi wywołaniami. Dla ChatGPT narzędzia to główny interfejs; bez porządnych metadanych żaden system‑prompt nie uratuje sytuacji.

Błąd nr 2: God Tool, który robi wszystko naraz.
Chęć „optymalizacji” i zrobienia jednej funkcji z parametrem mode jest zrozumiała, ale prowadzi do monstrualnych schematów JSON, zamieszania w opisach i gorszego routowania. Model zaczyna zgadywać, którego trybu użyć, a wy — utrzymujecie ogromny switch na serwerze. Lepiej mieć kilka wyraźnych narzędzi do konkretnych kroków scenariusza niż jedno „zrób wszystko”.

Błąd nr 3: Schemat wejściowy przepełniony polami „na wszelki wypadek”.
Często developerzy próbują od razu przepchnąć przez inputSchema wszystkie parametry, które kiedykolwiek mogą się przydać, plus parę pól wewnętrznych. W efekcie model próbuje zgadywać to, czego nie może znać (np. tenantId), a wy potem dziwicie się dziwnym wartościom. Input Schema powinien zawierać tylko to, co model realnie może wywnioskować z dialogu lub dopytać. Szczegóły wewnętrzne dodawajcie po stronie serwera.

Błąd nr 4: „Niemie” dane wyjściowe bez metainformacji.
Zwracanie z narzędzia po prostu tablicy obiektów bywa kuszące. Ale wtedy odbieracie modelowi zrozumienie, dlaczego te wyniki się pojawiły. Bez pól takich jak score, reason, searchCriteria, totalCandidates trudniej mu budować uczciwe wyjaśnienia i follow‑up. Dodanie niewielkiej otoczki meta z kryteriami wyszukiwania i wskazówkami często radykalnie poprawia jakość odpowiedzi.

Błąd nr 5: Deski w opisach: „Praca z prezentami”, „Wyszukiwanie kursów”, „Przetwarzanie danych”.
Takie opisy są złe, bo nie dają modelowi ani triggerów, ani ograniczeń. Nie wie, kiedy dokładnie trzeba wywołać narzędzie i w jakim obszarze jest ono stosowne. Dobry opis zaczyna się od „Użyj tego narzędzia, gdy…” i zawiera konkretne scenariusze oraz zakazy typu „Nie używaj do…”. Idealnie, jeśli te sformułowania pokrywają się ze złotymi zapytaniami z waszego golden prompt set.

Błąd nr 6: Ignorowanie adnotacji i mieszanie działań read‑only z modyfikującymi.
Jeśli nie oznaczacie narzędzi, które tylko czytają dane (readOnlyHint), i tych, które wykonują akcje (destructiveHint, openWorldHint), model nie może zbudować właściwego UX potwierdzeń. W efekcie albo pojawiają się zbędne „Czy na pewno?” na każdym kroku, albo przeciwnie — ciche zakupy i zmiany bez zgody użytkownika. Adnotacje to tani i skuteczny sposób podpowiedzenia modelowi wagi operacji.

Błąd nr 7: Metadane App do katalogu i metadane do in‑conversation żyją w różnych światach.
Bywa, że krótki opis w katalogu napisał marketing („Rewolucyjny asystent AI, który zmienia twoje życie”), a descriptions tools i system‑prompt — developer („dobór prezentów według budżetu”). W rezultacie w katalogu nie wiadomo, o co chodzi w App, a model w czacie nie może skojarzyć pytań typu „co to za serwis?” z realnymi możliwościami App. Piszcie metadane jako jedną specyfikację, a nie dwa niezależne teksty marketingowe.

1
Ankieta/quiz
Zachowanie ChatGPT App, poziom 5, lekcja 4
Niedostępny
Zachowanie ChatGPT App
Instrukcje dla modelu i zachowanie ChatGPT App
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION