
We are building a synthetic data generator that expands a small set of labeled customer support queries into a structured training dataset for intent classification. It is useful when you need to bootstrap a classifier for a new product area and do not have months of historical tickets to draw from. The entire pipeline runs against Oxlo.ai and outputs standard JSONL ready for fine-tuning.
What you'll need
Before starting, make sure you have the following ready.
- Python 3.10 or newer installed locally.
- An Oxlo.ai API key from https://portal.oxlo.ai.
- The OpenAI SDK installed with
pip install openai.
Step 1: Initialize the Oxlo.ai client
I keep my API key in an environment variable during local development, but the snippet below uses the placeholder so you can see where it fits. Oxlo.ai is a flat per-request provider, which means I can stuff these prompts with long few-shot contexts and not watch the meter spin on input tokens. That matters when you are iterating on a prompt ten times an hour. There are also no cold starts on popular models, so the first request in a loop fires immediately instead of hanging for seconds. For this pipeline I picked llama-3.3-70b as my default workhorse because it balances instruction following with speed, but you could swap in qwen-3-32b or kimi-k2.6 if you need multilingual reasoning or advanced chain-of-thought.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
Step 2: Prepare seed examples
Good augmentation starts with diverse seeds. I picked three intents and wrote three realistic queries per intent, varying tone from casual to formal. I keep them in a plain Python list so I never need a database just to prototype. The key is to cover edge cases in the seeds, because the model will extrapolate from them. If your seeds are all short, the generations will trend short too. If you only have one example per intent, the model will likely parrot it, so I always aim for at least two or three distinct phrasings before I generate.
SEED_EXAMPLES = [
{"text": "My password reset link expired, can you send a new one?", "label": "account_recovery"},
{"text": "I forgot my login details and cannot access my dashboard.", "label": "account_recovery"},
{"text": "The reset email never arrived, can you check if my address is valid?", "label": "account_recovery"},
{"text": "How do I change my subscription from Pro to Basic?", "label": "billing"},
{"text": "Why was I charged twice this month?", "label": "billing"},
{"text": "I need an invoice for last quarter for my finance team.", "label": "billing"},
{"text": "The API returns a 504 timeout every time I batch upload.", "label": "technical_issue"},
{"text": "Your webhook stopped firing after yesterday's deploy.", "label": "technical_issue"},
{"text": "CORS errors appear in the browser when I call the sandbox endpoint.", "label": "technical_issue"},
]
UNIQUE_LABELS = list({ex["label"] for ex in SEED_EXAMPLES})
Step 3: Build the augmentation prompt
The system prompt does the heavy lifting. I tell the model exactly what format to return, how many samples to generate, and how to vary the language. I also pin the label names so the output stays consistent with my downstream schema. I force JSON mode through the API so I do not have to write brittle regex parsers. The user prompt simply formats the relevant seeds for one label at a time. I call per label so I can parallelize later with a thread pool if I scale this beyond a handful of intents.
SYSTEM_PROMPT = """You are a training data augmentation assistant. Your job is to read a set of labeled examples and generate new synthetic examples that match the same intent labels but use different wording, sentence structures, and levels of detail.
Rules:
- Preserve the exact label names.
- Vary vocabulary. Do not copy phrases verbatim from the seed examples.
- Include a mix of short and long queries.
- Output strictly as a JSON object with key "samples" containing a list of objects. Each object must have "text" and "label" keys.
- Generate exactly 5 new samples per provided label.
Seed examples will be provided by the user."""
def build_user_prompt(seeds, target_label):
examples = "\n".join(
[f'- {ex["text"]} -> {ex["label"]}' for ex in seeds if ex["label"] == target_label]
)
return (
f"Generate 5 synthetic variants for the '{target_label}' intent "
f"based on these seed examples:\n{examples}"
)
Step 4: Generate synthetic variations
Now I define the generation function. I use llama-3.3-70b because it follows JSON instructions reliably and rarely drifts from the requested schema. If I were working with longer context seeds, such as full email threads, I might switch to kimi-k2.6 for its 131K context window. Since Oxlo.ai pricing is request-based, the cost is the same whether I send three seed examples or thirty. That flat rate makes it practical to iterate on prompt length without surprise bills. I ask for exactly five samples per call so I can eyeball quality per intent before committing to a larger batch.
import json
def generate_variations(client, seeds, label, system_prompt):
user_message = build_user_prompt(seeds, label)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
return json.loads(raw).get("samples", [])
Step 5: Deduplicate and validate
Generated data can contain near-duplicates or hallucinated labels. I run a quick deduplication pass and enforce an allow-list of labels before anything hits disk. This step is cheap insurance. A single mislabeled example in your training set can nudge a classifier more than you would expect.
def deduplicate_and_validate(samples, valid_labels):
seen = set()
clean = []
for item in samples:
text = item["text"].strip()
label = item["label"]
if text not in seen and label in valid_labels:
seen.add(text)
clean.append({"text": text, "label": label})
return clean
Step 6: Export the augmented dataset
I write the results to JSONL because every training framework, from Hugging Face to OpenAI fine-tuning, accepts it without fuss. Each line is a self-contained JSON object, so I can stream the file if I ever scale to millions of rows. I also print a count so I know immediately if a generation call silently failed.
def save_dataset(samples, filename="augmented_intents.jsonl"):
with open(filename, "w") as f:
for item in samples:
f.write(json.dumps(item) + "\n")
print(f"Saved {len(samples)} samples to {filename}")
Run it
I wrap the logic in a main block so I can import the module elsewhere without side effects. Running the script produces the augmented dataset and prints a preview. The entire run consumes exactly three API requests, one per intent, so forecasting cost is trivial. If you want more samples, increase the count in the system prompt or loop over the labels multiple times and let the deduplicator catch overlaps.
if __name__ == "__main__":
all_samples = []
for label in UNIQUE_LABELS:
batch = generate_variations(client, SEED_EXAMPLES, label, SYSTEM_PROMPT)
all_samples.extend(batch)
print(f"Generated {len(batch)} samples for {label}")
clean = deduplicate_and_validate(all_samples, UNIQUE_LABELS)
save_dataset(clean)
print("\nPreview:")
for row in clean[:3]:
print(row)
Example output:
Generated 5 samples for account_recovery
Generated 5 samples for billing
Generated 5 samples for technical_issue
Saved 15 samples to augmented_intents.jsonl
Preview:
{'text': 'I need a fresh password reset link because the previous one expired.', 'label': 'account_recovery'}
{'text': 'Can you downgrade my plan from Pro to Basic?', 'label': 'billing'}
{'text': 'Batch uploads are consistently timing out with a 504 error.', 'label': 'technical_issue'}
Next steps
You now have a reproducible augmentation pipeline. Two concrete ways to extend it. First, add a quality filter by running the generated samples through a second LLM pass on Oxlo.ai that scores semantic relevance to the intent, dropping anything below a threshold. Second, move from intent classification to something richer, like multi-turn dialogue augmentation for customer support agents, using deepseek-v3.2 or kimi-k2.6 to generate realistic back-and-forth threads. Both are natural fits for Oxlo.ai because long context windows and request-based pricing keep the cost predictable even when prompts grow.


