We are going to build a synthetic data generator that expands a small set of labeled customer support tickets into a full training dataset for intent classification. This is useful when you need to bootstrap a classifier but only have a handful of trusted examples per label.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Oxlo.ai uses flat per-request pricing, so generating long synthetic examples costs the same regardless of prompt length. See https://oxlo.ai/pricing for details.
1. Configure the client and seed data
I start by pointing the OpenAI SDK at Oxlo.ai. I also define a small seed dataset with three intents: Refund Request, Account Access, and Technical Bug. These are the only real examples the generator will see.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY")
)
SEEDS = {
"Refund Request": [
"I was charged twice for my subscription last month. Can I get a refund?",
"My order arrived damaged. I need my money back.",
],
"Account Access": [
"I forgot my password and the reset email is not arriving.",
"My account got locked after too many failed login attempts.",
],
"Technical Bug": [
"The export button crashes the app every time I click it.",
"Images are not loading on the dashboard after the latest update.",
],
}
2. Design the augmentation prompt
The system prompt does the heavy lifting. It tells the model to act as a data augmentation assistant, to vary sentence structure and vocabulary, and to return only a JSON object with a generated_examples array.
SYSTEM_PROMPT = """You are a data augmentation assistant. Your job is to generate synthetic training examples for a text classifier.
Rules:
- Read the provided label and seed examples.
- Generate new examples that belong to the same intent but use different wording, length, and style.
- Do not copy phrases directly from the seeds.
- Vary user tone: polite, frustrated, urgent, casual.
- Return ONLY a JSON object in this format: {"generated_examples": ["example 1", "example 2", ...]}.
- Generate exactly the requested number of examples.
"""
3. Generate examples for one intent
I write a helper that builds the user message from the label and seeds, then calls Llama 3.3 70B through Oxlo.ai. I use JSON mode so parsing is trivial.
import json
def generate_examples(intent: str, seeds: list[str], n: int) -> list[str]:
user_msg = (
f"Intent: {intent}\n"
f"Seed examples:\n" + "\n".join(f"- {s}" for s in seeds) + "\n\n"
f"Generate {n} new synthetic examples."
)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
response_format={"type": "json_object"},
temperature=0.9,
)
raw = response.choices[0].message.content
data = json.loads(raw)
return data.get("generated_examples", [])
4. Scale to all labels
Now I loop over every intent, request twenty synthetic examples for each, and collect them into a list of records. Because Oxlo.ai has no cold starts on popular models, the loop runs without delays between requests.
import time
def augment_dataset(seeds: dict[str, list[str]], per_label: int = 20):
records = []
for intent, examples in seeds.items():
print(f"Generating for {intent} ...")
batch = generate_examples(intent, examples, per_label)
for text in batch:
records.append({"text": text.strip(), "label": intent})
time.sleep(0.5) # polite rate limit padding
return records
augmented = augment_dataset(SEEDS, per_label=20)
print(f"Generated {len(augmented)} total examples.")
5. Validate and deduplicate
Synthetic data can drift. I run a quick validation pass to remove exact duplicates and any output that is suspiciously short or that exactly matches a seed.
def clean_records(records: list[dict], seeds: dict[str, list[str]]):
seed_set = {s.lower().strip() for vals in seeds.values() for s in vals}
seen = set()
cleaned = []
for r in records:
text = r["text"]
key = text.lower().strip()
if len(text) < 10 or key in seed_set or key in seen:
continue
seen.add(key)
cleaned.append(r)
return cleaned
final_data = clean_records(augmented, SEEDS)
print(f"After cleaning: {len(final_data)} examples.")
6. Export the dataset
I write the final set to a JSONL file that any training framework can ingest. Each line is a standalone JSON object with text and label fields.
import json
with open("augmented_intents.jsonl", "w", encoding="utf-8") as f:
for r in final_data:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
# Preview
for r in final_data[:4]:
print(f"[{r['label']}] {r['text']}")
Run it
Putting the pieces together, the script generates, cleans, and exports the data in one pass. Here is what the output looks like when I run it against Oxlo.ai.
if __name__ == "__main__":
augmented = augment_dataset(SEEDS, per_label=20)
final_data = clean_records(augmented, SEEDS)
with open("augmented_intents.jsonl", "w", encoding="utf-8") as f:
for r in final_data:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print("\nSample output:")
for r in final_data[:6]:
print(f"{r['label']}: {r['text']}")
Example output:
Generating for Refund Request ...
Generating for Account Access ...
Generating for Technical Bug ...
After cleaning: 58 examples.
Sample output:
Refund Request: I need my subscription fee returned. I was billed twice in January and this is unacceptable.
Refund Request: The product I received is completely broken. Please process a full reimbursement immediately.
Account Access: I cannot log in and the password reset link is going to spam.
Account Access: My profile is locked. I tried signing in too many times and now I am blocked.
Technical Bug: Clicking the export button causes the application to freeze and close.
Technical Bug: Since the last release, thumbnails on the main dashboard fail to appear.
Next steps
Train a small classifier such as logistic regression or a lightweight transformer on the augmented JSONL file, and compare its F1 score against a model trained only on the original six seeds. If you need higher quality or multilingual coverage, swap the model ID to qwen-3-32b or kimi-k2.6 in the Oxlo.ai client and rerun the pipeline.
Top comments (0)