smartaipack
Документация SmartAIPack

Вызов инструментов

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

Вызов инструментов через AI Gateway позволяет модели предложить вашему приложению функцию и аргументы, но не выполняет эту функцию вместо приложения. Вы отправляете описание доступных функций, разбираете choices[0].message.tool_calls, запускаете разрешённый код у себя и возвращаете результат сообщением с role: "tool".

В этом руководстве разобран цикл для POST https://route.smartaipack.ru/v1/chat/completions. Точный request_model_id, этот путь и признак «Инструменты» сначала проверьте в разделе Модели: набор моделей, поставщиков и возможностей меняется. Для начала работы понадобятся учётная запись в AI Gateway, API-ключ из переменной AI_GATEWAY_API_KEY и положительный баланс по актуальным ценам.

Как устроен вызов инструмента

Модель не получает прямого доступа к функции. Она видит только её имя, описание и JSON-схему аргументов. В ответ модель может предложить вызов примерно такого вида:

{
  "id": "call_abc123",
  "type": "function",
  "function": {
    "name": "get_order_status",
    "arguments": "{\"order_id\":\"ORD-1042\"}"
  }
}

Поле arguments здесь — строка с JSON, а не доверенный объект. Приложение должно разобрать её, проверить имя функции и аргументы, выполнить функцию и сформировать новое сообщение. Только после второго запроса модель сможет использовать результат в обычном ответе пользователю.

AI Gateway маршрутизирует запрос к выбранной модели, но не даёт ей доступ к операционной системе, базе данных или платежам. Шлюз также не выполняет встроенный веб-поиск, MCP-инструменты и автоматический цикл за ваше приложение. Все права, вызовы, повторы и остановка цикла остаются в вашем коде.

Если вы используете /v1/responses, откройте отдельное руководство по инструментам в Responses API. У этого протокола другая, зависящая от поставщика форма входа и выхода; смешивать её с messages и choices[0].message.tool_calls из Chat Completions нельзя.

Примеры тела запроса

Ниже показан минимальный цикл с функцией только для чтения. Значение <REQUEST_MODEL_ID> замените точным идентификатором варианта из каталога моделей.

Шаг 1. Отправить запрос с tools

curl https://route.smartaipack.ru/v1/chat/completions \
  -H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<REQUEST_MODEL_ID>",
    "messages": [
      {
        "role": "user",
        "content": "Какой статус у заказа ORD-1042?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_order_status",
          "description": "Возвращает текущий статус заказа по его идентификатору.",
          "parameters": {
            "type": "object",
            "properties": {
              "order_id": {
                "type": "string",
                "description": "Идентификатор вида ORD-1042"
              }
            },
            "required": ["order_id"],
            "additionalProperties": false
          }
        }
      }
    ],
    "tool_choice": {
      "type": "function",
      "function": { "name": "get_order_status" }
    },
    "stream": false
  }'

Для наглядности пример принудительно выбирает одну функцию — такая форма проверяется внутренней проверкой маршрута AI Gateway. Поддержка принудительного tool_choice зависит от модели и поставщика, поэтому для рабочего сценария сначала проверьте выбранный вариант; обычно безопаснее начать с "auto".

При выборе инструмента ответ содержит choices[0].message.tool_calls. Сохраните всё сообщение помощника, включая content и полный массив tool_calls: оно понадобится в истории следующего запроса.

Шаг 2. Выполнить функцию в приложении

Приложение берёт function.name, сверяет его со списком разрешённых функций, разбирает function.arguments и проверяет данные по схеме. В нашем безопасном примере функция читает локальную заглушку:

const orders = Object.freeze({
  "ORD-1042": { status: "ready_for_pickup", updated_at: "2026-08-22T10:30:00Z" }
});

function getOrderStatus({ order_id }) {
  if (!/^ORD-[0-9]{4}$/.test(order_id)) {
    throw new Error("Некорректный order_id");
  }

  return orders[order_id] ?? { status: "not_found" };
}

Это обычная функция вашего приложения. Она не обращается к внешнему API, ничего не меняет и не передаёт модели лишние данные.

Шаг 3. Вернуть результат и получить итог

Добавьте в messages исходный вопрос, полученное сообщение помощника и результат функции. tool_call_id должен точно совпасть с id предложенного вызова:

{
  "model": "<REQUEST_MODEL_ID>",
  "messages": [
    {
      "role": "user",
      "content": "Какой статус у заказа ORD-1042?"
    },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "get_order_status",
            "arguments": "{\"order_id\":\"ORD-1042\"}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_abc123",
      "content": "{\"status\":\"ready_for_pickup\",\"updated_at\":\"2026-08-22T10:30:00Z\"}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_order_status",
        "description": "Возвращает текущий статус заказа по его идентификатору.",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": { "type": "string" }
          },
          "required": ["order_id"],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": "auto",
  "stream": false
}

Отправьте это тело повторным POST на тот же /v1/chat/completions. Если дополнительных вызовов нет, текст итогового ответа находится в choices[0].message.content.

Практический пример

Этот самодостаточный пример на Node.js использует только встроенный fetch и локальный объект. Он допускает ровно одну функцию, проверяет JSON и ограничивает цикл четырьмя шагами и 20 секундами.

const apiKey = process.env.AI_GATEWAY_API_KEY;
const model = process.env.AI_GATEWAY_MODEL_ID;

if (!apiKey || !model) {
  throw new Error("Задайте AI_GATEWAY_API_KEY и AI_GATEWAY_MODEL_ID");
}

const url = "https://route.smartaipack.ru/v1/chat/completions";
const orders = Object.freeze({
  "ORD-1042": { status: "ready_for_pickup", updated_at: "2026-08-22T10:30:00Z" }
});

const tools = [
  {
    type: "function",
    function: {
      name: "get_order_status",
      description: "Возвращает текущий статус заказа по его идентификатору.",
      parameters: {
        type: "object",
        properties: {
          order_id: {
            type: "string",
            description: "Идентификатор вида ORD-1042"
          }
        },
        required: ["order_id"],
        additionalProperties: false
      }
    }
  }
];

function runAllowedTool(toolCall) {
  if (toolCall?.function?.name !== "get_order_status") {
    throw new Error("Запрошена неизвестная функция");
  }

  let args;
  try {
    args = JSON.parse(toolCall.function.arguments);
  } catch {
    throw new Error("Аргументы функции не являются JSON");
  }

  const validKeys =
    args && typeof args === "object" && !Array.isArray(args)
      ? Object.keys(args)
      : [];
  if (
    validKeys.length !== 1 ||
    validKeys[0] !== "order_id" ||
    typeof args.order_id !== "string" ||
    !/^ORD-[0-9]{4}$/.test(args.order_id)
  ) {
    throw new Error("Аргументы функции не прошли проверку");
  }

  return orders[args.order_id] ?? { status: "not_found" };
}

async function complete(messages, signal) {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model,
      messages,
      tools,
      tool_choice: "auto",
      max_tokens: 256,
      stream: false
    }),
    signal
  });

  if (!response.ok) {
    throw new Error(`AI Gateway вернул HTTP ${response.status}`);
  }

  const data = await response.json();
  const message = data?.choices?.[0]?.message;
  if (!message) throw new Error("В ответе нет сообщения модели");
  return message;
}

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 20_000);
const messages = [
  { role: "user", content: "Какой статус у заказа ORD-1042?" }
];

try {
  for (let step = 0; step < 4; step += 1) {
    const assistant = await complete(messages, controller.signal);
    messages.push({
      role: "assistant",
      content: assistant.content ?? null,
      ...(assistant.tool_calls ? { tool_calls: assistant.tool_calls } : {})
    });

    const calls = assistant.tool_calls ?? [];
    if (calls.length === 0) {
      console.log(assistant.content);
      break;
    }

    for (const call of calls) {
      const result = runAllowedTool(call);
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: JSON.stringify(result)
      });
    }

    if (step === 3) throw new Error("Превышен предел шагов");
  }
} finally {
  clearTimeout(timer);
}

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

Определение инструмента

Каждый элемент tools состоит из type: "function" и объекта function:

  • name — стабильное имя, которое приложение сверяет со своим списком разрешений;
  • description — конкретное объяснение, когда функцию можно применять;
  • parameters — JSON Schema для аргументов;
  • required — обязательные поля;
  • additionalProperties: false — запрет неожиданных полей на уровне схемы.

Делайте одну функцию для одной понятной операции. Имя get_order_status точнее, чем orders, а узкий параметр order_id безопаснее свободного объекта. Схема помогает модели сформировать аргументы, но не заменяет проверку на стороне приложения.

Использование инструмента и результата

Обработчик ответа должен различать обычный текст и запрос функции. Если есть tool_calls, для каждого элемента он проверяет id, type, имя и строку arguments. Затем он выполняет разрешённую функцию и возвращает короткий сериализованный результат с тем же tool_call_id.

Не подставляйте результат функции в сообщение пользователя или помощника. Роль tool явно связывает данные с конкретным вызовом и сохраняет корректную историю. Не пересказывайте самостоятельно сообщение помощника с tool_calls: передавайте его в следующем запросе в полученной форме.

После завершения проверьте запросы в разделе Использование, а расход по моделям и ключам — в Аналитике. Диагностику кодов ответа удобно вести по справочнику ошибок.

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

Иногда результат первой функции приводит к следующему предложению инструмента. Тогда приложение повторяет один и тот же порядок: запрос к модели, проверка tool_calls, локальное выполнение, добавление сообщений tool, новый запрос.

Это цикл приложения, а не встроенная автоматика AI Gateway. Обязательно задайте предел числа шагов, общее время выполнения и ограничение времени каждой функции. Также ограничьте число вызовов в одном ответе и общий размер возвращаемых данных. При достижении предела завершайте операцию контролируемой ошибкой, а не отправляйте запросы бесконечно.

Некоторые модели могут рассуждать между последовательными результатами инструментов. Это зависит от выбранной модели и поставщика, увеличивает задержку и расход токенов и не является отдельной функцией AI Gateway. Приложению не нужно запрашивать или раскрывать скрытую цепочку рассуждений; для доступных режимов смотрите отдельный материал о рассуждении в Responses API.

Рекомендации и расширенные схемы

Правила функций

Используйте короткие однозначные имена и описания. Ограничивайте строки по формату и длине, числа — диапазоном, а варианты — enum. Возвращайте только поля, необходимые для следующего решения модели. Большой внутренний объект повышает стоимость и риск утечки данных.

Выбор инструмента

Значение tool_choice: "auto" оставляет выбор модели. Формат принудительного выбора конкретной функции также существует в OpenAI-совместимом протоколе и используется проектной проверкой маршрута, но поддержка может отличаться у поставщиков. До применения проверьте выбранный вариант в каталоге моделей и коротким тестовым запросом. Не стройте рабочую логику на принудительном выборе без такой проверки.

Параллельные вызовы

Модель может предложить несколько элементов в tool_calls. Выполняйте их параллельно только тогда, когда операции независимы, безопасны и не меняют общий ресурс. Сохраните отдельный результат для каждого id. Параметр parallel_tool_calls поддерживается не каждым поставщиком, поэтому его нельзя считать универсальной возможностью AI Gateway.

Несколько инструментов

Передавайте только нужный для текущего сценария набор. Например, чтение статуса и чтение состава заказа можно разделить на две узкие функции. Не добавляйте универсальную функцию выполнения SQL, команд оболочки или произвольного HTTP-запроса: модельные аргументы нельзя считать разрешением на доступ к инфраструктуре.

Надёжность и безопасность

  • Разрешайте только имена функций из заранее заданного списка.
  • Разбирайте arguments как JSON и повторно проверяйте их собственной схемой.
  • Проверяйте авторизацию, организацию пользователя и принадлежность объекта до чтения данных.
  • Выдавайте функции минимальные права и отдельные учётные данные, если они нужны.
  • Ограничивайте время, число шагов, число вызовов и размер результата.
  • Удаляйте секреты и лишние персональные данные из результата и журналов.
  • Требуйте явного подтверждения человека для удаления, платежа, отправки сообщения и другого опасного действия.
  • Используйте ключ идемпотентности или собственную защиту от повторов для операций, которые нельзя выполнить дважды.
  • Логируйте имя функции, безопасные идентификаторы, длительность и исход без полного API-ключа и чувствительных аргументов.

Учитывайте общие лимиты API: повтор после сетевой ошибки не должен незаметно повторять действие. Если не удаётся определить причину отказа или поведение выбранного маршрута, передайте в поддержку время запроса, путь, request_model_id и код ошибки — без ключа и секретных данных.

На этой странице