Генерация тестовых данных нейросетью: схема, валидация и фикстуры

Когда тестовые данные стоит генерировать нейросетью, а когда хватит Faker. Скрипт на Python с валидацией pydantic, батчами, фикстурами для pytest и расчётом стоимости.

5 мин · · разработка · автоматизация

Генерация тестовых данных нейросетью полезна там, где случайных строк мало: обращения в поддержку, отзывы, описания товаров, профили с согласованными полями. Ниже — скрипт на Python, который получает такие записи через API, проверяет каждую через pydantic, убирает дубликаты и сохраняет результат в фикстуру для тестов. Отдельно — как сделать данные повторяемыми и сколько это стоит.

Нейросеть или Faker

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

ЗадачаЧем лучше генерировать
ID, UUID, даты, номера, суммыFaker или свой код
Поля с жёстким форматом (ИНН, телефоны)Faker или свой код с проверкой контрольных сумм
Связный текст: обращения, отзывы, комментариинейросеть
Согласованные профили (возраст, профессия, тариф, история)нейросеть
Краевые случаи для парсеров (опечатки, смешанные языки, эмодзи)нейросеть по явному списку случаев

Практичный вариант — гибрид: служебные поля (id, даты, суммы) генерирует ваш код, а нейросеть отвечает только за содержательные. Так ID гарантированно уникальны, а тексты выглядят естественно.

Схема → запрос → валидация → фикстура

  1. Описать запись моделью pydantic: типы, диапазоны, допустимые значения.
  2. Попросить модель вернуть JSON-массив с этими полями.
  3. Достать массив из ответа и проверить каждую запись; невалидные отбросить.
  4. Убрать дубликаты, назначить ID своим кодом, сохранить в файл.

Ключ fetch_... создайте в кабинете Zukko и задайте в переменной ZUKKO_API_KEY. Зависимости: pip install openai pydantic.

bash
import json
import os
from typing import Literal

from openai import OpenAI
from pydantic import BaseModel, Field, ValidationError

client = OpenAI(base_url="https://api.zukko.pro/v1", api_key=os.environ["ZUKKO_API_KEY"])
MODEL = os.getenv("ZUKKO_MODEL", "glm-5.3-flash")
SEGMENTS = ["студенты", "пенсионеры", "предприниматели", "IT-специалисты", "родители с детьми"]


class Customer(BaseModel):
    first_name: str = Field(min_length=2, max_length=50)

Что важно в этом коде:

  • Только зарезервированные домены. Адреса на example.com, example.org, example.net зарезервированы для документации и примеров, поэтому тестовое письмо случайно не уйдёт реальному человеку. Регулярное выражение в модели отбрасывает всё остальное.
  • Устойчивый разбор. Модели иногда оборачивают JSON в markdown-блок или добавляют фразу перед ним. extract_json_array берёт текст от первой [ до последней ]; если массива нет, партия пропускается.
  • Валидация каждой записи. Pydantic проверяет типы, длины, диапазон возраста и Literal для тарифа. Невалидные записи отбрасываются, остальные из партии сохраняются.
  • ID и уникальность — на стороне кода. Модель не знает, что она сгенерировала в прошлой партии, поэтому дубли email убираются по множеству seen, а id назначается по порядку.
  • Сегменты. Разные сегменты в соседних запросах делают выборку разнообразнее, чем десять одинаковых запросов подряд.
  • Ограничение попыток. Цикл не уйдёт в бесконечность, если модель раз за разом возвращает мусор.

Повторяемость данных

Ответ модели меняется от запуска к запуску, и слово «seed» в промпте этого не исправит. Не стоит полагаться и на параметры сэмплирования вроде temperature: они не делают ответ детерминированным, а новые модели Claude могут отклонить нестандартное значение temperature ошибкой 400, поэтому в примерах для claude-* его лучше не передавать вовсе.

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

bash
import json
from pathlib import Path

import pytest

FIXTURE = Path(__file__).parent / "data" / "customers.json"


@pytest.fixture(scope="session")
def customers():
    return json.loads(FIXTURE.read_text(encoding="utf-8"))


def test_every_customer_has_ticket(customers):
    assert all(len(c["support_ticket"]) >= 20 for c in customers)

Так тесты не зависят от сети и баланса, работают в CI без ключа, а перегенерация данных становится осознанным действием с понятным diff в ревью.

Если нужно повторять один и тот же запрос во время отладки, у Zukko есть кэш ответов по явному заголовку: запрос с X-Zukko-Cache: true и точно таким же телом в течение 7 дней вернёт сохранённый ответ, а в заголовке ответа будет X-Zukko-Cache: hit. В OpenAI SDK заголовок передаётся через extra_headers, а заголовки ответа видны через with_raw_response:

bash
raw = client.chat.completions.with_raw_response.create(
    model=MODEL,
    max_tokens=4000,
    messages=[{"role": "user", "content": PROMPT.format(count=10, segment="студенты")}],
    extra_headers={"X-Zukko-Cache": "true"},
)
print(raw.headers.get("X-Zukko-Cache"))
response = raw.parse()

Подробнее о кэше и лимитах ключей — в документации.

Валидация через jsonschema

Если схема уже описана в JSON Schema (например, это контракт API), можно проверять записи библиотекой jsonschema вместо pydantic:

bash
import jsonschema

for record in records:
    try:
        jsonschema.validate(instance=record, schema=schema)
    except jsonschema.ValidationError as e:
        print("Запись отброшена:", e.message)

Ту же схему полезно вставить в промпт: модели проще следовать явному описанию полей, чем пересказу словами.

Сколько стоит и какую модель взять

Цены в Zukko за 1M токенов (вход/выход): glm-5.3-flash — $0.04 / $0.04, gpt-5.6-luna — $0.10 / $0.10, claude-sonnet-5 — $0.30 / $0.30. Актуальный список — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.

Пример: 1 000 записей партиями по 10 — это 100 запросов. Пусть промпт занимает 400 входных токенов, а ответ с десятью записями — 1 500 выходных.

  • glm-5.3-flash: 100 × (400 + 1 500) × $0.04 / 1 000 000 = $0.0076.
  • claude-sonnet-5: 100 × (400 + 1 500) × $0.30 / 1 000 000 = $0.057.

Отброшенные записи и повторные партии добавят несколько процентов к этим суммам. Какая модель даёт достаточно разнообразные и корректные данные для ваших тестов, проверьте на своих задачах: сгенерируйте по 50 записей двумя моделями и посмотрите, сколько отброшено валидацией и насколько тексты похожи друг на друга.

Частые вопросы

Как не получить реальные персональные данные?

Пишите в промпте «вымышленные», запрещайте знаменитостей и используйте только зарезервированные домены example.*, как в примере. Для телефонов и документов генерируйте значения своим кодом в заведомо тестовых диапазонах, а не просите модель.

Модель возвращает меньше записей, чем просили

Это нормально: часть записей отбрасывает валидация, а иногда модель обрезает ответ по max_tokens. Цикл в generate() добирает недостающее следующими партиями. Если ответы регулярно обрываются, уменьшите batch_size или увеличьте max_tokens.

Можно ли генерировать картинки или PDF?

Моделей для генерации изображений в каталоге Zukko нет. Для PDF и документов используйте библиотеки вроде ReportLab, а нейросеть — только для текста, который в них попадёт.

Как генерировать данные для нескольких связанных таблиц?

Сначала сгенерируйте родительские записи (клиенты) и назначьте им ID, затем передавайте в промпт для дочерних (заказы, обращения) короткий список ID и ключевых полей родителя. Связи проверяйте кодом после генерации. Формат запросов описан в справочнике chat completions.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.