generate calls an OpenAI-compatible endpoint to synthesise {instruction, input, output} rows from a recipe; dedup merges parallel batches across files with one shared index; validate filters them with research-backed QC checks. All three are YAML-driven, like praisonai-train llm.
Quick Start
Generate a dataset
Validate and filter
Fine-tune on the clean dataset
llm reference.How It Works
generate fans a recipe’s diversity axes into teacher prompts, streams unique rows to JSONL, and validate scores each row against pluggable QC checks.
generate
Synthesise {instruction, input, output} rows from a recipe, streamed to a JSONL file with dedup, snapshots, and a stop-file circuit-breaker.
CLI flags
YAML-only keys
These have no CLI flag — set them in the config file.Behaviour worth knowing
output+num_examplesare validated up-front — missing either exits1witherror: 'output' and 'num_examples' (or --num) are required.- The output file is only truncated after the first row arrives — a run that fails on credentials, recipe, or the first request never wipes an existing file, and a self-referential
dedup_fromis read before it would be emptied. - Dedup is on-write and cross-run — the normaliser hashes
instruction; rows whose normalised instruction is already seen (fromdedup_fromor earlier in the run) are silently skipped. - Zero unique rows exits
1witherror: no rows generated — check endpoint/api_key/deployment and provider JSON-mode support. - Progress bar: when
tqdmis importable the CLI shows a live bar (generating: 42/1000 [00:12<04:38] req kept=39); otherwise it prints...500/1000 requests (487 kept)every 500 requests.tqdmstays optional — the import is guarded. - Per-row provenance — each emitted row carries
model(the teacher deployment), plustask_typeandtopicwhen the recipe stamps them (built-intamiland any_AxisRecipesubclass do). Downstream QC and category splits key off this metadata instead of brittle text detection. Backward compatible — keys are added only when values are available (see Per-row provenance).
Example config
Mirrorsetup/generate.yaml.
Python API — generate_dataset()
Import and drive the generator yourself.
Iterator[dict] of {"instruction", "input", "output"} — plus model, task_type, and topic when available (see Per-row provenance). The caller drives the run by iterating.
progress_callback guarantees (verified by the SDK tests): done is monotonic non-decreasing, kept is monotonic non-decreasing, kept <= done always, final done == total, and final kept == len(rows). When duplicates collapse everything, done still reaches total but kept plateaus. Keep the callback cheap — it runs on the hot path. progress_callback=None preserves the original behaviour exactly.Continuation guard
Halt a run from inside the loop when a billing or quota check flips — a belt-and-suspenders companion to the external stop-file circuit-breaker.should_continue is a fn() -> bool re-checked every sponsor_check_rows completed requests. When it returns False, the run halts via the same clean shutdown as the stop-file: touch the stop-file → cancel pending futures → break.
The guard is resolved in a fixed order.
- Fail-OPEN —
azure_sponsorship_guardreturnsTrueon any transient error (az CLI missing, network blip, unparseable output). It never halts a healthy run on a flaky check. Pair it with an external watchdog for hard budget enforcement. - Trigger row is emitted, not dropped — the just-completed row is yielded before the guard runs, so the paid, already-finished request that trips the check is never silently lost.
sponsor_check_rowscoercion —0(divide-by-zero) and negative (never-poll) values are corrected to1;Nonekeeps the5000default.
should_continue.
azure_sponsorship_guard(subscription_id, quota_id="Sponsored_2016-01-01") returns a fn() -> bool that is True while the subscription still reads as sponsored, and fail-OPEN on any error.
Per-row provenance
Emitted rows are self-describing: the generator stampsmodel (from deployment) and passes task_type / topic through from the recipe when present.
tamil, CustomRecipe, or any _AxisRecipe subclass) stamps task_type and topic automatically, so QC and category splits key off metadata instead of brittle text detection.
Recipes
tamil is the built-in recipe — a Tamil-language instruction recipe crossing five diversity axes (task × topic × style × audience × variant), registered automatically at import.
Define one inline (YAML or dict) without writing Python — pass recipe: as a dict with system, template, and axes (the first two axis keys are the task × topic grid). The template receives {task}, {topic}, {style}, {audience}, {variant} at format time.
--recipe spanish (or recipe: spanish in YAML) works from the CLI.
Any _AxisRecipe recipe (including tamil and inline CustomRecipe) automatically stamps task_type (its first axis) and topic (its second axis) onto every emitted row — see Per-row provenance. That metadata is what makes the task-aware QC checks and the metrics.translation by-direction breakdown reliable: they resolve direction from authoritative labels rather than brittle text detection. For the Tamil recipe, the EN_TA_TASK axis value becomes the stamped task_type on translation rows.
validate
Quality-check an instruction dataset — dedup, boilerplate/refusal, script purity, truncation, restates-question — plus dataset-level diversity metrics. Thresholds mirror Self-Instruct / LIMA / AlpaGasus / Deita defaults.
CLI flags
YAML-only keys
Checks
Drop checks remove a row; flag checks keep it but count it.Report
validate prints a text report to stdout.
metrics.translation.share is the fraction of kept rows that are translation tasks; by_direction counts them by direction (en_ta, ta_en, unknown); exempted is the number of English-target rows the task-aware exemption actually rescued from the low_script_purity drop. Because both the exemption and the metric use the same detector (translation_intent), the numbers always reconcile.
Malformed JSONL lines are skipped with a ⚠ skipping malformed JSON on line N warning — the run still succeeds on the remaining rows.
Example config
Mirrorsetup/validate.yaml.
Task-aware script purity
A naive script-purity filter deletes legitimate translation outputs written in the target script — a Tamil→English row’s correct output is English, so a Tamil-purity check would wrongly drop it. Only a resolved English target exempts a row from the Tamil-purity drop.en→ta, ambiguous, and native rows keep the strict thresholds.
The detector resolves intent in a fixed order: ground-truth task_type metadata is authoritative → instruction-text keyword fallback for external rows → an ambiguous direction yields no exemption (the safe default). That task_type metadata is now stamped automatically by any axis recipe (not just the Tamil recipe’s EN_TA_TASK task), so freshly-generated corpora resolve direction from authoritative labels out of the box.
task_aware_purity: false restores strict purity behaviour (English translation outputs are dropped again). verify_translation_target: false disables the wrong_target_script flag while leaving the exemption on.Translation composition metric
metrics.translation reports {share, by_direction, exempted} — the corpus-level view of how much of the kept dataset is translation.
The metric uses the same translation_intent() detector as the per-row exemption, so the numbers can never disagree with the checks they describe.
Python API — score() / filter_rows()
Run the same QC from Python.
distinct_2 < 0.5, length_cv < 0.4, or top_prefix_share > 0.01 — the “healthy dataset” bar.
translation_intent() is the shared detector behind both task-aware purity and the translation metric — call it directly to inspect a single row’s intent.
Custom row checks
Register aRowCheck to add a QC rule — no core change.
score() / validate run automatically.
Cross-file dedup
Merge many JSONL batch files into one deduped file with a single shared MinHash+LSH index — the cross-file duplicates that per-filevalidate (whose dedup state resets each call) always misses.
You generated N batches in parallel with disjoint --start-offset values. cat-ing them and re-running validate still leaves near-duplicates across files. dedup catches them because one index spans every file.
CLI flags
YAML-only keys
Behaviour worth knowing
- Atomic write — the command writes to a sibling temp file and does
os.replace()on success, so an--outthat aliases an input is never truncated before its rows are read, and a malformed line never leaves a partial file. - Exact-dup key = sha1 of the normalised
instruction + input + output, removed first; near-dup runs on the survivors. - Missing path raises
FileNotFoundError: dedup source not found: PATH— a clear error, not a crypticAttributeError. - Report prints at the end:
Which engine to pick
dedup defaults to minhash; validate/score default to sliding. Pick by scale and intent.
Example config
Python API — global_dedup()
Deduplicate rows across many JSONL paths or in-memory row lists with one shared exact + LSH index — streaming unique rows in source order.
FileNotFoundError: dedup source not found: PATH.
Python API — near_dedup() and MinHashLSH
Drop near-duplicate rows by instruction over either engine — this is what validate’s near-dup pass now delegates to.
MinHashLSH(threshold=0.7, num_perm=128, n=4, seed=1) is the pure-stdlib index behind both (no datasketch / numpy).
near_dup_method defaults to "sliding" in score/validate but "minhash" in global_dedup/dedup. Existing validate configs are unchanged unless you opt in. An unknown method raises ValueError("unknown near_dup_method 'X'; use 'sliding' or 'minhash'").Common Patterns
Disjoint parallel workers
Split a large run across two shells with disjoint--start-offset values, then dedup on both output files.
Merge parallel batches
Two shells generate disjoint slices, then onededup command merges and cross-dedups them before validate.
Live progress in a notebook
Pass aprogress_callback to drive a UI in Jupyter or Streamlit.
Circuit-breaker
Halt a rogue run without losing kept rows — queued requests cancel, in-flight ones drain. Two routes trip the same clean shutdown:- External / manual — touch the stop-file from another shell.
- In-loop / automatic — a
should_continueguard (or an auto-wired Azure subscription id) halts the run when a billing or quota check flips, without you touching anything. See Continuation guard.
Custom recipe for another language
Pointrecipe: at an inline dict — no Python needed. See the Spanish example under Recipes.
Audit a translation corpus
Readmetrics.translation before fine-tuning to verify how much of the corpus is translation and in which direction.
Best Practices
Use dedup_from across re-runs so cost never doubles
Use dedup_from across re-runs so cost never doubles
dedup_from. Their normalised instructions load into the seen set before the run starts, so a re-run only pays for genuinely new rows.Set snapshot_every on multi-hour runs
Set snapshot_every on multi-hour runs
--snapshot-every 1000 copies the output to snapshots/{stem}_{n}.jsonl every N rows. A crash then costs at most N rows, not the whole run.Use near_dup_method: minhash on 100k+ datasets
Use near_dup_method: minhash on 100k+ datasets
near_dup_window) and structurally blind to near-dups more than near_dup_window rows apart. The MinHash + LSH engine has no bounded look-back window and scales sub-linearly. --no-near-dup is still available for exact-dedup-only runs.Merge parallel batches with dedup, not manual concatenation
Merge parallel batches with dedup, not manual concatenation
praisonai-train generate with disjoint --start-offset values still produces near-duplicates across files. praisonai-train dedup batch_*.jsonl --out merged.jsonl removes both exact and near cross-file dups with a shared MinHash+LSH index; cat-ing the files then re-running validate cannot (its dedup state is rebuilt per call).Keep progress_callback cheap
Keep progress_callback cheap
Match script_range to your language
Match script_range to your language
[2944, 3071]). Set script_range to your language’s Unicode block so low_script_purity and script_review measure the right script.Prefer task_type metadata over text detection
Prefer task_type metadata over text detection
_AxisRecipe recipe now stamps task_type automatically — not just Tamil translation rows — so the detector resolves direction from authoritative metadata instead of the instruction-text fallback. When you build a recipe outside the axis base class, stamp task_type yourself to keep this signal.Set sponsor_check_rows low enough to catch sponsorship flips fast
Set sponsor_check_rows low enough to catch sponsorship flips fast
5000. On a fast teacher endpoint that can still be many minutes and dollars of overrun before the guard fires. Match the interval to your acceptable overrun — lower for expensive endpoints.Treat the guard as belt-and-suspenders, not the hard billing stop
Treat the guard as belt-and-suspenders, not the hard billing stop
azure_sponsorship_guard fails OPEN on transient errors (missing az CLI, network blip), so it never halts a healthy run on a flaky check — but that also means it can miss a real flip during an outage. Run an external watchdog too for hard budget enforcement.Split datasets by task_type / topic instead of re-detecting
Split datasets by task_type / topic instead of re-detecting
task_type and topic, so downstream evaluation and category splits can group by these metadata fields directly — no text-heuristic classifier needed.Keep verify_translation_target on for translation datasets
Keep verify_translation_target on for translation datasets
wrong_target_script — failed translations whose output is still in the source script. A naive purity filter keeps these silently because the wrong-script output still passes a same-script purity check.Related
praisonai-train Package
Train CLI
Multi-GPU Training
Checkpointing
Speed Benchmark
generate.
