Synthetic data has two honest jobs in a DS team: exercising pipelines with realistic fixtures and edge cases (nearly risk-free), and augmenting rare classes for training (where it can quietly poison your evaluation).
Tier: Core · Workflow area: Synthetic data & test fixtures
LLMs excel at schema-constrained generation and adversarial edge-case ideation; the craft is validation — proving synthetic rows are structurally valid, distributionally plausible, and never allowed to contaminate the test set.
Never use synthetic data in evaluation — evaluation is always on real data.
Encode the schema as Pydantic — the generator's hard boundary:
from pydantic import BaseModel, Field
from typing import Literal
from datetime import datetime
class SupportTicket(BaseModel):
ticket_id: str = Field(pattern=r"^TK-\d{8}$")
created_at: datetime
channel: Literal["email", "chat", "phone", "social"]
product_area: Literal["billing", "auth", "api", "mobile", "other"]
subject: str = Field(min_length=8, max_length=120)
body: str = Field(min_length=30, max_length=2000)
priority: Literal["p1", "p2", "p3", "p4"]
csat: int | None = Field(default=None, ge=1, le=5)
Split fields by generator. Identifiers, timestamps, and numeric draws go to Faker/NumPy (cheap, distribution-controlled); only semantic fields (subject, body) go to the LLM. Sending everything to the LLM wastes tokens and produces subtly non-random "random" numbers.
Generate semantic fields with distribution and diversity constraints:
Generate 40 support-ticket (subject, body) pairs for product_area=billing,
priority mix 10% p1 / 30% p2 / 60% p3-p4. Realism constraints: Indian and
international customer names and locales; mixed tone (angry, confused,
polite); include typos in ~20%; body must be consistent with subject and
priority (p1 = outage/blocked language). Vary length: 30% short (<50 words).
Output as a JSON array matching keys: subject, body, priority.
Avoid repeating sentence templates across items.
Validate every batch through the Pydantic model with a retry loop; log the rejection rate (rising means prompt and schema disagree).
Ideate edge cases adversarially:
You are attacking the ticket-ingestion pipeline described by this schema and
these transforms [paste pb-10 contract]. Generate 30 edge-case rows that are
SCHEMA-VALID but likely to break processing: emoji-only subjects, RTL text,
HTML/SQL fragments in body, body in Hindi/Tamil, max-length fields,
csat null vs missing, midnight/DST timestamps, duplicate ticket_ids across
batches. For each: the row + one line on what it tests.
Each edge case that breaks something graduates into a pb-10 regression test.
For rare-class augmentation, generate against the real minority profile. Give the LLM (or SDV, for numeric-heavy tables) the minority class's statistical profile and a few anonymized exemplars, generate candidates, then filter hard: schema-valid, passes the pb-10 validation suite, and — critically — a discriminator check: train a quick real-vs-synthetic classifier; AUC ≈ 1.0 means your rows are stylized caricatures that teach the model to detect synthetic-ness, not the class.
Enforce the containment rule. Synthetic rows are tagged with an is_synthetic column that survives every join; used in training folds only; never in validation folds, test sets, or reported metrics. Measure value the only honest way — train with and without synthetic rows, compare on untouched real-data test metrics, report both.
Document provenance. A datasheet per synthetic set: generator versions, prompts, seed, counts, validation pass rates, discriminator AUC, intended use. Undocumented synthetic data becomes indistinguishable from real data in a warehouse within a quarter — a governance incident, not a convenience.
Synthetic rows in the test set make every metric a lie — the containment rule (tag, train-only, real-data evaluation) is non-negotiable.
is_synthetic tag present and verified to survive the full pipeline.