Data Science

Training Data Methodology

How the dataset is structured, split, and validated

The dataset contains 170 synthetic phone conversations between a caller and a plumbing receptionist AI. These conversations are generated by a teacher model (Claude) and used to fine-tune smaller student models (Phi4-mini 3.8B, Mistral 7B, Gemma4 E2B 7B) via LoRA on Apple MLX.

Conversations: 170 Training examples: 922 Split: 75 / 12.5 / 12.5

Overview

The goal: compress Claude-quality phone conversation ability into models that run locally for free.

170
Conversations
922
Turn-level examples
11
Canonical phases

Data Format

Conversation Level

Each conversation is stored as a JSON object:

{
  "id": "train_001",
  "scenario": {
    "problem": "clogged_drain",
    "personality": "default",
    "path": "happy_path"
  },
  "turns": [
    {"role": "agent", "text": "Hi, Smith Plumbing...", "phase": "greeting"},
    {"role": "caller", "text": "Yeah, my sink is backed up.", "phase": "problem_determination"}
  ],
  "booking_details": { "service": "...", "date": "...", "time": "...", "caller_name": "...", "phone": "..." },
  "metadata": { "turn_count": 14, "has_booking": true, "phases_covered": ["greeting", ...] }
}

Turn Level (What the model actually trains on)

Each conversation is decomposed into multiple turn-level prompt/completion pairs. For each agent reply in a conversation, one training example is created:

  • Prompt: System prompt + all prior turns (user and assistant)
  • Completion: The single agent reply

This means a 14-turn conversation with 7 agent replies produces 7 training examples. The 170 conversations yield 922 training examples in the train split alone.

Why turn-level instead of whole-conversation? LLMs learn to predict the next token. One completion per example gives a cleaner learning signal. The opening greeting (agent speaks first) would otherwise violate the user→assistant turn order that Mistral and Gemma templates expect. More examples from the same data = stronger training signal without generating more conversations.

The Greeting Turn Problem

In phone calls, the agent speaks first. But training templates expect user→assistant ordering. We solve this with a synthetic [phone rings] user turn before the greeting, so every training example follows the user→assistant pattern.

Scenario Matrix (3 Axes)

Each conversation is defined by a combination of three independent axes:

Problem Types (8)

ProblemUrgencyNotes
Clogged drainNormalMost common call
Leak repairHighWater damage risk
Water heaterNormalNo hot water, pilot light
Toilet repairNormalRunning, clogged, leaking
Faucet installLowNew or replacement
Sewer lineHighMain line backup
Garbage disposalLowJammed or leaking
EmergencyEmergencyBurst pipe, gas smell, flooding

Caller Personalities (7)

PersonalityBehavior Pattern
DefaultCooperative, clear communication
RushedTerse answers, wants fastest slot
ChattyTangential stories, slow to the point
ConfusedVague descriptions, needs help identifying the problem
AngryFrustrated, complains, needs to be heard
ElderlySlow, repeats, appreciates patience
Non-native EnglishWrong terms ("water hot machine"), simpler grammar

Conversation Paths (8)

PathEnds with booking?Description
Happy pathYesSmooth booking
No slotsYes (usually)Preferred time unavailable, agent offers alternatives
CancellationNoCaller cancels existing appointment
Question onlyNoJust asking about pricing/services
Emergency escalationYesUrgent — safety first, fast-track dispatch
Caller hangupNoCall ends before completion
DIY recommendationNo (usually)Simple fix the agent can explain
Changes mindYesInitially declines, then reconsiders

Full matrix: 8 x 7 x 8 = 448 possible combinations. We sample ~170 to cover the most important scenarios, weighted toward difficult cases.

Splitting Strategy

Why Not 50/50

A 50/50 train/test split wastes training signal. LoRA fine-tuning updates only ~1.5% of model parameters and works well with 500–1000 examples. The test set needs to be large enough to measure quality reliably across all phases, but no larger.

Stratified Split (75 / 12.5 / 12.5)

SetConversationsTurn-level examplesPurpose
Train128 (75%)922LoRA fine-tuning — the model learns from these
Val21 (12.5%)159Overfitting detection during training
Test21 (12.5%)145Final tournament evaluation — never seen during training

Stratification: The split is not random. Conversations are grouped by path (the most behaviorally distinct axis), and each path is proportionally distributed across all three sets. This guarantees:

  • Every conversation path appears in train, val, AND test
  • All 8 problem types are covered in each set
  • All 7 personalities are covered in each set

Why stratify by path? A random split with 170 conversations can easily leave caller_hangup (13 total) entirely absent from the test set. Path determines the conversation structure more than problem or personality — a happy_path booking and a caller_hangup are fundamentally different evaluation targets.

What Each Set Proves

SetQuestion It Answers
Train"Can the model learn to imitate these conversations?"
Val"Is it memorizing or generalizing?" (overfitting check)
Test"How good is it on conversations it's never seen?"

The val set is checked automatically during training. If val loss diverges from train loss after epoch 2, the model is overfitting — reduce LoRA rank from 8 to 4, or increase dropout.

The test set is used ONLY during tournament evaluation. It's the "exam" — touching it during training would invalidate the results.

Phase Balancing

The dataset is intentionally weighted to cover all 11 canonical phases, with targeted generation for underrepresented phases:

PhaseBefore BalancingAfter BalancingTarget
greeting100128≥50
problem_determination182232≥50
solution_proposal9098≥50
time_preference7779≥50
match_propose2152≥50 ✓
confirmation5776≥50
summarize_book2350≥50 ✓
goodbye98127≥50
emergency_escalation3440≥30 ✓
cancellation1727≥20 ✓
caller_hangup513≥10 ✓

Bold rows are the phases that were specifically targeted for additional generation.

Why these targets? The model needs enough examples of each phase to learn the distinct behavior pattern. Greeting and goodbye are structurally simple — the model picks them up fast. Match & propose, emergency escalation, and cancellation require nuanced multi-turn behavior that needs more examples.

Validation Pipeline

Every conversation passes through two validation stages:

Structural Validation (hard gates — must pass)

  • All required fields present (id, scenario, turns, metadata)
  • First turn is agent (agent answers the phone)
  • Minimum turn count: 6 for bookings, 4 for non-bookings
  • No forbidden AI patterns ("As an AI", "as a language model")
  • All phase labels are canonical (after normalization)

Business Validation (soft warnings — flagged for review)

  • Reply length: Agent turns must be ≤50 words (it's a phone call)
  • Emergency triage: Emergency calls must reach escalation within 6 turns
  • Confirmation completeness: Booking confirmations must use the caller's name
  • Booking details: All booking fields (service, date, time, name, phone) must be present
  • DIY safety: DIY recommendations for dangerous problems (sewer, water heater, emergency) must also recommend a professional
  • Pricing policy: Agent-quoted prices must be within reasonable range
  • No premature promises: Agent shouldn't promise specific availability before checking calendar
170
Conversations
0
Structural errors
37
Business warnings

37 business warnings are mostly incomplete booking_details in edge-case conversations.

File Structure

Data Directory Layout
data/
├── scenario_matrix.json              # The 8x7x8 scenario definitions
├── conversations/
│   ├── batch_01.json                 # Original generation (20 conversations)
│   ├── batch_02.json                 # ...
│   ├── batch_03.json
│   ├── batch_04.json
│   ├── batch_05.json
│   ├── batch_06_hard.json            # Hard cases: hangups, cancellations, angry callers
│   ├── batch_07_hard.json            # Contrastive examples: corrections, interruptions
│   ├── batch_08_match_propose.json   # Phase-targeted: slot negotiation
│   ├── batch_09_hangup.json          # Phase-targeted: caller exits
│   ├── batch_10_cancellation.json    # Phase-targeted: retention and cancel
│   ├── batch_11_summarize.json       # Phase-targeted: booking read-back
│   └── batch_12_emergency.json       # Phase-targeted: safety triage
├── all_conversations.json            # Merged raw (before normalization)
├── all_conversations_normalized.json # Merged with canonical phase labels
└── splits/
    ├── train.json / train.jsonl      # 128 conversations
    ├── val.json / val.jsonl          # 21 conversations
    ├── test.json / test.jsonl        # 21 conversations
    ├── train_mlx.jsonl               # 922 turn-level examples (MLX format)
    ├── val_mlx.jsonl                 # 159 turn-level examples
    └── test_mlx.jsonl                # 145 turn-level examples

Scripts

ScriptPurpose
scripts/normalize_and_split.pyPhase normalization, structural + business validation, stratified splitting
scripts/convert_to_chat_templates.pyTurn-level decomposition, MLX/Phi4/Mistral/Gemma format conversion

Regeneration

To regenerate or extend the dataset:

  1. Add new conversation files to data/conversations/batch_NN_description.json
  2. Run python3 scripts/normalize_and_split.py — normalizes phases, validates, creates stratified splits
  3. Run python3 scripts/convert_to_chat_templates.py -i data/splits/train.json -f mlx -o data/splits/train_mlx.jsonl (repeat for val/test)
  4. Check phase distribution — if any phase has <30 training examples, generate more targeted conversations

The pipeline is designed to be additive. New conversations are added as new batch files; the normalizer and splitter always re-process everything from scratch.