Strona główna / Artykuły

Narzędzie Claude tool_use End to End: Pętla agentowa w trzech iteracjach

Narzędzie Claude tool_use end to end: jedna prośba do Messages API w ramach pętli agencyjnej, z definicjami narzędzi, wyborem narzędzia, połączeniami równoległymi oraz zasadami dotyczącymi wyników narzędzi.

Wydawca: REACTAPP.TOP19 min czytania4379 słów

Źródła: Part III | ‘tool_use’, End to End: The Agentic Loop in Three Iterations

Dlaczego to ty musisz napisać ten pętlę

Praca z tool_use bezpośrednio przez API Messages (anthropic.Anthropic() następnie client.messages.create(...)) umożliwia kontrolę nad tą pętlą. Twój kod deklaruje dostępne narzędzia, sprawdza powód zatrzymania, analizuje żądania Claude’a, je wykonywa i zwraca wyniki. Ręczne napisanie tej pętli stanowi podstawę każdego systemu wykorzystującego narzędzia i jest standardowym tematem w programie na certyfikat Claude Certified Architect.

Najpierw przypomnienie: API Messages nie przechowuje stanu. Nic nie jest zapamiętywane między wywołaniami, więc każde żądanie zawiera całą rozmowę, a runda wykorzystująca narzędzia składa się zazwyczaj z kilku żądań, powtarzanych aż do momentu, gdy Claude uzyska to, czego potrzebuje. Reszta procesu odbywa się poprzez jedno żądanie w ramach tego pełnego cyklu.

Przykład działania to mały asystent do planowania. Użytkownik pyta, czy zabrać parasol na jutrzejsze spotkanie w Londynie oraz o to, o której godzinie wyruszyć z domu, aby dotrzeć na czas. Odpowiedź wymaga trzech oddzielnych informacji: szczegółów spotkania, prognozy pogody oraz czasu dojazdu. Zobacz, jak Claude dzieli pytanie na trzy iteracje oraz jak duża część pracy spada na twój kod, a nie na model.

1. Kształt pętli

Każda wymiana z użyciem narzędzi przebiega według jednego czterofazowego cyklu:

  1. Ogłaszasz narzędzia w żądaniu.
  2. Claude pyta o jedno lub więcej z nich, co jest sygnalizowane przez stop_reason: „tool_use”.
  3. Twój kod uruchamia te narzędzia i zbiera wyniki.
  4. Twój kod odbiera wyniki i ponownie wywołuje API.

Fazy od drugiej do czwartej się powtarzają, a każdy przejście liczy się jako jedna iteracja. Pętla kończy się, gdy Claude przestaje prosić o narzędzia i zwraca normalne zakończenie (end_turn) lub inny powód zatrzymania. Ponieważ API nie ma stanu, sam musisz budować rozmowę i wysyłać ją ponownie za każdym razem. Nic nie idzie naprzód, chyba że twój kod to umożliwi.

2. Deklarowanie narzędzi

Claude jest ograniczony do narzędzi, które sam określisz. Znajdują się one w parametrze tools na najwyższym poziomie żądania, który zawiera tablicę z ich definicjami. Immer, gdy tools jest obecny, API tworzy na podstawie nich ukryty prompt systemowy przed wiadomością użytkownika. Ten ukryty prompt jest opłacany: każda nazwa, opis i schemat zużywa tokeny wejściowe przy każdym żądaniu, nawet jeśli Claude w ogóle nie korzysta z danego narzędzia.

Każde narzędzie klienta wymaga trzech pól:

  • name to unikalny identyfikator, który musi spełniać wzór ^[a-zA-Z0-9_-]{1,64}$
"tools": [
  {
    "name": "get_calendar_event",
    "description": "Look up a calendar event by a natural-language query. Returns the event's start time and location.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "What to search for, e.g. 'meeting in London tomorrow'" }
      },
      "required": ["query"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_weather",
    "description": "Get the forecast for a city on a given date. Returns condition and temperature in Celsius.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "city": { "type": "string", "description": "City name, e.g. 'London'" },
        "date": { "type": "string", "description": "ISO date, e.g. '2026-07-14'" }
      },
      "required": ["city", "date"],
      "additionalProperties": false
    }
  },
  {
    "name": "get_travel_time",
    "description": "Estimate door-to-door travel time between two places. Returns minutes.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "origin": { "type": "string", "description": "Starting location" },
        "destination": { "type": "string", "description": "Ending location" }
      },
      "required": ["origin", "destination"],
      "additionalProperties": false
    }
  }
]

Zdefiniuj je starannie. Wskazówki Anthropic dotyczące skutecznych narzędzi sprowadzają się do kilku nawyków:

  • Bądź konkretny co do nazw narzędzi. get_current_weather podaje Claude więcej informacji niż weather.
  • Nazwij i opisz parametry jednoznacznie. Wolij user_id zamiast user, aby została podana właściwa wartość, oraz opisz każdy parametr, ponieważ dobrze nazwane pole z niejasnym opisem nadal może przyciągać błędne dane wejściowe.
  • Trzymaj zestaw narzędzi w małych rozmiarach. Zbyt duży zbiór utrudnia tworzenie poprawnych instrukcji dla systemu i sprawia, że wybór modelu staje się niejasny.
  • Waliduj wszystko. Schemat określa strukturę, a nie intencję, więc sprawdzaj każdy element danych otrzymywany przez Twój kod.

Dwie dodatkowe kwestie w tych definicjach zasługują na bliższą analizę.

  1. strict: true ma największe znaczenie. Znajduje się na najwyższym poziomie każdego narzędzia obok name, description i input_schema i włącza próbkowanie ograniczone zasadami gramatycznymi dla argumentów narzędzia, dzięki czemu model fizycznie nie może wygenerować input, który naruszy podaną strukturę. Jeśli tego nie włączyć, Claude zazwyczaj generuje ważne argumenty, ale może popełniać błędy, na przykład "2" tam, gdzie potrzebna była liczba 2, wartość poza zakresem enum lub brakujące pole wymagane. Każdy taki błąd skutkuje awarią w czasie wykonywania kodu, który wykorzystuje tę funkcję. Gdy włączysz strict: true, input wewnątrz każdego bloku tool_use na pewno będzie odpowiadał input_schema, a nazwa na pewno będzie należeć do jednego z twoich własnych narzędzi.
  2. Jedna z zasad schematu gwarantuje, że każdy obiekt musi mieć ustawioną wartość additionalProperties: false, stąd jego obecność we wszystkich wymienionych narzędziach. Ścisłe stosowanie tych narzędzi stanowi element funkcji strukturyzowanych wyjść Anthropic. W momencie pisania tego tekstu była ona powszechnie dostępna w aktualnych modelach, takich jak Opus 4.8, natomiast starsze modele, jak Sonnet 4.5, wymagały nagłówka beta structured-outputs-2025-11-13. Skompilowane schematy są przechowywane w pamięci cache przez maksymalnie 24 godziny, dzięki czemu brak konieczności ponownej kompilacji oznacza oszczędność zasobów.

Można również dodać do definicji narzędzi atrybut cache_control, aby przechowywać je w cache między żądaniami i unikać opłat za ponowną obróbkę, co jest korzystne, gdy zbiór narzędzi jest duży i stabilny.

Definicje opisują jedynie dane wejściowe

Definicja narzędzia obejmuje wyłącznie dane wejściowe, czyli to, co Claude do Ciebie wysyła. To zaskakuje osoby przyzwyczajone do frameworków, które określają typy zwracanych wartości. Mówiąc prościej:

  • Definicja zawiera jako obowiązkowe elementy name, description oraz input_schema, natomiast strict, input_examples i cache_control są dodatkami opcjonalnymi. Nie istnieją ani output_schema, ani error_schema.
  • Wartości zwracane są ustalane w czasie wykonywania przez Twój kod, a następnie przekazywane z powrotem w bloku tool_result opisanym w sekcji 6. Pole content może zawierać albo ciąg znaków, albo listę bloków, przy czym API nie sprawdza go względem żadnego schematu, ponieważ taki schemat nie istnieje. Jedna praktyczna uwaga: narzędzie może zwrócić obiekt DTO, a standardowy parser spłaszcza go do zwykłego ciągu znaków za pomocą metody toString. Użycie tej spłaszczonej formy działa, ale nie jest idealne – lepiej konwertować DTO na strukturyzowany JSON.
  • Błędy również są kwestią czasu wykonywania. Aby zgłosić nieudaną próbę, ustaw is_error: true w polu tool_result oraz dodaj informacyjną wiadomość w polu content; definicja nie zawiera deklaratywnego typu błędu. Domyślnie SDK przekazuje wywołaną wyjątkowo do Claude jako wynik błędu, a Claude dostosowuje się do tego.
  • Nierównowaga jest celowa. Definicje służą do pokazania Claude, jak wywołać narzędzie. Wynik to po prostu dodatkowy kontekst wracający do pętli, więc poza tool_use_id łączącym go z żądaniem, nie jest potrzebny żaden kontrakt.
  • Chcesz gwarantowanego formatu wyjściowego? Sprawdź to osobiście w kodzie przed odesłaniem czegokolwiek, lub zastosuj ustrukturyzowane wyjścia (output_config.format) do końcowej odpowiedzi, którą Claude pisze na podstawie tych wyników. Narzędzia MCP mają własny outputSchema, ale to funkcja warstwy MCP, a nie definicji natywnych.

Kontrola tego, czy Claude wywołuje narzędzie

tool_choice decyduje o tym, czy w danej turze można wywołać narzędzia i które z nich.

  • Pole to znajduje się na najwyższym poziomie żądania obok tools i messages i nie należy do żadnego konkretnego narzędzia.
  • Ogranicza to treść, którą może wygenerować Claude. Logika pozostaje nienaruszona, ponieważ Claude i tak czyta całe polecenie.
  • Jego zasięg obejmuje pojedynczą prośbę, nigdy nie jest globalny. API jest bezstanowe, więc tool_choice dotyczy wyłącznie odpowiedzi, z którą jest wysyłany, co stanowi jedną iterację. Aby zachować tę wartość między iteracjami, należy ją przesyłać za każdym razem. Zmienianie jej w poszczególnych iteracjach jest dozwolone: można wcześniej zmusić do użycia narzędzia, później użyć auto, a na końcu none dla tekstowej odpowiedzi.

Obiekt wygląda w ten sposób:

{
  "type": "auto" | "any" | "tool" | "none",
  "name": "get_weather",              // required ONLY when type is "tool"
  "disable_parallel_tool_use": false  // optional; default false
}

type jest obowiązkowy i może przyjmować jedną z czterech wartości. name jest potrzebny tylko wtedy, gdy type wynosi "tool". disable_parallel_tool_use jest opcjonalny i ogranicza liczbę narzędzi, które mogą być wywołane za jednym razem.

auto. Claude decyduje: zero, jedno lub kilka narzędzi, albo odpowiedź tekstowa. Dozwolone są równoległe wywołania. Dozwolona jest odpowiedź w formie zwykłego tekstu, a Claude może przedstawić plan działania przed wywołaniami. Jest to domyślny tryb, gdy podano parametr tools. Ustawienie disable_parallel_tool_use: true ogranicza wybór do jednego narzędzia, chociaż możliwe jest również zero narzędzi. Nadaje się do asystentów czatowych i pętli agentowych, gdzie różne etapy wymagają różnych działań. Jest to jedyny tryb, który pozwala Claude’owi mówić na głos przed podjęciem działań, oraz jedyny tryb oprócz none, który współpracuje z rozszerzonym myśleniem.

any. Claude musi wywołać narzędzie, ale sam decyduje, które. Dokonuje się co najmniej jednego wywołania, a ponieważ domyślnie włączone jest równoległe używanie, mogą pojawić się kilka bloków tool_use, niekoniecznie tylko jeden. Dokładnie jeden taki blok powstaje tylko wtedy, gdy ustawiona jest opcja disable_parallel_tool_use: true. Nie dozwolony jest tekst prosty, nie ma żadnego wstępnego uzasadnienia, a to nie jest wartość domyślna. Używaj tego wtedy, gdy wynik uzyskany za pośrednictwem narzędzia jest obowiązkowy, ale narzędzie może się różnić, jak w przypadku routerów i klasyfikatorów.

tool. Claude musi wywołać narzędzie, które podałeś, dlatego pole name jest obowiązkowe. Dokonuje się co najmniej jednego wywołania tego narzędzia (może je wywołać kilka razy równocześnie), przy czym dokładnie jedno musi zawierać parametr disable_parallel_tool_use: true. Nie wolno używać tekstu prostego ani żadnych wyjaśnień na wstępie. Nadaje się do jednorazowej, strukturyzowanej ekstrakcji, jako obowiązkowy pierwszy krok lub w modelu JSON-przez-narzędzie, na przykład {"type": "tool", "name": "get_weather"}. Prośba o wywołanie narzędzia, którego nie ma w liście tools, powoduje błąd 400, a przymusowe użycie narzędzia nie ogranicza liczby wywołań, chyba że zablokowano jego równoczesne używanie.

none. Claude nie może użyć żadnych narzędzi w tym turze, mimo że są one zdefiniowane, a stop_reason nie może być równy tool_use. Dozwolony jest tekst prosty, podobnie jak uzasadnienia na początku. Jest to wartość domyślna, gdy nie podano żadnych tools. Użyj jej, aby wymusić turę tylko tekstową, na przykład do końcowej syntezy, pozostawiając przy tym tablicę tools nietkniętą. Wyłącza to narzędzia bez edycji tablicy, chociaż przejście na tę wartość nadal unieważnia zapisane bloki wiadomości.

Istnieje kilka istotnych szczegółów, które łatwo przeoczyć:

  • W trybach wymuszonych brakuje wstępu. W trybach any lub tool API z góry wypełnia turę asystenta, więc blok tool_use przychodzi pusty: bez żadnego wyjaśnienia ani uzasadnienia, niezależnie od Twojego pytania. Uzasadnienia widoczne wymagają użycia trybu auto.
  • Rozszerzone myślenie wymaga wartości auto lub none. Nie można go łączyć z przymusem używania narzędzia, a API odrzuca takie połączenie.
  • Zasada liczenia dla disable_parallel_tool_use. auto oznacza co najwyżej jedno narzędzie, any i tool oznaczają dokładnie jedno narzędzie, natomiast domyślna wartość false umożliwia równoległe wywołania w modelach Claude 4.
  • Koszt buforowania. Zmiana wartości tool_choice pomiędzy żądaniami unieważnia zbuforowane bloki wiadomości. Definicje narzędzi oraz prompt systemowy pozostają w buforze, ale rozmowa jest ponownie przetwarzana.
  • Łącz z strict. tool_choice zapewnia uruchomienie określonego narzędzia, a strict gwarantuje, że jego argumenty są zgodne ze schematem. Razem sprawiają, że na każdym kroku uzyskuje się zformatowany, typowany wynik.

W żądaniu:

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any", "disable_parallel_tool_use": True},  # must call exactly one tool
    messages=messages,
)

Stąd wynikają twoje zasady projektowe. Zachowaj opcję przymusową dla kroków, które naprawdę muszą dać wynik narzędzia, a jeśli odpowiedź w formie tekstu jest akceptowalna, pozostań przy ustawieniu auto. Ponadto, gdy narzędzie klasyfikacyjne jest używane przymusowo, dodaj do jego listy wyboru opcję "other", aby Claude mógł odrzucić daną kategorię zamiast błędnie oznaczyć dane, których nie potrafi zakwalifikować.

3. Pierwsza iteracja: Claude prosi o narzędzia

Prośba typu „one” łączy w sobie tablicę narzędzi, wybraną opcję tool_choice oraz pytanie użytkownika:

messages = [{"role": "user",
             "content": "I have a meeting in London tomorrow
                         — should I bring an umbrella, and
                         when should I leave home to be on time?"}]

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=tools,                    # the three strict definitions from Section 2
    tool_choice={"type": "auto"},   # let Claude decide whether, and what, to call
    messages=messages,
)

Claude analizuje to pytanie i stwierdza, że dwa z trzech wymagań nie zależą od siebie: wyszukiwanie w kalendarzu oraz prognoza mogą być wykonywane jednocześnie. Dlatego żąda obu narzędzi w jednej odpowiedzi. Niezależne wywołania zwracane razem nazywane są równoległym używaniem narzędzi i pojawiają się jako kilka bloków tool_use w jednej turze.

{
  "id": "msg_01...",
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "Let me check your meeting details and the London forecast." },
    { "type": "tool_use", "id": "toolu_01Cal", "name": "get_calendar_event",
      "input": { "query": "meeting in London tomorrow" } },
    { "type": "tool_use", "id": "toolu_01Wx", "name": "get_weather",
      "input": { "city": "London", "date": "2026-07-14" } }
  ]
}

W tej odpowiedzi wyróżniają się trzy rzeczy.

  1. stop_reason to tool_use, co oznacza, że runda nie została zakończona i Claude czeka na ciebie.
  2. Tablica content zawiera mieszankę różnych elementów. Blok text z opisem często poprzedza bloki tool_use, więc wywołania narzędzi nie znajdują się w stałej pozycji.
  3. Każdy blok tool_use ma id (z prefiksem toolu_), name oraz obiekt input, który już odpowiada twojemu schematowi.

Aby znaleźć te wywołania, należy przeszukiwać zawartość i filtrować według typu, a nie według indeksu:

tool_calls = [block for block in response.content if block.type == "tool_use"]

To daje tu dwa wywołania. Każde id jest identyfikatorem służącym do otrzymania wyniku danego wywołania, a powiązanie id z wynikiem jest sposobem, w jaki Claude dopasowuje odpowiedzi do pytań, gdy jest kilka nierozstrzygniętych.

Uwaga dla modeli Claude 4: rozszerzone myślenie może być realizowane w formie przerwanych etapów, więc rozumowanie może mieć miejsce zarówno pomiędzy wywołaniami narzędzi, jak i przed nimi. Gdy rozszerzone myślenie jest włączone, należy przekazać bloki z rozumowaniem razem z resztą odpowiedzi asystenta, w przeciwnym razie kontynuacja zapomni o tym rozumowaniu.

4. Uruchamianie narzędzi

Teraz praca przechodzi do twojego kodu. Claude poprosił o dwa narzędzia, ale żadnego z nich nie uruchomił. Wykonaj wszystko, o co prosił, zebraj wszystkie wyniki i wyślij jedną kolejną odpowiedź zawierającą je wszystkie. Claude czeka, aż każde z jego wywołań otrzyma odpowiadający mu wynik.

Czy wykonywanie ma być równoległe, czy sekwencyjne, to decyzja użytkownika, ponieważ Claude tego nie określa. Odpowiedź podaje, co należy wywołać, a nie jak to zaplanować. Niezależne wywołania, takie jak wyszukiwanie informacji w kalendarzu czy pogodzie, mogą być wykonywane jednocześnie w celu zmniejszenia opóźnień. Gdy jedno z nich zależy od wyniku drugiego, należy je wykonywać po kolei. Model wskazuje na zależności poprzez grupowanie niezależnych zadań w bloki równoległe oraz odkładanie zadań zależnych na późniejszy moment, co ma miejsce w drugiej iteracji. Wątki, obsługa asynchroniczna lub kolejka zadań to wybory użytkownika w ramach pętli, a nie API.

W środowisku produkcyjnym traktuj wykonywanie zadań jako niewiarygodną operację wejścia/wyjścia, która może zawieść:

  • Ustaw czas wygaśnięcia dla każdego narzędzia, aby pojedyncze wolne wywołanie nie mogło zablokować całego procesu.
  • W miarę możliwości preferuj operacje idempotentne, ponieważ ponawianie prób i odtwarzanie działań jest nieuniknione w agentach.
  • Zarządzaj błędami po kolei. Gdy jeden z dwóch działających równolegle narzędzi wywoła błąd, nadal należy wysłać dla niego wynik (więcej na temat is_error poniżej) zamiast przerwać proces.
  • Mierz cykl działania. Opóźnienie dla każdego narzędzia, liczba wywołań, zużycie tokenów oraz wskaźniki błędów pokazują, czy agent funkcjonuje prawidłowo, czy powoli się pogarsza.

5. Kroki przed i po uruchomieniu narzędzia

Kontrola nad cyklem oznacza kontrolę nad dwoma etapami: tym pomiędzy „Claude poprosił o narzędzie” a „narzędzie zostało uruchomione”, oraz tym pomiędzy „narzędzie zostało uruchomione” a „wynik został odesłany”. Zasługują one na nazwy, ponieważ jakość ich projektu stanowi główną różnicę pomiędzy bezpiecznym a ryzykownym agentem.

Krok przed użyciem narzędzia wykonywany jest przed samą eksploatacją. Analizuje żądany wywołanie i decyduje, czy uruchomić je bez zmian, zmodyfikować czy odrzucić. W tym momencie następuje egzekwowanie zasad – Claude proponuje rozwiązanie, a twój kod podejmuje ostateczną decyzję.

Krok po użyciu narzędzia działa po eksploatacji, ale przed tym, jak wynik wróci do Claude. Przetwarza surowy wynik poprzez usuwanie danych, skracanie tekstu, zmianę formatu lub rejestrowanie informacji.

Wewnątrz pętli Client SDK są to po prostu funkcje otaczające proces eksploatacji:

for block in tool_calls:
    if not pre_tool_use(block):                       # validate / guard
        results.append(error_result(block.id, "Blocked by policy."))
        continue
    output = execute(block.name, block.input)         # run the tool
    output = post_tool_use(block, output)             # redact / log / reshape
    results.append(tool_result(block.id, output))

Krok przed użyciem narzędzia to miejsce, gdzie znajdują się rzeczywiste zasady bezpieczeństwa, i to obowiązek osoby je wdrażającej, a nie modelu. Powszechne wzorce w produkcji:

  • Sprawdzaj argumenty pod kątem własnej polityki oraz schematu.
  • Kategorycznie odrzucaj operacje destrukcyjne: rm -rf w przypadku narzędzia w shellu, DROP TABLE w przypadku narzędzia bazodanowego.
  • Proś osobę o zatwierdzenie wszelkich działań o dużym wpływie, takich jak zapisywanie danych do środowiska produkcyjnego.
  • Usuń tajne dane z argumentów przed przesłaniem ich do usługi zewnętrznej.
  • Zapisuj każdą próbę wywołania, aby audyty i kontrola zgodności miały ślad działań.

Następnie krok po użyciu narzędzia zajmuje się tym, co wróciło – usuwa dane uwierzytelniające lub informacje osobiste przed wprowadzeniem wyników do kontekstu, skraca nadmiarowo duże dane i rejestruje wyniki.

6. Zwracanie wyników

Odpowiedz na ruch typu tool_use wiadomością od user zawierającą listę bloków tool_result – po jednym bloku dla każdego bloku tool_use w odpowiedzi, przy czym każdy z nich musi być powiązany ze swoim tool_use_id. Najpierw dodaj ruch asystenta do bieżącej listy wiadomości, a następnie wyniki jako nowy ruch użytkownika. Pierwsza iteracja wygląda w ten sposób:

{
  "role": "user",
  "content": [
    {
        "type": "tool_result",
        "tool_use_id": "toolu_01Cal",
        "content": "{\"start\": \"2026-07-14T15:00\", \"location\": \"Canary Wharf, London\"}"
    },
    {
        "type": "tool_result",
        "tool_use_id": "toolu_01Wx",
        "content": "{\"condition\": \"rain\", \"temp_c\": 12}"
    }
  ]
}

Dwa formaty treści. content wyniku może być zwykłą ciągłą wartością, zserializowaną wartością zwracaną, jak w przykładzie, lub listą bloków treści. Bloki zawierają coś więcej niż tylko tekst, ponieważ można łączyć text i image, co umożliwia narzędziom do tworzenia wykresów lub zrzutów ekranu przekazanie Claude’owi rzeczywistego obrazu. Ciągi tekstowe pasują do danych skalarowych i JSON, natomiast listy bloków pasują do wyjścia multimodalnego.

Mów jasno, gdy coś się nie udało. Oznacz nieudaną próbę połączenia za pomocą is_error: true oraz wiadomości wyjaśniającej, np. „Błąd: nie znaleziono żadnego wydarzenia kalendarzowego pasującego do tej wyszukiwarki”, a nigdy wartości pustej ani samego słowa „Nieudane”. Spośród wszystkich nawyków związanych z wynikami to właśnie ten ma największe znaczenie. Jasny błąd pozwala Claude’owi spróbować ponownie z innymi argumentami, wybrać inne narzędzie lub poinformować użytkownika o tym, co się stało, podczas gdy ukryty błąd utrzymuje pętlę w martwym punkcie. Duży wynik może również zawierać cache_control, co pozwala zaoszczędzić koszty ponownej obróbki później.

Umowa kontynuacji

Trzy ścisłe zasady, z których każda stanowi częsty punkt awarii:

  • Jeden tool_result na jeden tool_use. Jeśli któryś z nich zostanie pominięty, żądanie zakończy się błędem 400, ponieważ modele oczekują jednoznacznej odpowiedzi na każdą wywołaną funkcję narzędzia.
  • Wysyłaj ponownie ten sam zbiór tools. Każda prośba zawierająca bloki tool_use lub tool_result musi określić narzędzia, do których odnoszą się, w przeciwnym razie zostanie odrzucona.
  • Wyniki stanowią odrębną rundę. Bloki tool_result należy dostarczyć jako wiadomość od użytkownika, umieszczoną na początku jej treści. Późniejsze instrukcje w tej rundzie są niedozwolone. Każda nowa instrukcja trafia do osobnej wiadomości od użytkownika po zakończeniu rundy z narzędziami.

Każdy wynik jest niezaufanym danym wejściowym

tool_result to nie dane neutralne. To tekst, który trafia do kontekstu modelu i wpływa na jego dalsze działanie. Jeśli pochodzi z otwartej sieci, z dokumentu dostarczonego przez użytkownika lub z jakiegokolwiek systemu poza pełną kontrolą, może zawierać instrukcje mające na celu przejęcie kontroli nad agentem, np. polecenie ignorowania wcześniejszych poleceń i wysłania bazy danych. Model może na to przystać. To właśnie jest iniekcja promptu, a w pętli agentowej stanowi rzeczywiste zagrożenie w produkcji, a nie tylko hipotetyczne.

Obrony są strukturalne i znajdują się wewnątrz pętli, którą piszesz:

  • Przyznaj agentowi najmniejsze uprawnienia, jakie potrzebuje. Narzędzie, którego mu brakuje, to działanie, do którego nie można go oszukać.
  • Uruchamiaj ryzykowne narzędzia w sandboxzie.
  • Sanitizuj wyniki, które przekształcają się w działania, na przykład poprzez dopasowywanie ścieżek plików do ustalonej katalogi, aby zapobiec przechodzeniu poza niej.
  • Niech krok po użyciu narzędzia redaguje, usuwając wszystko, co wygląda jak instrukcja, a nie dane.
  • Wszystko, co ma rzeczywiste konsekwencje, powinno przejść przez zatwierdzenie człowieka.

Wynik narzędzia nigdy nie powinien mieć możliwości zatwierdzenia czegoś, czego samo wezwanie narzędzia nie mogło zrobić.

Rozważaj wyniki jako kontekst, który również kuratorujesz. Każdy token wyniku pozostaje w oknie przez całą dalszą część rozmowy, dlatego dąż do krótkich, dobrze zstrukturyzowanych wyników: spójny JSON lub oznaczone pola są lepsze od prozy, którą model musi dekodować. Pętle, które gromadzą wiele wyników, mogą korzystać z funkcji usuwania wyników narzędzia – beta funkcji zarządzania kontekstem, która usuwa przestarzałe wyniki, gdy zbliża się limit. Użyj jej, zanim pętla stanie się na tyle duża, by wyczerpać dostępne miejsce.

7. Druga iteracja: wywołanie zależne

Oba wyniki są już dostępne, więc API jest wywoływane ponownie, przekazując całą listę wiadomości oraz definicje narzędzi. Kalendarz wskazuje godzinę 15:00 w Canary Wharf, a prognoza deszcz, mimo to nadal nie można określić, kiedy wyruszyć, ponieważ czas podróży zależy od lokalizacji, którą dopiero co poznało. To właśnie ta zależność sprawiła, że wywołanie nie mogło zostać przeprowadzone równolegle podczas pierwszej iteracji – musiało zostać zrealizowane w późniejszym etapie.

{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_02Tt",
      "name": "get_travel_time",
      "input": {
        "origin": "home",
        "destination": "Canary Wharf, London"
      }
    }
  ]
}

Uruchamiasz narzędzie według tej samej ścieżki: weryfikacja przed użyciem, wykonywanie oraz przetwarzanie po użyciu, po czym zwracasz jeden wynik:

{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_02Tt", "content": "{\"minutes\": 45}" }
  ]
}

Struktura pętli jest identyczna jak przy pierwszej iteracji. Różni się jedynie liczba wywołań oraz ich wzajemne powiązania. To właśnie należy zapamiętać: pętla nie zmienia swojej formy z kolejki na kolejkę i powtarza się, dopóki Claude nie przestanie zadawać pytań.

8. Trzecia iteracja: synteza i end_turn

Trzecie wywołanie API pokazuje, że Claude posiada już wszystkie potrzebne informacje. Nie są już wymagane żadne dodatkowe narzędzia, więc system zapisuje odpowiedź. end_turn zastępuje wcześniejszy powód zatrzymania, a pętla się kończy.

{
  "role": "assistant",
  "stop_reason": "end_turn",
  "content": [
    {
      "type": "text",
      "text": "Yes, bring an umbrella — rain is forecast in London tomorrow, around 12°C. Your meeting is at 3:00 PM in Canary Wharf, roughly 45 minutes away, so leave home by about 2:00 PM to arrive with a buffer."
    }
  ]
}

Łącznie są to trzy iteracje: dwa równoległe wywołania, jedno wywołanie zależne oraz faza syntezy. Model decyduje, co i kiedy pobrać, a Twój kod określa, w jaki sposób każde wywołanie ma zostać przeprowadzone, czy jest dozwolone oraz w jaki sposób wyniki mają zostać zwrócone. Kontekst rozszerza się z każdym ruchem, ponieważ cała historia jest ponownie wysyłana, ale trzy ruchy łatwo mieszczą się w dostępnym zakresie.

9. Cały pętla w kodzie

Wszystko, co dotychczas opisano, sprowadza się do jednej pętli w Client SDK. Sprawdza ona wartość stop_reason, kieruje każde zapytane narzędzie przez przewidziane kroki przed i po jego użyciu, odpowiada na każde wywołanie jednym wynikiem i kontynuuje działanie, dopóki Claude nie zakończy pracy.

import anthropic
client = anthropic.Anthropic()

messages = [{"role": "user", "content": user_request}]
MAX_ITERATIONS = 10
for _ in range(MAX_ITERATIONS):
    response = client.messages.create(
        model="claude-opus-4-8", max_tokens=1024, tools=tools, messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason != "tool_use":
        break   # end_turn (or another reason): done
    results = []
    for block in (b for b in response.content if b.type == "tool_use"):
        if not pre_tool_use(block):   # your guard - may block the call
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": "Blocked by policy.", "is_error": True})
            continue
        try:
            output = execute(block.name, block.input)   # run it (concurrently if independent)
            output = post_tool_use(block, output)   # redact / log / reshape
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
        except Exception as e:
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": f"Error: {e}", "is_error": True})
    messages.append({"role": "user", "content": results})   # one result per tool_use
print(response.content[-1].text)

Pętla wyjaśnia kilka kwestii. Samodzielnie zarządzasz zmienną messages, ponieważ API nie przechowuje żadnego stanu. Każdy stop_reason inny niż tool_use powoduje zakończenie procesu. Określony limit iteracji zapobiega niekontrolowanemu działaniu agenta, dlatego nigdy nie uruchamiaj pętli bez ograniczeń w środowisku produkcyjnym. Wszelkie błędy, zarówno blokowane wywołania, jak i wyjątki, są zwracane jako wyniki is_error zamiast powodować awarię rundy. Kod obejmuje również parametry max_tokens (w przypadku skróconej rundy należy albo zwiększyć limit, albo kontynuować) oraz pause_turn (runda z udziałem narzędzia serwera, którą trzeba odesłać ponownie). Każdy z tych elementów może pojawić się w tej samej pętli.

Lista kontrolna do zapamiętania

  • Narzędzia są deklarowane w tablicy tools wraz z name, description i input_schema, a każde żądanie opłaca tokeny wejściowe za te definicje.
  • tool_choice określa, czy narzędzie ma zostać wywołane: auto, any, tool lub none. Rozszerzone rozważania działają tylko przy użyciu auto i none, a strict: true zmusza argumenty do dokładnego dopasowania do schematu.
  • stop_reason wartości "tool_use" przekazuje kontrolę tobie. Znajdź wywołania, filtrowując content według kryterium type == "tool_use", a nigdy na podstawie pozycji.
  • Paralelne używanie narzędzi oznacza obecność kilku bloków tool_use w jednej odpowiedzi. Kolejność wykonywania, czy to równolegle, czy sekwencyjnie, jest do wyboru ciebie, a nie modelu. Wywołania zależne pojawiają się w późniejszych turach.
  • Uruchom wszystkie wymagane narzędzia i udziel odpowiedzi raz: jeden ruch użytkownika zawierający tool_result dla każdego tool_use, powiązanego za pomocą tool_use_id, przy niezmienionej tablicy tools. Brak wyniku powoduje błąd 400.
  • To ty odpowiadasz za luki w procesie wykonywania. Sprawdź i zabezpiecz dane przed uruchomieniem, a następnie przetwórz i usuną niepotrzebne informacje. To kroki, które sam piszesz, a nie funkcje, które konfigurujesz.
  • tool_result.content może być łańcuchem lub listą bloków. W przypadku błędu ustaw is_error: true wraz z pomocliwą wiadomością oraz utrzymuj wyniki krótkie i uporządkowane.
  • Traktuj wyniki narzędzi jako niezaufany kontekst. Najmniejsze uprawnienia, środowiska izolowane, czyszczenie danych oraz ludzka weryfikacja decyzji o istotnych działaniach to rozwiązanie problemu iniekcji kodu.
  • Pętla działa do momentu osiągnięcia wartości end_turn, przechowuje swój własny stan i wymaga sztywnego ograniczenia liczby iteracji.