Synthetic Data Generation
Run synthetic data generation (SDG) with a registered adapter, a source dataset version, and a target dataset. Keep the adapter and project in the same workspace.
An SDG adapter is a named, versioned, workspace-scoped registration of a generation pipeline. Registering an adapter lets the platform invoke your pipeline by id; a run then binds one adapter to a source version, a target dataset, and a project. Registering an adapter and running it are done through the SDK (aip.register_sdg_adapter, aip.run_sdg) or REST (/sdg/adapters, /sdg/adapters/{id}/runs); the full parameter list is documented below.
Registration is Python-only. The simplest form wraps a custom pipeline class that implements run() and validate_chain(). Pass the class, not an instance. workspace_id is optional and auto-resolves from your membership, but pinning it explicitly matters on a multi-workspace server — an adapter registered into the wrong workspace makes a later run fail with Project '…' does not belong to the adapter's workspace. In the call below, pipeline receives the class itself rather than an instance, and workspace_id is pinned so the adapter and any project bound to it stay in the same workspace:
import aip_sdk as aip
class MySDGPipeline:
def run(self, prompt, n=10):
return [f"sample_{i}" for i in range(n)]
def validate_chain(self):
return True
adapter = aip.register_sdg_adapter(
name="adversarial-gen",
pipeline=MySDGPipeline,
version="1.0",
owner="data-team",
workspace_id=ws.id,
)
print(adapter.id, adapter.name, adapter.version, adapter.workspace_id)
for a in aip.list_sdg_adapters(workspace_id=ws.id):
print(a.id, a.name, a.version, a.owner or "—")
The two applied pipelines below are declarative JobConfig graphs from aip_sdg_core rather than hand-written classes. A JobConfig chains typed nodes — a seed input, one or more generation/augmentation steps, and an export sink — and is registered by posting its serialised form to /sdg/adapters with an explicit workspace_id. Use the configured client so the call carries your session, and keep the adapter and its target project in one workspace by passing workspace_id:
client = aip._context.get_default_client()
adapter = client.post(
"/sdg/adapters",
json={
"name": f"my-sdg-{int(time.time())}",
"version": "1.0",
"workspace_id": ws.id,
"job_config": sdg_job.model_dump_json(),
},
)
print("SDG adapter:", adapter["id"])
Every run is the same three-argument shape regardless of pipeline: which adapter, which seed version to read, and which dataset to register the output onto. The arguments map directly: source_dataset_version_id is the governed seed rows, and target_dataset_id is where the augmented version lands. aip.run_sdg() enqueues the run and returns a handle you poll to completion — the loop below waits until the run reaches a terminal state:
sdg_run = aip.run_sdg(
adapter_id=adapter["id"],
source_dataset_version_id=seed_version.id,
target_dataset_id=target_ds.id,
project_id=project.id,
)
_TERMINAL = {"completed", "failed", "cancelled"}
while sdg_run.status not in _TERMINAL:
time.sleep(4)
sdg_run.refresh()
print("SDG run status:", sdg_run.status)
The same run can be triggered over HTTP:
curl -sS -X POST "$AIP_API_URL/sdg/adapters/<adapter_id>/runs" \
-H "Authorization: Bearer $AIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"project_id":"<project>","target_dataset_id":"<dataset>","source_dataset_version_id":"<version>"}'
When the run completes it registers a new version on the target dataset. Detect it by diffing the version list before and after, then read its lineage and provenance — lineage_parent_id points back at your seed, and the dataset metadata records what generated it and how many rows it produced. In the snippet below, pre_ids captures the versions that existed before the run; you run SDG as shown above, then whatever id is new is the version the worker just registered:
pre_ids = {v.id for v in (aip.datasets.list_versions(target_ds.id) or [])}
new = [v for v in aip.datasets.list_versions(target_ds.id) if v.id not in pre_ids]
augmented = max(new, key=lambda v: v.version) if new else None
print(augmented.stage, augmented.lineage.get("lineage_parent_id"))
For complete examples with their prerequisites, use the synthetic data tutorial.
Advanced: object-detection generative pipelines
Object-detection SDG pipelines go further than the register/run/collect workflow above: a full pipeline chains selection, generative augmentation, multi-metric quality control, calibration, and a top-k selection terminal into one DAG. A representative shape:
seed → present → feasible → replicate → aug_0..aug_3 → pooled
→ silhouette → depth → composite (calibrator) → topk → out
seedreads a governed dataset version; the worker resolvessource_dataset_version_idand patches it onto this node at trigger time — don't bake a path into the config.presentandfeasibleare selection nodes: they drop seeds the scenario can't realistically apply to (e.g. an object-presence check, then a VLM-backed counterfactual-feasibility check).replicatefans one seed out into N augmentation branches (aug_0..aug_3), each a generative edit (e.g. FLUX) with its own prompt template.pooledmerges the branches back;silhouetteanddepthare quality-control nodes that each score one dimension of the edit against the original.compositeis a calibrator that fuses the QC scores (e.g. via a rank aggregator) into one per-seed ranking signal.topkis the terminal: it keeps the best-ranked variant(s) per seed before theoutnode exports GDI rows for registration.
Every GPU-bound module in this chain maps to a deployed ZeroGPU Hugging Face
Space, addressed via HFSpaceBackendConfig(space_id=...) on that node's
config — the worker delegates execution to the Space instead of needing a
local GPU. A few representative mappings:
| Config class | Module | Space ID |
|---|---|---|
CounterfactualFeasibilityConfig | selection | resaro/aip-sdg-counterfactual-feasibility |
FluxKleinConfig | augmentation | resaro/aip-sdg-flux-klein |
DepthMatchQualityControlConfig | QC | resaro/aip-sdg-depth-match |
SilhouetteMatchQualityControlConfig | QC | resaro/aip-sdg-silhouette-match |
CPU-only modules (perturbations like MotionBlurConfig, IsoNoiseConfig)
need no backend — they run in-process on the worker.
To compare prompt phrasings instead of keeping a single best variant per
seed, swap the topk terminal for PickBestPathConfig — this changes the
pipeline from "pick the best augmentation" to "compare augmentation
strategies against each other" using the same upstream selection, generation,
and QC chain.