Перейти к содержимому
Команда ClaudexiaПРАКТИКА

Tool use на практике: схемы инструментов, циклы и типичные ошибки

Как спроектировать схему инструмента, чтобы модель её понимала, как устроен многошаговый цикл вызовов и какие ошибки чаще всего ломают function calling в проде.

Tool use выглядит просто в документации и ломается в проде по десятку причин сразу. Разберём, как строить схемы и циклы, чтобы этого не случалось.

Как это работает на уровне протокола

Вы описываете модели набор функций: имя, описание, JSON-схему параметров. Модель не вызывает функцию сама, она возвращает структурированный блок tool_use с именем и аргументами. Вы выполняете код на своей стороне, отправляете результат обратно как tool_result, и модель продолжает разговор уже с этим результатом на руках.

Важная деталь: модель может запросить сразу несколько инструментов за один шаг, если задача это позволяет. Ваш код должен уметь выполнить их параллельно и вернуть результаты в правильном порядке, иначе цикл начнёт путать, какой результат к какому вызову относится.

Как проектировать схему

Название функции и описание параметров это единственное, на что опирается модель при выборе инструмента. Расплывчатое описание вроде «работает с данными» ведёт к случайным вызовам не в тех ситуациях, где нужно.

Хорошая схема отвечает на три вопроса прямо в описании: что делает функция, когда её вызывать, а когда не вызывать. Пример:

{
  "name": "search_orders",
  "description": "Ищет заказы клиента по номеру телефона или email. Вызывай только если пользователь явно спрашивает про свой заказ. Не вызывай для общих вопросов о доставке или ценах.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "Телефон или email клиента" },
      "status": { "type": "string", "enum": ["any", "pending", "shipped", "delivered"] }
    },
    "required": ["query"]
  }
}

Перечисления вместо свободного текста снижают долю невалидных вызовов сильнее, чем любое улучшение промпта. Если параметр может принимать три значения, дайте enum из трёх значений, а не описание в прозе.

Многошаговый цикл

Реальная задача редко решается одним вызовом. Модель вызывает инструмент, смотрит на результат, решает, нужен ли следующий шаг, и так до финального текстового ответа. Каркас цикла на Python:

messages = [{"role": "user", "content": user_query}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4.6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})

    tool_calls = [b for b in response.content if b.type == "tool_use"]
    if not tool_calls:
        break

    results = [run_tool(call.name, call.input) for call in tool_calls]
    messages.append({
        "role": "user",
        "content": [
            {"type": "tool_result", "tool_use_id": call.id, "content": result}
            for call, result in zip(tool_calls, results)
        ],
    })

Каждый круг цикла это новый запрос с растущей историей сообщений. Считайте токены на каждом шаге, история сообщений набухает быстро, особенно если инструменты возвращают большие куски текста.

Частые ошибки в схемах

Слишком много инструментов сразу. Если у модели на выбор двадцать похожих функций, она чаще путает, какую вызвать. Группируйте похожие операции в один инструмент с параметром-режимом вместо десяти похожих отдельных.

Описание, написанное для человека, а не для модели. Комментарий в стиле «см. документацию API» бесполезен, модель не читает внешние ссылки, ей нужно всё нужное прямо в тексте описания.

Отсутствие обработки ошибок в tool_result. Если функция упала, верните это как результат с понятным текстом ошибки, а не бросайте исключение наружу. Модель умеет реагировать на ошибку и попробовать иначе, если видит её текстом.

Защита от зацикливания

Модель может застрять, повторяя один и тот же вызов с небольшими вариациями аргументов, особенно если инструмент возвращает пустой или неоднозначный результат. Ставьте жёсткий потолок на число шагов цикла, обычно хватает пяти-десяти, и явно прерывайте выполнение с сообщением об ошибке, если потолок достигнут.

Полезно также логировать каждый вызов инструмента с аргументами. Когда цикл зацикливается, лог сразу показывает, на каком шаге модель начала повторяться.

Разница между форматами

Через Claudexia доступны оба формата. У Anthropic это блоки tool_use и tool_result внутри content, у OpenAI это поле tool_calls в ответе ассистента и роль tool в следующем сообщении. Логика та же, но структура JSON различается, поэтому код цикла для двух форматов не переиспользуется один в один, если вы работаете напрямую с REST, а не через SDK.

Коротко

Схема инструмента должна объяснять модели, что делает функция и когда её вызывать, желательно с перечислениями вместо свободного текста. Цикл выполнения растёт по токенам с каждым шагом, считайте их и ставьте потолок на число итераций. Ошибки инструмента возвращайте как текст в tool_result, а не как исключение, модель справляется с ними лучше, чем ваш код думает.