Интеграция с внешними API: Создание реальных действий

55 минут Урок 13

Введение: От генератора текста к оператору действий

Добро пожаловать в один из самых захватывающих разделов курса. До этого момента мы рассматривали Gemini 3 преимущественно как интеллектуального собеседника: он мог анализировать текст, писать код, создавать стихи или суммаризировать статьи. Но в реальном мире бизнеса и инженерии "разговоров" недостаточно. Нам нужны действия.

Представьте, что вы строите не просто чат-бота, а полноценного ИИ-ассистента для логистической компании. Пользователь пишет: "Груз #12345 задерживается. Перенеси доставку на завтра и отправь уведомление клиенту".

Языковая модель сама по себе не имеет доступа к вашей базе данных и не умеет отправлять email. Она изолирована. В этом уроке мы сломаем эту стену. Мы научимся превращать намерения модели (Intents) в реальные вызовы API (API Calls).

Что вы узнаете в этом уроке:

  • Как работает архитектура Function Calling «под капотом».
  • Почему Gemini не выполняет код сама, а просит об этом вас.
  • Как описывать инструменты (Tools) так, чтобы модель не ошибалась.
  • Паттерны безопасности: как не дать ИИ случайно удалить базу данных.
  • Реализация полного цикла: Запрос -> Выбор инструмента -> API вызов -> Ответ модели.

Архитектура взаимодействия: Кто за что отвечает?

Существует распространенное заблуждение, что когда мы подключаем «Tools» (инструменты), модель сама лезет в интернет и нажимает кнопки. Это не так, и понимание этого критически важно для архитектора.

Процесс выглядит как игра в пинг-понг между тремя участниками: Пользователем, Моделью (Gemini) и Вашим кодом (Client App).

  1. Пользователь задает вопрос: «Какая погода в Токио?»
  2. Ваш код отправляет этот вопрос в Gemini, прикладывая список доступных инструментов (например, функцию get_weather).
  3. Gemini анализирует вопрос, понимает, что не знает погоду, но видит инструмент get_weather.
  4. Gemini (и это ключевой момент!) НЕ вызывает функцию. Она возвращает вам специальный ответ: «Эй, разработчик! Пожалуйста, вызови функцию get_weather с аргументом city='Tokyo' и скажи мне, что получилось».
  5. Ваш код видит эту просьбу, выполняет реальный HTTP-запрос к погодному API, получает ответ («+22, Дождь»).
  6. Ваш код отправляет этот результат обратно в Gemini.
  7. Gemini, имея теперь и контекст вопроса, и фактические данные, формирует финальный ответ пользователю: «В Токио сейчас +22 градуса и идет дождь».

Модель выступает здесь как оркестратор и преобразователь естественного языка в структурированный JSON для API.

python
import os
import google.generativeai as genai
from google.generativeai.types import FunctionDeclaration, Tool

# 1. Настройка окружения
# Предполагается, что API_KEY уже настроен
genai.configure(api_key=os.environ["GEMINI_API_KEY"])

# 2. Определение функции (инструмента)
# Мы не пишем здесь саму логику API, мы только ОПИСЫВАЕМ её для модели.

get_exchange_rate_func = FunctionDeclaration(
    name="get_exchange_rate",
    description="Возвращает текущий курс обмена валют на определенную дату.",
    parameters={
        "type": "object",
        "properties": {
            "base_currency": {
                "type": "string",
                "description": "Код базовой валюты (например, USD, EUR)"
            },
            "target_currency": {
                "type": "string",
                "description": "Код целевой валюты (например, RUB, GBP)"
            },
            "date": {
                "type": "string",
                "description": "Дата в формате YYYY-MM-DD. Если не указана, используется текущая."
            }
        },
        "required": ["base_currency", "target_currency"]
    }
)

# 3. Создание объекта инструментов
finance_tools = Tool(
    function_declarations=[get_exchange_rate_func]
)

# 4. Инициализация модели с инструментами
model = genai.GenerativeModel(
    model_name='gemini-1.5-pro-latest', # Или gemini-3-pro в будущем
    tools=[finance_tools]
)

print("Инструменты успешно подключены к модели.")

Инженерия описаний (Description Engineering)

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

Плохое описание:
description="Получить данные"
param="x": "значение"

Хорошее описание:
description="Ищет информацию о наличии товара на складе по артикулу (SKU). Возвращает количество и дату ближайшей поставки."
param="sku": "Уникальный идентификатор товара, начинающийся с 'PROD-'"

Модель Gemini 3 обладает огромным контекстным окном, поэтому не стесняйтесь давать подробные описания. Если параметр имеет строгий формат (например, ISO 8601 для дат или конкретный список категорий), укажите это прямо в описании аргумента (docstring). Это снижает вероятность галлюцинаций, когда модель придумывает несуществующие параметры.

Упражнение

Спроектируйте декларацию функции для системы 'Умный Дом'. Функция должна называться `set_thermostat`. Она должна принимать целевую температуру (от 16 до 30 градусов) и название комнаты (Кухня, Спальня, Гостиная). Опишите структуру `parameters` в формате Python dict, аналогично примеру выше.

Обработка вызова: Цикл выполнения (The Execution Loop)

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

  1. Распознать этот объект.
  2. Извлечь имя функции и аргументы.
  3. Безопасно выполнить реальный код.
  4. Вернуть результат в модель.

Важное предостережение о безопасности: Никогда не используйте eval() для выполнения кода, сгенерированного моделью. Всегда используйте словарь (map) разрешенных функций. Если модель просит вызвать функцию delete_database, а у вас в словаре её нет или у пользователя нет прав — ваш код должен заблокировать это действие.

Давайте реализуем полноценный цикл чата с поддержкой функций.

python
# 1. Реализация "настоящих" функций (заглушки для примера)
def get_exchange_rate_api(base_currency, target_currency, date=None):
    # В реальности здесь был бы запрос к requests.get(...)
    # Эмуляция ответа:
    if not date:
        date = "сегодня"
    return {
        "base": base_currency,
        "target": target_currency,
        "rate": 92.5 if target_currency == "RUB" else 1.1,
        "date": date
    }

# 2. Словарь доступных функций (Dispatcher)
functions_map = {
    "get_exchange_rate": get_exchange_rate_api
}

# 3. Функция обработки чата
def chat_with_tools(message, chat_session):
    # Отправляем сообщение пользователя
    response = chat_session.send_message(message)
    
    # Проверяем, хочет ли модель вызвать функцию
    # В Gemini API это находится в response.parts
    part = response.parts[0]
    
    if part.function_call:
        fc = part.function_call
        fname = fc.name
        fargs = fc.args
        
        print(f"🤖 Gemini хочет вызвать: {fname} с аргументами {fargs}")
        
        # Проверка безопасности и существования функции
        if fname in functions_map:
            # Вызов реальной функции
            api_response = functions_map[fname](**fargs)
            print(f"📡 Результат API: {api_response}")
            
            # ОЧЕНЬ ВАЖНО: Возвращаем результат обратно модели
            # Модель ждет FunctionResponse
            response_part = genai.protos.Part(
                function_response=genai.protos.FunctionResponse(
                    name=fname,
                    response={"result": api_response}
                )
            )
            
            # Отправляем результат выполнения модели
            # Она использует его для генерации финального текста
            final_response = chat_session.send_message([response_part])
            return final_response.text
        else:
            return "Ошибка: Модель запросила неизвестную функцию."
            
    return response.text

# 4. Запуск сессии
chat = model.start_chat(enable_automatic_function_calling=False) # Мы управляем вручную для демонстрации
user_msg = "Сколько стоит 100 долларов в рублях?"
answer = chat_with_tools(user_msg, chat)
print(f"🗣 Ответ Gemini: {answer}")

Автоматический вызов функций (Automatic Function Calling)

В примере выше мы реализовали ручной цикл (Manual Loop). Это полезно для отладки и сложной логики (например, если перед вызовом API нужно подтверждение человека). Однако SDK Gemini предлагает режим enable_automatic_function_calling=True.

В этом режиме библиотека сама:

  • Обнаруживает function_call.
  • Вызывает соответствующую Python-функцию (если вы передали её при инициализации).
  • Отправляет результат обратно.
  • Возвращает вам только финальный текстовый ответ.

Это значительно упрощает код, но скрывает детали выполнения. Для архитектора важно уметь работать в обоих режимах: автоматическом для простых задач и ручном для критически важных систем, где требуется аудит каждого действия.

Работа с ошибками API

Что если внешний API недоступен или вернул ошибку 500? Если вы просто передадите ошибку в модель, Gemini достаточно умна, чтобы сказать пользователю: "К сожалению, сервис курсов валют сейчас недоступен, попробуйте позже". Это гораздо лучше, чем падение программы (Exception). Всегда оборачивайте вызовы реальных API в блоки try-except и возвращайте модели JSON с описанием ошибки, например: {"error": "Service timeout"}.

Вопрос

Почему модель Gemini не выполняет HTTP-запрос к внешнему API самостоятельно напрямую?

Продвинутая тема: Параллельный вызов функций (Parallel Function Calling)

Gemini 3 (и последние версии 1.5) поддерживают параллельные вызовы. Представьте, что пользователь спрашивает: "Какая погода в Лондоне, Париже и Нью-Йорке?".

Старые модели делали бы это последовательно: спросили Лондон -> получили ответ -> спросили Париж... Это долго. Gemini может вернуть сразу три объекта function_call в одном ответе.

Ваш обработчик должен быть готов к этому:

  • Проверять, является ли parts списком.
  • Выполнять все запрошенные функции (желательно асинхронно, используя asyncio для скорости).
  • Собирать все результаты и отправлять их обратно модели одним пакетом.

Это критически важно для создания отзывчивых интерфейсов (Low Latency), где пользователь не хочет ждать 10 секунд, пока система опросит разные сервисы по очереди.

Упражнение

Напишите функцию-обработчик на Python (псевдокод или реальный код), которая принимает список `function_calls` от модели и выполняет их. Предположим, что у нас есть словарь `tools_map`. Главная задача — показать, как итерироваться по списку вызовов.

Заключение: От теории к практике

Интеграция с внешними API превращает LLM из справочника в сотрудника. Вы научились определять инструменты, безопасно их вызывать и обрабатывать результаты. В следующем уроке мы разберем создание сложных агентов, которые могут планировать цепочки действий (Chain of Thought) для решения многосоставных задач.

Домашнее задание: Попробуйте создать бота-секретаря. Определите две функции: check_calendar(date) и send_email(to, subject, body). Проверьте сценарий: "Посмотри, свободен ли я завтра в 14:00, и если да, отправь приглашение Ивану".