Введение: почему LLM без инструментов — это только половина дела
Давайте начистоту: большие языковые модели умеют работать с текстом просто блестяще. Но вот в чём загвоздка — они только с текстом и работают. Спросите у LLM текущий курс доллара, и она выдаст вам что-то правдоподобное, но вполне возможно устаревшее. Попросите выполнить SQL-запрос — получите красивый текст запроса, но не результат его выполнения. Отправить письмо? Нет, только текст письма.
Именно здесь на сцену выходят Function Calling и Tool Use — механизмы, которые превращают LLM из (пусть и гениального) генератора текста в полноценного агента, способного взаимодействовать с реальным миром.
В этом руководстве мы разберём архитектуру Function Calling от основ до продвинутых паттернов, сравним реализации в API OpenAI и Anthropic Claude, и — что самое ценное — создадим работающие примеры агентов на Python. Если вы хотите выйти за рамки простого промпт-инжиниринга и строить по-настоящему интерактивные AI-системы, вы в правильном месте.
Архитектура Function Calling: как это устроено под капотом
Основной принцип
Первое, что нужно понять (и это важно!): LLM не выполняет функции напрямую. Модель лишь генерирует структурированный JSON, в котором говорит: «Вызови вот эту функцию с такими-то параметрами». А дальше ваше приложение берёт этот JSON и выполняет реальную работу.
Честно говоря, это гениальное архитектурное решение. Модель отвечает за интеллект — понимание намерения пользователя и выбор нужного инструмента. А вы отвечаете за безопасность и контроль выполнения.
Весь процесс укладывается в пять шагов:
- Определение инструментов — вы описываете доступные функции в формате JSON Schema: имя, описание, параметры
- Запрос к модели — сообщение пользователя отправляется вместе с описанием доступных инструментов
- Решение модели — LLM анализирует запрос и решает, какой инструмент вызвать и с какими аргументами (или не вызывать вовсе)
- Выполнение функции — ваше приложение получает структурированный вызов и выполняет реальную функцию
- Финальный ответ — результат передаётся обратно модели, которая формирует человекочитаемый ответ
Классификация инструментов
Не все инструменты одинаковы с точки зрения рисков. Я обычно делю их на три категории:
- Доступ к данным (Data Access) — чтение из баз данных, API, файловых систем. Самая безопасная категория — операции только на чтение, ничего не сломаете
- Вычисления (Computation) — математические расчёты, преобразование данных. Тоже без побочных эффектов
- Действия (Actions) — отправка сообщений, запись в базу, создание файлов. Вот тут нужно быть осторожнее всего
Для действий настоятельно рекомендуется паттерн «прочитай перед записью» — всегда проверяйте текущее состояние системы перед тем, как что-то менять. Поверьте, это избавит вас от многих неприятных сюрпризов.
Реализация в API OpenAI: практическое руководство
Определение функций
OpenAI использует параметр tools в Chat Completions API. Каждый инструмент описывается JSON-схемой. Начнём с классического примера — погодного агента:
from openai import OpenAI
import json
client = OpenAI()
# Определяем инструменты
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Получить текущую погоду для указанного города. Используйте эту функцию, когда пользователь спрашивает о погоде.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Название города, например: Москва, Санкт-Петербург"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Единицы измерения температуры"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "search_database",
"description": "Поиск информации о продуктах в базе данных по ключевым словам.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Поисковый запрос"
},
"category": {
"type": "string",
"description": "Категория для фильтрации результатов"
},
"limit": {
"type": "integer",
"description": "Максимальное количество результатов",
"default": 10
}
},
"required": ["query"]
}
}
}
]
Агентный цикл обработки
А вот это, пожалуй, самая важная часть. Агентный цикл (agentic loop) — это паттерн, при котором модель может совершать несколько вызовов инструментов подряд, пока не соберёт достаточно информации для финального ответа. По сути, это то, что делает агента агентом:
def run_agent(user_message: str, tools: list) -> str:
messages = [
{"role": "system", "content": "Вы — полезный ассистент с доступом к инструментам."},
{"role": "user", "content": user_message}
]
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
# Если модель решила не вызывать инструменты — возвращаем ответ
if not message.tool_calls:
return message.content
# Добавляем ответ модели в историю
messages.append(message)
# Обрабатываем каждый вызов инструмента
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Выполняем функцию
result = execute_function(function_name, arguments)
# Добавляем результат в историю
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
def execute_function(name: str, args: dict) -> dict:
"""Маршрутизатор вызовов функций."""
functions = {
"get_weather": get_weather_data,
"search_database": search_products,
}
if name in functions:
return functions[name](**args)
return {"error": f"Неизвестная функция: {name}"}
Строгий режим (Strict Mode)
Для production-систем стоит обратить внимание на строгий режим Function Calling в OpenAI. Он гарантирует, что модель генерирует аргументы, точно соответствующие вашей JSON-схеме. Никаких сюрпризов в виде неожиданных полей или пропущенных обязательных параметров:
tools = [
{
"type": "function",
"function": {
"name": "create_order",
"strict": True, # Включаем строгий режим
"parameters": {
"type": "object",
"properties": {
"product_id": {"type": "string"},
"quantity": {"type": "integer"},
"shipping_address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"postal_code": {"type": "string"}
},
"required": ["street", "city", "postal_code"],
"additionalProperties": False
}
},
"required": ["product_id", "quantity", "shipping_address"],
"additionalProperties": False
}
}
}
]
Реализация в API Anthropic Claude: Tool Use
Особенности подхода Claude
Anthropic пошли немного другим путём и используют термин «Tool Use». Концептуально всё похоже на OpenAI, но есть свои нюансы — в частности, структуры tool_use и tool_result в Messages API. Давайте посмотрим на примере финансового агента:
import anthropic
import json
client = anthropic.Anthropic()
# Определяем инструменты для Claude
tools = [
{
"name": "get_stock_price",
"description": "Получает текущую цену акций по тикеру. Используйте, когда пользователь спрашивает о стоимости акций компании.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Биржевой тикер, например: AAPL, GOOGL, MSFT"
},
"exchange": {
"type": "string",
"description": "Биржа: NYSE, NASDAQ, MOEX",
"enum": ["NYSE", "NASDAQ", "MOEX"]
}
},
"required": ["ticker"]
}
},
{
"name": "calculate_portfolio_return",
"description": "Рассчитывает доходность портфеля за указанный период.",
"input_schema": {
"type": "object",
"properties": {
"holdings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"ticker": {"type": "string"},
"shares": {"type": "number"},
"purchase_price": {"type": "number"}
},
"required": ["ticker", "shares", "purchase_price"]
},
"description": "Список позиций в портфеле"
},
"period_days": {
"type": "integer",
"description": "Период в днях для расчёта доходности"
}
},
"required": ["holdings", "period_days"]
}
}
]
Агентный цикл для Claude
Агентный цикл для Claude выглядит чуть иначе. Главное отличие — Claude использует блоки контента (content blocks) разных типов, и нужно правильно обрабатывать каждый из них:
def run_claude_agent(user_message: str, tools: list) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=4096,
system="Вы — финансовый аналитик с доступом к биржевым данным.",
tools=tools,
messages=messages
)
# Проверяем, нужно ли выполнять инструменты
if response.stop_reason == "end_turn":
# Извлекаем текстовый ответ
for block in response.content:
if block.type == "text":
return block.text
# Добавляем ответ ассистента в историю
messages.append({"role": "assistant", "content": response.content})
# Обрабатываем вызовы инструментов
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
# Передаём результаты обратно модели
messages.append({"role": "user", "content": tool_results})
def execute_tool(name: str, input_data: dict) -> dict:
"""Выполняет инструмент и возвращает результат."""
if name == "get_stock_price":
return fetch_stock_price(**input_data)
elif name == "calculate_portfolio_return":
return calc_return(**input_data)
return {"error": f"Неизвестный инструмент: {name}"}
Structured Outputs в Claude
С 2025 года Anthropic поддерживает строгие структурированные выходы. Это особенно полезно в агентных workflow, где нужна и надёжность вызовов инструментов, и предсказуемость формата ответов. Комбинация строгого Tool Use с Structured Outputs — это, по моему опыту, одна из самых недооценённых возможностей:
# Строгий Tool Use: валидация параметров вызова инструментов
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages,
headers={
"anthropic-beta": "advanced-tool-use-2025-11-20"
}
)
Паттерны проектирования агентов с инструментами
Итак, мы разобрались с базовой механикой. Теперь поговорим о том, как правильно организовать взаимодействие агента с инструментами. За последний год сложилось несколько проверенных паттернов.
1. Паттерн «Маршрутизатор» (Router Pattern)
Один из самых популярных — и, пожалуй, самый интуитивно понятный. LLM работает как диспетчер: получает запрос и направляет его к нужному инструменту. Отлично подходит для систем поддержки клиентов и мультифункциональных ассистентов:
router_tools = [
{
"name": "handle_billing",
"description": "Обработка вопросов по биллингу, оплате, счетам и подпискам",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"issue_type": {
"type": "string",
"enum": ["payment_failed", "refund_request", "plan_change", "invoice_question"]
},
"details": {"type": "string"}
},
"required": ["customer_id", "issue_type"]
}
},
{
"name": "handle_technical",
"description": "Обработка технических вопросов, ошибок и проблем с продуктом",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"error_code": {"type": "string"},
"description": {"type": "string"}
},
"required": ["customer_id", "description"]
}
},
{
"name": "escalate_to_human",
"description": "Передать запрос живому оператору, если проблема слишком сложна или клиент явно просит об этом",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {"type": "string"},
"reason": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
}
},
"required": ["customer_id", "reason", "priority"]
}
}
]
2. Паттерн «Цепочка инструментов» (Tool Chaining)
Этот паттерн — настоящая рабочая лошадка для сложных задач. Модель последовательно вызывает несколько инструментов, где результат одного становится входом для следующего. Представьте себе конвейер: запросить данные → посчитать статистику → построить график → написать отчёт:
# Пример: агент для анализа данных
# Шаг 1: Модель вызывает query_database для получения данных
# Шаг 2: Используя полученные данные, вызывает calculate_statistics
# Шаг 3: На основе статистики вызывает generate_chart
# Шаг 4: Формирует итоговый отчёт с текстом и визуализациями
analysis_tools = [
{
"name": "query_database",
"description": "Выполняет SQL-запрос к аналитической базе данных и возвращает результаты.",
"input_schema": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SQL-запрос для выполнения"},
"database": {"type": "string", "enum": ["analytics", "warehouse", "production_readonly"]}
},
"required": ["sql", "database"]
}
},
{
"name": "calculate_statistics",
"description": "Выполняет статистические расчёты над числовым набором данных.",
"input_schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {"type": "number"},
"description": "Массив числовых значений для анализа"
},
"operations": {
"type": "array",
"items": {
"type": "string",
"enum": ["mean", "median", "std_dev", "percentile_95", "trend"]
}
}
},
"required": ["data", "operations"]
}
},
{
"name": "generate_chart",
"description": "Создаёт визуализацию данных в указанном формате.",
"input_schema": {
"type": "object",
"properties": {
"chart_type": {"type": "string", "enum": ["line", "bar", "pie", "scatter"]},
"title": {"type": "string"},
"data_points": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {"type": "string"},
"value": {"type": "number"}
}
}
}
},
"required": ["chart_type", "data_points"]
}
}
]
3. Паттерн «Параллельные вызовы» (Parallel Tool Calls)
Зачем ждать, если можно делать несколько дел одновременно? Современные модели умеют запрашивать выполнение нескольких инструментов параллельно. Типичный пример: пользователь спрашивает «Сравни погоду в Москве и Лондоне» — и модель отправляет два запроса сразу.
# Модель может вернуть несколько tool_calls одновременно
# Например, при запросе "Сравни погоду в Москве и Лондоне":
# tool_call_1: get_weather(city="Москва")
# tool_call_2: get_weather(city="Лондон")
# Оба вызова выполняются параллельно
import asyncio
async def execute_parallel_tools(tool_calls: list) -> list:
"""Параллельное выполнение независимых инструментов."""
tasks = []
for call in tool_calls:
task = asyncio.create_task(
async_execute_function(call.function.name,
json.loads(call.function.arguments))
)
tasks.append((call.id, task))
results = []
for call_id, task in tasks:
result = await task
results.append({
"role": "tool",
"tool_call_id": call_id,
"content": json.dumps(result, ensure_ascii=False)
})
return results
4. Паттерн «Самоконтроль» (Self-Validation)
В более продвинутых системах модель не просто выполняет действия — она ещё и проверяет результаты. Если что-то пошло не так, агент может повторить операцию или откатить изменения. Это особенно важно для критических систем, где цена ошибки высока:
validation_tools = [
{
"name": "execute_action",
"description": "Выполняет действие в системе.",
"input_schema": {
"type": "object",
"properties": {
"action": {"type": "string"},
"parameters": {"type": "object"}
},
"required": ["action"]
}
},
{
"name": "verify_result",
"description": "Проверяет результат ранее выполненного действия, чтобы убедиться в корректности.",
"input_schema": {
"type": "object",
"properties": {
"action_id": {"type": "string"},
"expected_state": {"type": "object"}
},
"required": ["action_id", "expected_state"]
}
},
{
"name": "rollback_action",
"description": "Откатывает ранее выполненное действие в случае обнаружения ошибки.",
"input_schema": {
"type": "object",
"properties": {
"action_id": {"type": "string"},
"reason": {"type": "string"}
},
"required": ["action_id", "reason"]
}
}
]
Лучшие практики описания инструментов
Качество описаний решает всё
Вот что многие упускают: описания инструментов — это, по сути, промпт-инжиниринг. Чем точнее и понятнее вы опишете свои функции, тем лучше модель будет их использовать. Это не преувеличение — плохие описания буквально ломают поведение агента.
Ключевые рекомендации:
- Будьте конкретны — описание должно точно объяснять, что делает функция, когда её использовать и каких результатов ожидать
- Указывайте ограничения — если функция работает только с определёнными форматами или имеет лимиты, напишите об этом
- Приводите примеры — для строковых параметров укажите примеры допустимых значений
- Разделяйте обязательные и необязательные параметры — используйте поле
required - Помните о токенах — описания инструментов включаются в каждый запрос к API. Будьте лаконичны, но информативны
Антипаттерны, которых стоит избегать
На практике встречается несколько типичных ошибок:
- Слишком общие описания — «Выполняет действие» не даёт модели ни одной зацепки. Она просто не поймёт, когда использовать этот инструмент
- Перегруженные функции — одна функция, которая делает слишком много, сбивает модель с толку. Лучше несколько специализированных инструментов
- Параметры без описаний — каждый параметр должен иметь чёткое описание, иначе готовьтесь к некорректным значениям
- Конфликтующие инструменты — два инструмента с похожими описаниями — верный путь к непредсказуемому поведению
Обработка ошибок и безопасность
Многоуровневая обработка ошибок
В production-среде без надёжной обработки ошибок далеко не уедешь. Вот пример, который покрывает основные сценарии — от валидации входных данных до санитизации выходных:
import logging
from typing import Any
logger = logging.getLogger(__name__)
class ToolExecutionError(Exception):
"""Ошибка выполнения инструмента."""
def __init__(self, tool_name: str, message: str, retryable: bool = False):
self.tool_name = tool_name
self.retryable = retryable
super().__init__(message)
def safe_execute_tool(name: str, args: dict, max_retries: int = 2) -> dict:
"""Безопасное выполнение инструмента с обработкой ошибок."""
# 1. Валидация входных данных
if not validate_input(name, args):
return {
"error": "Некорректные входные параметры",
"details": f"Параметры для '{name}' не прошли валидацию"
}
# 2. Проверка прав доступа
if not check_permissions(name, args):
return {
"error": "Недостаточно прав",
"details": f"Отказано в доступе к инструменту '{name}'"
}
# 3. Выполнение с повторными попытками
for attempt in range(max_retries + 1):
try:
result = execute_function(name, args)
# 4. Санитизация выходных данных
return sanitize_output(result)
except ToolExecutionError as e:
logger.warning(f"Ошибка инструмента {name} (попытка {attempt + 1}): {e}")
if not e.retryable or attempt == max_retries:
return {
"error": str(e),
"tool": name,
"suggestion": "Попробуйте изменить параметры запроса"
}
except Exception as e:
logger.error(f"Непредвиденная ошибка {name}: {e}")
return {
"error": "Внутренняя ошибка сервиса",
"tool": name
}
def sanitize_output(result: Any) -> dict:
"""Удаляет чувствительные данные из результатов."""
if isinstance(result, dict):
sensitive_keys = {"password", "token", "secret", "api_key", "credit_card"}
return {
k: "***СКРЫТО***" if k.lower() in sensitive_keys else v
for k, v in result.items()
}
return {"result": result}
Безопасность вызовов инструментов
Безопасность — это не та вещь, которую можно прикрутить потом. Она должна быть заложена с самого начала. Основные принципы:
- Принцип наименьших привилегий — давайте агенту только те инструменты, которые нужны для конкретной задачи. Не больше
- Валидация входных данных — всегда проверяйте параметры до выполнения. Модель может галлюцинировать, и это нормально — ваша задача это перехватить
- Подтверждение опасных действий — удаление данных, финансовые операции, отправка сообщений — всё это должно требовать подтверждения пользователя
- Аудит и логирование — записывайте каждый вызов инструмента. Это пригодится и для отладки, и для расследований
- Rate limiting — ограничивайте частоту вызовов, чтобы агент не разорил ваш бюджет за один разговор
Продвинутые техники: Interleaved Thinking и динамическое управление
Interleaved Thinking
Одна из самых интересных возможностей современных моделей — «перемежающееся мышление». Суть в том, что модель может рассуждать между вызовами инструментов, а не просто механически выполнять один за другим. Это позволяет:
- Анализировать промежуточные результаты перед следующим вызовом
- Менять стратегию на лету, если данные оказались не такими, как ожидалось
- Принимать более обоснованные решения о выборе следующего инструмента
- Объяснять пользователю свою логику — делать процесс прозрачным
На практике это выглядит так: модель получает результат первого вызова, «думает» над ним (генерирует внутренний анализ), а затем решает — вызвать другой инструмент или уже можно давать финальный ответ.
Динамическое управление набором инструментов
В реальных системах вы вряд ли захотите давать всем пользователям доступ ко всем инструментам сразу. Набор может меняться в зависимости от роли пользователя, этапа диалога или контекста задачи:
class DynamicToolManager:
"""Управляет набором доступных инструментов на основе контекста."""
def __init__(self):
self.all_tools = {}
self.tool_categories = {}
def register_tool(self, tool_def: dict, category: str,
required_role: str = "user"):
"""Регистрирует инструмент с метаданными."""
name = tool_def["name"]
self.all_tools[name] = {
"definition": tool_def,
"category": category,
"required_role": required_role,
"call_count": 0
}
self.tool_categories.setdefault(category, []).append(name)
def get_tools_for_context(self, user_role: str,
conversation_stage: str,
active_categories: list = None) -> list:
"""Возвращает инструменты, доступные в текущем контексте."""
available = []
for name, meta in self.all_tools.items():
# Проверяем роль
if not self._has_role(user_role, meta["required_role"]):
continue
# Проверяем категорию
if active_categories and meta["category"] not in active_categories:
continue
available.append(meta["definition"])
return available
def _has_role(self, user_role: str, required_role: str) -> bool:
role_hierarchy = {"admin": 3, "operator": 2, "user": 1, "guest": 0}
return role_hierarchy.get(user_role, 0) >= role_hierarchy.get(required_role, 0)
# Использование
manager = DynamicToolManager()
manager.register_tool(billing_tool, category="billing", required_role="operator")
manager.register_tool(admin_tool, category="admin", required_role="admin")
manager.register_tool(search_tool, category="general", required_role="user")
# Получаем инструменты для текущего контекста
context_tools = manager.get_tools_for_context(
user_role="operator",
conversation_stage="resolution",
active_categories=["billing", "general"]
)
Сравнение подходов: OpenAI vs Claude vs Open Source
Какую платформу выбрать? Зависит от ваших приоритетов. Вот краткое сравнение основных игроков.
OpenAI (GPT-4o, o3, o4-mini)
- Формат: параметр
toolsв Chat Completions API - Строгий режим: полная поддержка через
strict: true - Параллельные вызовы: нативная поддержка
- Фишка: модели o-серии используют инструменты прямо внутри цепочки рассуждений, что заметно улучшает качество выбора
- Structured Outputs: гарантированная валидность JSON
Anthropic Claude (Sonnet 4.5, Opus 4.6)
- Формат: параметр
toolsв Messages API сinput_schema - Строгий режим: через бета-заголовок
advanced-tool-use - Параллельные вызовы: поддерживаются
- Фишка: Extended Thinking позволяет модели «думать» между вызовами; есть Agent SDK для построения полноценных агентов
- Бонус: встроенный веб-поиск и Computer Use
Open Source (LLaMA, Mistral, Qwen)
- Формат: зависит от реализации, часто через специальные токены
- Строгий режим: реализуется через библиотеки вроде Outlines или Instructor
- Параллельные вызовы: поддержка пока ограничена
- Главное преимущество: полный контроль, своя инфраструктура, никаких затрат на API
- Инструменты развёртывания: vLLM, Ollama, TGI
Практический пример: полнофункциональный агент-ассистент
Ну что ж, давайте соберём всё воедино. Вот полный рабочий пример — агент для управления задачами, который умеет создавать, обновлять и фильтровать задачи. Можете взять его за основу для своего проекта:
import anthropic
import json
from datetime import datetime, timezone
client = anthropic.Anthropic()
# Имитация базы данных задач
task_db = {}
def create_task(title: str, description: str, priority: str,
assignee: str = None) -> dict:
task_id = f"TASK-{len(task_db) + 1:04d}"
task = {
"id": task_id,
"title": title,
"description": description,
"priority": priority,
"assignee": assignee,
"status": "open",
"created_at": datetime.now(timezone.utc).isoformat()
}
task_db[task_id] = task
return {"success": True, "task": task}
def list_tasks(status: str = None, assignee: str = None) -> dict:
tasks = list(task_db.values())
if status:
tasks = [t for t in tasks if t["status"] == status]
if assignee:
tasks = [t for t in tasks if t["assignee"] == assignee]
return {"tasks": tasks, "total": len(tasks)}
def update_task(task_id: str, status: str = None,
priority: str = None) -> dict:
if task_id not in task_db:
return {"error": f"Задача {task_id} не найдена"}
if status:
task_db[task_id]["status"] = status
if priority:
task_db[task_id]["priority"] = priority
return {"success": True, "task": task_db[task_id]}
# Определение инструментов
task_tools = [
{
"name": "create_task",
"description": "Создаёт новую задачу в трекере. Используйте, когда пользователь хочет добавить новую задачу, баг или фичу.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Краткое название задачи"},
"description": {"type": "string", "description": "Подробное описание"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "Приоритет задачи"
},
"assignee": {"type": "string", "description": "Ответственный исполнитель"}
},
"required": ["title", "description", "priority"]
}
},
{
"name": "list_tasks",
"description": "Показывает список задач с возможностью фильтрации по статусу и исполнителю.",
"input_schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["open", "in_progress", "review", "done"],
"description": "Фильтр по статусу"
},
"assignee": {"type": "string", "description": "Фильтр по исполнителю"}
}
}
},
{
"name": "update_task",
"description": "Обновляет статус или приоритет существующей задачи.",
"input_schema": {
"type": "object",
"properties": {
"task_id": {"type": "string", "description": "Идентификатор задачи, например: TASK-0001"},
"status": {
"type": "string",
"enum": ["open", "in_progress", "review", "done"]
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"]
}
},
"required": ["task_id"]
}
}
]
# Запуск агента
def main():
print("Агент-менеджер задач запущен. Введите ваш запрос:")
while True:
user_input = input("\nВы: ")
if user_input.lower() in ["выход", "exit", "quit"]:
break
response = run_claude_agent(user_input, task_tools)
print(f"\nАгент: {response}")
if __name__ == "__main__":
main()
Мониторинг и оптимизация производительности
Метрики, за которыми стоит следить
Запустить агента в продакшен — это полдела. Дальше нужно понимать, как он работает. Вот метрики, которые я рекомендую мониторить:
- Точность выбора инструментов — как часто модель выбирает правильный инструмент
- Точность параметров — насколько корректны передаваемые аргументы
- Количество итераций — сколько циклов tool_call → result нужно для выполнения задачи (чем меньше — тем лучше)
- Latency — общее время ответа с учётом вызовов инструментов
- Стоимость токенов — сколько токенов уходит на описания инструментов и результаты
- Частота ошибок — доля неудачных вызовов и, что важнее, их причины
Оптимизация расхода токенов
Описания инструментов включаются в каждый запрос — и это может быстро стать дорогим удовольствием. Несколько стратегий для оптимизации:
- Контекстная загрузка — подключайте только те инструменты, которые нужны прямо сейчас, а не весь набор
- Компактные описания — лаконичные, но точные. Каждое лишнее слово стоит денег
- Кэширование контекста — используйте Prompt Caching (есть и у OpenAI, и у Anthropic) для снижения стоимости повторных вызовов
- Группировка инструментов — если есть лимит на количество инструментов, объединяйте связанные функции
Тренды Function Calling в 2026 году
Экосистема развивается очень быстро. Вот что определяет ландшафт прямо сейчас.
Model Context Protocol (MCP)
Anthropic выпустили открытый стандарт MCP, который унифицирует подключение инструментов к любым LLM. Идея простая, но мощная: вы создаёте сервер инструментов один раз, и он работает с любой моделью, поддерживающей протокол. Никакой привязки к конкретному провайдеру.
Нативное мышление с инструментами
Модели нового поколения (o3, o4-mini, Claude с Extended Thinking) обучены рассуждать об инструментах внутри цепочки мышления. На практике это значит заметно более точный выбор инструментов и параметров, особенно в сложных многошаговых сценариях.
Агентные фреймворки
В 2026 году фреймворки для построения агентов наконец вышли из стадии «интересный эксперимент» в «рабочий инструмент»: LangChain и LangGraph для оркестрации, CrewAI для мультиагентных систем, Anthropic Agent SDK для экосистемы Claude. Они берут на себя всю рутину управления циклом инструментов, а вы фокусируетесь на бизнес-логике.
Безопасность и контроль
Чем автономнее агенты, тем важнее контроль. Индустрия активно внедряет Human-in-the-Loop для опасных действий, гранулярное управление правами, аудит всех операций. Принцип «минимальных привилегий» становится стандартом для AI-агентов — и это правильно.
Заключение
Function Calling и Tool Use — это то, что превращает языковую модель из умного собеседника в полноценного агента, который может делать реальные вещи. Получать данные, считать, отправлять, создавать.
Если вынести из этого руководства главное:
- LLM не выполняет функции сама — она генерирует структурированные вызовы, а ваш код их обрабатывает
- Качество описаний инструментов критически влияет на поведение агента
- Агентный цикл — это основа для многошаговых задач
- Безопасность нужно закладывать с первого дня: валидация, контроль прав, аудит
- Паттерны маршрутизации, цепочек и параллельных вызовов покрывают большинство реальных сценариев
- Оптимизация токенов на описания инструментов — это прямая экономия бюджета
Мой совет: начните с простого агента с 2-3 инструментами. Отладьте агентный цикл и обработку ошибок. А дальше масштабируйте, добавляя паттерны и возможности по мере того, как растут требования. Не пытайтесь построить идеальную систему сразу — лучше итерируйте.