У ваших менеджеров уходит по 30 минут на каждый запрос клиента: открыть CRM, найти заказ, проверить статус, ответить. Ассистент на AI отвечает общими фразами, потому что не видит ваши данные. Вот как заставить его самому смотреть статус заказа, проверять остатки и создавать задачи - без единого программиста в штате.
Нейросеть умная, но запертая в комнате. Она не видит вашу базу клиентов, не может посмотреть остаток на складе, не знает курс рубля на сегодня. Сама по себе модель отвечает только текстом.
Вызов функций (function calling) - это способ выпустить её из этой комнаты. Вы даёте модели список инструментов и говорите: "если нужно, попроси меня позвать вот эту функцию". Модель просит, ваш код выполняет, результат возвращается обратно. Модель формирует финальный ответ уже с реальными данными.
Зачем это бизнесу
Разберём на примере стройфирмы - это пример, а не реальный кейс автора. У вас есть прайс на работы и типовой договор подряда. Клиент пишет в чат: "Когда закончите ремонт в спальне?" Без вызова функций ассистент отвечает общие правила: "сроки по договору". С вызовом функций ассистент сам смотрит статус заказа в базе, проверяет историю обращений клиента, создаёт задачу прорабу. Один диалог вместо переписки между тремя отделами.
Компании из ритейла, которые внедрили такой сценарий, сообщают о снижении среднего времени обработки обращения с 8-12 минут до 1-2 минут. 80% запросов решаются без участия живого оператора.
Внутренний финансовый помощник. Тянет фактические цифры из учётной системы, считает отклонения от плана, формирует сводку для руководителя. Не выдумывает числа - берёт их у вас. Финансовый директор утром видит готовую сводку вместо того, чтобы ждать аналитика.
Это базовый кирпич для агентных систем. Все агенты-исполнители, которые разбираются в других статьях учебника, построены на этом механизме. AutoGen, CrewAI, LangGraph - все они используют function calling для связи агентов с реальными данными и действиями. Без этого механизма AI остаётся умным собеседником. С ним - становится исполнителем.
Как это работает на самом деле
Важный момент: модель не запускает ваш код напрямую. Она физически не может. Цепочка длиннее.
Пользователь спрашивает: "какая температура в Москве?". Модель не знает погоду. Но она видит в списке инструментов функцию get_weather. Вместо обычного текста модель возвращает структурированный сигнал: "вызови get_weather с параметром city=Moscow".
Ваш код получает этот сигнал. Вызывает реальный API погоды. Получает "18 градусов, облачно". Передаёт результат обратно модели. И только теперь модель формирует ответ пользователю: "В Москве 18 градусов, облачно".
Полная цепочка: пользователь -> модель -> сигнал на вызов -> ваш код -> результат -> модель -> финальный ответ. За один диалог модель может попросить несколько функций или сделать несколько шагов подряд. Сначала найти клиента, потом проверить его баланс, потом создать задачу. Каждый шаг - отдельный вызов, отдельный ответ в API.
Описание инструментов в OpenAI
Функции описываются в параметре tools. Это схема JSON (стандартный формат описания структуры данных). Модель читает описание и сама решает, когда и как использовать инструмент. Чем точнее описание - тем точнее решение модели.
Код ниже описывает функцию получения погоды и отправляет запрос. Параметр tool_choice="auto" означает, что модель сама выбирает - звать функцию или ответить текстом.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Получить текущую погоду в городе",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Название города на английском, например 'Moscow'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Единицы температуры"
}
},
"required": ["city"],
"additionalProperties": False
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Какая погода в Москве?"}],
tools=tools,
tool_choice="auto"
)
Параметр tool_choice управляет поведением модели. "auto" - модель решает сама, нужен ли вызов или можно ответить текстом. "required" - обязательно вызвать хотя бы одну функцию. "none" - запрет на вызов (полезно когда хотите получить только текстовый анализ без действий). Можно зафиксировать конкретную функцию: {"type": "function", "function": {"name": "get_weather"}}.
Строгий режим OpenAI
Когда вы включаете strict=True, модель гарантированно возвращает параметры точно по описанной схеме. Это важно для критичных операций - создания записей в базе, отправки писем, финансовых расчётов. Без строгого режима модель иногда импровизирует с форматом, что ломает код, ожидающий определённой структуры.
Код ниже описывает функцию создания пользователя в строгом режиме. Поле role может быть строкой или пустым - так в строгом режиме описываются необязательные параметры.
"function": {
"name": "create_user",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"username": {"type": "string"},
"email": {"type": "string"},
"role": {
"anyOf": [{"type": "string"}, {"type": "null"}]
}
},
"required": ["username", "email", "role"],
"additionalProperties": False
}
}
Два правила строгого режима. additionalProperties всегда False - нельзя передавать дополнительные поля. Все поля - в списке required. Если поле необязательное, оно описывается через anyOf с null.
Описание инструментов в Claude
Anthropic использует похожий, но не идентичный формат. Нет обёртки {"type": "function", ...} - инструмент описывается напрямую. Параметры называются input_schema вместо parameters. Параметр tool_choice задаётся через объект, а не строку.
Это не принципиальное различие - концепция та же. Просто два разных API с разными именами для одних и тех же вещей. Если вы работаете с обоими провайдерами, потребуется небольшая адаптация кода.
Следующий код делает то же самое, что и пример с OpenAI выше, но для Claude API.
tools = [
{
"name": "get_weather",
"description": "Получить текущую погоду в городе",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Название города"
}
},
"required": ["city"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto"},
messages=[{"role": "user", "content": "Какая погода в Москве?"}]
)
Цикл обработки: OpenAI
Один вызов функции - это один шаг. В реальных задачах модель часто делает несколько шагов: сначала найти клиента, потом проверить баланс, потом создать задачу. Для этого нужен цикл - повторять запросы к модели, пока она не выдаст финальный текст.
Код ниже реализует полный цикл. Отправляет запрос. Проверяет, попросила ли модель вызвать функцию. Если да - выполняет, возвращает результат модели. Повторяет, пока модель не выдаст финальный текст.
def run_agent(client, tools, messages):
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
choice = response.choices[0]
messages.append(choice.message)
if choice.finish_reason != "tool_calls":
return choice.message.content
for tool_call in choice.message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
result = dispatch_function(fn_name, fn_args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
Ключевые моменты. Проверяем finish_reason == 'tool_calls' - это сигнал модели "мне нужны инструменты". Сохраняем объект сообщения целиком, там есть tool_calls. Для каждого результата добавляем сообщение с ролью tool и идентификатором tool_call_id.
Цикл обработки: Claude
Логика та же, отличаются детали API. Проверяем stop_reason == 'tool_use' вместо finish_reason == 'tool_calls'. Результаты возвращаем через роль user с блоком типа tool_result - отдельной роли tool у Claude нет.
Код ниже реализует тот же агентный цикл для Claude API.
def run_agent_claude(client, tools, messages):
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
if response.stop_reason != "tool_use":
text_blocks = [b for b in response.content if b.type == "text"]
return text_blocks[0].text if text_blocks else ""
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = dispatch_function(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
messages.append({"role": "user", "content": tool_results})
Параллельный вызов нескольких функций
Модель может попросить несколько функций сразу. Это полезно, когда функции независимы. Проверить погоду в трёх городах. Получить данные из трёх разных систем. Сделать всё параллельно вместо последовательно.
Для бизнеса: вместо того чтобы получать данные о клиенте, его заказах и его балансе тремя последовательными запросами по 2 секунды каждый - всё три запроса одновременно за 2 секунды общего времени. Итоговое время ответа пользователю сокращается в 3 раза. Особенно заметно, когда агент работает с медленными внешними сервисами.
Код ниже выполняет несколько вызовов одновременно через asyncio.gather. Все функции стартуют параллельно, общая задержка ответа снижается.
import asyncio
async def handle_parallel_calls(tool_calls, dispatch_async):
tasks = [
dispatch_async(call.function.name, json.loads(call.function.arguments))
for call in tool_calls
]
results = await asyncio.gather(*tasks)
return [
{
"role": "tool",
"tool_call_id": call.id,
"content": str(result)
}
for call, result in zip(tool_calls, results)
]
Отладка и обработка ошибок
Функции падают. Кривые параметры, недоступный сервис, битые данные. Главное правило: не давайте программе упасть. Поймайте ошибку, верните её описание модели как обычную строку. Модель часто исправит параметры на следующем шаге или объяснит проблему пользователю.
Для бизнеса это означает: агент не ломается при первой же проблеме с данными. Он сообщает об ошибке пользователю или пробует другой путь. Это принципиальное отличие от хрупких правил автоматизации, которые останавливаются при любом неожиданном вводе. Традиционная автоматизация хрупкая - один нестандартный случай и всё встаёт. Агент с функциями - адаптивный, он работает с ошибками как с данными.
Код ниже показывает рабочий подход. Логируем каждый вызов, ловим любую ошибку, возвращаем её текстом.
def dispatch_function(name: str, args: dict) -> str:
import json
print(f"[tool] {name}({json.dumps(args, ensure_ascii=False)})")
try:
if name == "get_weather":
return get_weather(**args)
elif name == "create_task":
return create_task(**args)
else:
return f"Unknown function: {name}"
except Exception as e:
return f"Error: {str(e)}"
Для строгой проверки параметров используйте Pydantic (библиотека для проверки данных в Python). Это защищает реальные функции от мусора, который иногда генерирует модель.
from pydantic import BaseModel, ValidationError
class WeatherArgs(BaseModel):
city: str
units: str = "celsius"
try:
validated = WeatherArgs(**args)
except ValidationError as e:
return f"Invalid arguments: {e}"
Типичные ошибки
Размытые описания функций. Если в description написано "работа с данными" или "обработка запроса" - модель не поймёт, когда использовать инструмент. Пишите конкретно: что делает, что принимает, что возвращает, в каких ситуациях звать.
Пустые описания полей. Поле description внутри properties - не для красоты. Модель читает его, чтобы понять, что передавать. "Название города на английском, например 'Moscow'" - хорошо. Просто "Город" - плохо.
Стоимость описаний. Описания инструментов попадают во входные токены (токен - кусочек текста, по которому считается стоимость). Сложная схема с 10+ функциями - это 1000-3000 токенов накладных расходов на каждый запрос. Учитывайте при подсчёте бюджета на API.
Слишком много инструментов сразу. Давать агенту 20 функций одновременно - плохая идея. Модель путается в выборе, начинает вызывать не те функции или вызывать лишние. Давайте ровно те инструменты, которые нужны для текущей задачи. Это не только про качество - меньше инструментов означает меньше токенов и меньше затрат.
Не проверять результат перед передачей в модель. Если функция вернула 10 000 строк из базы данных, не передавайте всё это в контекст модели. Контекст ограничен, стоимость растёт линейно с размером. Всегда обрезайте или фильтруйте результаты перед возвратом.
Частые вопросы
Чем tool_choice='required' отличается от 'auto'?
"required" заставляет модель вызвать хотя бы одну функцию из списка. Полезно, когда нужно гарантированно получить структурированные данные, а не свободный текст. "auto" - модель решает сама, нужен вызов или нет.
Как ограничить модель одной конкретной функцией?
OpenAI: tool_choice={"type": "function", "function": {"name": "extract_data"}}. Claude: tool_choice={"type": "tool", "name": "extract_data"}. Модель обязана вызвать именно эту функцию, никакую другую.
Что делать, если модель передаёт битые параметры?
Включите strict=True в OpenAI. Подробнее описывайте поля в схеме. Если ошибки всё равно случаются - возвращайте описание ошибки в результате, обычно модель сама исправляется на следующем шаге.
Можно ли использовать асинхронные функции в цикле?
Да. Используйте AsyncOpenAI или AsyncAnthropic и async def для всего цикла. asyncio.gather даёт параллельные вызовы без ожидания очереди.
Как посчитать стоимость запроса с вызовом функций?
Описания функций в tools считаются как входные токены. Смотрите usage.input_tokens в ответе API - там уже учтены и сообщения, и инструменты. Для частых операций можно кешировать описания инструментов через prompt caching.
Что дальше
Function calling - это фундамент. Освоив его, вы поймёте, как устроены все агентные фреймворки изнутри. Следующий шаг - изучить, как передавать ответ по мере генерации, не заставляя пользователя ждать полного ответа. Для общего понимания, зачем это всё нужно - почитайте про то, что такое AI-агенты.
Ваш следующий шаг: возьмите один сценарий из вашего бизнеса - например, обработку запроса клиента о статусе заказа. Опишите 2-3 функции (найти заказ, проверить статус, создать задачу) и запустите цикл обработки через OpenAI или Claude. Бесплатного плана хватит на тесты. Время внедрения - 2-4 часа с готовым промптом.
AI Компас (t.me/kosmoslab_ai) - канал для предпринимателей в РФ и СНГ, которые применяют AI в своём бизнесе без программиста. Разбираем инструменты и схемы - без курсов и теории.