CodeGym /Kursy /ChatGPT Apps /Narzędzia agenta (Tools): schematy, routowanie, błędy

Narzędzia agenta (Tools): schematy, routowanie, błędy

ChatGPT Apps
Poziom 12 , Lekcja 1
Dostępny

1. Narzędzie agenta: czym jest naprawdę

W poprzednich modułach widzieliście już narzędzia od strony Apps SDK — jako „funkcje backendu”, do których ChatGPT odwołuje się przez waszą aplikację. Teraz zmienimy perspektywę: spojrzymy na narzędzia oczami agenta w Agents SDK i omówimy, jak wybiera on, co wywołać, oraz co robić z błędami.

W typowym backendzie myślicie kategoriami „endpoint”, „metoda kontrolera”, „funkcja serwisu”. W świecie agentów podstawową jednostką działania staje się narzędzie (tool). Narzędzia agenta (tools) i mcp-tools to różne, choć częściowo pokrywające się rzeczy.

Ściślej mówiąc: narzędzie w kontekście ChatGPT Agents SDK to opis funkcji, którą model może poprosić o wykonanie. Model sam nie uruchamia kodu; generuje ustrukturyzowane żądanie (zwykle JSON), a runtime (wasz kod, serwer MCP lub Agents SDK) wykonuje operację i zwraca wynik.

W ekosystemie ChatGPT Agents SDK narzędzie opisuje się konfiguracją: ma name, description i parameters (JSON Schema argumentów). Agent widzi ten zestaw narzędzi, trzyma je w swoim kontekście i w trakcie rozumowania decyduje, które narzędzie wywołać i z jakimi argumentami.

Agent (lub ChatGPT jako host) otrzymuje tę listę, „zapamiętuje” ją w swoim kontekście i podczas rozumowania (reasoning) decyduje: na które zapytanie użytkownika wywołać które narzędzie i z jakimi argumentami. Dlatego w specyfikacjach ciągle powtarza się mantra „tools are a contract” — narzędzia to kontrakt między modelem a waszym kodem, a nie tylko „funkcja w Pythonie/TS”.

Można to porównać z klasycznym API. Route /api/gifts/search to czysta składnia: URL, metoda, format ciała. A tool search_gifts to semantyka: „wyszukiwanie prezentów według profilu i budżetu”. Opis narzędzia to taki sam prompt, tylko ustrukturyzowany i przeznaczony dla LLM, a nie dla człowieka.

2. Typy narzędzi: czym dokładnie może zajmować się agent LLM

Aby nie utonąć w chaosie „funkcji, które potrafią wszystko”, warto patrzeć na narzędzia jak na kilka typowych kategorii. To nie jest formalna typizacja SDK, a sposób myślenia architektonicznego, który bardzo pomaga.

W naszym backendzie agenci LLM zazwyczaj mają trzy źródła narzędzi.

  • Lokalne narzędzia biznesowe. To to, co żyje w waszym backendzie: praca z bazą danych, logika domenowa (filtrowanie, rekomendacje, scoring). Na przykład w GiftGenius możemy mieć narzędzia, które pobierają produkty z własnej tabeli PostgreSQL albo liczą osobisty scoring „na ile prezent spodoba się tej osobie”.
  • Narzędzia MCP. Tutaj serwer MCP występuje jako dostawca narzędzi (tools): rejestruje funkcje, zasoby i prompty i udostępnia je klientowi (ChatGPT, agent LLM). Narzędzia przez MCP mogą wywoływać zewnętrzne API, pracować z plikami lub dostarczać szablony promptów.
  • Narzędzia integracyjne. To wszystko, co łączy was z resztą świata: ACP/commerce (tworzenie zamówienia i checkout), wysyłka e‑maili, webhooks, zapis do CRM. Takie narzędzia (tools) bywają bardziej ryzykowne, bo zmieniają stan systemów zewnętrznych, więc trzeba traktować je szczególnie rygorystycznie pod kątem bezpieczeństwa i idempotencji.

Jest też inna użyteczna klasyfikacja — według charakteru działania. W badaniach nad narzędziami LLM zwykle wyróżnia się: narzędzia pozyskiwania danych (wyszukiwanie, RAG, get_*), narzędzia działań z efektami ubocznymi (create_order, send_email), czysto obliczeniowe (calculate_loan) oraz systemowe/sterujące (handoff_to_human, finish_task).

Aby to utrwalić, spójrzmy na małą tabelę.

Kategoria Przykład w GiftGenius Efekt uboczny Ryzyko
Data Retrieval
search_gifts, get_details
Nie Niski
Action / Mutating
create_order, buy_gift
Tak Wysoki
Computation
estimate_delivery_cost
Nie Średni
System / Control
finish_recommendation
Nie Logiczny

Z punktu widzenia architektury najważniejsze jest to, że narzędzia tylko do odczytu powinny być masowe i tanie, a te zmieniające stan — rzadkie, maksymalnie ostrożne, z logami, idempotencją i często z potwierdzeniem użytkownika.

Dalej będziemy głównie mówić o narzędziach pozyskiwania danych i narzędziach typu Action, ponieważ na nich opiera się logika GiftGenius.

3. JSON Schema jako kontrakt między modelem a waszym kodem

Przyjrzyjmy się teraz, jak opisuje się narzędzie. W ChatGPT Agents SDK (podobnie jak w Apps SDK) standardowym formatem opisu parametrów narzędzia jest JSON Schema: opisujecie typ object, jego properties, typy pól, pola obowiązkowe, ograniczenia itd.

Ważne: JSON Schema to tutaj nie tylko i nie tyle walidacja. To część promptu dla modelu. Oficjalne przewodniki OpenAI po projektowaniu narzędzi (tools) wprost mówią, że jakość pracy agenta mocno zależy od tego, jak szczegółowo i jednoznacznie opisane są pola, ich nazwy i komentarze.

Spójrzmy na przykład dla GiftGenius, który już pojawiał się w planach kursu.

{
  "name": "search_gifts",
  "description": "Wyszukuje prezenty według typu odbiorcy, zainteresowań i budżetu.",
  "parameters": {
    "type": "object",
    "properties": {
      "recipient_type": {
        "type": "string",
        "description": "Kto jest odbiorcą prezentu (np. 'mężczyzna', 'kobieta', 'dziecko')."
      },
      "interests": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Kluczowe zainteresowania (sport, książki, technologie itp.)."
      },
      "budget": {
        "type": "number",
        "description": "Maksymalny budżet w walucie użytkownika."
      }
    },
    "required": ["recipient_type", "budget"]
  }
}

Jest tu kilka ważnych punktów.

  • Po pierwsze, name i description. Dla modelu to główny sygnał, kiedy w ogóle użyć tego narzędzia. Dokumentacja o routingu semantycznym podkreśla, że opis narzędzia to de facto API dla modelu: jeśli nazwiecie je func1 i podpiszecie „robi coś pożytecznego”, model uczciwie nie zrozumie, kiedy ma je wywołać. A jeśli napisać search_gifts i dodać zrozumiały opis, wybór staje się prostszy.
  • Po drugie, parameters. Nazwy pól i ich opisy są niezwykle istotne. Dla LLM recipient_type jest znacznie bardziej zrozumiałe niż type. Dobry opis w rodzaju „Kto jest odbiorcą prezentu…” podpowiada modelowi, że tu należy wstawić typ odbiorcy, a nie np. format opakowania.
  • Po trzecie, required. To nie tylko walidacja po waszej stronie, ale też wskazówka dla modelu: będzie on starał się wypełnić pola obowiązkowe, a opcjonalne pominie, jeśli z kontekstu nie wynika nic więcej. To zmniejsza liczbę „pustych” lub niepoprawnych wywołań narzędzia.

Oficjalne przewodniki po Apps SDK wprost rekomendują: twórzcie narzędzia wąskie, z jedną odpowiedzialnością, o jasnych nazwach i opisach; unikajcie narzędzi „zrób wszystko z prezentami”, które próbują połączyć różne zadania.

4. Projektujemy narzędzia GiftGenius: od schematu do kodu

Weźmy nasze GiftGenius i dodajmy dwa kluczowe narzędzia agenta LLM, potrzebne prawie w każdym scenariuszu:

  • suggest_gifts(profile, budget) — zwraca listę kandydatów;
  • get_gift_details(gift_id) — pokazuje szczegóły konkretnego prezentu.

Nasze suggest_gifts i get_gift_details to typowy przykład lokalnych narzędzi biznesowych z poprzedniej klasyfikacji, głównie z kategorii Data Retrieval.

Schemat dla suggest_gifts

Zacznijmy od czystego JSON Schema, a potem pokażemy, jak może to wyglądać w kodzie TypeScript po stronie backendu/runtime’u agenta.

{
  "name": "suggest_gifts",
  "description": "Dobiera listę prezentów na podstawie profilu odbiorcy i budżetu.",
  "parameters": {
    "type": "object",
    "properties": {
      "age": {
        "type": "integer",
        "minimum": 0,
        "maximum": 120,
        "description": "Wiek odbiorcy w latach."
      },
      "relationship": {
        "type": "string",
        "enum": ["friend", "coworker", "partner", "family"],
        "description": "Relacja z odbiorcą: przyjaciel, kolega z pracy, partner, rodzina."
      },
      "interests": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Zainteresowania odbiorcy (sport, książki, technologie itp.)."
      },
      "budget": {
        "type": "number",
        "minimum": 1,
        "description": "Maksymalny budżet w walucie użytkownika."
      }
    },
    "required": ["budget"]
  }
}

Tutaj używamy enum dla relationship, aby model nie wymyślał dowolnych ciągów w rodzaju "zły kolega" i nie przekazywał ich dalej do kodu. Taki staranny projekt schematu pomaga zarówno modelowi (widzi dopuszczalne warianty), jak i deweloperowi (mniej niespodzianek w runtime’ie).

Załóżmy teraz, że mamy serwer MCP w Node.js z jakimś umownym McpServer. Rejestracja narzędzia może wyglądać tak:

// uproszczony przykład rejestracji narzędzia w serwerze MCP
server.registerTool(
  {
    name: "suggest_gifts",
    description: "Dobiera prezenty na podstawie profilu i budżetu.",
    inputSchema: suggestGiftsSchema
  },
  async (input, ctx) => {
    const gifts = await findGiftsInDb(input, ctx.userLocale);
    return { items: gifts }; // JSON, który następnie zobaczy agent
  }
);

Kod jest mocno uproszczony, ale logika jasna: w jednym miejscu — opis kontraktu (nazwa, opis, schemat), w innym — implementacja.

Schemat dla get_gift_details

Drugie narzędzie potrzebne niemal w każdej witrynie:

{
  "name": "get_gift_details",
  "description": "Pobiera pełne informacje o prezencie na podstawie jego identyfikatora.",
  "parameters": {
    "type": "object",
    "properties": {
      "gift_id": {
        "type": "string",
        "description": "UUID prezentu w bazie GiftGenius."
      }
    },
    "required": ["gift_id"]
  }
}

Analogiczna rejestracja:

server.registerTool(
  {
    name: "get_gift_details",
    description: "Zwraca szczegółowe informacje o prezencie.",
    inputSchema: getGiftDetailsSchema
  },
  async ({ gift_id }) => {
    const gift = await db.gifts.findById(gift_id);
    if (!gift) return { notFound: true };
    return { gift };
  }
);

Zwróćcie uwagę: od razu pokazujemy, że narzędzie może zwrócić notFound: true. To już zalążek błędów semantycznych (błędów biznesowych), o których powiemy niżej. Agent zobaczy „prezent nie znaleziony” i podejmie decyzję: np. spróbuje innego id albo zaproponuje użytkownikowi wybór innego towaru.

5. Jak agent wybiera, które narzędzie wywołać

Teraz najciekawsze: routowanie. W tradycyjnej aplikacji webowej routing jest sztywny: URL → konkretny kontroler. W świecie ChatGPT Apps i agentów wybór narzędzia zachodzi semantycznie i probabilistycznie.

Cykl wysokopoziomowy można przedstawić tak:

flowchart TD
  U[User message] --> M["Model (agent)"]
  M -->|analiza żądania| C{Czy potrzebne jest narzędzie?}
  C -->|nie| T[Odpowiedź tekstowa]
  C -->|tak| S[Wybór narzędzia]
  S --> K[Utworzenie argumentów JSON]
  K --> R[Wykonanie narzędzia]
  R --> M2[Model widzi wynik]
  M2 --> T2[Odpowiedź końcowa lub następny krok]

Na każdym kroku agent widzi kilka rzeczy:

  • Po pierwsze, instrukcje systemowe (rola agenta, ograniczenia);
  • Po drugie, historię dialogu;
  • I wreszcie listę narzędzi (tools) z ich name, description, inputSchema.

Gdy przychodzi kolejne wiadomość użytkownika, model porównuje sens żądania z opisami narzędzi (dopasowanie semantyczne). Jeśli prośba brzmi „dobierz prezent dla przyjaciela do 50 dolarów”, opis suggest_gifts jest dużo bardziej adekwatny niż get_gift_details, więc agent z dużym prawdopodobieństwem wybierze właśnie to narzędzie.

Oficjalne przewodniki podkreślają dwie rzeczy, które silnie wpływają na jakość routowania.

  • Po pierwsze, unikajcie narzędzi zachodzących semantycznie: jeśli macie search_gifts i find_gifts, opisane podobnie, model będzie się mylił.
  • Po drugie, trzymajcie się zasady jednej odpowiedzialności: jedno narzędzie — jedno jasno określone zadanie, a nie „dobierz prezenty i utwórz zamówienie i wyślij e‑mail”.

W różnych agentach LLM są mechanizmy sterowania trybem wyboru narzędzi: np. „auto” (model sam decyduje, czy narzędzie jest potrzebne), „required” (narzędzie musi być wywołane), „none” (narzędzia wyłączone). To pomaga w złożonych workflow (wielokrokowych scenariuszach), gdy na danym kroku chcecie wymusić wywołanie suggest_gifts, zamiast pozwalać modelowi po prostu rozmawiać.

Przykład routowania semantycznego w GiftGenius

Załóżmy, że nasz agent ma co najmniej dwa narzędzia: suggest_gifts i get_gift_details.

  1. Użytkownik pisze: „Dobierz prezent dla kolegi do 30 dolarów, lubi gry planszowe”.
  2. Agent widzi, że żądanie zawiera cel „dobrać prezent”, informację o budżecie i zainteresowaniach. Opis suggest_gifts pasuje idealnie — wywołujemy to narzędzie.
  3. Narzędzie zwraca listę pięciu prezentów z ich id, nazwami i krótkimi opisami.
  4. Użytkownik pisze dalej: „Opowiedz więcej o trzeciej opcji”. Agent wiąże „trzecią opcję” z id z poprzedniego wyniku i teraz semantycznie pasuje narzędzie get_gift_details — ono jest wywoływane.

Ważne: nigdzie w kodzie nie pisaliście jawnie „jeśli w żądaniu jest słowo ‘dobierz’, wywołaj suggest_gifts”. Tym zajmuje się model na podstawie waszych opisów i historii dialogu. Waszą odpowiedzialnością jest sprawić, by wybór był oczywisty zarówno dla modelu, jak i człowieka.

6. Błędy narzędzi: nie 500, lecz sygnał dla modelu

Pamiętacie, że w get_gift_details pokazaliśmy notFound: true? To właśnie przykład błędu biznesowego, który agent powinien zobaczyć i sensownie obsłużyć, zamiast dostawać nagie 500.

Przejdźmy teraz do najboleśniejszej części. W zwykłym REST‑API coś padło w głębi backendu — zwracamy 500 Internal Server Error, zapisujemy stack trace do loga — a dalej użytkownik już sobie poradzi. W przypadku agenta takie podejście działa słabo.

Praktyczne przewodniki i materiały o Agents SDK zalecają traktować błędy narzędzi jak zdarzenia obserwowalne, a nie po prostu awarie. Często nazywa się to wzorcem „Error as Observation”.

Mówiąc wprost, nie powinniście „padać” bez wyjaśnienia; powinniście zwrócić modelowi ustrukturyzowaną odpowiedź, która tłumaczy, co poszło nie tak, aby mógł on dostosować zachowanie: przeformułować żądanie, zapytać użytkownika, spróbować innego narzędzia itd.

Typy błędów zwykle dzielimy na trzy grupy.

  • Błędy walidacji argumentów. Model może wygenerować niepoprawne parametry: pominąć pole obowiązkowe, wstawić ciąg znaków zamiast liczby, wyjść poza dopuszczalne zakresy. Tutaj waszą schemę i walidację należy wykorzystać nie tylko do rzucania wyjątków, ale też do sensownej odpowiedzi: np. zwrócić, które pole jest niepoprawne i dlaczego.
  • Błędy biznesowe. To przewidywalne sytuacje w rodzaju „produkt nie znaleziony”, „region niedostępny”, „budżet zbyt niski dla tego typu prezentów”. Z punktu widzenia API to również błędy, ale należy je zwracać w ramach normalnej odpowiedzi — z czytelnym kodem i komunikatem, a nie jako crash.
  • Błędy systemowe. Timeouty usług zewnętrznych, problemy sieci, awarie bazy. Tutaj agentowi zwykle wystarczy ostrożny, ogólny komunikat w rodzaju „usługa tymczasowo niedostępna, spróbuj później”. Żadnych stack trace’ów, nazw tabel ani innych szczegółów, które modelowi nie są potrzebne i mogą być niebezpieczne z punktu widzenia bezpieczeństwa.

Oficjalne materiały o Agents SDK proponują nawet specjalny mechanizm failure_error_function, pozwalający starannie sformułować tekst błędu widoczny dla modelu, zamiast po prostu wyrzucać wyjątek w górę stosu.

Struktura „przyjaznego” błędu

W narzędziu agenta (w waszym backendzie) możecie ustalić, że każdy błąd zwracany jest np. jako obiekt:

type ToolError = {
  code: string;      // 'VALIDATION_ERROR', 'OUT_OF_STOCK', ...
  message: string;   // dla modelu
  retryable: boolean;
};

A wynik działania narzędzia — jako unia:

type SuggestGiftsResult =
  | {
      ok: true;
      items: GiftSummary[];
    }
  | {
      ok: false;
      error: ToolError;
    };

Model (lub runtime agenta) zobaczy taki JSON i zadecyduje: jeśli retryable: true, można spróbować ponownie z niewielkimi zmianami; jeśli to błąd biznesowy i nieretryowalny — lepiej wrócić do użytkownika i wyjaśnić, co jest nie tak.

7. Przykłady: walidacja, błąd biznesowy i błąd systemowy

Wróćmy do naszego backendu/narzędzi agenta i zobaczmy, jak można zaimplementować te same idee w kodzie.

Błąd walidacji

Wyobraźmy sobie, że do was trafia wywołanie narzędzia suggest_gifts, ale model z jakiegoś powodu przekazał ujemny budżet.

async function handleSuggestGifts(input: SuggestGiftsInput)
  : Promise<SuggestGiftsResult> {

  if (input.budget <= 0) {
    return {
      ok: false,
      error: {
        code: "VALIDATION_ERROR",
        message: "budget musi być dodatnią liczbą.",
        retryable: false
      }
    };
  }

  const items = await findGiftsInDb(input);
  return { ok: true, items };
}

Świadomie nie rzucamy wyjątku, tylko zwracamy ustrukturyzowany błąd. Agent może przemyśleć żądanie: być może uzna, że pomylił walutę, i zapyta użytkownika, albo po prostu przyzna, że nie może dobrać prezentu przy takim budżecie.

Błąd biznesowy

Teraz przykład z get_gift_details. Prezentu o wskazanym id może po prostu nie być.

async function handleGetGiftDetails(input: { gift_id: string }) {
  const gift = await db.gifts.findById(input.gift_id);

  if (!gift) {
    return {
      ok: false,
      error: {
        code: "GIFT_NOT_FOUND",
        message: "Prezent o takim identyfikatorze nie został znaleziony.",
        retryable: false
      }
    };
  }

  return { ok: true, gift };
}

W odpowiedzi modelu można oczekiwać czegoś w rodzaju: „Wygląda na to, że wybrany prezent jest już niedostępny. Mogę zaproponować kilka alternatyw z podobnej kategorii?”. Do tego agent nie potrzebuje informacji o błędach SQL ani stack trace’ów — wystarczy czytelny code i message.

Błąd systemowy

Na koniec przykład błędu systemowego. Załóżmy, że wasze narzędzie odwołuje się do zewnętrznego API dostawy, które czasem „pada”.

async function handleEstimateDelivery(input: EstimateDeliveryInput) {
  try {
    const eta = await callDeliveryApi(input);
    return { ok: true, eta_days: eta };
  } catch (e) {
    return {
      ok: false,
      error: {
        code: "DELIVERY_SERVICE_UNAVAILABLE",
        message: "Usługa dostawy jest tymczasowo niedostępna.",
        retryable: true
      }
    };
  }
}

Agent może zdecydować: „Wygląda na to, że usługa dostawy jest teraz niedostępna. I tak pokażę prezenty, ale dokładny czas dostawy może się różnić. Chcesz kontynuować?”.

8. Bezpieczeństwo i idempotencja narzędzi (szybkie spojrzenie ze strony tools)

Pełną dyskusję o bezpieczeństwie i uprawnieniach zostawimy na oddzielny temat, ale narzędzia agenta są z tym tak silnie związane, że nie da się tego pominąć.

Po pierwsze, trzeba rozdzielać narzędzia odczytu i narzędzia zapisu. W opisach, schematach i uprawnieniach jasno zaznaczajcie, które tools tylko czytają dane i są absolutnie bezpieczne, a które potrafią pobierać pieniądze, zmieniać zamówienia itp. Dokumentacja i fora scenariuszy agents wprost mówią o rozdzieleniu narzędzi ReadOnly i Mutating (tools).

Po drugie, dla narzędzi mutujących trzeba myśleć o idempotencji. Agent lub klient MCP może ponowić wywołanie (np. z powodu błędu sieci), a nie chcecie, by create_order utworzyło dwa zamówienia zamiast jednego. Typowe wzorce:

  • idempotency‑key, który jest przekazywany jako argument narzędzia;
  • sprawdzanie istnienia operacji przed wykonaniem;
  • rozdzielenie kroków na „utwórz szkic zamówienia” i „potwierdź zamówienie”.

To wszystko jest ściśle powiązane z tym, jak projektujecie kontrakt narzędzia: jeśli w JSON Schema nie ma pola na idempotency‑key, dodanie idempotencji później będzie dużo boleśniejsze.

9. Krótki rzut oka na Agents SDK: jak to wygląda w runtime agenta

Ten rozdział to krótki przegląd dla tych, którzy będą pracować z TypeScript‑owym Agents SDK. Chociaż główna część kursu jest o MCP, warto rozumieć, jak podobne narzędzia widzi Agents SDK i jak wygląda typowy tool w runtime’ie.

W oficjalnej dokumentacji zwykle opisuje się byt w rodzaju „narzędzia funkcjonalnego”: każda funkcja opisana przez obiekt konfiguracyjny (lub helper w rodzaju tool(...)) i opatrzona typami może zostać automatycznie przekształcona w narzędzie, dla którego SDK wygeneruje JSON Schema i opis.

Na poziomie koncepcji to dokładnie to samo, o czym już mówiliśmy: nazwa funkcji, jej parametry oraz komentarz/description pełnią rolę nazwy, schematu i opisu narzędzia. Różnica w tym, że za was większość pracy „mechanicznej” wykonuje SDK i/lub biblioteka pomocnicza do schematów (np. Zod lub JSON Schema).

Umowny przykład (pseudokod TypeScript, uproszczony):

type Gift = {
  id: string;
  title: string;
  // ...
};

const suggestGifts = tool({
  name: "suggest_gifts",
  description: "Dobiera listę prezentów według typu odbiorcy i budżetu.",
  parameters: {
    type: "object",
    properties: {
      recipient_type: {
        type: "string",
        description: "Kto jest odbiorcą prezentu (np. 'mężczyzna', 'kobieta', 'dziecko')."
      },
      budget: {
        type: "number",
        description: "Maksymalny budżet w walucie użytkownika."
      }
    },
    required: ["recipient_type", "budget"]
  }
}, async (args: { recipient_type: string; budget: number }): Promise<Gift[]> => {
  // W środku — wasza logika dziedzinowa
  return findGifts(args.recipient_type, args.budget);
});

SDK (lub wasz helper tool) na podstawie obiektu parameters zbuduje JSON Schema i przekaże ją agentowi, a runtime zadba o walidację i marshalling argumentów tam i z powrotem. Koncepcyjnie to dokładnie to, co ręcznie robiliście w serwerze MCP na TypeScript, tylko narzędzie jest teraz „wpięte” bezpośrednio w runtime agenta.

Tu nie chodzi o zapamiętanie konkretnej składni helpera tool, ale o uchwycenie myśli: dobra typizacja + sensowny description/komentarze = dobre narzędzie.

Jeśli to wszystko złożyć: dobre narzędzie agenta to wąska, jasno opisana funkcja z przemyślanym JSON Schema, zrozumiałym opisem dla modelu i staranną obsługą błędów. Routowanie semantyczne zadziała tylko wtedy, gdy narzędzia nie pokrywają się semantycznie. A operacje mutujące muszą być bezpieczne i idempotentne — inaczej agent na produkcji szybko stanie się źródłem niespodzianek.

10. Typowe błędy przy projektowaniu narzędzi agenta

Błąd nr 1: Zbyt szerokie narzędzia „do_everything”.
Czasem kusi, by wsadzić wszystko do jednego narzędzia manage_gifts, które i wyszukuje prezenty, i pokazuje szczegóły, i tworzy zamówienie, i wysyła e‑mail. Modelowi jest po tym ciężko: opis staje się rozmyty, routing semantyczny się pogarsza, a agent zaczyna wywoływać to narzędzie „na wszelki wypadek” nawet tam, gdzie potrzebne jest zwykłe wyszukiwanie. Lepiej dzielić zadania na osobne narzędzia z jedną dobrze zrozumiałą odpowiedzialnością.

Błąd nr 2: Narzędzia nakładające się znaczeniowo.
Jeśli macie search_gifts i find_gifts, oba „szukają prezentów według zainteresowań”, model będzie losowo wybierał między nimi. W rezultacie zachowanie staje się niestabilne: identyczne żądania czasem trafiają do jednego narzędzia, a czasem do drugiego. Starajcie się, by każda nazwa i opis zajmowały unikalną „niszę” w przestrzeni znaczeń.

Błąd nr 3: Słabe lub brak opisów i pól schematu.
Nazwa func1, opis „Does something” i parametr data: string — klasyczny sposób, by ogłupić agenta. Model nie jest telepatą i nie przeczyta waszego kodu źródłowego. Opiera się na description, properties oraz ich description w schemacie. Jeśli nie wyjaśnicie, czym jest recipient_type, model będzie zgadywać i się mylić.

Błąd nr 4: Nastawienie tylko na happy‑path, ignorowanie błędów.
Wiele implementacji narzędzi zakłada: „Przecież parametry będą zawsze poprawne, a usługa dostępna”. W realnym świecie model łatwo generuje błędne parametry, usługi zewnętrzne padają, a baza czasem mówi „timeout”. Jeśli nie przemyślicie formatów błędów i nie zwrócicie agentowi sensownego komunikatu, nie zdoła on skorygować zachowania i będzie albo milcząco padał, albo halucynował.

Błąd nr 5: Wyrzucanie surowego 500 i stack trace’u do LLM.
W REST‑API przywykliśmy logować pełny stack trace, by szybciej debugować. W kontekście agenta stack trace przekazany modelowi jest jednocześnie bezużyteczny (model nie wie, czym jest SQLException w waszej konkretnej bibliotece) i potencjalnie groźny (zbędne szczegóły implementacji i możliwe informacje poufne). Znacznie lepiej przechwycić wyjątek, zapisać szczegóły w logu, a do modelu wysłać zgrabny code i message.

Błąd nr 6: Brak idempotencji w narzędziach mutujących.
Narzędzie create_order bez idempotency‑key to zaproszenie do podwójnych zamówień, zwłaszcza przy problemach sieci i automatycznych retrach. Jeśli wasz agent działa w scenariuszu komercyjnym, narzędzia związane z pieniędzmi muszą być zaprojektowane tak, by ponowne wywołania nie prowadziły do dodatkowych obciążeń ani duplikatów.

Błąd nr 7: Przechowywanie sekretów i szczegółów technicznych w schemacie lub opisie.
Czasem deweloper z przyzwyczajenia pisze w description: „Wewnątrz wywołuje usługę X pod https://internal-api.example.com”. Modelowi ta informacja nie jest potrzebna, użytkownikowi — tym bardziej. Schematy i opisy to część promptu, żyją w kontekście modelu i nie należy tam umieszczać URL‑i wewnętrznych usług, nazw prywatnych tabel, a tym bardziej sekretów.

Błąd nr 8: Przekazywanie do narzędzi wszystkiego jak leci, zamiast przemyślanego zestawu pól.
Łatwo ulec pokusie „po prostu przekażmy całe żądanie użytkownika jako string, a dalej sobie poradzimy”. W ten sposób tracicie korzyści ze strukturyzacji przez JSON Schema: model już nie rozumie, które części żądania są istotne dla logiki, tracicie walidację i przewidywalność. Lepiej wydobyć z żądania jawne pola (budget, interests, user_location) i opisać je jako część kontraktu narzędzia.

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