Вызов внешних инструментов

В предыдущих уроках, вы создали простого фитнес-ассистента, который способен отвечать на вопросы и поддерживать диалог. Но ценность ассистента возрастает, когда он может не только разговаривать, но и выполнять действия. Например, записывать тренировки, рассчитывать калории или формировать персонализированные рекомендации на основе данных.Представьте, что фитнес-ассистент теперь не просто рассказывает про технику приседаний, но и записывает количество ваших подходов и повторений. А затем использует эти данные для анализа прогресса. Звучит круто! Именно для таких возможностей нужен механизм Function Calling. Сейчас всё расскажем!

Что такое Function Calling и зачем он нужен

Современные языковые модели знают многое, но они не могут выполнять конкретные действия: знать текущую погоду или сохранять информацию в базу данных. Function Calling решает эту проблему — он позволяет модели делегировать такие задачи специальным инструментам.💡 Function Calling (или вызов функций) — это способность языковой модели определять, когда и какую функцию следует вызвать в ответ на запрос пользователя, а также формировать корректные аргументы для этой функции.

Как работает Function Calling

Рассмотрите процесс работы Function Calling на простом примере с фитнес-ассистентом:1️⃣ Описание инструментов .

Вы задаёте доступные функции. Например, запись упражнения с параметрами — название, количество подходов и повторений.

2️⃣ Запрос пользователя. Пользователь формулирует задачу: «Запиши, что я сегодня сделал 3 подхода по 12 приседаний».

3️⃣ Формирование вызова функции. Модель анализирует запрос и возвращает структуру вызова:{"exercise_name": "приседания", "sets": 3, "reps": 12}.

4️⃣ Выполнение функции .Приложение получает результат, выполняет функцию и возвращает статус: {"status": "success", "message": "Упражнение записано в дневник тренировок"}.

5️⃣ Ответ пользователю. Модель формирует ответ на основе результата выполнения функции: «Отлично! Я записал твои приседания: 3 подхода по 12 повторений. Хорошая работа! Хочешь добавить ещё какие-то упражнения в сегодняшнюю тренировку?».Такой подход позволяет не только генерировать ответы, но и выполнять реальные действия с данными, например сохранять тренировки для последующего анализа. Взаимодействие пользователя, модели и приложения:

❗️ Модель не взаимодействует с внешними функциями напрямую и не вызывает их сама. Она структурирует свои намерения в специальном формате, а дальше обработка лежит на стороне разработчика.

Когда использовать Function Calling

Function Calling пригодится, когда нужно:

  • Получить актуальную информацию: курсы валют, расписание, прогноз погоды.
  • Выполнить точные вычисления, где нужен результат именно расчётов (а не приближение модели).
  • Автоматизировать рабочие процессы через CRM/ERP-системы.
  • Вести трекинг задач, назначать поручения.
  • Управлять удалёнными устройствами, сервисами (например, запускать бэкапы).
  • Генерировать и визуализировать структурированные данные (таблицы, отчёты, графики).

Преимущества Function Calling

Сравнение работы с AI с помощью Function Calling и без:

Дополнительные преимущества:

  • Контроль безопасности: вы решаете, какие инструменты доступны и как обрабатывать запросы.
  • Структурированная обработка данных: получение данных в предсказуемом формате без необходимости сложного парсинга текста.
  • Масштабируемость: лёгкое добавление новых возможностей через подключение дополнительных функций.

Как реализовать Function Calling для фитнес-ассистента

Теперь, когда вы понимаете принцип работы Function Calling, предлагаем расширить возможности фитнес-ассистента из предыдущего урока. Добавить ему способность записывать выполненные упражнения. Это позволит создать простую, но полезную систему учёта тренировок.

1️⃣ Определение функции для записи упражнений

Фитнес-ассистент должен сохранять информацию о том, что пользователь уже сделал в ходе тренировок. Например, когда пользователь говорит: Сегодня я сделал 3 подхода по 12 приседаний с весом 70 кг, ассистент должен не только похвалить за выполненную работу, но и сохранить эти данные для дальнейшего анализа прогресса.Сначала нужно определить функцию, которая будет записывать информацию о выполненных упражнениях. Нужно сохранять запись о том, какое упражнение было выполнено, с каким весом и количеством повторений:

import uuid

from datetime import datetime


# Простое хранилище данных в памяти
exercises_db = {}


def log_exercise(exercise_name, sets, reps, weight=None, date=None):
    """
    Записывает информацию о выполненном упражнении в журнал тренировок
    
    Args:
        exercise_name (str): Название упражнения
        sets (int): Количество подходов
        reps (int): Количество повторений в каждом подходе
        weight (float, optional): Вес в кг
        date (str, optional): Дата тренировки в формате YYYY-MM-DD
    
    Returns:
        dict: Информация о записи с уникальным ID
    """
    # Генерируем уникальный ID для записи
    record_id = str(uuid.uuid4())
    
    # Устанавливаем текущую дату, если не указана
    if date is None:
        date = datetime.now().strftime('%Y-%m-%d')
    
    # Создаём запись
    record = {
        "id": record_id,
        "exercise": exercise_name,
        "sets": sets,
        "reps": reps,
        "weight": weight,
        "date": date
    }
    
    # В реальном приложении здесь был бы код для сохранения в базу данных
    # Для примера просто сохраняем в памяти
    if 'exercise_log' not in exercises_db:
        exercises_db['exercise_log'] = []
    
    exercises_db['exercise_log'].append(record)
    
    return {
        "status": "success",
        "message": f"Упражнение '{exercise_name}' успешно записано",
        "record_id": record_id
    } 

2️⃣ Описание функции для языковой модели в Responses API

После определения функции нужно создать её описание для языковой модели. Такая информация позволит модели понять, когда и как вызывать эту функцию. В Responses API это делается с помощью специальной структуры данных:

# Описание функции для Responses API

log_exercise_tool = {
    "type": "function",
    "name": "log_exercise",
    "description": "Записывает информацию о выполненном упражнении в журнал тренировок",
    "parameters": {
        "type": "object",
        "properties": {
            "exercise_name": {
                "type": "string",
                "description": "Название упражнения"
            },
            "sets": {
                "type": "integer",
                "description": "Количество подходов"
            },
            "reps": {
                "type": "integer",
                "description": "Количество повторений в каждом подходе"
            },
            "weight": {
                "type": "number",
                "description": "Вес в кг (если применимо)"
            },
            "date": {
                "type": "string",
                "description": "Дата тренировки в формате YYYY-MM-DD (если не указана, используется сегодняшняя дата)"
            }
        },
        "required": ["exercise_name", "sets", "reps"]
    }
} 

Эта структура использует формат JSON Schema для определения параметров функции. Обратите внимание на ключевые элементы:

  • type: "function" — указывает, что это описание функции;
  • name — имя функции, которое будет использоваться при вызове;
  • description — помогает модели понять назначение функции и когда её вызывать;
  • parameters — схема параметров, которая включает:
    • properties — описание каждого параметра с его типом и назначением;
    • required — список обязательных параметров.

Качественное описание функции критически важно — оно напрямую влияет на то, насколько точно модель будет определять необходимость её вызова и корректно передавать параметры.

3️⃣ Вызов модели с инструментом

Теперь описание функции можно передать языковой модели при вызове. Это объяснит модели, что ей доступен дополнительный инструмент для записи упражнений:

# Вызов модели с доступным инструментом
response = client.responses.create(
    model=model,
    instructions="Ты — профессиональный фитнес-ассистент. Помогаешь пользователю вести дневник тренировок.",
    input="Я сегодня сделал 3 подхода по 12 приседаний с весом 70 кг",
    tools=[log_exercise_tool]  # Передача инструмента
) 

Что передавать модели:

  • Описание инструмента log_exercise_tool в параметре tools.
  • Системную инструкцию, которая объясняет роль ассистента.
  • Сообщение пользователя, содержащее информацию об упражнении.

Модель проанализирует входные данные. Если пользователь сообщает о выполненном упражнении, она вызовет функцию log_exercise с параметрами, извлечёнными из сообщения.

4️⃣ Обработка ответа модели

Когда модель решает, что нужно вызвать функцию, она возвращает специальную структуру в ответе. Задача — найти этот вызов функции и выполнить его, а затем отправить результат обратно модели:

# Проверка, есть ли вызов функции в ответе
for output_item in response.output:
    if output_item.type == "function_call":
        # Извлечение имени функции и аргументы
        function_name = output_item.name
        arguments_str = output_item.arguments  # Это строка в формате JSON
        
        print(f"Модель запросила вызов функции: {function_name}")
        print(f"С аргументами: {arguments_str}")
        
        # Парсинг аргументов из JSON-строки в словарь Python
        arguments = json.loads(arguments_str)
        
        # Вызов функции с аргументами
        if function_name == "log_exercise":
            result = log_exercise(**arguments)
            print(f"Функция выполнена, результат: {result}")
            
            # Формирование текстового сообщения с результатом
            result_message = f"Результат выполнения функции {function_name}: {json.dumps(result, ensure_ascii=False)}"
            
            # Отправка результата обратно модели
            follow_up = client.responses.create(
                model=model,
                store=True,
                previous_response_id=response.id,
                input=result_message
            )
            
            # Вывод финальный ответ модели пользователю
            print(follow_up.output_text) 

После выполнения этого кода модель получает результаты работы функции и формирует финальный ответ для пользователя с учётом успешности операции. Например, пользователь может получить сообщение: Отлично! Я записал твои приседания: 3 подхода по 12 повторений с весом 70 кг. Продолжай в том же духе!❗ В примере передаётся previous_response_id=response.id при отправке результатов выполнения функции, чтобы модель сохраняла контекст диалога и могла сформировать последовательный ответ.📚 Весь приведённый выше код можно посмотреть в репозитории GitHub.

💻 Практика. Расширение фитнес-ассистента

❗️Практическая часть курса предполагает использование облачных ресурсов, которые вы оплачиваете самостоятельно. Курс разработан так, чтобы расходы были минимальными, — часть из них покроет стартовый грант.Чтобы избежать лишних трат:— останавливайте ВМ в перерывах между практиками (это не снизит потребление до нуля, но заметно сократит расходы);— настройте бюджет и уведомления в разделе Биллинг;— удаляйте ресурсы после завершения курса;— необязательно выполнять все практики — сосредоточьтесь на релевантных.Вы научились создавать и использовать отдельную функцию, но можно ещё расширить возможности фитнес-ассистента! Добавить ему несколько полезных инструментов: журнал тренировок, просмотр истории упражнений и расчёт сожжённых калорий. Это превратит простого текстового помощника в полноценный инструмент для отслеживания тренировок.1️⃣ Создайте набор функций для расширения возможностей ассистента. Для этого определите простое хранилище данных и три основных функции: запись упражнения, получение истории тренировок и расчёт сожжённых калорий:

# Простое хранилище данных в памяти
exercises_db = {
    "users": {},
    "exercise_log": []
}


# Функции для работы с данными
def log_exercise(exercise_name, sets, reps, weight=None, date=None, user_id="default"):
    """Записывает упражнение в журнал тренировок"""
    # Код функции вы уже видели выше
    # ...


def get_exercise_history(user_id="default", days=7):
    """
    Получает историю тренировок пользователя за указанное количество дней
    
    Args:
        user_id (str): Идентификатор пользователя
        days (int): Количество дней для истории
        
    Returns:
        list: Записи о тренировках
    """
    # Определите дату начала периода
    start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
    
    # Отфильтруйте записи по пользователю и дате
    history = [
        record for record in exercises_db['exercise_log'] 
        if record.get('user_id') == user_id and record.get('date') >= start_date
    ]
    
    return {
        "status": "success",
        "history": history
    }


def calculate_calories(exercise_name, duration_minutes, intensity="moderate", weight_kg=70):
    """
    Рассчитывает примерное количество сожжённых калорий
    
    Args:
        exercise_name (str): Название упражнения
        duration_minutes (int): Продолжительность в минутах
        intensity (str): Интенсивность (low, moderate, high)
        weight_kg (float): Вес пользователя в кг
        
    Returns:
        dict: Информация о сожжённых калориях
    """
    # Приблизительные значения MET (метаболический эквивалент задачи)
    # для различных упражнений и интенсивностей
    met_values = {
        "бег": {"low": 7, "moderate": 9, "high": 12},
        "ходьба": {"low": 3, "moderate": 4, "high": 5},
        "плавание": {"low": 5, "moderate": 7, "high": 10},
        "велосипед": {"low": 4, "moderate": 6, "high": 8},
        "приседания": {"low": 3, "moderate": 5, "high": 7},
        "отжимания": {"low": 3, "moderate": 5, "high": 8},
        # Для неизвестных упражнений
        "default": {"low": 3, "moderate": 5, "high": 7}
    }
    
    # Вы получите MET для указанного упражнения и интенсивности
    exercise_name_lower = exercise_name.lower()
    exercise_met = met_values.get(exercise_name_lower, met_values["default"])
    met = exercise_met.get(intensity, exercise_met["moderate"])
    
    # Формула для расчёта калорий: MET × вес (кг) × время (часы)
    calories = met * weight_kg * (duration_minutes / 60)
    
    return {
        "status": "success",
        "exercise": exercise_name,
        "duration_minutes": duration_minutes,
        "intensity": intensity,
        "calories_burned": round(calories, 1),
        "met_used": met
    } 

2️⃣ Теперь опишите эти функции для языковой модели, чтобы она могла их вызывать. В поле description опишите каждую функцию, а внутри структуры parameters — полностью все параметры:

tools = [
    {
        "type": "function",
        "name": "log_exercise",
        "description": "Записывает информацию о выполненном упражнении в журнал тренировок",
        "parameters": {
            "type": "object",
            "properties": {
                "exercise_name": {
                    "type": "string",
                    "description": "Название упражнения"
                },
                "sets": {
                    "type": "integer",
                    "description": "Количество подходов"
                },
                "reps": {
                    "type": "integer",
                    "description": "Количество повторений в каждом подходе"
                },
                "weight": {
                    "type": "number",
                    "description": "Вес в кг (если применимо)"
                },
                "date": {
                    "type": "string",
                    "description": "Дата тренировки в формате YYYY-MM-DD"
                }
            },
            "required": ["exercise_name", "sets", "reps"]
        }
    },
    {
        "type": "function",
        "name": "get_exercise_history",
        "description": "Получает историю тренировок пользователя за указанное количество дней",
        "parameters": {
            "type": "object",
            "properties": {
                "days": {
                    "type": "integer",
                    "description": "За сколько последних дней получить историю (по умолчанию 7)"
                }
            },
            "required": []
        }
    },
    {
        "type": "function",
        "name": "calculate_calories",
        "description": "Рассчитывает примерное количество сожжённых калорий во время тренировки",
        "parameters": {
            "type": "object",
            "properties": {
                "exercise_name": {
                    "type": "string",
                    "description": "Название упражнения"
                },
                "duration_minutes": {
                    "type": "integer",
                    "description": "Продолжительность упражнения в минутах"
                },
                "intensity": {
                    "type": "string",
                    "enum": ["low", "moderate", "high"],
                    "description": "Интенсивность тренировки: low (низкая), moderate (средняя), high (высокая)"
                },
                "weight_kg": {
                    "type": "number",
                    "description": "Вес пользователя в килограммах"
                }
            },
            "required": ["exercise_name", "duration_minutes"]
        }
    }
] 

3️⃣ Теперь обновите класс Assistant из предыдущего урока — добавьте поддержку инструментов и их обработку:

class Assistant:
    def __init__(self, instructions, model=model, tools=None, function_map=None):
        self.model = model
        self.instructions = instructions
        self.tools = tools or []
        self.previous_response_id_map = {}
        
        # Словарь с реализациями функций
        self.function_map = function_map
    
    def __call__(self, input_text, session_id='default'):
        """Обрабатывает сообщение пользователя и возвращает ответ"""
        previous_response_id = self.previous_response_id_map.get(session_id, None)
        
        # Вызов модели с инструментами
        response = client.responses.create(
            model=self.model,
            store=True,
            previous_response_id=previous_response_id,
            instructions=self.instructions,
            input=input_text,
            tools=self.tools
        )
        
        # Обновление ID ответа
        self.previous_response_id_map[session_id] = response.id
        
        # Обработка ответа (включая возможные вызовы функций)
        return self._process_response(response, session_id)
    
    def _process_response(self, response, session_id):
        """Обрабатывает ответ модели, включая возможные вызовы функций"""
        
        # Проверка наличия вызовова функций
        for output_item in response.output:
            if output_item.type == "function_call":
                # Извлечение данных вызова
                function_name = output_item.name
                arguments_str = output_item.arguments
                
                # Парсинг аргументов из JSON-строки
                function_args = json.loads(arguments_str)
                
                print(f"[DEBUG] Вызов функции: {function_name}({function_args})")
                
                # Вызов функции, если она есть в маппинге
                if function_name in self.function_map:
                    function_result = self.function_map[function_name](**function_args)
                    
                    print(f"[DEBUG] Результат функции: {function_result}")
                    
                    # Формирование сообщения с результатом
                    result_message = f"Результат выполнения функции {function_name}: {json.dumps(function_result, ensure_ascii=False)}"
                    
                    # Отправление результата обратно модели
                    follow_up = client.responses.create(
                        model=self.model,
                        store=True,
                        previous_response_id=response.id,
                        input=result_message
                    )
                    
                    # Обновление ID последнего ответа
                    self.previous_response_id_map[session_id] = follow_up.id
                    
                    # Рекурсивная обработка нового ответа
                    # (модель может вызвать ещё одну функцию)
                    return self._process_response(follow_up, session_id)
        
        # Если вызовов функций нет, возвращается текстовый ответ
        return response.output_text if hasattr(response, 'output_text') else "" 

4️⃣ Создайте экземпляр ассистента с системным промтом, который описывает его роль и возможности:


instructions = """
Ты — профессиональный фитнес-ассистент спортивного клуба SuperGYM. 
Твоя задача — помогать пользователям:
1. Отвечать на вопросы о фитнесе, тренировках и здоровом образе жизни
2. Записывать информацию о выполненных упражнениях
3. Предоставлять историю тренировок
4. Рассчитывать сожжённые калории


Общайся энергично и мотивирующе. Предлагай конкретные рекомендации, 
основанные на данных пользователя.
"""


fitness_assistant = Assistant(instructions, tools=tools) 

Ура! Теперь вы можете взаимодействовать с расширенным ассистентом.


print(fitness_assistant("Привет! Я сегодня сделал 4 подхода по 10 отжиманий. Запиши это."))
# Ассистент использует log_exercise и возвращает ответ


print(fitness_assistant("Сколько калорий я сжёг за 30 минут бега с высокой интенсивностью?"))
# Ассистент использует calculate_calories и возвращает ответ


print(fitness_assistant("Покажи историю моих тренировок"))
# Ассистент использует get_exercise_history и возвращает ответ 

Благодаря Function Calling ассистент из простого собеседника превратился в полезный инструмент для отслеживания тренировок и расчёта физической активности. Он может не только давать советы, но и сохранять данные, анализировать их и предоставлять персонализированную информацию.

Дополнительные задания

1️⃣ Добавьте функцию для отслеживания потребления воды. Пользователь можета записывать количество выпитой воды за день и просматривать историю.2️⃣ Реализуйте функцию для подсчёта шаговПользователь может вводить количество пройденных шагов, а ассистент будет вести учёт и показывать статистику.3️⃣ Функция для отслеживания весаПользователь записывает свой текущий вес и получает историю измерений.Ассистент совершенствуется на глазах! Ну а вы пока пройдите короткий тест, чтобы лучше всё запомнить!

Какие компоненты являются обязательными для реализации механизма Function Calling? Выберите три верных ответа.

Правильный ответ

Описание функции с параметрамиНеобходимо, чтобы модель понимала, какие функции доступны и как их вызывать.

Правильный ответ

Промт для моделиПозволяет задать желаемое поведение модели, особенно при создании ассистента.

Правильный ответ

Интерфейс для вызова внешней функции или APIБез него невозможно выполнить вызов, даже если модель его корректно сформировала.Расширенные языковые возможности моделиПредобученная модель с доступом к интернетуКак вам задача?

Какие признаки в ответе модели говорят о том, что модель успешно инициировала вызов внешней функции?

Выберите три верных ответа.

Правильный ответ

В ответе содержится имя функциДа, имя функции — ключевой элемент структуры вызова.

Правильный ответ

Есть параметры с конкретными значениями

Правильно

Аргументы должны быть извлечены из запроса и оформлены как структура.Модель отправляет HTTP-запрос к серверу

Правильный ответ

Ответ модели содержит тип function_call

Да, это специальный маркер, указывающий на то, что модель решила вызвать функцию.В сообщении пользователя есть слово выполни

Как вам задача?

В каких ситуациях из списка стоит использовать Function Calling при разработке ассистента на базе языковой модели? Выберите три верных ответа.

Тоже правильный ответ

Ассистент должен показать актуальный курс доллараДа, у модели нет доступа к текущим данным — их нужно получить из внешнего источника.

Неправильный ответ

Ассистент объясняет общие принципы ведения дневника тренировокЭто информационный ответ, с которым модель справится самостоятельно, без внешних вызово

Тоже правильный ответ

Ассистент должен запускать процесс резервного копирования на сервереДа, это действие вне компетенции модели — его нужно делегировать через вызов специальной функции.

Неправильный ответ

Ассистент составляет вдохновляющее сообщение для пользователяГенерация мотивационного текста — задача самой языковой модели, Function Calling не нужен.

Правильный ответ

Ассистент должен рассчитать точное количество калорий, сожжённых за тренировку

В данном случае необходим точный математический расчёт по определённой формуле, что выходит за рамки возможностей языковой модели, которая допускает погрешности в вычислениях.Как вам задача?

Итоги урока

Function Calling значительно расширяет возможности языковых моделей: позволяет им взаимодействовать с внешними системами и получать актуальные данные. Это открывает путь к созданию по-настоящему полезных ассистентов, которые не только отвечают на вопросы, но и выполняют реальные действия.На этом всё. До встречи в следующем уроке!❗Не забудьте остановить ресурсы, чтобы избежать лишних трат

Ссылка на источник: https://practicum.yandex.ru/trainer/yc-ml-aiagents/lesson/e85eea3b-f34b-42a2-bdc7-ee42392bd3ba/

Рубрики: Uncategorized

0 комментариев

Добавить комментарий

Заполнитель аватара

Ваш адрес email не будет опубликован.