Оценка качества ответов и итеративное улучшение промптов

60 минут Урок 10

Введение: Конец эпохи «мне кажется, это звучит нормально»

Добро пожаловать на критически важный этап нашего курса. До этого момента мы учились говорить с Gemini. Теперь мы будем учиться слушать и судить.

В классическом программировании у нас есть unit-тесты: assert 2 + 2 == 4. Если результат 5, код сломан. В мире Generative AI, и особенно при работе с мультимодальными моделями уровня Gemini 3, понятие «правильно» размывается. Если вы просите модель «Описать фотографию заката», и она пишет «Красивое вечернее небо», а вы хотели «Багровый горизонт с перистыми облаками» — это ошибка модели или ошибка промпта?

В этом уроке мы перейдем от интуитивного написания промптов к инженерному подходу. Мы разберем:

  • Почему ваши глаза — плохой инструмент для оценки масштабируемых систем.
  • Концепцию LLM-as-a-Judge (LLM как судья).
  • Как построить цикл итеративного улучшения (Loop of Improvement).
  • Метрики для мультимодальных задач.

Наша цель: научиться измерять качество ответов Gemini, чтобы улучшать промпты не наугад, а на основе данных.

Часть 1: Проблема оценки генеративных моделей

Главная ловушка новичка — оценивать промпт по одному удачному или неудачному запуску. Это называется «anecdotal evidence» (анекдотическое доказательство). В Enterprise-решениях нам нужна статистика.

Детерминизм vs Вероятность

Gemini недетерминирована (если не выкрутить temperature в 0, и даже тогда есть нюансы). Один и тот же промпт может дать разный результат. Поэтому оценка должна строиться на выборках (datasets).

Типы метрик

  1. Классические NLP метрики (ROUGE, BLEU): Сравнивают перекрытие слов. Они ужасны для чат-ботов и творческих задач. Если модель перефразирует предложение, сохраняя смысл, BLEU упадет, хотя качество выросло.
  2. Human Evaluation (Человеческая оценка): Золотой стандарт, но дорого и медленно.
  3. Model-based Evaluation (LLM-as-a-Judge): Современный стандарт. Мы используем одну модель (часто более мощную, например, Gemini 1.5 Pro или Ultra), чтобы оценить ответы другой модели (или самой себя).

Мы сосредоточимся на третьем подходе.

Часть 2: Паттерн «LLM как Судья» (LLM-as-a-Judge)

Суть метода проста: мы создаем мета-промпт, который содержит:

  • Входные данные: То, что мы подали в систему (вопрос пользователя, картинка).
  • Ответ системы: То, что сгенерировала Gemini.
  • Критерии оценки (Рубрика): Четкие инструкции, за что ставить баллы.

Это позволяет автоматизировать проверку семантики, тональности и фактологии. Давайте посмотрим, как это реализовать в коде.

python
import google.generativeai as genai
import typing_extensions as typing
import json

# Представим, что у нас есть настроенный клиент
# genai.configure(api_key="YOUR_API_KEY")
model = genai.GenerativeModel('gemini-1.5-pro')

# 1. Функция, которую мы хотим протестировать (наша целевая задача)
def generate_marketing_copy(product_name, features):
    prompt = f"""
    Напиши короткий, энергичный рекламный слоган для продукта '{product_name}'.
    Основные фичи: {', '.join(features)}.
    Тон: агрессивный, молодежный.
    """
    response = model.generate_content(prompt)
    return response.text

# 2. Функция-Судья (Evaluator)
# Мы используем JSON schema для получения структурированной оценки
class EvaluationResult(typing.TypedDict):
    score: int
    reasoning: str
    pass_fail: bool

def evaluate_response(input_data, actual_output):
    eval_prompt = f"""
    Твоя роль - строгий редактор маркетинговых текстов.
    
    ВХОДНЫЕ ДАННЫЕ:
    Продукт: {input_data['product']}
    Фичи: {input_data['features']}
    
    ОТВЕТ МОДЕЛИ:
    {actual_output}
    
    КРИТЕРИИ ОЦЕНКИ:
    1. Соответствие тону (агрессивный, молодежный).
    2. Упоминание всех фич.
    3. Отсутствие клише.
    
    Поставь оценку от 1 до 5. Если оценка 4 или 5, то pass_fail = True.
    Верни ответ строго в формате JSON.
    """
    
    # Используем режим JSON для гарантии структуры
    result = model.generate_content(
        eval_prompt,
        generation_config=genai.GenerationConfig(
            response_mime_type="application/json",
            response_schema=EvaluationResult
        )
    )
    return json.loads(result.text)

# --- Тестирование ---

input_case = {
    "product": "EnergyDrink X",
    "features": ["без сахара", "двойной кофеин", "вкус кактуса"]
}

# Шаг 1: Генерация
generated_text = generate_marketing_copy(input_case["product"], input_case["features"])
print(f"Генерация: {generated_text}")

# Шаг 2: Оценка
evaluation = evaluate_response(input_case, generated_text)
print(f"\nОценка Судьи: {evaluation['score']}/5")
print(f"Вердикт: {'✅ PASS' if evaluation['pass_fail'] else '❌ FAIL'}")
print(f"Причина: {evaluation['reasoning']}")

Часть 3: Итеративное улучшение (The Loop)

Получив оценку, мы не останавливаемся. Оценка — это сигнал к действию. Процесс улучшения промпта выглядит как цикл:

  1. Baseline (Базовая линия): Запускаем текущий промпт на тестовой выборке (5-10 примеров).
  2. Evaluation (Оценка): Запускаем «Судью» и собираем метрики.
  3. Analysis (Анализ): Смотрим, где именно низкие баллы.
    • Модель галлюцинирует факты? → Добавляем RAG или Grounding.
    • Модель не соблюдает формат? → Добавляем Few-Shot примеры (One-Shot/Two-Shot).
    • Модель слишком многословна? → Вводим ограничения (Constraints).
  4. Refinement (Уточнение): Изменяем промпт.
  5. Regression Test (Регрессионный тест): Снова запускаем на тех же данных, чтобы убедиться, что исправив одно, мы не сломали другое.

Практический пример: Борьба с мультимодальными галлюцинациями

Представьте, что вы подаете в Gemini фото меню ресторана и просите извлечь цены. Модель часто пропускает позиции или путает цифры, если шрифт рукописный.

Плохой цикл: Вы меняете «Извлеки цены» на «Пожалуйста, найди все цены» (это шаманизм).

Инженерный цикл:
1. Вы запускаете промпт на 10 фото.
2. Видите, что ошибки возникают в разделе «Напитки».
3. Вы меняете промпт на Chain-of-Thought: «Сначала определи разделы меню. Затем иди по каждому разделу построчно. Для каждой строки выпиши название и цену».
4. Оценка повышается.

python
# Пример реализации логики сравнения двух версий промпта (A/B тестирование)

def run_ab_test(test_cases, prompt_func_a, prompt_func_b, evaluator_func):
    results = {"A": [], "B": []}
    
    for case in test_cases:
        # Версия A
        out_a = prompt_func_a(case)
        score_a = evaluator_func(case, out_a)
        results["A"].append(score_a['score'])
        
        # Версия B
        out_b = prompt_func_b(case)
        score_b = evaluator_func(case, out_b)
        results["B"].append(score_b['score'])
        
    avg_a = sum(results["A"]) / len(results["A"])
    avg_b = sum(results["B"]) / len(results["B"])
    
    print(f"Средний балл Промпта A: {avg_a:.2f}")
    print(f"Средний балл Промпта B: {avg_b:.2f}")
    
    if avg_b > avg_a:
        print("🚀 Промпт B победил! Внедряем изменения.")
    else:
        print("⚠️ Изменения не улучшили результат. Откат.")

Часть 4: Продвинутые техники для Gemini 3

В контексте Gemini 3, который обладает огромным контекстным окном и мощными мультимодальными возможностями, процесс оценки имеет свои нюансы.

1. Reference-Free Evaluation (Оценка без эталона)

Часто у нас нет «правильного» ответа для сравнения. В этом случае мы просим модель оценить внутреннюю согласованность. Например, если Gemini генерирует код, мы можем попросить её же (в отдельном вызове) проверить этот код на уязвимости или синтаксические ошибки перед тем, как отдать пользователю.

2. Self-Correction (Самокоррекция)

Это вершина итеративного подхода. Мы не просто оцениваем ответ, мы заставляем модель исправить его в рамках одной сессии.

Паттерн:
User -> Prompt -> Gemini -> [Draft] -> Gemini (как критик) -> [Critique] -> Gemini -> [Final Answer]

Этот метод потребляет больше токенов, но радикально повышает качество в сложных задачах (математика, логика, написание кода).

Упражнение

Задание: Исправление галлюцинаций в JSON.<br><br>У вас есть промпт, который должен извлекать данные из текста в формате JSON. Однако модель иногда добавляет лишний текст (вступление или заключение), что ломает парсинг.<br><br>ИСХОДНЫЙ ПРОМПТ:<br>"Извлеки список имен и возрастов из текста: '{text}'. Верни JSON."<br><br>ПРОБЛЕМА:<br>Ответ модели часто выглядит так: "Конечно, вот JSON: [{'name': 'Ivan', 'age': 30}]"<br><br>ВАША ЗАДАЧА:<br>1. Напишите улучшенный промпт, который гарантирует чистый JSON.<br>2. Добавьте в промпт инструкцию, которая заставит модель проверить саму себя перед выводом (Self-Correction/Reflection), даже если это неявно.

Вопрос

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