Перейти к содержанию

3.3 Механизм генерации квиза ИИ

Эта глава собирает воедино рабочий генератор квизов на базе ИИ: мы настраиваем окружение и доступ к внешним сервисам, готовим датасет с предметами/категориями/фактами, проектируем промпт так, чтобы вопросы точно соответствовали выбранной категории, и связываем всё в пайплайн на LangChain. Начинаем с подготовки окружения и ключей; для чистоты вывода несущественные предупреждения можно подавить.

# Импортируем библиотеку warnings для управления предупреждающими сообщениями
import warnings

# Игнорируем все предупреждающие сообщения для обеспечения чистого вывода во время выполнения
warnings.filterwarnings('ignore')

# Загружаем API-ключи для сторонних сервисов, используемых в проекте
from utils import get_circle_ci_api_key, get_github_api_key, get_openai_api_key

# Получаем отдельные API-ключи для CircleCI, GitHub и OpenAI
circle_ci_api_key = get_circle_ci_api_key()
github_api_key = get_github_api_key()
openai_api_key = get_openai_api_key()

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

# Определяем шаблон для структурирования вопросов квиза
quiz_question_template = "{question}"

# Инициализируем банк квизов с предметами, категориями и фактами
quiz_bank = """
Вот три новых вопроса квиза, следующих заданному формату:

1. Предмет: Исторический конфликт  
   Категории: История, Политика  
   Факты:  
   - Начался в 1914 году и закончился в 1918 году  
   - Вовлек два крупных альянса: Союзники и Центральные державы  
   - Известен обширным использованием окопной войны на Западном фронте  

2. Предмет: Революционная коммуникационная технология  
   Категории: Технологии, История  
   Факты:  
   - Изобретена Александром Грэхемом Беллом в 1876 году  
   - Революционизировала дальнюю связь  
   - Первые переданные слова были "Мистер Уотсон, идите сюда, я хочу вас видеть"  

3. Предмет: Знаковая американская достопримечательность  
   Категории: География, История  
   Факты:  
   - Подарена Соединенным Штатам Францией в 1886 году  
   - Символизирует свободу и демократию  
   - Расположена на острове Свободы в гавани Нью-Йорка  
"""

Чтобы вопросы были релевантны выбранной пользователем категории, проектируем подробный шаблон промпта: от выбора категории через банк предметов до формулирования вопросов в установленном формате.

# Определяем разделитель для разделения различных частей промпта квиза
section_delimiter = "####"

# Создаем детальный шаблон промпта, направляющий ИИ для генерации пользовательских квизов
quiz_generation_prompt_template = f"""
Инструкции для генерации пользовательского квиза:
Каждый вопрос разделен четырьмя хештегами, т.е. {section_delimiter}

Пользователь выбирает категорию для своего квиза. Убедитесь, что вопросы релевантны выбранной категории.

Шаг 1:{section_delimiter} Определите выбранную пользователем категорию из следующего списка:
* Культура
* Наука
* Искусство

Шаг 2:{section_delimiter} Выберите до двух предметов, которые соответствуют выбранной категории из банка квизов:

{quiz_bank}

Шаг 3:{section_delimiter} Создайте квиз на основе выбранных предметов, формулируя три вопроса на предмет.

Формат квиза:
Вопрос 1:{section_delimiter} <Вставьте вопрос 1>
Вопрос 2:{section_delimiter} <Вставьте вопрос 2>
Вопрос 3:{section_delimiter} <Вставьте вопрос 3>
"""

С этим шаблоном переходим к LangChain: формируем ChatPrompt, выбираем модель и парсер, чтобы приводить ответ к читаемому виду.

# Импортируем необходимые компоненты из LangChain для структурирования промптов и взаимодействия с ИИ-моделью
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser

# Преобразуем детальный промпт генерации квиза в структурированный формат для ИИ
structured_chat_prompt = ChatPromptTemplate.from_messages([("user", quiz_generation_prompt_template)])

# Выбираем языковую модель для генерации вопросов квиза
language_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# Настраиваем парсер вывода для преобразования ответа ИИ в читаемый формат
response_parser = StrOutputParser()

Теперь связываем всё через expression language LangChain в единый конвейер, получая воспроизводимую генерацию.

# Соединяем структурированный промпт, языковую модель и парсер вывода для формирования пайплайна генерации квиза
quiz_generation_pipeline = structured_chat_prompt | language_model | response_parser

# Выполняем пайплайн для генерации квиза (пример выполнения не показан)

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

Краткий обзор: generate_quiz_assistant_pipeline универсальна и позволяет подставлять разные шаблоны и конфигурации (модели/парсеры). Определение функции:

from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser

def generate_quiz_assistant_pipeline(
    system_prompt_message,
    user_question_template="{question}",
    selected_language_model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    response_format_parser=StrOutputParser()):
    """
    Собирает компоненты, необходимые для генерации квизов через процесс на базе ИИ.

    Параметры:
    - system_prompt_message: Сообщение, содержащее инструкции или контекст для генерации квиза.
    - user_question_template: Шаблон для структурирования пользовательских вопросов, по умолчанию простой заполнитель.
    - selected_language_model: ИИ-модель, используемая для генерации контента, с указанной моделью по умолчанию.
    - response_format_parser: Механизм для парсинга ответа ИИ-модели в желаемый формат.

    Возвращает:
    Пайплайн LangChain, который при выполнении генерирует квиз на основе предоставленного системного сообщения и пользовательского шаблона.
    """

    # Создаем структурированный чат-промпт из системного и пользовательского сообщений
    structured_chat_prompt = ChatPromptTemplate.from_messages([
        ("system", system_prompt_message),
        ("user", user_question_template),
    ])

    # Собираем чат-промпт, языковую модель и парсер вывода в единый пайплайн
    quiz_generation_pipeline = structured_chat_prompt | selected_language_model | response_format_parser

    return quiz_generation_pipeline

Практическое использование. Функция скрывает сложность сборки компонентов: достаточно вызвать generate_quiz_assistant_pipeline с нужными аргументами, чтобы генерировать квизы по темам/категориям и легко встраивать это в более крупные системы. Несколько практических советов:

  • Настройка — используйте параметры для гибкой настройки процесса.
  • Выбор ИИ-модели — экспериментируйте с моделями по качеству/креативности.
  • Дизайн шаблонов — продумывайте user_question_template и system_prompt_message.
  • Обработка ошибок — учитывайте лимиты API и неожиданные ответы.

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

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

Теперь про оценку содержимого. Функция интегрируется с пайплайном генерации: принимает системное сообщение (инструкции/контекст), конкретный запрос (например, квиз по теме) и список ожидаемых слов/фраз, которые должны встретиться в результате. Определение функции:

def evaluate_quiz_content(
    system_prompt_message,
    quiz_request_question,
    expected_keywords,
    user_question_template="{question}",
    selected_language_model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    response_format_parser=StrOutputParser()):
    """
    Оценивает сгенерированный контент квиза, чтобы убедиться, что он включает ожидаемые ключевые слова или фразы.

    Параметры:
    - system_prompt_message: Инструкции или контекст для генерации квиза.
    - quiz_request_question: Конкретный вопрос или запрос для генерации квиза.
    - expected_keywords: Список слов или фраз, которые должны быть включены в контент квиза.
    - user_question_template: Шаблон для структурирования пользовательских вопросов, с заполнителем по умолчанию.
    - selected_language_model: ИИ-модель, используемая для генерации контента, с указанной моделью по умолчанию.
    - response_format_parser: Механизм для парсинга ответа ИИ-модели в желаемый формат.

    Вызывает:
    - AssertionError: Если ни одно из ожидаемых ключевых слов не найдено в сгенерированном контенте квиза.
    """

    # Используем функцию-помощник для генерации контента квиза на основе предоставленного вопроса
    generated_content = generate_quiz_assistant_pipeline(
        system_prompt_message,
        user_question_template,
        selected_language_model,
        response_format_parser).invoke({"question": quiz_request_question})

    print(generated_content)

    # Проверяем, что сгенерированный контент включает хотя бы одно из ожидаемых ключевых слов
    assert any(keyword.lower() in generated_content.lower() for keyword in expected_keywords), \
        f"Ожидалось, что сгенерированный квиз будет содержать одно из '{expected_keywords}', но этого не произошло."

Рассмотрим пример: генерируем и оцениваем квиз по науке.

# Определяем системное сообщение (или шаблон промпта), конкретный вопрос и ожидаемые ключевые слова
system_prompt_message = quiz_generation_prompt_template  # Предполагается, что эта переменная определена ранее в вашем коде
quiz_request_question = "Generate a quiz about science."
expected_keywords = ["renaissance innovator", "astronomical observation tools", "natural sciences"]

# Вызываем функцию оценки с параметрами тестового случая
evaluate_quiz_content(
    system_prompt_message,
    quiz_request_question,
    expected_keywords
)

Этот пример показывает, как с помощью evaluate_quiz_content убедиться, что в научном квизе есть релевантные темы (фигуры, инструменты, понятия). Полезные практики:

  • Выбор ключевых слов — они должны быть достаточно конкретны, но оставлять пространство для вариативности.
  • Широкая проверка — используйте несколько наборов ключей для разных тем.
  • Итеративный подход — улучшайте шаблон/параметры/датасет по результатам оценки.

Структурированное тестирование помогает поддерживать качество и находить точки улучшения релевантности и вовлеченности.

Чтобы обработать запросы вне области компетенции, введём функцию evaluate_request_refusal, которая тестирует корректный отказ в неуместных сценариях. Это важно для доверия и пользовательского опыта (UX): функция симулирует случаи, когда система должна отказать (по критериям релевантности/ограничений), и проверяет, что вернётся ожидаемое сообщение об отказе. Определение функции:

def evaluate_request_refusal(
    system_prompt_message,
    invalid_quiz_request_question,
    expected_refusal_response,
    user_question_template="{question}",
    selected_language_model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
    response_format_parser=StrOutputParser()):
    """
    Оценивает ответ системы, чтобы убедиться, что она корректно отказывает в выполнении недействительных или неприменимых запросов.

    Параметры:
    - system_prompt_message: Инструкции или контекст для генерации квиза.
    - invalid_quiz_request_question: Запрос, который система должна отклонить.
    - expected_refusal_response: Ожидаемый ответ, указывающий на отказ системы в выполнении запроса.
    - user_question_template: Шаблон для структурирования пользовательских вопросов, с заполнителем по умолчанию.
    - selected_language_model: ИИ-модель, используемая для генерации контента, с указанной моделью по умолчанию.
    - response_format_parser: Механизм для парсинга ответа ИИ-модели в желаемый формат.

    Вызывает:
    - AssertionError: Если ответ системы не содержит ожидаемого сообщения об отказе.
    """

    # Изменяем порядок параметров, чтобы он соответствовал ожидаемому порядку в `generate_quiz_assistant_pipeline`
    generated_response = generate_quiz_assistant_pipeline(
        system_prompt_message,
        user_question_template,
        selected_language_model,
        response_format_parser).invoke({"question": invalid_quiz_request_question})

    print(generated_response)

    # Проверяем, что ответ системы содержит ожидаемое сообщение об отказе
    assert expected_refusal_response.lower() in generated_response.lower(), \
        f"Ожидалось, что система откажет с сообщением '{expected_refusal_response}', но получен ответ: {generated_response}"

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

# Определяем системное сообщение (или шаблон промпта), запрос, который должен быть отклонен, и ожидаемое сообщение об отказе
system_prompt_message = quiz_generation_prompt_template  # Предполагается, что эта переменная определена ранее в вашем коде
invalid_quiz_request_question = "Generate a quiz about Rome."
expected_refusal_response = "I'm sorry, but I can't generate a quiz about Rome at this time."

# Выполняем функцию оценки отказа с указанными параметрами
evaluate_request_refusal(
    system_prompt_message,
    invalid_quiz_request_question,
    expected_refusal_response
)

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

  • Четкие сообщения об отказе: Разрабатывайте четкие и информативные сообщения об отказе, помогающие пользователям понять, почему их запрос не может быть выполнен.
  • Всестороннее тестирование: Используйте различные тестовые сценарии, включая запросы по неподдерживаемым темам или форматам, чтобы тщательно оценить логику отказа системы.
  • Уточнение и обратная связь: На основе результатов тестирования дорабатывайте логику отказа и сообщения для повышения понимания и удовлетворенности пользователей.
  • Учитывайте пользовательский опыт: Хотя отказ иногда необходим, рассмотрите возможность предоставления альтернативных предложений или рекомендаций для поддержания позитивного взаимодействия с пользователем.

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

Для адаптации предоставленного шаблона к практическому тестовому сценарию, сфокусированному на создании квиза по научной тематике, разработана функция test_science_quiz. Эта функция призвана оценить, действительно ли сгенерированные ИИ-вопросы квиза сосредоточены вокруг ожидаемых научных тем или предметов. Интегрируя функцию evaluate_quiz_content, мы можем убедиться, что квиз включает определенные ключевые слова или темы, характерные для научной категории.

Наконец, адаптируем evaluate_quiz_content для научного тест‑кейса: функция проверяет, соответствует ли сгенерированный контент ожидаемым научным темам. Определение функции для тестирования научного квиза:

def test_science_quiz():
    """
    Тестирует способность генератора квизов создавать вопросы, связанные с наукой, проверяя включение ожидаемых предметов.
    """
    # Определяем запрос для генерации вопроса квиза
    question_request = "Generate a quiz question."

    # Список ожидаемых ключевых слов или предметов, которые указывают на соответствие квиза научным темам
    expected_science_subjects = ["physics", "chemistry", "biology", "astronomy"]

    # Системное сообщение или шаблон промпта, настроенный для генерации квиза
    system_prompt_message = quiz_generation_prompt_template  # This should be defined earlier in your code

    # Вызываем функцию оценки с параметрами, специфичными для науки
    evaluate_quiz_content(
        system_prompt_message=system_prompt_message,
        quiz_request_question=question_request,
        expected_keywords=expected_science_subjects
    )

Эта функция инкапсулирует логику проверки: при запросе по науке контент должен содержать ожидаемые научные темы/ключевые слова; вызов test_science_quiz имитирует запрос и проверяет наличие научных тем — важный индикатор корректной генерации. Уточняйте список ключевых слов под домен и охват тем, расширяйте покрытие тестами для других категорий (история/география/искусство) и анализируйте неудачи: сопоставляйте ожидания с результатом и улучшайте логику/датасет. Структурированное тестирование помогает поддерживать качество и находить точки улучшения релевантности и вовлечённости.

Напоследок — взгляд на CI/CD: файл .circleci/config.yml в корне репозитория описывает пайплайн (сборка/тестирование/развёртывание) в YAML. Ниже — эскиз для Python‑проекта с автотестами:

version: 2.1

orbs:
  python: circleci/python@1.2.0  # Use the Python orb to simplify your config

jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.8  # Specify the Python version
    steps:
      - checkout  # Check out the source code
      - restore_cache:  # Restore cache to save time on dependencies installation
          keys:
            - v1-dependencies-{{ checksum "requirements.txt" }}
            - v1-dependencies-
      - run:
          name: Install Dependencies
          command: pip install -r requirements.txt
      - save_cache:  # Cache dependencies to speed up future builds
          paths:
            - ./venv
          key: v1-dependencies-{{ checksum "requirements.txt" }}
      - run:
          name: Run Tests
          command: pytest  # Or any other command to run your tests

workflows:
  version: 2
  build_and_test:
    jobs:
      - build-and-test

Ключевые элементы: version — версия конфигурации (обычно 2.1); orbs — переиспользуемые блоки, здесь python помогает с настройкой сред; jobs — набор задач, у нас единая build-and-test; docker — образ для запуска (например, cimg/python:3.8); steps — последовательность действий (checkout, кэш, установка зависимостей, тесты); workflows — связывает задачи в общий процесс и запускает их по правилу.

Для настройки под себя: подберите версию Python в docker, замените pytest на свою команду тестов и добавьте дополнительные шаги (БД, переменные среды и т. п.) как новые - run:. После коммита файла .circleci/config.yml интегрированный CircleCI обнаружит конфигурацию и будет запускать пайплайн при каждом коммите в соответствии с правилами.

Теоретические вопросы:

  1. Каковы необходимые компоненты для настройки среды генератора квизов на базе ИИ?
  2. Опишите, как структурировать набор данных для генерации вопросов квиза. Включите примеры категорий и фактов.
  3. Как инженерия промптов влияет на генерацию кастомизированных квизов? Приведите пример шаблона промпта.
  4. Объясните роль LangChain в структурировании промптов для обработки ИИ-моделью.
  5. Что составляет пайплайн генерации квиза в контексте использования expression language LangChain?
  6. Как можно обеспечить релевантность и точность сгенерированного контента квиза с помощью функций оценки?
  7. Опишите метод тестирования способности системы отказывать в генерации квиза при определенных условиях.
  8. Как можно протестировать сгенерированные ИИ-вопросы квиза на соответствие ожидаемым научным темам или предметам?
  9. Опишите основные компоненты файла конфигурации CircleCI для проекта на Python, включая автоматический запуск тестов.
  10. Обсудите важность кастомизации файла конфигурации CircleCI для соответствия специфическим потребностям проекта.

Практические задания:

  1. Создайте датасет квиза: Определите словарь Python под названием quiz_bank, который представляет собой коллекцию вопросов для квиза, каждый из которых содержит предметы, категории и факты, аналогичные приведенному примеру. Убедитесь, что ваш словарь обеспечивает легкий доступ к предметам, категориям и фактам.

  2. Генерация вопросов квиза с использованием промптов: Разработайте функцию generate_quiz_questions(category), которая принимает категорию (например, "История", "Технологии") в качестве входных данных и возвращает список сгенерированных вопросов для квиза на основе предметов и фактов из словаря quiz_bank. Используйте строковые операции или шаблонные строки для конструирования вопросов квиза.

  3. Реализация структурирования промптов LangChain: Имитируйте процесс использования возможностей LangChain, определив функцию structure_quiz_prompt(quiz_questions), которая принимает список вопросов для квиза и возвращает структурированный чат-промпт в формате, аналогичном описанному, без фактической интеграции LangChain.

  4. Пайплайн генерации квиза: Создайте функцию Python под названием generate_quiz_pipeline(), которая имитирует создание и выполнение пайплайна генерации квиза, используя заполнители для компонентов LangChain. Функция должна выводить сообщение, имитирующее выполнение пайплайна.

  5. Повторно используемая функция генерации квизов: Реализуйте функцию Python generate_quiz_assistant_pipeline(system_prompt_message, user_question_template="{question}"), которая имитирует сборку компонентов, необходимых для генерации квизов. Используйте форматирование строк для конструирования детального промпта на основе входных данных.

  6. Оценка сгенерированного контента квиза: Напишите функцию evaluate_quiz_content(generated_content, expected_keywords), которая принимает сгенерированный контент квиза и список ожидаемых ключевых слов в качестве входных данных и проверяет, содержит ли контент какое-либо из ключевых слов. Вызовите ошибку утверждения с пользовательским сообщением, если ни одно из ключевых слов не найдено.

  7. Обработка неверных запросов квиза: Разработайте функцию evaluate_request_refusal(invalid_request, expected_response), которая имитирует оценку ответа системы на неверный запрос квиза. Функция должна проверять, соответствует ли сгенерированный ответ об отказе ожидаемому ответу об отказе.

  8. Science Quiz Evaluation Test: Develop a Python function test_science_quiz() that uses the evaluate_quiz_content function to test if a generated science quiz includes questions related to expected scientific topics, such as "physics" or "chemistry".