Function Calling и Tool Use: как LLM вызывает внешние инструменты и как это реализовать

Что такое function calling и зачем он нужен​


Function calling (вызов функций) — это механизм, при котором LLM не просто генерирует текст, а формирует структурированный запрос на выполнение внешнего действия: вызов API, запрос к базе данных, отправку письма, вычисление. Впервые эта возможность появилась в GPT-4 и затем была воспроизведена в других моделях.

Ключевое отличие от «классических» агентных подходов: способность вызывать функции приобретается моделью в процессе обучения (дообучения), а не только через промпт-инжиниринг. Модель не угадывает формат инструмента из контекста — она обучена генерировать вызов в определённом формате, который рантайм может распарсить и исполнить.

Цикл рассуждение–действие–наблюдение​


Любой агентный цикл строится на трёх шагах:

  1. Рассуждение (Reasoning). Модель анализирует запрос пользователя и определяет, какое действие нужно выполнить.
  2. Действие (Action). Модель генерирует структурированный вызов: имя функции и аргументы в формате JSON. Генерация останавливается.
  3. Наблюдение (Observation). Внешний код выполняет вызов и возвращает результат модели. Модель продолжает генерацию с учётом полученного результата.

Цикл может повторяться несколько раз, если задача требует последовательных действий.

Роли сообщений в диалоге с function calling​


В обычном чате чередуются роли user и assistant. Function calling добавляет новые роли:

РольНазначение
userЗапрос пользователя
assistantОтвет модели или запрос на вызов функции
toolРезультат выполнения инструмента

Пример диалога (формат, близкий к Mistral API):

JSON:
[
  {
    "role": "user",
    "content": "Каков статус моей транзакции T1001?"
  },
  {
    "role": "assistant",
    "content": "",
    "function_call": {
      "name": "retrieve_payment_status",
      "arguments": "{\"transaction_id\": \"T1001\"}"
    }
  },
  {
    "role": "tool",
    "name": "retrieve_payment_status",
    "content": "{\"status\": \"Paid\"}"
  },
  {
    "role": "assistant",
    "content": "Ваша транзакция T1001 была успешно оплачена."
  }
]

Обратите внимание: сообщение ассистента с function_call имеет пустой content. Модель не отвечает пользователю текстом — она запрашивает действие. Только после получения результата от роли tool формируется финальный текстовый ответ.

Специальные токены и шаблон чата​


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

  • [AVAILABLE_TOOLS] / [/AVAILABLE_TOOLS] — начало и конец списка доступных инструментов.
  • [TOOL_CALLS] — модель инициирует вызов инструмента.
  • [TOOL_RESULTS] / [/TOOL_RESULTS] — результат выполнения, после которого модель продолжает декодирование.

Конкретные токены различаются между моделями. Например, в Llama 3.x используются токены вида <|eom_id|> и JSON-блоки внутри сообщений ассистента, а в Qwen — свои маркеры. Важно: специальные токены — часть архитектуры модели, и их нельзя произвольно менять без дообучения.

Отличие от prompt-based агентов​


Существует два подхода к использованию инструментов:

АспектPrompt-based агентFunction calling
ОбучениеМодель не обучена вызывать инструментыМодель дообучена генерировать вызовы
Формат выводаЗависит от промпта, может «плыть»Стабильный структурированный JSON
Надёжность парсингаНизкая, нужны регулярки и эвристикиВысокая, формат предсказуем
ГибкостьМожно добавить любой инструмент без переобученияНабор инструментов задаётся в запросе, но формат фиксирован

Prompt-based подход — это когда вы описываете инструменты в системном промпте и просите модель ответить в определённом формате (например, JSON с полем action). Это работает, но модель может отклониться от формата, особенно при длинном контексте или сложных задачах.

Function calling решает проблему на уровне обучения: модель «знает», что после определённого токена нужно сгенерировать JSON с именем функции и аргументами. Это делает парсинг тривиальным.

Как добавить function calling модели без него​


Если модель не поддерживает function calling из коробки (например, Gemma 2 2B), есть два пути:

  1. Дообучение с LoRA. В модель добавляются новые специальные токены ([AVAILABLE_TOOLS], [TOOL_CALLS] и т. д.), затем она дообучается на датасете с примерами вызовов. LoRA позволяет сделать это с минимальными вычислительными затратами, обучая только низкоуровневые адаптеры.
  2. Prompt-based обёртка. Модель не переобучается, а вызовы эмулируются через промпт. Надёжность ниже, но не требует GPU для обучения.

Практическая реализация через API​


Большинство провайдеров (OpenAI, Anthropic, Mistral, Together AI) поддерживают function calling на уровне API. Общая схема:

Шаг 1: Описание инструментов​


Инструменты передаются в запросе как массив схем в формате JSON Schema:

JSON:
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Возвращает текущую погоду для указанного города",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "Название города"
            }
          },
          "required": ["city"]
        }
      }
    }
  ]
}

Шаг 2: Обработка ответа модели​


Модель возвращает сообщение с полем tool_calls (или function_call в старых версиях API). Ваше приложение должно:

  1. Извлечь имя функции и аргументы.
  2. Выполнить соответствующую функцию локально.
  3. Вернуть результат в диалог как сообщение с ролью tool.

Шаг 3: Повторный запрос к модели​


Результат инструмента добавляется в историю сообщений, и модель генерирует финальный ответ для пользователя.

Локальный инференс с function calling​


Для локального запуска моделей с поддержкой tool use подходят фреймворки вроде vLLM. vLLM поддерживает 200+ архитектур моделей, включая Llama, Qwen, Gemma, Mixtral и другие, и предоставляет OpenAI-совместимый API-сервер. Это означает, что вы можете поднять локальный сервер и использовать тот же протокол function calling, что и с облачными провайдерами.

Для структурированного вывода (в том числе для генерации аргументов вызова) vLLM поддерживает xgrammar и guidance — библиотеки, которые гарантируют, что вывод модели соответствует заданной JSON-схеме. Это снижает вероятность получения невалидного JSON от модели. Подробнее о структурированном выводе — в материале Structured Output: как получать от LLM валидный JSON и не парсить ответ регулярками.

Типичные ошибки при реализации​


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

2. Бесконечный цикл вызовов. Если результат инструмента не позволяет модели завершить задачу, она может снова запросить вызов. Ограничьте максимальное число итераций (обычно 5–10 достаточно).

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

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

5. Нечёткие описания функций. Поле description — это то, на что модель опирается при выборе инструмента. Размытое описание вроде «делает что-то с данными» приведёт к неверным вызовам. Пишите конкретно: что делает функция, какие параметры принимает, что возвращает.

Безопасность: что нужно учитывать​


Function calling даёт модели возможность инициировать действия в реальном мире. Это создаёт риски:

  • Prompt injection. Злоумышленник может через пользовательский ввод заставить модель вызвать нежелательную функцию. Валидируйте и ограничивайте набор доступных инструментов для каждого запроса.
  • Избыточные привилегии. Не давайте модели доступ к функциям, которые не нужны для текущей задачи. Принцип минимальных привилегий применим и здесь.
  • Отсутствие подтверждения. Для деструктивных операций (удаление данных, отправка платежей) добавляйте шаг подтверждения пользователем перед выполнением.

Связь с другими паттернами​


Function calling — не изолированная техника. Он комбинируется с другими подходами:


Итоговый чек-лист перед внедрением​


  • Убедитесь, что модель поддерживает function calling нативно или дообучена для этого.
  • Опишите каждый инструмент с чётким description и полной JSON Schema параметров.
  • Валидируйте аргументы перед выполнением.
  • Ограничьте число итераций цикла.
  • Передавайте ошибки инструментов обратно модели.
  • Для деструктивных действий требуйте подтверждение пользователя.
  • При локальном инференсе используйте structured output для гарантии валидного JSON.

Источники​


 
Назад
Верх Низ