Обработка ошибок и галлюцинаций при вызове инструментов
Введение: Когда модель «фантазирует» с вашим API
Добро пожаловать на критически важный урок модуля. До этого момента мы учились давать Gemini руки — подключать инструменты. Теперь мы должны научить её не ломать эти руки.
Function Calling — это мост между вероятностным миром LLM (где «примерно правильно» — это норма) и детерминированным миром программного кода (где одна опечатка вызывает SyntaxError). Самая большая проблема здесь — галлюцинации и ошибки исполнения.
Представьте ситуацию: вы подключили API для бронирования билетов. Пользователь просит билет на «Марс». Модель, вместо того чтобы сказать «нет рейсов», может выдумать несуществующий ID аэропорта MRS-01 и попытаться вызвать вашу функцию. Если ваш код не готов к этому, система упадет.
В этом уроке мы разберем:
- Типы ошибок: от выдуманных аргументов до неверных типов данных.
- Паттерн «Петля самокоррекции» (Self-Correction Loop): как заставить Gemini исправлять свои же ошибки.
- Валидацию на стороне клиента.
- Как правильно сообщать модели о сбоях внешних сервисов.
Анатомия ошибок при вызове инструментов
Прежде чем лечить, нужно поставить диагноз. Ошибки при работе с Function Calling в Gemini можно разделить на три основные категории:
- Галлюцинации параметров (Parameter Hallucination):
Модель пытается вызвать существующую функцию, но передает аргументы, которых нет в схеме, или значения, которые не имеют смысла (например, дату в формате «завтра» вместо «YYYY-MM-DD», если вы не прописали это в промпте). - Галлюцинации функций (Function Hallucination):
Модель пытается вызвать функцию, которую вы ей не давали, но название которой кажется ей логичным в данном контексте (например,send_email, хотя у вас есть толькоsend_message). - Ошибки исполнения (Execution Errors):
Модель все сделала правильно, параметры валидны, но внешний API вернул ошибку (500 Internal Server Error, Timeouts) или бизнес-логика запрещает действие (недостаточно средств на счете).
Ключ к решению всех этих проблем — отношение к ошибке не как к катастрофе, а как к информации для модели.
# Пример типичной уязвимой схемы
# Если мы не опишем формат четко, модель может передать что угодно
tools_schema = [
{
"name": "search_product",
"description": "Поиск товара на складе по артикулу",
"parameters": {
"type": "object",
"properties": {
"sku": {
"type": "string",
"description": "Артикул товара (например, 'A-123')"
},
"quantity": {
"type": "integer",
"description": "Количество"
}
},
"required": ["sku"]
}
}
]
# ПРОБЛЕМА:
# Пользователь пишет: "Найди мне красные кроссовки"
# Модель может вызвать: search_product(sku="red_sneakers")
# Это приведет к ошибке в базе данных, так как это не валидный артикул.
Паттерн «Петля самокоррекции» (Self-Correction Loop)
Это самая мощная техника при работе с агентами. Идея проста: если вызов функции в вашем коде вызывает исключение (Exception), вы не прерываете работу программы. Вместо этого вы возвращаете текст ошибки обратно модели в виде результата выполнения функции.
Gemini 3 (и Pro, и Flash версии) обучены анализировать такие ответы. Увидев сообщение об ошибке (например, Error: Invalid SKU format), модель способна переосмыслить свой предыдущий шаг, извиниться перед пользователем или попытаться вызвать функцию снова с исправленными данными.
Алгоритм действий:
- Получаем запрос от модели на вызов функции.
- Пытаемся выполнить функцию в блоке
try...except. - Если успешно — возвращаем результат JSON.
- Если ошибка — ловим её, формируем JSON вида
{"error": "Описание ошибки"}и отправляем модели. - Модель получает этот контекст и генерирует новый ответ.
import google.generativeai as genai
from google.protobuf import struct_pb2
# Имитация функции работы с БД
def get_user_info(user_id):
# Допустим, ID должны быть только числовыми
if not user_id.isdigit():
raise ValueError(f"Некорректный формат user_id: '{user_id}'. ID должен состоять только из цифр.")
if user_id == "123":
return {"name": "Алексей", "status": "active"}
else:
raise ValueError(f"Пользователь с ID {user_id} не найден.")
# Словарь доступных функций
functions = {
"get_user_info": get_user_info
}
chat = model.start_chat(history=[])
def process_message(user_message):
response = chat.send_message(user_message)
# Проверяем, хочет ли модель вызвать функцию
if response.candidates[0].content.parts[0].function_call:
fn_call = response.candidates[0].content.parts[0].function_call
fn_name = fn_call.name
fn_args = fn_call.args
print(f"🤖 Модель пытается вызвать: {fn_name} с аргументами {fn_args}")
try:
# Попытка исполнения
api_result = functions[fn_name](**fn_args)
result_to_send = api_result
except Exception as e:
# ВАЖНЫЙ МОМЕНТ: Мы не падаем, мы формируем отчет об ошибке
print(f"⚠️ Ошибка исполнения: {str(e)}")
result_to_send = {
"error": str(e),
"instruction": "Проанализируй ошибку. Если формат неверен, попроси пользователя уточнить данные. Не выдумывай ID."
}
# Отправляем результат (успех или ошибку) обратно модели
response_parts = [
genai.protos.Part(
function_response=genai.protos.FunctionResponse(
name=fn_name,
response={"result": result_to_send}
)
)
]
final_response = chat.send_message(response_parts)
return final_response.text
return response.text
# Пример использования
# Пользователь вводит некорректный ID
print("Ответ:", process_message("Проверь статус пользователя id_five"))
# ОЖИДАЕМОЕ ПОВЕДЕНИЕ:
# 1. Модель вызывает get_user_info(user_id="id_five")
# 2. Python выбрасывает ValueError
# 3. Мы отправляем ошибку обратно в Gemini
# 4. Gemini читает ошибку и отвечает пользователю: "Извините, ID должен состоять только из цифр. Уточните корректный ID."
Предотвращение лучше лечения: Валидация схемы
Хотя петля самокоррекции работает отлично, лучше всего вообще не допускать попадания «мусора» в функцию. Для этого нужно максимально точно описывать схему данных.
В Python экосистеме стандартом де-факто является библиотека Pydantic, но при работе с Gemini API через SDK мы часто передаем словари или используем декораторы. Важно использовать поле description в параметрах не просто как комментарий, а как инструкцию.
Советы по описанию схемы:
- Используйте Enums: Если параметр может принимать только определенные значения (например, статус заказа: 'new', 'processing', 'shipped'), жестко пропишите это в
enum. Это физически ограничит выбор модели. - Примеры в описании: В поле description добавьте: "Дата начала в формате ISO 8601, например '2023-10-05'. Не используй слова 'вчера' или 'сегодня'."
- System Instructions: В системный промпт модели добавьте раздел: "Ты — строгий ассистент. Если у тебя нет точного значения аргумента для функции, НЕ пытайся его угадать. Спроси пользователя."
Задание: Реализация «Предохранителя»<br><br>У вас есть функция `calculate_delivery(address: str, weight: float)`. <br>Внешний сервис доставки падает с ошибкой, если `weight` больше 20 кг или меньше 0.1 кг.<br><br>Напишите функцию-обертку (wrapper) на Python, которая:<br>1. Принимает аргументы от модели.<br>2. Проверяет вес ДО вызова реального API.<br>3. Если вес некорректен, сразу возвращает словарь с ошибкой, не дергая внешний сервис.<br>4. Если всё ок, вызывает `real_api_delivery`.
Обработка галлюцинаций названий инструментов
Иногда модель может попытаться вызвать функцию delete_database, которой вообще нет в списке tools. Это происходит редко в новых версиях Gemini 1.5/Pro, но это возможно, если контекст разговора перегружен.
В коде это нужно обрабатывать через проверку наличия ключа в словаре функций.
Если модель запросила несуществующую функцию:
- Не паникуйте.
- Верните ответ (FunctionResponse):
Error: Function '{name}' not found. Available functions: {list(functions.keys())}. Please verify user intent.
Это напомнит модели о том, какими инструментами она реально располагает.
Когда API «лежит» (500/Timeout)
Что делать, если внешний сервис недоступен? Если вы просто вернете ошибку, модель может попытаться повторить запрос немедленно, создавая бесконечный цикл (Retry Storm).
В таких случаях хорошей практикой является добавление мета-инструкции в ответ об ошибке: "API временно недоступен. Сообщи пользователю, чтобы он попробовал позже, и НЕ повторяй попытку автоматически."
Ваш код вызвал внешний погодный API через инструмент Gemini, но API вернул ошибку 404 (City Not Found). Какое действие является наилучшей практикой?
Резюме урока
Разработка надежных систем на базе LLM — это на 20% написание промптов и на 80% обработка крайних случаев. Модели склонны к галлюцинациям, и ваша задача как инженера — создать «защитные ограждения» (guardrails).
Мы выяснили, что ошибки — это не тупик, а часть диалога с моделью. Возвращая исключения обратно в контекст разговора, мы даем Gemini шанс исправиться, что значительно повышает качество пользовательского опыта (UX). Пользователь видит не «System Crash», а осмысленный уточняющий вопрос.