Учебник

Function Calling: AI сам проверяет заказы и создаёт задачи в CRM

Ваш AI-помощник отвечает общими фразами, потому что не видит данные бизнеса: статус заказа, остатки, историю клиента. Механика вызова функций (function calling) решает это - модель сама просит ваш код выполнить действие и возвращает ответ с реальными цифрами. Разбираем на примере стройфирмы: как ассистент сам находит клиента, проверяет баланс и создаёт задачу. Без программиста - за вечер настраиваете через готовые промпты.

Макс Космов··8 мин чтения

У ваших менеджеров уходит по 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 в своём бизнесе без программиста. Разбираем инструменты и схемы - без курсов и теории.