# Dataset Formats

## SFT Format (Supervised Fine-Tuning)

Standard chat-completion JSONL. Each line: JSON object with `messages` array.

```jsonl
{"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is 2+2?"}, {"role": "assistant", "content": "4"}]}
```

**Rules:**
- Each line must be valid JSON
- `messages` must contain at least one `user` and one `assistant` message
- `system` message is optional but recommended
- Multi-turn supported: alternate `user`/`assistant`
- Last message must be `assistant` (that's what the model learns)

**Validation checklist:** `.jsonl` extension, valid JSON per line, every example has `messages`, every message has `role` and `content`, no empty `content`.

## DPO Format (Direct Preference Optimization)

Three top-level fields: `input`, `preferred_output`, `non_preferred_output`.

```jsonl
{"input": {"messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Explain gravity."}]}, "preferred_output": [{"role": "assistant", "content": "Gravity is a fundamental force that attracts objects with mass toward each other."}], "non_preferred_output": [{"role": "assistant", "content": "Gravity is when stuff falls down."}]}
```

**Rules:**
- `input`: Object with `messages` array (system + user turns). May include `tools` and `parallel_tool_calls`.
- `preferred_output` / `non_preferred_output`: Array of messages (`assistant` or `tool` role only)
- Both must contain at least one `assistant` message
- Exactly two completions compared per example

**DPO REST API example:**
```json
{
  "model": "gpt-4.1-mini-2025-04-14",
  "training_file": "file-abc123",
  "method": {
    "type": "dpo",
    "dpo": { "beta": 0.1, "l2_multiplier": 0.1 }
  }
}
```

## RFT Format (Reinforcement Fine-Tuning)

Chat-completion format with key differences from SFT:

```jsonl
{"messages": [{"role": "user", "content": "Write a Python function to reverse a string."}], "reference_code": "def reverse_string(s):\n    return s[::-1]", "expected_output": "olleh"}
```

**Rules:**
- Last message **MUST** be `user` role (model generates its own response)
- Extra fields alongside `messages` are accessible to grader via `item.*`
- Both training and validation datasets are **required**
- ⚠️ Do NOT put `assistant` as last message — unlike SFT, RFT generates its own outputs

**API version**: Python graders require `api-version=2025-04-01-preview` or later.

**Grader types:** `string_check` (exact match), `text_similarity` (fuzzy/BLEU/ROUGE), `python` (custom function), `score_model` (LLM judge), `multi` (weighted combination).

**Python grader template:**
```python
def grade(sample, item):
    """
    sample: dict with 'output_text' (model's generation)
    item: dict with extra fields from JSONL
    Returns: float 0.0–1.0
    """
    output = sample.get("output_text", "")
    reference = item.get("reference_code", "")
    return score
```

**Python grader constraints:** 256KB code max, no network, 2GB memory, 1GB disk, 2min timeout.

**Grader field access:**
- `sample.output_text` → model's generation
- `sample.output_json` → structured output (if using response_format)
- `item.*` → extra JSONL fields
- Template variables: `{{item.field_name}}` — no spaces inside braces, no array indexing

## Converting Between Formats

- **SFT → RFT**: Strip assistant messages (RFT last message must be `user`), add grader reference fields. Use `scripts/convert_dataset.py --format rft`.
- **SFT → DPO**: Generate rejected responses (run base model on same prompts, intentionally degrade good outputs, or use human ranking).
- **DPO → SFT**: Extract chosen responses from the preferred output.
