Most failed fine-tuning projects come down to one sentence: the data was bad. The model learns exactly what you show it, including the mistakes and contradictions. This guide walks you through building a dataset worth the training time.
Sources of examples
- Real logs: support conversations, help tickets, emails (after anonymization and legal approval).
- Domain experts: hand-write 100–300 top-quality examples; they are the most valuable data you have.
- Generated data: use a strong model to produce synthetic examples from your documentation, then review them by hand. Check the model provider's terms of use before using its outputs to train another model.
Data formats
| Format | Shape | Use |
|---|---|---|
| Instruction | instruction / input / output | Single-turn tasks |
| Chat (messages) | A list of role and content | Best for conversation; supported by most tools |
| Preference | prompt / chosen / rejected | For DPO and preference tuning |
{"prompt": "Explain RAG in two sentences.",
"chosen": "RAG retrieves information from an external source and gives it to the model before it answers, so answers are more accurate and the knowledge stays updatable.",
"rejected": "RAG is a type of fabric."}Cleaning steps
- Deduplication: verbatim or near-verbatim repeats push the model toward memorization.
- Filtering: drop empty or very short examples, incomplete answers, and encoding errors.
- Style consistency: if the tone of answers varies across examples, the model will learn to be erratic.
- Personal data protection: mask names, emails, phone numbers, and account numbers.
- Human review of a sample: read at least 100 random examples. You will find problems that scripts miss.
import json, re, hashlib
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
PHONE = re.compile(r"\+?\d[\d\s-]{7,}\d")
def scrub(text: str) -> str:
return PHONE.sub("[PHONE]", EMAIL.sub("[EMAIL]", text))
seen, clean = set(), []
with open("raw.jsonl", encoding="utf-8") as f:
for line in f:
row = json.loads(line)
answer = row["messages"][-1]["content"].strip()
if len(answer) < 20:
continue
key = hashlib.sha1(answer.encode()).hexdigest()
if key in seen:
continue
seen.add(key)
for m in row["messages"]:
m["content"] = scrub(m["content"])
clean.append(row)
print(f"kept {len(clean)} examples")Chat Templates
Each model family encodes roles (system / user / assistant) in its own way using special tokens. Always use the model's own template via tokenizer.apply_chat_template instead of building the text by hand. Any mismatch between the training template and the inference template lowers quality without raising an obvious error.
Train, validation, and test splits
- Training ~90% and validation ~5–10% to monitor overfitting.
- A small, separate, hand-written final test set that is never used for tuning settings.
- Split by source or conversation, not by random lines, so parts of the same conversation do not leak into both sets.
FAQ
Can I use my customers' data for training?
Only if your privacy policy and legal agreements allow it. We recommend masking personal data before training and obtaining clear legal consent.
Your data is ready but you have no time to prepare it?
We turn your files and customer conversations into a smart assistant that speaks in your voice, and we handle the cleaning and preparation for you. Book a free consultation and send us a sample.