Принудительный вывод JSON (JSON Mode) и работа со схемами
Введение: Почему неструктурированный текст — это проблема
Приветствую! В этом уроке мы переходим к одной из самых мощных возможностей Gemini 3 API — Controlled Generation или управляемой генерации вывода.
Давайте честно: сколько раз вы писали промпты вроде «Пожалуйста, верни ответ в формате JSON, не пиши ничего лишнего, только JSON»? И сколько раз модель всё равно добавляла в начале: «Конечно! Вот ваш JSON: ...» или, что хуже, допускала синтаксическую ошибку, забывая закрыть скобку?
В Enterprise-разработке такая нестабильность недопустима. Если вы строите конвейер обработки данных, где выход одной нейросети идет на вход API или базы данных, вам нужна гарантия структуры. Раньше для этого использовали сложные регулярные выражения (Regex) и библиотеки для «исправления» битого JSON.
Gemini 3 меняет правила игры. Теперь модель не просто «старается» вернуть JSON. Мы можем принудительно переключить её режим декодирования так, чтобы она физически не могла сгенерировать токен, нарушающий заданную схему.
Режим JSON (JSON Mode) и MIME-типы
Самый простой способ получить структурированные данные — использовать параметр response_mime_type. Указав тип application/json, вы сообщаете модели, что результат должен быть валидным JSON-объектом.
Однако, просто включить этот режим недостаточно. В самом промпте вы всё равно должны объяснить модели, какую именно структуру вы ожидаете. Без схемы модель вернет валидный JSON, но ключи в нём могут быть случайными (hallucinated keys).
Рассмотрим базовый пример конфигурации.
import os
import google.generativeai as genai
# Настройка API ключа
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
# Инициализация модели
model = genai.GenerativeModel("gemini-1.5-pro-latest")
# Промпт
prompt = """
Составь список из 3 популярных языков программирования.
Для каждого укажи название, год создания и уровень сложности (низкий, средний, высокий).
"""
# Базовый вызов с принудительным JSON
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json"
)
)
print(response.text)
Работа со схемами (Response Schema)
Предыдущий пример хорош, но он не детерминирован. Ключи могут быть на английском, на русском, или вообще меняться от запроса к запросу. Для Enterprise-решений нам нужен Контракт данных.
В Gemini API вы можете передать параметр response_schema. Это позволяет жестко зафиксировать типы данных. Вы можете описывать схему вручную через словари Python, но современный стандарт индустрии — использование библиотеки Pydantic. Она позволяет описывать структуры данных как классы Python, обеспечивая валидацию типов «из коробки».
Gemini SDK умеет принимать классы Pydantic напрямую в response_schema. Это невероятно удобно, так как вам не нужно писать громоздкие JSON-схемы вручную.
import typing_extensions as typing
from pydantic import BaseModel, Field
# Определение структуры данных с помощью Pydantic
class Ingredient(BaseModel):
item: str = Field(description="Название ингредиента")
amount: str = Field(description="Количество с единицами измерения")
class Recipe(BaseModel):
name: str = Field(description="Название блюда")
# Используем typing.List для вложенных структур
ingredients: typing.List[Ingredient]
steps: typing.List[str]
calories: int = Field(description="Примерная калорийность")
is_vegetarian: bool
# Промпт
prompt = "Как приготовить классическую пасту карбонара?"
# Вызов с передачей схемы
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=Recipe # Передаем класс Pydantic напрямую
)
)
print(response.text)
Тонкости использования Enums (Перечислений)
Частая задача в бизнесе — классификация. Например, анализ тональности отзыва (Позитивный, Негативный, Нейтральный) или категоризация тикета в техподдержку.
Если оставить это поле как просто str (строка), модель может написать «Позитивно», «Хороший отзыв», «Огонь» и т.д. Это усложняет фильтрацию в базе данных. Используя Enum, мы ограничиваем модель конкретным набором значений. Модель буквально не сможет сгенерировать слово, которого нет в вашем списке (за исключением редких галлюцинаций, но механизм Constrained Decoding сводит их к минимуму).
import enum
# Определение Enums для жесткого контроля значений
class TicketPriority(enum.Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class TicketCategory(enum.Enum):
BILLING = "billing"
TECHNICAL = "technical"
FEATURE_REQUEST = "feature_request"
class SupportTicket(BaseModel):
summary: str = Field(description="Краткое описание проблемы на основе текста")
priority: TicketPriority
category: TicketCategory
sentiment_score: float = Field(description="Оценка настроения от 0.0 до 1.0")
user_complaint = """
У меня уже третий день не проходит оплата картой!
Я пробовал разные браузеры. Это блокирует работу всего отдела.
Срочно почините или мы уйдем к конкурентам!
"""
response = model.generate_content(
user_complaint,
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=SupportTicket
)
)
# Результат гарантированно будет содержать валидные значения Enum
print(response.text)
Лучшие практики и подводные камни
Несмотря на мощь этого инструмента, есть нюансы, о которых должен знать опытный разработчик:
- Описания полей (Docstrings/Field descriptions): Обратите внимание, что в Pydantic моделях я использую
Field(description="..."). Это не просто комментарии для программиста. Эти описания передаются в модель! Они служат частью промпта, объясняя семантику поля. Хорошее описание поля резко повышает качество извлечения данных. - Сложность схемы: Gemini 3 отлично справляется с глубокой вложенностью, но старайтесь не делать схемы чрезмерно глубокими (более 5-6 уровней). Это увеличивает нагрузку на контекстное окно и может привести к потере фокуса модели.
- Обработка ошибок: Хотя
response_mime_type="application/json"гарантирует синтаксис JSON, он не всегда гарантирует логическую целостность данных. Всегда валидируйте полученный JSON через тот же Pydantic (метод.model_validate_json()), чтобы убедиться, что типы данных совпадают (например, что в полеageдействительно число, а не строка "двадцать").
Создайте парсер данных из резюме. <br><br>1. Определите Pydantic модели для структуры `Resume`.<br>2. Структура должна включать: `full_name` (строка), `years_of_experience` (число), `skills` (список строк), и `education` (объект с полями `degree` и `university`).<br>3. Напишите код, который принимает неструктурированный текст резюме и возвращает JSON.<br>4. Важно: Поле `years_of_experience` должно автоматически вычисляться моделью на основе дат в тексте, если прямо не указано.
При использовании Pydantic схемы в generation_config, какую роль играют параметры Field(description='...')?