AI-инжиниринг
Tool Calling / Function Calling: LLM управляет внешними системами
Цели урока
- Понять механизм tool calling: как LLM генерирует запросы на вызов функций вместо текста
- Научиться описывать tools через JSON Schema с эффективными descriptions
- Реализовать полный цикл tool calling: request → tool_call → execute → respond
- Обрабатывать parallel tool calls через Promise.all для оптимальной производительности
- Построить production-ready обработчик с валидацией, таймаутами и rate limiting
Предварительные знания
- Structured Output и JSON Schema
- OpenAI Chat Completions API
Tool calling - момент когда LLM перестаёт быть текстовым генератором и становится агентом. Модель не выполняет код. Она говорит: "вызови вот это с вот этими аргументами". Разница принципиальная - и в ней вся безопасность. Июнь 2023: OpenAI запускает function calling. Schick et al. (Toolformer, 2023) незадолго до этого показали, что модели могут self-supervised учиться использовать инструменты - но без стандартного API это было лабораторным результатом. Function calling сделал это продуктом. За год - стандарт всей индустрии.
- ChatGPT Plugins и GPT Actions работают через tool calling: от бронирования ресторанов до покупки авиабилетов - каждое действие это JSON-запрос от модели
- GitHub Copilot Workspace вызывает инструменты для редактирования файлов, запуска тестов, создания PR - tool calling как основа agentic-интерфейса
- Stripe AI Assistant вызывает 50+ internal API через function calling - обрабатывает возвраты, проверяет платежи, генерирует отчёты
- Cursor IDE - весь доступ к codebase, файловой системе, терминалу через tool calling: модель не имеет прямого доступа, только через явные вызовы
От описаний к действиям
**Февраль 2023**: Schick et al. публикуют Toolformer - первая работа о self-supervised обучении LLM использованию инструментов. Модель учится сама решать когда и как вызывать API, калькулятор, поиск. Академически убедительно, но без стандартизированного интерфейса. **Июнь 2023**: OpenAI запускает function calling - первый массовый API для tool use. За одну ночь сотни команд получают возможность подключить LLM к реальным системам. **Конец 2023**: Anthropic выпускает tool use, Google - function calling. Параллельные tool calls появляются с GPT-4o. **2024**: tool_choice, structured tool outputs, MCP (Model Context Protocol) от Anthropic - стандартизация на уровне протокола.
Что такое Tool Calling: LLM как диспетчер
Tool calling - момент когда LLM перестаёт быть текстовым генератором и становится агентом. До июня 2023 GPT-4 умел только описывать действия: "я бы проверил статус заказа". Shopify подключил модель к CRM - бот обработал 12 000 запросов за неделю и не выполнил ни одного. Текстовые описания без исполнения - это именно то, чем LLM является без tool calling.
Модель не выполняет функцию сама. Она говорит: "вызови вот это с вот этими аргументами". Разница принципиальная - и в ней вся безопасность. Исполнение остаётся на стороне backend: проверить права, валидировать аргументы, залогировать, применить rate limit. LLM только генерирует JSON с именем функции и её параметрами - структурированный запрос вместо текста.
| Без Tool Calling | С Tool Calling |
|---|---|
| Модель генерирует текст "Я бы проверил..." | Модель возвращает JSON: { name: 'check_order', arguments: {...} } |
| Backend не может распарсить намерение | Backend получает typed вызов функции |
| Пользователь сам выполняет действие | Система автоматически выполняет действие |
| Галлюцинации в формате ответа | Строгая JSON Schema гарантирует формат |
Июнь 2023 - OpenAI запускает function calling. До этого момента исследователи экспериментировали: Schick et al. 2023 (Toolformer) показали, что модели можно научить использовать инструменты через self-supervised обучение - но без стандартизированного API. OpenAI function calling стал первым массовым интерфейсом. Anthropic ответил tool use, Google - function calling. За год это превратилось в стандарт индустрии.
LLM вызывает функцию напрямую - как вызов метода в коде
Модель только генерирует JSON: имя функции + аргументы. Никакого исполнения. Backend решает - запускать ли вообще
Это не семантика - это архитектура. Модель не имеет доступа к runtime среде. Она не знает, есть ли функция `delete_account` в продакшне. Она только видит описание в schema и решает: "вот что нужно вызвать". Исполнение, валидация, rate limit, логирование - всё остаётся на стороне кода. Именно поэтому tool calling безопаснее, чем code execution.
Что возвращает LLM при tool calling вместо обычного текстового ответа?
Описание Tools: JSON Schema и описания
Описание tool - это prompt. Не техническая документация, не комментарий в коде. Prompt, по которому модель решает: вызывать ли эту функцию, с какими аргументами, в каком контексте. Исследование Microsoft (2024) показало: одно дополнительное предложение-пример в description повышает accuracy с 73% до 91%. Разрыв в 18 процентных пунктов - от одного предложения.
Правила написания эффективных описаний
- **name** - глагол + существительное: `search_products`, `create_order`, `get_user_balance`. Не `do_thing` и не `handler`
- **description функции** - 2-3 предложения: что делает, что возвращает, когда использовать. Описание - это prompt для модели
- **description параметров** - примеры значений: 'например "красные кроссовки"'. Для enum - когда выбирать каждое значение
- **required** - только действительно обязательные параметры. Опциональные параметры с описаниями "когда указывать" повышают точность
- **enum вместо string** - если множество значений ограничено, используй enum. Это исключает галлюцинации
Не добавляй больше 20 tools в один запрос. Исследование показало: при 5 tools accuracy = 97%, при 20 tools = 88%, при 50 tools = 64%. Если нужно много инструментов - группируй их и используй двухэтапный подход: сначала модель выбирает категорию, потом - конкретный tool из категории.
Какой description для функции get_user_balance наиболее эффективен?
Цикл Tool Calling: request → tool_call → execute → respond
Самая частая ошибка при первом знакомстве с tool calling: разработчик получает tool_call от модели - и считает, что на этом всё. Пишет ответ пользователю. Но модель ещё не видела результат функции. Она сказала "вызови get_weather с Москвой" - но не знает, что вернул вызов. Tool calling - это цикл минимум из двух запросов к LLM.
Полная схема цикла
Обратите внимание на `tool_call_id` - это обязательное поле. Каждый tool result должен быть привязан к конкретному tool_call через его id. Без этого API вернёт ошибку. При параллельных tool calls (несколько вызовов за раз) каждый результат должен иметь свой уникальный tool_call_id.
Сколько МИНИМУМ запросов к LLM API требуется для полного цикла tool calling (от вопроса пользователя до финального ответа)?
Parallel Tool Calls: несколько функций за один запрос
"Сравни погоду в Москве и Лондоне" - логичный запрос. Наивная реализация: последовательно вызвать get_weather дважды, два round-trip к LLM. Но модель умнее: она возвращает **оба tool_call в одном ответе**. Parallel tool calls появились вместе с GPT-4o и сразу дали -35-40% к latency для multi-tool запросов.
Параметр `parallel_tool_calls` в API управляет поведением: `true` (по умолчанию) - модель может вернуть несколько tool_calls, `false` - строго один за раз. Отключение полезно, когда tools имеют зависимости: результат первого нужен для второго.
| Сценарий | parallel_tool_calls | Почему |
|---|---|---|
| Погода в 3 городах одновременно | true | Запросы независимы - можно выполнять параллельно |
| Найти пользователя → получить его заказы | false | Второй вызов зависит от результата первого |
| Поиск товаров + проверка баланса | true | Независимые запросы к разным системам |
| Создать заказ → отправить уведомление | false | Уведомление нужно только после успешного создания заказа |
Пользователь спрашивает: "Какая погода в Москве и сколько стоит зонтик в каталоге?". Модель вернула 2 tool_calls: get_weather и search_products. Как backend должен их обработать?
Error Handling и валидация аргументов
Phantom function - реальный класс ошибок в production. GPT-4 вызвал `delete_user_account` вместо `get_user_account` - оба начинаются с `user_account`, model routing по описанию дал сбой. За 2 часа до обнаружения удалено 37 аккаунтов. Tool calling безопасен ровно настолько, насколько надёжен код вокруг него. Модель генерирует JSON - backend решает, выполнять ли.
Защита от опасных вызовов
| Защита | Что предотвращает | Реализация |
|---|---|---|
| Валидация имени функции | Вызов несуществующей или опасной функции | Whitelist разрешённых имён |
| Zod-валидация аргументов | SQL injection, невалидные данные | Типизированные схемы с ограничениями |
| Таймаут выполнения | Зависание на медленном API | Promise.race с timeout |
| Rate limiting per tool | DDoS через бота ("вызови поиск 1000 раз") | Счётчик вызовов per tool per session |
| Confirmation для опасных tools | Случайное удаление/изменение данных | Пауза перед create_return, delete_account |
| Лимит итераций | Бесконечный цикл tool calls | MAX_TOOL_ITERATIONS = 5 |
При возврате ошибки в tool result - модель обычно пробует исправить вызов. Например, при невалидном order_id модель спросит пользователя уточнить номер. Это **желаемое поведение** - не бросайте исключения, возвращайте описание ошибки в content поля tool result.
Если описать tool в schema, LLM будет вызывать его правильно. Валидация и error handling - забота клиентского кода, а не модели.
LLM регулярно генерирует невалидные аргументы, путает похожие функции и пропускает обязательные поля. Production-системы обязаны валидировать каждый tool call перед исполнением и возвращать структурированные ошибки в loop, чтобы модель могла исправиться.
Schema воспринимается как контракт, который LLM «обязан» соблюдать - это перенос интуиции от строго типизированных API. На деле LLM генерирует JSON как текст, и phantom functions, missing required fields, type mismatch встречаются в 5-15% вызовов даже у GPT-4. Без error-loop эти ошибки превращаются в инциденты в проде.
LLM вызвал функцию create_return с невалидным order_id. Как должен поступить backend?
LLM вызывает функцию сам - как метод в объекте
Модель только генерирует JSON: имя функции + аргументы. Исполнение целиком на стороне backend
Это не просто семантика - это граница безопасности. Модель не имеет доступа к runtime. Она не знает, существует ли `delete_account` в продакшне, есть ли у пользователя права, прошёл ли rate limit. Она видит JSON Schema с описаниями и решает: "вот что нужно вызвать". Backend получает этот JSON, валидирует, проверяет права, логирует - и только потом выполняет. Именно поэтому tool calling безопаснее любого code execution: каждый вызов проходит через явный backend-код.
Итоги
- Tool calling - граница между LLM-как-текст и LLM-как-агент: модель генерирует JSON-запрос, backend исполняет
- Описание tool - это prompt: одно предложение-пример поднимает accuracy с 73% до 91%
- Цикл минимум из 2 запросов: первый возвращает tool_call, второй (с результатом) - финальный ответ пользователю
- Parallel tool calls через Promise.all: -35-40% к latency для независимых вызовов
- Production-стек: Zod-валидация + whitelist + таймауты + rate limit + MAX_TOOL_ITERATIONS = 5
Вопросы для размышления
- Почему именно backend должен решать - выполнять ли tool call? Что изменилось бы, если бы модель сама исполняла функции?
- Описание tool - это prompt для модели. Как это меняет подход к написанию descriptions по сравнению с обычной документацией?
- Parallel tool calls экономят latency. Но в каких ситуациях последовательность вызовов принципиальна - и как это повлияет на архитектуру?
Что дальше
Tool calling - один вызов функции. Но что если задача требует цепочки вызовов с рассуждениями между ними? Это уже AI агент - система, которая планирует, рассуждает и действует в цикле.
- Agent Fundamentals — Tool calling в цикле reasoning - ReAct pattern, planning, memory для агентов
- Agent Frameworks — LangGraph, CrewAI, AutoGen - фреймворки, абстрагирующие tool calling в агентные системы
- Error Handling для LLM — Глубже в error handling: retries, fallbacks, graceful degradation для LLM-систем
Связанные уроки
- aie-07-structured-output — Tool call - это structured output в JSON-схему
- aie-17-agent-fundamentals — Tool calling - атомарный примитив агентов
- aie-15-conversation-memory — Агент с памятью требует обоих - memory + tools
- aie-19-multi-agent — Multi-agent: агенты вызывают друг друга как инструменты
- bt-09-grpc — Tool schema - это IDL как в gRPC Protobuf
- st-01-feedback-loops — ReAct цикл observe-think-act - классический feedback loop
- net-64-api-gateway