Принудительный JSON (JSON Mode) и валидация схем Pydantic
Введение: От хаоса текста к структуре данных
Добро пожаловать в третий модуль. Если вы работали с LLM достаточно долго, вы наверняка сталкивались с этой болью: вы просите модель вернуть список книг в формате JSON, а она вежливо отвечает: «Конечно! Вот ваш JSON: ...».
Для человека это вежливость. Для вашего парсера — это катастрофа. Вам приходилось писать регулярные выражения (Regex), чтобы вырезать фигурные скобки, удалять лишний текст и надеяться, что внутри валидный синтаксис.
В этом уроке мы разберем, как Gemini 3 API решает эту проблему на архитектурном уровне. Мы переходим от «просьб» к «требованиям». Мы изучим два ключевых инструмента:
- JSON Mode — принудительный вывод в формате JSON.
- Controlled Generation (Schema Validation) — использование Pydantic для жесткого ограничения структуры ответа.
Это превращает LLM из чат-бота в надежный компонент бэкенда.
Базовый уровень: Включение JSON Mode
Самый простой способ получить JSON от Gemini — это явно указать response_mime_type в конфигурации генерации. Это сигнал модели: «Забудь о свободном тексте, мне нужен только data-формат».
Однако есть нюанс: даже при включенном режиме JSON, в самом промпте обязательно должно быть упоминание того, что вы ожидаете JSON. Если вы этого не сделаете, модель может войти в бесконечный цикл генерации пробелов или выдать ошибку.
Ключевые параметры:
response_mime_type="application/json"— переключает режим декодера.response_schema— (опционально) описывает структуру. Без этого параметра модель вернет какой-то валидный JSON, но структура останется на её усмотрение.
import google.generativeai as genai
import os
# Настройка API ключа
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-pro')
# Простой пример использования JSON Mode без строгой схемы
prompt = """
Составь список из 3 популярных языков программирования.
Укажи название, год создания и автора.
Выведи ответ в формате JSON.
"""
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json"
)
)
print(response.text)
# Результат гарантированно будет валидным JSON-строкой,
# которую можно сразу парсить через json.loads()
Продвинутый уровень: Pydantic и типизация
Просто получить JSON — это только полдела. Часто нам нужно, чтобы поля назывались определенным образом (например, created_at, а не creationDate), а типы данных соответствовали нашим ожиданиям (число было числом, а не строкой).
Здесь на сцену выходит Pydantic. Это стандарт де-факто в экосистеме Python для валидации данных. Gemini SDK умеет нативно работать с моделями Pydantic, преобразуя их в JSON Schema, которую понимает модель.
Зачем это нужно?
- Гарантия типов: Вы получите
intтам, где просилиint. - Документация: Описания полей (docstrings или
Field(description=...)) передаются модели как контекст, помогая ей понять, что именно писать в поле. - Отказ от парсинга: SDK может автоматически инстанцировать объекты вашего класса.
import typing_extensions as typing
from pydantic import BaseModel, Field
# 1. Определяем структуру данных, которую хотим получить
class Ingredient(BaseModel):
name: str = Field(description="Название ингредиента")
quantity: str = Field(description="Количество с единицами измерения, например '200 гр'")
calories: int = Field(description="Примерная калорийность этого количества")
class Recipe(BaseModel):
title: str = Field(description="Креативное название блюда")
ingredients: list[Ingredient]
difficulty: str = Field(description="Уровень сложности: Легко, Средне, Сложно")
prep_time_minutes: int
# 2. Передаем класс схемы в конфигурацию
# Обратите внимание: мы передаем сам класс, а не объект
response = model.generate_content(
"Придумай рецепт полезного завтрака из овсянки и ягод.",
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=Recipe
)
)
# 3. Работаем с результатом
import json
# Текст ответа — это JSON строка
data = json.loads(response.text)
print(f"Блюдо: {data['title']}")
# Можно валидировать через Pydantic для полной уверенности
recipe_obj = Recipe(**data)
print(f"Первый ингредиент: {recipe_obj.ingredients[0].name}")
Использование Enums для классификации
Один из самых мощных паттернов при работе со структурированным выводом — использование перечислений (Enum). Это заставляет модель выбирать значение из строго ограниченного списка. Это идеально подходит для задач классификации, сентимент-анализа или выбора действий агента.
Когда вы используете Enum в схеме Pydantic, Gemini видит допустимые значения и ограничивает свой вывод только ими. Это называется Constrained Decoding (ограниченное декодирование).
from enum import Enum
class Sentiment(str, Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
class CustomerFeedback(BaseModel):
raw_text: str
sentiment: Sentiment # Модель вынуждена выбрать одно из трех значений
priority_score: int = Field(description="Оценка срочности от 1 до 10")
tags: list[str] = Field(description="Список из 3 ключевых тегов")
# Пример вызова
prompt = """
Проанализируй отзыв:
'Купил этот тостер, а он сжег хлеб за 10 секунд! Ужасное качество сборки, хочу возврат!'
"""
response = model.generate_content(
prompt,
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema=CustomerFeedback
)
)
result = json.loads(response.text)
print(f"Sentiment: {result['sentiment']}") # Выведет: negative
Создайте систему анализа новостей. Вам нужно определить Pydantic-схему 'NewsAnalysis', которая будет извлекать из текста новости: 1) Заголовок (str), 2) Категорию (Enum: Политика, Технологии, Спорт, Экономика), 3) Список упомянутых персон (list[str]), 4) Флаг 'is_breaking_news' (bool). Напишите код запроса к модели для анализа короткого текста новости.
Тонкости и лучшие практики
При работе со структурированным выводом важно помнить несколько вещей, чтобы не столкнуться с неожиданным поведением:
- Вложенность: Gemini хорошо справляется с глубокой вложенностью, но старайтесь не делать схемы глубже 3-4 уровней. Слишком сложные схемы повышают риск галлюцинаций в структуре.
- Описания (Docstrings): Поле
descriptionвField()Pydantic критически важно. Это ваша инструкция для модели. Если поле называетсяscore, поясните: «Оценка от 1 до 5, где 5 — отлично». Без этого модель будет гадать. - Обработка ошибок: Хотя
response_mime_type="application/json"гарантирует синтаксическую корректность JSON, он не гарантирует логическую корректность данных. Всегда оборачивайте создание Pydantic-объекта в блокtry-except(ValidationError).
Какой параметр в generation_config является обязательным для того, чтобы Gemini гарантированно вернула ответ в формате JSON, а не просто текст?