Cookbook

Fine-tune an extraction model with zero labeled transcripts

Generate the structured record first — as relational data in Tonic Fabricate — and render the conversation from it. The training labels are correct by construction and verified by machine, so a small model on Fireworks can learn a frontier-grade extraction task without a single hand-labeled example.

Updated August, 2026
0 min read
0 hand-labeled transcripts anywhere in the pipeline — labels are projections of generated structure
6,847 evidence quotes in the published dataset, each machine-verified as an exact transcript substring
300 realism-gated synthetic conversations for a 4B student to pass a 120B anchor on every metric

The problem

Care-coordination calls, telehealth visits, and post-discharge check-ins all end the same way: someone turns a conversation into a structured encounter record. Automating that with a frontier model works — at frontier prices, on every single call. A small fine-tuned model would be radically cheaper, but fine-tuning needs labeled transcripts, and that's where the plan usually dies:

  • Real transcripts are PHI — they can't leave the compliance boundary, and often can't be used at all.
  • Hand-labeling is slow, expensive, and inconsistent exactly where it matters — negation ("no chest pain"), family history ("my mother had heart failure"), medication changes ("I stopped taking it last week").
  • Model-generated labels inherit the teacher's mistakes, and you can't measure what you can't trust.

The approach: structure first, conversation second

Fabricate generates synthetic data schema-first. Instead of writing conversations and then labeling them, it authors the structured truth as relational tables — conditions, symptoms, medications, measurements, care-plan items, each with controlled vocabularies and per-fact directives (mention it explicitly, imply it, omit it, correct it mid-conversation, or plant it as an irrelevant distractor). The conversation is rendered from those rows. The extraction target is a deterministic projection of those rows.

1. Fabricate Scenario fact tables — controlled vocab, difficulty directives, distractors
2. Render Conversation generated from the rows, evidence quote per fact
3. Corrupt Seeded ASR-noise variant — deterministic, fact-safe
3. Project Target JSON = pure function of the fact tables. No model writes labels

No model ever writes a label. And because every fact carries an exact-substring evidence quote, label quality isn't asserted — it's machine-checked, fact by fact, before anything reaches training.

Verified, not just generated
"Correct by construction" still gets audited. Every batch passes four machine gates before anything reaches training: integrity (evidence quotes resolve exactly, controlled vocabularies hold, omitted facts stay omitted), parity (attribute densities and label distributions match between train and eval), realism (template-skeleton deduplication keeps dialogue genuinely conversational), and behavioral canaries (nested training tiers make any weak batch visible immediately). Generation quality is measured, never assumed.

One example, end to end

This is record t0801-clean from the published dataset's training split — a post-discharge check-in. Fabricate authored the facts first, with per-fact directives; the conversation was rendered to realize them. You can pull this exact record from the dataset and check every quote.

1. What Fabricate authors Fact rows + directives
Target facts for the extraction model: each medication or finding, its key attributes, and the directive the label encodes — including one distractor that appears in the transcript but never in the target.
Fact Key attributes Directive
naproxen 500 mg twice daily status current · action decrease · adherence adherent explicit — dose comes down; the patient hasn’t missed a day
ibuprofen 200 mg as needed action continue · adherence missed_doses explicit — stays as-is, adherence imperfect
amlodipine 10 mg once daily action refill · adherence unknown explicit — the patient genuinely can’t say; unknown stays unknown
fever assertion absent explicit — a negation probe
anemia assertion suspected explicit — raised, not diagnosed
carpal tunnel syndrome experiencer other distractor
2. What the renderer produces Conversation excerpt
CARE COORDINATOR: Before we dive into the heartburn piece, I want to just check in on a few other things. You haven't had any fever, right? PATIENT: No fever, thank goodness. I've been checking my temp because I had that cellulitis thing cleared up last year… My coworker actually just got diagnosed with carpal tunnel syndrome, so I've been hearing all about it, but thankfully that's not something I'm dealing with. PATIENT: Yes, I am. 500 mg twice daily, like you prescribed. I haven't missed a single day. CARE COORDINATOR: …the naproxen at that dose can work against us a bit. I'd like to adjust naproxen dose, bring it down from twice daily to once daily. How does that feel? PATIENT: I am, though I'll be honest—I've missed some doses. Sometimes I just forget… But yeah, 200 mg as needed when something really hurts. CARE COORDINATOR: That, combined with the mild dizziness, makes me want to rule out anemia. It's probably nothing, but I'm not convinced yet… We got your hemoglobin a1c at 6.2 %, which is good.
3. What training data sees Target = projection of the rows
{
  "conditions": [
    { "name": "anemia", "assertion": "suspected", "temporality": "current", "experiencer": "patient" },
    { "name": "cellulitis", "assertion": "resolved", "temporality": "historical", "experiencer": "patient" }
    ...
  ],
  "symptoms": [
    { "name": "fever", "assertion": "absent", "severity": null, ... },
    { "name": "heartburn", "assertion": "present", "severity": "severe", "onset": "a couple weeks ago", ... }
  ],
  "medications": [
    { "name": "naproxen", "dose": "500", "unit": "mg", "frequency": "twice daily", "status": "current", "action": "decrease", "adherence": "adherent" },
    { "name": "ibuprofen", "dose": "200", "unit": "mg", "frequency": "as needed", "status": "current", "action": "continue", "adherence": "missed_doses" },
    { "name": "amlodipine", "dose": "10", "unit": "mg", "frequency": "once daily", "status": "current", "action": "refill", "adherence": "unknown" }
  ],
  "measurements": [
    { "name": "hemoglobin a1c", "value": "6.2", "unit": "%", "reported_by": "clinician_verified" }
    ...
  ],
  "follow_up": [
    { "action_type": "medication_change", "description": "adjust naproxen dose", "committed": true }
    ...
  ]
}
Note what's not there: the coworker's carpal tunnel, and no invented values — the amlodipine adherence the patient couldn't confirm stays unknown, and anemia stays suspected, not diagnosed. A model that extracts the distractor or upgrades the suspicion is scored down — the traps are part of the curriculum.

How you drive Fabricate

The generation contract is a natural-language prompt to Fabricate's agent — it builds the schema, authors the rows, renders the conversations, and self-checks, autonomously. An excerpt from the actual pilot contract:

Generate clinical-conversation scenarios as relational data, then render
each as free-prose dialogue.

SCENARIO FACTS — author rows in controlled-vocabulary tables (conditions,
symptoms, medications, measurements, care-plan items, escalations). Every
fact carries a directive: explicit | indirect | omitted | corrected — plus
a distractor flag for third-party facts that must appear in dialogue but
never in the target.

DIALOGUE — free prose, one conversation at a time: 8–40 utterances, varied
openings and registers, patient rambling and self-repairs, facts realized
out of order. Templated or slot-filled rendering is forbidden, audited by
skeleton deduplication (<30% of utterances may share a recurring skeleton
after normalizing numbers and clinical entities).

REALIZATION — every non-omitted fact is spoken at its authored specificity:
formal diagnosis terms said at least once, measurement values said aloud,
medication dose/unit/frequency/adherence ≥95% spoken, symptom severity and
onset ≈80%. Attributes the dialogue doesn't state stay NULL. Negated facts
≈10%, phrased as varied natural denials.

EVIDENCE — every non-omitted fact links to an exact-substring quote from
one utterance, written at insert time.

SELF-CHECK before export: evidence substring failures = 0, enum violations
= 0, omitted-fact leaks = 0; report skeleton-duplication share, attribute
densities, and coverage against targets.

And the pipeline around it

# the label rule, in its entirety — no model ever writes a target
released = [f for f in facts if f.mention_style != "omitted" and not f.is_distractor]

# deterministic gates + variants + tiers + training
python validate.py clinical_transcripts.db   # evidence, leaks, enums, split integrity
python noise.py clean.jsonl noisy.jsonl      # seeded ASR variants, evidence-safe
python make_sft.py --pairs 250 --out tier250.jsonl
python train_tier.py tier250.jsonl --tier 250   # LoRA on Fireworks, ~15 min

Cookbook steps

1

First question: is fine-tuning even needed?

Before generating training data, the pipeline generates only an evaluation set (100 conversations plus 50 counterfactual minimal-pairs, rendered by a different model family than the training data) and runs a zero-shot ladder. Two gates decide whether the task is worth fine-tuning:

GATE G0 · TASK IS WELL-POSED — PASS
A frontier-class anchor (gpt-oss-120B) scores 0.95 schema-valid and 0.72 lenient slot-F1 on realistic dialogue with near-zero trap intrusion — the labels are consistent and the task is learnable, and genuinely hard.
GATE G1 · HEADROOM EXISTS — PASS
The small tunable student (Qwen3 4B) trails the anchor by 26 lenient F1 points zero-shot, with negation accuracy near zero. That gap is the fine-tuning opportunity.

Zero-shot extraction, 100 held-out conversations (clean)

Realism-gated eval (0% template skeletons) · machine-verified labels · reference run from the published cookbook, July 2026

gpt-oss-120B (anchor) Qwen3 4B (student, zero-shot)
0 0.25 0.5 0.75 1.0
Slot F1 matches extracted facts by section and normalized name; "strict" additionally requires every stated attribute (assertion, dose, status…) to be correct. Every number on this page comes from the published cookbook notebook — reproduce the full run in Colab
2

Second question: how much synthetic data does it take?

This is the number teams actually need — not "does SFT help" but how many synthetic conversations buy how much accuracy, at what cost. The pipeline trains one LoRA per nested data tier (each tier a superset of the last) and evaluates all of them from a single multi-LoRA Fireworks deployment against the frozen eval set.

Learning curve: Qwen3 4B vs training-set size

Slot F1 on 100 realism-gated held-out conversations · dashed lines = gpt-oss-120B anchor · reference run, July 2026

slot F1 (lenient) slot F1 (strict)
300 600 900 2,000 planned

Two findings carry the chart. First, 300 verified synthetic conversations put the 4B student past the 120B anchor on every metric — including strict F1 and negation — and 900 stretch the lenient-F1 lead to nine points (0.818 vs 0.724), with ASR-noisy audio within two points of clean. Second, the curve localizes the remaining work: strict attribute precision — getting every dose, status, and assertion simultaneously right — hovers at frontier-model level for every contender, tuned or not. It's the hard half of the task, and attribute-dense training data, not model scale, is the lever

The curve is also a debugger
Because the tiers are nested, the learning curve doubles as a data-quality instrument: train on strictly more data with everything else held fixed, and any dip in the curve indicts the newest batch of data, never the model. Together with the pre-training gates, this keeps label quality measurable end to end — including for the eval itself, which passes the same audits (0% template skeletons, independently verified).

Try it yourself

Notebook Run the full cookbook in Colab The generation contract, verification checks, zero-shot ladder, tiered fine-tuning, and the learning curve, end to end.
Dataset TonicAI/synthetic_clinical_conversations 1,200 records with machine-verified evidence and trap metadata, on Hugging Face.
Product Tonic Fabricate Schema-first synthetic data: relational scenario tables, controlled vocabularies, AI-rendered text — labels included.
Platform Fireworks AI Serverless frontier anchors, LoRA fine-tuning, and multi-adapter deployments used throughout this playbook.
All conversations, patients, and records in this playbook are fully synthetic — generated structure-first in Fabricate, with lineage and validation manifests for every example.