Synthetic Data Generation skill for Codex

Test and eval data with coverage you chose. Codex picks the generator by purpose: seeded Faker for fixtures, an explicit attribute grid for LLM-written eval cases, and SDV for stand-ins of sensitive tables. Every record is schema-validated, near-duplicates are removed, coverage is reported per grid cell, a human spot check is built in, and each dataset ships with a manifest recording model, prompt hash and seed so it can be regenerated.

  • AI & LLM Engineering
  • $synthetic-data-generation
  • 176 lines
  • 2 files
  • ~1,900 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- synthetic-data-generation

Installs to ~/.agents/skills/synthetic-data-generation/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $synthetic-data-generation, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Generates synthetic data for tests and LLM evals - seeded Faker fixtures, schema-valid records from Pydantic or JSON Schema, LLM-generated eval cases driven by an explicit attribute grid for coverage, adversarial and edge cases, deduplication by n-gram and embedding similarity, validation and labeling passes, and tabular synthesizers such as SDV with fidelity and privacy checks. Use when you need test fixtures, an eval or red-team set before real traffic exists, more examples for a rare class, or a shareable stand-in for a production table that contains personal data.

Use it when

  • You need an eval or red-team set before real traffic exists
  • Tests need deterministic, schema-valid fixtures
  • A classifier lacks examples for a rare class
  • A sensitive table needs a shareable synthetic stand-in

Use something else when

  • You only need factories and builders inside a test suite: use test-data-factories
  • You are designing graders and regression gates rather than data: use llm-evals

How to check the result

All records pass the production schema, the per-cell coverage report has no empty cells, near-duplicates are under the cut-off, and a human sample of 30-50 records is mostly rated realistic.

The mistake it prevents

Asking an LLM for '100 diverse examples' with no coverage grid, which yields near-copies of a few patterns and an eval that measures nothing.

~/.agents/skills/synthetic-data-generation/SKILL.md

--- name: synthetic-data-generation description: Generates synthetic data for tests and LLM evals - seeded Faker fixtures, schema-valid records from Pydantic or JSON Schema, LLM-generated eval cases… license: MIT ---

Synthetic data is useful when it is reproducible (seeded, versioned), covers the cases you chose on purpose rather than whatever the generator likes, is validated against the same schema as real data, and is labelled as synthetic everywhere it lands. It never replaces a real held-out test set for claiming production quality.

Pick the generator by purpose

PurposeGeneratorNotes
Unit and integration test fixturesFaker + factories (see test-data-factories)Seeded, deterministic, no network
Property-based edge casesHypothesis / fast-check (see property-based-testing)Shrinks failures automatically
Database seed for dev/stagingFaker + domain rules (see database-seeding)Referential integrity first
LLM eval or red-team casesLLM with an attribute gridNeeds dedup, validation, human spot check
Rare-class augmentation for a classifierLLM paraphrase of real examplesTrain/dev only, never test
Stand-in for a sensitive production tableSDV or similar synthesizerMeasure fidelity and privacy before sharing

Seeded fixtures (no LLM)

from faker import Faker
import random

Faker.seed(20260101)          # class-level seed: same output on every run
random.seed(20260101)
fake = Faker(["en_US", "de_DE"])

def customer(i: int) -> dict:
    return {
        "id": f"cus_{i:06d}",
        "name": fake.name(),
        "email": f"user{i}@example.test",     # reserved test domain, never a real address
        "country": random.choice(["US", "DE"]),
        "created_at": fake.date_time_between("-2y", "now").isoformat(),
    }
  • Use example.com, example.test and documented test card numbers; Faker can emit plausible real-looking emails and phone numbers that belong to someone.
  • Validate every generated record with the production schema (Pydantic Model.model_validate, JSON Schema, DB constraints). Generators drift from schemas.
  • Write the seed and generator version into the output metadata.

LLM-generated eval cases

Never ask “generate 100 diverse test questions”. You get 100 variations of the same three. Define the coverage yourself as an attribute grid, then generate per cell.

import itertools, json

GRID = {
    "intent": ["refund", "cancel", "upgrade", "bug_report", "account_access"],
    "persona": ["new user", "power user", "angry customer", "non-native speaker"],
    "difficulty": ["direct", "ambiguous", "multi-intent", "missing key detail"],
    "channel": ["chat", "email"],
}
cells = [dict(zip(GRID, combo)) for combo in itertools.product(*GRID.values())]
# 160 cells; sample if too many, but sample cells, not free-form prompts.

PROMPT = """Write one realistic customer message for a SaaS support inbox.
Intent: {intent}. Persona: {persona}. Difficulty: {difficulty}. Channel: {channel}.
Return JSON: {{"message": str, "expected_intent": str, "notes": str}}.
Do not mention the persona or difficulty explicitly. Vary length and tone."""

Rules:

  • Use structured outputs for the record shape; reject anything that fails validation.
  • Generate with a different model (or at least a different prompt) from the system under test, or the eval rewards the generator’s own style.
  • Store the grid cell with each record so you can report quality per slice.
  • Seed with 10-20 real anonymized examples in the prompt when available; it anchors realism (typos, length, jargon).
  • Generate expected answers separately from inputs, and have a human verify a sample. LLM-written gold labels inherit the generator’s mistakes.

Edge and adversarial cases

Add these deliberately, as their own grid dimension or set:

  • Empty, whitespace, very long (near the context limit), mixed language, emoji, RTL text.
  • Malformed inputs: truncated JSON, wrong encodings, HTML fragments.
  • Prompt-injection attempts in user content and in retrieved documents (see prompt-injection-defense for the attack catalogue).
  • Out-of-scope requests the system must refuse or route.

Dedup and quality filters

def shingles(text: str, n: int = 5) -> set[str]:
    toks = text.lower().split()
    return {" ".join(toks[i:i + n]) for i in range(max(1, len(toks) - n + 1))}

def jaccard(a: set, b: set) -> float:
    return len(a & b) / len(a | b) if a | b else 1.0
  1. Exact dedup on normalized text.
  2. Near-dup: drop pairs with 5-gram Jaccard above ~0.6, or embedding cosine above ~0.92 (tune by eyeballing 20 pairs around the cut-off).
  3. Validate schema and business rules; drop failures, log the rate. Over ~10% failures means the prompt is wrong.
  4. Diversity report: counts per grid cell, length distribution, distinct-n (unique n-grams / total). Fill empty cells with targeted generation.
  5. Human review: 30-50 random records, scored realistic / unrealistic / wrong label.

Tabular synthesizers

SDV (pip install sdv, needs network for install; check its Business Source License terms for commercial use):

from sdv.metadata import Metadata
from sdv.single_table import GaussianCopulaSynthesizer
from sdv.evaluation.single_table import run_diagnostic, evaluate_quality

metadata = Metadata.detect_from_dataframe(data=df, table_name="orders")
metadata.validate()                         # fix detected types and primary keys by hand
synth = GaussianCopulaSynthesizer(metadata)
synth.fit(df)
fake = synth.sample(num_rows=10_000)

run_diagnostic(df, fake, metadata)          # structural validity
evaluate_quality(df, fake, metadata)        # column shapes and pair trends
  • Drop direct identifiers (names, emails, ids) before fitting; regenerate them with Faker.
  • Privacy check before sharing: for each synthetic row, distance to the closest real row. Exact or near-exact copies of real rows mean the model memorized; do not share. Rare categories (a single customer in a small town) re-identify people; bucket them first.
  • Synthetic tabular data is not anonymization under GDPR by default. Have the data protection owner approve before it leaves the controlled environment.

Workflow

  • Read AGENTS.md; find schemas, existing fixtures and factory helpers
  • State the purpose and pick the generator from the table
  • Define coverage (grid, edge cases) and record seeds and versions
  • Generate in batches with structured output; validate every record
  • Dedup, diversity report, fill gaps
  • Human spot check; fix prompt or rules; regenerate
  • Write to a versioned file (evals/data/support_v3.jsonl) with a README line on how it was made
  • Mark records "synthetic": true

Output contract

Each dataset ships with a manifest:

{
  "name": "support_intents_v3",
  "purpose": "eval",
  "records": 640,
  "generator": {"model": "<id>", "prompt_sha": "<hash>", "seed": 20260101},
  "grid": {"intent": 5, "persona": 4, "difficulty": 4, "channel": 2},
  "filters": {"schema_fail": 21, "near_dup": 37},
  "human_review": {"sampled": 40, "unrealistic": 3, "wrong_label": 1},
  "created": "2026-10-28"
}

Gotchas

  • Synthetic eval sets overestimate quality: they are cleaner and more on-topic than real traffic. Report them separately from real-data results.
  • Training on LLM-paraphrased test items leaks the test set. Keep generation inputs and test items disjoint.
  • Unseeded Faker in tests makes failures unreproducible.
  • Large generation runs cost real money; estimate tokens per record times count and confirm with the user before generating more than a few thousand LLM records.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Synthetic Data Generation"
  short_description: "Test and eval data with coverage you chose"
  default_prompt: "$synthetic-data-generation Generate an eval set of 300 realistic support messages covering every intent and some tricky edge cases"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem