COMRAD404 / HOWTO

Как использовать function calling в API

Практический разбор function calling в API: как описать функции, валидировать аргументы, безопасно выполнять вызовы и тестировать интеграцию без сюрпризов.

Короткий ответ

Function calling в API используют так: вы описываете доступные функции через строгую схему аргументов, передаёте этот список модели в запросе, получаете от неё структурированное намерение вызвать конкретную функцию, валидируете аргументы на своей стороне, выполняете действие в коде и при необходимости возвращаете результат обратно модели для финального ответа пользователю. Метод нужен там, где модели требуется доступ к внешним данным или операциям, но он не подходит для необратимых действий без подтверждения, для сценариев с жёсткой детерминированностью на уровне каждого байта и для интеграций, где вы не готовы проверять каждый вызов серверной логикой.

  • Подходит для поиска данных, расчётов, вызова внутренних сервисов, бронирования, работы с CRM и любой задачи, где нужны структурированные аргументы.
  • Не подходит как замена авторизации, бизнес-правил и контроля доступа: модель не должна решать, кому что можно.
  • Избыточен, если вам нужен только фиксированный JSON-ответ без вызова внешнего кода; в таком случае лучше ограничиться структурированным выводом.
  • Опасен без защитного слоя, если функция может списать деньги, удалить запись, отправить сообщение или изменить состояние системы.

Что понадобится

Минимальная интеграция состоит из пяти частей: API модели с поддержкой инструментов, серверный обработчик, описание функций в виде схемы, валидатор аргументов и код, который действительно выполняет действие. В терминологии разных провайдеров это называется по-разному: tools, functions, function calling. У OpenAI рабочая отправная точка описана в официальном руководстве по function calling.

Компонент Зачем нужен На что смотреть
API модели Принимает описание инструментов и возвращает решение о вызове Поддержка tools, контроль выбора инструмента, стабильный JSON
Серверный обработчик Разбирает ответ модели и запускает ваш код Allowlist функций, таймауты, аудит
Схема аргументов Ограничивает форму данных для вызова Жёсткие типы, required, enum, additionalProperties: false
Валидатор схем Проверяет, что аргументы допустимы Валидация до исполнения и понятные ошибки
Бизнес-функции Работают с БД, внешними API или внутренними сервисами Идемпотентность, авторизация, обработка сбоев
  • Доступ к внешнему сервису, который вы хотите вызвать: погода, биллинг, база заказов, календарь.
  • Понимание JSON Schema хотя бы на уровне типов, обязательных полей и перечислений.
  • Логи, где видно имя инструмента, сырые аргументы, результат валидации и исход выполнения.
  • Ясная политика по опасным операциям: подтверждение пользователя, запрет автозапуска или режим только чтения.

Ключевой принцип: модель не вызывает ваш код напрямую. Она только предлагает структурированный вызов. Исполняет всегда ваш сервер.

Пошаговый план

1. Отберите функции, которые действительно стоит отдавать модели

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

  • Хорошие кандидаты: чтение внешних данных, узкие действия с явными полями, маршрутизация по внутренним сервисам.
  • Плохие кандидаты: произвольный SQL, shell-команды, доступ к файловой системе, неограниченный HTTP-запрос по любому URL.
  • Если задача сводится к форматированию текста, function calling не нужен.
  • Если задача требует чтения большого массива документов, сначала решайте retrieval, а не вызов функции.

На старте держите набор инструментов маленьким. Чем больше список, тем чаще модель выбирает не тот путь или просит лишние уточнения.

2. Опишите функцию как контракт, а не как свободное описание

У каждой функции должны быть стабильное имя, короткое описание назначения и строгая схема аргументов. Не прячьте важные правила в длинный текст. То, что можно выразить типом или перечислением, лучше выразить типом или перечислением.

Простейшая схема для функции get_weather может выглядеть так: {"type":"object","properties":{"city":{"type":"string"},"units":{"type":"string","enum":["celsius","fahrenheit"]}},"required":["city"],"additionalProperties":false}.

  • Используйте enum там, где значений мало и они известны заранее.
  • Делайте обязательными только действительно обязательные поля.
  • Запрещайте лишние поля через additionalProperties: false.
  • Не заставляйте модель угадывать формат даты, валюты, таймзоны и единиц измерения.

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

3. Передайте инструменты в запрос модели

Дальше вы включаете список функций в запрос к API. В OpenAI это обычно поле tools с элементами типа function. В каждом элементе указываются имя, описание и параметры. Если ваш сценарий предполагает только один допустимый инструмент, используйте ограничение выбора инструмента, если API это поддерживает.

  • Не передавайте десятки функций без необходимости. Для одной пользовательской задачи чаще всего достаточно 2–5 инструментов.
  • Давайте модели инструкцию, когда нужно задавать уточняющий вопрос, а не вызывать функцию с догадками.
  • Явно указывайте, что запрещено выдумывать значения для обязательных полей.

Пример правила: если пользователь сказал «забронируй встречу на завтра», а функция требует точное время и часовой пояс, модель должна сначала уточнить недостающие параметры.

4. Разберите ответ модели и отделите намерение от исполнения

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

  • Проверяйте, что имя функции входит в разрешённый список.
  • Разбирайте аргументы строго как JSON; если парсинг не удался, это ошибка интеграции, а не повод «додумать» данные.
  • Храните отдельный лог сырых ответов модели и отдельный бизнес-лог выполнения функции.

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

5. Провалидируйте аргументы и выполните функцию на своей стороне

Даже если модель вернула корректный по виду JSON, валидируйте его заново по схеме и по бизнес-правилам. Схема отвечает за форму данных, а бизнес-логика — за допустимость действия в контексте пользователя, роли, лимитов и состояния системы.

  • Проверяйте типы, диапазоны, перечисления и обязательные поля.
  • Проверяйте авторизацию отдельно от prompt: доступ к заказу, счёту или календарю нельзя решать по тексту модели.
  • Для операций записи добавляйте идемпотентность, чтобы повторный вызов не создал дубль.
  • Ставьте таймауты и, по возможности, разделяйте инструменты только чтения и инструменты записи.

Если выполнение не удалось, возвращайте в модель короткую структурированную ошибку, например {"ok":false,"error":"slot_not_found"}. Не отправляйте HTML, stack trace и внутренние сообщения исключений: модель не должна видеть лишние детали вашей инфраструктуры.

Для опасных действий добавляйте подтверждение пользователя. Практическое правило простое: всё, что нельзя безболезненно откатить, должно требовать явного «да, подтверждаю» до исполнения.

6. Верните результат инструмента обратно модели

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

  • Успешный результат: только ключевые поля, например {"ok":true,"temperature":12,"units":"celsius"}.
  • Неуспешный результат: код ошибки и, если уместно, подсказка, что именно уточнить.
  • Большие ответы лучше агрегировать заранее: список из 500 строк хуже, чем краткая сводка и ограниченный набор полей.

Второй вызов модели не обязателен всегда. Если ваш backend уже знает, как отобразить результат пользователю без языковой обработки, можно обойтись без него. Но если нужна гибкая формулировка, пояснение ошибки или суммирование данных, второй проход полезен.

7. Добавьте production-защиту

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

  • Таймауты: отдельный лимит времени на каждую функцию.
  • Повторы: автоматические retry только для безопасных операций чтения.
  • Версионирование: при смене контракта лучше создать function_v2, чем тихо ломать старую схему.
  • Метрики: доля вызовов инструмента, доля ошибок валидации, доля уточняющих вопросов, средняя задержка выполнения.
  • Аудит: кто запросил действие, какую функцию выбрала модель, что реально выполнил сервер.
  1. Пользователь формулирует задачу.
  2. Вы отправляете текст и список инструментов в API модели.
  3. Модель выбирает функцию и возвращает аргументы.
  4. Сервер валидирует аргументы и проверяет права.
  5. Сервер выполняет функцию или возвращает ошибку.
  6. Результат при необходимости уходит обратно модели для финального ответа.

Типичные ошибки

  • Слишком широкие функции. Инструмент «выполни любую SQL-команду» почти всегда хуже, чем несколько узких функций с ясным контрактом.
  • Слабая схема. Если не заданы обязательные поля, перечисления и запрет лишних параметров, модель начнёт импровизировать.
  • Слепое исполнение. Нельзя запускать вызов только потому, что модель его предложила.
  • Смешение текста и данных. Результат инструмента должен быть структурированным. Текст «вроде всё получилось» плохо годится для следующего шага.
  • Отсутствие подтверждения на опасных действиях. Платежи, удаления, публикации и отправка сообщений без явного согласия — плохая идея.
  • Нет логов и тестов. Без записи аргументов и исходов вы не поймёте, где ошибка: в prompt, схеме, модели или внешнем сервисе.
  • Перегруженный набор инструментов. Когда функций слишком много и они перекрываются по смыслу, качество выбора падает.

Как проверить результат

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

Сценарий Ожидаемое поведение модели Что должен сделать сервер
Все поля есть Выбрать нужную функцию и передать валидные аргументы Успешно выполнить вызов и вернуть результат
Не хватает обязательного поля Запросить уточнение, а не выдумывать значение Не исполнять функцию до получения данных
Лишнее поле в аргументах Либо не передавать его, либо получить ошибку схемы Отклонить вызов как невалидный
Опасная операция Попросить подтверждение Блокировать исполнение без подтверждения
Таймаут внешнего сервиса Корректно обработать неуспех после результата инструмента Вернуть структурированную ошибку и не зависнуть
Повтор одного и того же запроса Не менять аргументы случайным образом Защититься от дублей через идемпотентность
  • Проверьте, что необязательные поля действительно остаются необязательными.
  • Проверьте локаль: числа с запятой, даты без года, города с неоднозначным названием.
  • Проверьте, что модель не вызывает инструмент, когда может ответить без него.
  • Проверьте, что логи содержат имя инструмента, аргументы, результат валидации, длительность и код исхода.

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

FAQ

Function calling — это автоматический запуск кода моделью?

Нет. Модель только возвращает структурированное намерение вызвать функцию. Реальный запуск всегда должен происходить в вашем приложении после валидации и проверок.

Нужен ли второй запрос к модели после выполнения функции?

Не всегда. Если backend может сам отдать пользователю готовый результат, второй проход не нужен. Но если требуется естественное объяснение, суммаризация или согласование нескольких результатов, второй запрос удобен.

Можно ли давать модели доступ к базе данных напрямую?

Обычно не стоит. Безопаснее дать узкие функции вроде get_order_by_id или list_open_tickets, чем разрешать произвольные SQL-запросы.

Что делать, если модель часто выбирает не ту функцию?

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

Какой формат ответа лучше возвращать из функции?

Короткий структурированный JSON с нужными полями. Избегайте свободного текста, HTML и избыточных данных. Чем чище и стабильнее контракт результата, тем легче модели корректно использовать его дальше.

Когда function calling лучше не использовать?

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

Читайте также

LINKS