CodeGym /Kursy /ChatGPT Apps /Opis narzędzi: JSON Schema, typowanie, adnotacje

Opis narzędzi: JSON Schema, typowanie, adnotacje

ChatGPT Apps
Poziom 4 , Lekcja 1
Dostępny

1. Narzędzie jako kontrakt: co dokładnie opisujemy

Rejestrując narzędzie w serwerze MCP, opisujesz je za pomocą niewielkiego obiektu. Uproszczona struktura dla TypeScript‑SDK wygląda tak:

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description: "Dobiera prezenty na podstawie profilu obdarowywanego.",
    inputSchema: {
      type: "object",
      // w to właśnie teraz się zagłębimy
    },
  },
  async ({ input }) => {
    // Twój kod
  }
);

Model nie wie, co dzieje się wewnątrz handlera async ({ input }) => { ... }. Dla niego istnieją tylko trzy rzeczy:

  1. name/title — jak nazywa się narzędzie.
  2. description — kiedy należy go używać.
  3. inputSchema — jakie argumenty trzeba przekazać i w jakim formacie.

Wszystko, co robimy w tej lekcji, dotyczy punktu 3 (i trochę metadanych _meta/annotations, o których porozmawiamy później).

Ważne: JSON Schema w kontekście ChatGPT App to nie nudny walidator, lecz część promptu dla modelu. Model rzeczywiście czyta description pól, rozumie, czym jest enum, zauważa minItems, format itd.

Czyli nie tylko chronisz backend przed błędnymi danymi — wyjaśniasz modelowi SI, jak poprawnie wywołać twoją funkcję.

2. Podstawowy JSON Schema dla narzędzia suggest_gifts

Zacznijmy od prostego. Załóżmy, że mamy taki scenariusz:

Użytkownik pisze:
„Dobierz prezent dla brata, 25 lat, budżet 50–70 dolarów, lubi gry wideo i gry planszowe”.

Narzędzie suggest_gifts powinno przyjąć mniej więcej takie argumenty:

  • wiek obdarowywanego;
  • rodzaj relacji (brat, kolega, partner itp.);
  • minimalny i maksymalny budżet;
  • lista zainteresowań.

Opiszmy to jako JSON Schema „wprost”, bez Zod, czystym obiektem:

const suggestGiftsInputSchema = {
  type: "object",
  properties: {
    age: {
      type: "integer",
      minimum: 0,
      maximum: 120,
      description: "Wiek obdarowywanego w latach.",
    },
    relationship: {
      type: "string",
      enum: ["friend", "partner", "sibling", "colleague", "parent"],
      description:
        "Rodzaj relacji z obdarowywanym: friend, partner, sibling (brat/siostra), colleague, parent.",
    },
    minBudget: {
      type: "number",
      minimum: 0,
      description: "Minimalny budżet w walucie użytkownika.",
    },
    maxBudget: {
      type: "number",
      minimum: 0,
      description: "Maksymalny budżet w walucie użytkownika.",
    },
    interests: {
      type: "array",
      items: {
        type: "string",
        description:
          "Krótka nazwa zainteresowania, np.: videogames, boardgames, books.",
      },
      minItems: 1,
      description: "Lista zainteresowań obdarowywanego.",
    },
  },
  required: ["relationship", "maxBudget"],
};

Kilka ważnych uwag, które warto od razu wypowiedzieć.

Po pierwsze, description pól. W zwykłym API mógłbyś ich nawet nie pisać — frontend‑developer i tak przeczyta Swagger i zrozumie. Tutaj jednak „klientem” jest model, który próbuje wyciągnąć sens z nazwy i opisu. Im jaśniej powiesz: „wiek w latach”, „budżet w walucie użytkownika”, „enum ze stałymi wartościami”, tym mniej dziwnych argumentów zobaczysz w runtime.

Po drugie, enum to jedno z najpotężniejszych narzędzi kontroli modelu. Jeśli pozwolisz modelowi wpisywać dowolny string w relationship, dostaniesz „bro”, „girlfriend”, „bestie”, „teammate” i coś jeszcze bardziej kreatywnego. Jeśli zadeklarujesz enum, model z bardzo wysokim prawdopodobieństwem będzie wybierał tylko spośród tych wartości. To bezpośrednio zmniejsza liczbę „halucynacji” w argumentach.

Po trzecie, nie wszystko musi być required. Na przykład age może być opcjonalny: jeśli użytkownik go nie poda, model nie będzie wymyślał „przybliżonego wieku” z powietrza (jeśli tak sformułujesz opis). Tutaj zaczyna się sztuka: balans między elastycznością a rygorem.

Teraz użyjmy tej schemy przy rejestracji narzędzia:

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "Dobiera pomysły na prezenty według budżetu, rodzaju relacji i zainteresowań obdarowywanego.",
    inputSchema: suggestGiftsInputSchema,
  },
  async ({ input }) => {
    // tutaj input już mniej więcej odpowiada schematowi
    // ...
  }
);

Taki „ręczny” obiekt świetnie nadaje się do szybkich eksperymentów. Ale wraz ze wzrostem aplikacji zamienia się w osobny świat, który łatwo rozjedzie się z twoimi typami TypeScript. Wrócimy do tego problemu nieco później i zobaczymy, jak rozwiązać go za pomocą Zod oraz generowania JSON Schema z typów.

3. JSON Schema jako prompt: jak pisać description, żeby model nie cierpiał

Formalnie JSON Schema dotyczy walidacji. Nieformalnie, w świecie LLM — to także ustrukturyzowany prompt. Kilka praktycznych zasad:

  1. Pole description musi odpowiadać na pytanie „co tu włożyć i w jakim formacie”.
    Sformułowanie typu „Data” nie pomaga. Sformułowanie „Data w formacie ISO 8601 YYYY-MM-DD, na przykład "2025-02-14"” — pomaga bardzo.
  2. Jeśli pole dotyczy pieniędzy — doprecyzuj jednostki.
    Lepiej wyraźnie napisać „Kwota w walucie użytkownika” lub „Kwota w dolarach amerykańskich”. W przeciwnym razie model może uczciwie wpisać 50 i będziesz się zastanawiać, czy to 50 jenów, czy 50 euro.
  3. „Kategorie” łańcuchowe prawie zawsze lepiej robić przez enum.
    Jeśli pole to string z „kategorią”, lepiej zrobić enum i opisać każdą wartość w description narzędzia. Na przykład dla relationship możesz w opisie narzędzia napisać: „relationship: jeden z friend (przyjaciel), partner (partner romantyczny), sibling (brat lub siostra), colleague (kolega z pracy), parent (rodzic). Nie wymyślaj innych wartości.”
  4. Dla tablic warto ustawić minItems i wyjaśnić, czym jest ta lista.
    Jeśli pole jest tablicą, warto wskazać minItems i krótko wyjaśnić, co dokładnie to za lista. Na przykład interests — to nie „opis osoby prozą”, lecz „zestaw krótkich tagów”.

Brzmi to trochę nudno, ale w praktyce różnica między „są opisy” a „nie ma opisów” to różnica między stabilną aplikacją a wieczną loterią „co dziś przyśle model”.

Insight

Narzędzia MCP mają twarde ograniczenia rozmiaru — i to one najczęściej stają się przyczyną „mistycznych” padów, dziwnych błędów oraz tego, że asystent nagle przestaje widzieć twoje tools.

Kluczowa zasada jest prosta: narzędzie musi mieścić się w ~4 KB JSON w całości. To nie tylko tekst description, ale cała struktura:

  • opis narzędzia,
  • schema argumentów (inputSchema),
  • zagnieżdżone obiekty i enum,
  • _meta i adnotacje.

Jeśli twoje narzędzie puchnie, platforma zaczyna zachowywać się nieprzewidywalnie: pojawiają się błędy w rodzaju "Tool description is too long", "Schema validation failed", "Manifest exceeds size limits", a czasem ChatGPT po prostu przestaje ładować narzędzie albo „zapomina” o jego istnieniu.

Rekomendacja: trzymaj description w granicach 10002000 znaków, a całe narzędzie — w „bezpiecznych” ~4 KB. Jeśli opis staje się zbyt długi, to prawie zawsze znak, że narzędzie robi zbyt wiele rzeczy naraz. Oddzielne narzędzia powinny być wąskie i bardzo precyzyjne — wtedy model lepiej rozumie ich granice i rzadziej myli się we danych wejściowych.

4. TypeScript i Zod: jedno źródło prawdy zamiast dwóch

Ręczne pisanie JSON‑schem to ból dla programisty TypeScript. Trzeba utrzymywać dwa równoległe światy:

  • typy w kodzie TS;
  • JSON Schema dla modelu.

Wraz ze wzrostem aplikacji zaczynają się rozjeżdżać. Dziś zmienisz pole w typie TypeScript, jutro zapomnisz zaktualizować schemę — i za tydzień łapiesz crash na produkcji.

De facto standardowe podejście w świecie TS to użyć Zod i konwersji Zod -> JSON Schema.

Instalujemy zależności (jeśli jeszcze nie):

npm install zod zod-to-json-schema

Opiszmy wejściową schemę dla suggest_gifts w Zod:

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const SuggestGiftsInputZod = z.object({
  age: z
    .number()
    .int()
    .min(0)
    .max(120)
    .describe("Wiek obdarowywanego w latach."),
  relationship: z
    .enum(["friend", "partner", "sibling", "colleague", "parent"])
    .describe(
      "Rodzaj relacji: friend (przyjaciel), partner (partner), sibling (brat/siostra), colleague (kolega), parent (rodzic)."
    ),
  minBudget: z
    .number()
    .min(0)
    .optional()
    .describe("Minimalny budżet w walucie użytkownika."),
  maxBudget: z
    .number()
    .min(0)
    .describe("Maksymalny budżet w walucie użytkownika."),
  interests: z
    .array(
      z
        .string()
        .min(1)
        .describe(
          "Krótki tag zainteresowania, np.: videogames, boardgames, books."
        )
    )
    .min(1)
    .describe("Lista zainteresowań obdarowywanego."),
});

Teraz masz:

  1. Walidację w runtime: SuggestGiftsInputZod.parse(input);
  2. Typ TypeScript: type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;
  3. JSON Schema dla modelu: zodToJsonSchema(SuggestGiftsInputZod).

Użyjmy tego przy rejestracji narzędzia:

type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;

const suggestGiftsInputSchemaJson = zodToJsonSchema(
  SuggestGiftsInputZod,
  "SuggestGiftsInput"
);

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "Dobiera pomysły na prezenty według budżetu, rodzaju relacji i zainteresowań obdarowywanego.",
    inputSchema: suggestGiftsInputSchemaJson,
  },
  async ({ input }) => {
    // tutaj input można dodatkowo sprawdzić Zodem:
    const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;

    // dalej pracujemy z typizowanym args
  }
);

Takie podejście daje właśnie ten single source of truth — jedno źródło prawdy: opisujesz schemę raz, a typ TypeScript i JSON Schema generują się automatycznie.

W prawdziwym świecie dodasz do tego testy, które sprawdzają, że zodToJsonSchema zwraca oczekiwaną strukturę, ale to już temat modułu o testowaniu.

Insight: ChatGPT słabo radzi sobie z parametrami opcjonalnymi

Jeden z najbardziej bolesnych przypadków w produkcji: gdy tylko zaczynasz aktywnie używać pól optional w schemach narzędzi, jakość wywołań (tool‑calls) zauważalnie spada. Model w teorii „rozumie”, czym są parametry nieobowiązkowe, ale w praktyce najczęściej po prostu ich nie przesyła — nawet wtedy, gdy z logiki biznesowej są ci bardzo potrzebne.

Ten problem elegancko rozwiązano w Response API: po prostu usunięto pola optional — wszystkie parametry narzędzia muszą być zadeklarowane jako required. Ale problem nie znika, jeśli trzymasz się idei „oznaczę połowę pól jako nieobowiązkowe, a model sam zdecyduje, co wypełnić”: zwykle po prostu nic nie przyśle.

5. Gdzie kończy się „schemat”, a zaczyna „projekt interfejsu”

Do tej pory mówiliśmy o inputSchema — czyli o tym, jakie argumenty model ma wygenerować do uruchomienia narzędzia. Ale po wywołaniu narzędzia życie się nie kończy: wynik trzeba jeszcze wyrenderować w UI.

Warto rozdzielić dwa poziomy:

  • Schema dla narzędzia opisuje parametry wejściowe, które model ma wygenerować. To zawsze JSON, który żyje w przestrzeni MCP / tool‑call.
  • Komponent UI (widżet) czyta toolOutput.structuredContent i na jego podstawie buduje interfejs. Format structuredContent też projektujesz sam, ale to już nie jest JSON Schema dla modelu (choć możesz to sobie sformalizować).

Czasem deweloperzy próbują jednym obiektem JSON zabić dwie pieczenie — połączyć wejścia dla modelu i format danych dla UI. Rzadko kończy się to dobrze. Wygodniej rozdzielić:

  • inputSchema — o tym, czego potrzebuje model, by uruchomić narzędzie;
  • structuredContent — o tym, czego potrzebuje UI, by wyrenderować wynik.

Na przykład inputSchema dla suggest_gifts nie zawiera żadnych id prezentów. A structuredContent przeciwnie — zawiera listę kart z id, title, price, linkiem do zakupu itp.

6. Adnotacje i _meta: jak wpływać na UX i bezpieczeństwo

Oprócz samej schemy parametrów i struktury odpowiedzi jest jeszcze jedna warstwa — to, jak platforma traktuje narzędzie i pokazuje je użytkownikowi. Za to odpowiadają metadane i adnotacje.

Oprócz standardowych pól title, description, inputSchema, narzędzie może mieć dodatkowe metadane i adnotacje. W Apps SDK i MCP część tych rzeczy żyje w _meta (np. securitySchemes), a część — w specjalnych polach, jak wskazówki specyficzne dla OpenAI, np. readOnlyHint i destructiveHint.

Ważne: te adnotacje nie zmieniają JSON Schema, ale wpływają na to, jak ChatGPT pokazuje narzędzie użytkownikowi i jak podchodzi do jego wywołania.

Przykład: readOnlyHint i destructiveHint

Załóżmy, że masz dwa narzędzia:

  • list_gifts — po prostu pobiera listę prezentów (bezpieczne);
  • create_order — tworzy zamówienie (potencjalnie niebezpieczne: pieniądze, adres — poważna sprawa).

Możesz je oznaczyć mniej więcej tak (pseudokod):

server.registerTool(
  "list_gifts",
  {
    title: "List gift suggestions",
    description: "Pobiera listę dostępnych prezentów według podanych filtrów.",
    inputSchema: listGiftsInputSchema,
    _meta: {
      readOnlyHint: true,
    },
  },
  async ({ input }) => { /* ... */ }
);

server.registerTool(
  "create_order",
  {
    title: "Create gift order",
    description:
      "Tworzy zamówienie na konkretny prezent w imieniu użytkownika. Używaj dopiero po wyraźnym potwierdzeniu.",
    inputSchema: createOrderInputSchema,
    _meta: {
      destructiveHint: true,
    },
  },
  async ({ input }) => { /* ... */ }
);

Semantyka jest następująca. readOnlyHint sygnalizuje ChatGPT, że narzędzie niczego nie zmienia i jest bezpieczne; model i UI mogą wywoływać je swobodniej. destructiveHint oznacza, że narzędzie wykonuje nieodwracalne lub krytyczne działania, dlatego użytkownik częściej zobaczy prośbę o potwierdzenie, a model będzie ostrożniejszy.

W twojej aplikacji Gift suggest_gifts jest wyraźnie read‑only, a narzędzia do składania zamówień, obciążania środków i zmiany danych użytkownika lepiej oznaczać jako potencjalnie destructive.

openWorldHint i podobne pola

W niektórych przypadkach chcesz zasugerować modelowi, że narzędzie działa w „otwartym świecie”, czyli jego wyniki nie są wyczerpujące. Na przykład search_products nigdy nie zwróci wszystkich produktów, które istnieją na świecie, a jedynie te relewantne.

Takie adnotacje pomagają modelowi nie wyciągać zbyt mocnych wniosków w stylu „jeśli produktu nie ma w search_products, to znaczy, że nie istnieje”. To subtelny element UX, ale w aplikacjach produkcyjnych różnica jest wyraźna.

_meta wokół wyświetlania UI

Gdy twoje narzędzie zwraca wynik, możesz dodatkowo wskazać w _meta ustawienia wpływające na widżet. Na przykład: którego szablonu HTML użyć jako output‑template, czy potrzebne są obramowania, jaki napis pokazywać podczas wywołania itp.

W oficjalnym przykładzie serwer osobno rejestruje HTML widżetu jako zasób MCP, a następnie odwołuje się do niego przez _meta["openai/outputTemplate"].

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description: "Dobiera pomysły na prezenty.",
    inputSchema: suggestGiftsInputSchemaJson,
    _meta: {
      "openai/outputTemplate": "ui://widget/gifts.html", // To jest ID zasobu MCP: server.registerResource(...)
      "openai/toolInvocation/invoking": "Dobieram prezenty…",		// Wyświetlane w trakcie wyszukiwania
      "openai/toolInvocation/invoked": "Znaleziono propozycje prezentów",   // Wyświetlane po zakończeniu wyszukiwania
    },
  },
  async ({ input }) => {
    // ...
    return {
      content: [],
      structuredContent: { items: gifts },
    };
  }
);

W ten sposób w jednym miejscu opisujesz:

  • formę danych wejściowych dla modelu (inputSchema);
  • to, jak narzędzie będzie wyglądać i zachowywać się w UI (_meta).

7. Projektowanie schem: o co prosić model, a o co — nie

Jedna z typowych pułapek — próbować zrzucić całą pracę na model. Na przykład opisujesz w inputSchema pole giftId, a w description piszesz: „UUID prezentu z naszej bazy danych”. Model oczywiście spróbuje uczciwie wygenerować UUID w stylu "0f21b5f0-5a3a-4d1b-8f0b-9f1a6e3c1234", tylko problem w tym, że taki prezent u ciebie najpewniej nie istnieje.

Dobra zasada: nie proś modelu o generowanie technicznych identyfikatorów i danych powiązanych z twoim wewnętrznym światem.

Zamiast tego zrób scenariusz wieloetapowy:

  1. suggest_gifts zwraca listę prezentów z id, title, price itp.;
  2. UI/model pozwalają użytkownikowi wybrać jedną z propozycji;
  3. create_order przyjmuje giftId z już istniejącego zestawu.

Z punktu widzenia schem oznacza to, że:

  • inputSchema narzędzi, które patrzą „na zewnątrz” (na użytkownika), opisują tylko to, co człowiek może sensownie wprowadzić: parametry wyszukiwania, filtry, kryteria;
  • inputSchema narzędzi operujących wewnętrznymi bytami opierają się na już znanych id, a nie wymagają od modelu ich wymyślania.

Dla twojej aplikacji Gift oznacza to, że w suggest_gifts nie prosisz modelu o „wymyślenie kodu SKU”, a jedynie o parametry zapytania. Same SKU dodasz po stronie backendu, a UI pokaże je użytkownikowi.

Uwaga: SKU to międzynarodowy unikalny kod produktu. Przykład "GFT-CHC-500-BS".

8. Mały blok praktyczny: złóżmy wszystko razem

Zbierzmy w jednym miejscu wszystko, o czym mówiliśmy wyżej: schemę Zod, generowanie JSON Schema, rejestrację narzędzia z _meta i użycie schemy w logice biznesowej. Zbudujmy minimalny, ale spójny przykład dla aplikacji Gift.

Najpierw schema Zod i typ:

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const SuggestGiftsInputZod = z.object({
  relationship: z
    .enum(["friend", "partner", "sibling", "colleague", "parent"])
    .describe("Rodzaj relacji z obdarowywanym."),
  maxBudget: z
    .number()
    .min(0)
    .describe("Maksymalny budżet w walucie użytkownika."),
  interests: z
    .array(
      z
        .string()
        .min(1)
        .describe("Krótki tag zainteresowania, np.: videogames.")
    )
    .min(1)
    .describe("Lista zainteresowań obdarowywanego."),
});

type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;

const suggestGiftsInputSchemaJson = zodToJsonSchema(
  SuggestGiftsInputZod,
  "SuggestGiftsInput"
);

Dalej — rejestracja narzędzia z _meta dla UI:

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "Używaj, gdy trzeba dobrać pomysły na prezenty według budżetu, relacji i zainteresowań.",
    inputSchema: suggestGiftsInputSchemaJson,
    _meta: {
      "openai/outputTemplate": "ui://widget/gifts.html",
      "openai/toolInvocation/invoking": "Dobieram prezenty…",
      "openai/toolInvocation/invoked": "Znaleziono propozycje prezentów",
      readOnlyHint: true,
    },
  },
  async ({ input }) => {
    const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;

    const gifts = await findGifts(args); // Twoja logika biznesowa

    return {
      content: [],
      structuredContent: {
        items: gifts,
      },
    };
  }
);

Gdzieś obok będziesz mieć typizowaną funkcję biznesową:

async function findGifts(input: SuggestGiftsInput) {
  // tutaj możesz używać input.relationship, input.maxBudget, input.interests
  // i zwrócić tablicę obiektów typu Gift
  return [
    {
      id: "gift-1",
      title: "Gra planszowa inspirowana grami wideo",
      price: 45,
      currency: "USD",
    },
  ];
}

Po stronie widżetu weźmiesz potem window.openai.toolOutput.structuredContent.items i wyrenderujesz karty, ale o tym szerzej za dwie lekcje.

9. Typowe błędy przy opisie narzędzi

Błąd nr 1: Zbyt ogólne lub bezsensowne opisy pól.
Jeśli piszesz description: "Data" lub description: "Parametr filtra", model dostaje praktycznie zero użytecznej informacji. To jak dokumentacja w stylu „metoda robi coś ważnego”. Używaj opisów, które odpowiadają na pytania „co tu włożyć” i „w jakim formacie”. Na przykład: „Data w formacie ISO 8601 YYYY-MM-DD, np. "2025-02-14"” lub „Kwota w walucie użytkownika, przykład: 49.99”.

Błąd nr 2: Brak enum tam, gdzie się prosi.
Często deweloperom nie chce się zamieniać stringów na enum i zostawiają type: "string". W efekcie model wymyśla własne wartości, backend się dziwi, UI się psuje. Jeśli masz stały zestaw opcji (relationship, typy statusów, sposoby sortowania) — prawie zawsze warto zrobić enum i wyliczyć dopuszczalne wartości. To mocno zwiększa przewidywalność tool‑calls.

Błąd nr 3: Dwa źródła prawdy dla schemy i typów.
Klasyka: w TypeScript zmieniasz pole maxBudget na priceMax, a w JSON Schema zapominasz. Model nadal wysyła maxBudget, kod oczekuje priceMax — wszystko się wywraca. Często takie błędy wychodzą dopiero na produkcji. Dlatego lepiej od początku używać Zod lub podobnego narzędzia, które generuje i typ, i JSON Schema z jednej deklaracji.

Błąd nr 4: Proszenie modelu o generowanie wewnętrznych identyfikatorów.
Pola typu userId, giftId, orderId, jeśli opiszesz je jako „UUID użytkownika w naszym systemie”, nieuchronnie będą wypełniane przez model zmyślonymi wartościami. Nawet jeśli dodasz pattern dla UUID, model zacznie generować „poprawnie wyglądające” UUID, które niczemu nie odpowiadają. Takie pola lepiej uzupełniać po stronie backendu na podstawie kontekstu (autentykacja, poprzedni tool‑call), a nie prosić o to model.

Błąd nr 5: Gigantyczne „boskie” schemy na wszystkie przypadki.
Czasem kusi, by zrobić jedno narzędzie do_everything z ogromnym obiektem, połowa pól nullable, połowa optional. Model się w tym topi. Lepiej podzielić funkcjonalność na kilka narzędzi z węższymi i bardziej zrozumiałymi schemami: jedno odpowiada za wyszukiwanie prezentów, drugie — za pobieranie szczegółów konkretnego prezentu, trzecie — za tworzenie zamówienia.

Błąd nr 6: Ignorowanie _meta i adnotacji.
Wielu deweloperów ogranicza się do name, description i inputSchema, pomijając pola _meta w rodzaju openai/outputTemplate i wskazówki w stylu destructiveHint. W efekcie narzędzia, które „po cichu” robią niebezpieczne rzeczy, nie są opatrzone podpowiedziami i potwierdzeniami w UI. To obniża zaufanie użytkownika i tworzy ryzyko nieoczekiwanych operacji. Używaj adnotacji, aby jawnie oznaczać narzędzia read‑only i niebezpieczne, a także ustawić przyjazne statusy wykonywania.

Błąd nr 7: Brak walidacji wejścia po stronie serwera.
Nawet jeśli JSON Schema i Zod „wszystko opisują”, poleganie wyłącznie na modelu jest ryzykowne. Czasem model może zwrócić częściowo poprawne dane albo sam zmienisz schemę i zapomnisz o ograniczeniach biznesowych. Opakowanie handlera w try { parse } catch { ... } z przyjaznym błędem daje modelowi szansę skorygować argumenty, a tobie — nie wywrócić całego serwisu przez jedno nieudane wywołanie narzędzia.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION