Головна / Статті

Інструмент Claude tool_use End to End: Агентний цикл у трьох ітераціях

Інструмент Claude use end to end: один запит до Messages API через цикл агентів, із правилами визначення інструментів, вибору інструменту, паралельних викликів та результатів інструментів.

Видавець: REACTAPP.TOP19 хв читання4379 слів

Джерела: Part III | ‘tool_use’, End to End: The Agentic Loop in Three Iterations

Чому цикл потрібно писати самостійно

Робота з tool_use безпосередньо через API Messages (anthropic.Anthropic() за допомогою client.messages.create(...)) дає можливість створити цикл власноруч. Ваш код оголошує інструменти, аналізує причину зупинки, парсує запити Claude, виконує їх та повертає результати. Ручне написання такого циклу є основою кожної системи, що використовує інструменти, і це стандартна тема у навчальній програмі Claude Certified Architect.

Спочатку нагадуємо: API Messages є безстановим. Нічого не зберігається між викликами, тому кожен запит містить усю розмову, а етап використання інструментів зазвичай складається з кількох запитів, які повторюються, доки Claude не отримає необхідну інформацію. Решта процесу відбувається за допомогою одного запиту протягом усього цього циклу.

Цей приклад — це невеликий асистент для планування. Користувач запитує, чи взяти парасольку на зустріч у Лондоні завтра та о котрій годині вирушити з дому, щоб прибути вчасно. Для відповіді потрібні три окремі дані: деталі зустрічі, прогноз погоди та час у дорозі. Подивіться, як Claude розділяє запит на три етапи та скільки роботи лягає на ваш код, а не на модель.

1. Форма циклу

Кожна взаємодія з використанням інструментів проходить за одним чотирифазним циклом:

  1. Оголошується перелік інструментів у запиті.
  2. Claude запитує про один або кілька з них, що позначається значенням stop_reason: "tool_use".
  3. Ваш код виконує ці інструменти та збирає результати.
  4. Ви надсилаєте назад результати та знову викликаєте API.

Етапи з двого по четвертий повторюються, і кожен прохід вважається однією ітерацією. Цикл завершується, коли Claude перестає запитувати про інструменти та повертає звичайний результат (end_turn) або іншу причину зупинки. Оскільки API є безстановим, ви самі формуєте розмову та надсилаєте її знову щоразу. Нічого не рухається вперед, якщо тільки ваш код цього не зробить.

2. Оголошення інструментів

Claude обмежений інструментами, які ви вказуєте. Вони знаходяться у параметрі tools верхнього рівня запиту, який містить масив визначень. Кожного разу, коли tools присутній, API формує прихований системний запит на основі цих даних перед повідомленням користувача. Цей прихований запит оплачується: кожна назва, опис та схема використовують токени вхідних даних у кожному запиті, навіть якщо Claude ніколи не використовує цей інструмент.

Кожен клієнтський інструмент потребує трьох полів:

  • name — це унікальний ідентифікатор, який має відповідати формату ^[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
    }
  }
]

Визначайте їх ретельно. Керівництво Anthropic щодо ефективних інструментів зводиться до кількох звичок:

  • Будьте конкретні щодо назв інструментів. get_current_weather дає Claude більше інформації, ніж weather.
  • Називайте та описуйте параметри однозначно. Використовуйте user_id замість user, щоб було надано правильний тип значення, та описуйте кожен параметр, адже добре назване поле з нечітким описом все одно призводить до некоректних даних.
  • Тримайте набір інструментів невеликим. Занадто великий набір ускладнює створений системний запит та робить вибір моделі нечітким.
  • Перевіряйте все. Схема обмежує структуру, а не намір, тому перевіряйте кожен вхідний даний, який отримує ваш код.

Два доповнення до цих визначень заслуговують уважної уваги.

  1. strict: true має найвище значення. Він розташовується на верхньому рівні кожного інструменту поруч із name, description та input_schema та увімкнює обмеження граматики під час генерації аргументів інструменту, тож модель фізично не може створити input, який порушує вашу схему. Якщо його не ввімкнути, Claude здебільшого генерує коректні аргументи, але іноді може допускати помилки — наприклад "2" там, де потрібне число 2, значення поза межами enum чи відсутнє обов’язкове поле. Кожна така помилка призводить до збою під час виконання коду, який використовує цей виклик. Якщо увімкнути strict: true, input у будь-якому блоку tool_use обов’язково буде відповідати input_schema, а назва — безумовно буде однією з ваших власних інструментів.
  2. Одне з правил схеми гарантує, що кожен об’єкт має мати значення additionalProperties: false, тому воно присутнє у кожному з вищезгаданих інструментів. Суворе дотримання цих правил є частиною функції структурованих вихідних даних Anthropic. На момент написання цього тексту ця функція була загальнодоступною для сучасних моделей, таких як Opus 4.8, а старіші моделі, наприклад Sonnet 4.5, вимагали параметра beta structured-outputs-2025-11-13. Скомпільовані схеми зберігаються у кеші протягом 24 годин, тож наявність «теплого» кешу усуває необхідність повторної компіляції.

Ви також можете додати до визначень інструментів параметр cache_control, щоб кешувати їх між запитами та уникнути оплати за повторну обробку, що є корисним, коли набір інструментів є великим та стабільним.

Визначення описують лише вхідні дані

Визначення інструменту охоплює лише вхідні дані, тобто те, що Claude надсилає вам. Це дивує користувачів фреймворків, які визначають типи повернення. Простіше кажучи:

  • У визначенні обов’язковими є name, description та input_schema, а strict, input_examples та cache_control є додатковими, необов’язковими елементами. output_schema та error_schema не існує.
  • Значення, які повертаються, визначаються вашим кодом під час виконання, а потім передаються у блоку tool_result, описаному у розділі 6. Поле content може містити або рядок, або список блоків, причому API не перевіряє його на відповідність жодній схемі, оскільки такої схеми немає. Одна практична порада: інструмент може повернути об’єкт DTO, а стандартний парсер перетворює його на звичайний рядок за допомогою методу toString. Використання цієї перетвореної форми працює, але не є ідеальним. Краще конвертувати DTO у структурований JSON.
  • Помилки також є питанням часу виконання. Повідомляйте про невдалий виклик за допомогою параметра is_error: true у полі tool_result та інформативного повідомлення у полі content; у визначенні немає декларативного типу помилки. За замовчуванням SDK передає виниклу виняток до Claude як результат помилки, і Claude адаптується під це.
  • Нерівновага є навмисною. Визначення існують для того, щоб показати Claude, як викликати інструмент. Результат — це просто додатковий контекст, який повертається у цикл, тому, окрім tool_use_id, який пов’язує його з запитом, жодних умов не потрібно.
  • Хочете гарантованого формату вихідних даних? Перевірте його самі у коді перед тим, як щось надсилати назад, або застосуйте структуровані вихідні дані (output_config.format) до відповіді, яку Claude пише на основі цих результатів. Інструменти MCP мають власний outputSchema, але це функція шару MCP, а не вбудованих визначень.

Контроль над тим, чи буде Claude викликати інструмент

tool_choice визначає, чи можна викликати інструменти під час певного кроку та які саме.

  • Це поле знаходиться на верхньому рівні запиту поруч із tools та messages і не належить до жодного конкретного інструменту.
  • Це обмежує те, що може надати Claude у відповіді. Логіка залишається незмінною, адже Claude все одно читає весь запит.
  • Його дія поширюється лише на один запит, ніколи не є глобальним. API є безстановим, тому tool_choice керує лише відповіддю, яка надсилається разом з ним, тобто однією ітерацією. Щоб зберегти його між ітераціями, потрібно пересилати його щоразу. Зміна його на кожній ітерації є допустимою: можна змусити використати інструмент раніше, пізніше використати auto, а для остаточної текстової відповіді — none.

Об’єкт виглядає так:

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

type є обов’язковим та може мати одне з чотирьох значень. name потрібен лише тоді, коли type дорівнює "tool". disable_parallel_tool_use є необов’язковим та обмежує кількість інструментів, які можна викликати за один раз.

auto. Claude сам вирішує: жодного інструменту, один або кілька інструментів, або текстова відповідь. Дозволені паралельні виклики. Допускається відповідь у простому текстовому форматі, і Claude може надати пояснення перед викликами. Це стандартний режим, якщо вказано tools. Встановлення disable_parallel_tool_use: true обмежує використання лише одним інструментом, хоча можливий варіант з нулем інструментів. Цей режим підходить для чат-асистентів та агентних циклів, де різні кроки вимагають різних дій. Це єдиний режим, який дозволяє Claude голосно міркувати перед виконанням дії, а також єдиний режим, окрім none, який підтримує розширене міркування.

any. Claude мусить викликати інструмент, але сам вибирає, який. Виконується принаймні один виклик, і оскільки паралельне використання увімкнене за замовчуванням, може повернутися кілька блоків tool_use, а не обов’язково один. Рівно один виклик відбувається лише у разі використання параметра disable_parallel_tool_use: true. Простий текст дозволений не є, передумови також відсутні, і це ніколи не є значенням за замовчуванням. Використовуйте його тоді, коли результат, отриманий за допомогою інструменту, є обов’язковим, але сам інструмент може відрізнятися, як у випадку з маршрутизаторами та класифікаторами.

tool. Claude має викликати саме той інструмент, який ви вказали, тому параметр name є обов’язковим. Має відбутися принаймні один виклик цього інструменту (він може викликати його кілька разів одночасно), і саме один з параметром disable_parallel_tool_use: true. Жодного простого тексту, жодних пояснень перед результатом. Цей підхід підходить для одноразової структурованої екстракції, обов’язкового першого кроку чи для схеми використання JSON через інструмент, наприклад {"type": "tool", "name": "get_weather"}. Запит із назвою інструменту, якого немає у списку tools, повертає помилку 400, а примусове використання інструменту не обмежує кількістю викликів, якщо паралельне використання не вимкнено.

none. Claude не може використовувати жодних інструментів у цьому раунді, навіть якщо вони визначені, а stop_reason не може дорівнювати tool_use. Дозволяється використання звичайного тексту, а також пояснень перед відповіддю. Це стандартний режим, коли не вказано жодних tools. Використовуйте його, щоб змусити систему працювати лише з текстом, наприклад під час фінальної синтезації, залишаючи масив tools незмінним. Цей режим вимикає інструменти без змін масиву, проте перехід у нього все одно скасовує кешовані блоки повідомлень.

Існує кілька важливих деталей, які легко пропустити:

  • У примусових режимах відсутній вступний текст. У режимах any або tool API автоматично заповнює частину повідомлення асистента, тож блок tool_use надходить порожнім: без пояснень чи обґрунтувань, незалежно від запиту. Для відображення пояснень потрібно використовувати режим auto.
  • Для розширеного мислення потрібні значення auto або none. Його не можна поєднувати з примусовим використанням інструментів, і API відхиляє таку комбінацію.
  • Правило підрахунку для disable_parallel_tool_use. Значення auto означає щонайбільше один інструмент, any та tool — саме один, а значення за замовчуванням false дозволяє паралельні виклики у моделях Claude 4.
  • Витрати на кешування. Зміна значення tool_choice між запитами усуває ефективність кешованих блоків повідомлень. Визначення інструментів та системний запит залишаються у кеші, але розмова обробляється заново.
  • Поєднуйте з strict. tool_choice забезпечує виконання певного інструменту, а strict — дотримання його аргументів схемою. Разом вони забезпечують отримання типізованого, структурованого результату після кожного кроку.
  • У запиті:

    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,
    )
    

    З цього випливають два правила дизайну. Залиште використання примусових інструментів лише для тих кроків, де справді необхідний результат від інструменту, а якщо текстова відповідь є прийнятною — залишайтеся на режимі auto. Крім того, коли використовується примусовий інструмент класифікації, додайте до його переліку опцію "other", щоб Claude міг відхилити певну категорію замість того, щоб неправильно позначити вхідні дані, які він не може класифікувати.

    3. Перша ітерація: Claude просить про інструменти

    Запит типу «один» об’єднує масив інструментів, вибір tool_choice та запит користувача:

    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,
    )
    

    Клод аналізує запит та помічає, що дві з трьох його потреб не залежать одна від одної: пошук у календарі та прогноз можуть виконуватися одночасно. Тому він запитує обидві інструменти в одній відповіді. Окремі виклики, які повертаються разом, називаються паралельним використанням інструментів, і вони відображаються як кілька блоків tool_use у одному кроці.

    {
      "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" } }
      ]
    }
    

    У цій відповіді виділяються три аспекти.

    1. stop_reason дорівнює tool_use, що означає, що етап ще не завершений, і Claude чекає на вас.
    2. Масив content містить змішаний формат даних. Блок text з описом часто йде перед блоками tool_use, тому виклики інструментів не знаходяться у фіксованій позиції.
    3. Кожен блок tool_use має id (з префіксом toolu_), name та об’єкт input, який вже відповідає вашій схемі.

    Щоб знайти відповідні виклики, перегляньте елементи масиву content та відфільтруйте їх за типом, а не за індексом:

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

    Це призводить до двох викликів тут. Кожен id є ідентифікатором для отримання результату відповідного виклику, а поєднання id з результатом дозволяє Claude зіставляти відповіді з запитаннями, коли є кілька нерозв’язаних запитань.

    Примітка для моделей Claude 4: розширене мислення може відбуватися періодично, тож логічні міркування можуть відбуватися як між викликами інструментів, так і перед ними. Коли мислення увімкнене, необхідно передати блоки мислення разом із рештою частини відповіді асистента, інакше продовження відповіді забуде про ці міркування.

    4. Виконання інструментів

    Тепер обов’язки переходять до вашого коду. Claude запитав два інструменти, але жоден з них не було виконано. Виконайте все, про що він просив, зіберіть усі результати та надішліть єдину подальшу відповідь, яка міститиме їх усі разом. Claude чекатиме, поки кожен з його викликів отримає відповідний результат.

    Чи буде виконання паралельним чи послідовним — це ваш вибір, адже Claude це не вказує. У відповіді сказано, що потрібно викликати, а не як це запланувати. Незалежні виклики, такі як пошук у календарі чи прогноз погоди, можна виконувати одночасно для зменшення затримки. Якщо один виклик залежить від результату іншого, виконуйте їх по черзі. Модель натякає на залежність, групуючи незалежні завдання в паралельні блоки та відкладаючи залежні завдання на наступний етап, що й відбувається під час другої ітерації. Шини, асинхронність чи черга завдань — це вибір вашого циклу, а не API.

    У продакшні сприймайте виконання як ненадійний вхід/вихід, який може зазнати невдач:

    • Встановіть час вимкнення для кожного інструменту, щоб один повільний виклик не заблокував усю процедуру.
    • Якщо це можливо, віддавайте перевагу ідемпотентним операціям, адже повторні спроби та відтворення є неминучими у агенті.
  • Контролюйте помилки окремо для кожного виклику. Коли один із двох паралельних інструментів створює помилку, все одно надсилайте результат для нього (деталі про is_error нижче), замість того щоб припинити виконання.
  • Вимірюйте роботу циклу. Затримка для кожного інструменту, кількість викликів, використання токенів та рівень помилок допомагають з’ясувати, чи працює агент належним чином, чи поступово погіршує свою роботу.
  • 5. Кроки до та після виконання інструменту

    Керування циклом означає керування двома етапами: від моменту, коли „Claude запитав про інструмент“, до моменту, коли „інструмент виконався“, та від моменту, коли „інструмент виконався“, до моменту, коли „результат надійшов назад“. Їм потрібні назви, адже якістю їхнього проектування зумовлюється основна різниця між безпечним та ризикованим агентом.

    Крок перед використанням інструменту виконується до початку роботи. Він аналізує запит на виконання та вирішує, чи виконати його без змін, змінити чи відхилити. Саме тут відбувається контроль – Claude пропонує варіанти, а ваш код приймає рішення.

    Крок після використання інструменту виконується після завершення роботи та перед тим, як результат повернеться до Claude. Він обробляє необроблений вихідний даний шляхом приховування інформації, обрізання, переформатування чи запису в журнал.

    У циклі Client SDK це просто функції, які оточують процес виконання:

    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))
    

    Саме на етапі перед використанням інструменту знаходяться справжні механізми контролю, і це обов’язок того, хто його впроваджує, а не моделі. Поширені патерни виробництва:

    • Перевіряйте аргументи згідно з власною політикою та схемою.
    • Категорично відмовляйте у виконанні руйнівних операцій: rm -rf для інструментів командного рядка, DROP TABLE для інструментів баз даних.
    • Запитуйте у людини схвалення для будь-яких дій із високим впливом, наприклад, запису даних у продакшн.
    • Видаляйте конфіденційну інформацію з аргументів перед тим, як вони надійдуть до зовнішнього сервісу.
    • Фіксуйте кожну спробу виклику, щоб була можливість аудиту та перевірки дотримання правил.

    Після цього на етапі після використання інструменту обробляються результати, видаляються облікові дані чи особисті інформації перед тим, як вони потраплять у контекст, скорочуються надто великі об’єми даних та фіксуються результати.

    6. Повернення результатів

    Відповідайте на запит tool_use повідомленням від user, яке містить список блоків tool_result — по одному на кожен блок tool_use у відповіді, причому кожен з них повинен бути прив’язаний до свого tool_use_id. Спочатку додайте запит асистента до поточного списку повідомлень, а потім результати як новий запит від користувача. Перша ітерація виглядає так:

    {
      "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}"
        }
      ]
    }
    

    Два формати вмісту. content результату може бути звичайним рядком, серіалізованою значенням, як у прикладі, або списком блоків вмісту. Блоки містять більше, ніж лише текст, оскільки можна поєднувати text та image, що дозволяє інструментам для створення діаграм чи скріншотів надавати Claude справжнє зображення. Рядки підходять для скалярних та JSON-даних, а списки блоків — для багатомодального виводу.

    Чітко вказуйте, коли щось зазнало невдачі. Позначте невдалий виклик значенням is_error: true та пояснювальним повідомленням, наприклад "Error: no calendar event found matching that query", ніколи не використовуйте порожнє значення чи просто "Failed". Серед усіх звичок, пов’язаних із результатами, саме ця має найбільше значення. Чіткий повідомлення про помилку дозволяє Claude спробувати знову з іншими параметрами, обрати інструмент або пояснити користувачеві, що сталося, тоді як прихована помилка змушує цикл застрягти. Великий результат також може містити значення cache_control, що дозволяє уникнути повторної обробки пізніше.

    Контракт продовження

    Три суворі правила, кожне з яких є поширеною причиною невдачі:

    • Один tool_result на кожен tool_use. Якщо пропустити один з них, запит зазнає невдачі з помилкою 400, оскільки моделі очікують однозначної відповіді на кожен виклик інструменту.
    • Надішліть знову той самий масив tools. Будь-який запит, який містить блоки tool_use або tool_result, повинен визначати інструменти, на які вони посилаються, інакше запит буде відхилений.
    • Результати утворюють окремий етап. Надсилайте блоки tool_result як повідомлення користувача на початку його вмісту. Інструкції, що йдуть після цього, заборонені. Будь-які нові інструкції слід додавати у окреме повідомлення користувача після того, як етап використання інструментів завершиться.

    Кожен результат є ненадійним вхідним даним

    tool_result — це не нейтральні дані. Це текст, який потрапляє до контексту моделі та впливає на наступні кроки її роботи. Якщо він походить з відкритого Інтернету, з документа, наданого користувачем, або з будь-якої системи, яка не знаходиться під вашим повним контролем, він може містити вбудовані інструкції, спрямовані на захоплення агента, наприклад, команду проігнорувати попередні інструкції та надіслати базу даних. Модель може їм підкоритися. Це є ін’єкцією запиту, і в ланцюзі агентів це є реальною проблемою у продакшені, а не гіпотетичною.

    Захисти є структурними та знаходяться всередині ланцюга, який ви пишете:

    • Надайте агенту мінімальні привілеї, необхідні для його роботи. Інструмент, якого у нього немає, — це дія, в яку його неможливо обманом змусити.
    • Виконуйте ризиковані інструменти в сандбоксі.
  • Очищення вихідних даних, які перетворюються на дії, наприклад шляхом знаходження шляхів до файлів у визначеній директорії, щоб запобігти їх проходженню.
  • Нехай крок після використання інструменту видаляє все, що схоже на інструкцію, а не на дані.
  • Усе, що має реальні наслідки, має проходити через людське схвалення.
  • Результат інструменту ніколи не повинен мати можливості авторизувати те, що сам інструмент не міг зробити.

    Розглядайте результати як контекст, який ви також кураторуєте. Кожен токен результату залишається у вікні протягом усієї розмови, тому прагніть до короткого та добре структурованого вихідного тексту: послідовний JSON або позначені поля кращі за прозу, яку модель мусить декодувати. Цикли, які накопичують багато результатів, можуть використовувати функцію очищення результатів інструменту — бета-функцію керування контекстом, яка видаляє застарілі результати, коли наближається ліміт. Використовуйте її до того, як цикл стане настільки великим, що заповнить вікно.

    7. Друга ітерація: залежний виклик

    Обидва результати вже отримані, тому API викликається ще раз із передачею всього списку повідомлень та визначень інструментів. Календар повідомляє про час 15:00 у Canary Wharf, а прогноз — про дощ, проте він все одно не може вказати, коли вирушати, оскільки час у дорозі залежить від місця, про яке він щойно дізнався. Саме ця залежність є причиною того, що виклик не можна було здійснити паралельно під час першої ітерації. Його довелося виконати у наступному етапі.

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

    Ви запускаєте інструмент через ту саму послідовність перевірки перед використанням, виконання та обробки після використання, і повертаєте єдиний результат:

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

    Структура циклу збігається з першою ітерацією. Відрізняється лише кількість викликів та їхній залежності. Саме цей принцип потрібно запам’ятати: форма циклу не змінюється з кожної ітерації, і він продовжується, поки Claude не припинить ставити запитання.

    8. Третя ітерація: синтез та end_turn

    Під час третього виклику API Claude вже має всі необхідні факти. Додаткових інструментів не потрібно, тому він записує відповідь. end_turn замінює попередню причину зупинки, і цикл завершується.

    {
      "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."
        }
      ]
    }
    

    Загалом це три ітерації: два паралельні виклики, залежний виклик та синтез. Модель вирішує, що та коли отримати, а ваш код визначає, як відбувається кожен виклик, чи це дозволено та як повертаються результати. Контекст зростає з кожним кроком, оскільки вся історія надсилається знову, але три кроки легко вміщуються у вікно.

    9. Весь цикл у коді

    Усе, що було розглянуто досі, об’єднується в один цикл у Client SDK. Він перевіряє stop_reason, направляє кожен запитаний інструмент через ваші кроки перед та після використання інструменту, відповідає на кожен виклик одним результатом та продовжує роботу, поки Claude не завершить свою роботу.

    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)
    

    Цей цикл описує деякі моменти. Ви самі керуєте масивом messages, оскільки API не зберігає стан. Будь-який значення stop_reason, окрім tool_use, призводить до завершення. Жорсткий ліміт кількості ітерацій запобігає неконтрольованій роботі агента, тому ніколи не використовуйте необмежений цикл у продакшені. Усі види помилок, заблоковані виклики та винятки повертаються як результати is_error, замість того щоб призвести до збою етапу. Готовий код також враховує параметр max_tokens (що означає обрізання етапу, тож потрібно або підвищити ліміт, або продовжити роботу) та pause_turn (етап роботи сервера-інструменту, який необхідно надіслати знову). Кожен з цих факторів може з’явитися в одному циклі.

    Список перевірки для запам’ятовування

    • Інструменти оголошуються в масиві tools разом із name, description та input_schema, і кожен запит оплачує кількість токенів вхідних даних відповідно до цих визначень.
    • tool_choice визначає, чи буде викликано інструмент: auto, any, tool або none. Режим розширеного мислення працює лише з auto та none, а параметр strict: true змушує аргументи точно відповідати схемі.
    • Якщо stop_reason дорівнює "tool_use", контроль передається вам. Виявляйте виклики шляхом фільтрації content за критерієм type == "tool_use", а не за положенням.
    • Паралельне використання інструментів означає наявність кількох блоків tool_use у одній відповіді. Порядок виконання — чи то паралельний, чи послідовний — вибираєте ви, а не модель. Залежні виклики надходять у наступних етапах.
    • Запустіть усі запитані інструменти та відповідайте один раз: один крок користувача містить tool_result для кожного tool_use, що поєднується за допомогою tool_use_id, причому масив tools залишається незмінним. Відсутній результат призводить до коду 400.
    • Ви несете відповідальність за проміжки часу під час виконання. Перевіряйте та захищайте дані перед запуском, а потім обробляйте та мінімізуйте ризики. Це кроки, які ви пишете, а не функції, які налаштовуєте.
    • tool_result.content може бути рядком або списком блоків. У разі помилки встановіть is_error: true з корисним повідомленням та зберігайте результати короткими та структурованими.
    • Ставіться до результатів інструментів як до ненадійного контексту. Принцип мінімальних привілеїв, сандбокси, санітайзація даних та людське схвалення для критичних дій — це способи протидії вставці коду.
    • Цикл продовжується до моменту досягнення end_turn, зберігає власний стан та потребує жорсткого обмеження кількості ітерацій.

    Пов’язана література

  • Практичні нотатки: Усередині ARD: Як насправді функціонує специфікація Agentic Resource Discovery — Детальний огляд Практичних нотаток: Усередині ARD: Як насправді функціонує специфікація Agentic Resource Discovery: контракти, перевірки та шаблони коду для команд, які впроваджують цю модель.