Что общего между LLM и телефонным автоответчиком?
Помните старые добрые времена, когда ChatGPT мог только болтать? Вы просите: «Напомни мне встретиться с Петей завтра», а он отвечает: «Конечно, я напомнил!» — и ничего не происходит. Потому что он не умеет нажимать кнопки. Автоответчик тоже болтает, но календарь не откроет.
LLM без tool calling — это тот же автоответчик: умный и красивый, но бесполезный, когда нужно сделать реальное дело. Tool calling (или function calling) — механизм, который превращает болтуна в деятеля. Модель не пишет «Я отправил письмо», а вызывает функцию для отправки письма. И письмо действительно улетает.
Суть: LLM генерирует JSON с именем функции и аргументами, а внешний код выполняет эту функцию. Модель только решает, какую функцию вызвать и с какими параметрами.
Почему JSON круче обычного текста
Можно было бы просить модель писать команды на естественном языке: «отправь письмо Пете с темой „Привет“». Но это путь в никуда. Парсить естественный язык — ад. JSON же — структура, которую железно разберёт любой парсер.
Когда модель выбирает tool, она возвращает объект вроде:
{
"function": "send_email",
"arguments": {
"to": "petr@example.com",
"subject": "Привет",
"body": "Созвонимся завтра?"
}
}
Этот JSON — мост между «думающей» нейросетью и «делающим» кодом. Вызываешь функцию, передаёшь аргументы, получаешь результат, отправляешь его обратно модели — и она продолжает диалог уже с учётом реального результата.
Анатомия tool calling: от промпта до API
Разберём цикл на пальцах. Всё начинается с того, что вы описываете модели список доступных инструментов. Каждый инструмент — это функция с именем, описанием и схемой параметров в JSON Schema. Модель не выполняет код, она только генерирует вызов. Исполнение — ваша зона ответственности.
1 Определите функции
Допустим, мы пишем агента для путешествий. Ему нужны: get_weather(city, date) и book_hotel(hotel_name, checkin, checkout). Описываем их в формате OpenAI:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Получить прогноз погоды в городе на дату",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Название города"},
"date": {"type": "string", "format": "date"}
},
"required": ["city", "date"]
}
}
},
{
"type": "function",
"function": {
"name": "book_hotel",
"description": "Забронировать отель",
"parameters": {
"type": "object",
"properties": {
"hotel_name": {"type": "string"},
"checkin": {"type": "string", "format": "date"},
"checkout": {"type": "string", "format": "date"}
},
"required": ["hotel_name", "checkin", "checkout"]
}
}
}
]
2 Запрос + инструменты = ответ с вызовом
Отправляем пользовательский запрос вместе со списком инструментов. Модель анализирует контекст и решает, нужно ли что-то вызвать. Если нужно — в ответе появляется поле tool_calls (в терминах OpenAI). Если нет — модель просто отвечает текстом.
response = client.chat.completions.create(
model="gpt-5", # или gpt-4o, последняя версия на июнь 2026
messages=[
{"role": "user", "content": "Какая погода в Лондоне завтра? Если дождливо, забронируй отель с крытым бассейном."}
],
tools=tools
)
3 Вызовите функцию вручную
Вытаскиваем имя и аргументы, вызываем реальный API погоды:
import json
for tool_call in response.choices[0].message.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if func_name == "get_weather":
result = get_weather_api(args["city"], args["date"])
4 Верните результат модели
Добавляем результат в историю как сообщение с ролью tool. Теперь модель может решить, вызывать ли следующий инструмент (например, бронь отеля) или ответить пользователю.
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({"temperature": 15, "condition": "rain"})
})
# Повторный запрос
response2 = client.chat.completions.create(
model="gpt-5",
messages=messages,
tools=tools
)
Шаг за шагом: пишем агента с инструментами
Соберём полноценного агента — персонального ассистента, который работает с календарём и почтой. Полный код с пояснениями — ниже. Это минимальная версия того, как создавать LLM-агентов без фреймворков.
Базовая структура
import openai
import json
client = openai.OpenAI()
def create_event(summary, start_time, end_time):
# Здесь должен быть вызов Google Calendar API
return {"status": "created", "event_id": "123"}
def send_email(to, subject, body):
# Здесь — интеграция с SMTP
return {"status": "sent"}
tools = [
{
"type": "function",
"function": {
"name": "create_event",
"description": "Создать событие в календаре",
"parameters": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"start_time": {"type": "string"},
"end_time": {"type": "string"}
},
"required": ["summary", "start_time", "end_time"]
}
}
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "Отправить email",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
"required": ["to", "subject", "body"]
}
}
}
]
messages = [{"role": "system", "content": "Ты — ассистент, который работает с календарём и почтой. Вызывай функции, когда нужно."}]
while True:
user_input = input("Вы: ")
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="gpt-5",
messages=messages,
tools=tools
)
msg = response.choices[0].message
messages.append(msg)
if msg.tool_calls:
for tc in msg.tool_calls:
func_name = tc.function.name
args = json.loads(tc.function.arguments)
if func_name == "create_event":
result = create_event(**args)
elif func_name == "send_email":
result = send_email(**args)
else:
result = {"error": "unknown function"}
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})
# Повторный запрос для генерации ответа
response = client.chat.completions.create(
model="gpt-5",
messages=messages,
tools=tools
)
msg = response.choices[0].message
messages.append(msg)
print(f"Ассистент: {msg.content}")
Важно: если не обработать параллельные tool calls (модель может запросить несколько функций сразу), вы рискуете потерять часть запросов. OpenAI поддерживает до 10 инструментов в одном ответе — обрабатывайте их в цикле.
В реальном проекте не забывайте про лимиты токенов. Контекст быстро растёт: каждое сообщение с результатом функции может быть большим. Техники сжатия — в статье Как не сжечь токены.
Грабли, на которые наступают все (даже я)
Tool calling выглядит магией, пока не упрёшься в реальность. Вот самые частые ошибки.
1. Модель вызывает не ту функцию
Причина — плохое описание. Если две функции похожи, модель путается. Решение — максимально конкретные описания. Например, не «получить информацию», а «получить текущую температуру и влажность для города». Используйте чёткие имена параметров.
2. Галлюцинации в аргументах
Модель может выдумать дату, email или ID. Валидируйте аргументы перед вызовом. Не доверяйте — проверяйте. Если дата невалидна — верните ошибку в результатах tool и дайте модели шанс исправиться.
3. Забыли передать результаты обратно
Без результата модели неоткуда узнать, выполнилась ли функция. Она будет гадать или повторять вызов. Добавляйте role: tool с content — это обязательный шаг.
4. Слишком много инструментов
Когда в списке 50 функций, модель начинает путаться и тратить токены на лишние «думы». Группируйте похожие функции, используйте суб-агентов для разных доменов. О том, как правильно декомпозировать, читайте в этом материале.
Часто задаваемые вопросы
Что такое tool calling простыми словами?
Это когда LLM не отвечает текстом, а возвращает JSON-инструкцию для вызова конкретной функции. Вы эту функцию выполняете и результат отдаёте модели. Так агент получает способность взаимодействовать с внешним миром.
Чем отличается tool calling от простого промпта?
В промпте вы просите модель написать «вызови функцию X» в ответе — но это лишь текст. Tool calling даёт структурированный, гарантированно парсимый вывод, который модель обучена генерировать. Шанс ошибки парсинга — ниже.
Можно ли сделать tool calling для open-source моделей?
Да, многие модели (Llama 3, Mistral, Qwen) поддерживают function calling, но качество ниже, чем у проприетарных. Для критичных сценариев лучше использовать GPT-5 или Claude 4 Sonnet.
Как защититься от инъекций в аргументы?
Не доверяйте аргументам, которые пришли от модели. Валидируйте типы, форматы, проверяйте границы. Ограничьте права функций — пусть модель не может вызвать удаление базы данных.
Взгляд в будущее: агенты, которые не болтают, а делают
К 2026 году tool calling стал стандартом де-факто. Все ведущие провайдеры — OpenAI, Anthropic, Google — встроили его на уровне API. Но эволюция не остановится. Модели учатся планировать последовательность вызовов заранее, а не «на ходу». Техника Dialogue Tree Search уже позволяет модели «проигрывать» ветки решений и выбирать оптимальную.
Следующий шаг — агенты, которые сами создают новые инструменты (функции) на лету. Представьте: вы говорите «Мне нужно отслеживать цену биткоина каждый час», и LLM пишет код для нового API-коллера, регистрирует его в своём инструментарии и начинает использовать. Без участия разработчика.
Tool calling — это не фича, это фундамент. Если ваш AI-агент до сих пор только болтает, вы теряете 90% его потенциала. Идите и дайте ему инструменты. Начните с малого: два-три вызова, простой цикл, и вы увидите, как «разговорчивый болван» превращается в полезного сотрудника.