Self-Correction: Механизмы самопроверки и исправления ошибок

55 минут Урок 29

Введение: Почему агенты ошибаются и как это исправить

Добро пожаловать в один из самых критически важных уроков курса. Мы уже научились создавать агентов, которые могут использовать инструменты и планировать действия. Но давайте будем честны: даже самые продвинутые модели, включая Gemini 3, иногда ошибаются. Они могут сгенерировать невалидный JSON, вызвать функцию с неверными аргументами или просто «загаллюцинировать» факт, которого не существует.

В классическом программировании ошибка часто приводит к падению программы (Exception). В мире LLM ошибка может выглядеть как вполне уверенный, но абсолютно неверный ответ. Для Enterprise-решений это недопустимо.

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

Анатомия ошибки и цикл Рефлексии

Прежде чем писать код, давайте разберем механику. Почему модель вообще может исправить сама себя? Если она ошиблась первый раз, почему второй раз будет успешным?

Ответ кроется в изменении контекста. Когда модель генерирует ответ, она работает в режиме «потока». Но если мы попросим её посмотреть на свой предыдущий ответ и найти в нем ошибку, мы переключаем её в режим «аналитика». Это похоже на то, как программист пишет код (быстро, могут быть баги), а затем делает Code Review (медленно, вдумчиво).

Цикл Reflecion (Рефлексия):

  1. Action (Действие): Агент выполняет задачу (например, пишет SQL-запрос).
  2. Evaluation (Оценка): Агент или внешний валидатор проверяет результат (запрос не выполнился, база данных вернула ошибку).
  3. Feedback (Обратная связь): Ошибка подается обратно в контекст модели.
  4. Refinement (Уточнение): Агент генерирует новое решение, учитывая ошибку.

С Gemini 3, благодаря огромному контекстному окну, мы можем хранить всю историю этих «неудачных попыток», что позволяет модели учиться на ошибках прямо в процессе выполнения задачи.

python
import google.generativeai as genai
import os

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

model = genai.GenerativeModel('gemini-1.5-pro-latest')

def generate_with_self_correction(prompt, max_retries=3):
    history = []
    
    # Первичный запрос
    full_prompt = f"Задача: {prompt}\nРеши задачу и выведи только ответ."
    
    for attempt in range(max_retries):
        print(f"--- Попытка {attempt + 1} ---")
        
        # Генерация ответа (в реальном кейсе здесь может быть история чата)
        response = model.generate_content(full_prompt)
        result = response.text.strip()
        print(f"Агент предложил: {result}")
        
        # ЭТАП ВАЛИДАЦИИ (Simulated Validator)
        # Представим, что мы просим агента сгенерировать код Python, который выводит 'Hello World'
        # Простой валидатор: проверяет, есть ли слово 'print' и запускается ли код
        validation_error = None
        
        if "print" not in result:
             validation_error = "Ошибка: Код не содержит функции print()."
        else:
            try:
                # В продакшене используйте песочницу для выполнения кода!
                exec(result)
                print("✅ Проверка пройдена!")
                return result
            except Exception as e:
                validation_error = f"Ошибка выполнения кода: {str(e)}"
        
        # Если мы здесь, значит есть ошибка
        print(f"❌ Обнаружена проблема: {validation_error}")
        
        # ЭТАП РЕФЛЕКСИИ
        # Добавляем контекст ошибки в следующий промпт
        full_prompt = (
            f"Предыдущее решение: {result}\n"
            f"Сообщение об ошибке: {validation_error}\n"
            f"Проанализируй ошибку и предложи исправленное решение. Думай шаг за шагом."
        )
    
    return "Не удалось найти решение после нескольких попыток."

# Пример вызова
# Допустим, модель сначала забудет скобки (в Python 2 стиле), а потом исправится
task = "Напиши код на Python 3, который выводит фразу 'Hello Gemini'"
final_result = generate_with_self_correction(task)

Стратегии обработки ошибок инструментов (Tool Use Correction)

Самый частый сценарий для Enterprise-агентов — это использование внешних инструментов (Function Calling). Здесь ошибки делятся на два типа:

  • Hallucinated Arguments: Агент придумал аргумент, которого нет в сигнатуре функции.
  • Logic/Runtime Errors: Аргументы валидны, но API вернул ошибку (например, «Товар не найден» или «Дата занята»).

В Gemini 3 API механизм Function Calling нативно поддерживает возврат результата функции обратно модели. Это идеальное место для внедрения самокоррекции. Если ваш API возвращает JSON с ошибкой (например, {"status": "error", "msg": "Invalid date format"}), модель Gemini автоматически воспримет это как сигнал к действию, если вы правильно настроите системный промпт.

Ключевая техника: Никогда не скрывайте Traceback от модели. В промышленных системах мы часто логируем ошибки для разработчиков, а пользователю показываем «Что-то пошло не так». Для агента всё наоборот: ему нужно показать «сырую» техническую ошибку, чтобы он понял, что именно исправить.

python
from google.protobuf import struct_pb2

# Эмуляция функции бронирования
def book_meeting(date: str, email: str):
    # Простая валидация формата даты
    if "2024" not in date:
        return {"error": "Date must be in 2024. Format: YYYY-MM-DD"}
    if "@" not in email:
        return {"error": "Invalid email format"}
    return {"success": True, "booking_id": "12345"}

tools_config = [book_meeting]

# Чат с моделью
chat = model.start_chat(enable_automatic_function_calling=True)

# Сценарий: Пользователь вводит неверные данные
# Модель попытается вызвать функцию, получит JSON с ошибкой,
# проанализирует его и либо сама исправит (если знает контекст),
# либо спросит пользователя уточнение.

response = chat.send_message(
    "Забронируй встречу на 5 мая для user_at_gmail.com" 
    # Обратите внимание: год не указан, email странный, но валидный для упрощения
)

# Внутри (невидимо для нас) происходит следующее:
# 1. Model -> call book_meeting("05-05", "user_at_gmail.com")
# 2. Tool -> returns {"error": "Date must be in 2024..."}
# 3. Model -> Analysing error -> "Ага, нужен год."
# 4. Model -> call book_meeting("2024-05-05", "user_at_gmail.com")
# 5. Tool -> returns {"success": True...}
# 6. Model -> "Встреча успешно забронирована!"

print(response.text)

Архитектурный паттерн: Critic-Actor (Критик-Исполнитель)

Для сложных задач, где стоимость ошибки высока (например, финансовые транзакции или генерация юридических документов), полагаться на самопроверку одной и той же сессии модели бывает рискованно. Модель может быть «предвзята» к своему собственному ответу.

Решение: паттерн Critic-Actor.

  • Actor (Исполнитель): Генерирует черновик решения. У него высокая «температура» (креативность).
  • Critic (Критик): Отдельная сессия (или даже другая, более мощная модель), которой подается задача и решение Исполнителя. Задача Критика — только искать ошибки. У него низкая температура (строгость).

Этот диалог может продолжаться несколько итераций. Исследования показывают, что разделение ролей повышает качество генерации кода и сложных рассуждений на 20-30% по сравнению с простым Chain-of-Thought.

Совет из практики: В промпте Критика явно укажите: «Будь придирчивым. Твоя задача — найти уязвимости безопасности и логические несостыковки. Если ошибок нет, ответь 'OK'».

Упражнение

Создайте систему из двух агентов (ролей) для генерации SQL-запросов. <br>1. Агент-Генератор получает схему БД и вопрос на естественном языке, генерирует SQL.<br>2. Агент-Валидатор проверяет этот SQL. Валидатор не имеет доступа к БД, но должен проверить синтаксис (например, отсутствие DROP TABLE) и соответствие вопросу.<br>3. Если Валидатор находит ошибку, Генератор должен переписать запрос.<br><br>Напишите псевдокод или Python-код, реализующий этот цикл (максимум 2 итерации).

Опасности и ограничения (Guardrails)

Внедряя самокоррекцию, важно помнить о рисках бесконечных циклов. Если агент не знает ответа или инструмент сломан фундаментально, он может бесконечно пытаться «исправить» запрос, сжигая ваши токены и деньги.

Best Practices:

  • Жесткий лимит итераций: Обычно 3 попытки достаточно. Если агент не справился за 3 раза, лучше вернуть ошибку человеку или эскалировать задачу.
  • Детекция зацикливания: Проверяйте, не генерирует ли агент тот же самый ошибочный ответ, что и в прошлый раз.
  • Температура: При повторных попытках (retry) полезно немного повысить температуру (на 0.1-0.2), чтобы выбить модель из локального минимума и заставить искать альтернативные пути.

Самокоррекция — это не панацея, а инструмент повышения надежности (Robustness). Она увеличивает время отклика (Latency) и стоимость, но критически важна для автономных систем.

Вопрос

В паттерне Critic-Actor, какова основная роль агента-Критика?